Bug 1128959 - Implement the WHATWG Streams spec - part 16 - report stream errors during consumption r=bkelly

This commit is contained in:
Andrea Marchesini 2017-08-10 18:04:56 -07:00
Родитель 76f69afc03
Коммит 6e330b0360
9 изменённых файлов: 170 добавлений и 77 удалений

Просмотреть файл

@ -41,6 +41,8 @@
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/CustomElementRegistry.h"
#include "mozilla/dom/DocumentFragment.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/DOMExceptionBinding.h"
#include "mozilla/dom/DOMTypes.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/FileSystemSecurity.h"
@ -10750,3 +10752,73 @@ nsContentUtils::IsOverridingWindowName(const nsAString& aName)
!aName.LowerCaseEqualsLiteral("_parent") &&
!aName.LowerCaseEqualsLiteral("_self");
}
/* static */ void
nsContentUtils::ExtractErrorValues(JSContext* aCx,
JS::Handle<JS::Value> aValue,
nsACString& aSourceSpecOut,
uint32_t* aLineOut,
uint32_t* aColumnOut,
nsString& aMessageOut)
{
MOZ_ASSERT(aLineOut);
MOZ_ASSERT(aColumnOut);
if (aValue.isObject()) {
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
RefPtr<dom::DOMException> domException;
// Try to process as an Error object. Use the file/line/column values
// from the Error as they will be more specific to the root cause of
// the problem.
JSErrorReport* err = obj ? JS_ErrorFromException(aCx, obj) : nullptr;
if (err) {
// Use xpc to extract the error message only. We don't actually send
// this report anywhere.
RefPtr<xpc::ErrorReport> report = new xpc::ErrorReport();
report->Init(err,
"<unknown>", // toString result
false, // chrome
0); // window ID
if (!report->mFileName.IsEmpty()) {
CopyUTF16toUTF8(report->mFileName, aSourceSpecOut);
*aLineOut = report->mLineNumber;
*aColumnOut = report->mColumn;
}
aMessageOut.Assign(report->mErrorMsg);
}
// Next, try to unwrap the rejection value as a DOMException.
else if(NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, obj, domException))) {
nsAutoString filename;
domException->GetFilename(aCx, filename);
if (!filename.IsEmpty()) {
CopyUTF16toUTF8(filename, aSourceSpecOut);
*aLineOut = domException->LineNumber(aCx);
*aColumnOut = domException->ColumnNumber();
}
domException->GetName(aMessageOut);
aMessageOut.AppendLiteral(": ");
nsAutoString message;
domException->GetMessageMoz(message);
aMessageOut.Append(message);
}
}
// If we could not unwrap a specific error type, then perform default safe
// string conversions on primitives. Objects will result in "[Object]"
// unfortunately.
if (aMessageOut.IsEmpty()) {
nsAutoJSString jsString;
if (jsString.init(aCx, aValue)) {
aMessageOut = jsString;
} else {
JS_ClearPendingException(aCx);
}
}
}

Просмотреть файл

