/* -*- 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 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 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(mParser.get()); } void PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled() { if (mParser && mParser->IsParserEnabled()) { GetParser()->ContinueInterruptedParsing(); } } void PrototypeDocumentContentSink::ContinueInterruptedParsingAsync() { nsCOMPtr 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 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 >& 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 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 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(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 sele = do_QueryInterface(aElement); MOZ_ASSERT(sele, "Node didn't QI to script."); if (sele->GetScriptIsModule()) { DebugOnly block = sele->AttemptToExecute(); MOZ_ASSERT(!block, "