summaryrefslogtreecommitdiffstats
path: root/dom/clients/manager/ClientNavigateOpChild.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/clients/manager/ClientNavigateOpChild.cpp334
1 files changed, 334 insertions, 0 deletions
diff --git a/dom/clients/manager/ClientNavigateOpChild.cpp b/dom/clients/manager/ClientNavigateOpChild.cpp
new file mode 100644
index 0000000000..b1fd5fb36f
--- /dev/null
+++ b/dom/clients/manager/ClientNavigateOpChild.cpp
@@ -0,0 +1,334 @@
+/* -*- 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 "ClientNavigateOpChild.h"
+
+#include "ClientState.h"
+#include "ClientSource.h"
+#include "ClientSourceChild.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/Unused.h"
+#include "nsIDocShell.h"
+#include "nsDocShellLoadState.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsURLHelper.h"
+#include "ReferrerInfo.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+class NavigateLoadListener final : public nsIWebProgressListener,
+ public nsSupportsWeakReference {
+ RefPtr<ClientOpPromise::Private> mPromise;
+ RefPtr<nsPIDOMWindowOuter> mOuterWindow;
+ nsCOMPtr<nsIURI> mBaseURL;
+
+ ~NavigateLoadListener() = default;
+
+ public:
+ NavigateLoadListener(ClientOpPromise::Private* aPromise,
+ nsPIDOMWindowOuter* aOuterWindow, nsIURI* aBaseURL)
+ : mPromise(aPromise), mOuterWindow(aOuterWindow), mBaseURL(aBaseURL) {
+ MOZ_DIAGNOSTIC_ASSERT(mPromise);
+ MOZ_DIAGNOSTIC_ASSERT(mOuterWindow);
+ MOZ_DIAGNOSTIC_ASSERT(mBaseURL);
+ }
+
+ NS_IMETHOD
+ OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aStateFlags, nsresult aResult) override {
+ if (!(aStateFlags & STATE_IS_DOCUMENT) ||
+ !(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) {
+ return NS_OK;
+ }
+
+ aWebProgress->RemoveProgressListener(this);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (!channel) {
+ // This is not going to happen; how could it?
+ CopyableErrorResult result;
+ result.ThrowInvalidStateError("Bad request");
+ mPromise->Reject(result, __func__);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> channelURL;
+ nsresult rv = NS_GetFinalChannelURI(channel, getter_AddRefs(channelURL));
+ if (NS_FAILED(rv)) {
+ CopyableErrorResult result;
+ // XXXbz We can't actually get here; NS_GetFinalChannelURI never fails in
+ // practice!
+ result.Throw(rv);
+ mPromise->Reject(result, __func__);
+ return NS_OK;
+ }
+
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ MOZ_DIAGNOSTIC_ASSERT(ssm);
+
+ // If the resulting window is not same origin, then resolve immediately
+ // without returning any information about the new Client. This is
+ // step 6.10 in the Client.navigate(url) spec.
+ // todo: if you intend to update CheckSameOriginURI to log the error to the
+ // console you also need to update the 'aFromPrivateWindow' argument.
+ rv = ssm->CheckSameOriginURI(mBaseURL, channelURL, false, false);
+ if (NS_FAILED(rv)) {
+ mPromise->Resolve(CopyableErrorResult(), __func__);
+ return NS_OK;
+ }
+
+ nsPIDOMWindowInner* innerWindow = mOuterWindow->GetCurrentInnerWindow();
+ MOZ_DIAGNOSTIC_ASSERT(innerWindow);
+
+ Maybe<ClientInfo> clientInfo = innerWindow->GetClientInfo();
+ MOZ_DIAGNOSTIC_ASSERT(clientInfo.isSome());
+
+ Maybe<ClientState> clientState = innerWindow->GetClientState();
+ MOZ_DIAGNOSTIC_ASSERT(clientState.isSome());
+
+ // Otherwise, if the new window is same-origin we want to return a
+ // ClientInfoAndState object so we can provide a Client snapshot
+ // to the caller. This is step 6.11 and 6.12 in the Client.navigate(url)
+ // spec.
+ mPromise->Resolve(
+ ClientInfoAndState(clientInfo.ref().ToIPC(), clientState.ref().ToIPC()),
+ __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_CRASH("Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsIURI* aLocation, uint32_t aFlags) override {
+ MOZ_CRASH("Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsresult aStatus, const char16_t* aMessage) override {
+ MOZ_CRASH("Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aState) override {
+ MOZ_CRASH("Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnContentBlockingEvent(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aEvent) override {
+ MOZ_CRASH("Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_DECL_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS(NavigateLoadListener, nsIWebProgressListener,
+ nsISupportsWeakReference);
+
+} // anonymous namespace
+
+RefPtr<ClientOpPromise> ClientNavigateOpChild::DoNavigate(
+ const ClientNavigateOpConstructorArgs& aArgs) {
+ nsCOMPtr<nsPIDOMWindowInner> window;
+
+ // Navigating the target client window will result in the original
+ // ClientSource being destroyed. To avoid potential UAF mistakes
+ // we use a small scope to access the ClientSource object. Once
+ // we have a strong reference to the window object we should not
+ // access the ClientSource again.
+ {
+ ClientSourceChild* targetActor =
+ static_cast<ClientSourceChild*>(aArgs.target().AsChild().get());
+ MOZ_DIAGNOSTIC_ASSERT(targetActor);
+
+ ClientSource* target = targetActor->GetSource();
+ if (!target) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Unknown Client");
+ return ClientOpPromise::CreateAndReject(rv, __func__);
+ }
+
+ window = target->GetInnerWindow();
+ if (!window) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Client load for a destroyed Window");
+ return ClientOpPromise::CreateAndReject(rv, __func__);
+ }
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mSerialEventTarget = window->EventTargetFor(TaskCategory::Other);
+
+ // In theory we could do the URL work before paying the IPC overhead
+ // cost, but in practice its easier to do it here. The ClientHandle
+ // may be off-main-thread while this method is guaranteed to always
+ // be main thread.
+ nsCOMPtr<nsIURI> baseURL;
+ nsresult rv = NS_NewURI(getter_AddRefs(baseURL), aArgs.baseURL());
+ if (NS_FAILED(rv)) {
+ // This is rather unexpected: This is the worker URL we passed from the
+ // parent, so we expect this to parse fine!
+ CopyableErrorResult result;
+ result.ThrowInvalidStateError("Invalid worker URL");
+ return ClientOpPromise::CreateAndReject(result, __func__);
+ }
+
+ // There is an edge case for view-source url here. According to the wpt test
+ // windowclient-navigate.https.html, a view-source URL with a relative inner
+ // URL should be treated as an invalid URL. However, we will still resolve it
+ // into a valid view-source URL since the baseURL is involved while creating
+ // the URI. So, an invalid view-source URL will be treated as a valid URL
+ // in this case. To address this, we should not take the baseURL into account
+ // for the view-source URL.
+ bool shouldUseBaseURL = true;
+ nsAutoCString scheme;
+ if (NS_SUCCEEDED(net_ExtractURLScheme(aArgs.url(), scheme)) &&
+ scheme.LowerCaseEqualsLiteral("view-source")) {
+ shouldUseBaseURL = false;
+ }
+
+ nsCOMPtr<nsIURI> url;
+ rv = NS_NewURI(getter_AddRefs(url), aArgs.url(), nullptr,
+ shouldUseBaseURL ? baseURL.get() : nullptr);
+ if (NS_FAILED(rv)) {
+ // Per https://w3c.github.io/ServiceWorker/#dom-windowclient-navigate step
+ // 2, if the URL fails to parse, we reject with a TypeError.
+ nsPrintfCString err("Invalid URL \"%s\"", aArgs.url().get());
+ CopyableErrorResult result;
+ result.ThrowTypeError(err);
+ return ClientOpPromise::CreateAndReject(result, __func__);
+ }
+
+ if (url->GetSpecOrDefault().EqualsLiteral("about:blank")) {
+ CopyableErrorResult result;
+ result.ThrowTypeError("Navigation to \"about:blank\" is not allowed");
+ return ClientOpPromise::CreateAndReject(result, __func__);
+ }
+
+ RefPtr<Document> doc = window->GetExtantDoc();
+ if (!doc || !doc->IsActive()) {
+ CopyableErrorResult result;
+ result.ThrowInvalidStateError("Document is not active.");
+ return ClientOpPromise::CreateAndReject(result, __func__);
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
+ if (!docShell || !webProgress) {
+ CopyableErrorResult result;
+ result.ThrowInvalidStateError(
+ "Document's browsing context has been discarded");
+ return ClientOpPromise::CreateAndReject(result, __func__);
+ }
+
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(url);
+ loadState->SetTriggeringPrincipal(principal);
+ loadState->SetTriggeringSandboxFlags(doc->GetSandboxFlags());
+ loadState->SetCsp(doc->GetCsp());
+
+ auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc);
+ loadState->SetReferrerInfo(referrerInfo);
+ loadState->SetLoadType(LOAD_STOP_CONTENT);
+ loadState->SetSourceBrowsingContext(docShell->GetBrowsingContext());
+ loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
+ loadState->SetFirstParty(true);
+ loadState->SetHasValidUserGestureActivation(
+ doc->HasValidTransientUserGestureActivation());
+ rv = docShell->LoadURI(loadState, false);
+ if (NS_FAILED(rv)) {
+ /// There are tests that try sending file:/// and mixed-content URLs
+ /// in here and expect them to reject with a TypeError. This does not match
+ /// the spec, but does match the current behavior of both us and Chrome.
+ /// https://github.com/w3c/ServiceWorker/issues/1500 tracks sorting that
+ /// out.
+ /// We now run security checks asynchronously, so these tests now
+ /// just fail to load rather than hitting this failure path. I've
+ /// marked them as failing for now until they get fixed to match the
+ /// spec.
+ nsPrintfCString err("Invalid URL \"%s\"", aArgs.url().get());
+ CopyableErrorResult result;
+ result.ThrowTypeError(err);
+ return ClientOpPromise::CreateAndReject(result, __func__);
+ }
+
+ RefPtr<ClientOpPromise::Private> promise =
+ new ClientOpPromise::Private(__func__);
+
+ nsCOMPtr<nsIWebProgressListener> listener =
+ new NavigateLoadListener(promise, window->GetOuterWindow(), baseURL);
+
+ rv = webProgress->AddProgressListener(listener,
+ nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+ if (NS_FAILED(rv)) {
+ CopyableErrorResult result;
+ // XXXbz Can we throw something better here?
+ result.Throw(rv);
+ promise->Reject(result, __func__);
+ return promise;
+ }
+
+ return promise->Then(
+ mSerialEventTarget, __func__,
+ [listener](const ClientOpPromise::ResolveOrRejectValue& aValue) {
+ return ClientOpPromise::CreateAndResolveOrReject(aValue, __func__);
+ });
+}
+
+void ClientNavigateOpChild::ActorDestroy(ActorDestroyReason aReason) {
+ mPromiseRequestHolder.DisconnectIfExists();
+}
+
+void ClientNavigateOpChild::Init(const ClientNavigateOpConstructorArgs& aArgs) {
+ RefPtr<ClientOpPromise> promise = DoNavigate(aArgs);
+
+ // Normally we get the event target from the window in DoNavigate(). If a
+ // failure occurred, though, we may need to fall back to the current thread
+ // target.
+ if (!mSerialEventTarget) {
+ mSerialEventTarget = GetCurrentSerialEventTarget();
+ }
+
+ // Capturing `this` is safe here since we clear the mPromiseRequestHolder in
+ // ActorDestroy.
+ promise
+ ->Then(
+ mSerialEventTarget, __func__,
+ [this](const ClientOpResult& aResult) {
+ mPromiseRequestHolder.Complete();
+ PClientNavigateOpChild::Send__delete__(this, aResult);
+ },
+ [this](const CopyableErrorResult& aResult) {
+ mPromiseRequestHolder.Complete();
+ PClientNavigateOpChild::Send__delete__(this, aResult);
+ })
+ ->Track(mPromiseRequestHolder);
+}
+
+} // namespace mozilla::dom