diff options
Diffstat (limited to 'dom/html/nsHTMLContentSink.cpp')
-rw-r--r-- | dom/html/nsHTMLContentSink.cpp | 939 |
1 files changed, 939 insertions, 0 deletions
diff --git a/dom/html/nsHTMLContentSink.cpp b/dom/html/nsHTMLContentSink.cpp new file mode 100644 index 0000000000..e7956d674c --- /dev/null +++ b/dom/html/nsHTMLContentSink.cpp @@ -0,0 +1,939 @@ +/* -*- 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 http://mozilla.org/MPL/2.0/. */ + +/** + * This file is near-OBSOLETE. It is used for about:blank only and for the + * HTML element factory. + * Don't bother adding new stuff in this file. + */ + +#include "mozilla/ArrayUtils.h" + +#include "nsContentSink.h" +#include "nsCOMPtr.h" +#include "nsHTMLTags.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" +#include "nsIHTMLContentSink.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIURI.h" +#include "mozilla/dom/NodeInfo.h" +#include "mozilla/dom/ScriptLoader.h" +#include "nsCRT.h" +#include "prtime.h" +#include "mozilla/Logging.h" +#include "nsIContent.h" +#include "mozilla/dom/CustomElementRegistry.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/MutationObservers.h" +#include "mozilla/Preferences.h" + +#include "nsGenericHTMLElement.h" + +#include "nsIScriptElement.h" + +#include "nsDocElementCreatedNotificationRunner.h" +#include "nsGkAtoms.h" +#include "nsContentUtils.h" +#include "nsIChannel.h" +#include "mozilla/dom/Document.h" +#include "nsStubDocumentObserver.h" +#include "nsHTMLDocument.h" +#include "nsTArray.h" +#include "nsTextFragment.h" +#include "nsIScriptGlobalObject.h" +#include "nsNameSpaceManager.h" + +#include "nsError.h" +#include "nsContentPolicyUtils.h" +#include "nsIDocShell.h" +#include "nsIScriptContext.h" + +#include "nsLayoutCID.h" + +#include "nsEscape.h" +#include "nsNodeInfoManager.h" +#include "nsContentCreatorFunctions.h" +#include "mozAutoDocUpdate.h" +#include "nsTextNode.h" + +using namespace mozilla; +using namespace mozilla::dom; + +//---------------------------------------------------------------------- + +nsGenericHTMLElement* NS_NewHTMLNOTUSEDElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + FromParser aFromParser) { + MOZ_ASSERT_UNREACHABLE("The element ctor should never be called"); + return nullptr; +} + +#define HTML_TAG(_tag, _classname, _interfacename) \ + NS_NewHTML##_classname##Element, +#define HTML_OTHER(_tag) NS_NewHTMLNOTUSEDElement, +static const HTMLContentCreatorFunction sHTMLContentCreatorFunctions[] = { + NS_NewHTMLUnknownElement, +#include "nsHTMLTagList.h" +#undef HTML_TAG +#undef HTML_OTHER + NS_NewHTMLUnknownElement}; + +class SinkContext; +class HTMLContentSink; + +/** + * This class is near-OBSOLETE. It is used for about:blank only. + * Don't bother adding new stuff in this file. + */ +class HTMLContentSink : public nsContentSink, public nsIHTMLContentSink { + public: + friend class SinkContext; + + HTMLContentSink(); + + nsresult Init(Document* aDoc, nsIURI* aURI, nsISupports* aContainer, + nsIChannel* aChannel); + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLContentSink, nsContentSink) + + // nsIContentSink + NS_IMETHOD WillParse(void) override; + NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode) override; + NS_IMETHOD DidBuildModel(bool aTerminated) override; + NS_IMETHOD WillInterrupt(void) override; + void WillResume() override; + NS_IMETHOD SetParser(nsParserBase* aParser) override; + virtual void FlushPendingNotifications(FlushType aType) override; + virtual void SetDocumentCharset(NotNull<const Encoding*> aEncoding) override; + virtual nsISupports* GetTarget() override; + virtual bool IsScriptExecuting() override; + virtual bool WaitForPendingSheets() override; + virtual void ContinueInterruptedParsingAsync() override; + + // nsIHTMLContentSink + NS_IMETHOD OpenContainer(ElementType aNodeType) override; + NS_IMETHOD CloseContainer(ElementType aTag) override; + + protected: + virtual ~HTMLContentSink(); + + RefPtr<nsHTMLDocument> mHTMLDocument; + + // The maximum length of a text run + int32_t mMaxTextRun; + + RefPtr<nsGenericHTMLElement> mRoot; + RefPtr<nsGenericHTMLElement> mBody; + RefPtr<nsGenericHTMLElement> mHead; + + AutoTArray<SinkContext*, 8> mContextStack; + SinkContext* mCurrentContext; + SinkContext* mHeadContext; + + // Boolean indicating whether we've seen a <head> tag that might have had + // attributes once already. + bool mHaveSeenHead; + + // Boolean indicating whether we've notified insertion of our root content + // yet. We want to make sure to only do this once. + bool mNotifiedRootInsertion; + + nsresult FlushTags() override; + + // Routines for tags that require special handling + nsresult CloseHTML(); + nsresult OpenBody(); + nsresult CloseBody(); + + void CloseHeadContext(); + + // nsContentSink overrides + void UpdateChildCounts() override; + + void NotifyInsert(nsIContent* aContent, nsIContent* aChildContent); + void NotifyRootInsertion(); + + private: + void ContinueInterruptedParsingIfEnabled(); +}; + +class SinkContext { + public: + explicit SinkContext(HTMLContentSink* aSink); + ~SinkContext(); + + nsresult Begin(nsHTMLTag aNodeType, nsGenericHTMLElement* aRoot, + uint32_t aNumFlushed, int32_t aInsertionPoint); + nsresult OpenBody(); + nsresult CloseBody(); + nsresult End(); + + nsresult GrowStack(); + nsresult FlushTags(); + + bool IsCurrentContainer(nsHTMLTag aTag) const; + + void DidAddContent(nsIContent* aContent); + void UpdateChildCounts(); + + private: + // Function to check whether we've notified for the current content. + // What this actually does is check whether we've notified for all + // of the parent's kids. + bool HaveNotifiedForCurrentContent() const; + + public: + HTMLContentSink* mSink; + int32_t mNotifyLevel; + + struct Node { + nsHTMLTag mType; + nsGenericHTMLElement* mContent; + uint32_t mNumFlushed; + int32_t mInsertionPoint; + + nsIContent* Add(nsIContent* child); + }; + + Node* mStack; + int32_t mStackSize; + int32_t mStackPos; +}; + +nsresult NS_NewHTMLElement(Element** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + FromParser aFromParser, nsAtom* aIsAtom, + mozilla::dom::CustomElementDefinition* aDefinition) { + RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo; + + NS_ASSERTION( + nodeInfo->NamespaceEquals(kNameSpaceID_XHTML), + "Trying to create HTML elements that don't have the XHTML namespace"); + + return nsContentUtils::NewXULOrHTMLElement(aResult, nodeInfo, aFromParser, + aIsAtom, aDefinition); +} + +already_AddRefed<nsGenericHTMLElement> CreateHTMLElement( + uint32_t aNodeType, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + FromParser aFromParser) { + NS_ASSERTION( + aNodeType <= NS_HTML_TAG_MAX || aNodeType == eHTMLTag_userdefined, + "aNodeType is out of bounds"); + + HTMLContentCreatorFunction cb = sHTMLContentCreatorFunctions[aNodeType]; + + NS_ASSERTION(cb != NS_NewHTMLNOTUSEDElement, + "Don't know how to construct tag element!"); + + RefPtr<nsGenericHTMLElement> result = cb(std::move(aNodeInfo), aFromParser); + + return result.forget(); +} + +//---------------------------------------------------------------------- + +SinkContext::SinkContext(HTMLContentSink* aSink) + : mSink(aSink), + mNotifyLevel(0), + mStack(nullptr), + mStackSize(0), + mStackPos(0) { + MOZ_COUNT_CTOR(SinkContext); +} + +SinkContext::~SinkContext() { + MOZ_COUNT_DTOR(SinkContext); + + if (mStack) { + for (int32_t i = 0; i < mStackPos; i++) { + NS_RELEASE(mStack[i].mContent); + } + delete[] mStack; + } +} + +nsresult SinkContext::Begin(nsHTMLTag aNodeType, nsGenericHTMLElement* aRoot, + uint32_t aNumFlushed, int32_t aInsertionPoint) { + if (mStackSize < 1) { + nsresult rv = GrowStack(); + if (NS_FAILED(rv)) { + return rv; + } + } + + mStack[0].mType = aNodeType; + mStack[0].mContent = aRoot; + mStack[0].mNumFlushed = aNumFlushed; + mStack[0].mInsertionPoint = aInsertionPoint; + NS_ADDREF(aRoot); + mStackPos = 1; + + return NS_OK; +} + +bool SinkContext::IsCurrentContainer(nsHTMLTag aTag) const { + return aTag == mStack[mStackPos - 1].mType; +} + +void SinkContext::DidAddContent(nsIContent* aContent) { + if ((mStackPos == 2) && (mSink->mBody == mStack[1].mContent)) { + // We just finished adding something to the body + mNotifyLevel = 0; + } + + // If we just added content to a node for which + // an insertion happen, we need to do an immediate + // notification for that insertion. + if (0 < mStackPos && mStack[mStackPos - 1].mInsertionPoint != -1 && + mStack[mStackPos - 1].mNumFlushed < + mStack[mStackPos - 1].mContent->GetChildCount()) { + nsIContent* parent = mStack[mStackPos - 1].mContent; + mSink->NotifyInsert(parent, aContent); + mStack[mStackPos - 1].mNumFlushed = parent->GetChildCount(); + } else if (mSink->IsTimeToNotify()) { + FlushTags(); + } +} + +nsresult SinkContext::OpenBody() { + if (mStackPos <= 0) { + NS_ERROR("container w/o parent"); + + return NS_ERROR_FAILURE; + } + + nsresult rv; + if (mStackPos + 1 > mStackSize) { + rv = GrowStack(); + if (NS_FAILED(rv)) { + return rv; + } + } + + RefPtr<mozilla::dom::NodeInfo> nodeInfo = + mSink->mNodeInfoManager->GetNodeInfo( + nsGkAtoms::body, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE); + NS_ENSURE_TRUE(nodeInfo, NS_ERROR_UNEXPECTED); + + // Make the content object + RefPtr<nsGenericHTMLElement> body = + NS_NewHTMLBodyElement(nodeInfo.forget(), FROM_PARSER_NETWORK); + if (!body) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mStack[mStackPos].mType = eHTMLTag_body; + body.forget(&mStack[mStackPos].mContent); + mStack[mStackPos].mNumFlushed = 0; + mStack[mStackPos].mInsertionPoint = -1; + ++mStackPos; + mStack[mStackPos - 2].Add(mStack[mStackPos - 1].mContent); + + return NS_OK; +} + +bool SinkContext::HaveNotifiedForCurrentContent() const { + if (0 < mStackPos) { + nsIContent* parent = mStack[mStackPos - 1].mContent; + return mStack[mStackPos - 1].mNumFlushed == parent->GetChildCount(); + } + + return true; +} + +nsIContent* SinkContext::Node::Add(nsIContent* child) { + NS_ASSERTION(mContent, "No parent to insert/append into!"); + if (mInsertionPoint != -1) { + NS_ASSERTION(mNumFlushed == mContent->GetChildCount(), + "Inserting multiple children without flushing."); + nsCOMPtr<nsIContent> nodeToInsertBefore = + mContent->GetChildAt_Deprecated(mInsertionPoint++); + mContent->InsertChildBefore(child, nodeToInsertBefore, false, + IgnoreErrors()); + } else { + mContent->AppendChildTo(child, false, IgnoreErrors()); + } + return child; +} + +nsresult SinkContext::CloseBody() { + NS_ASSERTION(mStackPos > 0, "stack out of bounds. wrong context probably!"); + + if (mStackPos <= 0) { + return NS_OK; // Fix crash - Ref. bug 45975 or 45007 + } + + --mStackPos; + NS_ASSERTION(mStack[mStackPos].mType == eHTMLTag_body, + "Tag mismatch. Closing tag on wrong context or something?"); + + nsGenericHTMLElement* content = mStack[mStackPos].mContent; + + content->Compact(); + + // If we're in a state where we do append notifications as + // we go up the tree, and we're at the level where the next + // notification needs to be done, do the notification. + if (mNotifyLevel >= mStackPos) { + // Check to see if new content has been added after our last + // notification + + if (mStack[mStackPos].mNumFlushed < content->GetChildCount()) { + mSink->NotifyAppend(content, mStack[mStackPos].mNumFlushed); + mStack[mStackPos].mNumFlushed = content->GetChildCount(); + } + + // Indicate that notification has now happened at this level + mNotifyLevel = mStackPos - 1; + } + + DidAddContent(content); + NS_IF_RELEASE(content); + + return NS_OK; +} + +nsresult SinkContext::End() { + for (int32_t i = 0; i < mStackPos; i++) { + NS_RELEASE(mStack[i].mContent); + } + + mStackPos = 0; + + return NS_OK; +} + +nsresult SinkContext::GrowStack() { + int32_t newSize = mStackSize * 2; + if (newSize == 0) { + newSize = 32; + } + + Node* stack = new Node[newSize]; + + if (mStackPos != 0) { + memcpy(stack, mStack, sizeof(Node) * mStackPos); + delete[] mStack; + } + + mStack = stack; + mStackSize = newSize; + + return NS_OK; +} + +/** + * NOTE!! Forked into nsXMLContentSink. Please keep in sync. + * + * Flush all elements that have been seen so far such that + * they are visible in the tree. Specifically, make sure + * that they are all added to their respective parents. + * Also, do notification at the top for all content that + * has been newly added so that the frame tree is complete. + */ +nsresult SinkContext::FlushTags() { + mSink->mDeferredFlushTags = false; + uint32_t oldUpdates = mSink->mUpdatesInNotification; + + ++(mSink->mInNotification); + mSink->mUpdatesInNotification = 0; + { + // Scope so we call EndUpdate before we decrease mInNotification + mozAutoDocUpdate updateBatch(mSink->mDocument, true); + + // Start from the base of the stack (growing downward) and do + // a notification from the node that is closest to the root of + // tree for any content that has been added. + + // Note that we can start at stackPos == 0 here, because it's the caller's + // responsibility to handle flushing interactions between contexts (see + // HTMLContentSink::BeginContext). + int32_t stackPos = 0; + bool flushed = false; + uint32_t childCount; + nsGenericHTMLElement* content; + + while (stackPos < mStackPos) { + content = mStack[stackPos].mContent; + childCount = content->GetChildCount(); + + if (!flushed && (mStack[stackPos].mNumFlushed < childCount)) { + if (mStack[stackPos].mInsertionPoint != -1) { + // We might have popped the child off our stack already + // but not notified on it yet, which is why we have to get it + // directly from its parent node. + + int32_t childIndex = mStack[stackPos].mInsertionPoint - 1; + nsIContent* child = content->GetChildAt_Deprecated(childIndex); + // Child not on stack anymore; can't assert it's correct + NS_ASSERTION(!(mStackPos > (stackPos + 1)) || + (child == mStack[stackPos + 1].mContent), + "Flushing the wrong child."); + mSink->NotifyInsert(content, child); + } else { + mSink->NotifyAppend(content, mStack[stackPos].mNumFlushed); + } + + flushed = true; + } + + mStack[stackPos].mNumFlushed = childCount; + stackPos++; + } + mNotifyLevel = mStackPos - 1; + } + --(mSink->mInNotification); + + if (mSink->mUpdatesInNotification > 1) { + UpdateChildCounts(); + } + + mSink->mUpdatesInNotification = oldUpdates; + + return NS_OK; +} + +/** + * NOTE!! Forked into nsXMLContentSink. Please keep in sync. + */ +void SinkContext::UpdateChildCounts() { + // Start from the top of the stack (growing upwards) and see if any + // new content has been appended. If so, we recognize that reflows + // have been generated for it and we should make sure that no + // further reflows occur. Note that we have to include stackPos == 0 + // to properly notify on kids of <html>. + int32_t stackPos = mStackPos - 1; + while (stackPos >= 0) { + Node& node = mStack[stackPos]; + node.mNumFlushed = node.mContent->GetChildCount(); + + stackPos--; + } + + mNotifyLevel = mStackPos - 1; +} + +nsresult NS_NewHTMLContentSink(nsIHTMLContentSink** aResult, Document* aDoc, + nsIURI* aURI, nsISupports* aContainer, + nsIChannel* aChannel) { + NS_ENSURE_ARG_POINTER(aResult); + + RefPtr<HTMLContentSink> it = new HTMLContentSink(); + + nsresult rv = it->Init(aDoc, aURI, aContainer, aChannel); + + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = it; + NS_ADDREF(*aResult); + + return NS_OK; +} + +HTMLContentSink::HTMLContentSink() + : mMaxTextRun(0), + mCurrentContext(nullptr), + mHeadContext(nullptr), + mHaveSeenHead(false), + mNotifiedRootInsertion(false) {} + +HTMLContentSink::~HTMLContentSink() { + if (mNotificationTimer) { + mNotificationTimer->Cancel(); + } + + if (mCurrentContext == mHeadContext && !mContextStack.IsEmpty()) { + // Pop off the second html context if it's not done earlier + mContextStack.RemoveLastElement(); + } + + for (int32_t i = 0, numContexts = mContextStack.Length(); i < numContexts; + i++) { + SinkContext* sc = mContextStack.ElementAt(i); + if (sc) { + sc->End(); + if (sc == mCurrentContext) { + mCurrentContext = nullptr; + } + + delete sc; + } + } + + if (mCurrentContext == mHeadContext) { + mCurrentContext = nullptr; + } + + delete mCurrentContext; + + delete mHeadContext; +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLContentSink, nsContentSink, + mHTMLDocument, mRoot, mBody, mHead) + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLContentSink, nsContentSink, + nsIContentSink, nsIHTMLContentSink) + +nsresult HTMLContentSink::Init(Document* aDoc, nsIURI* aURI, + nsISupports* aContainer, nsIChannel* aChannel) { + NS_ENSURE_TRUE(aContainer, NS_ERROR_NULL_POINTER); + + nsresult rv = nsContentSink::Init(aDoc, aURI, aContainer, aChannel); + if (NS_FAILED(rv)) { + return rv; + } + + aDoc->AddObserver(this); + mIsDocumentObserver = true; + mHTMLDocument = aDoc->AsHTMLDocument(); + + NS_ASSERTION(mDocShell, "oops no docshell!"); + + // Changed from 8192 to greatly improve page loading performance on + // large pages. See bugzilla bug 77540. + mMaxTextRun = Preferences::GetInt("content.maxtextrun", 8191); + + RefPtr<mozilla::dom::NodeInfo> nodeInfo; + nodeInfo = mNodeInfoManager->GetNodeInfo( + nsGkAtoms::html, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE); + + // Make root part + mRoot = NS_NewHTMLHtmlElement(nodeInfo.forget()); + if (!mRoot) { + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_ASSERTION(mDocument->GetChildCount() == 0, + "Document should have no kids here!"); + ErrorResult error; + mDocument->AppendChildTo(mRoot, false, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + // Make head part + nodeInfo = mNodeInfoManager->GetNodeInfo( + nsGkAtoms::head, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE); + + mHead = NS_NewHTMLHeadElement(nodeInfo.forget()); + if (NS_FAILED(rv)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mRoot->AppendChildTo(mHead, false, IgnoreErrors()); + + mCurrentContext = new SinkContext(this); + mCurrentContext->Begin(eHTMLTag_html, mRoot, 0, -1); + mContextStack.AppendElement(mCurrentContext); + + return NS_OK; +} + +NS_IMETHODIMP +HTMLContentSink::WillParse(void) { return WillParseImpl(); } + +NS_IMETHODIMP +HTMLContentSink::WillBuildModel(nsDTDMode aDTDMode) { + WillBuildModelImpl(); + + mDocument->SetCompatibilityMode(aDTDMode == eDTDMode_full_standards + ? eCompatibility_FullStandards + : eCompatibility_NavQuirks); + + // Notify document that the load is beginning + mDocument->BeginLoad(); + + return NS_OK; +} + +NS_IMETHODIMP +HTMLContentSink::DidBuildModel(bool aTerminated) { + DidBuildModelImpl(aTerminated); + + // Reflow the last batch of content + if (mBody) { + mCurrentContext->FlushTags(); + } else if (!mLayoutStarted) { + // We never saw the body, and layout never got started. Force + // layout *now*, to get an initial reflow. + // NOTE: only force the layout if we are NOT destroying the + // docshell. If we are destroying it, then starting layout will + // likely cause us to crash, or at best waste a lot of time as we + // are just going to tear it down anyway. + bool bDestroying = true; + if (mDocShell) { + mDocShell->IsBeingDestroyed(&bDestroying); + } + + if (!bDestroying) { + StartLayout(false); + } + } + + ScrollToRef(); + + // Make sure we no longer respond to document mutations. We've flushed all + // our notifications out, so there's no need to do anything else here. + + // XXXbz I wonder whether we could End() our contexts here too, or something, + // just to make sure we no longer notify... Or is the mIsDocumentObserver + // thing sufficient? + mDocument->RemoveObserver(this); + mIsDocumentObserver = false; + + mDocument->EndLoad(); + + DropParserAndPerfHint(); + + return NS_OK; +} + +NS_IMETHODIMP +HTMLContentSink::SetParser(nsParserBase* aParser) { + MOZ_ASSERT(aParser, "Should have a parser here!"); + mParser = aParser; + return NS_OK; +} + +nsresult HTMLContentSink::CloseHTML() { + if (mHeadContext) { + if (mCurrentContext == mHeadContext) { + // Pop off the second html context if it's not done earlier + mCurrentContext = mContextStack.PopLastElement(); + } + + mHeadContext->End(); + + delete mHeadContext; + mHeadContext = nullptr; + } + + return NS_OK; +} + +nsresult HTMLContentSink::OpenBody() { + CloseHeadContext(); // do this just in case if the HEAD was left open! + + // if we already have a body we're done + if (mBody) { + return NS_OK; + } + + nsresult rv = mCurrentContext->OpenBody(); + + if (NS_FAILED(rv)) { + return rv; + } + + mBody = mCurrentContext->mStack[mCurrentContext->mStackPos - 1].mContent; + + if (mCurrentContext->mStackPos > 1) { + int32_t parentIndex = mCurrentContext->mStackPos - 2; + nsGenericHTMLElement* parent = + mCurrentContext->mStack[parentIndex].mContent; + int32_t numFlushed = mCurrentContext->mStack[parentIndex].mNumFlushed; + int32_t childCount = parent->GetChildCount(); + NS_ASSERTION(numFlushed < childCount, "Already notified on the body?"); + + int32_t insertionPoint = + mCurrentContext->mStack[parentIndex].mInsertionPoint; + + // XXX: I have yet to see a case where numFlushed is non-zero and + // insertionPoint is not -1, but this code will try to handle + // those cases too. + + uint32_t oldUpdates = mUpdatesInNotification; + mUpdatesInNotification = 0; + if (insertionPoint != -1) { + NotifyInsert(parent, mBody); + } else { + NotifyAppend(parent, numFlushed); + } + mCurrentContext->mStack[parentIndex].mNumFlushed = childCount; + if (mUpdatesInNotification > 1) { + UpdateChildCounts(); + } + mUpdatesInNotification = oldUpdates; + } + + StartLayout(false); + + return NS_OK; +} + +nsresult HTMLContentSink::CloseBody() { + // Flush out anything that's left + mCurrentContext->FlushTags(); + mCurrentContext->CloseBody(); + + return NS_OK; +} + +NS_IMETHODIMP +HTMLContentSink::OpenContainer(ElementType aElementType) { + nsresult rv = NS_OK; + + switch (aElementType) { + case eBody: + rv = OpenBody(); + break; + case eHTML: + if (mRoot) { + // If we've already hit this code once, then we're done + if (!mNotifiedRootInsertion) { + NotifyRootInsertion(); + } + } + break; + } + + return rv; +} + +NS_IMETHODIMP +HTMLContentSink::CloseContainer(const ElementType aTag) { + nsresult rv = NS_OK; + + switch (aTag) { + case eBody: + rv = CloseBody(); + break; + case eHTML: + rv = CloseHTML(); + break; + } + + return rv; +} + +NS_IMETHODIMP +HTMLContentSink::WillInterrupt() { return WillInterruptImpl(); } + +void HTMLContentSink::WillResume() { WillResumeImpl(); } + +void HTMLContentSink::CloseHeadContext() { + if (mCurrentContext) { + if (!mCurrentContext->IsCurrentContainer(eHTMLTag_head)) return; + + mCurrentContext->FlushTags(); + } + + if (!mContextStack.IsEmpty()) { + mCurrentContext = mContextStack.PopLastElement(); + } +} + +void HTMLContentSink::NotifyInsert(nsIContent* aContent, + nsIContent* aChildContent) { + mInNotification++; + + { + // Scope so we call EndUpdate before we decrease mInNotification + // Note that aContent->OwnerDoc() may be different to mDocument already. + MOZ_AUTO_DOC_UPDATE(aContent ? aContent->OwnerDoc() : mDocument.get(), + true); + MutationObservers::NotifyContentInserted(NODE_FROM(aContent, mDocument), + aChildContent); + mLastNotificationTime = PR_Now(); + } + + mInNotification--; +} + +void HTMLContentSink::NotifyRootInsertion() { + MOZ_ASSERT(!mNotifiedRootInsertion, "Double-notifying on root?"); + NS_ASSERTION(!mLayoutStarted, + "How did we start layout without notifying on root?"); + // Now make sure to notify that we have now inserted our root. If + // there has been no initial reflow yet it'll be a no-op, but if + // there has been one we need this to get its frames constructed. + // Note that if mNotifiedRootInsertion is true we don't notify here, + // since that just means there are multiple <html> tags in the + // document; in those cases we just want to put all the attrs on one + // tag. + mNotifiedRootInsertion = true; + NotifyInsert(nullptr, mRoot); + + // Now update the notification information in all our + // contexts, since we just inserted the root and notified on + // our whole tree + UpdateChildCounts(); + + nsContentUtils::AddScriptRunner( + new nsDocElementCreatedNotificationRunner(mDocument)); +} + +void HTMLContentSink::UpdateChildCounts() { + uint32_t numContexts = mContextStack.Length(); + for (uint32_t i = 0; i < numContexts; i++) { + SinkContext* sc = mContextStack.ElementAt(i); + + sc->UpdateChildCounts(); + } + + mCurrentContext->UpdateChildCounts(); +} + +void HTMLContentSink::FlushPendingNotifications(FlushType aType) { + // Only flush tags if we're not doing the notification ourselves + // (since we aren't reentrant) + if (!mInNotification) { + // Only flush if we're still a document observer (so that our child counts + // should be correct). + if (mIsDocumentObserver) { + if (aType >= FlushType::ContentAndNotify) { + FlushTags(); + } + } + if (aType >= FlushType::EnsurePresShellInitAndFrames) { + // Make sure that layout has started so that the reflow flush + // will actually happen. + StartLayout(true); + } + } +} + +nsresult HTMLContentSink::FlushTags() { + if (!mNotifiedRootInsertion) { + NotifyRootInsertion(); + return NS_OK; + } + + return mCurrentContext ? mCurrentContext->FlushTags() : NS_OK; +} + +void HTMLContentSink::SetDocumentCharset(NotNull<const Encoding*> aEncoding) { + MOZ_ASSERT_UNREACHABLE("<meta charset> case doesn't occur with about:blank"); +} + +nsISupports* HTMLContentSink::GetTarget() { return ToSupports(mDocument); } + +bool HTMLContentSink::IsScriptExecuting() { return IsScriptExecutingImpl(); } + +void HTMLContentSink::ContinueInterruptedParsingIfEnabled() { + if (mParser && mParser->IsParserEnabled()) { + static_cast<nsIParser*>(mParser.get())->ContinueInterruptedParsing(); + } +} + +bool HTMLContentSink::WaitForPendingSheets() { + return nsContentSink::WaitForPendingSheets(); +} + +void HTMLContentSink::ContinueInterruptedParsingAsync() { + nsCOMPtr<nsIRunnable> ev = NewRunnableMethod( + "HTMLContentSink::ContinueInterruptedParsingIfEnabled", this, + &HTMLContentSink::ContinueInterruptedParsingIfEnabled); + + RefPtr<Document> doc = mHTMLDocument; + doc->Dispatch(mozilla::TaskCategory::Other, ev.forget()); +} |