ClientNavigateOpChild.cpp (12353B)
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/. */ 6 7 #include "ClientNavigateOpChild.h" 8 9 #include "ClientSource.h" 10 #include "ClientSourceChild.h" 11 #include "ClientState.h" 12 #include "ReferrerInfo.h" 13 #include "mozilla/dom/Document.h" 14 #include "mozilla/dom/PolicyContainer.h" 15 #include "nsDocShellLoadState.h" 16 #include "nsIDocShell.h" 17 #include "nsIWebNavigation.h" 18 #include "nsIWebProgress.h" 19 #include "nsIWebProgressListener.h" 20 #include "nsNetUtil.h" 21 #include "nsPIDOMWindow.h" 22 #include "nsURLHelper.h" 23 24 namespace mozilla::dom { 25 26 namespace { 27 28 class NavigateLoadListener final : public nsIWebProgressListener, 29 public nsSupportsWeakReference { 30 RefPtr<ClientOpPromise::Private> mPromise; 31 RefPtr<nsPIDOMWindowOuter> mOuterWindow; 32 nsCOMPtr<nsIURI> mBaseURL; 33 34 ~NavigateLoadListener() = default; 35 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); 43 } 44 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; 51 } 52 53 aWebProgress->RemoveProgressListener(this); 54 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; 62 } 63 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; 73 } 74 75 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); 76 MOZ_DIAGNOSTIC_ASSERT(ssm); 77 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; 87 } 88 89 nsPIDOMWindowInner* innerWindow = mOuterWindow->GetCurrentInnerWindow(); 90 MOZ_DIAGNOSTIC_ASSERT(innerWindow); 91 92 Maybe<ClientInfo> clientInfo = innerWindow->GetClientInfo(); 93 MOZ_DIAGNOSTIC_ASSERT(clientInfo.isSome()); 94 95 Maybe<ClientState> clientState = innerWindow->GetClientState(); 96 MOZ_DIAGNOSTIC_ASSERT(clientState.isSome()); 97 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__); 105 106 return NS_OK; 107 } 108 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; 116 } 117 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; 123 } 124 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; 130 } 131 132 NS_IMETHOD 133 OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, 134 uint32_t aState) override { 135 MOZ_CRASH("Unexpected notification."); 136 return NS_OK; 137 } 138 139 NS_IMETHOD 140 OnContentBlockingEvent(nsIWebProgress* aWebProgress, nsIRequest* aRequest, 141 uint32_t aEvent) override { 142 MOZ_CRASH("Unexpected notification."); 143 return NS_OK; 144 } 145 146 NS_DECL_ISUPPORTS 147 }; 148 149 NS_IMPL_ISUPPORTS(NavigateLoadListener, nsIWebProgressListener, 150 nsISupportsWeakReference); 151 152 } // anonymous namespace 153 154 RefPtr<ClientOpPromise> ClientNavigateOpChild::DoNavigate( 155 const ClientNavigateOpConstructorArgs& aArgs) { 156 nsCOMPtr<nsPIDOMWindowInner> window; 157 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. 163 { 164 ClientSourceChild* targetActor = 165 static_cast<ClientSourceChild*>(aArgs.target().AsChild().get()); 166 MOZ_DIAGNOSTIC_ASSERT(targetActor); 167 168 ClientSource* target = targetActor->GetSource(); 169 if (!target) { 170 CopyableErrorResult rv; 171 rv.ThrowInvalidStateError("Unknown Client"); 172 return ClientOpPromise::CreateAndReject(rv, __func__); 173 } 174 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__); 180 } 181 } 182 183 MOZ_ASSERT(NS_IsMainThread()); 184 185 mSerialEventTarget = GetMainThreadSerialEventTarget(); 186 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__); 199 } 200 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; 213 } 214 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__); 225 } 226 227 if (NS_IsAboutBlankAllowQueryAndFragment(url)) { 228 CopyableErrorResult result; 229 result.ThrowTypeError("Navigation to \"about:blank\" is not allowed"); 230 return ClientOpPromise::CreateAndReject(result, __func__); 231 } 232 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__); 238 } 239 240 nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal(); 241 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__); 249 } 250 251 RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(url); 252 loadState->SetTriggeringPrincipal(principal); 253 loadState->SetTriggeringSandboxFlags(doc->GetSandboxFlags()); 254 loadState->SetPolicyContainer(doc->GetPolicyContainer()); 255 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__); 279 } 280 281 RefPtr<ClientOpPromise::Private> promise = 282 new ClientOpPromise::Private(__func__); 283 284 nsCOMPtr<nsIWebProgressListener> listener = 285 new NavigateLoadListener(promise, window->GetOuterWindow(), baseURL); 286 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; 295 } 296 297 return promise->Then( 298 mSerialEventTarget, __func__, 299 [listener](const ClientOpPromise::ResolveOrRejectValue& aValue) { 300 return ClientOpPromise::CreateAndResolveOrReject(aValue, __func__); 301 }); 302 } 303 304 void ClientNavigateOpChild::ActorDestroy(ActorDestroyReason aReason) { 305 mPromiseRequestHolder.DisconnectIfExists(); 306 } 307 308 void ClientNavigateOpChild::Init(const ClientNavigateOpConstructorArgs& aArgs) { 309 RefPtr<ClientOpPromise> promise = DoNavigate(aArgs); 310 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(); 316 } 317 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); 326 }, 327 [this](const CopyableErrorResult& aResult) { 328 mPromiseRequestHolder.Complete(); 329 PClientNavigateOpChild::Send__delete__(this, aResult); 330 }) 331 ->Track(mPromiseRequestHolder); 332 } 333 334 } // namespace mozilla::dom