1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /*
8  *
9  * For each import tree there is one master document (the root) and one
10  * import manager. The import manager is a map of URI ImportLoader pairs.
11  * An ImportLoader is responsible for loading an import document from a
12  * given location, and sending out load or error events to all the link
13  * nodes that refer to it when it's done. For loading it opens up a
14  * channel, using the same CSP as the master document. It then creates a
15  * blank document, and starts parsing the data from the channel. When
16  * there is no more data on the channel we wait for the DOMContentLoaded
17  * event from the parsed document. For the duration of the loading
18  * process the scripts on the parent documents are blocked. When an error
19  * occurs, or the DOMContentLoaded event is received, the scripts on the
20  * parent document are unblocked and we emit the corresponding event on
21  * all the referrer link nodes. If a new link node is added to one of the
22  * DOM trees in the import tree that refers to an import that was already
23  * loaded, the already existing ImportLoader is being used (without
24  * loading the referred import document twice) and if necessary the
25  * load/error is emitted on it immediately.
26  *
27  * Ownership model:
28  *
29  * ImportDocument ----------------------------
30  *  ^                                        |
31  *  |                                        v
32  *  MasterDocument <- ImportManager <-ImportLoader
33  *  ^                                        ^
34  *  |                                        |
35  *  LinkElement <-----------------------------
36  *
37  */
38 
39 #ifndef mozilla_dom_ImportManager_h__
40 #define mozilla_dom_ImportManager_h__
41 
42 #include "nsTArray.h"
43 #include "nsCycleCollectionParticipant.h"
44 #include "nsIDOMEventListener.h"
45 #include "nsIStreamListener.h"
46 #include "nsIWeakReferenceUtils.h"
47 #include "nsRefPtrHashtable.h"
48 #include "nsScriptLoader.h"
49 #include "nsURIHashKey.h"
50 
51 class nsIDocument;
52 class nsIPrincipal;
53 class nsINode;
54 class AutoError;
55 
56 namespace mozilla {
57 namespace dom {
58 
59 class ImportManager;
60 
61 typedef nsTHashtable<nsPtrHashKey<nsINode>> NodeTable;
62 
63 class ImportLoader final : public nsIStreamListener
64                          , public nsIDOMEventListener
65 {
66 
67   // A helper inner class to decouple the logic of updating the import graph
68   // after a new import link has been found by one of the parsers.
69   class Updater {
70 
71   public:
Updater(ImportLoader * aLoader)72     explicit Updater(ImportLoader* aLoader) : mLoader(aLoader)
73     {}
74 
75     // After a new link is added that refers to this import, we
76     // have to update the spanning tree, since given this new link the
77     // priority of this import might be higher in the scripts
78     // execution order than before. It updates mMainReferrer, mImportParent,
79     // the corresponding pending ScriptRunners, etc.
80     // It also handles updating additional dependant loaders via the
81     // UpdateDependants calls.
82     // (NOTE: See GetMainReferrer about spanning tree.)
83     void UpdateSpanningTree(nsINode* aNode);
84 
85   private:
86     // Returns an array of links that forms a referring chain from
87     // the master document to this import. Each link in the array
88     // is marked as main referrer in the list.
89     void GetReferrerChain(nsINode* aNode, nsTArray<nsINode*>& aResult);
90 
91     // Once we find a new referrer path to our import, we have to see if
92     // it changes the load order hence we have to do an update on the graph.
93     bool ShouldUpdate(nsTArray<nsINode*>& aNewPath);
94     void UpdateMainReferrer(uint32_t newIdx);
95 
96     // It's a depth first graph traversal algorithm, for UpdateDependants. The
97     // nodes in the graph are the import link elements, and there is a directed
98     // edge from link1 to link2 if link2 is a subimport in the import document
99     // of link1.
100     // If the ImportLoader that aCurrentLink points to didn't need to be updated
101     // the algorithm skips its "children" (subimports). Note, that this graph can
102     // also contain cycles, aVisistedLinks is used to track the already visited
103     // links to avoid an infinite loop.
104     // aPath - (in/out) the referrer link chain of aCurrentLink when called, and
105     //                  of the next link when the function returns
106     // aVisitedLinks - (in/out) list of links that the traversal already visited
107     //                          (to handle cycles in the graph)
108     // aSkipChildren - when aCurrentLink points to an import that did not need
109     //                 to be updated, we can skip its sub-imports ('children')
110     nsINode* NextDependant(nsINode* aCurrentLink,
111                            nsTArray<nsINode*>& aPath,
112                            NodeTable& aVisitedLinks, bool aSkipChildren);
113 
114     // When we find a new link that changes the load order of the known imports,
115     // we also have to check all the subimports of it, to see if they need an
116     // update too. (see test_imports_nested_2.html)
117     void UpdateDependants(nsINode* aNode, nsTArray<nsINode*>& aPath);
118 
119     ImportLoader* mLoader;
120   };
121 
122   friend class ::AutoError;
123   friend class ImportManager;
124   friend class Updater;
125 
126 public:
127   ImportLoader(nsIURI* aURI, nsIDocument* aOriginDocument);
128 
129   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
130   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ImportLoader, nsIStreamListener)
131   NS_DECL_NSISTREAMLISTENER
132   NS_DECL_NSIREQUESTOBSERVER
133 
134   // We need to listen to DOMContentLoaded event to know when the document
135   // is fully leaded.
136   NS_IMETHOD HandleEvent(nsIDOMEvent *aEvent) override;
137 
138   // Validation then opening and starting up the channel.
139   void Open();
140   void AddLinkElement(nsINode* aNode);
141   void RemoveLinkElement(nsINode* aNode);
IsReady()142   bool IsReady() { return mReady; }
IsStopped()143   bool IsStopped() { return mStopped; }
IsBlocking()144   bool IsBlocking() { return mBlockingScripts; }
145 
Manager()146   ImportManager* Manager() {
147     MOZ_ASSERT(mDocument || mImportParent, "One of them should be always set");
148     return (mDocument ? mDocument : mImportParent)->ImportManager();
149   }
150 
151   // Simply getter for the import document. Can return a partially parsed
152   // document if called too early.
GetDocument()153   nsIDocument* GetDocument()
154   {
155     return mDocument;
156   }
157 
158   // Getter for the import document that is used in the spec. Returns
159   // nullptr if the import is not yet ready.
GetImport()160   nsIDocument* GetImport()
161   {
162     if (!mReady) {
163       return nullptr;
164     }
165     return mDocument;
166   }
167 
168   // There is only one referring link that is marked as primary link per
169   // imports. This is the one that has to be taken into account when
170   // scrip execution order is determined. Links marked as primary link form
171   // a spanning tree in the import graph. (Eliminating the cycles and
172   // multiple parents.) This spanning tree is recalculated every time
173   // a new import link is added to the manager.
GetMainReferrer()174   nsINode* GetMainReferrer()
175   {
176     if (mLinks.IsEmpty()) {
177       return nullptr;
178     }
179     return mLinks[mMainReferrer];
180   }
181 
182   // An import is not only blocked by its import children, but also
183   // by its predecessors. It's enough to find the closest predecessor
184   // and wait for that to run its scripts. We keep track of all the
185   // ScriptRunners that are waiting for this import. NOTE: updating
186   // the main referrer might change this list.
187   void AddBlockedScriptLoader(nsScriptLoader* aScriptLoader);
188   bool RemoveBlockedScriptLoader(nsScriptLoader* aScriptLoader);
189   void SetBlockingPredecessor(ImportLoader* aLoader);
190 
191 private:
~ImportLoader()192   ~ImportLoader() {}
193 
194   // If a new referrer LinkElement was added, let's
195   // see if we are already finished and if so fire
196   // the right event.
197   void DispatchEventIfFinished(nsINode* aNode);
198 
199   // Dispatch event for a single referrer LinkElement.
200   void DispatchErrorEvent(nsINode* aNode);
201   void DispatchLoadEvent(nsINode* aNode);
202 
203   // Must be called when an error has occured during load.
204   void Error(bool aUnblockScripts);
205 
206   // Must be called when the import document has been loaded successfully.
207   void Done();
208 
209   // When the reading from the channel and the parsing
210   // of the document is done, we can release the resources
211   // that we don't need any longer to hold on.
212   void ReleaseResources();
213 
214   // While the document is being loaded we must block scripts
215   // on the import parent document.
216   void BlockScripts();
217   void UnblockScripts();
218 
219   nsIPrincipal* Principal();
220 
221   nsCOMPtr<nsIDocument> mDocument;
222   nsCOMPtr<nsIURI> mURI;
223   nsCOMPtr<nsIStreamListener> mParserStreamListener;
224   nsCOMPtr<nsIDocument> mImportParent;
225   ImportLoader* mBlockingPredecessor;
226 
227   // List of the LinkElements that are referring to this import
228   // we need to keep track of them so we can fire event on them.
229   nsTArray<nsCOMPtr<nsINode>> mLinks;
230 
231   // List of pending ScriptLoaders that are waiting for this import
232   // to finish.
233   nsTArray<RefPtr<nsScriptLoader>> mBlockedScriptLoaders;
234 
235   // There is always exactly one referrer link that is flagged as
236   // the main referrer the primary link. This is the one that is
237   // used in the script execution order calculation.
238   // ("Branch" according to the spec.)
239   uint32_t mMainReferrer;
240   bool mReady;
241   bool mStopped;
242   bool mBlockingScripts;
243   Updater mUpdater;
244 };
245 
246 class ImportManager final : public nsISupports
247 {
248   typedef nsRefPtrHashtable<nsURIHashKey, ImportLoader> ImportMap;
249 
~ImportManager()250   ~ImportManager() {}
251 
252 public:
ImportManager()253   ImportManager() {}
254 
255   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
256   NS_DECL_CYCLE_COLLECTION_CLASS(ImportManager)
257 
258   // Finds the ImportLoader that belongs to aImport in the map.
259   ImportLoader* Find(nsIDocument* aImport);
260 
261   // Find the ImportLoader aLink refers to.
262   ImportLoader* Find(nsINode* aLink);
263 
264   void AddLoaderWithNewURI(ImportLoader* aLoader, nsIURI* aNewURI);
265 
266   // When a new import link is added, this getter either creates
267   // a new ImportLoader for it, or returns an existing one if
268   // it was already created and in the import map.
269   already_AddRefed<ImportLoader> Get(nsIURI* aURI, nsINode* aNode,
270                                      nsIDocument* aOriginDocument);
271 
272   // It finds the predecessor for an import link node that runs its
273   // scripts the latest among its predecessors.
274   ImportLoader* GetNearestPredecessor(nsINode* aNode);
275 
276 private:
277   ImportMap mImports;
278 };
279 
280 } // namespace dom
281 } // namespace mozilla
282 
283 #endif // mozilla_dom_ImportManager_h__
284