diff options
-rw-r--r-- | dom/base/SimpleTreeIterator.h | 71 | ||||
-rwxr-xr-x | dom/base/moz.build | 1 | ||||
-rw-r--r-- | dom/base/nsDocument.cpp | 50 | ||||
-rw-r--r-- | dom/base/nsDocument.h | 33 | ||||
-rw-r--r-- | dom/base/nsIDocument.h | 15 | ||||
-rw-r--r-- | dom/html/nsHTMLDocument.cpp | 397 | ||||
-rw-r--r-- | layout/base/nsDocumentViewer.cpp | 5 | ||||
-rw-r--r-- | uriloader/base/nsDocLoader.cpp | 102 | ||||
-rw-r--r-- | uriloader/base/nsDocLoader.h | 23 |
9 files changed, 400 insertions, 297 deletions
diff --git a/dom/base/SimpleTreeIterator.h b/dom/base/SimpleTreeIterator.h new file mode 100644 index 000000000..7ca504082 --- /dev/null +++ b/dom/base/SimpleTreeIterator.h @@ -0,0 +1,71 @@ +/* -*- 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 is a light-weight tree iterator for `for` loops when full iterator + * functionality isn't required. + */ + +#ifndef mozilla_dom_SimpleTreeIterator_h +#define mozilla_dom_SimpleTreeIterator_h + +#include "nsINode.h" +#include "nsTArray.h" +#include "mozilla/dom/Element.h" + +namespace mozilla { +namespace dom { + +class SimpleTreeIterator { +public: + /** + * Initialize an iterator with aRoot. After that it can be iterated with a + * range-based for loop. At the moment, that's the only supported form of use + * for this iterator. + */ + explicit SimpleTreeIterator(nsINode& aRoot) + : mCurrent(&aRoot) + { + mTree.AppendElement(&aRoot); + } + + // Basic support for range-based for loops. + // This will modify the iterator as it goes. + SimpleTreeIterator& begin() { return *this; } + + SimpleTreeIterator end() { return SimpleTreeIterator(); } + + bool operator!=(const SimpleTreeIterator& aOther) { + return mCurrent != aOther.mCurrent; + } + + void operator++() { Next(); } + + nsINode* operator*() { return mCurrent; } + +private: + // Constructor used only for end() to represent a drained iterator. + SimpleTreeIterator() + : mCurrent(nullptr) + {} + + void Next() { + MOZ_ASSERT(mCurrent, "Don't call Next() when we have no current node"); + + mCurrent = mCurrent->GetNextNode(mTree.LastElement()); + } + + // The current node. + nsINode* mCurrent; + + // The DOM tree that we're inside of right now. + AutoTArray<nsINode*, 1> mTree; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_SimpleTreeIterator_h diff --git a/dom/base/moz.build b/dom/base/moz.build index ebb76d617..75ddefded 100755 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -210,6 +210,7 @@ EXPORTS.mozilla.dom += [ 'ScreenOrientation.h', 'ScriptSettings.h', 'ShadowRoot.h', + 'SimpleTreeIterator.h', 'StructuredCloneHolder.h', 'StructuredCloneTags.h', 'StyleSheetList.h', diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index e2be6b664..0f663636f 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -1968,22 +1968,10 @@ nsDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) } void -nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup, - nsIPrincipal* aPrincipal) -{ - NS_PRECONDITION(aURI, "Null URI passed to ResetToURI"); - - if (gDocumentLeakPRLog && MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) { - PR_LogPrint("DOCUMENT %p ResetToURI %s", this, - aURI->GetSpecOrDefault().get()); - } - - mSecurityInfo = nullptr; - - mDocumentLoadGroup = nullptr; - +nsDocument::DisconnectNodeTree() { // Delete references to sub-documents and kill the subdocument map, - // if any. It holds strong references + // if any. This is not strictly needed, but makes the node tree + // teardown a bit faster. delete mSubDocuments; mSubDocuments = nullptr; @@ -2019,6 +2007,22 @@ nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup, "After removing all children, there should be no root elem"); } mInUnlinkOrDeletion = oldVal; +} + +void +nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup, + nsIPrincipal* aPrincipal) +{ + NS_PRECONDITION(aURI, "Null URI passed to ResetToURI"); + + if (gDocumentLeakPRLog && MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) { + PR_LogPrint("DOCUMENT %p ResetToURI %s", this, + aURI->GetSpecOrDefault().get()); + } + + mSecurityInfo = nullptr; + + mDocumentLoadGroup = nullptr; // Reset our stylesheets ResetStylesheetsToURI(aURI); @@ -2029,6 +2033,8 @@ nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup, mListenerManager = nullptr; } + DisconnectNodeTree(); + // Release the stylesheets list. mDOMStyleSheets = nullptr; @@ -9077,7 +9083,8 @@ nsDocument::CloneDocHelper(nsDocument* clone) const } void -nsDocument::SetReadyStateInternal(ReadyState rs) +nsDocument::SetReadyStateInternal(ReadyState rs, + bool updateTimingInformation) { mReadyState = rs; if (rs == READYSTATE_UNINITIALIZED) { @@ -9086,7 +9093,12 @@ nsDocument::SetReadyStateInternal(ReadyState rs) // transition undetectable by Web content. return; } - if (mTiming) { + + if (updateTimingInformation && READYSTATE_LOADING == rs) { + mLoadingTimeStamp = mozilla::TimeStamp::Now(); + } + + if (updateTimingInformation && mTiming) { switch (rs) { case READYSTATE_LOADING: mTiming->NotifyDOMLoading(nsIDocument::GetDocumentURI()); @@ -9102,10 +9114,6 @@ nsDocument::SetReadyStateInternal(ReadyState rs) break; } } - // At the time of loading start, we don't have timing object, record time. - if (READYSTATE_LOADING == rs) { - mLoadingTimeStamp = mozilla::TimeStamp::Now(); - } RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(this, NS_LITERAL_STRING("readystatechange"), diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index ac600eb43..c2b1d3cdb 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -704,7 +704,11 @@ public: virtual void BeginLoad() override; virtual void EndLoad() override; - virtual void SetReadyStateInternal(ReadyState rs) override; + // Set the readystate of the document. If updateTimingInformation is true, + // this will record relevant timestamps in the document's performance timing. + // Some consumers like document.open() don't want to do that. + virtual void SetReadyStateInternal(ReadyState rs, + bool updateTimingInformation = true) override; virtual void ContentStateChanged(nsIContent* aContent, mozilla::EventStates aStateMask) @@ -916,6 +920,14 @@ public: UpdateFrameRequestCallbackSchedulingState(); } + void SetLoadEventFiring(bool aFiring) override { mLoadEventFiring = aFiring; } + + bool SkipLoadEventAfterClose() override { + bool skip = mSkipLoadEventAfterClose; + mSkipLoadEventAfterClose = false; + return skip; + } + virtual nsIDocument* GetTemplateContentsOwner() override; NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsDocument, @@ -1255,6 +1267,11 @@ protected: */ Element* GetTitleElement(); + /** + * Perform tree disconnection needed by ResetToURI and document.open() + */ + void DisconnectNodeTree(); + public: // Get our title virtual void GetTitle(nsString& aTitle) override; @@ -1458,6 +1475,20 @@ public: // additional sheets and sheets from the nsStyleSheetService. bool mStyleSetFilled:1; + // The HTML spec has a "iframe load in progress" flag, but that doesn't seem + // to have the right semantics. See <https://github.com/whatwg/html/issues/4292>. + // What we have instead is a flag that is set while the window's 'load' event is + // firing if this document is the window's document. + bool mLoadEventFiring : 1; + + // The HTML spec has a "mute iframe load" flag, but that doesn't seem to have + // the right semantics. See <https://github.com/whatwg/html/issues/4292>. + // What we have instead is a flag that is set if completion of our document + // via document.close() should skip firing the load event. Note that this + // flag is only relevant for HTML documents, but lives here for reasons that + // are documented above on SkipLoadEventAfterClose(). + bool mSkipLoadEventAfterClose : 1; + uint8_t mPendingFullscreenRequests; uint8_t mXMLDeclarationBits; diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index d76a12d71..e2b34f7e3 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -1448,7 +1448,7 @@ public: virtual void EndLoad() = 0; enum ReadyState { READYSTATE_UNINITIALIZED = 0, READYSTATE_LOADING = 1, READYSTATE_INTERACTIVE = 3, READYSTATE_COMPLETE = 4}; - virtual void SetReadyStateInternal(ReadyState rs) = 0; + virtual void SetReadyStateInternal(ReadyState rs, bool updateTimingInformation = true) = 0; ReadyState GetReadyStateEnum() { return mReadyState; @@ -2187,6 +2187,19 @@ public: } /** + * Flag whether we're about to fire the window's load event for this document. + */ + virtual void SetLoadEventFiring(bool aFiring) = 0; + + /** + * Test whether we should be firing a load event for this document after a + * document.close(). + * This method should only be called at the point when the load event is about + * to be fired, since it resets `skip`. + */ + virtual bool SkipLoadEventAfterClose() = 0; + + /** * Returns the template content owner document that owns the content of * HTMLTemplateElement. */ diff --git a/dom/html/nsHTMLDocument.cpp b/dom/html/nsHTMLDocument.cpp index d64c27727..112e957cb 100644 --- a/dom/html/nsHTMLDocument.cpp +++ b/dom/html/nsHTMLDocument.cpp @@ -15,6 +15,7 @@ #include "nsPrintfCString.h" #include "nsReadableUtils.h" #include "nsUnicharUtils.h" +#include "nsIDocumentLoader.h" #include "nsIHTMLContentSink.h" #include "nsIXMLContentSink.h" #include "nsHTMLParts.h" @@ -84,6 +85,7 @@ #include "mozilla/dom/EncodingUtils.h" #include "mozilla/dom/FallbackEncoding.h" +#include "mozilla/EventListenerManager.h" #include "mozilla/LoadInfo.h" #include "nsIEditingSession.h" #include "nsIEditor.h" @@ -107,12 +109,14 @@ #include "nsIImageDocument.h" #include "mozilla/dom/HTMLBodyElement.h" #include "mozilla/dom/HTMLDocumentBinding.h" +#include "mozilla/dom/SimpleTreeIterator.h" #include "nsCharsetSource.h" #include "nsIStringBundle.h" #include "nsDOMClassInfo.h" #include "nsFocusManager.h" #include "nsIFrame.h" #include "nsIContent.h" +#include "nsIStructuredCloneContainer.h" #include "nsLayoutStylesheetCache.h" #include "mozilla/StyleSheet.h" #include "mozilla/StyleSheetInlines.h" @@ -842,6 +846,24 @@ nsHTMLDocument::EndLoad() if (turnOnEditing) { EditingStateChanged(); } + + if (!GetWindow()) { + // This is a document that's not in a window. For example, this could be an + // XMLHttpRequest responseXML document, or a document created via DOMParser + // or DOMImplementation. We don't reach this code normally for such + // documents (which is not obviously correct), but can reach it via + // document.open()/document.close(). + // + // Such documents don't fire load events, but per spec should set their + // readyState to "complete" when parsing and all loading of subresources is + // done. Parsing is done now, and documents not in a window don't load + // subresources, so just go ahead and mark ourselves as complete. + SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE, + /* updateTimingInformation = */ false); + + // Reset mSkipLoadEventAfterClose just in case. + mSkipLoadEventAfterClose = false; + } } void @@ -1410,19 +1432,21 @@ already_AddRefed<nsIDocument> nsHTMLDocument::Open(JSContext* cx, const nsAString& aType, const nsAString& aReplace, - ErrorResult& rv) + ErrorResult& aError) { - // Implements the "When called with two arguments (or fewer)" steps here: - // https://html.spec.whatwg.org/multipage/webappapis.html#opening-the-input-stream + // Implements + // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps> NS_ASSERTION(nsContentUtils::CanCallerAccess(static_cast<nsIDOMHTMLDocument*>(this)), "XOW should have caught this!"); + + // Step 1 - Throw if we're the wrong type of document. if (!IsHTMLDocument() || mDisableDocWrite || !IsMasterDocument()) { - // No calling document.open() on XHTML - rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr; } + // Set up the content type for insertion nsAutoCString contentType; contentType.AssignLiteral("text/html"); @@ -1435,51 +1459,7 @@ nsHTMLDocument::Open(JSContext* cx, contentType.AssignLiteral("text/plain"); } - // If we already have a parser we ignore the document.open call. - if (mParser || mParserAborted) { - // The WHATWG spec says: "If the document has an active parser that isn't - // a script-created parser, and the insertion point associated with that - // parser's input stream is not undefined (that is, it does point to - // somewhere in the input stream), then the method does nothing. Abort - // these steps and return the Document object on which the method was - // invoked." - // Note that aborting a parser leaves the parser "active" with its - // insertion point "not undefined". We track this using mParserAborted, - // because aborting a parser nulls out mParser. - nsCOMPtr<nsIDocument> ret = this; - return ret.forget(); - } - - // No calling document.open() without a script global object - if (!mScriptGlobalObject) { - nsCOMPtr<nsIDocument> ret = this; - return ret.forget(); - } - - nsPIDOMWindowOuter* outer = GetWindow(); - if (!outer || (GetInnerWindow() != outer->GetCurrentInnerWindow())) { - nsCOMPtr<nsIDocument> ret = this; - return ret.forget(); - } - - // check whether we're in the middle of unload. If so, ignore this call. - nsCOMPtr<nsIDocShell> shell(mDocumentContainer); - if (!shell) { - // We won't be able to create a parser anyway. - nsCOMPtr<nsIDocument> ret = this; - return ret.forget(); - } - - bool inUnload; - shell->GetIsInUnload(&inUnload); - if (inUnload) { - nsCOMPtr<nsIDocument> ret = this; - return ret.forget(); - } - - // Note: We want to use GetEntryDocument here because this document - // should inherit the security information of the document that's opening us, - // (since if it's secure, then it's presumably trusted). + // Step 3 - Get the entryDocument for security checks nsCOMPtr<nsIDocument> callerDoc = GetEntryDocument(); if (!callerDoc) { // If we're called from C++ or in some other way without an originating @@ -1489,67 +1469,39 @@ nsHTMLDocument::Open(JSContext* cx, // change the principals of a document for security reasons we'll have to // refuse to go ahead with this call. - rv.Throw(NS_ERROR_DOM_SECURITY_ERR); + aError.Throw(NS_ERROR_DOM_SECURITY_ERR); return nullptr; } - // Grab a reference to the calling documents security info (if any) - // and URIs as they may be lost in the call to Reset(). - nsCOMPtr<nsISupports> securityInfo = callerDoc->GetSecurityInfo(); - nsCOMPtr<nsIURI> uri = callerDoc->GetDocumentURI(); - nsCOMPtr<nsIURI> baseURI = callerDoc->GetBaseURI(); - nsCOMPtr<nsIPrincipal> callerPrincipal = callerDoc->NodePrincipal(); - nsCOMPtr<nsIChannel> callerChannel = callerDoc->GetChannel(); - - // We're called from script. Make sure the script is from the same - // origin, not just that the caller can access the document. This is - // needed to keep document principals from ever changing, which is - // needed because of the way we use our XOW code, and is a sane - // thing to do anyways. - - bool equals = false; - if (NS_FAILED(callerPrincipal->Equals(NodePrincipal(), &equals)) || - !equals) { - -#ifdef DEBUG - nsCOMPtr<nsIURI> callerDocURI = callerDoc->GetDocumentURI(); - nsCOMPtr<nsIURI> thisURI = nsIDocument::GetDocumentURI(); - printf("nsHTMLDocument::Open callerDoc %s this %s\n", - callerDocURI ? callerDocURI->GetSpecOrDefault().get() : "", - thisURI ? thisURI->GetSpecOrDefault().get() : ""); -#endif - - rv.Throw(NS_ERROR_DOM_SECURITY_ERR); + // Step 4 - Throw if we're not same-origin + if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) { + aError.Throw(NS_ERROR_DOM_SECURITY_ERR); return nullptr; } - // Stop current loads targeted at the window this document is in. - if (mScriptGlobalObject) { - nsCOMPtr<nsIContentViewer> cv; - shell->GetContentViewer(getter_AddRefs(cv)); - - if (cv) { - bool okToUnload; - if (NS_SUCCEEDED(cv->PermitUnload(&okToUnload)) && !okToUnload) { - // We don't want to unload, so stop here, but don't throw an - // exception. - nsCOMPtr<nsIDocument> ret = this; - return ret.forget(); - } - - // Now double-check that our invariants still hold. - if (!mScriptGlobalObject) { - nsCOMPtr<nsIDocument> ret = this; - return ret.forget(); - } - - nsPIDOMWindowOuter* outer = GetWindow(); - if (!outer || (GetInnerWindow() != outer->GetCurrentInnerWindow())) { - nsCOMPtr<nsIDocument> ret = this; - return ret.forget(); - } + // Step 5 - If we have an active parser, abort with no-op + if (mParser || mParserAborted) { + nsCOMPtr<nsIDocument> ret = this; + return ret.forget(); + } + + // Step 6 - Check if document.open() is called during unload + nsCOMPtr<nsIDocShell> shell(mDocumentContainer); + if (shell) { + bool inUnload; + shell->GetIsInUnload(&inUnload); + if (inUnload) { + nsCOMPtr<nsIDocument> ret = this; + return ret.forget(); } + } + // Step 7 - Stop existing navigation of our browsing context (and all + // other loads it's doing) if we're the active document of our browsing + // context. If there's no existing navigation, we don't want to stop + // anything. + if (shell && IsCurrentActiveDocument() && + mScriptGlobalObject) { nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(shell)); webnav->Stop(nsIWebNavigation::STOP_NETWORK); @@ -1560,189 +1512,123 @@ nsHTMLDocument::Open(JSContext* cx, EnsureOnloadBlocker(); } - // The open occurred after the document finished loading. - // So we reset the document and then reinitialize it. - nsCOMPtr<nsIChannel> channel; - nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup); - rv = NS_NewChannel(getter_AddRefs(channel), - uri, - callerDoc, - nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL, - nsIContentPolicy::TYPE_OTHER, - group); - - if (rv.Failed()) { - return nullptr; - } - - if (callerChannel) { - nsLoadFlags callerLoadFlags; - rv = callerChannel->GetLoadFlags(&callerLoadFlags); - if (rv.Failed()) { - return nullptr; - } - - nsLoadFlags loadFlags; - rv = channel->GetLoadFlags(&loadFlags); - if (rv.Failed()) { - return nullptr; - } - - loadFlags |= callerLoadFlags & nsIRequest::INHIBIT_PERSISTENT_CACHING; - - rv = channel->SetLoadFlags(loadFlags); - if (rv.Failed()) { - return nullptr; + // Step 8 - Clear all event listeners out of our DOM tree + for (nsINode* node : SimpleTreeIterator(*this)) { + if (EventListenerManager* elm = node->GetExistingListenerManager()) { + elm->RemoveAllListeners(); } + } - // If the user has allowed mixed content on the rootDoc, then we should propogate it - // down to the new document channel. - bool rootHasSecureConnection = false; - bool allowMixedContent = false; - bool isDocShellRoot = false; - nsresult rvalue = shell->GetAllowMixedContentAndConnectionData(&rootHasSecureConnection, &allowMixedContent, &isDocShellRoot); - if (NS_SUCCEEDED(rvalue) && allowMixedContent && isDocShellRoot) { - shell->SetMixedContentChannel(channel); + // Step 9 - Clear event listeners from our window, if we have one. + // + // Note that we explicitly want the inner window, and only if we're its + // document. We want to do this (per spec) even when we're not the "active + // document", so we can't go through GetWindow(), because it might forward to + // the wrong inner. + if (nsPIDOMWindowInner* win = GetInnerWindow()) { + if (win->GetExtantDoc() == this) { + if (EventListenerManager* elm = + nsGlobalWindow::Cast(win)->GetExistingListenerManager()) { + elm->RemoveAllListeners(); + } } } - // Before we reset the doc notify the globalwindow of the change, - // but only if we still have a window (i.e. our window object the - // current inner window in our outer window). + // Step 10 - Remove all of our DOM children without firing any mutation events. + DisconnectNodeTree(); - // Hold onto ourselves on the offchance that we're down to one ref - nsCOMPtr<nsIDocument> kungFuDeathGrip = this; + // --- At this point our tree is clean and we can switch to the new URI --- - if (nsPIDOMWindowInner *window = GetInnerWindow()) { - // Remember the old scope in case the call to SetNewDocument changes it. - nsCOMPtr<nsIScriptGlobalObject> oldScope(do_QueryReferent(mScopeObject)); - -#ifdef DEBUG - bool willReparent = mWillReparent; - mWillReparent = true; - - nsDocument* templateContentsOwner = - static_cast<nsDocument*>(mTemplateContentsOwner.get()); - - if (templateContentsOwner) { - templateContentsOwner->mWillReparent = true; - } -#endif + // Step 11 - If we're the current document in our docshell, do the + // equivalent of pushState() with the new URL we should have. + if (shell && IsCurrentActiveDocument()) { + nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI(); - // Per spec, we pass false here so that a new Window is created. - rv = window->SetNewDocument(this, nullptr, - /* aForceReuseInnerWindow */ false); - if (rv.Failed()) { + // UpdateURLAndHistory might do various member-setting, so make sure we're + // holding strong refs to all the refcounted args on the stack. We can + // assume that our caller is holding on to "this" already. + nsCOMPtr<nsIURI> currentURI = nsIDocument::GetDocumentURI(); + bool equalURIs; + nsresult rv = currentURI->Equals(newURI, &equalURIs); + if (NS_WARN_IF(NS_FAILED(rv))) { + aError.Throw(rv); return nullptr; } - -#ifdef DEBUG - if (templateContentsOwner) { - templateContentsOwner->mWillReparent = willReparent; + nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer); + rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, EmptyString(), + /* aReplace = */ true, currentURI, + equalURIs); + if (NS_WARN_IF(NS_FAILED(rv))) { + aError.Throw(rv); + return nullptr; } - mWillReparent = willReparent; -#endif + // And use the security info of the caller document as well, since + // it's the thing providing our data. + mSecurityInfo = callerDoc->GetSecurityInfo(); - // Now make sure we're not flagged as the initial document anymore, now - // that we've had stuff done to us. From now on, if anyone tries to - // document.open() us, they get a new inner window. + // This is not mentioned in the spec, but that's probably a spec bug. + // See <https://github.com/whatwg/html/issues/4299>. + // Since our URL may be changing away from about:blank here, we really want + // to unset this flag on any document.open(), since only about:blank can be + // an initial document. SetIsInitialDocument(false); - nsCOMPtr<nsIScriptGlobalObject> newScope(do_QueryReferent(mScopeObject)); - JS::Rooted<JSObject*> wrapper(cx, GetWrapper()); - if (oldScope && newScope != oldScope && wrapper) { - JSAutoCompartment ac(cx, wrapper); - rv = mozilla::dom::ReparentWrapper(cx, wrapper); - if (rv.Failed()) { - return nullptr; - } + // And let our docloader know that it will need to track our load event. + nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded(); + } - // Also reparent the template contents owner document - // because its global is set to the same as this document. - if (mTemplateContentsOwner) { - JS::Rooted<JSObject*> contentsOwnerWrapper(cx, - mTemplateContentsOwner->GetWrapper()); - if (contentsOwnerWrapper) { - rv = mozilla::dom::ReparentWrapper(cx, contentsOwnerWrapper); - if (rv.Failed()) { - return nullptr; - } - } - } - } - } + // Step 12 + mSkipLoadEventAfterClose = mLoadEventFiring; - mDidDocumentOpen = true; + // Preliminary to steps 13-16. Set our ready state to uninitialized before + // we do anything else, so we can then proceed to later ready state levels. + SetReadyStateInternal(READYSTATE_UNINITIALIZED, + /* updateTimingInformation = */ false); - // Call Reset(), this will now do the full reset - Reset(channel, group); - if (baseURI) { - mDocumentBaseURI = baseURI; - } + mDidDocumentOpen = true; - // Store the security info of the caller now that we're done - // resetting the document. - mSecurityInfo = securityInfo; + // Step 13 - Set our compatibility mode to standards. + SetCompatibilityMode(eCompatibility_FullStandards); + // Step 14 - Create a new parser associated with document. + // This also does step 16 implicitly. mParserAborted = false; mParser = nsHtml5Module::NewHtml5Parser(); - nsHtml5Module::Initialize(mParser, this, uri, shell, channel); + nsHtml5Module::Initialize(mParser, this, nsIDocument::GetDocumentURI(), shell, nullptr); if (mReferrerPolicySet) { // CSP may have set the referrer policy, so a speculative parser should // start with the new referrer policy. nsHtml5TreeOpExecutor* executor = nullptr; - executor = static_cast<nsHtml5TreeOpExecutor*> (mParser->GetContentSink()); + executor = static_cast<nsHtml5TreeOpExecutor*>(mParser->GetContentSink()); if (executor && mReferrerPolicySet) { - executor->SetSpeculationReferrerPolicy(static_cast<ReferrerPolicy>(mReferrerPolicy)); + executor->SetSpeculationReferrerPolicy( + static_cast<ReferrerPolicy>(mReferrerPolicy)); } } - // This will be propagated to the parser when someone actually calls write() - SetContentTypeInternal(contentType); - - // Prepare the docshell and the document viewer for the impending - // out of band document.write() - shell->PrepareForNewContentModel(); - - // Now check whether we were opened with a "replace" argument. If - // so, we need to tell the docshell to not create a new history - // entry for this load. Otherwise, make sure that we're doing a normal load, - // not whatever type of load was previously done on this docshell. - shell->SetLoadType(aReplace.LowerCaseEqualsLiteral("replace") ? - LOAD_NORMAL_REPLACE : LOAD_NORMAL); + if (shell) { + // Prepare the docshell and the document viewer for the impending + // out-of-band document.write() + shell->PrepareForNewContentModel(); - nsCOMPtr<nsIContentViewer> cv; - shell->GetContentViewer(getter_AddRefs(cv)); - if (cv) { - cv->LoadStart(this); + nsCOMPtr<nsIContentViewer> cv; + shell->GetContentViewer(getter_AddRefs(cv)); + if (cv) { + cv->LoadStart(this); + } } - // Add a wyciwyg channel request into the document load group - NS_ASSERTION(!mWyciwygChannel, "nsHTMLDocument::Open(): wyciwyg " - "channel already exists!"); - - // In case the editor is listening and will see the new channel - // being added, make sure mWriteLevel is non-zero so that the editor - // knows that document.open/write/close() is being called on this - // document. - ++mWriteLevel; - - CreateAndAddWyciwygChannel(); + // Step 15. + SetReadyStateInternal(nsIDocument::READYSTATE_LOADING, + /* updateTimingInformation = */ false); - --mWriteLevel; - - SetReadyStateInternal(nsIDocument::READYSTATE_LOADING); + // Step 16 happened with step 14 above. - // After changing everything around, make sure that the principal on the - // document's compartment exactly matches NodePrincipal(). - DebugOnly<JSObject*> wrapper = GetWrapperPreserveColor(); - MOZ_ASSERT_IF(wrapper, - JS_GetCompartmentPrincipals(js::GetObjectCompartment(wrapper)) == - nsJSPrincipals::get(NodePrincipal())); - - return kungFuDeathGrip.forget(); -} + // Step 17. + nsCOMPtr<nsIDocument> ret = this; + return ret.forget(); +} NS_IMETHODIMP nsHTMLDocument::Clear() @@ -1806,15 +1692,6 @@ nsHTMLDocument::Close(ErrorResult& rv) if (GetShell()) { FlushPendingNotifications(Flush_Layout); } - - // Removing the wyciwygChannel here is wrong when document.close() is - // called from within the document itself. However, legacy requires the - // channel to be removed here. Otherwise, the load event never fires. - NS_ASSERTION(mWyciwygChannel, "nsHTMLDocument::Close(): Trying to remove " - "nonexistent wyciwyg channel!"); - RemoveWyciwygChannel(); - NS_ASSERTION(!mWyciwygChannel, "nsHTMLDocument::Close(): " - "nsIWyciwygChannel could not be removed!"); } void diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp index 5478c61b0..8baf1a464 100644 --- a/layout/base/nsDocumentViewer.cpp +++ b/layout/base/nsDocumentViewer.cpp @@ -998,6 +998,9 @@ nsDocumentViewer::LoadComplete(nsresult aStatus) // will depend on whether it's cached! if(window && (NS_SUCCEEDED(aStatus) || aStatus == NS_ERROR_PARSED_DATA_CACHED)) { + // If this code changes, the code in nsDocLoader::DocLoaderIsEmpty + // that fires load events for document.open() cases might need to + // be updated too. nsEventStatus status = nsEventStatus_eIgnore; WidgetEvent event(true, eLoad); event.mFlags.mBubbles = false; @@ -1063,7 +1066,9 @@ nsDocumentViewer::LoadComplete(nsresult aStatus) MakeUnique<DocLoadingTimelineMarker>("document::Load")); } + d->SetLoadEventFiring(true); EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status); + d->SetLoadEventFiring(false); if (timing) { timing->NotifyLoadEventEnd(); } diff --git a/uriloader/base/nsDocLoader.cpp b/uriloader/base/nsDocLoader.cpp index 69885b93f..a4beb4827 100644 --- a/uriloader/base/nsDocLoader.cpp +++ b/uriloader/base/nsDocLoader.cpp @@ -4,6 +4,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nspr.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/EventDispatcher.h" #include "mozilla/Logging.h" #include "nsDocLoader.h" @@ -23,6 +25,7 @@ #include "nsQueryObject.h" #include "nsIDOMWindow.h" +#include "nsPIDOMWindow.h" #include "nsIStringBundle.h" #include "nsIScriptSecurityManager.h" @@ -36,7 +39,10 @@ #include "nsIAsyncVerifyRedirectCallback.h" using mozilla::DebugOnly; +using mozilla::eLoad; +using mozilla::EventDispatcher; using mozilla::LogLevel; +using mozilla::WidgetEvent; static NS_DEFINE_CID(kThisImplCID, NS_THIS_DOCLOADER_IMPL_CID); @@ -114,7 +120,8 @@ nsDocLoader::nsDocLoader() mIsLoadingDocument(false), mIsRestoringDocument(false), mDontFlushLayout(false), - mIsFlushingLayout(false) + mIsFlushingLayout(false), + mDocumentOpenedButNotLoaded(false) { ClearInternalProgress(); @@ -289,7 +296,7 @@ nsDocLoader::IsBusy() } /* Is this document loader busy? */ - if (!mIsLoadingDocument) { + if (!IsBlockingLoadEvent()) { return false; } @@ -414,6 +421,7 @@ nsDocLoader::OnStartRequest(nsIRequest *request, nsISupports *aCtxt) if (!mIsLoadingDocument && (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)) { bJustStartedLoading = true; mIsLoadingDocument = true; + mDocumentOpenedButNotLoaded = false; ClearInternalProgress(); // only clear our progress if we are starting a new load.... } @@ -480,10 +488,11 @@ nsDocLoader::OnStopRequest(nsIRequest *aRequest, mLoadGroup->GetActiveCount(&count); MOZ_LOG(gDocLoaderLog, LogLevel::Debug, - ("DocLoader:%p: OnStopRequest[%p](%s) status=%x mIsLoadingDocument=%s, %u active URLs", + ("DocLoader:%p: OnStopRequest[%p](%s) status=%x" + "mIsLoadingDocument=%s, mDocumentOpenedButNotLoaded=%s, %u active URLs", this, aRequest, name.get(), aStatus, (mIsLoadingDocument ? "true" : "false"), - count)); + (mDocumentOpenedButNotLoaded ? "true" : "false"), count)); } bool bFireTransferring = false; @@ -598,10 +607,9 @@ nsDocLoader::OnStopRequest(nsIRequest *aRequest, RemoveRequestInfo(aRequest); // - // Only fire the DocLoaderIsEmpty(...) if the document loader has initiated a - // load. This will handle removing the request from our hashtable as needed. + // Only fire the DocLoaderIsEmpty(...) if we may need to fire onload. // - if (mIsLoadingDocument) { + if (IsBlockingLoadEvent()) { nsCOMPtr<nsIDocShell> ds = do_QueryInterface(static_cast<nsIRequestObserver*>(this)); bool doNotFlushLayout = false; if (ds) { @@ -647,7 +655,7 @@ NS_IMETHODIMP nsDocLoader::GetDocumentChannel(nsIChannel ** aChannel) void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout) { - if (mIsLoadingDocument) { + if (IsBlockingLoadEvent()) { /* In the unimagineably rude circumstance that onload event handlers triggered by this function actually kill the window ... ok, it's not unimagineable; it's happened ... this deathgrip keeps this object @@ -660,7 +668,9 @@ void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout) } NS_ASSERTION(!mIsFlushingLayout, "Someone screwed up"); - NS_ASSERTION(mDocumentRequest, "No Document Request!"); + // We may not have a document request if we are in a document.open() situation. + NS_ASSERTION(mDocumentRequest || mDocumentOpenedButNotLoaded, + "No Document Request!"); // The load group for this DocumentLoader is idle. Flush if we need to. if (aFlushLayout && !mDontFlushLayout) { @@ -687,9 +697,14 @@ void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout) // And now check whether we're really busy; that might have changed with // the layout flush. - // Note, mDocumentRequest can be null if the flushing above re-entered this - // method. - if (!IsBusy() && mDocumentRequest) { + // + // Note, mDocumentRequest can be null while mDocumentOpenedButNotLoaded is + // false if the flushing above re-entered this method. Exit in that case. + if (IsBusy() || (!mDocumentRequest && !mDocumentOpenedButNotLoaded)) { + return; + } + + if (mDocumentRequest) { // Clear out our request info hash, now that our load really is done and // we don't need it anymore to CalculateMaxProgress(). ClearInternalProgress(); @@ -715,7 +730,7 @@ void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout) // mLoadGroup->SetDefaultLoadRequest(nullptr); - // Take a ref to our parent now so that we can call DocLoaderIsEmpty() on + // Take a ref to our parent now so that we can call ChildDoneWithOnload() on // it even if our onload handler removes us from the docloader tree. RefPtr<nsDocLoader> parent = mParent; @@ -733,6 +748,67 @@ void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout) parent->ChildDoneWithOnload(this); } } + } else { + MOZ_ASSERT(mDocumentOpenedButNotLoaded); + mDocumentOpenedButNotLoaded = false; + + // Make sure we do the ChildEnteringOnload/ChildDoneWithOnload even if we + // plan to skip firing our own load event, because otherwise we might + // never end up firing our parent's load event. + RefPtr<nsDocLoader> parent = mParent; + if (!parent || parent->ChildEnteringOnload(this)) { + nsresult loadGroupStatus = NS_OK; + mLoadGroup->GetStatus(&loadGroupStatus); + // Make sure we're not canceling the loadgroup. If we are, then we should + // not fire a load event just like in the normal navigation case. + if (NS_SUCCEEDED(loadGroupStatus) || + loadGroupStatus == NS_ERROR_PARSED_DATA_CACHED) { + // Can "doc" or "window" ever come back null here? Our state machine + // is complicated enough, so I wouldn't bet against it... + nsCOMPtr<nsIDocument> doc = do_GetInterface(GetAsSupports(this)); + if (doc) { + doc->SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE, + /* updateTimingInformation = */ false); + + nsCOMPtr<nsPIDOMWindowOuter> window = doc->GetWindow(); + if (window && !doc->SkipLoadEventAfterClose()) { + MOZ_LOG(gDocLoaderLog, LogLevel::Debug, + ("DocLoader:%p: Firing load event for document.open\n", + this)); + + // This is a very cut-down version of + // nsDocumentViewer::LoadComplete that doesn't do various things + // that are not relevant here because this wasn't an actual + // navigation. + WidgetEvent event(true, eLoad); + event.mFlags.mBubbles = false; + event.mFlags.mCancelable = false; + // Dispatching to |window|, but using |document| as the target, + // per spec. + event.mTarget = doc; + nsEventStatus unused = nsEventStatus_eIgnore; + doc->SetLoadEventFiring(true); + EventDispatcher::Dispatch(window, nullptr, &event, nullptr, + &unused); + doc->SetLoadEventFiring(false); + + // Now unsuppress painting on the presshell, if we + // haven't done that yet. + nsCOMPtr<nsIPresShell> shell = doc->GetShell(); + if (shell && !shell->IsDestroying()) { + shell->UnsuppressPainting(); + + if (!shell->IsDestroying()) { + shell->LoadComplete(); + } + } + } + } + } + if (parent) { + parent->ChildDoneWithOnload(this); + } + } } } } diff --git a/uriloader/base/nsDocLoader.h b/uriloader/base/nsDocLoader.h index 481b1397b..b469b8e07 100644 --- a/uriloader/base/nsDocLoader.h +++ b/uriloader/base/nsDocLoader.h @@ -109,6 +109,8 @@ public: unsigned long mNotifyMask; }; + void SetDocumentOpenedButNotLoaded() { mDocumentOpenedButNotLoaded = true; } + protected: virtual ~nsDocLoader(); @@ -302,6 +304,15 @@ protected: bool mIsFlushingLayout; private: + /** + * This flag indicates that the loader is waiting for completion of + * a document.open-triggered "document load". This is set when + * document.open() happens and sets up a new parser and cleared out + * when we go to fire our load event or end up with a new document + * channel. + */ + bool mDocumentOpenedButNotLoaded; + static const PLDHashTableOps sRequestInfoHashOps; // A list of kids that are in the middle of their onload calls and will let @@ -324,10 +335,20 @@ private: nsRequestInfo *GetRequestInfo(nsIRequest* aRequest); void ClearRequestInfoHash(); int64_t CalculateMaxProgress(); -/// void DumpChannelInfo(void); + /// void DumpChannelInfo(void); // used to clear our internal progress state between loads... void ClearInternalProgress(); + + /** + * Used to test whether we might need to fire a load event. This + * can happen when we have a document load going on, or when we've + * had document.open() called and haven't fired the corresponding + * load event yet. + */ + bool IsBlockingLoadEvent() const { + return mIsLoadingDocument || mDocumentOpenedButNotLoaded; + } }; NS_DEFINE_STATIC_IID_ACCESSOR(nsDocLoader, NS_THIS_DOCLOADER_IMPL_CID) |