diff --git a/docshell/base/nsDSURIContentListener.cpp b/docshell/base/nsDSURIContentListener.cpp index 55cf7d5118..7aa3a5d14d 100644 --- a/docshell/base/nsDSURIContentListener.cpp +++ b/docshell/base/nsDSURIContentListener.cpp @@ -136,7 +136,7 @@ nsDSURIContentListener::DoContent(const nsACString& aContentType, mExistingJPEGRequest = baseChannel; } - if (rv == NS_ERROR_REMOTE_XUL) { + if (rv == NS_ERROR_REMOTE_XUL || rv == NS_ERROR_DOCSHELL_DYING) { aRequest->Cancel(rv); *aAbortProcess = true; return NS_OK; diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index d2e1af36b3..7ad4be98da 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -893,6 +893,8 @@ nsDocShell::~nsDocShell() nsresult nsDocShell::Init() { + MOZ_ASSERT(!mIsBeingDestroyed); + nsresult rv = nsDocLoader::Init(); NS_ENSURE_SUCCESS(rv, rv); @@ -1921,6 +1923,8 @@ bool nsDocShell::SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest, bool aFireOnLocationChange, uint32_t aLocationFlags) { + MOZ_ASSERT(!mIsBeingDestroyed); + if (gDocShellLeakLog && MOZ_LOG_TEST(gDocShellLeakLog, LogLevel::Debug)) { PR_LogPrint("DOCSHELL %p SetCurrentURI %s\n", this, aURI ? aURI->GetSpecOrDefault().get() : ""); @@ -2181,6 +2185,8 @@ nsDocShell::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) NS_IMETHODIMP nsDocShell::SetPrivateBrowsing(bool aUsePrivateBrowsing) { + MOZ_ASSERT(!mIsBeingDestroyed); + bool changed = aUsePrivateBrowsing != (mPrivateBrowsingId > 0); if (changed) { mPrivateBrowsingId = aUsePrivateBrowsing ? 1 : 0; @@ -2251,6 +2257,8 @@ nsDocShell::SetRemoteTabs(bool aUseRemoteTabs) NS_IMETHODIMP nsDocShell::SetAffectPrivateSessionLifetime(bool aAffectLifetime) { + MOZ_ASSERT(!mIsBeingDestroyed); + bool change = aAffectLifetime != mAffectPrivateSessionLifetime; if (change && UsePrivateBrowsing()) { AssertOriginAttributesMatchPrivateBrowsing(); @@ -2753,6 +2761,8 @@ nsDocShell::GetSecurityUI(nsISecureBrowserUI** aSecurityUI) NS_IMETHODIMP nsDocShell::SetSecurityUI(nsISecureBrowserUI* aSecurityUI) { + MOZ_ASSERT(!mIsBeingDestroyed); + mSecurityUI = aSecurityUI; mSecurityUI->SetDocShell(this); return NS_OK; @@ -3935,6 +3945,10 @@ PrintDocTree(nsIDocShellTreeItem* aParentNode) NS_IMETHODIMP nsDocShell::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner) { + if (mIsBeingDestroyed && aTreeOwner) { + return NS_ERROR_FAILURE; + } + #ifdef DEBUG_DOCSHELL_FOCUS nsCOMPtr item(do_QueryInterface(aTreeOwner)); if (item) { @@ -5216,6 +5230,8 @@ nsDocShell::LoadErrorPage(nsIURI* aURI, const char16_t* aURL, const char* aCSSClass, nsIChannel* aFailedChannel) { + MOZ_ASSERT(!mIsBeingDestroyed); + #if defined(DEBUG) if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) { nsAutoCString chanName; @@ -5571,6 +5587,8 @@ nsDocShell::SetSessionHistory(nsISHistory* aSessionHistory) // make sure that we are the root docshell and // set a handle to root docshell in SH. + MOZ_ASSERT(!mIsBeingDestroyed); + nsCOMPtr root; /* Get the root docshell. If *this* is the root docshell * then save a handle to *this* in SH. SH needs it to do @@ -5751,16 +5769,22 @@ nsDocShell::Create() NS_IMETHODIMP nsDocShell::Destroy() { + // XXX: We allow this function to be called just once. If you are going to + // reset new variables in this function, please make sure the variables will + // never be re-initialized. Adding assertions to check |mIsBeingDestroyed| + // in the setter functions for the variables would be enough. + if (mIsBeingDestroyed) { + return NS_ERROR_DOCSHELL_DYING; + } + NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome, "Unexpected item type in docshell"); - if (!mIsBeingDestroyed) { - nsCOMPtr serv = services::GetObserverService(); - if (serv) { - const char* msg = mItemType == typeContent ? - NS_WEBNAVIGATION_DESTROY : NS_CHROME_WEBNAVIGATION_DESTROY; - serv->NotifyObservers(GetAsSupports(this), msg, nullptr); - } + nsCOMPtr serv = services::GetObserverService(); + if (serv) { + const char* msg = mItemType == typeContent ? + NS_WEBNAVIGATION_DESTROY : NS_CHROME_WEBNAVIGATION_DESTROY; + serv->NotifyObservers(GetAsSupports(this), msg, nullptr); } mIsBeingDestroyed = true; @@ -6043,6 +6067,7 @@ nsDocShell::GetParentWidget(nsIWidget** aParentWidget) NS_IMETHODIMP nsDocShell::SetParentWidget(nsIWidget* aParentWidget) { + MOZ_ASSERT(!mIsBeingDestroyed); mParentWidget = aParentWidget; return NS_OK; @@ -6313,6 +6338,8 @@ nsDocShell::SetOnePermittedSandboxedNavigator(nsIDocShell* aSandboxedNavigator) return NS_OK; } + MOZ_ASSERT(!mIsBeingDestroyed); + mOnePermittedSandboxedNavigator = do_GetWeakReference(aSandboxedNavigator); NS_ASSERTION(mOnePermittedSandboxedNavigator, "One Permitted Sandboxed Navigator must support weak references."); @@ -6673,6 +6700,8 @@ nsDocShell::RefreshURI(nsIURI* aURI, bool aMetaRefresh, nsIPrincipal* aPrincipal) { + MOZ_ASSERT(!mIsBeingDestroyed); + NS_ENSURE_ARG(aURI); /* Check if Meta refresh/redirects are permitted. Some @@ -8056,6 +8085,10 @@ nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal, // wrong information :-( // (void)FirePageHideNotification(!mSavingOldViewer); + // pagehide notification might destroy this docshell. + if (mIsBeingDestroyed) { + return NS_ERROR_DOCSHELL_DYING; + } } // Now make sure we don't think we're in the middle of firing unload after @@ -8199,6 +8232,8 @@ nsDocShell::CanSavePresentation(uint32_t aLoadType, void nsDocShell::ReattachEditorToWindow(nsISHEntry* aSHEntry) { + MOZ_ASSERT(!mIsBeingDestroyed); + NS_ASSERTION(!mEditorData, "Why reattach an editor when we already have one?"); NS_ASSERTION(aSHEntry && aSHEntry->HasDetachedEditor(), @@ -8236,6 +8271,9 @@ nsDocShell::DetachEditorFromWindow() if (NS_SUCCEEDED(res)) { // Make mOSHE hold the owning ref to the editor data. if (mOSHE) { + MOZ_ASSERT(!mIsBeingDestroyed || !mOSHE->HasDetachedEditor(), + "We should not set the editor data again once after we " + "detached the editor data during destroying this docshell"); mOSHE->SetEditorData(mEditorData.forget()); } else { mEditorData = nullptr; @@ -8422,6 +8460,8 @@ nsDocShell::GetRestoringDocument(bool* aRestoring) nsresult nsDocShell::RestorePresentation(nsISHEntry* aSHEntry, bool* aRestoring) { + MOZ_ASSERT(!mIsBeingDestroyed); + NS_ASSERTION(mLoadType & LOAD_CMD_HISTORY, "RestorePresentation should only be called for history loads"); @@ -8583,6 +8623,10 @@ nsDocShell::RestoreFromHistory() // Notify the old content viewer that it's being hidden. FirePageHideNotification(!mSavingOldViewer); + // pagehide notification might destroy this docshell. + if (mIsBeingDestroyed) { + return NS_ERROR_DOCSHELL_DYING; + } // If mLSHE was changed as a result of the pagehide event, then // something else was loaded. Don't finish restoring. @@ -9040,6 +9084,12 @@ nsDocShell::CreateContentViewer(const nsACString& aContentType, { *aContentHandler = nullptr; + if (!mTreeOwner || mIsBeingDestroyed) { + // If we don't have a tree owner, then we're in the process of being + // destroyed. Rather than continue trying to load something, just give up. + return NS_ERROR_DOCSHELL_DYING; + } + // Can we check the content type of the current content viewer // and reuse it without destroying it and re-creating it? @@ -9079,6 +9129,11 @@ nsDocShell::CreateContentViewer(const nsACString& aContentType, aOpenedChannel->GetURI(getter_AddRefs(mLoadingURI)); } FirePageHideNotification(!mSavingOldViewer); + if (mIsBeingDestroyed) { + // Force to stop the newly created orphaned viewer. + viewer->Stop(); + return NS_ERROR_DOCSHELL_DYING; + } mLoadingURI = nullptr; // Set mFiredUnloadEvent = false so that the unload handler for the @@ -9268,6 +9323,8 @@ nsDocShell::NewContentViewerObj(const nsACString& aContentType, nsresult nsDocShell::SetupNewViewer(nsIContentViewer* aNewViewer) { + MOZ_ASSERT(!mIsBeingDestroyed); + // // Copy content viewer state from previous or parent content viewer. // @@ -13460,6 +13517,8 @@ nsDocShell::EnsureScriptEnvironment() NS_IMETHODIMP nsDocShell::EnsureEditorData() { + MOZ_ASSERT(!mIsBeingDestroyed); + bool openDocHasDetachedEditor = mOSHE && mOSHE->HasDetachedEditor(); if (!mEditorData && !mIsBeingDestroyed && !openDocHasDetachedEditor) { // We shouldn't recreate the editor data if it already exists, or @@ -13475,6 +13534,8 @@ nsDocShell::EnsureEditorData() nsresult nsDocShell::EnsureTransferableHookData() { + MOZ_ASSERT(!mIsBeingDestroyed); + if (!mTransferableHookData) { mTransferableHookData = new nsTransferableHookData(); } @@ -14562,6 +14623,8 @@ nsDocShell::CanSetOriginAttributes() nsresult nsDocShell::SetOriginAttributes(const DocShellOriginAttributes& aAttrs) { + MOZ_ASSERT(!mIsBeingDestroyed); + if (!CanSetOriginAttributes()) { return NS_ERROR_FAILURE; } diff --git a/dom/base/DOMIntersectionObserver.cpp b/dom/base/DOMIntersectionObserver.cpp index 7ee1a4c938..aaa1609b33 100644 --- a/dom/base/DOMIntersectionObserver.cpp +++ b/dom/base/DOMIntersectionObserver.cpp @@ -80,7 +80,14 @@ DOMIntersectionObserver::Constructor(const mozilla::dom::GlobalObject& aGlobal, RefPtr observer = new DOMIntersectionObserver(window.forget(), aCb); - observer->mRoot = aOptions.mRoot; + if (!aOptions.mRoot.IsNull()) { + if (aOptions.mRoot.Value().IsElement()) { + observer->mRoot = aOptions.mRoot.Value().GetAsElement(); + } else { + MOZ_ASSERT(aOptions.mRoot.Value().IsDocument()); + observer->mRoot = aOptions.mRoot.Value().GetAsDocument(); + } + } if (!observer->SetRootMargin(aOptions.mRootMargin)) { aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR, @@ -258,6 +265,30 @@ EdgeInclusiveIntersection(const nsRect& aRect, const nsRect& aOtherRect) return Some(nsRect(left, top, right - left, bottom - top)); } +// NOTE: This returns nullptr if |aDocument| is in a cross process. +static nsIDocument* GetTopLevelDocument(const nsIDocument& aDocument) { + nsCOMPtr presShell = aDocument.GetShell(); + + if (presShell) { + nsIFrame* rootFrame = presShell->GetRootScrollFrame(); + if (rootFrame) { + nsPresContext* presContext = rootFrame->PresContext(); + while (!presContext->IsRootContentDocument()) { + // Walk up the tree + presContext = presContext->GetParentPresContext(); + if (!presContext) { + break; + } + } + if(presContext && presContext->IsRootContentDocument()) { + return presContext->Document(); + } + } + } + + return nullptr; +} + enum class BrowsingContextInfo { SimilarOriginBrowsingContext, DifferentOriginBrowsingContext, @@ -267,14 +298,12 @@ enum class BrowsingContextInfo { void DOMIntersectionObserver::Update(nsIDocument* aDocument, DOMHighResTimeStamp time) { - Element* root = nullptr; + nsINode* root = mRoot; nsIFrame* rootFrame = nullptr; nsRect rootRect; - if (mRoot) { - root = mRoot; - rootFrame = root->GetPrimaryFrame(); - if (rootFrame) { + if (mRoot && mRoot->IsElement()) { + if ((rootFrame = mRoot->AsElement()->GetPrimaryFrame())) { nsRect rootRectRelativeToRootFrame; if (rootFrame->GetType() == nsGkAtoms::scrollFrame) { // rootRectRelativeToRootFrame should be the content rect of rootFrame, not including the scrollbars. @@ -292,30 +321,18 @@ DOMIntersectionObserver::Update(nsIDocument* aDocument, DOMHighResTimeStamp time containingBlock); } } else { - nsCOMPtr presShell = aDocument->GetShell(); - if (presShell) { - rootFrame = presShell->GetRootScrollFrame(); - if (rootFrame) { - nsPresContext* presContext = rootFrame->PresContext(); - while (!presContext->IsRootContentDocument()) { - // Walk up the tree - presContext = presContext->GetParentPresContext(); - if (!presContext) { - break; + MOZ_ASSERT(!mRoot || mRoot->IsInUncomposedDoc()); + nsIDocument* rootDocument = + mRoot ? mRoot->GetUncomposedDoc() : GetTopLevelDocument(*aDocument); + if (rootDocument) { + if (nsIPresShell* presShell = rootDocument->GetShell()) { + rootFrame = presShell->GetRootScrollFrame(); + if (rootFrame) { + root = rootFrame->GetContent()->AsElement(); + nsIScrollableFrame* scrollFrame = do_QueryFrame(rootFrame); + if (scrollFrame) { + rootRect = scrollFrame->GetScrollPortRect(); } - nsIFrame* rootScrollFrame = presContext->PresShell()->GetRootScrollFrame(); - if (rootScrollFrame) { - rootFrame = rootScrollFrame; - } else { - break; - } - } - root = rootFrame->GetContent()->AsElement(); - nsIScrollableFrame* scrollFrame = do_QueryFrame(rootFrame); - // If we end up with a null root frame for some reason, we'll proceed - // with an empty root intersection rect. - if (scrollFrame) { - rootRect = scrollFrame->GetScrollPortRect(); } } } diff --git a/dom/base/DOMIntersectionObserver.h b/dom/base/DOMIntersectionObserver.h index a72454007c..f309544978 100644 --- a/dom/base/DOMIntersectionObserver.h +++ b/dom/base/DOMIntersectionObserver.h @@ -145,7 +145,7 @@ public: return mOwner; } - Element* GetRoot() const { + nsINode* GetRoot() const { return mRoot; } @@ -178,7 +178,7 @@ protected: nsCOMPtr mOwner; RefPtr mDocument; RefPtr mCallback; - RefPtr mRoot; + RefPtr mRoot; nsCSSRect mRootMargin; nsTArray mThresholds; diff --git a/dom/base/nsContentSink.cpp b/dom/base/nsContentSink.cpp index 0392fce500..d8760db70c 100644 --- a/dom/base/nsContentSink.cpp +++ b/dom/base/nsContentSink.cpp @@ -100,6 +100,7 @@ nsContentSink::nsContentSink() , mDeferredFlushTags(0) , mIsDocumentObserver(0) , mRunsToCompletion(0) + , mIsBlockingOnload(false) , mDeflectedCount(0) , mHasPendingEvent(false) , mCurrentParseEndTime(0) @@ -1539,8 +1540,14 @@ nsContentSink::DropParserAndPerfHint(void) FavorPerformanceHint(true, 0); } - if (!mRunsToCompletion) { + // Call UnblockOnload only if mRunsToComletion is false and if + // we have already started loading because it's possible that this function + // is called (i.e. the parser is terminated) before we start loading due to + // destroying the window inside unload event callbacks for the previous + // document. + if (!mRunsToCompletion && mIsBlockingOnload) { mDocument->UnblockOnload(true); + mIsBlockingOnload = false; } } @@ -1595,6 +1602,7 @@ nsContentSink::WillBuildModelImpl() { if (!mRunsToCompletion) { mDocument->BlockOnload(); + mIsBlockingOnload = true; mBeginLoadTime = PR_IntervalToMicroseconds(PR_IntervalNow()); } diff --git a/dom/base/nsContentSink.h b/dom/base/nsContentSink.h index 43a873dd6f..4140a3f19f 100644 --- a/dom/base/nsContentSink.h +++ b/dom/base/nsContentSink.h @@ -304,6 +304,8 @@ protected: // True if this is parser is a fragment parser or an HTML DOMParser. // XML DOMParser leaves this to false for now! uint8_t mRunsToCompletion : 1; + // True if we are blocking load event. + bool mIsBlockingOnload : 1; // // -- Can interrupt parsing members -- diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 6d092a53d0..029748c40a 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -5444,6 +5444,14 @@ nsContentUtils::AddPendingIDBTransaction(already_AddRefed aTransact CycleCollectedJSContext::Get()->AddPendingIDBTransaction(Move(aTransaction)); } +/* static */ +bool +nsContentUtils::IsInStableOrMetaStableState() +{ + MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!"); + return CycleCollectedJSContext::Get()->IsInStableOrMetaStableState(); +} + /* * Helper function for nsContentUtils::ProcessViewportInfo. * diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 9c22fa491e..bce0085c61 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -1779,6 +1779,11 @@ public: */ static void AddPendingIDBTransaction(already_AddRefed aTransaction); + /** + * Returns true if we are doing StableState/MetastableState. + */ + static bool IsInStableOrMetaStableState(); + /* Process viewport META data. This gives us information for the scale * and zoom of a page on mobile devices. We stick the information in * the document header and use it later on after rendering. diff --git a/dom/base/nsRange.cpp b/dom/base/nsRange.cpp index 150bf7d628..a51dd5e7b5 100644 --- a/dom/base/nsRange.cpp +++ b/dom/base/nsRange.cpp @@ -269,6 +269,7 @@ nsRange::nsRange(nsINode* aNode) , mStartOffsetWasIncremented(false) , mEndOffsetWasIncremented(false) , mEnableGravitationOnElementRemoval(true) + , mCalledByJS(false) #ifdef DEBUG , mAssertNextInsertOrAppendIndex(-1) , mAssertNextInsertOrAppendNode(nullptr) @@ -997,7 +998,16 @@ nsRange::DoSetRange(nsINode* aStartN, uint32_t aStartOffset, // Notify any selection listeners. This has to occur last because otherwise the world // could be observed by a selection listener while the range was in an invalid state. if (mSelection) { - mSelection->NotifySelectionListeners(); + // Our internal code should not move focus with using this instance while + // it's calling Selection::NotifySelectionListeners() which may move focus + // or calls selection listeners. So, let's set mCalledByJS to false here + // since non-*JS() methods don't set it to false. + AutoCalledByJSRestore calledByJSRestorer(*this); + mCalledByJS = false; + // Be aware, this range may be modified or stop being a range for selection + // after this call. Additionally, the selection instance may have gone. + RefPtr selection = mSelection; + selection->NotifySelectionListeners(calledByJSRestorer.SavedValue()); } } @@ -1224,6 +1234,14 @@ nsRange::IsValidBoundary(nsINode* aNode) return root; } +void +nsRange::SetStartJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr) +{ + AutoCalledByJSRestore calledByJSRestorer(*this); + mCalledByJS = true; + SetStart(aNode, aOffset, aErr); +} + void nsRange::SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv) { @@ -1279,6 +1297,14 @@ nsRange::SetStart(nsINode* aParent, uint32_t aOffset) return NS_OK; } +void +nsRange::SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr) +{ + AutoCalledByJSRestore calledByJSRestorer(*this); + mCalledByJS = true; + SetStartBefore(aNode, aErr); +} + void nsRange::SetStartBefore(nsINode& aNode, ErrorResult& aRv) { @@ -1310,6 +1336,14 @@ nsRange::SetStartBefore(nsIDOMNode* aSibling) return rv.StealNSResult(); } +void +nsRange::SetStartAfterJS(nsINode& aNode, ErrorResult& aErr) +{ + AutoCalledByJSRestore calledByJSRestorer(*this); + mCalledByJS = true; + SetStartAfter(aNode, aErr); +} + void nsRange::SetStartAfter(nsINode& aNode, ErrorResult& aRv) { @@ -1341,6 +1375,14 @@ nsRange::SetStartAfter(nsIDOMNode* aSibling) return rv.StealNSResult(); } +void +nsRange::SetEndJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr) +{ + AutoCalledByJSRestore calledByJSRestorer(*this); + mCalledByJS = true; + SetEnd(aNode, aOffset, aErr); +} + void nsRange::SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv) { @@ -1455,6 +1497,14 @@ nsRange::SetStartAndEnd(nsINode* aStartParent, uint32_t aStartOffset, return NS_OK; } +void +nsRange::SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr) +{ + AutoCalledByJSRestore calledByJSRestorer(*this); + mCalledByJS = true; + SetEndBefore(aNode, aErr); +} + void nsRange::SetEndBefore(nsINode& aNode, ErrorResult& aRv) { @@ -1486,6 +1536,14 @@ nsRange::SetEndBefore(nsIDOMNode* aSibling) return rv.StealNSResult(); } +void +nsRange::SetEndAfterJS(nsINode& aNode, ErrorResult& aErr) +{ + AutoCalledByJSRestore calledByJSRestorer(*this); + mCalledByJS = true; + SetEndAfter(aNode, aErr); +} + void nsRange::SetEndAfter(nsINode& aNode, ErrorResult& aRv) { @@ -1532,6 +1590,14 @@ nsRange::Collapse(bool aToStart) return NS_OK; } +void +nsRange::CollapseJS(bool aToStart) +{ + AutoCalledByJSRestore calledByJSRestorer(*this); + mCalledByJS = true; + Unused << Collapse(aToStart); +} + NS_IMETHODIMP nsRange::SelectNode(nsIDOMNode* aN) { @@ -1543,6 +1609,14 @@ nsRange::SelectNode(nsIDOMNode* aN) return rv.StealNSResult(); } +void +nsRange::SelectNodeJS(nsINode& aNode, ErrorResult& aErr) +{ + AutoCalledByJSRestore calledByJSRestorer(*this); + mCalledByJS = true; + SelectNode(aNode, aErr); +} + void nsRange::SelectNode(nsINode& aNode, ErrorResult& aRv) { @@ -1582,6 +1656,14 @@ nsRange::SelectNodeContents(nsIDOMNode* aN) return rv.StealNSResult(); } +void +nsRange::SelectNodeContentsJS(nsINode& aNode, ErrorResult& aErr) +{ + AutoCalledByJSRestore calledByJSRestorer(*this); + mCalledByJS = true; + SelectNodeContents(aNode, aErr); +} + void nsRange::SelectNodeContents(nsINode& aNode, ErrorResult& aRv) { diff --git a/dom/base/nsRange.h b/dom/base/nsRange.h index 7cc3049ded..cfd6055fef 100644 --- a/dom/base/nsRange.h +++ b/dom/base/nsRange.h @@ -20,6 +20,7 @@ #include "nsStubMutationObserver.h" #include "nsWrapperCache.h" #include "mozilla/Attributes.h" +#include "mozilla/GuardObjects.h" namespace mozilla { class ErrorResult; @@ -256,14 +257,20 @@ public: void InsertNode(nsINode& aNode, ErrorResult& aErr); bool IntersectsNode(nsINode& aNode, ErrorResult& aRv); bool IsPointInRange(nsINode& aParent, uint32_t aOffset, ErrorResult& aErr); - void SelectNode(nsINode& aNode, ErrorResult& aErr); - void SelectNodeContents(nsINode& aNode, ErrorResult& aErr); - void SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); - void SetEndAfter(nsINode& aNode, ErrorResult& aErr); - void SetEndBefore(nsINode& aNode, ErrorResult& aErr); - void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); - void SetStartAfter(nsINode& aNode, ErrorResult& aErr); - void SetStartBefore(nsINode& aNode, ErrorResult& aErr); + + // *JS() methods are mapped to Range.*() of DOM. + // They may move focus only when the range represents normal selection. + // These methods shouldn't be used from internal. + void CollapseJS(bool aToStart); + void SelectNodeJS(nsINode& aNode, ErrorResult& aErr); + void SelectNodeContentsJS(nsINode& aNode, ErrorResult& aErr); + void SetEndJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); + void SetEndAfterJS(nsINode& aNode, ErrorResult& aErr); + void SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr); + void SetStartJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); + void SetStartAfterJS(nsINode& aNode, ErrorResult& aErr); + void SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr); + void SurroundContents(nsINode& aNode, ErrorResult& aErr); already_AddRefed GetBoundingClientRect(bool aClampToEdge = true, bool aFlushLayout = true); @@ -272,6 +279,17 @@ public: void GetClientRectsAndTexts( mozilla::dom::ClientRectsAndTexts& aResult, ErrorResult& aErr); + + // Following methods should be used for internal use instead of *JS(). + void SelectNode(nsINode& aNode, ErrorResult& aErr); + void SelectNodeContents(nsINode& aNode, ErrorResult& aErr); + void SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); + void SetEndAfter(nsINode& aNode, ErrorResult& aErr); + void SetEndBefore(nsINode& aNode, ErrorResult& aErr); + void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); + void SetStartAfter(nsINode& aNode, ErrorResult& aErr); + void SetStartBefore(nsINode& aNode, ErrorResult& aErr); + static void GetInnerTextNoFlush(mozilla::dom::DOMString& aValue, mozilla::ErrorResult& aError, nsIContent* aStartParent, @@ -392,6 +410,31 @@ protected: size_t aRangeStart, size_t aRangeEnd); + // Assume that this is guaranteed that this is held by the caller when + // this is used. (Note that we cannot use AutoRestore for mCalledByJS + // due to a bit field.) + class MOZ_RAII AutoCalledByJSRestore final + { + private: + nsRange& mRange; + bool mOldValue; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + + public: + explicit AutoCalledByJSRestore(nsRange& aRange + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mRange(aRange) + , mOldValue(aRange.mCalledByJS) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + } + ~AutoCalledByJSRestore() + { + mRange.mCalledByJS = mOldValue; + } + bool SavedValue() const { return mOldValue; } + }; + struct MOZ_STACK_CLASS AutoInvalidateSelection { explicit AutoInvalidateSelection(nsRange* aRange) : mRange(aRange) @@ -428,6 +471,7 @@ protected: bool mStartOffsetWasIncremented : 1; bool mEndOffsetWasIncremented : 1; bool mEnableGravitationOnElementRemoval : 1; + bool mCalledByJS : 1; #ifdef DEBUG int32_t mAssertNextInsertOrAppendIndex; nsINode* mAssertNextInsertOrAppendNode; diff --git a/dom/events/EventDispatcher.cpp b/dom/events/EventDispatcher.cpp index 001b458cf3..df3080dd47 100644 --- a/dom/events/EventDispatcher.cpp +++ b/dom/events/EventDispatcher.cpp @@ -693,6 +693,13 @@ EventDispatcher::Dispatch(nsISupports* aTarget, NS_ENSURE_TRUE(aEvent->mMessage || !aDOMEvent || aTargets, NS_ERROR_DOM_INVALID_STATE_ERR); + // Events shall not be fired while we are in stable state to prevent anything + // visible from the scripts. + // See comment in CycleCollectedJSContext::AfterProcessMicrotasks for why we allow it anyway. + // MOZ_ASSERT(!nsContentUtils::IsInStableOrMetaStableState()); + // NS_ENSURE_TRUE(!nsContentUtils::IsInStableOrMetaStableState(), + // NS_ERROR_DOM_INVALID_STATE_ERR); + #ifdef MOZ_TASK_TRACER { if (aDOMEvent) { diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index c5849de49e..dd2d134f8e 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -3105,6 +3105,26 @@ HTMLInputElement::GetFiles() return mFileList; } +void +HTMLInputElement::SetFiles(FileList* aFiles) +{ + if (mType != NS_FORM_INPUT_FILE || !aFiles) { + return; + } + + // Clear |mFileList| to omit |UpdateFileList| + if (mFileList) { + mFileList->Clear(); + mFileList = nullptr; + } + + // Update |mFilesOrDirectories| + SetFiles(aFiles, true); + + // Update |mFileList| without copy + mFileList = aFiles; +} + /* static */ void HTMLInputElement::HandleNumberControlSpin(void* aData) { diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h index 16651d5200..24fc7454ac 100644 --- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -474,6 +474,7 @@ public: // XPCOM GetForm() is OK FileList* GetFiles(); + void SetFiles(FileList* aFiles); // XPCOM GetFormAction() is OK void SetFormAction(const nsAString& aValue, ErrorResult& aRv) diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp index 0cec5ea746..f1d9398301 100644 --- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -53,6 +53,7 @@ #include "MediaDecoder.h" #include "MediaPrefs.h" #include "MediaResource.h" +#include "MediaShutdownManager.h" #include "nsICategoryManager.h" #include "nsIContentPolicy.h" @@ -3203,6 +3204,13 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed& aNo NotifyOwnerDocumentActivityChanged(); MOZ_ASSERT(NS_IsMainThread()); + + // We initialize the MediaShutdownManager as the HTMLMediaElement is always + // constructed on the main thread, and not during stable state. + // (MediaShutdownManager make use of nsIAsyncShutdownClient which is written + // in JS) + MediaShutdownManager::InitStatics(); + mWatchManager.Watch(mDownloadSuspendedByCache, &HTMLMediaElement::UpdateReadyStateInternal); // Paradoxically, there is a self-edge whereby UpdateReadyStateInternal refuses // to run until mReadyState reaches at least HAVE_METADATA by some other means. diff --git a/dom/html/HTMLTrackElement.cpp b/dom/html/HTMLTrackElement.cpp index 07fbe2073d..710e506d6c 100644 --- a/dom/html/HTMLTrackElement.cpp +++ b/dom/html/HTMLTrackElement.cpp @@ -264,14 +264,16 @@ void HTMLTrackElement::DispatchLoadResource() { if (!mLoadResourceDispatched) { - RefPtr r = NewRunnableMethod(this, &HTMLTrackElement::LoadResource); + RefPtr listener = new WebVTTListener(this); + RefPtr r = NewRunnableMethod>(this, + &HTMLTrackElement::LoadResource, std::move(listener)); nsContentUtils::RunInStableState(r.forget()); mLoadResourceDispatched = true; } } void -HTMLTrackElement::LoadResource() +HTMLTrackElement::LoadResource(RefPtr&& aWebVTTListener) { mLoadResourceDispatched = false; @@ -317,33 +319,46 @@ HTMLTrackElement::LoadResource() } } - nsCOMPtr channel; - nsCOMPtr loadGroup = OwnerDoc()->GetDocumentLoadGroup(); - rv = NS_NewChannel(getter_AddRefs(channel), - uri, - static_cast(this), - secFlags, - nsIContentPolicy::TYPE_INTERNAL_TRACK, - loadGroup, - nullptr, // aCallbacks - nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI); - - NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv)); - - mListener = new WebVTTListener(this); + mListener = std::move(aWebVTTListener); + // This will do 6. Set the text track readiness state to loading. rv = mListener->LoadResource(); NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv)); - channel->SetNotificationCallbacks(mListener); - LOG(LogLevel::Debug, ("opening webvtt channel")); - rv = channel->AsyncOpen2(mListener); - - if (NS_FAILED(rv)) { - SetReadyState(TextTrackReadyState::FailedToLoad); + nsIDocument* doc = OwnerDoc(); + if (!doc) { return; } - mChannel = channel; + // 9. End the synchronous section, continuing the remaining steps in parallel. + NS_DispatchToMainThread(NS_NewRunnableFunction( + [ self = RefPtr(this), uri, secFlags ]() { + if (!self->mListener) { + // Shutdown got called, abort. + return; + } + nsCOMPtr channel; + nsCOMPtr loadGroup = self->OwnerDoc()->GetDocumentLoadGroup(); + nsresult rv = NS_NewChannel(getter_AddRefs(channel), + uri, + static_cast(self), + secFlags, + nsIContentPolicy::TYPE_INTERNAL_TRACK, + loadGroup, + nullptr, // aCallbacks + nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI); + NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv)); + + channel->SetNotificationCallbacks(self->mListener); + + LOG(LogLevel::Debug, ("opening webvtt channel")); + rv = channel->AsyncOpen2(self->mListener); + + if (NS_FAILED(rv)) { + self->SetReadyState(TextTrackReadyState::FailedToLoad); + return; + } + self->mChannel = channel; + })); } nsresult diff --git a/dom/html/HTMLTrackElement.h b/dom/html/HTMLTrackElement.h index 84828e4a9f..c5cab96b6a 100644 --- a/dom/html/HTMLTrackElement.h +++ b/dom/html/HTMLTrackElement.h @@ -118,9 +118,6 @@ protected: virtual JSObject* WrapNode(JSContext* aCx, JS::Handle aGivenProto) override; void OnChannelRedirect(nsIChannel* aChannel, nsIChannel* aNewChannel, uint32_t aFlags); - // Open a new channel to the HTMLTrackElement's src attribute and call - // mListener's LoadResource(). - void LoadResource(); friend class TextTrackCue; friend class WebVTTListener; @@ -134,6 +131,9 @@ protected: private: void DispatchLoadResource(); + // Open a new channel to the HTMLTrackElement's src attribute and call + // mListener's LoadResource(). + void LoadResource(RefPtr&& aWebVTTListener); bool mLoadResourceDispatched; RefPtr mWindowDestroyObserver; diff --git a/dom/html/TextTrackManager.cpp b/dom/html/TextTrackManager.cpp index 34307e47ec..1809478f2d 100644 --- a/dom/html/TextTrackManager.cpp +++ b/dom/html/TextTrackManager.cpp @@ -602,6 +602,17 @@ void TextTrackManager::TimeMarchesOn() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + CycleCollectedJSContext* context = CycleCollectedJSContext::Get(); + if (context && context->IsInStableOrMetaStableState()) { + // FireTimeUpdate can be called while at stable state following a + // current position change which triggered a state watcher in MediaDecoder + // (see bug 1443429). + // TimeMarchesOn() will modify JS attributes which is forbidden while in + // stable state. So we dispatch a task to perform such operation later + // instead. + DispatchTimeMarchesOn(); + return; + } WEBVTT_LOG("TimeMarchesOn"); mTimeMarchesOnDispatched = false; diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index 26528893f6..2e23dec1e6 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -453,8 +453,6 @@ MediaDecoder::MediaDecoder(MediaDecoderOwner* aOwner) mWatchManager.Watch(mLogicallySeeking, &MediaDecoder::SeekingChanged); mWatchManager.Watch(mIsAudioDataAudible, &MediaDecoder::NotifyAudibleStateChanged); - - MediaShutdownManager::InitStatics(); } #undef INIT_MIRROR diff --git a/dom/media/WebVTTListener.cpp b/dom/media/WebVTTListener.cpp index 18692ca273..7ff824073e 100644 --- a/dom/media/WebVTTListener.cpp +++ b/dom/media/WebVTTListener.cpp @@ -4,6 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "WebVTTListener.h" +#include "mozilla/CycleCollectedJSContext.h" #include "mozilla/dom/TextTrackCue.h" #include "mozilla/dom/TextTrackRegion.h" #include "mozilla/dom/VTTRegionBinding.h" @@ -34,9 +35,22 @@ LazyLogModule gTextTrackLog("TextTrack"); WebVTTListener::WebVTTListener(HTMLTrackElement* aElement) : mElement(aElement) + , mParserWrapperError(NS_OK) { MOZ_ASSERT(mElement, "Must pass an element to the callback"); VTT_LOG("WebVTTListener created."); + MOZ_DIAGNOSTIC_ASSERT( + CycleCollectedJSContext::Get() && + !CycleCollectedJSContext::Get()->IsInStableOrMetaStableState()); + mParserWrapper = do_CreateInstance(NS_WEBVTTPARSERWRAPPER_CONTRACTID, + &mParserWrapperError); + if (NS_SUCCEEDED(mParserWrapperError)) { + nsPIDOMWindowInner* window = mElement->OwnerDoc()->GetInnerWindow(); + mParserWrapperError = mParserWrapper->LoadParser(window); + } + if (NS_SUCCEEDED(mParserWrapperError)) { + mParserWrapperError = mParserWrapper->Watch(this); + } } WebVTTListener::~WebVTTListener() @@ -54,16 +68,8 @@ WebVTTListener::GetInterface(const nsIID &aIID, nsresult WebVTTListener::LoadResource() { - nsresult rv; - mParserWrapper = do_CreateInstance(NS_WEBVTTPARSERWRAPPER_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - nsPIDOMWindowInner* window = mElement->OwnerDoc()->GetInnerWindow(); - rv = mParserWrapper->LoadParser(window); - NS_ENSURE_SUCCESS(rv, rv); - - rv = mParserWrapper->Watch(this); - NS_ENSURE_SUCCESS(rv, rv); + // Exit if we failed to create the WebVTTParserWrapper (vtt.jsm) + NS_ENSURE_SUCCESS(mParserWrapperError, mParserWrapperError); mElement->SetReadyState(TextTrackReadyState::Loading); return NS_OK; diff --git a/dom/media/WebVTTListener.h b/dom/media/WebVTTListener.h index 461d7f00da..03cb6e11c7 100644 --- a/dom/media/WebVTTListener.h +++ b/dom/media/WebVTTListener.h @@ -57,6 +57,7 @@ private: RefPtr mElement; nsCOMPtr mParserWrapper; + nsresult mParserWrapperError; }; } // namespace dom diff --git a/dom/media/webspeech/synth/nsSpeechTask.cpp b/dom/media/webspeech/synth/nsSpeechTask.cpp index 66ab0139e9..02f2fa5260 100644 --- a/dom/media/webspeech/synth/nsSpeechTask.cpp +++ b/dom/media/webspeech/synth/nsSpeechTask.cpp @@ -59,15 +59,30 @@ public: switch (event) { case MediaStreamGraphEvent::EVENT_FINISHED: { + RefPtr self = this; if (!mStarted) { mStarted = true; - nsCOMPtr startRunnable = - NewRunnableMethod(this, &SynthStreamListener::DoNotifyStarted); + nsCOMPtr startRunnable = NS_NewRunnableFunction( + [self] { + // "start" event will be fired in DoNotifyStarted() which is + // not allowed in stable state, so we do it asynchronously in + // next run. + NS_DispatchToMainThread(NewRunnableMethod( + self, + &SynthStreamListener::DoNotifyStarted)); + }); aGraph->DispatchToMainThreadAfterStreamStateUpdate(startRunnable.forget()); } - nsCOMPtr endRunnable = - NewRunnableMethod(this, &SynthStreamListener::DoNotifyFinished); + nsCOMPtr endRunnable = NS_NewRunnableFunction( + [self] { + // "end" event will be fired in DoNotifyFinished() which is + // not allowed in stable state, so we do it asynchronously in + // next run. + NS_DispatchToMainThread(NewRunnableMethod( + self, + &SynthStreamListener::DoNotifyFinished)); + }); aGraph->DispatchToMainThreadAfterStreamStateUpdate(endRunnable.forget()); } break; @@ -85,8 +100,16 @@ public: { if (aBlocked == MediaStreamListener::UNBLOCKED && !mStarted) { mStarted = true; - nsCOMPtr event = - NewRunnableMethod(this, &SynthStreamListener::DoNotifyStarted); + RefPtr self = this; + nsCOMPtr event = NS_NewRunnableFunction( + [self] { + // "start" event will be fired in DoNotifyStarted() which is + // not allowed in stable state, so we do it asynchronously in + // next run. + NS_DispatchToMainThread(NewRunnableMethod( + self, + &SynthStreamListener::DoNotifyStarted)); + }); aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget()); } } diff --git a/dom/webidl/HTMLInputElement.webidl b/dom/webidl/HTMLInputElement.webidl index 93ad90f456..528689bbd1 100644 --- a/dom/webidl/HTMLInputElement.webidl +++ b/dom/webidl/HTMLInputElement.webidl @@ -40,7 +40,7 @@ interface HTMLInputElement : HTMLElement { attribute boolean disabled; readonly attribute HTMLFormElement? form; [Pure] - readonly attribute FileList? files; + attribute FileList? files; [CEReactions, Pure, SetterThrows] attribute DOMString formAction; [CEReactions, Pure, SetterThrows] diff --git a/dom/webidl/IntersectionObserver.webidl b/dom/webidl/IntersectionObserver.webidl index 83200d950d..b5c904c139 100644 --- a/dom/webidl/IntersectionObserver.webidl +++ b/dom/webidl/IntersectionObserver.webidl @@ -30,7 +30,7 @@ interface IntersectionObserverEntry { Pref="dom.intersectionObserver.enabled"] interface IntersectionObserver { [Constant] - readonly attribute Element? root; + readonly attribute Node? root; [Constant] readonly attribute DOMString rootMargin; [Constant,Cached] @@ -56,7 +56,7 @@ dictionary IntersectionObserverEntryInit { }; dictionary IntersectionObserverInit { - Element? root = null; + (Element or Document)? root = null; DOMString rootMargin = "0px"; (double or sequence) threshold = 0; }; diff --git a/dom/webidl/Range.webidl b/dom/webidl/Range.webidl index 6b8be970ba..a7bd5f95d6 100644 --- a/dom/webidl/Range.webidl +++ b/dom/webidl/Range.webidl @@ -26,22 +26,23 @@ interface Range { [Throws] readonly attribute Node commonAncestorContainer; - [Throws] + [Throws, BinaryName="setStartJS"] void setStart(Node refNode, unsigned long offset); - [Throws] + [Throws, BinaryName="setEndJS"] void setEnd(Node refNode, unsigned long offset); - [Throws] + [Throws, BinaryName="setStartBeforeJS"] void setStartBefore(Node refNode); - [Throws] + [Throws, BinaryName="setStartAfterJS"] void setStartAfter(Node refNode); - [Throws] + [Throws, BinaryName="setEndBeforeJS"] void setEndBefore(Node refNode); - [Throws] + [Throws, BinaryName="setEndAfterJS"] void setEndAfter(Node refNode); + [BinaryName="collapseJS"] void collapse(optional boolean toStart = false); - [Throws] + [Throws, BinaryName="selectNodeJS"] void selectNode(Node refNode); - [Throws] + [Throws, BinaryName="selectNodeContentsJS"] void selectNodeContents(Node refNode); const unsigned short START_TO_START = 0; diff --git a/dom/webidl/Selection.webidl b/dom/webidl/Selection.webidl index 2b85b87f90..b5da4a5eb4 100644 --- a/dom/webidl/Selection.webidl +++ b/dom/webidl/Selection.webidl @@ -17,17 +17,17 @@ interface Selection { readonly attribute unsigned long focusOffset; readonly attribute boolean isCollapsed; - [Throws] + [Throws, BinaryName="collapseJS"] void collapse(Node node, unsigned long offset); - [Throws] + [Throws, BinaryName="collapseToStartJS"] void collapseToStart(); - [Throws] + [Throws, BinaryName="collapseToEndJS"] void collapseToEnd(); - [Throws] + [Throws, BinaryName="extendJS"] void extend(Node node, unsigned long offset); - [Throws] + [Throws, BinaryName="selectAllChildrenJS"] void selectAllChildren(Node node); [Throws] void deleteFromDocument(); @@ -36,7 +36,7 @@ interface Selection { readonly attribute DOMString type; [Throws] Range getRangeAt(unsigned long index); - [Throws] + [Throws, BinaryName="addRangeJS"] void addRange(Range range); [Throws] void removeRange(Range range); @@ -46,7 +46,7 @@ interface Selection { [Throws] boolean containsNode(Node node, boolean allowPartialContainment); - [Throws] + [Throws, BinaryName="setBaseAndExtentJS"] void setBaseAndExtent(Node anchorNode, unsigned long anchorOffset, Node focusNode, diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index 0bea0223f8..7f70c9ee10 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -3082,6 +3082,8 @@ void ServiceWorkerManager::FireControllerChange(ServiceWorkerRegistrationInfo* aRegistration) { AssertIsOnMainThread(); + + AutoTArray, 16> documents; for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) { if (iter.UserData() != aRegistration) { continue; @@ -3092,6 +3094,12 @@ ServiceWorkerManager::FireControllerChange(ServiceWorkerRegistrationInfo* aRegis continue; } + documents.AppendElement(doc); + } + + // Fire event after iterating mControlledDocuments is done to prevent + // modification by reentering from the event handlers during iteration. + for (auto& doc : documents) { FireControllerChangeOnDocument(doc); } } diff --git a/dom/workers/WorkerPrefs.h b/dom/workers/WorkerPrefs.h index 415435cf06..a8a1e61f4f 100644 --- a/dom/workers/WorkerPrefs.h +++ b/dom/workers/WorkerPrefs.h @@ -26,6 +26,7 @@ WORKER_SIMPLE_PREF("browser.dom.window.dump.enabled", DumpEnabled, DUMP) WORKER_SIMPLE_PREF("canvas.imagebitmap_extensions.enabled", ImageBitmapExtensionsEnabled, IMAGEBITMAP_EXTENSIONS_ENABLED) WORKER_SIMPLE_PREF("dom.caches.enabled", DOMCachesEnabled, DOM_CACHES) WORKER_SIMPLE_PREF("dom.caches.testing.enabled", DOMCachesTestingEnabled, DOM_CACHES_TESTING) +WORKER_SIMPLE_PREF("dom.min_timeout_value", DOMMinTimeoutValue, DOM_MIN_TIMEOUT_VALUE) WORKER_SIMPLE_PREF("dom.performance.enable_user_timing_logging", PerformanceLoggingEnabled, PERFORMANCE_LOGGING_ENABLED) WORKER_SIMPLE_PREF("dom.webnotifications.enabled", DOMWorkerNotificationEnabled, DOM_WORKERNOTIFICATION) WORKER_SIMPLE_PREF("dom.webnotifications.serviceworker.enabled", DOMServiceWorkerNotificationEnabled, DOM_SERVICEWORKERNOTIFICATION) diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index 22511af313..f6622d9e3d 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -1995,13 +1995,22 @@ struct WorkerPrivate::TimeoutInfo mNestingLevel = kClampTimeoutNestingLevel; } - void CalculateTargetTime() { + void CalculateTargetTime(JSContext* aCx) { auto target = mInterval; + int32_t minTimeoutValue; + + // We're on a worker thread; go through WorkerPrivate for the pref. + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); + if (workerPrivate) { + minTimeoutValue = workerPrivate->DOMMinTimeoutValue(); + } else { + // fall back to default 4 ms + minTimeoutValue = 4; + } + // Clamp timeout for workers, except chrome workers if (mNestingLevel >= kClampTimeoutNestingLevel && !mOnChromeWorker) { - target = TimeDuration::Max( - mInterval, - TimeDuration::FromMilliseconds(Preferences::GetInt("dom.min_timeout_value"))); + target = TimeDuration::Max(mInterval,TimeDuration::FromMilliseconds(minTimeoutValue)); } mTargetTime = TimeStamp::Now() + target; } @@ -6387,7 +6396,7 @@ WorkerPrivate::RunExpiredTimeouts(JSContext* aCx) // Reschedule intervals. // Reschedule a timeout and, if needed, increase the nesting level. info->AccumulateNestingLevel(info->mNestingLevel); - info->CalculateTargetTime(); + info->CalculateTargetTime(aCx); // Don't re-sort the list here, we'll do that at the end. ++index; } diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 689256ed9b..dd893a15fd 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -1771,6 +1771,9 @@ PresShell::Initialize(nscoord aWidth, nscoord aHeight) // (Do this in a script runner, since our caller might have a script // blocker on the stack.) nsContentUtils::AddScriptRunner(new XBLConstructorRunner(mDocument)); + + // XBLConstructorRunner might destroy us. + NS_ENSURE_STATE(!mHaveShutDown); } NS_ASSERTION(rootFrame, "How did that happen?"); diff --git a/layout/generic/Selection.h b/layout/generic/Selection.h index 83a2e2c688..b20e5d7bf6 100644 --- a/layout/generic/Selection.h +++ b/layout/generic/Selection.h @@ -21,8 +21,12 @@ struct CachedOffsetForFrame; class nsAutoScrollTimer; class nsIContentIterator; +class nsIDocument; +class nsIEditor; class nsIFrame; +class nsIHTMLEditor; class nsFrameSelection; +class nsPIDOMWindowOuter; struct SelectionDetails; class nsCopySupport; class nsHTMLCopyEncoder; @@ -170,13 +174,19 @@ public: uint32_t FocusOffset(); bool IsCollapsed() const; - void Collapse(nsINode& aNode, uint32_t aOffset, mozilla::ErrorResult& aRv); - void CollapseToStart(mozilla::ErrorResult& aRv); - void CollapseToEnd(mozilla::ErrorResult& aRv); + // *JS() methods are mapped to Selection.*(). + // They may move focus only when the range represents normal selection. + // These methods shouldn't be used by non-JS callers. + void CollapseJS(nsINode& aNode, uint32_t aOffset, + mozilla::ErrorResult& aRv); + void CollapseToStartJS(mozilla::ErrorResult& aRv); + void CollapseToEndJS(mozilla::ErrorResult& aRv); - void Extend(nsINode& aNode, uint32_t aOffset, mozilla::ErrorResult& aRv); + void ExtendJS(nsINode& aNode, uint32_t aOffset, + mozilla::ErrorResult& aRv); + + void SelectAllChildrenJS(nsINode& aNode, mozilla::ErrorResult& aRv); - void SelectAllChildren(nsINode& aNode, mozilla::ErrorResult& aRv); void DeleteFromDocument(mozilla::ErrorResult& aRv); uint32_t RangeCount() const @@ -187,7 +197,7 @@ public: void GetType(nsAString& aOutType) const; nsRange* GetRangeAt(uint32_t aIndex, mozilla::ErrorResult& aRv); - void AddRange(nsRange& aRange, mozilla::ErrorResult& aRv); + void AddRangeJS(nsRange& aRange, mozilla::ErrorResult& aRv); void RemoveRange(nsRange& aRange, mozilla::ErrorResult& aRv); void RemoveAllRanges(mozilla::ErrorResult& aRv); @@ -207,9 +217,9 @@ public: void Modify(const nsAString& aAlter, const nsAString& aDirection, const nsAString& aGranularity, mozilla::ErrorResult& aRv); - void SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset, - nsINode& aFocusNode, uint32_t aFocusOffset, - mozilla::ErrorResult& aRv); + void SetBaseAndExtentJS(nsINode& aAnchorNode, uint32_t aAnchorOffset, + nsINode& aFocusNode, uint32_t aFocusOffset, + mozilla::ErrorResult& aRv); bool GetInterlinePosition(mozilla::ErrorResult& aRv); void SetInterlinePosition(bool aValue, mozilla::ErrorResult& aRv); @@ -243,6 +253,17 @@ public: int16_t aVPercent, int16_t aHPercent, mozilla::ErrorResult& aRv); + // Non-JS callers should use the following methods. + void Collapse(nsINode& aNode, uint32_t aOffset, mozilla::ErrorResult& aRv); + void CollapseToStart(mozilla::ErrorResult& aRv); + void CollapseToEnd(mozilla::ErrorResult& aRv); + void Extend(nsINode& aNode, uint32_t aOffset, mozilla::ErrorResult& aRv); + void AddRange(nsRange& aRange, mozilla::ErrorResult& aRv); + void SelectAllChildren(nsINode& aNode, mozilla::ErrorResult& aRv); + void SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset, + nsINode& aFocusNode, uint32_t aFocusOffset, + mozilla::ErrorResult& aRv); + void AddSelectionChangeBlocker(); void RemoveSelectionChangeBlocker(); bool IsBlockingSelectionChangeEvents() const; @@ -265,7 +286,8 @@ public: mSelectionType = aSelectionType; } - nsresult NotifySelectionListeners(); + nsresult NotifySelectionListeners(bool aCalledByJS); + nsresult NotifySelectionListeners(); friend struct AutoUserInitiated; struct MOZ_RAII AutoUserInitiated @@ -341,6 +363,43 @@ private: */ nsresult AddItemInternal(nsRange* aRange, int32_t* aOutIndex); + nsIDocument* GetDocument() const; + nsPIDOMWindowOuter* GetWindow() const; + nsIEditor* GetEditor() const; + + /** + * GetCommonEditingHostForAllRanges() returns common editing host of all + * ranges if there is. If at least one of the ranges is in non-editable + * element, returns nullptr. See following examples for the detail: + * + *
+ * an[cestor + *
+ * non-editable + *
+ * desc]endant + * in this case, this returns div#a because div#c is also in div#a. + * + *
+ * an[ce]stor + *
+ * non-editable + *
+ * de[sc]endant + * in this case, this returns div#a because second range is also in div#a + * and common ancestor of the range (i.e., div#c) is editable. + * + *
+ * an[ce]stor + *
+ * [non]-editable + *
+ * de[sc]endant + * in this case, this returns nullptr because the second range is in + * non-editable area. + */ + Element* GetCommonEditingHostForAllRanges(); + // These are the ranges inside this selection. They are kept sorted in order // of DOM start position. // @@ -371,6 +430,12 @@ private: */ bool mUserInitiated; + /** + * When the selection change is caused by a call of Selection API, + * mCalledByJS is true. Otherwise, false. + */ + bool mCalledByJS; + // Non-zero if we don't want any changes we make to the selection to be // visible to content. If non-zero, content won't be notified about changes. uint32_t mSelectionChangeBlockerCount; diff --git a/layout/generic/nsSelection.cpp b/layout/generic/nsSelection.cpp index b9ee20d1fc..a2bdecaa81 100644 --- a/layout/generic/nsSelection.cpp +++ b/layout/generic/nsSelection.cpp @@ -10,6 +10,7 @@ #include "mozilla/dom/Selection.h" #include "mozilla/Attributes.h" +#include "mozilla/AutoRestore.h" #include "mozilla/EventStates.h" #include "nsCOMPtr.h" @@ -84,6 +85,7 @@ static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID); #include "nsIEditor.h" #include "nsIHTMLEditor.h" #include "nsFocusManager.h" +#include "nsPIDOMWindow.h" using namespace mozilla; using namespace mozilla::dom; @@ -1199,7 +1201,7 @@ Selection::ToString(nsAString& aReturn) aReturn.Truncate(); return NS_OK; } - shell->FlushPendingNotifications(Flush_Style); + shell->FlushPendingNotifications(Flush_Frames); return ToStringWithFormat("text/plain", nsIDocumentEncoder::SkipInvisibleContent, @@ -1876,6 +1878,7 @@ printf(" * TakeFocus - moving into new cell\n"); // Don't notify selection listeners if batching is on: if (GetBatching()) return NS_OK; + // Be aware, the Selection instance may be destroyed after this call. return NotifySelectionListeners(SelectionType::eNormal); } @@ -1916,6 +1919,7 @@ nsFrameSelection::SetDragState(bool aState) mDragSelectingCells = false; // Notify that reason is mouse up. PostReason(nsISelectionListener::MOUSEUP_REASON); + // Be aware, the Selection instance may be destroyed after this call. NotifySelectionListeners(SelectionType::eNormal); } } @@ -2410,6 +2414,7 @@ nsFrameSelection::EndBatchChanges(int16_t aReason) int16_t postReason = PopReason() | aReason; PostReason(postReason); mChangesDuringBatching = false; + // Be aware, the Selection instance may be destroyed after this call. NotifySelectionListeners(SelectionType::eNormal); } } @@ -2421,7 +2426,8 @@ nsFrameSelection::NotifySelectionListeners(SelectionType aSelectionType) int8_t index = GetIndexFromSelectionType(aSelectionType); if (index >=0 && mDomSelections[index]) { - return mDomSelections[index]->NotifySelectionListeners(); + RefPtr selection = mDomSelections[index]; + return selection->NotifySelectionListeners(); } return NS_ERROR_FAILURE; } @@ -3473,6 +3479,7 @@ Selection::Selection() , mDirection(eDirNext) , mSelectionType(SelectionType::eNormal) , mUserInitiated(false) + , mCalledByJS(false) , mSelectionChangeBlockerCount(0) { } @@ -3483,6 +3490,7 @@ Selection::Selection(nsFrameSelection* aList) , mDirection(eDirNext) , mSelectionType(SelectionType::eNormal) , mUserInitiated(false) + , mCalledByJS(false) , mSelectionChangeBlockerCount(0) { } @@ -4943,7 +4951,10 @@ Selection::RemoveAllRanges(ErrorResult& aRv) RefPtr frameSelection = mFrameSelection; frameSelection->ClearTableCellSelection(); + // Be aware, this instance may be destroyed after this call. + // XXX Why doesn't this call Selection::NotifySelectionListener() directly? result = frameSelection->NotifySelectionListeners(GetType()); + // Also need to notify the frames! // PresShell::CharacterDataChanged should do that on DocumentChanged if (NS_FAILED(result)) { @@ -4966,6 +4977,14 @@ Selection::AddRange(nsIDOMRange* aDOMRange) return result.StealNSResult(); } +void +Selection::AddRangeJS(nsRange& aRange, ErrorResult& aRv) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = true; + AddRange(aRange, aRv); +} + void Selection::AddRange(nsRange& aRange, ErrorResult& aRv) { @@ -5020,6 +5039,8 @@ Selection::AddRangeInternal(nsRange& aRange, nsIDocument* aDocument, if (!mFrameSelection) return;//nothing to do + // Be aware, this instance may be destroyed after this call. + // XXX Why doesn't this call Selection::NotifySelectionListener() directly? RefPtr frameSelection = mFrameSelection; result = frameSelection->NotifySelectionListeners(GetType()); if (NS_FAILED(result)) { @@ -5115,6 +5136,9 @@ Selection::RemoveRange(nsRange& aRange, ErrorResult& aRv) if (!mFrameSelection) return;//nothing to do + + // Be aware, this instance may be destroyed after this call. + // XXX Why doesn't this call Selection::NotifySelectionListener() directly? RefPtr frameSelection = mFrameSelection; rv = frameSelection->NotifySelectionListeners(GetType()); if (NS_FAILED(rv)) { @@ -5140,6 +5164,14 @@ Selection::CollapseNative(nsINode* aParentNode, int32_t aOffset) return Collapse(aParentNode, aOffset); } +void +Selection::CollapseJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = true; + Collapse(aNode, aOffset, aRv); +} + nsresult Selection::Collapse(nsINode* aParentNode, int32_t aOffset) { @@ -5222,6 +5254,9 @@ Selection::Collapse(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv) } setAnchorFocusRange(0); selectFrames(presContext, range, true); + + // Be aware, this instance may be destroyed after this call. + // XXX Why doesn't this call Selection::NotifySelectionListener() directly? result = frameSelection->NotifySelectionListeners(GetType()); if (NS_FAILED(result)) { aRv.Throw(result); @@ -5240,6 +5275,14 @@ Selection::CollapseToStart() return result.StealNSResult(); } +void +Selection::CollapseToStartJS(ErrorResult& aRv) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = true; + CollapseToStart(aRv); +} + void Selection::CollapseToStart(ErrorResult& aRv) { @@ -5281,6 +5324,14 @@ Selection::CollapseToEnd() return result.StealNSResult(); } +void +Selection::CollapseToEndJS(ErrorResult& aRv) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = true; + CollapseToEnd(aRv); +} + void Selection::CollapseToEnd(ErrorResult& aRv) { @@ -5495,6 +5546,14 @@ Selection::ExtendNative(nsINode* aParentNode, int32_t aOffset) return Extend(aParentNode, aOffset); } +void +Selection::ExtendJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = true; + Extend(aNode, aOffset, aRv); +} + nsresult Selection::Extend(nsINode* aParentNode, int32_t aOffset) { @@ -5773,6 +5832,9 @@ Selection::Extend(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv) printf ("Sel. Extend to %p %s %d\n", content.get(), nsAtomCString(content->NodeInfo()->NameAtom()).get(), aOffset); #endif + + // Be aware, this instance may be destroyed after this call. + // XXX Why doesn't this call Selection::NotifySelectionListener() directly? RefPtr frameSelection = mFrameSelection; res = frameSelection->NotifySelectionListeners(GetType()); if (NS_FAILED(res)) { @@ -5790,6 +5852,14 @@ Selection::SelectAllChildren(nsIDOMNode* aParentNode) return result.StealNSResult(); } +void +Selection::SelectAllChildrenJS(nsINode& aNode, ErrorResult& aRv) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = true; + SelectAllChildren(aNode, aRv); +} + void Selection::SelectAllChildren(nsINode& aNode, ErrorResult& aRv) { @@ -5938,6 +6008,30 @@ Selection::GetPresShell() const return mFrameSelection->GetShell(); } +nsIDocument* +Selection::GetDocument() const +{ + nsIPresShell* presShell = GetPresShell(); + return presShell ? presShell->GetDocument() : nullptr; +} + +nsPIDOMWindowOuter* +Selection::GetWindow() const +{ + nsIDocument* document = GetDocument(); + return document ? document->GetWindow() : nullptr; +} + +nsIEditor* +Selection::GetEditor() const +{ + nsPresContext* presContext = GetPresContext(); + if (!presContext) { + return nullptr; + } + return nsContentUtils::GetHTMLEditor(presContext); +} + nsIFrame * Selection::GetSelectionAnchorGeometry(SelectionRegion aRegion, nsRect* aRect) { @@ -6239,12 +6333,101 @@ Selection::RemoveSelectionListener(nsISelectionListener* aListenerToRemove, } } +Element* +Selection::GetCommonEditingHostForAllRanges() +{ + Element* editingHost = nullptr; + for (RangeData& rangeData : mRanges) { + nsRange* range = rangeData.mRange; + MOZ_ASSERT(range); + nsINode* commonAncestorNode = range->GetCommonAncestor(); + if (!commonAncestorNode || !commonAncestorNode->IsContent()) { + return nullptr; + } + nsIContent* commonAncestor = commonAncestorNode->AsContent(); + Element* foundEditingHost = commonAncestor->GetEditingHost(); + // Even when common ancestor is a non-editable element in a contenteditable + // element, we don't need to move focus to the contenteditable element + // because Chromium doesn't set focus to it. + if (!foundEditingHost) { + return nullptr; + } + if (!editingHost) { + editingHost = foundEditingHost; + continue; + } + if (editingHost == foundEditingHost) { + continue; + } + if (nsContentUtils::ContentIsDescendantOf(foundEditingHost, editingHost)) { + continue; + } + if (nsContentUtils::ContentIsDescendantOf(editingHost, foundEditingHost)) { + editingHost = foundEditingHost; + continue; + } + // editingHost and foundEditingHost are not a descendant of the other. + // So, there is no common editing host. + return nullptr; + } + return editingHost; +} + +nsresult +Selection::NotifySelectionListeners(bool aCalledByJS) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = aCalledByJS; + return NotifySelectionListeners(); +} + nsresult Selection::NotifySelectionListeners() { if (!mFrameSelection) return NS_OK;//nothing to do + // Our internal code should not move focus with using this class while + // this moves focus nor from selection listeners. + AutoRestore calledByJSRestorer(mCalledByJS); + mCalledByJS = false; + + // When normal selection is changed by Selection API, we need to move focus + // if common ancestor of all ranges are in an editing host. Note that we + // don't need to move focus *to* the other focusable node, because other + // browsers don't do this, either. + if (mSelectionType == SelectionType::eNormal && + calledByJSRestorer.SavedValue()) { + nsPIDOMWindowOuter* window = GetWindow(); + nsIDocument* document = GetDocument(); + // If the document is in design mode or doesn't have contenteditable + // element, we don't need to move focus. + if (window && document && !document->HasFlag(NODE_IS_EDITABLE) && + GetEditor()) { + RefPtr newEditingHost = GetCommonEditingHostForAllRanges(); + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + nsCOMPtr focusedWindow; + nsIContent* focusedContent = + fm->GetFocusedDescendant(window, false, getter_AddRefs(focusedWindow)); + nsCOMPtr focusedElement = do_QueryInterface(focusedContent); + // When all selected ranges are in an editing host, it should take focus. + if (newEditingHost && newEditingHost != focusedElement) { + MOZ_ASSERT(!newEditingHost->IsInNativeAnonymousSubtree()); + nsCOMPtr domElementToFocus = + do_QueryInterface(newEditingHost->AsDOMNode()); + // Note that don't steal focus from focused window if the window + // doesn't have focus. Additionally, when an element gets focus, + // we usually scroll to the element, but in this case we shouldn't + // do that because Blink&Gecko don't do this. + fm->SetFocus(domElementToFocus, nsIFocusManager::FLAG_NOSWITCHFRAME | + nsIFocusManager::FLAG_NOSCROLL); + } + // Otherwise, we shouldn't move focus since Blink&Gecko don't move + // focus; only the selection range is updated. This is a bit weird but + // it is what it is and we should act the same for parity. + } + } + RefPtr frameSelection = mFrameSelection; if (frameSelection->GetBatching()) { frameSelection->SetDirty(); @@ -6454,6 +6637,19 @@ Selection::Modify(const nsAString& aAlter, const nsAString& aDirection, } } +void +Selection::SetBaseAndExtentJS(nsINode& aAnchorNode, + uint32_t aAnchorOffset, + nsINode& aFocusNode, + uint32_t aFocusOffset, + ErrorResult& aRv) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = true; + SetBaseAndExtent(aAnchorNode, aAnchorOffset, + aFocusNode, aFocusOffset, aRv); +} + void Selection::SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset, nsINode& aFocusNode, uint32_t aFocusOffset, diff --git a/netwerk/protocol/http/nsCORSListenerProxy.cpp b/netwerk/protocol/http/nsCORSListenerProxy.cpp index 3e58d07dae..a06a0df55e 100644 --- a/netwerk/protocol/http/nsCORSListenerProxy.cpp +++ b/netwerk/protocol/http/nsCORSListenerProxy.cpp @@ -7,6 +7,8 @@ #include "mozilla/LinkedList.h" #include "nsCORSListenerProxy.h" + +#include "nsQueryObject.h" #include "nsIChannel.h" #include "nsIHttpChannel.h" #include "HttpChannelChild.h" @@ -1507,6 +1509,9 @@ nsCORSListenerProxy::StartCORSPreflight(nsIChannel* aRequestChannel, method, false); NS_ENSURE_SUCCESS(rv, rv); + RefPtr reqCh = do_QueryObject(aRequestChannel); + RefPtr preCh = do_QueryObject(preHttp); + nsTArray preflightHeaders; if (!aUnsafeHeaders.IsEmpty()) { for (uint32_t i = 0; i < aUnsafeHeaders.Length(); ++i) { @@ -1535,6 +1540,23 @@ nsCORSListenerProxy::StartCORSPreflight(nsIChannel* aRequestChannel, rv = preflightChannel->SetNotificationCallbacks(preflightListener); NS_ENSURE_SUCCESS(rv, rv); + if (reqCh && preCh) { + // Per https://fetch.spec.whatwg.org/#cors-preflight-fetch step 1, the + // request's referrer and referrer policy should match the original request. + // Note that RFC 9110 says we SHOULD NOT send referrers on insecure requests + // which CORS preflights by definition are, so there's a conflict here, but + // since this is expected behaviour in mainstream implementations, we get and + // send the referrer on CORS preflights here. See issue #2451 + uint32_t referrerPolicy = nsIHttpChannel::REFERRER_POLICY_UNSET; + rv = reqCh->GetReferrerPolicy(&referrerPolicy); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr requestReferrerURI; + rv = reqCh->GetReferrer(getter_AddRefs(requestReferrerURI)); + NS_ENSURE_SUCCESS(rv, rv); + rv = preCh->SetReferrerWithPolicy(requestReferrerURI, referrerPolicy); + NS_ENSURE_SUCCESS(rv, rv); + } + // Start preflight rv = preflightChannel->AsyncOpen2(preflightListener); NS_ENSURE_SUCCESS(rv, rv); diff --git a/xpcom/base/CycleCollectedJSContext.cpp b/xpcom/base/CycleCollectedJSContext.cpp index d92283fb20..997769055f 100644 --- a/xpcom/base/CycleCollectedJSContext.cpp +++ b/xpcom/base/CycleCollectedJSContext.cpp @@ -1394,6 +1394,8 @@ CycleCollectedJSContext::ProcessStableStateQueue() MOZ_RELEASE_ASSERT(!mDoingStableStates); mDoingStableStates = true; + // When run, one event can add another event to the mStableStateEvents, as + // such you can't use iterators here. for (uint32_t i = 0; i < mStableStateEvents.Length(); ++i) { nsCOMPtr event = mStableStateEvents[i].forget(); event->Run(); @@ -1477,11 +1479,33 @@ CycleCollectedJSContext::AfterProcessMicrotasks() } // Cleanup Indexed Database transactions: // https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint - CleanupIDBTransactions(RecursionDepth()); + + // We should only ever get here from PerformMicroTaskCheckPoint after a task or other + // checkpoint-able state, never from within ProcessStableStateQueue (mDoingStableStates==false). + // However, some buggy XUL addons may dispatch JS Events from runnables in the StableState queue, + // which then perform a checkpoint at their end, ending up here while ProcessStableStateQueue is + // on the stack. + // Specifically catch that here and add the call to CleanupIDBTransactions to the outer queue, to + // be performed when we know we're in a "regular" stable state again. + if (!mDoingStableStates) { + CleanupIDBTransactions(RecursionDepth()); + } else { + // Don't need a RefPtr to this here as we know this->ProcessStableStateQueue is on the stack + nsCOMPtr cleanupRunnable = NS_NewRunnableFunction( + [this, rec = RecursionDepth()] { + MOZ_ASSERT(mDoingStableStates); + // As this is called from ProcessStableStateQueue, mDoingStableStates == true. + // Switch the flag while CleanupIDBTransactions executes. + mDoingStableStates = false; + CleanupIDBTransactions(rec); + mDoingStableStates = true; + }); + RunInStableState(cleanupRunnable.forget()); + }; } uint32_t -CycleCollectedJSContext::RecursionDepth() +CycleCollectedJSContext::RecursionDepth() const { return mOwningThread->RecursionDepth(); } diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h index 4e387bae1a..c0d7851568 100644 --- a/xpcom/base/CycleCollectedJSContext.h +++ b/xpcom/base/CycleCollectedJSContext.h @@ -357,7 +357,7 @@ public: virtual void BeforeProcessTask(bool aMightBlock); virtual void AfterProcessTask(uint32_t aRecursionDepth); - uint32_t RecursionDepth(); + uint32_t RecursionDepth() const; // Run in stable state (call through nsContentUtils) void RunInStableState(already_AddRefed&& aRunnable); @@ -397,12 +397,12 @@ public: } } - bool IsInMicroTask() + bool IsInMicroTask() const { return mMicroTaskLevel != 0; } - uint32_t MicroTaskLevel() + uint32_t MicroTaskLevel() const { return mMicroTaskLevel; } @@ -416,6 +416,11 @@ public: void PerformDebuggerMicroTaskCheckpoint(); + bool IsInStableOrMetaStableState() const + { + return mDoingStableStates; + } + // Storage for watching rejected promises waiting for some client to // consume their rejection. diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h index 192bc16e06..195b72998b 100644 --- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -247,6 +247,9 @@ ERROR(NS_ERROR_REMOTE_XUL, FAILURE(75)), /* The request resulted in an error page being displayed. */ ERROR(NS_ERROR_LOAD_SHOWED_ERRORPAGE, FAILURE(77)), + /* The request occurred in docshell that lacks a treeowner, so it is + * probably in the process of being torn down. */ + ERROR(NS_ERROR_DOCSHELL_DYING, FAILURE(78)), /* FTP specific error codes: */