Bug 1857841 - pt 3. Add a new page kind named "fresh" r=glandium
[gecko.git] / dom / clients / manager / ClientNavigateOpChild.cpp
blob4d47dd826eb63ed7f57bd01d734295052fd1e295
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 {
26 namespace {
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;
36 public:
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);
45 NS_IMETHOD
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))) {
50 return NS_OK;
53 aWebProgress->RemoveProgressListener(this);
55 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
56 if (!channel) {
57 // This is not going to happen; how could it?
58 CopyableErrorResult result;
59 result.ThrowInvalidStateError("Bad request");
60 mPromise->Reject(result, __func__);
61 return NS_OK;
64 nsCOMPtr<nsIURI> channelURL;
65 nsresult rv = NS_GetFinalChannelURI(channel, getter_AddRefs(channelURL));
66 if (NS_FAILED(rv)) {
67 CopyableErrorResult result;
68 // XXXbz We can't actually get here; NS_GetFinalChannelURI never fails in
69 // practice!
70 result.Throw(rv);
71 mPromise->Reject(result, __func__);
72 return NS_OK;
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);
84 if (NS_FAILED(rv)) {
85 mPromise->Resolve(CopyableErrorResult(), __func__);
86 return NS_OK;
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)
101 // spec.
102 mPromise->Resolve(
103 ClientInfoAndState(clientInfo.ref().ToIPC(), clientState.ref().ToIPC()),
104 __func__);
106 return NS_OK;
109 NS_IMETHOD
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.");
115 return NS_OK;
118 NS_IMETHOD
119 OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
120 nsIURI* aLocation, uint32_t aFlags) override {
121 MOZ_CRASH("Unexpected notification.");
122 return NS_OK;
125 NS_IMETHOD
126 OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
127 nsresult aStatus, const char16_t* aMessage) override {
128 MOZ_CRASH("Unexpected notification.");
129 return NS_OK;
132 NS_IMETHOD
133 OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
134 uint32_t aState) override {
135 MOZ_CRASH("Unexpected notification.");
136 return NS_OK;
139 NS_IMETHOD
140 OnContentBlockingEvent(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
141 uint32_t aEvent) override {
142 MOZ_CRASH("Unexpected notification.");
143 return NS_OK;
146 NS_DECL_ISUPPORTS
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();
169 if (!target) {
170 CopyableErrorResult rv;
171 rv.ThrowInvalidStateError("Unknown Client");
172 return ClientOpPromise::CreateAndReject(rv, __func__);
175 window = target->GetInnerWindow();
176 if (!window) {
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 = GetMainThreadSerialEventTarget();
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
190 // be main thread.
191 nsCOMPtr<nsIURI> baseURL;
192 nsresult rv = NS_NewURI(getter_AddRefs(baseURL), aArgs.baseURL());
193 if (NS_FAILED(rv)) {
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);
218 if (NS_FAILED(rv)) {
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);
265 if (NS_FAILED(rv)) {
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
270 /// out.
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
274 /// spec.
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);
289 if (NS_FAILED(rv)) {
290 CopyableErrorResult result;
291 // XXXbz Can we throw something better here?
292 result.Throw(rv);
293 promise->Reject(result, __func__);
294 return promise;
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
313 // target.
314 if (!mSerialEventTarget) {
315 mSerialEventTarget = GetCurrentSerialEventTarget();
318 // Capturing `this` is safe here since we clear the mPromiseRequestHolder in
319 // ActorDestroy.
320 promise
321 ->Then(
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