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