diff --git a/content/base/public/nsIDocument.h b/content/base/public/nsIDocument.h index 4d04291b1169..5caba4352a11 100644 --- a/content/base/public/nsIDocument.h +++ b/content/base/public/nsIDocument.h @@ -134,8 +134,8 @@ typedef CallbackObjectHolder NodeFilterHolder; } // namespace mozilla #define NS_IDOCUMENT_IID \ -{ 0x613ea294, 0x0288, 0x48b4, \ - { 0x9e, 0x7b, 0x0f, 0xe9, 0x3f, 0x8c, 0xf8, 0x95 } } +{ 0x42a263db, 0x6ac6, 0x40ff, \ + { 0x89, 0xe2, 0x25, 0x12, 0xe4, 0xbc, 0x2d, 0x2d } } // Enum for requesting a particular type of document when creating a doc enum DocumentFlavor { @@ -2362,10 +2362,15 @@ public: // Each import tree has exactly one master document which is // the root of the tree, and owns the browser context. - virtual already_AddRefed MasterDocument() = 0; + virtual nsIDocument* MasterDocument() = 0; virtual void SetMasterDocument(nsIDocument* master) = 0; virtual bool IsMasterDocument() = 0; - virtual already_AddRefed ImportManager() = 0; + virtual mozilla::dom::ImportManager* ImportManager() = 0; + // We keep track of the order of sub imports were added to the document. + virtual bool HasSubImportLink(nsINode* aLink) = 0; + virtual uint32_t IndexOfSubImportLink(nsINode* aLink) = 0; + virtual void AddSubImportLink(nsINode* aLink) = 0; + virtual nsINode* GetSubImportLink(uint32_t aIdx) = 0; /* * Given a node, get a weak reference to it and append that reference to diff --git a/content/base/src/ImportManager.cpp b/content/base/src/ImportManager.cpp index a539b3dd27f7..7af448d4b67c 100644 --- a/content/base/src/ImportManager.cpp +++ b/content/base/src/ImportManager.cpp @@ -23,6 +23,10 @@ #include "nsScriptLoader.h" #include "nsNetUtil.h" +//----------------------------------------------------------------------------- +// AutoError +//----------------------------------------------------------------------------- + class AutoError { public: explicit AutoError(mozilla::dom::ImportLoader* loader, bool scriptsBlocked = true) @@ -49,6 +53,220 @@ private: namespace mozilla { namespace dom { +//----------------------------------------------------------------------------- +// ImportLoader::Updater +//----------------------------------------------------------------------------- + +void +ImportLoader::Updater::GetReferrerChain(nsINode* aNode, + nsTArray& aResult) +{ + // We fill up the array backward. First the last link: aNode. + MOZ_ASSERT(mLoader->mLinks.Contains(aNode)); + + aResult.AppendElement(aNode); + nsINode* node = aNode; + nsRefPtr manager = mLoader->Manager(); + for (ImportLoader* referrersLoader = manager->Find(node->OwnerDoc()); + referrersLoader; + referrersLoader = manager->Find(node->OwnerDoc())) + { + // Then walking up the main referrer chain and append each link + // to the array. + node = referrersLoader->GetMainReferrer(); + MOZ_ASSERT(node); + aResult.AppendElement(node); + } + + // The reversed order is more useful for consumers. + // XXX: This should probably go to nsTArray or some generic utility + // lib for our containers that we don't have... I would really like to + // get rid of this part... + uint32_t l = aResult.Length(); + for (uint32_t i = 0; i < l / 2; i++) { + Swap(aResult[i], aResult[l - i - 1]); + } +} + +bool +ImportLoader::Updater::ShouldUpdate(nsTArray& aNewPath) +{ + // Let's walk down on the main referrer chains of both the current main and + // the new link, and find the last pair of links that are from the same + // document. This is the junction point between the two referrer chain. Their + // order in the subimport list of that document will determine if we have to + // update the spanning tree or this new edge changes nothing in the script + // execution order. + nsTArray oldPath; + GetReferrerChain(mLoader->mLinks[mLoader->mMainReferrer], oldPath); + uint32_t max = std::min(oldPath.Length(), aNewPath.Length()); + MOZ_ASSERT(max > 0); + uint32_t lastCommonImportAncestor = 0; + + for (uint32_t i = 0; + i < max && oldPath[i]->OwnerDoc() == aNewPath[i]->OwnerDoc(); + i++) + { + lastCommonImportAncestor = i; + } + + MOZ_ASSERT(lastCommonImportAncestor < max); + nsINode* oldLink = oldPath[lastCommonImportAncestor]; + nsINode* newLink = aNewPath[lastCommonImportAncestor]; + + if ((lastCommonImportAncestor == max - 1) && + newLink == oldLink ) { + // If one chain contains the other entirely, then this is a simple cycle, + // nothing to be done here. + MOZ_ASSERT(oldPath.Length() != aNewPath.Length(), + "This would mean that new link == main referrer link"); + return false; + } + + MOZ_ASSERT(aNewPath != oldPath, + "How could this happen?"); + nsIDocument* doc = oldLink->OwnerDoc(); + MOZ_ASSERT(doc->HasSubImportLink(newLink)); + MOZ_ASSERT(doc->HasSubImportLink(oldLink)); + + return doc->IndexOfSubImportLink(newLink) < doc->IndexOfSubImportLink(oldLink); +} + +void +ImportLoader::Updater::UpdateMainReferrer(uint32_t aNewIdx) +{ + MOZ_ASSERT(aNewIdx < mLoader->mLinks.Length()); + nsINode* newMainReferrer = mLoader->mLinks[aNewIdx]; + + // This new link means we have to execute our scripts sooner... + // Let's make sure that unblocking a loader does not trigger a script execution. + // So we start with placing the new blockers and only then will we remove any + // blockers. + if (mLoader->IsBlocking()) { + // Our import parent is changed, let's block the new one and later unblock + // the old one. + newMainReferrer->OwnerDoc()->ScriptLoader()->AddExecuteBlocker(); + } + + if (mLoader->mDocument) { + // Our nearest predecessor has changed. So let's add the ScriptLoader to the + // new one if there is any. And remove it from the old one. + nsRefPtr manager = mLoader->Manager(); + nsScriptLoader* loader = mLoader->mDocument->ScriptLoader(); + ImportLoader*& pred = mLoader->mBlockingPredecessor; + ImportLoader* newPred = manager->GetNearestPredecessor(newMainReferrer); + if (pred) { + if (newPred) { + newPred->AddBlockedScriptLoader(loader); + } + pred->RemoveBlockedScriptLoader(loader); + } + } + + if (mLoader->IsBlocking()) { + mLoader->mImportParent->ScriptLoader()->RemoveExecuteBlocker(); + } + + // Finally update mMainReferrer to point to the newly added link. + mLoader->mMainReferrer = aNewIdx; + mLoader->mImportParent = newMainReferrer->OwnerDoc(); +} + +nsINode* +ImportLoader::Updater::NextDependant(nsINode* aCurrentLink, + nsTArray& aPath, + NodeTable& aVisitedNodes, bool aSkipChildren) +{ + // Depth first graph traversal. + if (!aSkipChildren) { + // "first child" + ImportLoader* loader = mLoader->Manager()->Find(aCurrentLink); + if (loader && loader->GetDocument()) { + nsINode* firstSubImport = loader->GetDocument()->GetSubImportLink(0); + if (firstSubImport && !aVisitedNodes.Contains(firstSubImport)) { + aPath.AppendElement(aCurrentLink); + aVisitedNodes.PutEntry(firstSubImport); + return firstSubImport; + } + } + } + + aPath.AppendElement(aCurrentLink); + // "(parent's) next sibling" + while(aPath.Length() > 1) { + aCurrentLink = aPath[aPath.Length() - 1]; + aPath.RemoveElementAt(aPath.Length() - 1); + + // Let's find the next "sibling" + ImportLoader* loader = mLoader->Manager()->Find(aCurrentLink->OwnerDoc()); + MOZ_ASSERT(loader && loader->GetDocument(), "How can this happend?"); + nsIDocument* doc = loader->GetDocument(); + MOZ_ASSERT(doc->HasSubImportLink(aCurrentLink)); + uint32_t idx = doc->IndexOfSubImportLink(aCurrentLink); + nsINode* next = doc->GetSubImportLink(idx + 1); + if (next) { + // Note: If we found an already visited link that means the parent links has + // closed the circle it's always the "first child" section that should find + // the first already visited node. Let's just assert that. + MOZ_ASSERT(!aVisitedNodes.Contains(next)); + aVisitedNodes.PutEntry(next); + return next; + } + } + + return nullptr; +} + +void +ImportLoader::Updater::UpdateDependants(nsINode* aNode, + nsTArray& aPath) +{ + NodeTable visitedNodes; + nsINode* current = aNode; + uint32_t initialLength = aPath.Length(); + bool neededUpdate = true; + while ((current = NextDependant(current, aPath, visitedNodes, !neededUpdate))) { + if (!current || aPath.Length() <= initialLength) { + break; + } + ImportLoader* loader = mLoader->Manager()->Find(current); + if (!loader) { + continue; + } + Updater& updater = loader->mUpdater; + neededUpdate = updater.ShouldUpdate(aPath); + if (neededUpdate) { + updater.UpdateMainReferrer(loader->mLinks.IndexOf(current)); + } + } +} + +void +ImportLoader::Updater::UpdateSpanningTree(nsINode* aNode) +{ + if (mLoader->mReady || mLoader->mStopped) { + // Scripts already executed, nothing to be done here. + return; + } + + if (mLoader->mLinks.Length() == 1) { + // If this is the first referrer, let's mark it. + mLoader->mMainReferrer = 0; + return; + } + + nsTArray newReferrerChain; + GetReferrerChain(aNode, newReferrerChain); + if (ShouldUpdate(newReferrerChain)) { + UpdateMainReferrer(mLoader->mLinks.Length() - 1); + UpdateDependants(aNode, newReferrerChain); + } +} + +//----------------------------------------------------------------------------- +// ImportLoader +//----------------------------------------------------------------------------- + NS_INTERFACE_MAP_BEGIN(ImportLoader) NS_INTERFACE_MAP_ENTRY(nsIStreamListener) NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) @@ -66,10 +284,13 @@ NS_IMPL_CYCLE_COLLECTION(ImportLoader, ImportLoader::ImportLoader(nsIURI* aURI, nsIDocument* aImportParent) : mURI(aURI) , mImportParent(aImportParent) + , mBlockingPredecessor(nullptr) , mReady(false) , mStopped(false) , mBlockingScripts(false) -{} + , mUpdater(MOZ_THIS_IN_INITIALIZER_LIST()) +{ +} void ImportLoader::BlockScripts() @@ -84,9 +305,19 @@ ImportLoader::UnblockScripts() { MOZ_ASSERT(mBlockingScripts); mImportParent->ScriptLoader()->RemoveExecuteBlocker(); + for (uint32_t i = 0; i < mBlockedScriptLoaders.Length(); i++) { + mBlockedScriptLoaders[i]->RemoveExecuteBlocker(); + } + mBlockedScriptLoaders.Clear(); mBlockingScripts = false; } +void +ImportLoader::SetBlockingPredecessor(ImportLoader* aLoader) +{ + mBlockingPredecessor = aLoader; +} + void ImportLoader::DispatchEventIfFinished(nsINode* aNode) { @@ -99,6 +330,26 @@ ImportLoader::DispatchEventIfFinished(nsINode* aNode) } } +void +ImportLoader::AddBlockedScriptLoader(nsScriptLoader* aScriptLoader) +{ + if (mBlockedScriptLoaders.Contains(aScriptLoader)) { + return; + } + + aScriptLoader->AddExecuteBlocker(); + + // Let's keep track of the pending script loaders. + mBlockedScriptLoaders.AppendElement(aScriptLoader); +} + +bool +ImportLoader::RemoveBlockedScriptLoader(nsScriptLoader* aScriptLoader) +{ + aScriptLoader->RemoveExecuteBlocker(); + return mBlockedScriptLoaders.RemoveElement(aScriptLoader); +} + void ImportLoader::AddLinkElement(nsINode* aNode) { @@ -106,14 +357,15 @@ ImportLoader::AddLinkElement(nsINode* aNode) // refers to an import that is already finished loading or // stopped trying, we need to fire the corresponding event // on it. - mLinks.AppendObject(aNode); + mLinks.AppendElement(aNode); + mUpdater.UpdateSpanningTree(aNode); DispatchEventIfFinished(aNode); } void ImportLoader::RemoveLinkElement(nsINode* aNode) { - mLinks.RemoveObject(aNode); + mLinks.RemoveElement(aNode); } // Events has to be fired with a script runner, so mImport can @@ -159,8 +411,8 @@ void ImportLoader::Done() { mReady = true; - uint32_t count = mLinks.Count(); - for (uint32_t i = 0; i < count; i++) { + uint32_t l = mLinks.Length(); + for (uint32_t i = 0; i < l; i++) { DispatchLoadEvent(mLinks[i]); } UnblockScripts(); @@ -172,8 +424,8 @@ ImportLoader::Error(bool aUnblockScripts) { mDocument = nullptr; mStopped = true; - uint32_t count = mLinks.Count(); - for (uint32_t i = 0; i < count; i++) { + uint32_t l = mLinks.Length(); + for (uint32_t i = 0; i < l; i++) { DispatchErrorEvent(mLinks[i]); } if (aUnblockScripts) { @@ -391,7 +643,25 @@ ImportLoader::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) true); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR); - // Let's start parser. + nsCOMPtr originalURI; + rv = channel->GetOriginalURI(getter_AddRefs(originalURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR); + + nsCOMPtr URI; + rv = channel->GetURI(getter_AddRefs(URI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR); + MOZ_ASSERT(URI, "URI of a channel should never be null"); + + bool equals; + rv = URI->Equals(originalURI, &equals); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR); + + if (!equals) { + // In case of a redirection we must add the new URI to the import map. + Manager()->AddLoaderWithNewURI(this, URI); + } + + // Let's start the parser. mParserStreamListener = listener; rv = listener->OnStartRequest(aRequest, aContext); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR); @@ -400,6 +670,10 @@ ImportLoader::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) return NS_OK; } +//----------------------------------------------------------------------------- +// ImportManager +//----------------------------------------------------------------------------- + NS_IMPL_CYCLE_COLLECTION(ImportManager, mImports) @@ -417,16 +691,78 @@ ImportManager::Get(nsIURI* aURI, nsINode* aNode, nsIDocument* aOrigDocument) // and start it up. nsRefPtr loader; mImports.Get(aURI, getter_AddRefs(loader)); - + bool needToStart = false; if (!loader) { loader = new ImportLoader(aURI, aOrigDocument); mImports.Put(aURI, loader); + needToStart = true; + } + + MOZ_ASSERT(loader); + // Let's keep track of the sub imports links in each document. It will + // be used later for scrip execution order calculation. (see UpdateSpanningTree) + // NOTE: removing and adding back the link to the tree somewhere else will + // NOT have an effect on script execution order. + if (!aOrigDocument->HasSubImportLink(aNode)) { + aOrigDocument->AddSubImportLink(aNode); + } + + loader->AddLinkElement(aNode); + + if (needToStart) { loader->Open(); } - loader->AddLinkElement(aNode); - MOZ_ASSERT(loader); + return loader.forget(); } +ImportLoader* +ImportManager::Find(nsIDocument* aImport) +{ + return mImports.GetWeak(aImport->GetDocumentURIObject()); +} + +ImportLoader* +ImportManager::Find(nsINode* aLink) +{ + HTMLLinkElement* linkElement = static_cast(aLink); + nsCOMPtr uri = linkElement->GetHrefURI(); + return mImports.GetWeak(uri); +} + +void +ImportManager::AddLoaderWithNewURI(ImportLoader* aLoader, nsIURI* aNewURI) +{ + mImports.Put(aNewURI, aLoader); +} + +nsRefPtr ImportManager::GetNearestPredecessor(nsINode* aNode) +{ + // Return the previous link if there is any in the same document. + nsIDocument* doc = aNode->OwnerDoc(); + int32_t idx = doc->IndexOfSubImportLink(aNode); + MOZ_ASSERT(idx != -1, "aNode must be a sub import link of its owner document"); + if (idx == 0) { + if (doc->IsMasterDocument()) { + // If there is no previous one, and it was the master document, then + // there is no predecessor. + return nullptr; + } + // Else we find the main referrer of the import parent of the link's document. + // And do a recursion. + ImportLoader* owner = Find(doc); + MOZ_ASSERT(owner); + nsCOMPtr mainReferrer = owner->GetMainReferrer(); + return GetNearestPredecessor(mainReferrer); + } + MOZ_ASSERT(idx > 0); + HTMLLinkElement* link = + static_cast(doc->GetSubImportLink(idx - 1)); + nsCOMPtr uri = link->GetHrefURI(); + nsRefPtr ret; + mImports.Get(uri, getter_AddRefs(ret)); + return ret; +} + } // namespace dom } // namespace mozilla diff --git a/content/base/src/ImportManager.h b/content/base/src/ImportManager.h index ba6199418046..4ce97ebc8e43 100644 --- a/content/base/src/ImportManager.h +++ b/content/base/src/ImportManager.h @@ -39,12 +39,13 @@ #ifndef mozilla_dom_ImportManager_h__ #define mozilla_dom_ImportManager_h__ -#include "nsCOMArray.h" +#include "nsTArray.h" #include "nsCycleCollectionParticipant.h" #include "nsIDOMEventListener.h" #include "nsIStreamListener.h" #include "nsIWeakReferenceUtils.h" #include "nsRefPtrHashtable.h" +#include "nsScriptLoader.h" #include "nsURIHashKey.h" class nsIDocument; @@ -58,11 +59,70 @@ namespace dom { class ImportManager; +typedef nsTHashtable> NodeTable; + class ImportLoader MOZ_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: + 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& 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& 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& 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& aPath); + + ImportLoader* mLoader; + }; + friend class ::AutoError; friend class ImportManager; + friend class Updater; public: ImportLoader(nsIURI* aURI, nsIDocument* aOriginDocument); @@ -83,11 +143,46 @@ public: bool IsReady() { return mReady; } bool IsStopped() { return mStopped; } bool IsBlocking() { return mBlockingScripts; } - already_AddRefed GetImport() - { - return mReady ? nsCOMPtr(mDocument).forget() : nullptr; + + 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() + { + return mReady ? mDocument : nullptr; + } + + // 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() + { + return mLinks.IsEmpty() ? nullptr : 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(nsScriptLoader* aScriptLoader); + bool RemoveBlockedScriptLoader(nsScriptLoader* aScriptLoader); + void SetBlockingPredecessor(ImportLoader* aLoader); + private: ~ImportLoader() {} @@ -122,12 +217,25 @@ private: nsCOMPtr mURI; nsCOMPtr mParserStreamListener; nsCOMPtr 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. - nsCOMArray mLinks; + nsTArray> mLinks; + + // List of pending ScriptLoaders that are waiting for this import + // to finish. + nsTArray> 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 MOZ_FINAL : public nsISupports @@ -142,9 +250,24 @@ public: 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 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. + nsRefPtr GetNearestPredecessor(nsINode* aNode); + private: ImportMap mImports; }; diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index 8c0444e3f20d..f791e9f8d959 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -1997,6 +1997,8 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnDemandBuiltInUASheets) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSubImportLinks) + for (uint32_t i = 0; i < tmp->mFrameRequestCallbacks.Length(); ++i) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFrameRequestCallbacks[i]"); cb.NoteXPCOMChild(tmp->mFrameRequestCallbacks[i].mCallback.GetISupports()); @@ -2061,6 +2063,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK(mRegistry) NS_IMPL_CYCLE_COLLECTION_UNLINK(mMasterDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK(mImportManager) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSubImportLinks) tmp->mParentDocument = nullptr; diff --git a/content/base/src/nsDocument.h b/content/base/src/nsDocument.h index efe1658c1a43..b89ed1d72222 100644 --- a/content/base/src/nsDocument.h +++ b/content/base/src/nsDocument.h @@ -1290,10 +1290,10 @@ public: mozilla::ErrorResult& rv) MOZ_OVERRIDE; virtual void UseRegistryFromDocument(nsIDocument* aDocument) MOZ_OVERRIDE; - virtual already_AddRefed MasterDocument() + virtual nsIDocument* MasterDocument() { - return mMasterDocument ? (nsCOMPtr(mMasterDocument)).forget() - : (nsCOMPtr(this)).forget(); + return mMasterDocument ? mMasterDocument.get() + : this; } virtual void SetMasterDocument(nsIDocument* master) @@ -1306,11 +1306,11 @@ public: return !mMasterDocument; } - virtual already_AddRefed ImportManager() + virtual mozilla::dom::ImportManager* ImportManager() { if (mImportManager) { MOZ_ASSERT(!mMasterDocument, "Only the master document has ImportManager set"); - return nsRefPtr(mImportManager).forget(); + return mImportManager.get(); } if (mMasterDocument) { @@ -1322,7 +1322,28 @@ public: // master document and this is the first import in it. // Let's create a new manager. mImportManager = new mozilla::dom::ImportManager(); - return nsRefPtr(mImportManager).forget(); + return mImportManager.get(); + } + + virtual bool HasSubImportLink(nsINode* aLink) + { + return mSubImportLinks.Contains(aLink); + } + + virtual uint32_t IndexOfSubImportLink(nsINode* aLink) + { + return mSubImportLinks.IndexOf(aLink); + } + + virtual void AddSubImportLink(nsINode* aLink) + { + mSubImportLinks.AppendElement(aLink); + } + + virtual nsINode* GetSubImportLink(uint32_t aIdx) + { + return aIdx < mSubImportLinks.Length() ? mSubImportLinks[aIdx].get() + : nullptr; } virtual void UnblockDOMContentLoaded() MOZ_OVERRIDE; @@ -1754,6 +1775,7 @@ private: nsCOMPtr mMasterDocument; nsRefPtr mImportManager; + nsTArray > mSubImportLinks; // Set to true when the document is possibly controlled by the ServiceWorker. // Used to prevent multiple requests to ServiceWorkerManager. diff --git a/content/base/src/nsScriptLoader.cpp b/content/base/src/nsScriptLoader.cpp index 4e60dc20b288..f11842915296 100644 --- a/content/base/src/nsScriptLoader.cpp +++ b/content/base/src/nsScriptLoader.cpp @@ -49,6 +49,7 @@ #include "nsSandboxFlags.h" #include "nsContentTypeParser.h" #include "nsINetworkPredictor.h" +#include "ImportManager.h" #include "mozilla/dom/EncodingUtils.h" #include "mozilla/CORSMode.h" @@ -1236,7 +1237,7 @@ nsScriptLoader::ReadyToExecuteScripts() if (!SelfReadyToExecuteScripts()) { return false; } - + for (nsIDocument* doc = mDocument; doc; doc = doc->GetParentDocument()) { nsScriptLoader* ancestor = doc->ScriptLoader(); if (!ancestor->SelfReadyToExecuteScripts() && @@ -1246,10 +1247,44 @@ nsScriptLoader::ReadyToExecuteScripts() } } + if (!mDocument->IsMasterDocument()) { + nsRefPtr im = mDocument->ImportManager(); + nsRefPtr loader = im->Find(mDocument); + MOZ_ASSERT(loader, "How can we have an import document without a loader?"); + + // The referring link that counts in the execution order calculation + // (in spec: flagged as branch) + nsCOMPtr referrer = loader->GetMainReferrer(); + MOZ_ASSERT(referrer, "There has to be a main referring link for each imports"); + + // Import documents are blocked by their import predecessors. We need to + // wait with script execution until all the predecessors are done. + // Technically it means we have to wait for the last one to finish, + // which is the neares one to us in the order. + nsRefPtr lastPred = im->GetNearestPredecessor(referrer); + if (!lastPred) { + // If there is no predecessor we can run. + return true; + } + + nsCOMPtr doc = lastPred->GetDocument(); + if (lastPred->IsBlocking() || !doc || (doc && !doc->ScriptLoader()->SelfReadyToExecuteScripts())) { + // Document has not been created yet or it was created but not ready. + // Either case we are blocked by it. The ImportLoader will take care + // of blocking us, and adding the pending child loader to the blocking + // ScriptLoader when it's possible (at this point the blocking loader + // might not have created the document/ScriptLoader) + lastPred->AddBlockedScriptLoader(this); + // As more imports are parsed, this can change, let's cache what we + // blocked, so it can be later updated if needed (see: ImportLoader::Updater). + loader->SetBlockingPredecessor(lastPred); + return false; + } + } + return true; } - // This function was copied from nsParser.cpp. It was simplified a bit. static bool DetectByteOrderMark(const unsigned char* aBytes, int32_t aLen, nsCString& oCharset) diff --git a/content/base/src/nsScriptLoader.h b/content/base/src/nsScriptLoader.h index 6f5e80b76d39..f0f48724c393 100644 --- a/content/base/src/nsScriptLoader.h +++ b/content/base/src/nsScriptLoader.h @@ -249,6 +249,10 @@ public: nsresult ProcessOffThreadRequest(nsScriptLoadRequest *aRequest, void **aOffThreadToken); + bool AddPendingChildLoader(nsScriptLoader* aChild) { + return mPendingChildLoaders.AppendElement(aChild) != nullptr; + } + private: virtual ~nsScriptLoader(); @@ -302,10 +306,6 @@ private: return mEnabled && !mBlockerCount; } - bool AddPendingChildLoader(nsScriptLoader* aChild) { - return mPendingChildLoaders.AppendElement(aChild) != nullptr; - } - nsresult AttemptAsyncScriptParse(nsScriptLoadRequest* aRequest); nsresult ProcessRequest(nsScriptLoadRequest* aRequest, void **aOffThreadToken = nullptr); diff --git a/content/html/content/moz.build b/content/html/content/moz.build index 140507dd7127..7318432f13ae 100644 --- a/content/html/content/moz.build +++ b/content/html/content/moz.build @@ -8,6 +8,7 @@ DIRS += ['public', 'src'] MOCHITEST_MANIFESTS += [ 'test/forms/mochitest.ini', + 'test/imports/mochitest.ini', 'test/mochitest.ini', ] diff --git a/content/html/content/src/HTMLLinkElement.cpp b/content/html/content/src/HTMLLinkElement.cpp index 1b03afb68646..5731763602e5 100644 --- a/content/html/content/src/HTMLLinkElement.cpp +++ b/content/html/content/src/HTMLLinkElement.cpp @@ -263,15 +263,6 @@ HTMLLinkElement::UpdateImport() return; } - // Until the script execution order is not sorted out for nested cases - // let's not allow them. - if (!doc->IsMasterDocument()) { - nsContentUtils::LogSimpleConsoleError( - NS_LITERAL_STRING("Nested imports are not supported yet"), - "Imports"); - return; - } - // 2. rel type should be import. nsAutoString rel; GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel); @@ -526,7 +517,7 @@ HTMLLinkElement::WrapNode(JSContext* aCx) already_AddRefed HTMLLinkElement::GetImport() { - return mImportLoader ? mImportLoader->GetImport() : nullptr; + return mImportLoader ? nsRefPtr(mImportLoader->GetImport()).forget() : nullptr; } } // namespace dom diff --git a/content/html/content/test/imports/file_importA1.html b/content/html/content/test/imports/file_importA1.html new file mode 100644 index 000000000000..ecce6f061232 --- /dev/null +++ b/content/html/content/test/imports/file_importA1.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/file_importA2.html b/content/html/content/test/imports/file_importA2.html new file mode 100644 index 000000000000..d03e80a4b41f --- /dev/null +++ b/content/html/content/test/imports/file_importA2.html @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/file_importB1.html b/content/html/content/test/imports/file_importB1.html new file mode 100644 index 000000000000..82fb55f9f61e --- /dev/null +++ b/content/html/content/test/imports/file_importB1.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/file_importB2.html b/content/html/content/test/imports/file_importB2.html new file mode 100644 index 000000000000..01b6cc21584b --- /dev/null +++ b/content/html/content/test/imports/file_importB2.html @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/file_importC1.html b/content/html/content/test/imports/file_importC1.html new file mode 100644 index 000000000000..9ac117e65c9b --- /dev/null +++ b/content/html/content/test/imports/file_importC1.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/file_importC10.html b/content/html/content/test/imports/file_importC10.html new file mode 100644 index 000000000000..801d0f085574 --- /dev/null +++ b/content/html/content/test/imports/file_importC10.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/file_importC2.html b/content/html/content/test/imports/file_importC2.html new file mode 100644 index 000000000000..f0193be44999 --- /dev/null +++ b/content/html/content/test/imports/file_importC2.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/file_importC3.html b/content/html/content/test/imports/file_importC3.html new file mode 100644 index 000000000000..eb942b707f5a --- /dev/null +++ b/content/html/content/test/imports/file_importC3.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/file_importC4.html b/content/html/content/test/imports/file_importC4.html new file mode 100644 index 000000000000..5a172772ad93 --- /dev/null +++ b/content/html/content/test/imports/file_importC4.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/file_importC5.html b/content/html/content/test/imports/file_importC5.html new file mode 100644 index 000000000000..c29dc24ba06f --- /dev/null +++ b/content/html/content/test/imports/file_importC5.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/file_importC6.html b/content/html/content/test/imports/file_importC6.html new file mode 100644 index 000000000000..a53b62bb45ef --- /dev/null +++ b/content/html/content/test/imports/file_importC6.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/file_importC7.html b/content/html/content/test/imports/file_importC7.html new file mode 100644 index 000000000000..1c7d60114e06 --- /dev/null +++ b/content/html/content/test/imports/file_importC7.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/file_importC8.html b/content/html/content/test/imports/file_importC8.html new file mode 100644 index 000000000000..04bef617d392 --- /dev/null +++ b/content/html/content/test/imports/file_importC8.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/file_importC9.html b/content/html/content/test/imports/file_importC9.html new file mode 100644 index 000000000000..1a27497557f5 --- /dev/null +++ b/content/html/content/test/imports/file_importC9.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/file_importD.html b/content/html/content/test/imports/file_importD.html new file mode 100644 index 000000000000..a4fbda536dd0 --- /dev/null +++ b/content/html/content/test/imports/file_importD.html @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/file_importE.html b/content/html/content/test/imports/file_importE.html new file mode 100644 index 000000000000..6a8792acf2ad --- /dev/null +++ b/content/html/content/test/imports/file_importE.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/content/html/content/test/imports/mochitest.ini b/content/html/content/test/imports/mochitest.ini new file mode 100644 index 000000000000..c1a863ea8483 --- /dev/null +++ b/content/html/content/test/imports/mochitest.ini @@ -0,0 +1,20 @@ +[DEFAULT] +support-files = + file_importA1.html + file_importA2.html + file_importB1.html + file_importB2.html + file_importC1.html + file_importC2.html + file_importC3.html + file_importC4.html + file_importC5.html + file_importC6.html + file_importC7.html + file_importC8.html + file_importC9.html + file_importC10.html + file_importD.html + file_importE.html + + diff --git a/content/html/content/test/mochitest.ini b/content/html/content/test/mochitest.ini index 824935e25093..860f741cdf52 100644 --- a/content/html/content/test/mochitest.ini +++ b/content/html/content/test/mochitest.ini @@ -463,6 +463,10 @@ skip-if = buildapp == 'b2g' || e10s # b2g(multiple concurrent window.open()s fai [test_imports_basics.html] [test_imports_redirect.html] [test_imports_nonhttp.html] +[test_imports_nested.html] +skip-if = toolkit == 'gonk' # nested imports fail on b2g emulator +[test_imports_nested_2.html] +skip-if = toolkit == 'gonk' # nested imports fail on b2g emulator [test_li_attributes_reflection.html] [test_link_attributes_reflection.html] [test_link_sizes.html] diff --git a/content/html/content/test/test_imports_nested.html b/content/html/content/test/test_imports_nested.html new file mode 100644 index 000000000000..518e5b741788 --- /dev/null +++ b/content/html/content/test/test_imports_nested.html @@ -0,0 +1,41 @@ + + + + + Test for Bug 877072 + + + + + + Mozilla Bug 877072 + + + + + + + + + + diff --git a/content/html/content/test/test_imports_nested_2.html b/content/html/content/test/test_imports_nested_2.html new file mode 100644 index 000000000000..8e8e57d684bb --- /dev/null +++ b/content/html/content/test/test_imports_nested_2.html @@ -0,0 +1,56 @@ + + + + + Test for Bug 877072 + + + + + + Mozilla Bug 877072 + + + + + + + + + + + + diff --git a/testing/web-platform/meta/html-imports/fetching/already-in-import-map.html.ini b/testing/web-platform/meta/html-imports/fetching/already-in-import-map.html.ini deleted file mode 100644 index 83ec5c0bd6cb..000000000000 --- a/testing/web-platform/meta/html-imports/fetching/already-in-import-map.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[already-in-import-map.html] - type: testharness - [If LOCATION is already in the import map, let IMPORT be the imported document for LOCATION and stop. (2)] - expected: FAIL -