summaryrefslogtreecommitdiff
path: root/dom/base/ImportManager.h
blob: ccc00125ae5853dbf1d8d8ed4add605d1f46bab3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 *
 * For each import tree there is one master document (the root) and one
 * import manager. The import manager is a map of URI ImportLoader pairs.
 * An ImportLoader is responsible for loading an import document from a
 * given location, and sending out load or error events to all the link
 * nodes that refer to it when it's done. For loading it opens up a
 * channel, using the same CSP as the master document. It then creates a
 * blank document, and starts parsing the data from the channel. When
 * there is no more data on the channel we wait for the DOMContentLoaded
 * event from the parsed document. For the duration of the loading
 * process the scripts on the parent documents are blocked. When an error
 * occurs, or the DOMContentLoaded event is received, the scripts on the
 * parent document are unblocked and we emit the corresponding event on
 * all the referrer link nodes. If a new link node is added to one of the
 * DOM trees in the import tree that refers to an import that was already
 * loaded, the already existing ImportLoader is being used (without
 * loading the referred import document twice) and if necessary the
 * load/error is emitted on it immediately.
 *
 * Ownership model:
 *
 * ImportDocument ----------------------------
 *  ^                                        |
 *  |                                        v
 *  MasterDocument <- ImportManager <-ImportLoader
 *  ^                                        ^
 *  |                                        |
 *  LinkElement <-----------------------------
 *
 */

#ifndef mozilla_dom_ImportManager_h__
#define mozilla_dom_ImportManager_h__

#include "nsTArray.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIDOMEventListener.h"
#include "nsIStreamListener.h"
#include "nsIWeakReferenceUtils.h"
#include "nsRefPtrHashtable.h"
#include "nsURIHashKey.h"
#include "mozilla/dom/ScriptLoader.h"

class nsIDocument;
class nsIPrincipal;
class nsINode;
class AutoError;

namespace mozilla {
namespace dom {

class ImportManager;

typedef nsTHashtable<nsPtrHashKey<nsINode>> NodeTable;

class ImportLoader final : public nsIStreamListener
                         , public nsIDOMEventListener
{

  // A helper inner class to decouple the logic of updating the import graph
  // after a new import link has been found by one of the parsers.
  class Updater {

  public:
    explicit Updater(ImportLoader* aLoader) : mLoader(aLoader)
    {}

    // After a new link is added that refers to this import, we
    // have to update the spanning tree, since given this new link the
    // priority of this import might be higher in the scripts
    // execution order than before. It updates mMainReferrer, mImportParent,
    // the corresponding pending ScriptRunners, etc.
    // It also handles updating additional dependant loaders via the
    // UpdateDependants calls.
    // (NOTE: See GetMainReferrer about spanning tree.)
    void UpdateSpanningTree(nsINode* aNode);

  private:
    // Returns an array of links that forms a referring chain from
    // the master document to this import. Each link in the array
    // is marked as main referrer in the list.
    void GetReferrerChain(nsINode* aNode, nsTArray<nsINode*>& aResult);

    // Once we find a new referrer path to our import, we have to see if
    // it changes the load order hence we have to do an update on the graph.
    bool ShouldUpdate(nsTArray<nsINode*>& aNewPath);
    void UpdateMainReferrer(uint32_t newIdx);

    // It's a depth first graph traversal algorithm, for UpdateDependants. The
    // nodes in the graph are the import link elements, and there is a directed
    // edge from link1 to link2 if link2 is a subimport in the import document
    // of link1.
    // If the ImportLoader that aCurrentLink points to didn't need to be updated
    // the algorithm skips its "children" (subimports). Note, that this graph can
    // also contain cycles, aVisistedLinks is used to track the already visited
    // links to avoid an infinite loop.
    // aPath - (in/out) the referrer link chain of aCurrentLink when called, and
    //                  of the next link when the function returns
    // aVisitedLinks - (in/out) list of links that the traversal already visited
    //                          (to handle cycles in the graph)
    // aSkipChildren - when aCurrentLink points to an import that did not need
    //                 to be updated, we can skip its sub-imports ('children')
    nsINode* NextDependant(nsINode* aCurrentLink,
                           nsTArray<nsINode*>& aPath,
                           NodeTable& aVisitedLinks, bool aSkipChildren);

    // When we find a new link that changes the load order of the known imports,
    // we also have to check all the subimports of it, to see if they need an
    // update too. (see test_imports_nested_2.html)
    void UpdateDependants(nsINode* aNode, nsTArray<nsINode*>& aPath);

    ImportLoader* mLoader;
  };

  friend class ::AutoError;
  friend class ImportManager;
  friend class Updater;

public:
  ImportLoader(nsIURI* aURI, nsIDocument* aOriginDocument);

  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ImportLoader, nsIStreamListener)
  NS_DECL_NSISTREAMLISTENER
  NS_DECL_NSIREQUESTOBSERVER