@ -1077,6 +1077,11 @@ public:
static bool PrefetchPreloadEnabled(nsIDocShell* aDocShell);
static void
ExtractErrorValues(JSContext* aCx, JS::Handle<JS::Value> aValue,
nsACString& aSourceSpecOut, uint32_t *aLineOut,
uint32_t *aColumnOut, nsString& aMessageOut);
/**
* Fill (with the parameters given) the localized string named |aKey| in
* properties file |aFile|.

Просмотреть файл

@ -211,7 +211,7 @@ public:
// reporting way.
//
// Exceptions generated when reading from the ReadableStream are directly sent
// to the Console (NOTE FOR THE REVIEWER: this is part of patch 16)
// to the Console.
void
SetBodyUsed(JSContext* aCx, ErrorResult& aRv);

Просмотреть файл

@ -7,6 +7,11 @@
#include "FetchStreamReader.h"
#include "InternalResponse.h"
#include "mozilla/dom/PromiseBinding.h"
#include "mozilla/SystemGroup.h"
#include "mozilla/TaskCategory.h"
#include "nsContentUtils.h"
#include "nsIScriptError.h"
#include "nsPIDOMWindow.h"
namespace mozilla {
namespace dom {
@ -297,8 +302,57 @@ void
FetchStreamReader::RejectedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue)
{
ReportErrorToConsole(aCx, aValue);
CloseAndRelease(NS_ERROR_FAILURE);
}
void
FetchStreamReader::ReportErrorToConsole(JSContext* aCx,
JS::Handle<JS::Value> aValue)
{
nsCString sourceSpec;
uint32_t line = 0;
uint32_t column = 0;
nsString valueString;
nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line,
&column, valueString);
nsTArray<nsString> params;
params.AppendElement(valueString);
RefPtr<ConsoleReportCollector> reporter = new ConsoleReportCollector();
reporter->AddConsoleReport(nsIScriptError::errorFlag,
NS_LITERAL_CSTRING("ReadableStreamReader.read"),
nsContentUtils::eDOM_PROPERTIES,
sourceSpec, line, column,
NS_LITERAL_CSTRING("ReadableStreamReadingFailed"),
params);
uint64_t innerWindowId = 0;
if (NS_IsMainThread()) {
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
if (window) {
innerWindowId = window->WindowID();
}
reporter->FlushReportsToConsole(innerWindowId);
return;
}
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
if (workerPrivate) {
innerWindowId = workerPrivate->WindowID();
}
RefPtr<Runnable> r = NS_NewRunnableFunction(
"FetchStreamReader::ReportErrorToConsole",
[reporter, innerWindowId] () {
reporter->FlushReportsToConsole(innerWindowId);
});
workerPrivate->DispatchToMainThread(r.forget());
}
} // dom namespace
} // mozilla namespace

Просмотреть файл

@ -55,6 +55,9 @@ private:
nsresult
WriteBuffer();
void
ReportErrorToConsole(JSContext* aCx, JS::Handle<JS::Value> aValue);
nsCOMPtr<nsIGlobalObject> mGlobal;
nsCOMPtr<nsIEventTarget> mOwningEventTarget;

Просмотреть файл

@ -345,3 +345,5 @@ ScriptSourceMalformed=<script> source URI is malformed: “%S”.
ScriptSourceNotAllowed=<script> source URI is not allowed in this document: “%S”.
# LOCALIZATION NOTE: %1$S is the invalid property value and %2$S is the property name.
InvalidKeyframePropertyValue=Keyframe property value “%1$S” is invalid according to the syntax for “%2$S”.
# LOCALIZATION NOTE: Do not translate "ReadableStream".
ReadableStreamReadingFailed=Failed to read data from the ReadableStream: “%S”.

Просмотреть файл

@ -7,6 +7,7 @@
#include "ServiceWorkerEvents.h"
#include "nsAutoPtr.h"
#include "nsContentUtils.h"
#include "nsIConsoleReportCollector.h"
#include "nsIHttpChannelInternal.h"
#include "nsINetworkInterceptController.h"
@ -30,8 +31,6 @@
#include "mozilla/LoadInfo.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/BodyUtil.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/DOMExceptionBinding.h"
#include "mozilla/dom/FetchEventBinding.h"
#include "mozilla/dom/MessagePort.h"
#include "mozilla/dom/PromiseNativeHandler.h"
@ -414,76 +413,6 @@ void RespondWithCopyComplete(void* aClosure, nsresult aStatus)
}
}
namespace {
void
ExtractErrorValues(JSContext* aCx, JS::Handle<JS::Value> aValue,
nsACString& aSourceSpecOut, uint32_t *aLineOut,
uint32_t *aColumnOut, nsString& aMessageOut)
{
MOZ_ASSERT(aLineOut);
MOZ_ASSERT(aColumnOut);
if (aValue.isObject()) {
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
RefPtr<DOMException> domException;
// Try to process as an Error object. Use the file/line/column values
// from the Error as they will be more specific to the root cause of
// the problem.
JSErrorReport* err = obj ? JS_ErrorFromException(aCx, obj) : nullptr;
if (err) {
// Use xpc to extract the error message only. We don't actually send
// this report anywhere.
RefPtr<xpc::ErrorReport> report = new xpc::ErrorReport();
report->Init(err,
"<unknown>", // toString result
false, // chrome
0); // window ID
if (!report->mFileName.IsEmpty()) {
CopyUTF16toUTF8(report->mFileName, aSourceSpecOut);
*aLineOut = report->mLineNumber;
*aColumnOut = report->mColumn;
}
aMessageOut.Assign(report->mErrorMsg);
}
// Next, try to unwrap the rejection value as a DOMException.
else if(NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, obj, domException))) {
nsAutoString filename;
domException->GetFilename(aCx, filename);
if (!filename.IsEmpty()) {
CopyUTF16toUTF8(filename, aSourceSpecOut);
*aLineOut = domException->LineNumber(aCx);
*aColumnOut = domException->ColumnNumber();
}
domException->GetName(aMessageOut);
aMessageOut.AppendLiteral(": ");
nsAutoString message;
domException->GetMessageMoz(message);
aMessageOut.Append(message);
}
}
// If we could not unwrap a specific error type, then perform default safe
// string conversions on primitives. Objects will result in "[Object]"
// unfortunately.
if (aMessageOut.IsEmpty()) {
nsAutoJSString jsString;
if (jsString.init(aCx, aValue)) {
aMessageOut = jsString;
} else {
JS_ClearPendingException(aCx);
}
}
}
} // anonymous namespace
class MOZ_STACK_CLASS AutoCancel
{
RefPtr<RespondWithHandler> mOwner;
@ -606,7 +535,8 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
uint32_t line = 0;
uint32_t column = 0;
nsString valueString;
ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString);
nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
valueString);
autoCancel.SetCancelMessageAndLocation(sourceSpec, line, column,
NS_LITERAL_CSTRING("InterceptedNonResponseWithURL"),
@ -621,7 +551,8 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
uint32_t line = 0;
uint32_t column = 0;
nsString valueString;
ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString);
nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
valueString);
autoCancel.SetCancelMessageAndLocation(sourceSpec, line, column,
NS_LITERAL_CSTRING("InterceptedNonResponseWithURL"),
@ -771,7 +702,8 @@ RespondWithHandler::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
mInterceptedChannel->SetFinishResponseStart(TimeStamp::Now());
ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString);
nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
valueString);
::AsyncLog(mInterceptedChannel, sourceSpec, line, column,
NS_LITERAL_CSTRING("InterceptionRejectedResponseWithURL"),
@ -917,7 +849,8 @@ public:
nsCString spec;
uint32_t line = 0;
uint32_t column = 0;
ExtractErrorValues(aCx, aValue, spec, &line, &column, mRejectValue);
nsContentUtils::ExtractErrorValues(aCx, aValue, spec, &line, &column,
mRejectValue);
// only use the extracted location if we found one
if (!spec.IsEmpty()) {

Просмотреть файл

@ -149,6 +149,14 @@ fetchXHR('readable-stream-with-exception.txt', function(xhr) {
finish();
});
fetchXHR('readable-stream-with-exception2.txt', function(xhr) {
my_ok(false, "This should not be called!");
finish();
}, function() {
my_ok(true, "The exception has been correctly handled!");
finish();
});
fetchXHR('readable-stream-already-consumed.txt', function(xhr) {
my_ok(false, "This should not be called!");
finish();

Просмотреть файл

@ -137,6 +137,22 @@ onfetch = function(ev) {
));
}
else if (ev.request.url.includes("readable-stream-with-exception2.txt")) {
ev.respondWith(
new Response(
new ReadableStream({
_controller: null,
_count: 0,
start(controller) { this._controller = controller; },
pull() {
if (++this._count == 5) { throw "EXCEPTION 2!"; }
this._controller.enqueue(new Uint8Array([this._count]));
}
})
));
}
else if (ev.request.url.includes("readable-stream-already-consumed.txt")) {
let r = new Response(
new ReadableStream({