465 lines
16 KiB
C++
465 lines
16 KiB
C++
/* -*- 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 "nsGlobalWindowOuter.h"
|
|
#include "nsIBrowserDOMWindow.h"
|
|
#include "nsIDocShell.h"
|
|
#include "nsIDocShellTreeOwner.h"
|
|
#include "nsIURI.h"
|
|
#include "nsIBrowser.h"
|
|
#include "nsIWebProgress.h"
|
|
#include "nsIWebProgressListener.h"
|
|
#include "nsIWindowMediator.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_GECKOVIEW
|
|
# include "mozilla/dom/Promise-inl.h"
|
|
# include "nsIGeckoViewServiceWorker.h"
|
|
# include "nsImportModule.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().IsPrivateBrowsing();
|
|
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;
|
|
};
|
|
|
|
#ifndef MOZ_GECKOVIEW
|
|
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.
|
|
WindowMediatorFilter filter = WindowMediatorFilter::SkipClosed;
|
|
if (aArgsValidated.principal->GetIsInPrivateBrowsing()) {
|
|
filter |= WindowMediatorFilter::SkipNonPrivateBrowsing;
|
|
} else {
|
|
filter |= WindowMediatorFilter::SkipPrivateBrowsing;
|
|
}
|
|
nsCOMPtr<nsPIDOMWindowOuter> browserWindow =
|
|
nsContentUtils::GetMostRecentWindowBy(filter);
|
|
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;
|
|
}
|
|
|
|
if (NS_WARN_IF(!nsGlobalWindowOuter::Cast(browserWindow)->IsChromeWindow())) {
|
|
// XXXbz Can this actually happen? Seems unlikely.
|
|
aRv.ThrowTypeError("Unable to open window");
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIBrowserDOMWindow> bwin =
|
|
nsGlobalWindowOuter::Cast(browserWindow)->GetBrowserDOMWindow();
|
|
|
|
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;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
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_GECKOVIEW
|
|
void GeckoViewOpenWindow(const ClientOpenWindowArgsParsed& aArgsValidated,
|
|
nsOpenWindowInfo* aOpenInfo, BrowsingContext** aBC,
|
|
ErrorResult& aRv) {
|
|
MOZ_ASSERT(aOpenInfo);
|
|
|
|
// passes the request to open a new window to GeckoView. Allowing the
|
|
// application to decide how to hand the open window request.
|
|
nsCOMPtr<nsIGeckoViewServiceWorker> sw = do_ImportESModule(
|
|
"resource://gre/modules/GeckoViewServiceWorker.sys.mjs");
|
|
MOZ_ASSERT(sw);
|
|
|
|
RefPtr<dom::Promise> promise;
|
|
nsresult rv =
|
|
sw->OpenWindow(aArgsValidated.uri, aOpenInfo, getter_AddRefs(promise));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
aRv.Throw(rv);
|
|
return;
|
|
}
|
|
|
|
promise->AddCallbacksWithCycleCollectedArgs(
|
|
[](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
|
|
nsOpenWindowInfo* aOpenWindowInfo) {
|
|
if (aValue.isNull()) {
|
|
// nsIBrowsingContextReadyCallback will be called when browsing
|
|
// context is ready
|
|
return;
|
|
}
|
|
|
|
auto cancelOpen =
|
|
MakeScopeExit([&aOpenWindowInfo] { aOpenWindowInfo->Cancel(); });
|
|
|
|
RefPtr<BrowsingContext> browsingContext;
|
|
if (NS_WARN_IF(!aValue.isObject()) ||
|
|
NS_WARN_IF(NS_FAILED(
|
|
UNWRAP_OBJECT(BrowsingContext, aValue, browsingContext)))) {
|
|
return;
|
|
}
|
|
|
|
if (nsIBrowsingContextReadyCallback* callback =
|
|
aOpenWindowInfo->BrowsingContextReadyCallback()) {
|
|
callback->BrowsingContextReady(browsingContext);
|
|
}
|
|
cancelOpen.release();
|
|
},
|
|
[](JSContext* aContext, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
|
|
nsOpenWindowInfo* aOpenWindowInfo) { aOpenWindowInfo->Cancel(); },
|
|
RefPtr(aOpenInfo));
|
|
}
|
|
#endif // MOZ_GECKOVIEW
|
|
|
|
} // 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,
|
|
};
|
|
|
|
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;
|
|
IgnoredErrorResult errResult;
|
|
#ifdef MOZ_GECKOVIEW
|
|
// GeckoView has a delegation for service worker window.
|
|
GeckoViewOpenWindow(argsValidated, openInfo, getter_AddRefs(bc), errResult);
|
|
#else
|
|
OpenWindow(argsValidated, openInfo, getter_AddRefs(bc), errResult);
|
|
#endif
|
|
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
|