1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ClientNavigateOpChild.h"
9 #include "ClientState.h"
10 #include "ClientSource.h"
11 #include "ClientSourceChild.h"
12 #include "mozilla/dom/Document.h"
13 #include "mozilla/Unused.h"
14 #include "nsIDocShell.h"
15 #include "nsDocShellLoadState.h"
16 #include "nsIWebNavigation.h"
17 #include "nsIWebProgress.h"
18 #include "nsIWebProgressListener.h"
19 #include "nsNetUtil.h"
20 #include "nsPIDOMWindow.h"
21 #include "nsURLHelper.h"
22 #include "ReferrerInfo.h"
24 namespace mozilla::dom
{
28 class NavigateLoadListener final
: public nsIWebProgressListener
,
29 public nsSupportsWeakReference
{
30 RefPtr
<ClientOpPromise::Private
> mPromise
;
31 RefPtr
<nsPIDOMWindowOuter
> mOuterWindow
;
32 nsCOMPtr
<nsIURI
> mBaseURL
;
34 ~NavigateLoadListener() = default;
37 NavigateLoadListener(ClientOpPromise::Private
* aPromise
,
38 nsPIDOMWindowOuter
* aOuterWindow
, nsIURI
* aBaseURL
)
39 : mPromise(aPromise
), mOuterWindow(aOuterWindow
), mBaseURL(aBaseURL
) {
40 MOZ_DIAGNOSTIC_ASSERT(mPromise
);
41 MOZ_DIAGNOSTIC_ASSERT(mOuterWindow
);
42 MOZ_DIAGNOSTIC_ASSERT(mBaseURL
);
46 OnStateChange(nsIWebProgress
* aWebProgress
, nsIRequest
* aRequest
,
47 uint32_t aStateFlags
, nsresult aResult
) override
{
48 if (!(aStateFlags
& STATE_IS_DOCUMENT
) ||
49 !(aStateFlags
& (STATE_STOP
| STATE_TRANSFERRING
))) {
53 aWebProgress
->RemoveProgressListener(this);
55 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
57 // This is not going to happen; how could it?
58 CopyableErrorResult result
;
59 result
.ThrowInvalidStateError("Bad request");
60 mPromise
->Reject(result
, __func__
);
64 nsCOMPtr
<nsIURI
> channelURL
;
65 nsresult rv
= NS_GetFinalChannelURI(channel
, getter_AddRefs(channelURL
));
67 CopyableErrorResult result
;
68 // XXXbz We can't actually get here; NS_GetFinalChannelURI never fails in
71 mPromise
->Reject(result
, __func__
);
75 nsIScriptSecurityManager
* ssm
= nsContentUtils::GetSecurityManager();
76 MOZ_DIAGNOSTIC_ASSERT(ssm
);
78 // If the resulting window is not same origin, then resolve immediately
79 // without returning any information about the new Client. This is
80 // step 6.10 in the Client.navigate(url) spec.
81 // todo: if you intend to update CheckSameOriginURI to log the error to the
82 // console you also need to update the 'aFromPrivateWindow' argument.
83 rv
= ssm
->CheckSameOriginURI(mBaseURL
, channelURL
, false, false);
85 mPromise
->Resolve(CopyableErrorResult(), __func__
);
89 nsPIDOMWindowInner
* innerWindow
= mOuterWindow
->GetCurrentInnerWindow();
90 MOZ_DIAGNOSTIC_ASSERT(innerWindow
);
92 Maybe
<ClientInfo
> clientInfo
= innerWindow
->GetClientInfo();
93 MOZ_DIAGNOSTIC_ASSERT(clientInfo
.isSome());
95 Maybe
<ClientState
> clientState
= innerWindow
->GetClientState();
96 MOZ_DIAGNOSTIC_ASSERT(clientState
.isSome());
98 // Otherwise, if the new window is same-origin we want to return a
99 // ClientInfoAndState object so we can provide a Client snapshot
100 // to the caller. This is step 6.11 and 6.12 in the Client.navigate(url)
103 ClientInfoAndState(clientInfo
.ref().ToIPC(), clientState
.ref().ToIPC()),
110 OnProgressChange(nsIWebProgress
* aWebProgress
, nsIRequest
* aRequest
,
111 int32_t aCurSelfProgress
, int32_t aMaxSelfProgress
,
112 int32_t aCurTotalProgress
,
113 int32_t aMaxTotalProgress
) override
{
114 MOZ_CRASH("Unexpected notification.");
119 OnLocationChange(nsIWebProgress
* aWebProgress
, nsIRequest
* aRequest
,
120 nsIURI
* aLocation
, uint32_t aFlags
) override
{
121 MOZ_CRASH("Unexpected notification.");
126 OnStatusChange(nsIWebProgress
* aWebProgress
, nsIRequest
* aRequest
,
127 nsresult aStatus
, const char16_t
* aMessage
) override
{
128 MOZ_CRASH("Unexpected notification.");
133 OnSecurityChange(nsIWebProgress
* aWebProgress
, nsIRequest
* aRequest
,
134 uint32_t aState
) override
{
135 MOZ_CRASH("Unexpected notification.");
140 OnContentBlockingEvent(nsIWebProgress
* aWebProgress
, nsIRequest
* aRequest
,
141 uint32_t aEvent
) override
{
142 MOZ_CRASH("Unexpected notification.");
149 NS_IMPL_ISUPPORTS(NavigateLoadListener
, nsIWebProgressListener
,
150 nsISupportsWeakReference
);
152 } // anonymous namespace
154 RefPtr
<ClientOpPromise
> ClientNavigateOpChild::DoNavigate(
155 const ClientNavigateOpConstructorArgs
& aArgs
) {
156 nsCOMPtr
<nsPIDOMWindowInner
> window
;
158 // Navigating the target client window will result in the original
159 // ClientSource being destroyed. To avoid potential UAF mistakes
160 // we use a small scope to access the ClientSource object. Once
161 // we have a strong reference to the window object we should not
162 // access the ClientSource again.
164 ClientSourceChild
* targetActor
=
165 static_cast<ClientSourceChild
*>(aArgs
.target().AsChild().get());
166 MOZ_DIAGNOSTIC_ASSERT(targetActor
);
168 ClientSource
* target
= targetActor
->GetSource();
170 CopyableErrorResult rv
;
171 rv
.ThrowInvalidStateError("Unknown Client");
172 return ClientOpPromise::CreateAndReject(rv
, __func__
);
175 window
= target
->GetInnerWindow();
177 CopyableErrorResult rv
;
178 rv
.ThrowInvalidStateError("Client load for a destroyed Window");
179 return ClientOpPromise::CreateAndReject(rv
, __func__
);
183 MOZ_ASSERT(NS_IsMainThread());
185 mSerialEventTarget
= window
->EventTargetFor(TaskCategory::Other
);
187 // In theory we could do the URL work before paying the IPC overhead
188 // cost, but in practice its easier to do it here. The ClientHandle
189 // may be off-main-thread while this method is guaranteed to always
191 nsCOMPtr
<nsIURI
> baseURL
;
192 nsresult rv
= NS_NewURI(getter_AddRefs(baseURL
), aArgs
.baseURL());
194 // This is rather unexpected: This is the worker URL we passed from the
195 // parent, so we expect this to parse fine!
196 CopyableErrorResult result
;
197 result
.ThrowInvalidStateError("Invalid worker URL");
198 return ClientOpPromise::CreateAndReject(result
, __func__
);
201 // There is an edge case for view-source url here. According to the wpt test
202 // windowclient-navigate.https.html, a view-source URL with a relative inner
203 // URL should be treated as an invalid URL. However, we will still resolve it
204 // into a valid view-source URL since the baseURL is involved while creating
205 // the URI. So, an invalid view-source URL will be treated as a valid URL
206 // in this case. To address this, we should not take the baseURL into account
207 // for the view-source URL.
208 bool shouldUseBaseURL
= true;
209 nsAutoCString scheme
;
210 if (NS_SUCCEEDED(net_ExtractURLScheme(aArgs
.url(), scheme
)) &&
211 scheme
.LowerCaseEqualsLiteral("view-source")) {
212 shouldUseBaseURL
= false;
215 nsCOMPtr
<nsIURI
> url
;
216 rv
= NS_NewURI(getter_AddRefs(url
), aArgs
.url(), nullptr,
217 shouldUseBaseURL
? baseURL
.get() : nullptr);
219 // Per https://w3c.github.io/ServiceWorker/#dom-windowclient-navigate step
220 // 2, if the URL fails to parse, we reject with a TypeError.
221 nsPrintfCString
err("Invalid URL \"%s\"", aArgs
.url().get());
222 CopyableErrorResult result
;
223 result
.ThrowTypeError(err
);
224 return ClientOpPromise::CreateAndReject(result
, __func__
);
227 if (url
->GetSpecOrDefault().EqualsLiteral("about:blank")) {
228 CopyableErrorResult result
;
229 result
.ThrowTypeError("Navigation to \"about:blank\" is not allowed");
230 return ClientOpPromise::CreateAndReject(result
, __func__
);
233 RefPtr
<Document
> doc
= window
->GetExtantDoc();
234 if (!doc
|| !doc
->IsActive()) {
235 CopyableErrorResult result
;
236 result
.ThrowInvalidStateError("Document is not active.");
237 return ClientOpPromise::CreateAndReject(result
, __func__
);
240 nsCOMPtr
<nsIPrincipal
> principal
= doc
->NodePrincipal();
242 nsCOMPtr
<nsIDocShell
> docShell
= window
->GetDocShell();
243 nsCOMPtr
<nsIWebProgress
> webProgress
= do_GetInterface(docShell
);
244 if (!docShell
|| !webProgress
) {
245 CopyableErrorResult result
;
246 result
.ThrowInvalidStateError(
247 "Document's browsing context has been discarded");
248 return ClientOpPromise::CreateAndReject(result
, __func__
);
251 RefPtr
<nsDocShellLoadState
> loadState
= new nsDocShellLoadState(url
);
252 loadState
->SetTriggeringPrincipal(principal
);
253 loadState
->SetTriggeringSandboxFlags(doc
->GetSandboxFlags());
254 loadState
->SetCsp(doc
->GetCsp());
256 auto referrerInfo
= MakeRefPtr
<ReferrerInfo
>(*doc
);
257 loadState
->SetReferrerInfo(referrerInfo
);
258 loadState
->SetLoadType(LOAD_STOP_CONTENT
);
259 loadState
->SetSourceBrowsingContext(docShell
->GetBrowsingContext());
260 loadState
->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE
);
261 loadState
->SetFirstParty(true);
262 loadState
->SetHasValidUserGestureActivation(
263 doc
->HasValidTransientUserGestureActivation());
264 rv
= docShell
->LoadURI(loadState
, false);
266 /// There are tests that try sending file:/// and mixed-content URLs
267 /// in here and expect them to reject with a TypeError. This does not match
268 /// the spec, but does match the current behavior of both us and Chrome.
269 /// https://github.com/w3c/ServiceWorker/issues/1500 tracks sorting that
271 /// We now run security checks asynchronously, so these tests now
272 /// just fail to load rather than hitting this failure path. I've
273 /// marked them as failing for now until they get fixed to match the
275 nsPrintfCString
err("Invalid URL \"%s\"", aArgs
.url().get());
276 CopyableErrorResult result
;
277 result
.ThrowTypeError(err
);
278 return ClientOpPromise::CreateAndReject(result
, __func__
);
281 RefPtr
<ClientOpPromise::Private
> promise
=
282 new ClientOpPromise::Private(__func__
);
284 nsCOMPtr
<nsIWebProgressListener
> listener
=
285 new NavigateLoadListener(promise
, window
->GetOuterWindow(), baseURL
);
287 rv
= webProgress
->AddProgressListener(listener
,
288 nsIWebProgress::NOTIFY_STATE_DOCUMENT
);
290 CopyableErrorResult result
;
291 // XXXbz Can we throw something better here?
293 promise
->Reject(result
, __func__
);
297 return promise
->Then(
298 mSerialEventTarget
, __func__
,
299 [listener
](const ClientOpPromise::ResolveOrRejectValue
& aValue
) {
300 return ClientOpPromise::CreateAndResolveOrReject(aValue
, __func__
);
304 void ClientNavigateOpChild::ActorDestroy(ActorDestroyReason aReason
) {
305 mPromiseRequestHolder
.DisconnectIfExists();
308 void ClientNavigateOpChild::Init(const ClientNavigateOpConstructorArgs
& aArgs
) {
309 RefPtr
<ClientOpPromise
> promise
= DoNavigate(aArgs
);
311 // Normally we get the event target from the window in DoNavigate(). If a
312 // failure occurred, though, we may need to fall back to the current thread
314 if (!mSerialEventTarget
) {
315 mSerialEventTarget
= GetCurrentSerialEventTarget();
318 // Capturing `this` is safe here since we clear the mPromiseRequestHolder in
322 mSerialEventTarget
, __func__
,
323 [this](const ClientOpResult
& aResult
) {
324 mPromiseRequestHolder
.Complete();
325 PClientNavigateOpChild::Send__delete__(this, aResult
);
327 [this](const CopyableErrorResult
& aResult
) {
328 mPromiseRequestHolder
.Complete();
329 PClientNavigateOpChild::Send__delete__(this, aResult
);
331 ->Track(mPromiseRequestHolder
);
334 } // namespace mozilla::dom