diff options
Diffstat (limited to 'dom/prototype/PrototypeDocumentContentSink.cpp')
-rw-r--r-- | dom/prototype/PrototypeDocumentContentSink.cpp | 1137 |
1 files changed, 1137 insertions, 0 deletions
diff --git a/dom/prototype/PrototypeDocumentContentSink.cpp b/dom/prototype/PrototypeDocumentContentSink.cpp new file mode 100644 index 0000000000..2c299f6a91 --- /dev/null +++ b/dom/prototype/PrototypeDocumentContentSink.cpp @@ -0,0 +1,1137 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "mozilla/dom/PrototypeDocumentContentSink.h" +#include "nsIParser.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/URL.h" +#include "nsIContent.h" +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "nsHTMLParts.h" +#include "nsCRT.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/css/Loader.h" +#include "nsGkAtoms.h" +#include "nsContentUtils.h" +#include "nsDocElementCreatedNotificationRunner.h" +#include "nsIScriptContext.h" +#include "nsNameSpaceManager.h" +#include "nsIScriptError.h" +#include "prtime.h" +#include "mozilla/Logging.h" +#include "nsRect.h" +#include "nsIScriptElement.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" +#include "nsIChannel.h" +#include "nsNodeInfoManager.h" +#include "nsContentCreatorFunctions.h" +#include "nsIContentPolicy.h" +#include "nsContentPolicyUtils.h" +#include "nsError.h" +#include "nsIScriptGlobalObject.h" +#include "mozAutoDocUpdate.h" +#include "nsMimeTypes.h" +#include "nsHtml5SVGLoadDispatcher.h" +#include "nsTextNode.h" +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/dom/CDATASection.h" +#include "mozilla/dom/Comment.h" +#include "mozilla/dom/DocumentType.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLTemplateElement.h" +#include "mozilla/dom/ProcessingInstruction.h" +#include "mozilla/dom/XMLStylesheetProcessingInstruction.h" +#include "mozilla/dom/ScriptLoader.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/PresShell.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/RefPtr.h" + +#include "nsXULPrototypeCache.h" +#include "nsXULElement.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "js/CompilationAndEvaluation.h" +#include "js/experimental/JSStencil.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<PrototypeDocumentContentSink> 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), + mOffThreadCompileStringBuf(nullptr), + mOffThreadCompileStringLength(0), + mStillWalking(false), + mPendingSheets(0) {} + +PrototypeDocumentContentSink::~PrototypeDocumentContentSink() { + NS_ASSERTION( + mNextSrcLoadWaiter == nullptr, + "unreferenced document still waiting for script source to load?"); + + if (mOffThreadCompileStringBuf) { + js_free(mOffThreadCompileStringBuf); + } +} + +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::CreateContentViewer. + nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(mDocumentURI)); + NS_ENSURE_SUCCESS(rv, rv); + + mScriptLoader = mDocument->ScriptLoader(); + + 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<const Encoding*> aEncoding) { + if (mDocument) { + mDocument->SetDocumentCharacterSet(aEncoding); + } +} + +nsISupports* PrototypeDocumentContentSink::GetTarget() { + return ToSupports(mDocument); +} + +bool PrototypeDocumentContentSink::IsScriptExecuting() { + 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<nsIParser*>(mParser.get()); +} + +void PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled() { + if (mParser && mParser->IsParserEnabled()) { + GetParser()->ContinueInterruptedParsing(); + } +} + +void PrototypeDocumentContentSink::ContinueInterruptedParsingAsync() { + nsCOMPtr<nsIRunnable> ev = NewRunnableMethod( + "PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled", this, + &PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled); + + mDocument->Dispatch(mozilla::TaskCategory::Other, 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(); + + // 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<nsIURI> 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; + } + + nsINode* nodeToInsertBefore = mDocument->GetFirstChild(); + + const nsTArray<RefPtr<nsXULPrototypePI> >& processingInstructions = + mCurrentPrototype->GetProcessingInstructions(); + + uint32_t total = processingInstructions.Length(); + for (uint32_t i = 0; i < total; ++i) { + rv = CreateAndInsertPI(processingInstructions[i], mDocument, + nodeToInsertBefore); + if (NS_FAILED(rv)) return rv; + } + + // Do one-time initialization. + RefPtr<Element> 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, nsINode* aBeforeThis) { + MOZ_ASSERT(aProtoPI, "null ptr"); + MOZ_ASSERT(aParent, "null ptr"); + + RefPtr<ProcessingInstruction> node = + NS_NewXMLProcessingInstruction(aParent->OwnerDoc()->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<XMLStylesheetProcessingInstruction*>(node.get()); + rv = InsertXMLStylesheetPI(aProtoPI, aParent, aBeforeThis, pi); + } else { + // No special processing, just add the PI to the document. + ErrorResult error; + aParent->InsertChildBefore(node->AsContent(), + aBeforeThis ? aBeforeThis->AsContent() : nullptr, + false, error); + rv = error.StealNSResult(); + } + + return rv; +} + +nsresult PrototypeDocumentContentSink::InsertXMLStylesheetPI( + const nsXULPrototypePI* aProtoPI, nsINode* aParent, nsINode* aBeforeThis, + XMLStylesheetProcessingInstruction* aPINode) { + // We want to be notified when the style sheet finishes loading, so + // disable style sheet loading for now. + aPINode->SetEnableUpdates(false); + aPINode->OverrideBaseURI(mCurrentPrototype->GetURI()); + + ErrorResult rv; + aParent->InsertChildBefore( + aPINode, aBeforeThis ? aBeforeThis->AsContent() : nullptr, false, rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + + aPINode->SetEnableUpdates(true); + + // load the stylesheet if necessary, passing ourselves as + // nsICSSObserver + auto result = aPINode->UpdateStyleSheet(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, + bool aHadChildren) { + if (nsIContent::RequiresDoneAddingChildren( + aElement->NodeInfo()->NamespaceID(), + aElement->NodeInfo()->NameAtom())) { + aElement->DoneAddingChildren(false); + } + + if (!aHadChildren) { + return; + } + + // See bug 370111 and bug 1495946. We don't cache inline styles nor module + // scripts in the prototype cache, and we don't notify on node insertion, so + // we need to do this for the stylesheet / script to be properly processed. + // This kinda sucks, but notifying was a pretty sizeable perf regression so... + if (aElement->IsHTMLElement(nsGkAtoms::script) || + aElement->IsSVGElement(nsGkAtoms::script)) { + nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aElement); + MOZ_ASSERT(sele, "Node didn't QI to script."); + if (sele->GetScriptIsModule()) { + DebugOnly<bool> block = sele->AttemptToExecute(); + MOZ_ASSERT(!block, "<script type=module> shouldn't block the parser"); + } + } + + if (aElement->IsHTMLElement(nsGkAtoms::style) || + aElement->IsSVGElement(nsGkAtoms::style)) { + auto* linkStyle = LinkStyle::FromNode(*aElement); + NS_ASSERTION(linkStyle, + "<html:style> doesn't implement " + "nsIStyleSheetLinkingElement?"); + Unused << linkStyle->UpdateStyleSheet(nullptr); + } +} + +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, + mDocumentURI); + } + 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., + // <html:script src="..." />) can be properly re-loaded if the + // cached copy of the document becomes stale. + nsresult rv; + nsCOMPtr<nsIURI> docURI = + mCurrentPrototype ? mCurrentPrototype->GetURI() : nullptr; + + while (1) { + // 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<nsIContent> element; + nsCOMPtr<nsIContent> 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(), /* aHadChildren = */ true); + } + // 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<nsXULPrototypeElement*>(childproto); + + RefPtr<Element> child; + + rv = CreateElementFromPrototype(protoele, getter_AddRefs(child), + nodeToPushTo); + if (NS_FAILED(rv)) return rv; + + // ...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, /* aHadChildren = */ false); + } + } 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<nsXULPrototypeScript*>(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<nsTextNode> text = new (nim) nsTextNode(nim); + + auto* textproto = static_cast<nsXULPrototypeText*>(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<nsXULPrototypePI*>(childproto); + + // <?xml-stylesheet?> doesn't have an effect + // outside the prolog, like it used to. Issue a warning. + + if (piProto->mTarget.EqualsLiteral("xml-stylesheet")) { + AutoTArray<nsString, 1> params = {piProto->mTarget}; + + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + "XUL Document"_ns, nullptr, + nsContentUtils::eXUL_PROPERTIES, + "PINotInProlog", params, docURI); + } + + nsIContent* parent = element.get(); + if (parent) { + // an inline script could have removed the root element + rv = CreateAndInsertPI(piProto, parent, nullptr); + 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, ToSupports(mDocument), + u"MozBeforeInitialXULLayout"_ns, + CanBubble::eYes, Cancelable::eNo); + } + + if (mScriptLoader) { + mScriptLoader->ParsingComplete(false); + mScriptLoader->DeferCheckpointReached(); + } + + StartLayout(); + + if (IsChromeURI(mDocumentURI) && + nsXULPrototypeCache::GetInstance()->IsEnabled()) { + bool isCachedOnDisk; + nsXULPrototypeCache::GetInstance()->HasPrototype(mDocumentURI, + &isCachedOnDisk); + if (!isCachedOnDisk) { + nsXULPrototypeCache::GetInstance()->WritePrototype(mCurrentPrototype); + } + } + + mDocument->SetDelayFrameLoaderInitialization(false); + RefPtr<Document> 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> 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 = IsChromeURI(mDocumentURI); + + 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<JS::Stencil> 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<nsILoadGroup> group = + mDocument + ->GetDocumentLoadGroup(); // found in + // mozilla::dom::Document::SetScriptGlobalObject + + // Note: the loader will keep itself alive while it's loading. + nsCOMPtr<nsIStreamLoader> 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<nsIRequest> request; + aLoader->GetRequest(getter_AddRefs(request)); + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + +#ifdef DEBUG + // print a load error on bad status + if (NS_FAILED(aStatus)) { + if (channel) { + nsCOMPtr<nsIURI> 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<nsIURI> uri = mCurrentScriptProto->mSrcURI; + + // XXX should also check nsIHttpChannel::requestSucceeded + + MOZ_ASSERT(!mOffThreadCompiling && (mOffThreadCompileStringLength == 0 && + !mOffThreadCompileStringBuf), + "PrototypeDocument can't load multiple scripts at once"); + + rv = ScriptLoader::ConvertToUTF8(channel, string, stringLen, u""_ns, + mDocument, mOffThreadCompileStringBuf, + mOffThreadCompileStringLength); + if (NS_SUCCEEDED(rv)) { + // Pass ownership of the buffer, carefully emptying the existing + // fields in the process. Note that the |Compile| function called + // below always takes ownership of the buffer. + Utf8Unit* units = nullptr; + size_t unitsLength = 0; + + std::swap(units, mOffThreadCompileStringBuf); + std::swap(unitsLength, mOffThreadCompileStringLength); + + rv = mCurrentScriptProto->Compile(units, unitsLength, + JS::SourceOwnership::TakeOwnership, 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); + } + + // After compilation finishes the script's characters are no longer needed. + if (mOffThreadCompileStringBuf) { + js_free(mOffThreadCompileStringBuf); + mOffThreadCompileStringBuf = nullptr; + mOffThreadCompileStringLength = 0; + } + + // 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 && IsChromeURI(mDocumentURI) && 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 <script> element"); + JSContext* cx = aes.cx(); + + JS::Rooted<JSScript*> scriptObject(cx); + rv = aScript->InstantiateScript(cx, &scriptObject); + NS_ENSURE_SUCCESS(rv, rv); + + JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); + NS_ENSURE_TRUE(xpc::Scriptability::Get(global).Allowed(), NS_OK); + + // On failure, ~AutoScriptEntry will handle exceptions, so + // there is no need to manually check the return value. + JS::Rooted<JS::Value> rval(cx); + Unused << JS_ExecuteScript(cx, scriptObject, &rval); + + return NS_OK; +} + +nsresult PrototypeDocumentContentSink::CreateElementFromPrototype( + nsXULPrototypeElement* aPrototype, Element** aResult, nsIContent* aParent) { + // Create a content model element from a prototype element. + MOZ_ASSERT(aPrototype, "null ptr"); + if (!aPrototype) return NS_ERROR_NULL_POINTER; + + *aResult = nullptr; + nsresult rv = NS_OK; + + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + MOZ_LOG( + gLog, LogLevel::Debug, + ("prototype: creating <%s> from prototype", + NS_ConvertUTF16toUTF8(aPrototype->mNodeInfo->QualifiedName()).get())); + } + + RefPtr<Element> result; + + Document* doc = aParent ? aParent->OwnerDoc() : mDocument.get(); + if (aPrototype->mNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) { + const bool isRoot = !aParent; + // If it's a XUL element, it'll be lightweight until somebody + // monkeys with it. + rv = nsXULElement::CreateFromPrototype(aPrototype, doc, true, isRoot, + getter_AddRefs(result)); + if (NS_FAILED(rv)) return rv; + } else { + // If it's not a XUL element, it's gonna be heavyweight no matter + // what. So we need to copy everything out of the prototype + // into the element. Get a nodeinfo from our nodeinfo manager + // for this node. + RefPtr<NodeInfo> newNodeInfo = doc->NodeInfoManager()->GetNodeInfo( + aPrototype->mNodeInfo->NameAtom(), + aPrototype->mNodeInfo->GetPrefixAtom(), + aPrototype->mNodeInfo->NamespaceID(), nsINode::ELEMENT_NODE); + if (!newNodeInfo) { + return NS_ERROR_OUT_OF_MEMORY; + } + const bool isScript = + newNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_XHTML) || + newNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_SVG); + if (aPrototype->mIsAtom && + newNodeInfo->NamespaceID() == kNameSpaceID_XHTML) { + rv = NS_NewHTMLElement(getter_AddRefs(result), newNodeInfo.forget(), + NOT_FROM_PARSER, aPrototype->mIsAtom); + } else { + rv = NS_NewElement(getter_AddRefs(result), newNodeInfo.forget(), + NOT_FROM_PARSER); + } + if (NS_FAILED(rv)) return rv; + + rv = AddAttributes(aPrototype, result); + if (NS_FAILED(rv)) return rv; + + if (isScript) { + nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(result); + MOZ_ASSERT(sele, "Node didn't QI to script."); + + sele->FreezeExecutionAttrs(doc); + // Script loading is handled by the this content sink, so prevent the + // script from loading when it is bound to the document. + // + // NOTE(emilio): This is only done for non-module scripts, because we + // don't support caching modules properly yet, see the comment in + // XULContentSinkImpl::OpenScript. For non-inline scripts, this is enough, + // since we can start the load when the node is inserted. Non-inline + // scripts need another special-case in CloseElement. + if (!sele->GetScriptIsModule()) { + sele->PreventExecution(); + } + } + } + + // FIXME(bug 1627474): Is this right if this is inside an <html:template>? + if (result->HasAttr(kNameSpaceID_None, nsGkAtoms::datal10nid)) { + mDocument->mL10nProtoElements.InsertOrUpdate(result, RefPtr{aPrototype}); + result->SetElementCreatedFromPrototypeAndHasUnmodifiedL10n(); + } + result.forget(aResult); + return NS_OK; +} + +nsresult PrototypeDocumentContentSink::AddAttributes( + nsXULPrototypeElement* aPrototype, Element* aElement) { + nsresult rv; + + for (size_t i = 0; i < aPrototype->mAttributes.Length(); ++i) { + nsXULPrototypeAttribute* protoattr = &(aPrototype->mAttributes[i]); + nsAutoString valueStr; + protoattr->mValue.ToString(valueStr); + + rv = aElement->SetAttr(protoattr->mName.NamespaceID(), + protoattr->mName.LocalName(), + protoattr->mName.GetPrefix(), valueStr, false); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; +} + +} // namespace mozilla::dom |