diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 46f0f5017dc..0e4550100d8 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -5203,6 +5203,7 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, case NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE: case NS_ERROR_INTERCEPTED_ERROR_RESPONSE: case NS_ERROR_INTERCEPTED_USED_RESPONSE: + case NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION: // ServiceWorker intercepted request, but something went wrong. nsContentUtils::MaybeReportInterceptionErrorToConsole(GetDocument(), aError); @@ -7840,6 +7841,7 @@ nsDocShell::EndPageLoad(nsIWebProgress* aProgress, aStatus == NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE || aStatus == NS_ERROR_INTERCEPTED_ERROR_RESPONSE || aStatus == NS_ERROR_INTERCEPTED_USED_RESPONSE || + aStatus == NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION || NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) { // Errors to be shown for any frame DisplayLoadError(aStatus, url, nullptr, aChannel); diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 8076f8d98c7..5abc257bb38 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -3432,6 +3432,8 @@ nsContentUtils::MaybeReportInterceptionErrorToConsole(nsIDocument* aDocument, messageName = "InterceptedErrorResponse"; } else if (aError == NS_ERROR_INTERCEPTED_USED_RESPONSE) { messageName = "InterceptedUsedResponse"; + } else if (aError == NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION) { + messageName = "ClientRequestOpaqueInterception"; } if (messageName) { diff --git a/dom/fetch/InternalRequest.cpp b/dom/fetch/InternalRequest.cpp index 448d5d4ff07..86b5c42e563 100644 --- a/dom/fetch/InternalRequest.cpp +++ b/dom/fetch/InternalRequest.cpp @@ -195,5 +195,44 @@ InternalRequest::MapContentPolicyTypeToRequestContext(nsContentPolicyType aConte return context; } +bool +InternalRequest::IsNavigationRequest() const +{ + // https://fetch.spec.whatwg.org/#navigation-request-context + // + // A navigation request context is one of "form", "frame", "hyperlink", + // "iframe", "internal" (as long as context frame type is not "none"), + // "location", "metarefresh", and "prerender". + // + // TODO: include equivalent check for "form" context + // TODO: include equivalent check for "prerender" context + return mContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT || + mContentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT || + mContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_FRAME || + mContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IFRAME || + mContentPolicyType == nsIContentPolicy::TYPE_REFRESH; +} + +bool +InternalRequest::IsWorkerRequest() const +{ + // https://fetch.spec.whatwg.org/#worker-request-context + // + // A worker request context is one of "serviceworker", "sharedworker", and + // "worker". + // + // Note, service workers are not included here because currently there is + // no way to generate a Request with a "serviceworker" RequestContext. + // ServiceWorker scripts cannot be intercepted. + return mContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_WORKER || + mContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER; +} + +bool +InternalRequest::IsClientRequest() const +{ + return IsNavigationRequest() || IsWorkerRequest(); +} + } // namespace dom } // namespace mozilla diff --git a/dom/fetch/InternalRequest.h b/dom/fetch/InternalRequest.h index 92b6cc3bab1..d20ac226f00 100644 --- a/dom/fetch/InternalRequest.h +++ b/dom/fetch/InternalRequest.h @@ -356,6 +356,16 @@ public: mCreatedByFetchEvent = false; } + bool + IsNavigationRequest() const; + + bool + IsWorkerRequest() const; + + bool + IsClientRequest() const; + + private: // Does not copy mBodyStream. Use fallible Clone() for complete copy. explicit InternalRequest(const InternalRequest& aOther); diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index 3c4b63fb43d..c76539dbf82 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -176,3 +176,5 @@ BadOpaqueInterceptionRequestMode=A ServiceWorker passed an opaque Response to Fe InterceptedErrorResponse=A ServiceWorker passed an Error Response to FetchEvent.respondWith(). This typically means the ServiceWorker performed an invalid fetch() call. # LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", or "Response.clone()". InterceptedUsedResponse=A ServiceWorker passed a used Response to FetchEvent.respondWith(). The body of a Response may only be read once. Use Response.clone() to access the body multiple times. +# LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", "FetchEvent.request", or "Worker". +ClientRequestOpaqueInterception=A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while FetchEvent.request was a client request. A client request is generally a browser navigation or top-level Worker script. diff --git a/dom/workers/ServiceWorkerEvents.cpp b/dom/workers/ServiceWorkerEvents.cpp index ce27b34ae9b..cb925d9c68a 100644 --- a/dom/workers/ServiceWorkerEvents.cpp +++ b/dom/workers/ServiceWorkerEvents.cpp @@ -149,15 +149,17 @@ class RespondWithHandler final : public PromiseNativeHandler nsMainThreadPtrHandle mInterceptedChannel; nsMainThreadPtrHandle mServiceWorker; RequestMode mRequestMode; + bool mIsClientRequest; public: NS_DECL_ISUPPORTS RespondWithHandler(nsMainThreadPtrHandle& aChannel, nsMainThreadPtrHandle& aServiceWorker, - RequestMode aRequestMode) + RequestMode aRequestMode, bool aIsClientRequest) : mInterceptedChannel(aChannel) , mServiceWorker(aServiceWorker) , mRequestMode(aRequestMode) + , mIsClientRequest(aIsClientRequest) { } @@ -262,15 +264,26 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle aValu return; } - // Section 4.2, step 2.2 "If either response's type is "opaque" and request's - // mode is not "no-cors" or response's type is error, return a network error." + // Section 4.2, step 2.2: + // If one of the following conditions is true, return a network error: + // * response's type is "error". + // * request's mode is not "no-cors" and response's type is "opaque". + // * request is a client request and response's type is neither "basic" + // nor "default". + + if (response->Type() == ResponseType::Error) { + autoCancel.SetCancelStatus(NS_ERROR_INTERCEPTED_ERROR_RESPONSE); + return; + } + if (response->Type() == ResponseType::Opaque && mRequestMode != RequestMode::No_cors) { autoCancel.SetCancelStatus(NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE); return; } - if (response->Type() == ResponseType::Error) { - autoCancel.SetCancelStatus(NS_ERROR_INTERCEPTED_ERROR_RESPONSE); + if (mIsClientRequest && response->Type() != ResponseType::Basic && + response->Type() != ResponseType::Default) { + autoCancel.SetCancelStatus(NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION); return; } @@ -357,9 +370,11 @@ FetchEvent::RespondWith(const ResponseOrPromise& aArg, ErrorResult& aRv) } else if (aArg.IsPromise()) { promise = &aArg.GetAsPromise(); } + nsRefPtr ir = mRequest->GetInternalRequest(); mWaitToRespond = true; nsRefPtr handler = - new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode()); + new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode(), + ir->IsClientRequest()); promise->AppendNativeHandler(handler); } diff --git a/dom/workers/test/serviceworkers/app-protocol/application.zip b/dom/workers/test/serviceworkers/app-protocol/application.zip index f8669cd77e4..7d4be7f4617 100644 Binary files a/dom/workers/test/serviceworkers/app-protocol/application.zip and b/dom/workers/test/serviceworkers/app-protocol/application.zip differ diff --git a/dom/workers/test/serviceworkers/app-protocol/controlled.html b/dom/workers/test/serviceworkers/app-protocol/controlled.html index 27d16c19f9d..ce8ce987e0a 100644 --- a/dom/workers/test/serviceworkers/app-protocol/controlled.html +++ b/dom/workers/test/serviceworkers/app-protocol/controlled.html @@ -21,10 +21,12 @@ function runTests() { return testFetchAppResource('test_custom_content_type', 'customContentType', 'text/html'); }) - .then(testRedirectedResponse) - .then(testRedirectedHttpsResponse) - .then(testCachedRedirectedResponse) - .then(testCachedRedirectedHttpsResponse) + // XXX: Cross-origin interceptions without CORS result in opaque responses + // which are illegal for navigations like iframes. (bug 1183313) + //.then(testRedirectedResponse) + //.then(testRedirectedHttpsResponse) + //.then(testCachedRedirectedResponse) + //.then(testCachedRedirectedHttpsResponse) .then(done); } diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h index 13a42de1990..e05c9dd70b2 100644 --- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -331,6 +331,8 @@ ERROR(NS_ERROR_INTERCEPTED_ERROR_RESPONSE, FAILURE(103)), /* Service worker intercepted with a response with bodyUsed set to true */ ERROR(NS_ERROR_INTERCEPTED_USED_RESPONSE, FAILURE(104)), + /* Service worker intercepted a client request with an opaque response */ + ERROR(NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION, FAILURE(105)), #undef MODULE