  // We need to listen to DOMContentLoaded event to know when the document
  // is fully leaded.
  NS_IMETHOD HandleEvent(nsIDOMEvent *aEvent) override;

  // Validation then opening and starting up the channel.
  void Open();
  void AddLinkElement(nsINode* aNode);
  void RemoveLinkElement(nsINode* aNode);
  bool IsReady() { return mReady; }
  bool IsStopped() { return mStopped; }
  bool IsBlocking() { return mBlockingScripts; }

  ImportManager* Manager() {
    MOZ_ASSERT(mDocument || mImportParent, "One of them should be always set");
    return (mDocument ? mDocument : mImportParent)->ImportManager();
  }

  // Simply getter for the import document. Can return a partially parsed
  // document if called too early.
  nsIDocument* GetDocument()
  {
    return mDocument;
  }

  // Getter for the import document that is used in the spec. Returns
  // nullptr if the import is not yet ready.
  nsIDocument* GetImport()
  {
    if (!mReady) {
      return nullptr;
    }
    return mDocument;
  }

  // There is only one referring link that is marked as primary link per
  // imports. This is the one that has to be taken into account when
  // scrip execution order is determined. Links marked as primary link form
  // a spanning tree in the import graph. (Eliminating the cycles and
  // multiple parents.) This spanning tree is recalculated every time
  // a new import link is added to the manager.
  nsINode* GetMainReferrer()
  {
    if (mLinks.IsEmpty()) {
      return nullptr;
    }
    return mLinks[mMainReferrer];
  }

  // An import is not only blocked by its import children, but also
  // by its predecessors. It's enough to find the closest predecessor
  // and wait for that to run its scripts. We keep track of all the
  // ScriptRunners that are waiting for this import. NOTE: updating
  // the main referrer might change this list.
  void AddBlockedScriptLoader(ScriptLoader* aScriptLoader);
  bool RemoveBlockedScriptLoader(ScriptLoader* aScriptLoader);
  void SetBlockingPredecessor(ImportLoader* aLoader);

private:
  ~ImportLoader() {}

  // If a new referrer LinkElement was added, let's
  // see if we are already finished and if so fire
  // the right event.
  void DispatchEventIfFinished(nsINode* aNode);

  // Dispatch event for a single referrer LinkElement.
  void DispatchErrorEvent(nsINode* aNode);
  void DispatchLoadEvent(nsINode* aNode);

  // Must be called when an error has occured during load.
  void Error(bool aUnblockScripts);

  // Must be called when the import document has been loaded successfully.
  void Done();

  // When the reading from the channel and the parsing
  // of the document is done, we can release the resources
  // that we don't need any longer to hold on.
  void ReleaseResources();

  // While the document is being loaded we must block scripts
  // on the import parent document.
  void BlockScripts();
  void UnblockScripts();

  nsIPrincipal* Principal();

  nsCOMPtr<nsIDocument> mDocument;
  nsCOMPtr<nsIURI> mURI;
  nsCOMPtr<nsIStreamListener> mParserStreamListener;
  nsCOMPtr<nsIDocument> mImportParent;
  ImportLoader* mBlockingPredecessor;

  // List of the LinkElements that are referring to this import
  // we need to keep track of them so we can fire event on them.
  nsTArray<nsCOMPtr<nsINode>> mLinks;

  // List of pending ScriptLoaders that are waiting for this import
  // to finish.
  nsTArray<RefPtr<ScriptLoader>> mBlockedScriptLoaders;

  // There is always exactly one referrer link that is flagged as
  // the main referrer the primary link. This is the one that is
  // used in the script execution order calculation.
  // ("Branch" according to the spec.)
  uint32_t mMainReferrer;
  bool mReady;
  bool mStopped;
  bool mBlockingScripts;
  Updater mUpdater;
};

class ImportManager final : public nsISupports
{
  typedef nsRefPtrHashtable<nsURIHashKey, ImportLoader> ImportMap;

  ~ImportManager() {}

public:
  ImportManager() {}

  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS(ImportManager)

  // Finds the ImportLoader that belongs to aImport in the map.
  ImportLoader* Find(nsIDocument* aImport);

  // Find the ImportLoader aLink refers to.
  ImportLoader* Find(nsINode* aLink);

  void AddLoaderWithNewURI(ImportLoader* aLoader, nsIURI* aNewURI);

  // When a new import link is added, this getter either creates
  // a new ImportLoader for it, or returns an existing one if
  // it was already created and in the import map.
  already_AddRefed<ImportLoader> Get(nsIURI* aURI, nsINode* aNode,
                                     nsIDocument* aOriginDocument);

  // It finds the predecessor for an import link node that runs its
  // scripts the latest among its predecessors.
  ImportLoader* GetNearestPredecessor(nsINode* aNode);

private:
  ImportMap mImports;
};

} // namespace dom
} // namespace mozilla

#endif // mozilla_dom_ImportManager_h__