From 28243d5736d8ab5953e04d141f0969b7ad1258a9 Mon Sep 17 00:00:00 2001 From: Anny Gakhokidze Date: Wed, 27 May 2020 18:15:36 +0000 Subject: [PATCH] Bug 1630323 - Do not override user preferences when clicking on a service worker notification to open a new document, r=Gijs,nika,geckoview-reviewers,snorp In Bug 1622749 a user preference for where to open new documents (from a service worker notification) was temporarily overriden in order to quickly fix a crash that was happening in mozilla::dom::ClientOpenWindow. The crash was ocurring when the pref "browser.link.open_newwindow" was set to 2, meaning new documents are opened in a new window, instead of a new tab. The reason the browser crashed is because the path for opening a new document is different depending on the current user setting, and in NEWWINDOW case we did not get a browsing context returned when calling mozilla::dom::OpenWindow which resulted in a failed assertion. The solution is to pass in a callback to mozilla::dom::OpenWindow as part of nsOpenWindowInfo object, and invoke that callback with a corresponding BrowsingContext in nsFrameLoader when that browsing context is ready. After we call mozilla::dom::OpenWindow, we wait on a promise, that will be resolved when the callback is invoked, before executing the rest of the code that depends on the browsing context for a newly opened document being available. Differential Revision: https://phabricator.services.mozilla.com/D72745 --- browser/base/content/browser.js | 24 +-- browser/base/content/tabbrowser.js | 4 + dom/base/nsFrameLoader.cpp | 16 +- dom/base/nsFrameLoader.h | 4 + dom/clients/manager/ClientOpenWindowUtils.cpp | 192 +++++++++++------- dom/clients/manager/ClientOpenWindowUtils.h | 3 + dom/clients/manager/moz.build | 1 + .../test/test_notification_openWindow.html | 9 + .../org/mozilla/geckoview/GeckoRuntime.java | 11 +- .../windowwatcher/nsIOpenWindowInfo.idl | 15 ++ .../windowwatcher/nsOpenWindowInfo.cpp | 30 +++ .../windowwatcher/nsOpenWindowInfo.h | 24 ++- 12 files changed, 233 insertions(+), 100 deletions(-) diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index d5c315a31cd2..cdc70e060c8f 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -2218,6 +2218,7 @@ var gBrowserInit = { // [8]: triggeringPrincipal (nsIPrincipal) // [9]: allowInheritPrincipal (bool) // [10]: csp (nsIContentSecurityPolicy) + // [11]: nsOpenWindowInfo let userContextId = window.arguments[5] != undefined ? window.arguments[5] @@ -6106,7 +6107,7 @@ nsBrowserAccess.prototype = { case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW: // FIXME: Bug 408379. So how come this doesn't send the // referrer like the other loads do? - var url = aURI ? aURI.spec : "about:blank"; + var url = aURI && aURI.spec; let features = "all,dialog=no"; if (isPrivate) { features += ",private"; @@ -6129,18 +6130,14 @@ nsBrowserAccess.prototype = { null, aTriggeringPrincipal, null, - aCsp + aCsp, + aOpenWindowInfo ); // At this point, the new browser window is just starting to load, and - // hasn't created the content that we should return. So we - // can't actually return a valid BrowsingContext for this load without - // spinning the event loop. - // - // Fortunately, no current callers of this API who pass OPEN_NEWWINDOW - // actually use the return value, so we're safe returning null for - // now. - // - // Ideally this should be fixed. + // hasn't created the content that we should return. + // If the caller of this function is originating in C++, they can pass a + // callback in nsOpenWindowInfo and it will be invoked when the browsing + // context for a newly opened window is ready. browsingContext = null; } catch (ex) { Cu.reportError(ex); @@ -6153,7 +6150,7 @@ nsBrowserAccess.prototype = { // we can hand back the nsIDOMWindow. The XULBrowserWindow.shouldLoadURI // will do the job of shuttling off the newly opened browser to run in // the right process once it starts loading a URI. - let forceNotRemote = aOpenWindowInfo && !aOpenWindowInfo.remote; + let forceNotRemote = aOpenWindowInfo && !aOpenWindowInfo.isRemote; let userContextId = aOpenWindowInfo ? aOpenWindowInfo.originAttributes.userContextId : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID; @@ -6177,8 +6174,7 @@ nsBrowserAccess.prototype = { break; default: // OPEN_CURRENTWINDOW or an illegal value - browsingContext = - window.content && BrowsingContext.getFromWindow(window.content); + browsingContext = window.gBrowser.selectedBrowser.browsingContext; if (aURI) { let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; if (isExternal) { diff --git a/browser/base/content/tabbrowser.js b/browser/base/content/tabbrowser.js index 030ab7286bf7..423a7953f989 100644 --- a/browser/base/content/tabbrowser.js +++ b/browser/base/content/tabbrowser.js @@ -326,6 +326,10 @@ .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIAppWindow).initialOpenWindowInfo; + if (!openWindowInfo && window.arguments && window.arguments[11]) { + openWindowInfo = window.arguments[11]; + } + let tabArgument = gBrowserInit.getTabToAdopt(); // We only need sameProcessAsFrameLoader in the case where we're passed a tab diff --git a/dom/base/nsFrameLoader.cpp b/dom/base/nsFrameLoader.cpp index 6ad5ed13d007..31fde8262d03 100644 --- a/dom/base/nsFrameLoader.cpp +++ b/dom/base/nsFrameLoader.cpp @@ -275,8 +275,8 @@ static already_AddRefed CreateBrowsingContext( RefPtr opener; if (aOpenWindowInfo && !aOpenWindowInfo->GetForceNoOpener()) { opener = aOpenWindowInfo->GetParent(); - MOZ_ASSERT(opener->IsInProcess(), - "Must create BrowsingContext with opener in-process"); + // Must create BrowsingContext with opener in-process. + MOZ_ASSERT_IF(opener, opener->IsInProcess()); } RefPtr parentInner = @@ -2067,6 +2067,8 @@ nsresult nsFrameLoader::MaybeCreateDocShell() { mPendingBrowsingContext->SetEmbedderElement(mOwnerContent); mPendingBrowsingContext->Embed(); + InvokeBrowsingContextReadyCallback(); + mIsTopLevelContent = mPendingBrowsingContext->IsContent() && !mPendingBrowsingContext->GetParent(); if (!mNetworkCreated && !mIsTopLevelContent) { @@ -2588,6 +2590,7 @@ bool nsFrameLoader::TryRemoteBrowserInternal() { mRemoteBrowser->GetBrowsingContext()); mRemoteBrowser->GetBrowsingContext()->Embed(); + InvokeBrowsingContextReadyCallback(); // Grab the reference to the actor RefPtr browserParent = GetBrowserParent(); @@ -3554,3 +3557,12 @@ bool nsFrameLoader::EnsureBrowsingContextAttached() { mPendingBrowsingContext->EnsureAttached(); return true; } + +void nsFrameLoader::InvokeBrowsingContextReadyCallback() { + if (mOpenWindowInfo) { + if (RefPtr callback = + mOpenWindowInfo->BrowsingContextReadyCallback()) { + callback->BrowsingContextReady(mPendingBrowsingContext); + } + } +} diff --git a/dom/base/nsFrameLoader.h b/dom/base/nsFrameLoader.h index 62d3cef137f6..68194b05f9f5 100644 --- a/dom/base/nsFrameLoader.h +++ b/dom/base/nsFrameLoader.h @@ -465,6 +465,10 @@ class nsFrameLoader final : public nsStubMutationObserver, bool EnsureBrowsingContextAttached(); + // Invoke the callback from nsOpenWindowInfo to indicate that a + // browsing context for a newly opened tab/window is ready. + void InvokeBrowsingContextReadyCallback(); + RefPtr mPendingBrowsingContext; nsCOMPtr mURIToLoad; nsCOMPtr mTriggeringPrincipal; diff --git a/dom/clients/manager/ClientOpenWindowUtils.cpp b/dom/clients/manager/ClientOpenWindowUtils.cpp index e7ac98b35ff8..f3ca23523e2f 100644 --- a/dom/clients/manager/ClientOpenWindowUtils.cpp +++ b/dom/clients/manager/ClientOpenWindowUtils.cpp @@ -27,6 +27,7 @@ #include "nsPIWindowWatcher.h" #include "nsPrintfCString.h" #include "nsWindowWatcher.h" +#include "nsOpenWindowInfo.h" #include "mozilla/dom/BrowserParent.h" #include "mozilla/dom/BrowsingContext.h" @@ -180,43 +181,18 @@ class WebProgressListener final : public nsIWebProgressListener, NS_IMPL_ISUPPORTS(WebProgressListener, nsIWebProgressListener, nsISupportsWeakReference); -void OpenWindow(const ClientOpenWindowArgs& aArgs, BrowsingContext** aBC, +struct ClientOpenWindowArgsParsed { + nsCOMPtr uri; + nsCOMPtr baseURI; + nsCOMPtr principal; + nsCOMPtr csp; +}; + +void OpenWindow(const ClientOpenWindowArgsParsed& aArgsValidated, + nsOpenWindowInfo* aOpenInfo, BrowsingContext** aBC, ErrorResult& aRv) { MOZ_DIAGNOSTIC_ASSERT(aBC); - // [[1. Let url be the result of parsing url with entry settings object's API - // base URL.]] - nsCOMPtr uri; - - nsCOMPtr baseURI; - nsresult rv = NS_NewURI(getter_AddRefs(baseURI), aArgs.baseURL()); - if (NS_WARN_IF(NS_FAILED(rv))) { - nsPrintfCString err("Invalid base URL \"%s\"", aArgs.baseURL().get()); - aRv.ThrowTypeError(err); - return; - } - - rv = NS_NewURI(getter_AddRefs(uri), aArgs.url(), nullptr, baseURI); - if (NS_WARN_IF(NS_FAILED(rv))) { - nsPrintfCString err("Invalid URL \"%s\"", aArgs.url().get()); - aRv.ThrowTypeError(err); - return; - } - - auto principalOrErr = PrincipalInfoToPrincipal(aArgs.principalInfo()); - if (NS_WARN_IF(principalOrErr.isErr())) { - nsPrintfCString err("Failed to obtain principal"); - aRv.ThrowTypeError(err); - return; - } - nsCOMPtr principal = principalOrErr.unwrap(); - MOZ_DIAGNOSTIC_ASSERT(principal); - - nsCOMPtr csp; - if (aArgs.cspInfo().isSome()) { - csp = CSPInfoToCSP(aArgs.cspInfo().ref(), nullptr); - } - // [[6.1 Open Window]] // Find the most recent browser window and open a new tab in it. @@ -244,33 +220,23 @@ void OpenWindow(const ClientOpenWindowArgs& aArgs, BrowsingContext** aBC, aRv.ThrowTypeError("Unable to open window"); return; } - // annyG: This is a hack to fix bug 1622749. - // We will force to open new windows in tabs so we don't crash later. - rv = bwin->OpenURI(uri, nullptr, nsIBrowserDOMWindow::OPEN_NEWTAB, - nsIBrowserDOMWindow::OPEN_NEW, principal, csp, aBC); + nsresult rv = bwin->CreateContentWindow( + nullptr, aOpenInfo, nsIBrowserDOMWindow::OPEN_DEFAULTWINDOW, + nsIBrowserDOMWindow::OPEN_NEW, aArgsValidated.principal, + aArgsValidated.csp, aBC); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.ThrowTypeError("Unable to open window"); return; } } -void WaitForLoad(const ClientOpenWindowArgs& aArgs, +void WaitForLoad(const ClientOpenWindowArgsParsed& aArgsValidated, BrowsingContext* aBrowsingContext, ClientOpPromise::Private* aPromise) { MOZ_DIAGNOSTIC_ASSERT(aBrowsingContext); RefPtr promise = aPromise; - - nsCOMPtr baseURI; - nsresult rv = NS_NewURI(getter_AddRefs(baseURI), aArgs.baseURL()); - if (NS_WARN_IF(NS_FAILED(rv))) { - // Shouldn't really happen, since we passed in the serialization of a URI. - CopyableErrorResult result; - result.ThrowSyntaxError("Bad URL"); - promise->Reject(result, __func__); - return; - } - + nsresult rv; nsCOMPtr webProgress; if (nsIDocShell* docShell = aBrowsingContext->GetDocShell()) { // We're dealing with a non-remote frame. We have access to an nsDocShell, @@ -307,14 +273,11 @@ void WaitForLoad(const ClientOpenWindowArgs& aArgs, promise->Reject(result, __func__); return; } - - if (BrowserParent* browserParent = BrowserParent::GetFrom(element)) { - browserParent->Activate(); - } } - RefPtr listener = - new WebProgressListener(aBrowsingContext, baseURI, do_AddRef(promise)); + // Add a progress listener before we start the load of the service worker URI + RefPtr listener = new WebProgressListener( + aBrowsingContext, aArgsValidated.baseURI, do_AddRef(promise)); rv = webProgress->AddProgressListener(listener, nsIWebProgress::NOTIFY_STATE_WINDOW); @@ -326,6 +289,22 @@ void WaitForLoad(const ClientOpenWindowArgs& aArgs, return; } + // Load the service worker URI + RefPtr loadState = + new nsDocShellLoadState(aArgsValidated.uri); + loadState->SetTriggeringPrincipal(aArgsValidated.principal); + loadState->SetFirstParty(true); + loadState->SetLoadFlags( + nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL); + + rv = aBrowsingContext->LoadURI(loadState, true); + if (NS_FAILED(rv)) { + CopyableErrorResult result; + result.ThrowInvalidStateError("Unable to start the load of the actual URI"); + promise->Reject(result, __func__); + return; + } + // Hold the listener alive until the promise settles. promise->Then( GetMainThreadSerialEventTarget(), __func__, @@ -335,14 +314,15 @@ void WaitForLoad(const ClientOpenWindowArgs& aArgs, #ifdef MOZ_WIDGET_ANDROID -void GeckoViewOpenWindow(const ClientOpenWindowArgs& aArgs, +void GeckoViewOpenWindow(const ClientOpenWindowArgsParsed& aArgsValidated, ClientOpPromise::Private* aPromise) { RefPtr promise = aPromise; // passes the request to open a new window to GeckoView. Allowing the // application to decide how to hand the open window request. - auto genericResult = - java::GeckoRuntime::ServiceWorkerOpenWindow(aArgs.baseURL(), aArgs.url()); + nsAutoCString uri; + MOZ_ALWAYS_SUCCEEDS(aArgsValidated.uri->GetSpec(uri)); + auto genericResult = java::GeckoRuntime::ServiceWorkerOpenWindow(uri); auto typedResult = java::GeckoResult::LocalRef(std::move(genericResult)); // MozPromise containing the ID for the handling GeckoSession @@ -352,7 +332,7 @@ void GeckoViewOpenWindow(const ClientOpenWindowArgs& aArgs, promiseResult->Then( GetMainThreadSerialEventTarget(), __func__, - [aArgs, promise](nsString sessionId) { + [aArgsValidated, promise](nsString sessionId) { nsresult rv; nsCOMPtr wwatch = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); @@ -372,7 +352,7 @@ void GeckoViewOpenWindow(const ClientOpenWindowArgs& aArgs, return NS_ERROR_FAILURE; } - WaitForLoad(aArgs, browsingContext, promise); + WaitForLoad(aArgsValidated, browsingContext, promise); return NS_OK; }, [promise](nsString aResult) { @@ -390,23 +370,87 @@ RefPtr ClientOpenWindow(const ClientOpenWindowArgs& aArgs) { RefPtr promise = new ClientOpPromise::Private(__func__); -#ifdef MOZ_WIDGET_ANDROID - // If we are on Android we are GeckoView. - GeckoViewOpenWindow(aArgs, promise); - return promise.forget(); -#endif // MOZ_WIDGET_ANDROID - - RefPtr bc; - ErrorResult rv; - OpenWindow(aArgs, getter_AddRefs(bc), rv); - if (NS_WARN_IF(rv.Failed())) { - promise->Reject(rv, __func__); + // [[1. Let url be the result of parsing url with entry settings object's API + // base URL.]] + nsCOMPtr baseURI; + nsresult rv = NS_NewURI(getter_AddRefs(baseURI), aArgs.baseURL()); + if (NS_WARN_IF(NS_FAILED(rv))) { + nsPrintfCString err("Invalid base URL \"%s\"", aArgs.baseURL().get()); + CopyableErrorResult errResult; + errResult.ThrowTypeError(err); + promise->Reject(errResult, __func__); return promise; } - MOZ_DIAGNOSTIC_ASSERT(bc); - WaitForLoad(aArgs, bc, promise); + nsCOMPtr uri; + rv = NS_NewURI(getter_AddRefs(uri), aArgs.url(), nullptr, baseURI); + if (NS_WARN_IF(NS_FAILED(rv))) { + nsPrintfCString err("Invalid URL \"%s\"", aArgs.url().get()); + CopyableErrorResult errResult; + errResult.ThrowTypeError(err); + promise->Reject(errResult, __func__); + return promise; + } + auto principalOrErr = PrincipalInfoToPrincipal(aArgs.principalInfo()); + if (NS_WARN_IF(principalOrErr.isErr())) { + CopyableErrorResult errResult; + errResult.ThrowTypeError("Failed to obtain principal"); + promise->Reject(errResult, __func__); + return promise; + } + nsCOMPtr principal = principalOrErr.unwrap(); + MOZ_DIAGNOSTIC_ASSERT(principal); + + nsCOMPtr csp; + if (aArgs.cspInfo().isSome()) { + csp = CSPInfoToCSP(aArgs.cspInfo().ref(), nullptr); + } + ClientOpenWindowArgsParsed argsValidated; + argsValidated.uri = uri; + argsValidated.baseURI = baseURI; + argsValidated.principal = principal; + argsValidated.csp = csp; + +#ifdef MOZ_WIDGET_ANDROID + // If we are on Android we are GeckoView. + GeckoViewOpenWindow(argsValidated, promise); + return promise.forget(); +#endif // MOZ_WIDGET_ANDROID + + RefPtr + browsingContextReadyPromise = + new BrowsingContextCallbackReceivedPromise::Private(__func__); + RefPtr callback = + new nsBrowsingContextReadyCallback(browsingContextReadyPromise); + + RefPtr openInfo = new nsOpenWindowInfo(); + openInfo->mBrowsingContextReadyCallback = callback; + openInfo->mOriginAttributes = principal->OriginAttributesRef(); + openInfo->mIsRemote = true; + + RefPtr bc; + ErrorResult errResult; + OpenWindow(argsValidated, openInfo, getter_AddRefs(bc), errResult); + if (NS_WARN_IF(errResult.Failed())) { + promise->Reject(errResult, __func__); + return promise; + } + + browsingContextReadyPromise->Then( + GetCurrentThreadSerialEventTarget(), __func__, + [argsValidated, promise](const RefPtr& aBC) { + WaitForLoad(argsValidated, aBC, promise); + }, + [promise]() { + // in case of failure, reject the original promise + CopyableErrorResult result; + result.ThrowTypeError("Unable to open window"); + promise->Reject(result, __func__); + }); + if (bc) { + browsingContextReadyPromise->Resolve(bc, __func__); + } return promise; } diff --git a/dom/clients/manager/ClientOpenWindowUtils.h b/dom/clients/manager/ClientOpenWindowUtils.h index 6df6b5d21111..213ea34ad415 100644 --- a/dom/clients/manager/ClientOpenWindowUtils.h +++ b/dom/clients/manager/ClientOpenWindowUtils.h @@ -12,6 +12,9 @@ namespace mozilla { namespace dom { +typedef MozPromise, CopyableErrorResult, false> + BrowsingContextCallbackReceivedPromise; + MOZ_MUST_USE RefPtr ClientOpenWindow( const ClientOpenWindowArgs& aArgs); diff --git a/dom/clients/manager/moz.build b/dom/clients/manager/moz.build index e2be201898f4..02f03c77d8af 100644 --- a/dom/clients/manager/moz.build +++ b/dom/clients/manager/moz.build @@ -12,6 +12,7 @@ EXPORTS.mozilla.dom += [ 'ClientManager.h', 'ClientManagerActors.h', 'ClientManagerService.h', + 'ClientOpenWindowUtils.h', 'ClientOpPromise.h', 'ClientSource.h', 'ClientState.h', diff --git a/dom/serviceworkers/test/test_notification_openWindow.html b/dom/serviceworkers/test/test_notification_openWindow.html index b7f381cdcdd4..3e5a91c744b2 100644 --- a/dom/serviceworkers/test/test_notification_openWindow.html +++ b/dom/serviceworkers/test/test_notification_openWindow.html @@ -48,6 +48,13 @@ add_task(async function test() { SpecialPowers.Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW, SpecialPowers.Ci.nsIBrowserDOMWindow.OPEN_NEWTAB, ]) { + if (prefValue == SpecialPowers.Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW) { + // Let's open a new tab and focus on it. When the service + // worker notification is shown, the document will open in the focused tab. + // If we don't open a new tab, the document will be opened in the + // current test-runner tab and mess up the test setup. + window.open(""); + } info(`Setting browser.link.open_newwindow to ${prefValue}.`); await SpecialPowers.pushPrefEnv({ set: [["browser.link.open_newwindow", prefValue]], @@ -72,6 +79,8 @@ add_task(async function test() { // If we make it here, then we didn't crash. ok(true, "Didn't crash!"); + + navigator.serviceWorker.onmessage = null; } }); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java index c22778f14f85..ed86dcc5c299 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java @@ -48,7 +48,6 @@ import org.yaml.snakeyaml.error.YAMLException; import java.io.File; import java.io.FileNotFoundException; -import java.net.URI; import java.util.List; import java.util.Map; @@ -195,26 +194,24 @@ public final class GeckoRuntime implements Parcelable { /** * Called by mozilla::dom::ClientOpenWindow to retrieve the window id to use * for a ServiceWorkerClients.openWindow() request. - * @param baseUrl The base Url for the request. - * @param url Url being requested to be opened in a new window. + * @param url validated Url being requested to be opened in a new window. * @return SessionID to use for the request. */ @WrapForJNI(calledFrom = "gecko") - private static @NonNull GeckoResult serviceWorkerOpenWindow(final @NonNull String baseUrl, final @NonNull String url) { + private static @NonNull GeckoResult serviceWorkerOpenWindow(final @NonNull String url) { if (sRuntime != null && sRuntime.mServiceWorkerDelegate != null) { - final URI actual = URI.create(baseUrl).resolve(url); GeckoResult result = new GeckoResult<>(); // perform the onOpenWindow call in the UI thread ThreadUtils.runOnUiThread(() -> { sRuntime .mServiceWorkerDelegate - .onOpenWindow(actual.toString()) + .onOpenWindow(url) .accept( session -> { if (session != null) { if (!session.isOpen()) { result.completeExceptionally(new RuntimeException("Returned GeckoSession must be open.")); } else { - session.loadUri(actual.toString()); + session.loadUri(url); result.complete(session.getId()); } } else { diff --git a/toolkit/components/windowwatcher/nsIOpenWindowInfo.idl b/toolkit/components/windowwatcher/nsIOpenWindowInfo.idl index cc911326562b..54894b9382bb 100644 --- a/toolkit/components/windowwatcher/nsIOpenWindowInfo.idl +++ b/toolkit/components/windowwatcher/nsIOpenWindowInfo.idl @@ -19,6 +19,17 @@ class BrowserParent; [ref] native const_OriginAttributes(const mozilla::OriginAttributes); [ptr] native BrowserParent(mozilla::dom::BrowserParent); +/* + * nsIBrowsingContextReadyCallback.browsingContextReady() is called within + * nsFrameLoader to indicate that the browsing context for a newly opened + * window/tab is ready. + */ +[uuid(0524ee06-7f4c-4cd3-ab80-084562745cad)] +interface nsIBrowsingContextReadyCallback : nsISupports +{ + void browsingContextReady(in BrowsingContext bc); +}; + /** * nsIOpenWindowInfo is a helper type which contains details used when opening * new content windows. This object is used to correctly create new initial @@ -48,4 +59,8 @@ interface nsIOpenWindowInfo : nsISupports { [notxpcom, nostdcall, binaryname(GetOriginAttributes)] const_OriginAttributes binaryGetOriginAttributes(); + + /* Callback to invoke when the browsing context for a new window is ready. */ + [notxpcom, nostdcall] + nsIBrowsingContextReadyCallback browsingContextReadyCallback(); }; diff --git a/toolkit/components/windowwatcher/nsOpenWindowInfo.cpp b/toolkit/components/windowwatcher/nsOpenWindowInfo.cpp index 090a3ec414c5..4eeae1b3c8e6 100644 --- a/toolkit/components/windowwatcher/nsOpenWindowInfo.cpp +++ b/toolkit/components/windowwatcher/nsOpenWindowInfo.cpp @@ -41,3 +41,33 @@ const OriginAttributes& nsOpenWindowInfo::GetOriginAttributes() { BrowserParent* nsOpenWindowInfo::GetNextRemoteBrowser() { return mNextRemoteBrowser; } + +nsIBrowsingContextReadyCallback* +nsOpenWindowInfo::BrowsingContextReadyCallback() { + return mBrowsingContextReadyCallback; +} + +NS_IMPL_ISUPPORTS(nsBrowsingContextReadyCallback, + nsIBrowsingContextReadyCallback) + +nsBrowsingContextReadyCallback::nsBrowsingContextReadyCallback( + RefPtr aPromise) + : mPromise(std::move(aPromise)) {} + +nsBrowsingContextReadyCallback::~nsBrowsingContextReadyCallback() { + if (mPromise) { + mPromise->Reject(NS_ERROR_FAILURE, __func__); + } + mPromise = nullptr; +} + +NS_IMETHODIMP nsBrowsingContextReadyCallback::BrowsingContextReady( + BrowsingContext* aBC) { + if (aBC) { + mPromise->Resolve(aBC, __func__); + } else { + mPromise->Reject(NS_ERROR_FAILURE, __func__); + } + mPromise = nullptr; + return NS_OK; +} diff --git a/toolkit/components/windowwatcher/nsOpenWindowInfo.h b/toolkit/components/windowwatcher/nsOpenWindowInfo.h index c5743ff544fe..24a77da01de9 100644 --- a/toolkit/components/windowwatcher/nsOpenWindowInfo.h +++ b/toolkit/components/windowwatcher/nsOpenWindowInfo.h @@ -11,6 +11,7 @@ #include "nsISupportsImpl.h" #include "mozilla/OriginAttributes.h" #include "mozilla/RefPtr.h" +#include "mozilla/dom/ClientOpenWindowUtils.h" class nsOpenWindowInfo : public nsIOpenWindowInfo { public: @@ -19,12 +20,29 @@ class nsOpenWindowInfo : public nsIOpenWindowInfo { bool mForceNoOpener = false; bool mIsRemote = false; - RefPtr mNextRemoteBrowser; - OriginAttributes mOriginAttributes; - RefPtr mParent; + RefPtr mNextRemoteBrowser; + mozilla::OriginAttributes mOriginAttributes; + RefPtr mParent; + RefPtr mBrowsingContextReadyCallback; private: virtual ~nsOpenWindowInfo() = default; }; +class nsBrowsingContextReadyCallback : public nsIBrowsingContextReadyCallback { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIBROWSINGCONTEXTREADYCALLBACK + + explicit nsBrowsingContextReadyCallback( + RefPtr + aPromise); + + private: + virtual ~nsBrowsingContextReadyCallback(); + + RefPtr + mPromise; +}; + #endif // nsOpenWindowInfo_h