diff options
Diffstat (limited to '')
-rw-r--r-- | dom/clients/manager/ClientOpenWindowUtils.cpp | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/dom/clients/manager/ClientOpenWindowUtils.cpp b/dom/clients/manager/ClientOpenWindowUtils.cpp new file mode 100644 index 0000000000..e741a266fd --- /dev/null +++ b/dom/clients/manager/ClientOpenWindowUtils.cpp @@ -0,0 +1,464 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientOpenWindowUtils.h" + +#include "ClientInfo.h" +#include "ClientManager.h" +#include "ClientState.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/dom/ContentParent.h" +#include "nsContentUtils.h" +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsFocusManager.h" +#include "nsIBrowserDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIDOMChromeWindow.h" +#include "nsIURI.h" +#include "nsIBrowser.h" +#include "nsIWebProgress.h" +#include "nsIWebProgressListener.h" +#include "nsIWindowWatcher.h" +#include "nsIXPConnect.h" +#include "nsNetUtil.h" +#include "nsPIDOMWindow.h" +#include "nsPIWindowWatcher.h" +#include "nsPrintfCString.h" +#include "nsWindowWatcher.h" +#include "nsOpenWindowInfo.h" + +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/nsCSPContext.h" +#include "mozilla/dom/WindowGlobalParent.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/java/GeckoResultWrappers.h" +# include "mozilla/java/GeckoRuntimeWrappers.h" +#endif + +namespace mozilla::dom { + +namespace { + +class WebProgressListener final : public nsIWebProgressListener, + public nsSupportsWeakReference { + public: + NS_DECL_ISUPPORTS + + WebProgressListener(BrowsingContext* aBrowsingContext, nsIURI* aBaseURI, + already_AddRefed<ClientOpPromise::Private> aPromise) + : mPromise(aPromise), + mBaseURI(aBaseURI), + mBrowserId(aBrowsingContext->GetBrowserId()) { + MOZ_ASSERT(mBrowserId != 0); + MOZ_ASSERT(aBaseURI); + MOZ_ASSERT(NS_IsMainThread()); + } + + NS_IMETHOD + OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + uint32_t aStateFlags, nsresult aStatus) override { + if (!(aStateFlags & STATE_IS_WINDOW) || + !(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) { + return NS_OK; + } + + // Our browsing context may have been discarded before finishing the load, + // this is a navigation error. + RefPtr<CanonicalBrowsingContext> browsingContext = + CanonicalBrowsingContext::Cast( + BrowsingContext::GetCurrentTopByBrowserId(mBrowserId)); + if (!browsingContext || browsingContext->IsDiscarded()) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Unable to open window"); + mPromise->Reject(rv, __func__); + mPromise = nullptr; + return NS_OK; + } + + // Our caller keeps a strong reference, so it is safe to remove the listener + // from the BrowsingContext's nsIWebProgress. + auto RemoveListener = [&] { + nsCOMPtr<nsIWebProgress> webProgress = browsingContext->GetWebProgress(); + webProgress->RemoveProgressListener(this); + }; + + RefPtr<dom::WindowGlobalParent> wgp = + browsingContext->GetCurrentWindowGlobal(); + if (NS_WARN_IF(!wgp)) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Unable to open window"); + mPromise->Reject(rv, __func__); + mPromise = nullptr; + RemoveListener(); + return NS_OK; + } + + if (NS_WARN_IF(wgp->IsInitialDocument())) { + // This is the load of the initial document, which is not the document we + // care about for the purposes of checking same-originness of the URL. + return NS_OK; + } + + RemoveListener(); + + // Check same origin. If the origins do not match, resolve with null (per + // step 7.2.7.1 of the openWindow spec). + nsCOMPtr<nsIScriptSecurityManager> securityManager = + nsContentUtils::GetSecurityManager(); + bool isPrivateWin = + wgp->DocumentPrincipal()->OriginAttributesRef().mPrivateBrowsingId > 0; + nsresult rv = securityManager->CheckSameOriginURI( + wgp->GetDocumentURI(), mBaseURI, false, isPrivateWin); + if (NS_WARN_IF(NS_FAILED(rv))) { + mPromise->Resolve(CopyableErrorResult(), __func__); + mPromise = nullptr; + return NS_OK; + } + + Maybe<ClientInfo> info = wgp->GetClientInfo(); + if (info.isNothing()) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Unable to open window"); + mPromise->Reject(rv, __func__); + mPromise = nullptr; + return NS_OK; + } + + const nsID& id = info.ref().Id(); + const mozilla::ipc::PrincipalInfo& principal = info.ref().PrincipalInfo(); + ClientManager::GetInfoAndState(ClientGetInfoAndStateArgs(id, principal), + GetCurrentSerialEventTarget()) + ->ChainTo(mPromise.forget(), __func__); + + return NS_OK; + } + + NS_IMETHOD + OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + int32_t aCurSelfProgress, int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) override { + MOZ_ASSERT(false, "Unexpected notification."); + return NS_OK; + } + + NS_IMETHOD + OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + nsIURI* aLocation, uint32_t aFlags) override { + MOZ_ASSERT(false, "Unexpected notification."); + return NS_OK; + } + + NS_IMETHOD + OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + nsresult aStatus, const char16_t* aMessage) override { + MOZ_ASSERT(false, "Unexpected notification."); + return NS_OK; + } + + NS_IMETHOD + OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + uint32_t aState) override { + MOZ_ASSERT(false, "Unexpected notification."); + return NS_OK; + } + + NS_IMETHOD + OnContentBlockingEvent(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + uint32_t aEvent) override { + MOZ_ASSERT(false, "Unexpected notification."); + return NS_OK; + } + + private: + ~WebProgressListener() { + if (mPromise) { + CopyableErrorResult rv; + rv.ThrowAbortError("openWindow aborted"); + mPromise->Reject(rv, __func__); + mPromise = nullptr; + } + } + + RefPtr<ClientOpPromise::Private> mPromise; + nsCOMPtr<nsIURI> mBaseURI; + uint64_t mBrowserId; +}; + +NS_IMPL_ISUPPORTS(WebProgressListener, nsIWebProgressListener, + nsISupportsWeakReference); + +struct ClientOpenWindowArgsParsed { + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIURI> baseURI; + nsCOMPtr<nsIPrincipal> principal; + nsCOMPtr<nsIContentSecurityPolicy> csp; + RefPtr<ThreadsafeContentParentHandle> originContent; +}; + +void OpenWindow(const ClientOpenWindowArgsParsed& aArgsValidated, + nsOpenWindowInfo* aOpenInfo, BrowsingContext** aBC, + ErrorResult& aRv) { + MOZ_DIAGNOSTIC_ASSERT(aBC); + + // [[6.1 Open Window]] + + // Find the most recent browser window and open a new tab in it. + nsCOMPtr<nsPIDOMWindowOuter> browserWindow = + nsContentUtils::GetMostRecentNonPBWindow(); + if (!browserWindow) { + // It is possible to be running without a browser window on Mac OS, so + // we need to open a new chrome window. + // TODO(catalinb): open new chrome window. Bug 1218080 + aRv.ThrowTypeError("Unable to open window"); + return; + } + + nsCOMPtr<nsIDOMChromeWindow> chromeWin = do_QueryInterface(browserWindow); + if (NS_WARN_IF(!chromeWin)) { + // XXXbz Can this actually happen? Seems unlikely. + aRv.ThrowTypeError("Unable to open window"); + return; + } + + nsCOMPtr<nsIBrowserDOMWindow> bwin; + chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin)); + + if (NS_WARN_IF(!bwin)) { + aRv.ThrowTypeError("Unable to open window"); + return; + } + 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 ClientOpenWindowArgsParsed& aArgsValidated, + BrowsingContext* aBrowsingContext, + ClientOpPromise::Private* aPromise) { + MOZ_DIAGNOSTIC_ASSERT(aBrowsingContext); + + RefPtr<ClientOpPromise::Private> promise = aPromise; + // We can get a WebProgress off of + // the BrowsingContext for the <xul:browser> to listen for content + // events. Note that this WebProgress filters out events which don't have + // STATE_IS_NETWORK or STATE_IS_REDIRECTED_DOCUMENT set on them, and so this + // listener will only see some web progress events. + nsCOMPtr<nsIWebProgress> webProgress = + aBrowsingContext->Canonical()->GetWebProgress(); + if (NS_WARN_IF(!webProgress)) { + CopyableErrorResult result; + result.ThrowInvalidStateError("Unable to watch window for navigation"); + promise->Reject(result, __func__); + return; + } + + // Add a progress listener before we start the load of the service worker URI + RefPtr<WebProgressListener> listener = new WebProgressListener( + aBrowsingContext, aArgsValidated.baseURI, do_AddRef(promise)); + + nsresult rv = webProgress->AddProgressListener( + listener, nsIWebProgress::NOTIFY_STATE_WINDOW); + if (NS_WARN_IF(NS_FAILED(rv))) { + CopyableErrorResult result; + // XXXbz Can we throw something better here? + result.Throw(rv); + promise->Reject(result, __func__); + return; + } + + // Load the service worker URI + RefPtr<nsDocShellLoadState> loadState = + new nsDocShellLoadState(aArgsValidated.uri); + loadState->SetTriggeringPrincipal(aArgsValidated.principal); + loadState->SetFirstParty(true); + loadState->SetLoadFlags( + nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL); + loadState->SetTriggeringRemoteType( + aArgsValidated.originContent + ? aArgsValidated.originContent->GetRemoteType() + : NOT_REMOTE_TYPE); + + 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__, + [listener](const ClientOpResult& aResult) {}, + [listener](const CopyableErrorResult& aResult) {}); +} + +#ifdef MOZ_WIDGET_ANDROID + +void GeckoViewOpenWindow(const ClientOpenWindowArgsParsed& aArgsValidated, + ClientOpPromise::Private* aPromise) { + RefPtr<ClientOpPromise::Private> promise = aPromise; + + // passes the request to open a new window to GeckoView. Allowing the + // application to decide how to hand the open window request. + 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 + auto promiseResult = + mozilla::MozPromise<nsString, nsString, false>::FromGeckoResult( + typedResult); + + promiseResult->Then( + GetMainThreadSerialEventTarget(), __func__, + [aArgsValidated, promise](nsString sessionId) { + // Retrieve the primary content BrowsingContext using the GeckoSession + // ID. The chrome window is named the same as the ID of the GeckoSession + // it is associated with. + RefPtr<BrowsingContext> browsingContext; + nsresult rv = [&sessionId, &browsingContext]() -> nsresult { + nsresult rv; + nsCOMPtr<nsIWindowWatcher> wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<mozIDOMWindowProxy> chromeWindow; + rv = wwatch->GetWindowByName(sessionId, getter_AddRefs(chromeWindow)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(chromeWindow, NS_ERROR_FAILURE); + nsCOMPtr<nsIDocShellTreeOwner> treeOwner = + nsPIDOMWindowOuter::From(chromeWindow)->GetTreeOwner(); + NS_ENSURE_TRUE(treeOwner, NS_ERROR_FAILURE); + rv = treeOwner->GetPrimaryContentBrowsingContext( + getter_AddRefs(browsingContext)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(browsingContext, NS_ERROR_FAILURE); + return NS_OK; + }(); + if (NS_WARN_IF(NS_FAILED(rv))) { + promise->Reject(rv, __func__); + return rv; + } + + WaitForLoad(aArgsValidated, browsingContext, promise); + return NS_OK; + }, + [promise](nsString aResult) { + promise->Reject(NS_ERROR_FAILURE, __func__); + }); +} + +#endif // MOZ_WIDGET_ANDROID + +} // anonymous namespace + +RefPtr<ClientOpPromise> ClientOpenWindow( + ThreadsafeContentParentHandle* aOriginContent, + const ClientOpenWindowArgs& aArgs) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + + RefPtr<ClientOpPromise::Private> promise = + new ClientOpPromise::Private(__func__); + + // [[1. Let url be the result of parsing url with entry settings object's API + // base URL.]] + nsCOMPtr<nsIURI> 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; + } + + nsCOMPtr<nsIURI> 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<nsIPrincipal> principal = principalOrErr.unwrap(); + MOZ_DIAGNOSTIC_ASSERT(principal); + + nsCOMPtr<nsIContentSecurityPolicy> csp; + if (aArgs.cspInfo().isSome()) { + csp = CSPInfoToCSP(aArgs.cspInfo().ref(), nullptr); + } + ClientOpenWindowArgsParsed argsValidated{ + .uri = uri, + .baseURI = baseURI, + .principal = principal, + .csp = csp, + .originContent = aOriginContent, + }; + +#ifdef MOZ_WIDGET_ANDROID + // If we are on Android we are GeckoView. + GeckoViewOpenWindow(argsValidated, promise); + return promise.forget(); +#endif // MOZ_WIDGET_ANDROID + + RefPtr<BrowsingContextCallbackReceivedPromise::Private> + browsingContextReadyPromise = + new BrowsingContextCallbackReceivedPromise::Private(__func__); + RefPtr<nsIBrowsingContextReadyCallback> callback = + new nsBrowsingContextReadyCallback(browsingContextReadyPromise); + + RefPtr<nsOpenWindowInfo> openInfo = new nsOpenWindowInfo(); + openInfo->mBrowsingContextReadyCallback = callback; + openInfo->mOriginAttributes = principal->OriginAttributesRef(); + openInfo->mIsRemote = true; + + RefPtr<BrowsingContext> bc; + ErrorResult errResult; + OpenWindow(argsValidated, openInfo, getter_AddRefs(bc), errResult); + if (NS_WARN_IF(errResult.Failed())) { + promise->Reject(errResult, __func__); + return promise; + } + + browsingContextReadyPromise->Then( + GetCurrentSerialEventTarget(), __func__, + [argsValidated, promise](const RefPtr<BrowsingContext>& 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; +} + +} // namespace mozilla::dom |