diff options
Diffstat (limited to 'dom/html/nsHTMLDocument.cpp')
-rw-r--r-- | dom/html/nsHTMLDocument.cpp | 747 |
1 files changed, 747 insertions, 0 deletions
diff --git a/dom/html/nsHTMLDocument.cpp b/dom/html/nsHTMLDocument.cpp new file mode 100644 index 0000000000..885e4d3d24 --- /dev/null +++ b/dom/html/nsHTMLDocument.cpp @@ -0,0 +1,747 @@ +/* -*- 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/. */ + +#include "nsHTMLDocument.h" + +#include "mozilla/DebugOnly.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_intl.h" +#include "nsCommandManager.h" +#include "nsCOMPtr.h" +#include "nsGlobalWindow.h" +#include "nsString.h" +#include "nsPrintfCString.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" +#include "nsGlobalWindowInner.h" +#include "nsIHTMLContentSink.h" +#include "nsIProtocolHandler.h" +#include "nsIXMLContentSink.h" +#include "nsHTMLParts.h" +#include "nsHTMLStyleSheet.h" +#include "nsGkAtoms.h" +#include "nsPresContext.h" +#include "nsPIDOMWindow.h" +#include "nsDOMString.h" +#include "nsIStreamListener.h" +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "nsIContentViewer.h" +#include "nsDocShell.h" +#include "nsDocShellLoadTypes.h" +#include "nsIScriptContext.h" +#include "nsContentList.h" +#include "nsError.h" +#include "nsIPrincipal.h" +#include "nsJSPrincipals.h" +#include "nsAttrName.h" + +#include "nsNetCID.h" +#include "mozilla/parser/PrototypeDocumentParser.h" +#include "mozilla/dom/PrototypeDocumentContentSink.h" +#include "nsNameSpaceManager.h" +#include "nsGenericHTMLElement.h" +#include "mozilla/css/Loader.h" +#include "nsFrameSelection.h" + +#include "nsContentUtils.h" +#include "nsJSUtils.h" +#include "DocumentInlines.h" +#include "nsICachingChannel.h" +#include "nsIScriptElement.h" +#include "nsArrayUtils.h" + +// AHMED 12-2 +#include "nsBidiUtils.h" + +#include "mozilla/Encoding.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/IdentifierMapEntry.h" +#include "mozilla/LoadInfo.h" +#include "nsNodeInfoManager.h" +#include "nsRange.h" +#include "mozAutoDocUpdate.h" +#include "nsCCUncollectableMarker.h" +#include "nsHtml5Module.h" +#include "mozilla/dom/Element.h" +#include "mozilla/Preferences.h" +#include "nsMimeTypes.h" +#include "nsIRequest.h" +#include "nsHtml5TreeOpExecutor.h" +#include "nsHtml5Parser.h" +#include "nsParser.h" +#include "nsSandboxFlags.h" +#include "mozilla/dom/HTMLBodyElement.h" +#include "mozilla/dom/HTMLDocumentBinding.h" +#include "mozilla/dom/nsCSPContext.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/dom/ShadowIncludingTreeIterator.h" +#include "nsCharsetSource.h" +#include "nsFocusManager.h" +#include "nsIFrame.h" +#include "nsIContent.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StyleSheet.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/Unused.h" + +using namespace mozilla; +using namespace mozilla::dom; + +#include "prtime.h" + +//#define DEBUG_charset + +// ================================================================== +// = +// ================================================================== + +static bool IsAsciiCompatible(const Encoding* aEncoding) { + return aEncoding->IsAsciiCompatible() || aEncoding == ISO_2022_JP_ENCODING; +} + +nsresult NS_NewHTMLDocument(Document** aInstancePtrResult, bool aLoadedAsData) { + RefPtr<nsHTMLDocument> doc = new nsHTMLDocument(); + + nsresult rv = doc->Init(); + + if (NS_FAILED(rv)) { + *aInstancePtrResult = nullptr; + return rv; + } + + doc->SetLoadedAsData(aLoadedAsData, /* aConsiderForMemoryReporting */ true); + doc.forget(aInstancePtrResult); + + return NS_OK; +} + +nsHTMLDocument::nsHTMLDocument() + : Document("text/html"), + mContentListHolder(nullptr), + mNumForms(0), + mLoadFlags(0), + mWarnedWidthHeight(false), + mIsPlainText(false), + mViewSource(false) { + mType = eHTML; + mDefaultElementType = kNameSpaceID_XHTML; + mCompatMode = eCompatibility_NavQuirks; +} + +nsHTMLDocument::~nsHTMLDocument() = default; + +JSObject* nsHTMLDocument::WrapNode(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return HTMLDocument_Binding::Wrap(aCx, this, aGivenProto); +} + +nsresult nsHTMLDocument::Init() { + nsresult rv = Document::Init(); + NS_ENSURE_SUCCESS(rv, rv); + + // Now reset the compatibility mode of the CSSLoader + // to match our compat mode. + CSSLoader()->SetCompatibilityMode(mCompatMode); + + return NS_OK; +} + +void nsHTMLDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) { + Document::Reset(aChannel, aLoadGroup); + + if (aChannel) { + aChannel->GetLoadFlags(&mLoadFlags); + } +} + +void nsHTMLDocument::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup, + nsIPrincipal* aPrincipal, + nsIPrincipal* aPartitionedPrincipal) { + mLoadFlags = nsIRequest::LOAD_NORMAL; + + Document::ResetToURI(aURI, aLoadGroup, aPrincipal, aPartitionedPrincipal); + + mImages = nullptr; + mApplets = nullptr; + mEmbeds = nullptr; + mLinks = nullptr; + mAnchors = nullptr; + mScripts = nullptr; + + mForms = nullptr; + + // Make the content type default to "text/html", we are a HTML + // document, after all. Once we start getting data, this may be + // changed. + SetContentType(nsDependentCString("text/html")); +} + +void nsHTMLDocument::TryReloadCharset(nsIContentViewer* aCv, + int32_t& aCharsetSource, + NotNull<const Encoding*>& aEncoding) { + if (aCv) { + int32_t reloadEncodingSource; + const auto reloadEncoding = + aCv->GetReloadEncodingAndSource(&reloadEncodingSource); + if (kCharsetUninitialized != reloadEncodingSource) { + aCv->ForgetReloadEncoding(); + + if (reloadEncodingSource <= aCharsetSource || + !IsAsciiCompatible(aEncoding)) { + return; + } + + if (reloadEncoding && IsAsciiCompatible(reloadEncoding)) { + aCharsetSource = reloadEncodingSource; + aEncoding = WrapNotNull(reloadEncoding); + } + } + } +} + +void nsHTMLDocument::TryUserForcedCharset(nsIContentViewer* aCv, + nsIDocShell* aDocShell, + int32_t& aCharsetSource, + NotNull<const Encoding*>& aEncoding, + bool& aForceAutoDetection) { + auto resetForce = MakeScopeExit([&] { + if (aDocShell) { + nsDocShell::Cast(aDocShell)->ResetForcedAutodetection(); + } + }); + + if (aCharsetSource >= kCharsetFromOtherComponent) { + return; + } + + // mCharacterSet not updated yet for channel, so check aEncoding, too. + if (WillIgnoreCharsetOverride() || !IsAsciiCompatible(aEncoding)) { + return; + } + + if (aDocShell && nsDocShell::Cast(aDocShell)->GetForcedAutodetection()) { + // This is the Character Encoding menu code path in Firefox + aForceAutoDetection = true; + } +} + +void nsHTMLDocument::TryParentCharset(nsIDocShell* aDocShell, + int32_t& aCharsetSource, + NotNull<const Encoding*>& aEncoding, + bool& aForceAutoDetection) { + if (!aDocShell) { + return; + } + if (aCharsetSource >= kCharsetFromOtherComponent) { + return; + } + + int32_t parentSource; + const Encoding* parentCharset; + nsCOMPtr<nsIPrincipal> parentPrincipal; + aDocShell->GetParentCharset(parentCharset, &parentSource, + getter_AddRefs(parentPrincipal)); + if (!parentCharset) { + return; + } + if (kCharsetFromInitialUserForcedAutoDetection == parentSource || + kCharsetFromFinalUserForcedAutoDetection == parentSource) { + if (WillIgnoreCharsetOverride() || + !IsAsciiCompatible(aEncoding) || // if channel said UTF-16 + !IsAsciiCompatible(parentCharset)) { + return; + } + aEncoding = WrapNotNull(parentCharset); + aCharsetSource = kCharsetFromParentFrame; + aForceAutoDetection = true; + return; + } + + if (aCharsetSource >= kCharsetFromParentFrame) { + return; + } + + if (kCharsetFromInitialAutoDetectionASCII <= parentSource) { + // Make sure that's OK + if (!NodePrincipal()->Equals(parentPrincipal) || + !IsAsciiCompatible(parentCharset)) { + return; + } + + aEncoding = WrapNotNull(parentCharset); + aCharsetSource = kCharsetFromParentFrame; + } +} + +// Using a prototype document is only allowed with chrome privilege. +bool ShouldUsePrototypeDocument(nsIChannel* aChannel, Document* aDoc) { + if (!aChannel || !aDoc || + !StaticPrefs::dom_prototype_document_cache_enabled()) { + return false; + } + return nsContentUtils::IsChromeDoc(aDoc); +} + +nsresult nsHTMLDocument::StartDocumentLoad( + const char* aCommand, nsIChannel* aChannel, nsILoadGroup* aLoadGroup, + nsISupports* aContainer, nsIStreamListener** aDocListener, bool aReset) { + if (!aCommand) { + MOZ_ASSERT(false, "Command is mandatory"); + return NS_ERROR_INVALID_POINTER; + } + if (mType != eHTML) { + MOZ_ASSERT(mType == eXHTML); + MOZ_ASSERT(false, "Must not set HTML doc to XHTML mode before load start."); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + nsAutoCString contentType; + aChannel->GetContentType(contentType); + + bool view = + !strcmp(aCommand, "view") || !strcmp(aCommand, "external-resource"); + mViewSource = !strcmp(aCommand, "view-source"); + bool asData = !strcmp(aCommand, kLoadAsData); + if (!(view || mViewSource || asData)) { + MOZ_ASSERT(false, "Bad parser command"); + return NS_ERROR_INVALID_ARG; + } + + bool html = contentType.EqualsLiteral(TEXT_HTML); + bool xhtml = !html && (contentType.EqualsLiteral(APPLICATION_XHTML_XML) || + contentType.EqualsLiteral(APPLICATION_WAPXHTML_XML)); + mIsPlainText = + !html && !xhtml && nsContentUtils::IsPlainTextType(contentType); + if (!(html || xhtml || mIsPlainText || mViewSource)) { + MOZ_ASSERT(false, "Channel with bad content type."); + return NS_ERROR_INVALID_ARG; + } + + bool forceUtf8 = + mIsPlainText && nsContentUtils::IsUtf8OnlyPlainTextType(contentType); + + bool loadAsHtml5 = true; + + if (!mViewSource && xhtml) { + // We're parsing XHTML as XML, remember that. + mType = eXHTML; + SetCompatibilityMode(eCompatibility_FullStandards); + loadAsHtml5 = false; + } + + // TODO: Proper about:blank treatment is bug 543435 + if (loadAsHtml5 && view) { + // mDocumentURI hasn't been set, yet, so get the URI from the channel + nsCOMPtr<nsIURI> uri; + aChannel->GetOriginalURI(getter_AddRefs(uri)); + // Adapted from nsDocShell: + // GetSpec can be expensive for some URIs, so check the scheme first. + if (uri && uri->SchemeIs("about")) { + if (uri->GetSpecOrDefault().EqualsLiteral("about:blank")) { + loadAsHtml5 = false; + } + } + } + + nsresult rv = Document::StartDocumentLoad(aCommand, aChannel, aLoadGroup, + aContainer, aDocListener, aReset); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIURI> uri; + rv = aChannel->GetURI(getter_AddRefs(uri)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer)); + + bool loadWithPrototype = false; + RefPtr<nsHtml5Parser> html5Parser; + if (loadAsHtml5) { + html5Parser = nsHtml5Module::NewHtml5Parser(); + mParser = html5Parser; + if (mIsPlainText) { + if (mViewSource) { + html5Parser->MarkAsNotScriptCreated("view-source-plain"); + } else { + html5Parser->MarkAsNotScriptCreated("plain-text"); + } + } else if (mViewSource && !html) { + html5Parser->MarkAsNotScriptCreated("view-source-xml"); + } else { + html5Parser->MarkAsNotScriptCreated(aCommand); + } + } else if (xhtml && ShouldUsePrototypeDocument(aChannel, this)) { + loadWithPrototype = true; + nsCOMPtr<nsIURI> originalURI; + aChannel->GetOriginalURI(getter_AddRefs(originalURI)); + mParser = new mozilla::parser::PrototypeDocumentParser(originalURI, this); + } else { + mParser = new nsParser(); + } + + // Look for the parent document. Note that at this point we don't have our + // content viewer set up yet, and therefore do not have a useful + // mParentDocument. + + // in this block of code, if we get an error result, we return it + // but if we get a null pointer, that's perfectly legal for parent + // and parentContentViewer + nsCOMPtr<nsIDocShellTreeItem> parentAsItem; + if (docShell) { + docShell->GetInProcessSameTypeParent(getter_AddRefs(parentAsItem)); + } + + nsCOMPtr<nsIDocShell> parent(do_QueryInterface(parentAsItem)); + nsCOMPtr<nsIContentViewer> parentContentViewer; + if (parent) { + rv = parent->GetContentViewer(getter_AddRefs(parentContentViewer)); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIContentViewer> cv; + if (docShell) { + docShell->GetContentViewer(getter_AddRefs(cv)); + } + if (!cv) { + cv = std::move(parentContentViewer); + } + + nsAutoCString urlSpec; + uri->GetSpec(urlSpec); +#ifdef DEBUG_charset + printf("Determining charset for %s\n", urlSpec.get()); +#endif + + // These are the charset source and charset for our document + bool forceAutoDetection = false; + int32_t charsetSource = kCharsetUninitialized; + auto encoding = UTF_8_ENCODING; + + // For error reporting and referrer policy setting + nsHtml5TreeOpExecutor* executor = nullptr; + if (loadAsHtml5) { + executor = static_cast<nsHtml5TreeOpExecutor*>(mParser->GetContentSink()); + } + + if (forceUtf8) { + charsetSource = kCharsetFromUtf8OnlyMime; + } else if (!IsHTMLDocument() || !docShell) { // no docshell for text/html XHR + charsetSource = + IsHTMLDocument() ? kCharsetFromFallback : kCharsetFromDocTypeDefault; + TryChannelCharset(aChannel, charsetSource, encoding, executor); + } else { + NS_ASSERTION(docShell, "Unexpected null value"); + + // The following will try to get the character encoding from various + // sources. Each Try* function will return early if the source is already + // at least as large as any of the sources it might look at. Some of + // these functions (like TryReloadCharset and TryParentCharset) can set + // charsetSource to various values depending on where the charset they + // end up finding originally comes from. + + // Try the channel's charset (e.g., charset from HTTP + // "Content-Type" header) first. This way, we get to reject overrides in + // TryParentCharset and TryUserForcedCharset if the channel said UTF-16. + // This is to avoid socially engineered XSS by adding user-supplied + // content to a UTF-16 site such that the byte have a dangerous + // interpretation as ASCII and the user can be lured to using the + // charset menu. + TryChannelCharset(aChannel, charsetSource, encoding, executor); + + TryUserForcedCharset(cv, docShell, charsetSource, encoding, + forceAutoDetection); + + TryReloadCharset(cv, charsetSource, encoding); // For encoding reload + TryParentCharset(docShell, charsetSource, encoding, forceAutoDetection); + } + + SetDocumentCharacterSetSource(charsetSource); + SetDocumentCharacterSet(encoding); + + // Set the parser as the stream listener for the document loader... + rv = NS_OK; + nsCOMPtr<nsIStreamListener> listener = mParser->GetStreamListener(); + listener.forget(aDocListener); + +#ifdef DEBUG_charset + printf(" charset = %s source %d\n", charset.get(), charsetSource); +#endif + mParser->SetDocumentCharset(encoding, charsetSource, forceAutoDetection); + mParser->SetCommand(aCommand); + + if (!IsHTMLDocument()) { + MOZ_ASSERT(!loadAsHtml5); + if (loadWithPrototype) { + nsCOMPtr<nsIContentSink> sink; + NS_NewPrototypeDocumentContentSink(getter_AddRefs(sink), this, uri, + docShell, aChannel); + mParser->SetContentSink(sink); + } else { + nsCOMPtr<nsIXMLContentSink> xmlsink; + NS_NewXMLContentSink(getter_AddRefs(xmlsink), this, uri, docShell, + aChannel); + mParser->SetContentSink(xmlsink); + } + } else { + if (loadAsHtml5) { + html5Parser->Initialize(this, uri, docShell, aChannel); + } else { + // about:blank *only* + nsCOMPtr<nsIHTMLContentSink> htmlsink; + NS_NewHTMLContentSink(getter_AddRefs(htmlsink), this, uri, docShell, + aChannel); + mParser->SetContentSink(htmlsink); + } + } + + // parser the content of the URI + mParser->Parse(uri); + + return rv; +} + +bool nsHTMLDocument::UseWidthDeviceWidthFallbackViewport() const { + if (mIsPlainText) { + // Plain text documents are simple enough that font inflation doesn't offer + // any appreciable advantage over defaulting to "width=device-width" and + // subsequently turning on word-wrapping. + return true; + } + return Document::UseWidthDeviceWidthFallbackViewport(); +} + +Element* nsHTMLDocument::GetUnfocusedKeyEventTarget() { + if (nsGenericHTMLElement* body = GetBody()) { + return body; + } + return Document::GetUnfocusedKeyEventTarget(); +} + +bool nsHTMLDocument::IsRegistrableDomainSuffixOfOrEqualTo( + const nsAString& aHostSuffixString, const nsACString& aOrigHost) { + // https://html.spec.whatwg.org/multipage/browsers.html#is-a-registrable-domain-suffix-of-or-is-equal-to + if (aHostSuffixString.IsEmpty()) { + return false; + } + + nsCOMPtr<nsIURI> origURI = CreateInheritingURIForHost(aOrigHost); + if (!origURI) { + // Error: failed to parse input domain + return false; + } + + nsCOMPtr<nsIURI> newURI = + RegistrableDomainSuffixOfInternal(aHostSuffixString, origURI); + if (!newURI) { + // Error: illegal domain + return false; + } + return true; +} + +void nsHTMLDocument::AddedForm() { ++mNumForms; } + +void nsHTMLDocument::RemovedForm() { --mNumForms; } + +int32_t nsHTMLDocument::GetNumFormsSynchronous() const { return mNumForms; } + +bool nsHTMLDocument::ResolveName(JSContext* aCx, const nsAString& aName, + JS::MutableHandle<JS::Value> aRetval, + ErrorResult& aError) { + IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName); + if (!entry) { + return false; + } + + nsBaseContentList* list = entry->GetNameContentList(); + uint32_t length = list ? list->Length() : 0; + + nsIContent* node; + if (length > 0) { + if (length > 1) { + // The list contains more than one element, return the whole list. + if (!ToJSValue(aCx, list, aRetval)) { + aError.NoteJSContextException(aCx); + return false; + } + return true; + } + + // Only one element in the list, return the element instead of returning + // the list. + node = list->Item(0); + } else { + // No named items were found, see if there's one registerd by id for aName. + Element* e = entry->GetIdElement(); + + if (!e || !nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(e)) { + return false; + } + + node = e; + } + + if (!ToJSValue(aCx, node, aRetval)) { + aError.NoteJSContextException(aCx); + return false; + } + + return true; +} + +void nsHTMLDocument::GetSupportedNames(nsTArray<nsString>& aNames) { + for (const auto& entry : mIdentifierMap) { + if (entry.HasNameElement() || + entry.HasIdElementExposedAsHTMLDocumentProperty()) { + aNames.AppendElement(entry.GetKeyAsString()); + } + } +} + +//---------------------------- + +// forms related stuff + +bool nsHTMLDocument::MatchFormControls(Element* aElement, int32_t aNamespaceID, + nsAtom* aAtom, void* aData) { + return aElement->IsNodeOfType(nsIContent::eHTML_FORM_CONTROL); +} + +nsresult nsHTMLDocument::Clone(dom::NodeInfo* aNodeInfo, + nsINode** aResult) const { + NS_ASSERTION(aNodeInfo->NodeInfoManager() == mNodeInfoManager, + "Can't import this document into another document!"); + + RefPtr<nsHTMLDocument> clone = new nsHTMLDocument(); + nsresult rv = CloneDocHelper(clone.get()); + NS_ENSURE_SUCCESS(rv, rv); + + // State from nsHTMLDocument + clone->mLoadFlags = mLoadFlags; + + clone.forget(aResult); + return NS_OK; +} + +/* virtual */ +void nsHTMLDocument::DocAddSizeOfExcludingThis( + nsWindowSizes& aWindowSizes) const { + Document::DocAddSizeOfExcludingThis(aWindowSizes); + + // Measurement of the following members may be added later if DMD finds it is + // worthwhile: + // - mLinks + // - mAnchors +} + +bool nsHTMLDocument::WillIgnoreCharsetOverride() { + if (mEncodingMenuDisabled) { + return true; + } + if (mType != eHTML) { + MOZ_ASSERT(mType == eXHTML); + return true; + } + if (mCharacterSetSource >= kCharsetFromByteOrderMark) { + return true; + } + if (!mCharacterSet->IsAsciiCompatible() && + mCharacterSet != ISO_2022_JP_ENCODING) { + return true; + } + nsIURI* uri = GetOriginalURI(); + if (uri) { + if (uri->SchemeIs("about")) { + return true; + } + bool isResource; + nsresult rv = NS_URIChainHasFlags( + uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, &isResource); + if (NS_FAILED(rv) || isResource) { + return true; + } + } + + switch (mCharacterSetSource) { + case kCharsetUninitialized: + case kCharsetFromFallback: + case kCharsetFromDocTypeDefault: + case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8: + case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: + case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: + case kCharsetFromParentFrame: + case kCharsetFromXmlDeclaration: + case kCharsetFromMetaTag: + case kCharsetFromChannel: + return false; + } + + bool potentialEffect = false; + nsIPrincipal* parentPrincipal = NodePrincipal(); + + auto subDoc = [&potentialEffect, parentPrincipal](Document& aSubDoc) { + if (parentPrincipal->Equals(aSubDoc.NodePrincipal()) && + !aSubDoc.WillIgnoreCharsetOverride()) { + potentialEffect = true; + return CallState::Stop; + } + return CallState::Continue; + }; + EnumerateSubDocuments(subDoc); + + return !potentialEffect; +} + +void nsHTMLDocument::GetFormsAndFormControls(nsContentList** aFormList, + nsContentList** aFormControlList) { + RefPtr<ContentListHolder> holder = mContentListHolder; + if (!holder) { + // Flush our content model so it'll be up to date + // If this becomes unnecessary and the following line is removed, + // please also remove the corresponding flush operation from + // nsHtml5TreeBuilderCppSupplement.h. (Look for "See bug 497861." there.) + // XXXsmaug nsHtml5TreeBuilderCppSupplement doesn't seem to have such flush + // anymore. + FlushPendingNotifications(FlushType::Content); + + RefPtr<nsContentList> htmlForms = GetExistingForms(); + if (!htmlForms) { + // If the document doesn't have an existing forms content list, create a + // new one which will be released soon by ContentListHolder. The idea is + // that we don't have that list hanging around for a long time and slowing + // down future DOM mutations. + // + // Please keep this in sync with Document::Forms(). + htmlForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form, + nsGkAtoms::form, + /* aDeep = */ true, + /* aLiveList = */ true); + } + + RefPtr<nsContentList> htmlFormControls = new nsContentList( + this, nsHTMLDocument::MatchFormControls, nullptr, nullptr, + /* aDeep = */ true, + /* aMatchAtom = */ nullptr, + /* aMatchNameSpaceId = */ kNameSpaceID_None, + /* aFuncMayDependOnAttr = */ true, + /* aLiveList = */ true); + + holder = new ContentListHolder(this, htmlForms, htmlFormControls); + RefPtr<ContentListHolder> runnable = holder; + if (NS_SUCCEEDED( + Dispatch(TaskCategory::GarbageCollection, runnable.forget()))) { + mContentListHolder = holder; + } + } + + NS_ADDREF(*aFormList = holder->mFormList); + NS_ADDREF(*aFormControlList = holder->mFormControlList); +} |