зеркало из https://github.com/mozilla/gecko-dev.git
458 строки
13 KiB
C++
458 строки
13 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 "ClientState.h"
|
|
#include "mozilla/SystemGroup.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsIBrowserDOMWindow.h"
|
|
#include "nsIDocShell.h"
|
|
#include "nsIDOMChromeWindow.h"
|
|
#include "nsIURI.h"
|
|
#include "nsIWebProgress.h"
|
|
#include "nsIWebProgressListener.h"
|
|
#include "nsIWindowWatcher.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsPIWindowWatcher.h"
|
|
|
|
#ifdef MOZ_WIDGET_ANDROID
|
|
#include "FennecJNIWrappers.h"
|
|
#endif
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
namespace {
|
|
|
|
class WebProgressListener final : public nsIWebProgressListener
|
|
, public nsSupportsWeakReference
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
WebProgressListener(nsPIDOMWindowOuter* aWindow,
|
|
nsIURI* aBaseURI,
|
|
already_AddRefed<ClientOpPromise::Private> aPromise)
|
|
: mPromise(aPromise)
|
|
, mWindow(aWindow)
|
|
, mBaseURI(aBaseURI)
|
|
{
|
|
MOZ_ASSERT(aWindow);
|
|
MOZ_ASSERT(aBaseURI);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
}
|
|
|
|
NS_IMETHOD
|
|
OnStateChange(nsIWebProgress* aWebProgress,
|
|
nsIRequest* aRequest,
|
|
uint32_t aStateFlags, nsresult aStatus) override
|
|
{
|
|
if (!(aStateFlags & STATE_IS_DOCUMENT) ||
|
|
!(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Our caller keeps a strong reference, so it is safe to remove the listener
|
|
// from ServiceWorkerPrivate.
|
|
aWebProgress->RemoveProgressListener(this);
|
|
|
|
nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
|
|
if (NS_WARN_IF(!doc)) {
|
|
mPromise->Reject(NS_ERROR_FAILURE, __func__);
|
|
mPromise = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Check same origin.
|
|
nsCOMPtr<nsIScriptSecurityManager> securityManager =
|
|
nsContentUtils::GetSecurityManager();
|
|
nsresult rv = securityManager->CheckSameOriginURI(doc->GetOriginalURI(),
|
|
mBaseURI, false);
|
|
if (NS_FAILED(rv)) {
|
|
mPromise->Resolve(NS_OK, __func__);
|
|
mPromise = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
Maybe<ClientInfo> info(doc->GetClientInfo());
|
|
Maybe<ClientState> state(doc->GetClientState());
|
|
|
|
if (NS_WARN_IF(info.isNothing() || state.isNothing())) {
|
|
mPromise->Reject(NS_ERROR_FAILURE, __func__);
|
|
mPromise = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
mPromise->Resolve(ClientInfoAndState(info.ref().ToIPC(), state.ref().ToIPC()),
|
|
__func__);
|
|
mPromise = nullptr;
|
|
|
|
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;
|
|
}
|
|
|
|
private:
|
|
~WebProgressListener()
|
|
{
|
|
if (mPromise) {
|
|
mPromise->Reject(NS_ERROR_ABORT, __func__);
|
|
mPromise = nullptr;
|
|
}
|
|
}
|
|
|
|
RefPtr<ClientOpPromise::Private> mPromise;
|
|
// TODO: make window a weak ref and stop cycle collecting
|
|
nsCOMPtr<nsPIDOMWindowOuter> mWindow;
|
|
nsCOMPtr<nsIURI> mBaseURI;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(WebProgressListener, nsIWebProgressListener,
|
|
nsISupportsWeakReference);
|
|
|
|
nsresult
|
|
OpenWindow(const ClientOpenWindowArgs& aArgs,
|
|
nsPIDOMWindowOuter** aWindow)
|
|
{
|
|
MOZ_DIAGNOSTIC_ASSERT(aWindow);
|
|
|
|
// [[1. Let url be the result of parsing url with entry settings object's API
|
|
// base URL.]]
|
|
nsCOMPtr<nsIURI> uri;
|
|
|
|
nsCOMPtr<nsIURI> baseURI;
|
|
nsresult rv = NS_NewURI(getter_AddRefs(baseURI), aArgs.baseURL());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return NS_ERROR_TYPE_ERR;
|
|
}
|
|
|
|
rv = NS_NewURI(getter_AddRefs(uri), aArgs.url(), nullptr, baseURI);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return NS_ERROR_TYPE_ERR;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> principal =
|
|
PrincipalInfoToPrincipal(aArgs.principalInfo());
|
|
MOZ_DIAGNOSTIC_ASSERT(principal);
|
|
|
|
// [[6.1 Open Window]]
|
|
if (XRE_IsContentProcess()) {
|
|
|
|
// Let's create a sandbox in order to have a valid JSContext and correctly
|
|
// propagate the SubjectPrincipal.
|
|
AutoJSAPI jsapi;
|
|
jsapi.Init();
|
|
|
|
JSContext* cx = jsapi.cx();
|
|
|
|
nsIXPConnect* xpc = nsContentUtils::XPConnect();
|
|
MOZ_DIAGNOSTIC_ASSERT(xpc);
|
|
|
|
JS::Rooted<JSObject*> sandbox(cx);
|
|
rv = xpc->CreateSandbox(cx, principal, sandbox.address());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return NS_ERROR_TYPE_ERR;
|
|
}
|
|
|
|
JSAutoCompartment ac(cx, sandbox);
|
|
|
|
// ContentProcess
|
|
nsCOMPtr<nsIWindowWatcher> wwatch =
|
|
do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
nsCOMPtr<nsPIWindowWatcher> pwwatch(do_QueryInterface(wwatch));
|
|
NS_ENSURE_STATE(pwwatch);
|
|
|
|
nsCString spec;
|
|
rv = uri->GetSpec(spec);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<mozIDOMWindowProxy> newWindow;
|
|
rv = pwwatch->OpenWindow2(nullptr,
|
|
spec.get(),
|
|
nullptr,
|
|
nullptr,
|
|
false, false, true, nullptr,
|
|
// Not a spammy popup; we got permission, we swear!
|
|
/* aIsPopupSpam = */ false,
|
|
// Don't force noopener. We're not passing in an
|
|
// opener anyway, and we _do_ want the returned
|
|
// window.
|
|
/* aForceNoOpener = */ false,
|
|
/* aLoadInfp = */ nullptr,
|
|
getter_AddRefs(newWindow));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
nsCOMPtr<nsPIDOMWindowOuter> pwindow = nsPIDOMWindowOuter::From(newWindow);
|
|
pwindow.forget(aWindow);
|
|
MOZ_DIAGNOSTIC_ASSERT(*aWindow);
|
|
return NS_OK;
|
|
}
|
|
|
|
// 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
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMChromeWindow> chromeWin = do_QueryInterface(browserWindow);
|
|
if (NS_WARN_IF(!chromeWin)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsIBrowserDOMWindow> bwin;
|
|
chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin));
|
|
|
|
if (NS_WARN_IF(!bwin)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<mozIDOMWindowProxy> win;
|
|
rv = bwin->OpenURI(uri, nullptr,
|
|
nsIBrowserDOMWindow::OPEN_DEFAULTWINDOW,
|
|
nsIBrowserDOMWindow::OPEN_NEW,
|
|
principal,
|
|
getter_AddRefs(win));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
NS_ENSURE_STATE(win);
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> pWin = nsPIDOMWindowOuter::From(win);
|
|
pWin.forget(aWindow);
|
|
MOZ_DIAGNOSTIC_ASSERT(*aWindow);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
WaitForLoad(const ClientOpenWindowArgs& aArgs,
|
|
nsPIDOMWindowOuter* aOuterWindow,
|
|
ClientOpPromise::Private* aPromise)
|
|
{
|
|
MOZ_DIAGNOSTIC_ASSERT(aOuterWindow);
|
|
|
|
RefPtr<ClientOpPromise::Private> promise = aPromise;
|
|
|
|
nsresult rv = nsContentUtils::DispatchFocusChromeEvent(aOuterWindow);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
promise->Reject(rv, __func__);
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> baseURI;
|
|
rv = NS_NewURI(getter_AddRefs(baseURI), aArgs.baseURL());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
promise->Reject(rv, __func__);
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocShell> docShell = aOuterWindow->GetDocShell();
|
|
nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
|
|
|
|
if (NS_WARN_IF(!webProgress)) {
|
|
promise->Reject(NS_ERROR_FAILURE, __func__);
|
|
return;
|
|
}
|
|
|
|
RefPtr<ClientOpPromise> ref = promise;
|
|
|
|
RefPtr<WebProgressListener> listener =
|
|
new WebProgressListener(aOuterWindow, baseURI, promise.forget());
|
|
|
|
|
|
rv = webProgress->AddProgressListener(listener,
|
|
nsIWebProgress::NOTIFY_STATE_DOCUMENT);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
promise->Reject(rv, __func__);
|
|
return;
|
|
}
|
|
|
|
// Hold the listener alive until the promise settles
|
|
ref->Then(aOuterWindow->EventTargetFor(TaskCategory::Other), __func__,
|
|
[listener] (const ClientOpResult& aResult) { },
|
|
[listener] (nsresult aResult) { });
|
|
}
|
|
|
|
#ifdef MOZ_WIDGET_ANDROID
|
|
|
|
class LaunchObserver final : public nsIObserver
|
|
{
|
|
RefPtr<GenericPromise::Private> mPromise;
|
|
|
|
LaunchObserver()
|
|
: mPromise(new GenericPromise::Private(__func__))
|
|
{
|
|
}
|
|
|
|
~LaunchObserver() = default;
|
|
|
|
NS_IMETHOD
|
|
Observe(nsISupports* aSubject, const char* aTopic, const char16_t * aData) override
|
|
{
|
|
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
|
if (os) {
|
|
os->RemoveObserver(this, "BrowserChrome:Ready");
|
|
}
|
|
mPromise->Resolve(true, __func__);
|
|
return NS_OK;
|
|
}
|
|
|
|
public:
|
|
static already_AddRefed<LaunchObserver>
|
|
Create()
|
|
{
|
|
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
|
if (NS_WARN_IF(!os)) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<LaunchObserver> ref = new LaunchObserver();
|
|
|
|
nsresult rv = os->AddObserver(ref, "BrowserChrome:Ready", /* weakRef */ false);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return nullptr;
|
|
}
|
|
|
|
return ref.forget();
|
|
}
|
|
|
|
void
|
|
Cancel()
|
|
{
|
|
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
|
if (os) {
|
|
os->RemoveObserver(this, "BrowserChrome:Ready");
|
|
}
|
|
mPromise->Reject(NS_ERROR_ABORT, __func__);
|
|
}
|
|
|
|
GenericPromise*
|
|
Promise()
|
|
{
|
|
return mPromise;
|
|
}
|
|
|
|
NS_DECL_ISUPPORTS
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(LaunchObserver, nsIObserver);
|
|
|
|
#endif // MOZ_WIDGET_ANDROID
|
|
|
|
} // anonymous namespace
|
|
|
|
already_AddRefed<ClientOpPromise>
|
|
ClientOpenWindowInCurrentProcess(const ClientOpenWindowArgs& aArgs)
|
|
{
|
|
RefPtr<ClientOpPromise::Private> promise =
|
|
new ClientOpPromise::Private(__func__);
|
|
RefPtr<ClientOpPromise> ref = promise;
|
|
|
|
#ifdef MOZ_WIDGET_ANDROID
|
|
// This fires an intent that will start launching Fennec and foreground it,
|
|
// if necessary. We create an observer so that we can determine when
|
|
// the launch has completed.
|
|
RefPtr<LaunchObserver> launchObserver = LaunchObserver::Create();
|
|
java::GeckoApp::LaunchOrBringToFront();
|
|
#endif // MOZ_WIDGET_ANDROID
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> outerWindow;
|
|
nsresult rv = OpenWindow(aArgs, getter_AddRefs(outerWindow));
|
|
|
|
#ifdef MOZ_WIDGET_ANDROID
|
|
// If we get the NOT_AVAILABLE error that means the browser is still
|
|
// launching on android. Use the observer we created above to wait
|
|
// until the launch completes and then try to open the window again.
|
|
if (rv == NS_ERROR_NOT_AVAILABLE && launchObserver) {
|
|
RefPtr<GenericPromise> p = launchObserver->Promise();
|
|
p->Then(SystemGroup::EventTargetFor(TaskCategory::Other), __func__,
|
|
[aArgs, promise] (bool aResult) {
|
|
nsCOMPtr<nsPIDOMWindowOuter> outerWindow;
|
|
nsresult rv = OpenWindow(aArgs, getter_AddRefs(outerWindow));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
promise->Reject(rv, __func__);
|
|
}
|
|
|
|
WaitForLoad(aArgs, outerWindow, promise);
|
|
}, [promise] (nsresult aResult) {
|
|
promise->Reject(aResult, __func__);
|
|
});
|
|
return ref.forget();
|
|
}
|
|
|
|
// If we didn't get the NOT_AVAILABLE error then there is no need
|
|
// wait for the browser to launch. Cancel the observer so that it
|
|
// will release.
|
|
if (launchObserver) {
|
|
launchObserver->Cancel();
|
|
}
|
|
#endif // MOZ_WIDGET_ANDROID
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
promise->Reject(rv, __func__);
|
|
return ref.forget();
|
|
}
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(outerWindow);
|
|
WaitForLoad(aArgs, outerWindow, promise);
|
|
|
|
return ref.forget();
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|