Bug 1173934 Show a message if a docshell fails to load due to SW intercept failure. r=ehsan r=jdm

This commit is contained in:
Ben Kelly 2015-07-14 13:11:26 -07:00
parent 4db135286b
commit a8c12eb83d
12 changed files with 118 additions and 23 deletions

View File

@ -5198,6 +5198,16 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
// Broken Content Detected. e.g. Content-MD5 check failure.
error.AssignLiteral("corruptedContentError");
break;
case NS_ERROR_INTERCEPTION_FAILED:
case NS_ERROR_OPAQUE_INTERCEPTION_DISABLED:
case NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE:
case NS_ERROR_INTERCEPTED_ERROR_RESPONSE:
case NS_ERROR_INTERCEPTED_USED_RESPONSE:
// ServiceWorker intercepted request, but something went wrong.
nsContentUtils::MaybeReportInterceptionErrorToConsole(GetDocument(),
aError);
error.AssignLiteral("corruptedContentError");
break;
default:
break;
}
@ -7825,6 +7835,11 @@ nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE ||
aStatus == NS_ERROR_REMOTE_XUL ||
aStatus == NS_ERROR_OFFLINE ||
aStatus == NS_ERROR_INTERCEPTION_FAILED ||
aStatus == NS_ERROR_OPAQUE_INTERCEPTION_DISABLED ||
aStatus == NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE ||
aStatus == NS_ERROR_INTERCEPTED_ERROR_RESPONSE ||
aStatus == NS_ERROR_INTERCEPTED_USED_RESPONSE ||
NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) {
// Errors to be shown for any frame
DisplayLoadError(aStatus, url, nullptr, aChannel);
@ -14091,7 +14106,7 @@ nsDocShell::ChannelIntercepted(nsIInterceptedChannel* aChannel)
{
nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (!swm) {
aChannel->Cancel();
aChannel->Cancel(NS_ERROR_INTERCEPTION_FAILED);
return NS_OK;
}

View File

@ -3417,6 +3417,34 @@ nsContentUtils::ReportToConsole(uint32_t aErrorFlags,
aLineNumber, aColumnNumber);
}
/* static */ nsresult
nsContentUtils::MaybeReportInterceptionErrorToConsole(nsIDocument* aDocument,
nsresult aError)
{
const char* messageName = nullptr;
if (aError == NS_ERROR_INTERCEPTION_FAILED) {
messageName = "InterceptionFailed";
} else if (aError == NS_ERROR_OPAQUE_INTERCEPTION_DISABLED) {
messageName = "OpaqueInterceptionDisabled";
} else if (aError == NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE) {
messageName = "BadOpaqueInterceptionRequestMode";
} else if (aError == NS_ERROR_INTERCEPTED_ERROR_RESPONSE) {
messageName = "InterceptedErrorResponse";
} else if (aError == NS_ERROR_INTERCEPTED_USED_RESPONSE) {
messageName = "InterceptedUsedResponse";
}
if (messageName) {
return ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("Service Worker Interception"),
aDocument,
nsContentUtils::eDOM_PROPERTIES,
messageName);
}
return NS_OK;
}
/* static */ nsresult
nsContentUtils::ReportToConsoleNonLocalized(const nsAString& aErrorText,

View File

@ -842,6 +842,9 @@ public:
uint32_t aLineNumber = 0,
uint32_t aColumnNumber = 0);
static nsresult
MaybeReportInterceptionErrorToConsole(nsIDocument* aDocument, nsresult aError);
static void LogMessageToConsole(const char* aMsg, ...);
/**

View File

@ -166,3 +166,13 @@ HittingMaxWorkersPerDomain=A ServiceWorker could not be started immediately beca
PannerNodeDopplerWarning=Use of setVelocity on the PannerNode and AudioListener, and speedOfSound and dopplerFactor on the AudioListener are deprecated and those members will be removed. For more help https://developer.mozilla.org/en-US/docs/Web/API/AudioListener#Deprecated_features
# LOCALIZATION NOTE: Do not translate "Worker".
EmptyWorkerSourceWarning=Attempting to create a Worker from an empty source. This is probably unintentional.
# LOCALIZATION NOTE: Do not translate "ServiceWorker".
InterceptionFailed=ServiceWorker network interception failed due to an unexpected error.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "FetchEvent.respondWith()", "opaque", or "Response".
OpaqueInterceptionDisabled=A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while opaque interception is disabled.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "FetchEvent.respondWith()", "FetchEvent.request.type", "same-origin", "cors", "no-cors", "opaque", "Response", or "RequestMode".
BadOpaqueInterceptionRequestMode=A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while the FetchEvent.request.type was either "same-origin" or "cors". Opaque Response objects are only valid when the RequestMode is "no-cors".
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "Error", "Response", "FetchEvent.respondWith()", or "fetch()".
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.

View File

@ -79,16 +79,19 @@ namespace {
class CancelChannelRunnable final : public nsRunnable
{
nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
const nsresult mStatus;
public:
explicit CancelChannelRunnable(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel)
CancelChannelRunnable(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
nsresult aStatus)
: mChannel(aChannel)
, mStatus(aStatus)
{
}
NS_IMETHOD Run()
{
MOZ_ASSERT(NS_IsMainThread());
nsresult rv = mChannel->Cancel();
nsresult rv = mChannel->Cancel(mStatus);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
@ -162,7 +165,7 @@ public:
void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
void CancelRequest();
void CancelRequest(nsresult aStatus);
private:
~RespondWithHandler() {}
};
@ -192,7 +195,8 @@ void RespondWithCopyComplete(void* aClosure, nsresult aStatus)
data->mInternalResponse,
data->mWorkerChannelInfo);
} else {
event = new CancelChannelRunnable(data->mInterceptedChannel);
event = new CancelChannelRunnable(data->mInterceptedChannel,
NS_ERROR_INTERCEPTION_FAILED);
}
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(event)));
}
@ -200,20 +204,28 @@ void RespondWithCopyComplete(void* aClosure, nsresult aStatus)
class MOZ_STACK_CLASS AutoCancel
{
nsRefPtr<RespondWithHandler> mOwner;
nsresult mStatus;
public:
explicit AutoCancel(RespondWithHandler* aOwner)
: mOwner(aOwner)
, mStatus(NS_ERROR_INTERCEPTION_FAILED)
{
}
~AutoCancel()
{
if (mOwner) {
mOwner->CancelRequest();
mOwner->CancelRequest(mStatus);
}
}
void SetCancelStatus(nsresult aStatus)
{
MOZ_ASSERT(NS_FAILED(aStatus));
mStatus = aStatus;
}
void Reset()
{
mOwner = nullptr;
@ -246,17 +258,24 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
// security implications are not a complete disaster.
if (response->Type() == ResponseType::Opaque &&
!worker->OpaqueInterceptionEnabled()) {
autoCancel.SetCancelStatus(NS_ERROR_OPAQUE_INTERCEPTION_DISABLED);
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."
if (((response->Type() == ResponseType::Opaque) && (mRequestMode != RequestMode::No_cors)) ||
response->Type() == ResponseType::Error) {
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);
return;
}
if (NS_WARN_IF(response->BodyUsed())) {
autoCancel.SetCancelStatus(NS_ERROR_INTERCEPTED_USED_RESPONSE);
return;
}
@ -302,13 +321,14 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
void
RespondWithHandler::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
{
CancelRequest();
CancelRequest(NS_ERROR_INTERCEPTION_FAILED);
}
void
RespondWithHandler::CancelRequest()
RespondWithHandler::CancelRequest(nsresult aStatus)
{
nsCOMPtr<nsIRunnable> runnable = new CancelChannelRunnable(mInterceptedChannel);
nsCOMPtr<nsIRunnable> runnable =
new CancelChannelRunnable(mInterceptedChannel, aStatus);
NS_DispatchToMainThread(runnable);
}

View File

@ -89,13 +89,15 @@ InterceptedJARChannel::FinishSynthesizedResponse()
}
NS_IMETHODIMP
InterceptedJARChannel::Cancel()
InterceptedJARChannel::Cancel(nsresult aStatus)
{
MOZ_ASSERT(NS_FAILED(aStatus));
if (!mChannel) {
return NS_ERROR_FAILURE;
}
nsresult rv = mChannel->Cancel(NS_BINDING_ABORTED);
nsresult rv = mChannel->Cancel(aStatus);
NS_ENSURE_SUCCESS(rv, rv);
mResponseBody = nullptr;
mChannel = nullptr;

View File

@ -26,7 +26,7 @@ class ChannelInfo;
* which do not implement nsIChannel.
*/
[scriptable, uuid(f2c07a6b-366d-4ef4-85ab-a77f4bcb1646)]
[scriptable, uuid(1062c96a-d73c-4ad5-beb7-6e803e414973)]
interface nsIInterceptedChannel : nsISupports
{
/**
@ -59,7 +59,7 @@ interface nsIInterceptedChannel : nsISupports
* @return NS_ERROR_FAILURE if the response has already been synthesized or
* the original request has been instructed to continue.
*/
void cancel();
void cancel(in nsresult status);
/**
* The synthesized response body to be produced.

View File

@ -98,7 +98,7 @@ HttpChannelParent::ActorDestroy(ActorDestroyReason why)
// If this is an intercepted channel, we need to make sure that any resources are
// cleaned up to avoid leaks.
if (mInterceptedChannel) {
mInterceptedChannel->Cancel();
mInterceptedChannel->Cancel(NS_ERROR_INTERCEPTION_FAILED);
mInterceptedChannel = nullptr;
}
}

View File

@ -230,15 +230,17 @@ InterceptedChannelChrome::FinishSynthesizedResponse()
}
NS_IMETHODIMP
InterceptedChannelChrome::Cancel()
InterceptedChannelChrome::Cancel(nsresult aStatus)
{
MOZ_ASSERT(NS_FAILED(aStatus));
if (!mChannel) {
return NS_ERROR_FAILURE;
}
// we need to use AsyncAbort instead of Cancel since there's no active pump
// to cancel which will provide OnStart/OnStopRequest to the channel.
nsresult rv = mChannel->AsyncAbort(NS_BINDING_ABORTED);
nsresult rv = mChannel->AsyncAbort(aStatus);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
@ -335,15 +337,17 @@ InterceptedChannelContent::FinishSynthesizedResponse()
}
NS_IMETHODIMP
InterceptedChannelContent::Cancel()
InterceptedChannelContent::Cancel(nsresult aStatus)
{
MOZ_ASSERT(NS_FAILED(aStatus));
if (!mChannel) {
return NS_ERROR_FAILURE;
}
// we need to use AsyncAbort instead of Cancel since there's no active pump
// to cancel which will provide OnStart/OnStopRequest to the channel.
nsresult rv = mChannel->AsyncAbort(NS_BINDING_ABORTED);
nsresult rv = mChannel->AsyncAbort(aStatus);
NS_ENSURE_SUCCESS(rv, rv);
mChannel = nullptr;
mStreamListener = nullptr;

View File

@ -81,7 +81,7 @@ public:
NS_IMETHOD GetChannel(nsIChannel** aChannel) override;
NS_IMETHOD SynthesizeStatus(uint16_t aStatus, const nsACString& aReason) override;
NS_IMETHOD SynthesizeHeader(const nsACString& aName, const nsACString& aValue) override;
NS_IMETHOD Cancel() override;
NS_IMETHOD Cancel(nsresult aStatus) override;
NS_IMETHOD SetChannelInfo(mozilla::dom::ChannelInfo* aChannelInfo) override;
virtual void NotifyController() override;
@ -108,7 +108,7 @@ public:
NS_IMETHOD GetChannel(nsIChannel** aChannel) override;
NS_IMETHOD SynthesizeStatus(uint16_t aStatus, const nsACString& aReason) override;
NS_IMETHOD SynthesizeHeader(const nsACString& aName, const nsACString& aValue) override;
NS_IMETHOD Cancel() override;
NS_IMETHOD Cancel(nsresult aStatus) override;
NS_IMETHOD SetChannelInfo(mozilla::dom::ChannelInfo* aChannelInfo) override;
virtual void NotifyController() override;

View File

@ -188,7 +188,7 @@ add_test(function() {
// ensure that the intercepted channel can be cancelled
add_test(function() {
var chan = make_channel(URL + '/body', null, function(intercepted) {
intercepted.cancel();
intercepted.cancel(Cr.NS_BINDING_ABORTED);
});
chan.asyncOpen(new ChannelListener(run_next_test, null,
CL_EXPECT_FAILURE), null);

View File

@ -318,6 +318,19 @@
ERROR(NS_NET_STATUS_SENDING_TO, FAILURE(5)),
ERROR(NS_NET_STATUS_WAITING_FOR, FAILURE(10)),
ERROR(NS_NET_STATUS_RECEIVING_FROM, FAILURE(6)),
/* nsIInterceptedChannel */
/* Generic error for non-specific failures during service worker interception */
ERROR(NS_ERROR_INTERCEPTION_FAILED, FAILURE(100)),
/* Service worker intercepted with an opaque response while
dom.serviceWorkers.interception.opaque.enabled pref was set to false */
ERROR(NS_ERROR_OPAQUE_INTERCEPTION_DISABLED, FAILURE(101)),
/* Attempt to return opaque response for anything but "non-cors" request */
ERROR(NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE, FAILURE(102)),
/* Service worker intercepted with an error response */
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)),
#undef MODULE