/* 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 https://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/PrototypeDocumentContentSink.h" #include "js/CompilationAndEvaluation.h" #include "js/Utility.h" // JS::FreePolicy #include "js/experimental/JSStencil.h" #include "mozAutoDocUpdate.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/LoadInfo.h" #include "mozilla/Logging.h" #include "mozilla/PresShell.h" #include "mozilla/ProfilerLabels.h" #include "mozilla/RefPtr.h" #include "mozilla/StyleSheetInlines.h" #include "mozilla/Try.h" #include "mozilla/css/Loader.h" #include "mozilla/dom/AutoEntryScript.h" #include "mozilla/dom/CDATASection.h" #include "mozilla/dom/Comment.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/DocumentType.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLTemplateElement.h" #include "mozilla/dom/PolicyContainer.h" #include "mozilla/dom/ProcessingInstruction.h" #include "mozilla/dom/ScriptLoader.h" #include "mozilla/dom/XMLStylesheetProcessingInstruction.h" #include "mozilla/dom/nsCSPUtils.h" #include "nsCOMPtr.h" #include "nsCRT.h" #include "nsContentCreatorFunctions.h" #include "nsContentPolicyUtils.h" #include "nsContentUtils.h" #include "nsDocElementCreatedNotificationRunner.h" #include "nsError.h" #include "nsGkAtoms.h" #include "nsHTMLParts.h" #include "nsHtml5SVGLoadDispatcher.h" #include "nsIChannel.h" #include "nsIContent.h" #include "nsIContentPolicy.h" #include "nsIParser.h" #include "nsIScriptContext.h" #include "nsIScriptElement.h" #include "nsIScriptError.h" #include "nsIScriptGlobalObject.h" #include "nsIURI.h" #include "nsMimeTypes.h" #include "nsNameSpaceManager.h" #include "nsNetUtil.h" #include "nsNodeInfoManager.h" #include "nsReadableUtils.h" #include "nsRect.h" #include "nsTextNode.h" #include "nsUnicharUtils.h" #include "nsXULElement.h" #include "nsXULPrototypeCache.h" #include "prtime.h" using namespace mozilla; using namespace mozilla::dom; LazyLogModule PrototypeDocumentContentSink::gLog("PrototypeDocument"); nsresult NS_NewPrototypeDocumentContentSink(nsIContentSink** aResult, Document* aDoc, nsIURI* aURI, nsISupports* aContainer, nsIChannel* aChannel) { MOZ_ASSERT(nullptr != aResult, "null ptr"); if (nullptr == aResult) { return NS_ERROR_NULL_POINTER; } RefPtr it = new PrototypeDocumentContentSink(); nsresult rv = it->Init(aDoc, aURI, aContainer, aChannel); NS_ENSURE_SUCCESS(rv, rv); it.forget(aResult); return NS_OK; } namespace mozilla::dom { PrototypeDocumentContentSink::PrototypeDocumentContentSink() : mNextSrcLoadWaiter(nullptr), mCurrentScriptProto(nullptr), mOffThreadCompiling(false), mStillWalking(false), mPendingSheets(0) {} PrototypeDocumentContentSink::~PrototypeDocumentContentSink() { NS_ASSERTION( mNextSrcLoadWaiter == nullptr, "unreferenced document still waiting for script source to load?"); } nsresult PrototypeDocumentContentSink::Init(Document* aDoc, nsIURI* aURI, nsISupports* aContainer, nsIChannel* aChannel) { MOZ_ASSERT(aDoc, "null ptr"); MOZ_ASSERT(aURI, "null ptr"); mDocument = aDoc; mDocument->SetDelayFrameLoaderInitialization(true); mDocument->SetMayStartLayout(false); // Get the URI. this should match the uri used for the OnNewURI call in // nsDocShell::CreateDocumentViewer. nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(mDocumentURI)); NS_ENSURE_SUCCESS(rv, rv); mScriptLoader = mDocument->GetScriptLoader(); return NS_OK; } NS_IMPL_CYCLE_COLLECTION(PrototypeDocumentContentSink, mParser, mDocumentURI, mDocument, mScriptLoader, mContextStack, mCurrentPrototype) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PrototypeDocumentContentSink) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentSink) NS_INTERFACE_MAP_ENTRY(nsIContentSink) NS_INTERFACE_MAP_ENTRY(nsIStreamLoaderObserver) NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver) NS_INTERFACE_MAP_ENTRY(nsIOffThreadScriptReceiver) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(PrototypeDocumentContentSink) NS_IMPL_CYCLE_COLLECTING_RELEASE(PrototypeDocumentContentSink) //---------------------------------------------------------------------- // // nsIContentSink interface // void PrototypeDocumentContentSink::SetDocumentCharset( NotNull aEncoding) { if (mDocument) { mDocument->SetDocumentCharacterSet(aEncoding); } } nsISupports* PrototypeDocumentContentSink::GetTarget() { return ToSupports(mDocument); } bool PrototypeDocumentContentSink::IsScriptExecuting() { if (!mScriptLoader) { MOZ_ASSERT(false, "Can't load prototype docs as data"); return false; } return !!mScriptLoader->GetCurrentScript(); } NS_IMETHODIMP PrototypeDocumentContentSink::SetParser(nsParserBase* aParser) { MOZ_ASSERT(aParser, "Should have a parser here!"); mParser = aParser; return NS_OK; } nsIParser* PrototypeDocumentContentSink::GetParser() { return static_cast(mParser.get()); } void PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled() { if (mParser && mParser->IsParserEnabled()) { GetParser()->ContinueInterruptedParsing(); } } void PrototypeDocumentContentSink::ContinueInterruptedParsingAsync() { nsCOMPtr ev = NewRunnableMethod( "PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled", this, &PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled); mDocument->Dispatch(ev.forget()); } //---------------------------------------------------------------------- // // PrototypeDocumentContentSink::ContextStack // PrototypeDocumentContentSink::ContextStack::ContextStack() : mTop(nullptr), mDepth(0) {} PrototypeDocumentContentSink::ContextStack::~ContextStack() { Clear(); } void PrototypeDocumentContentSink::ContextStack::Traverse( nsCycleCollectionTraversalCallback& aCallback, const char* aName, uint32_t aFlags) { aFlags |= CycleCollectionEdgeNameArrayFlag; Entry* current = mTop; while (current) { CycleCollectionNoteChild(aCallback, current->mElement, aName, aFlags); current = current->mNext; } } void PrototypeDocumentContentSink::ContextStack::Clear() { while (mTop) { Entry* doomed = mTop; mTop = mTop->mNext; NS_IF_RELEASE(doomed->mElement); delete doomed; } mDepth = 0; } nsresult PrototypeDocumentContentSink::ContextStack::Push( nsXULPrototypeElement* aPrototype, nsIContent* aElement) { Entry* entry = new Entry; entry->mPrototype = aPrototype; entry->mElement = aElement; NS_IF_ADDREF(entry->mElement); entry->mIndex = 0; entry->mNext = mTop; mTop = entry; ++mDepth; return NS_OK; } nsresult PrototypeDocumentContentSink::ContextStack::Pop() { if (mDepth == 0) return NS_ERROR_UNEXPECTED; Entry* doomed = mTop; mTop = mTop->mNext; --mDepth; NS_IF_RELEASE(doomed->mElement); delete doomed; return NS_OK; } nsresult PrototypeDocumentContentSink::ContextStack::Peek( nsXULPrototypeElement** aPrototype, nsIContent** aElement, int32_t* aIndex) { if (mDepth == 0) return NS_ERROR_UNEXPECTED; *aPrototype = mTop->mPrototype; *aElement = mTop->mElement; NS_IF_ADDREF(*aElement); *aIndex = mTop->mIndex; return NS_OK; } nsresult PrototypeDocumentContentSink::ContextStack::SetTopIndex( int32_t aIndex) { if (mDepth == 0) return NS_ERROR_UNEXPECTED; mTop->mIndex = aIndex; return NS_OK; } //---------------------------------------------------------------------- // // Content model walking routines // nsresult PrototypeDocumentContentSink::OnPrototypeLoadDone( nsXULPrototypeDocument* aPrototype) { mCurrentPrototype = aPrototype; mDocument->SetPrototypeDocument(aPrototype); nsresult rv = PrepareToWalk(); NS_ENSURE_SUCCESS(rv, rv); rv = ResumeWalk(); return rv; } nsresult PrototypeDocumentContentSink::PrepareToWalk() { MOZ_ASSERT(mCurrentPrototype); nsresult rv; mStillWalking = true; // Notify document that the load is beginning mDocument->BeginLoad(); MOZ_ASSERT(!mDocument->HasChildren()); // Get the prototype's root element and initialize the context // stack for the prototype walk. nsXULPrototypeElement* proto = mCurrentPrototype->GetRootElement(); if (!proto) { if (MOZ_LOG_TEST(gLog, LogLevel::Error)) { nsCOMPtr url = mCurrentPrototype->GetURI(); nsAutoCString urlspec; rv = url->GetSpec(urlspec); if (NS_FAILED(rv)) return rv; MOZ_LOG(gLog, LogLevel::Error, ("prototype: error parsing '%s'", urlspec.get())); } return NS_OK; } const nsTArray >& processingInstructions = mCurrentPrototype->GetProcessingInstructions(); uint32_t total = processingInstructions.Length(); for (uint32_t i = 0; i < total; ++i) { rv = CreateAndInsertPI(processingInstructions[i], mDocument, /* aInProlog */ true); if (NS_FAILED(rv)) return rv; } // Do one-time initialization. RefPtr root; // Add the root element rv = CreateElementFromPrototype(proto, getter_AddRefs(root), nullptr); if (NS_FAILED(rv)) return rv; ErrorResult error; mDocument->AppendChildTo(root, false, error); if (error.Failed()) { return error.StealNSResult(); } // TODO(emilio): Should this really notify? We don't notify of appends anyhow, // and we just appended the root so no styles can possibly depend on it. mDocument->UpdateDocumentStates(DocumentState::RTL_LOCALE, true); nsContentUtils::AddScriptRunner( new nsDocElementCreatedNotificationRunner(mDocument)); // There'd better not be anything on the context stack at this // point! This is the basis case for our "induction" in // ResumeWalk(), below, which'll assume that there's always a // content element on the context stack if we're in the document. NS_ASSERTION(mContextStack.Depth() == 0, "something's on the context stack already"); if (mContextStack.Depth() != 0) return NS_ERROR_UNEXPECTED; rv = mContextStack.Push(proto, root); if (NS_FAILED(rv)) return rv; return NS_OK; } nsresult PrototypeDocumentContentSink::CreateAndInsertPI( const nsXULPrototypePI* aProtoPI, nsINode* aParent, bool aInProlog) { MOZ_ASSERT(aProtoPI, "null ptr"); MOZ_ASSERT(aParent, "null ptr"); RefPtr node = NS_NewXMLProcessingInstruction( aParent->NodeInfoManager(), aProtoPI->mTarget, aProtoPI->mData); nsresult rv; if (aProtoPI->mTarget.EqualsLiteral("xml-stylesheet")) { MOZ_ASSERT(LinkStyle::FromNode(*node), "XML Stylesheet node does not implement LinkStyle!"); auto* pi = static_cast(node.get()); rv = InsertXMLStylesheetPI(aProtoPI, aParent, pi); } else { // Handles the special PI, which will be handled before // creating any element with potential inline style or scripts. if (aInProlog && aProtoPI->mTarget.EqualsLiteral("csp")) { CSP_ApplyMetaCSPToDoc(*aParent->OwnerDoc(), aProtoPI->mData); } // No special processing, just add the PI to the document. ErrorResult error; aParent->AppendChildTo(node->AsContent(), false, error); rv = error.StealNSResult(); } return rv; } nsresult PrototypeDocumentContentSink::InsertXMLStylesheetPI( const nsXULPrototypePI* aProtoPI, nsINode* aParent, XMLStylesheetProcessingInstruction* aPINode) { // We want to be notified when the style sheet finishes loading, so // disable style sheet loading for now. aPINode->DisableUpdates(); aPINode->OverrideBaseURI(mCurrentPrototype->GetURI()); ErrorResult rv; aParent->AppendChildTo(aPINode, false, rv); if (rv.Failed()) { return rv.StealNSResult(); } // load the stylesheet if necessary, passing ourselves as // nsICSSObserver auto result = aPINode->EnableUpdatesAndUpdateStyleSheet(this); if (result.isErr()) { // Ignore errors from UpdateStyleSheet; we don't want failure to // do that to break the XUL document load. But do propagate out // NS_ERROR_OUT_OF_MEMORY. if (result.unwrapErr() == NS_ERROR_OUT_OF_MEMORY) { return result.unwrapErr(); } return NS_OK; } auto update = result.unwrap(); if (update.ShouldBlock()) { ++mPendingSheets; } return NS_OK; } void PrototypeDocumentContentSink::CloseElement(Element* aElement) { if (nsIContent::RequiresDoneAddingChildren( aElement->NodeInfo()->NamespaceID(), aElement->NodeInfo()->NameAtom())) { aElement->DoneAddingChildren(false); } if (auto* linkStyle = LinkStyle::FromNode(*aElement)) { auto result = linkStyle->EnableUpdatesAndUpdateStyleSheet(this); if (result.isOk() && result.unwrap().ShouldBlock()) { ++mPendingSheets; } return; } // See bug 370111 and bug 1495946. We don't cache module scripts in the // prototype cache, so we need to do this for the script to be properly // processed. if (aElement->IsHTMLElement(nsGkAtoms::script) || aElement->IsSVGElement(nsGkAtoms::script)) { nsCOMPtr sele = do_QueryInterface(aElement); MOZ_ASSERT(sele, "Node didn't QI to script."); if (sele->GetScriptIsModule()) { // https://html.spec.whatwg.org/#parsing-main-incdata // An end tag whose tag name is "script" // - If the active speculative HTML parser is null and the JavaScript // execution context stack is empty, then perform a microtask checkpoint. { nsAutoMicroTask mt; } sele->AttemptToExecute(nullptr /* aParser */); } } } nsresult PrototypeDocumentContentSink::ResumeWalk() { nsresult rv = ResumeWalkInternal(); if (NS_FAILED(rv)) { nsContentUtils::ReportToConsoleNonLocalized( u"Failed to load document from prototype document."_ns, nsIScriptError::errorFlag, "Prototype Document"_ns, mDocument, SourceLocation{mDocumentURI.get()}); } return rv; } nsresult PrototypeDocumentContentSink::ResumeWalkInternal() { MOZ_ASSERT(mStillWalking); // Walk the prototype and build the delegate content model. The // walk is performed in a top-down, left-to-right fashion. That // is, a parent is built before any of its children; a node is // only built after all of its siblings to the left are fully // constructed. // // It is interruptable so that transcluded documents (e.g., // ) can be properly re-loaded if the // cached copy of the document becomes stale. nsresult rv; nsCOMPtr docURI = mCurrentPrototype ? mCurrentPrototype->GetURI() : nullptr; while (true) { // Begin (or resume) walking the current prototype. while (mContextStack.Depth() > 0) { // Look at the top of the stack to determine what we're // currently working on. // This will always be a node already constructed and // inserted to the actual document. nsXULPrototypeElement* proto; nsCOMPtr element; nsCOMPtr nodeToPushTo; int32_t indx; // all children of proto before indx (not // inclusive) have already been constructed rv = mContextStack.Peek(&proto, getter_AddRefs(element), &indx); if (NS_FAILED(rv)) return rv; if (indx >= (int32_t)proto->mChildren.Length()) { if (element) { // We've processed all of the prototype's children. CloseElement(element->AsElement()); } // Now pop the context stack back up to the parent // element and continue the prototype walk. mContextStack.Pop(); continue; } nodeToPushTo = element; // For template elements append the content to the template's document // fragment. if (auto* templateElement = HTMLTemplateElement::FromNode(element)) { nodeToPushTo = templateElement->Content(); } // Grab the next child, and advance the current context stack // to the next sibling to our right. nsXULPrototypeNode* childproto = proto->mChildren[indx]; mContextStack.SetTopIndex(++indx); switch (childproto->mType) { case nsXULPrototypeNode::eType_Element: { // An 'element', which may contain more content. auto* protoele = static_cast(childproto); RefPtr child; MOZ_TRY(CreateElementFromPrototype(protoele, getter_AddRefs(child), nodeToPushTo)); if (auto* linkStyle = LinkStyle::FromNode(*child)) { linkStyle->DisableUpdates(); } // ...and append it to the content model. ErrorResult error; nodeToPushTo->AppendChildTo(child, false, error); if (error.Failed()) { return error.StealNSResult(); } if (nsIContent::RequiresDoneCreatingElement( protoele->mNodeInfo->NamespaceID(), protoele->mNodeInfo->NameAtom())) { child->DoneCreatingElement(); } // If it has children, push the element onto the context // stack and begin to process them. if (protoele->mChildren.Length() > 0) { rv = mContextStack.Push(protoele, child); if (NS_FAILED(rv)) return rv; } else { // If there are no children, close the element immediately. CloseElement(child); } } break; case nsXULPrototypeNode::eType_Script: { // A script reference. Execute the script immediately; // this may have side effects in the content model. auto* scriptproto = static_cast(childproto); if (scriptproto->mSrcURI) { // A transcluded script reference; this may // "block" our prototype walk if the script isn't // cached, or the cached copy of the script is // stale and must be reloaded. bool blocked; rv = LoadScript(scriptproto, &blocked); // If the script cannot be loaded, just keep going! if (NS_SUCCEEDED(rv) && blocked) return NS_OK; } else if (scriptproto->HasStencil()) { // An inline script rv = ExecuteScript(scriptproto); if (NS_FAILED(rv)) return rv; } } break; case nsXULPrototypeNode::eType_Text: { nsNodeInfoManager* nim = nodeToPushTo->NodeInfo()->NodeInfoManager(); // A simple text node. RefPtr text = new (nim) nsTextNode(nim); auto* textproto = static_cast(childproto); text->SetText(textproto->mValue, false); ErrorResult error; nodeToPushTo->AppendChildTo(text, false, error); if (error.Failed()) { return error.StealNSResult(); } } break; case nsXULPrototypeNode::eType_PI: { auto* piProto = static_cast(childproto); // and don't have an effect // outside the prolog, issue a warning. if (piProto->mTarget.EqualsLiteral("xml-stylesheet") || piProto->mTarget.EqualsLiteral("csp")) { AutoTArray params = {piProto->mTarget}; nsContentUtils::ReportToConsole( nsIScriptError::warningFlag, "XUL Document"_ns, nullptr, PropertiesFile::XUL_PROPERTIES, "PINotInProlog2", params, SourceLocation(docURI.get())); } if (nsIContent* parent = element.get()) { // an inline script could have removed the root element rv = CreateAndInsertPI(piProto, parent, /* aInProlog */ false); NS_ENSURE_SUCCESS(rv, rv); } } break; default: MOZ_ASSERT_UNREACHABLE("Unexpected nsXULPrototypeNode::Type"); } } // Once we get here, the context stack will have been // depleted. That means that the entire prototype has been // walked and content has been constructed. break; } mStillWalking = false; return MaybeDoneWalking(); } void PrototypeDocumentContentSink::InitialTranslationCompleted() { MaybeDoneWalking(); } nsresult PrototypeDocumentContentSink::MaybeDoneWalking() { if (mPendingSheets > 0 || mStillWalking) { return NS_OK; } if (mDocument->HasPendingInitialTranslation()) { mDocument->OnParsingCompleted(); return NS_OK; } return DoneWalking(); } nsresult PrototypeDocumentContentSink::DoneWalking() { MOZ_ASSERT(mPendingSheets == 0, "there are sheets to be loaded"); MOZ_ASSERT(!mStillWalking, "walk not done"); MOZ_ASSERT(!mDocument->HasPendingInitialTranslation(), "translation pending"); if (mDocument) { MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING, "Bad readyState"); mDocument->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE); mDocument->NotifyPossibleTitleChange(false); nsContentUtils::DispatchEventOnlyToChrome(mDocument, mDocument, u"MozBeforeInitialXULLayout"_ns, CanBubble::eYes, Cancelable::eNo); } if (mScriptLoader) { mScriptLoader->ParsingComplete(false); mScriptLoader->DeferCheckpointReached(); } StartLayout(); if (mDocumentURI->SchemeIs("chrome") && nsXULPrototypeCache::GetInstance()->IsEnabled()) { bool isCachedOnDisk; nsXULPrototypeCache::GetInstance()->HasPrototype(mDocumentURI, &isCachedOnDisk); if (!isCachedOnDisk) { if (!mDocument->GetDocumentElement() || (mDocument->GetDocumentElement()->NodeInfo()->Equals( nsGkAtoms::parsererror) && mDocument->GetDocumentElement()->NodeInfo()->NamespaceEquals( nsDependentAtomString(nsGkAtoms::nsuri_parsererror)))) { nsXULPrototypeCache::GetInstance()->RemovePrototype(mDocumentURI); } else { nsXULPrototypeCache::GetInstance()->WritePrototype(mCurrentPrototype); } } } mDocument->SetDelayFrameLoaderInitialization(false); RefPtr doc = mDocument; doc->MaybeInitializeFinalizeFrameLoaders(); // If the document we are loading has a reference or it is a // frameset document, disable the scroll bars on the views. doc->SetScrollToRef(mDocument->GetDocumentURI()); doc->EndLoad(); return NS_OK; } void PrototypeDocumentContentSink::StartLayout() { AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING( "PrototypeDocumentContentSink::StartLayout", LAYOUT, mDocumentURI->GetSpecOrDefault()); mDocument->SetMayStartLayout(true); RefPtr presShell = mDocument->GetPresShell(); if (presShell && !presShell->DidInitialize()) { nsresult rv = presShell->Initialize(); if (NS_FAILED(rv)) { return; } } } NS_IMETHODIMP PrototypeDocumentContentSink::StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred, nsresult aStatus) { if (!aWasDeferred) { // Don't care about when alternate sheets finish loading MOZ_ASSERT(mPendingSheets > 0, "Unexpected StyleSheetLoaded notification"); --mPendingSheets; return MaybeDoneWalking(); } return NS_OK; } nsresult PrototypeDocumentContentSink::LoadScript( nsXULPrototypeScript* aScriptProto, bool* aBlock) { // Load a transcluded script nsresult rv; bool isChromeDoc = mDocumentURI->SchemeIs("chrome"); if (isChromeDoc && aScriptProto->HasStencil()) { rv = ExecuteScript(aScriptProto); // Ignore return value from execution, and don't block *aBlock = false; return NS_OK; } // Try the XUL script cache, in case two XUL documents source the same // .js file (e.g., strres.js from navigator.xul and utilityOverlay.xul). // XXXbe the cache relies on aScriptProto's GC root! bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled(); if (isChromeDoc && useXULCache) { RefPtr newStencil = nsXULPrototypeCache::GetInstance()->GetStencil(aScriptProto->mSrcURI); if (newStencil) { // The script language for a proto must remain constant - we // can't just change it for this unexpected language. aScriptProto->Set(newStencil); } if (aScriptProto->HasStencil()) { rv = ExecuteScript(aScriptProto); // Ignore return value from execution, and don't block *aBlock = false; return NS_OK; } } // Release stencil from FastLoad since we decided against using them aScriptProto->Set(nullptr); // Set the current script prototype so that OnStreamComplete can report // the right file if there are errors in the script. NS_ASSERTION(!mCurrentScriptProto, "still loading a script when starting another load?"); mCurrentScriptProto = aScriptProto; if (isChromeDoc && aScriptProto->mSrcLoading) { // Another document load has started, which is still in progress. // Remember to ResumeWalk this document when the load completes. mNextSrcLoadWaiter = aScriptProto->mSrcLoadWaiters; aScriptProto->mSrcLoadWaiters = this; NS_ADDREF_THIS(); } else { nsCOMPtr group = mDocument ->GetDocumentLoadGroup(); // found in // mozilla::dom::Document::SetScriptGlobalObject // Note: the loader will keep itself alive while it's loading. nsCOMPtr loader; rv = NS_NewStreamLoader( getter_AddRefs(loader), aScriptProto->mSrcURI, this, // aObserver mDocument, // aRequestingContext nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT, nsIContentPolicy::TYPE_INTERNAL_SCRIPT, group); if (NS_FAILED(rv)) { mCurrentScriptProto = nullptr; return rv; } aScriptProto->mSrcLoading = true; } // Block until OnStreamComplete resumes us. *aBlock = true; return NS_OK; } NS_IMETHODIMP PrototypeDocumentContentSink::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* context, nsresult aStatus, uint32_t stringLen, const uint8_t* string) { nsCOMPtr request; aLoader->GetRequest(getter_AddRefs(request)); nsCOMPtr channel = do_QueryInterface(request); #ifdef DEBUG // print a load error on bad status if (NS_FAILED(aStatus)) { if (channel) { nsCOMPtr uri; channel->GetURI(getter_AddRefs(uri)); if (uri) { printf("Failed to load %s\n", uri->GetSpecOrDefault().get()); } } } #endif // This is the completion routine that will be called when a // transcluded script completes. Compile and execute the script // if the load was successful, then continue building content // from the prototype. nsresult rv = aStatus; NS_ASSERTION(mCurrentScriptProto && mCurrentScriptProto->mSrcLoading, "script source not loading on unichar stream complete?"); if (!mCurrentScriptProto) { // XXX Wallpaper for bug 270042 return NS_OK; } if (NS_SUCCEEDED(aStatus)) { // If the including document is a FastLoad document, and we're // compiling an out-of-line script (one with src=...), then we must // be writing a new FastLoad file. If we were reading this script // from the FastLoad file, XULContentSinkImpl::OpenScript (over in // nsXULContentSink.cpp) would have already deserialized a non-null // script->mStencil, causing control flow at the top of LoadScript // not to reach here. nsCOMPtr uri = mCurrentScriptProto->mSrcURI; // XXX should also check nsIHttpChannel::requestSucceeded MOZ_ASSERT(!mOffThreadCompiling, "PrototypeDocument can't load multiple scripts at once"); UniquePtr units; size_t unitsLength = 0; rv = ScriptLoader::ConvertToUTF8(channel, string, stringLen, u""_ns, mDocument, units, unitsLength); if (NS_SUCCEEDED(rv)) { rv = mCurrentScriptProto->CompileMaybeOffThread( std::move(units), unitsLength, uri, 1, mDocument, this); if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->HasStencil()) { mOffThreadCompiling = true; mDocument->BlockOnload(); return NS_OK; } } } return OnScriptCompileComplete(mCurrentScriptProto->GetStencil(), rv); } NS_IMETHODIMP PrototypeDocumentContentSink::OnScriptCompileComplete(JS::Stencil* aStencil, nsresult aStatus) { // The mCurrentScriptProto may have been cleared out by another // PrototypeDocumentContentSink. if (!mCurrentScriptProto) { return NS_OK; } // When compiling off thread the script will not have been attached to the // script proto yet. if (aStencil && !mCurrentScriptProto->HasStencil()) { mCurrentScriptProto->Set(aStencil); } // Allow load events to be fired once off thread compilation finishes. if (mOffThreadCompiling) { mOffThreadCompiling = false; mDocument->UnblockOnload(false); } // Clear mCurrentScriptProto now, but save it first for use below in // the execute code, and in the while loop that resumes walks of other // documents that raced to load this script. nsXULPrototypeScript* scriptProto = mCurrentScriptProto; mCurrentScriptProto = nullptr; // Clear the prototype's loading flag before executing the script or // resuming document walks, in case any of those control flows starts a // new script load. scriptProto->mSrcLoading = false; nsresult rv = aStatus; if (NS_SUCCEEDED(rv)) { rv = ExecuteScript(scriptProto); // If the XUL cache is enabled, save the script object there in // case different XUL documents source the same script. // // But don't save the script in the cache unless the master XUL // document URL is a chrome: URL. It is valid for a URL such as // about:config to translate into a master document URL, whose // prototype document nodes -- including prototype scripts that // hold GC roots protecting their mJSObject pointers -- are not // cached in the XUL prototype cache. See StartDocumentLoad, // the fillXULCache logic. // // A document such as about:config is free to load a script via // a URL such as chrome://global/content/config.js, and we must // not cache that script object without a prototype cache entry // containing a companion nsXULPrototypeScript node that owns a // GC root protecting the script object. Otherwise, the script // cache entry will dangle once the uncached prototype document // is released when its owning document is unloaded. // // (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for // the true crime story.) bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled(); if (useXULCache && mDocumentURI->SchemeIs("chrome") && scriptProto->HasStencil()) { nsXULPrototypeCache::GetInstance()->PutStencil(scriptProto->mSrcURI, scriptProto->GetStencil()); } // ignore any evaluation errors } rv = ResumeWalk(); // Load a pointer to the prototype-script's list of documents who // raced to load the same script PrototypeDocumentContentSink** docp = &scriptProto->mSrcLoadWaiters; // Resume walking other documents that waited for this one's load, first // executing the script we just compiled, in each doc's script context PrototypeDocumentContentSink* doc; while ((doc = *docp) != nullptr) { NS_ASSERTION(doc->mCurrentScriptProto == scriptProto, "waiting for wrong script to load?"); doc->mCurrentScriptProto = nullptr; // Unlink doc from scriptProto's list before executing and resuming *docp = doc->mNextSrcLoadWaiter; doc->mNextSrcLoadWaiter = nullptr; if (aStatus == NS_BINDING_ABORTED && !scriptProto->HasStencil()) { // If the previous doc load was aborted, we want to try loading // again for the next doc. Otherwise, one abort would lead to all // subsequent waiting docs to abort as well. bool block = false; doc->LoadScript(scriptProto, &block); NS_RELEASE(doc); return rv; } // Execute only if we loaded and compiled successfully, then resume if (NS_SUCCEEDED(aStatus) && scriptProto->HasStencil()) { doc->ExecuteScript(scriptProto); } doc->ResumeWalk(); NS_RELEASE(doc); } return rv; } nsresult PrototypeDocumentContentSink::ExecuteScript( nsXULPrototypeScript* aScript) { MOZ_ASSERT(aScript != nullptr, "null ptr"); NS_ENSURE_TRUE(aScript, NS_ERROR_NULL_POINTER); nsIScriptGlobalObject* scriptGlobalObject; bool aHasHadScriptHandlingObject; scriptGlobalObject = mDocument->GetScriptHandlingObject(aHasHadScriptHandlingObject); NS_ENSURE_TRUE(scriptGlobalObject, NS_ERROR_NOT_INITIALIZED); nsresult rv; rv = scriptGlobalObject->EnsureScriptEnvironment(); NS_ENSURE_SUCCESS(rv, rv); // Execute the precompiled script with the given version nsAutoMicroTask mt; // We're about to run script via JS_ExecuteScript, so we need an // AutoEntryScript. This is Gecko specific and not in any spec. AutoEntryScript aes(scriptGlobalObject, "precompiled XUL