Bug 1413112 - WorkerError in separate files, r=bkelly

This commit is contained in:
Andrea Marchesini 2018-01-30 10:12:50 +01:00
parent 900cbdf1e3
commit 1be2954fe1
7 changed files with 573 additions and 371 deletions

View File

@ -55,7 +55,7 @@ private:
} mState;
// Touched on worker-thread only.
UniquePtr<WorkerHolder> mWorkerHolder;
UniquePtr<workers::WorkerHolder> mWorkerHolder;
};
} // namespace dom

View File

@ -13,6 +13,7 @@
#include "nsThreadUtils.h"
#include "ScriptLoader.h"
#include "WorkerCommon.h"
#include "WorkerError.h"
#include "WorkerPrivate.h"
#include "WorkerRunnable.h"
#include "WorkerScope.h"

485
dom/workers/WorkerError.cpp Normal file
View File

@ -0,0 +1,485 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WorkerError.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/dom/ErrorEvent.h"
#include "mozilla/dom/ErrorEventBinding.h"
#include "mozilla/dom/ServiceWorkerManager.h"
#include "mozilla/dom/SimpleGlobalObject.h"
#include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
#include "mozilla/dom/WorkerGlobalScopeBinding.h"
#include "mozilla/EventDispatcher.h"
#include "nsIConsoleService.h"
#include "nsScriptError.h"
#include "WorkerRunnable.h"
#include "WorkerPrivate.h"
#include "WorkerScope.h"
namespace mozilla {
namespace dom {
using namespace workers;
namespace {
class ReportErrorRunnable final : public WorkerRunnable
{
WorkerErrorReport mReport;
public:
// aWorkerPrivate is the worker thread we're on (or the main thread, if null)
// aTarget is the worker object that we are going to fire an error at
// (if any).
static void
ReportError(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
bool aFireAtScope, WorkerPrivate* aTarget,
const WorkerErrorReport& aReport, uint64_t aInnerWindowId,
JS::Handle<JS::Value> aException = JS::NullHandleValue)
{
if (aWorkerPrivate) {
aWorkerPrivate->AssertIsOnWorkerThread();
} else {
AssertIsOnMainThread();
}
// We should not fire error events for warnings but instead make sure that
// they show up in the error console.
if (!JSREPORT_IS_WARNING(aReport.mFlags)) {
// First fire an ErrorEvent at the worker.
RootedDictionary<ErrorEventInit> init(aCx);
if (aReport.mMutedError) {
init.mMessage.AssignLiteral("Script error.");
} else {
init.mMessage = aReport.mMessage;
init.mFilename = aReport.mFilename;
init.mLineno = aReport.mLineNumber;
init.mError = aException;
}
init.mCancelable = true;
init.mBubbles = false;
if (aTarget) {
RefPtr<ErrorEvent> event =
ErrorEvent::Constructor(aTarget, NS_LITERAL_STRING("error"), init);
event->SetTrusted(true);
bool defaultActionEnabled;
aTarget->DispatchEvent(event, &defaultActionEnabled);
if (!defaultActionEnabled) {
return;
}
}
// Now fire an event at the global object, but don't do that if the error
// code is too much recursion and this is the same script threw the error.
// XXXbz the interaction of this with worker errors seems kinda broken.
// An overrecursion in the debugger or debugger sandbox will get turned
// into an error event on our parent worker!
// https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks making this
// better.
if (aFireAtScope &&
(aTarget || aReport.mErrorNumber != JSMSG_OVER_RECURSED)) {
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
NS_ASSERTION(global, "This should never be null!");
nsEventStatus status = nsEventStatus_eIgnore;
nsIScriptGlobalObject* sgo;
if (aWorkerPrivate) {
WorkerGlobalScope* globalScope = nullptr;
UNWRAP_OBJECT(WorkerGlobalScope, &global, globalScope);
if (!globalScope) {
WorkerDebuggerGlobalScope* globalScope = nullptr;
UNWRAP_OBJECT(WorkerDebuggerGlobalScope, &global, globalScope);
MOZ_ASSERT_IF(globalScope, globalScope->GetWrapperPreserveColor() == global);
if (globalScope || IsDebuggerSandbox(global)) {
aWorkerPrivate->ReportErrorToDebugger(aReport.mFilename, aReport.mLineNumber,
aReport.mMessage);
return;
}
MOZ_ASSERT(SimpleGlobalObject::SimpleGlobalType(global) ==
SimpleGlobalObject::GlobalType::BindingDetail);
// XXXbz We should really log this to console, but unwinding out of
// this stuff without ending up firing any events is ... hard. Just
// return for now.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks
// making this better.
return;
}
MOZ_ASSERT(globalScope->GetWrapperPreserveColor() == global);
nsIDOMEventTarget* target = static_cast<nsIDOMEventTarget*>(globalScope);
RefPtr<ErrorEvent> event =
ErrorEvent::Constructor(aTarget, NS_LITERAL_STRING("error"), init);
event->SetTrusted(true);
if (NS_FAILED(EventDispatcher::DispatchDOMEvent(target, nullptr,
event, nullptr,
&status))) {
NS_WARNING("Failed to dispatch worker thread error event!");
status = nsEventStatus_eIgnore;
}
}
else if ((sgo = nsJSUtils::GetStaticScriptGlobal(global))) {
MOZ_ASSERT(NS_IsMainThread());
if (NS_FAILED(sgo->HandleScriptError(init, &status))) {
NS_WARNING("Failed to dispatch main thread error event!");
status = nsEventStatus_eIgnore;
}
}
// Was preventDefault() called?
if (status == nsEventStatus_eConsumeNoDefault) {
return;
}
}
}
// Now fire a runnable to do the same on the parent's thread if we can.
if (aWorkerPrivate) {
RefPtr<ReportErrorRunnable> runnable =
new ReportErrorRunnable(aWorkerPrivate, aReport);
runnable->Dispatch();
return;
}
// Otherwise log an error to the error console.
LogErrorToConsole(aReport, aInnerWindowId);
}
ReportErrorRunnable(WorkerPrivate* aWorkerPrivate,
const WorkerErrorReport& aReport)
: WorkerRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount),
mReport(aReport)
{ }
private:
virtual void
PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
{
aWorkerPrivate->AssertIsOnWorkerThread();
// Dispatch may fail if the worker was canceled, no need to report that as
// an error, so don't call base class PostDispatch.
}
virtual bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
JS::Rooted<JSObject*> target(aCx, aWorkerPrivate->GetWrapper());
uint64_t innerWindowId;
bool fireAtScope = true;
bool workerIsAcceptingEvents = aWorkerPrivate->IsAcceptingEvents();
WorkerPrivate* parent = aWorkerPrivate->GetParent();
if (parent) {
innerWindowId = 0;
}
else {
AssertIsOnMainThread();
if (aWorkerPrivate->IsFrozen() ||
aWorkerPrivate->IsParentWindowPaused()) {
MOZ_ASSERT(!IsDebuggerRunnable());
aWorkerPrivate->QueueRunnable(this);
return true;
}
if (aWorkerPrivate->IsSharedWorker()) {
aWorkerPrivate->BroadcastErrorToSharedWorkers(aCx, &mReport,
/* isErrorEvent */ true);
return true;
}
// Service workers do not have a main thread parent global, so normal
// worker error reporting will crash. Instead, pass the error to
// the ServiceWorkerManager to report on any controlled documents.
if (aWorkerPrivate->IsServiceWorker()) {
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (swm) {
swm->HandleError(aCx, aWorkerPrivate->GetPrincipal(),
aWorkerPrivate->ServiceWorkerScope(),
aWorkerPrivate->ScriptURL(),
mReport.mMessage,
mReport.mFilename, mReport.mLine, mReport.mLineNumber,
mReport.mColumnNumber, mReport.mFlags,
mReport.mExnType);
}
return true;
}
// The innerWindowId is only required if we are going to ReportError
// below, which is gated on this condition. The inner window correctness
// check is only going to succeed when the worker is accepting events.
if (workerIsAcceptingEvents) {
aWorkerPrivate->AssertInnerWindowIsCorrect();
innerWindowId = aWorkerPrivate->WindowID();
}
}
// Don't fire this event if the JS object has been disconnected from the
// private object.
if (!workerIsAcceptingEvents) {
return true;
}
ReportError(aCx, parent, fireAtScope, aWorkerPrivate, mReport,
innerWindowId);
return true;
}
};
} // anonymous
void
WorkerErrorBase::AssignErrorBase(JSErrorBase* aReport)
{
mFilename = NS_ConvertUTF8toUTF16(aReport->filename);
mLineNumber = aReport->lineno;
mColumnNumber = aReport->column;
mErrorNumber = aReport->errorNumber;
}
void
WorkerErrorNote::AssignErrorNote(JSErrorNotes::Note* aNote)
{
WorkerErrorBase::AssignErrorBase(aNote);
xpc::ErrorNote::ErrorNoteToMessageString(aNote, mMessage);
}
void
WorkerErrorReport::AssignErrorReport(JSErrorReport* aReport)
{
WorkerErrorBase::AssignErrorBase(aReport);
xpc::ErrorReport::ErrorReportToMessageString(aReport, mMessage);
mLine.Assign(aReport->linebuf(), aReport->linebufLength());
mFlags = aReport->flags;
MOZ_ASSERT(aReport->exnType >= JSEXN_FIRST && aReport->exnType < JSEXN_LIMIT);
mExnType = JSExnType(aReport->exnType);
mMutedError = aReport->isMuted;
if (aReport->notes) {
if (!mNotes.SetLength(aReport->notes->length(), fallible)) {
return;
}
size_t i = 0;
for (auto&& note : *aReport->notes) {
mNotes.ElementAt(i).AssignErrorNote(note.get());
i++;
}
}
}
namespace workers {
// aWorkerPrivate is the worker thread we're on (or the main thread, if null)
// aTarget is the worker object that we are going to fire an error at
// (if any).
void
ReportError(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
bool aFireAtScope, DOMEventTargetHelper* aTarget,
const WorkerErrorReport& aReport, uint64_t aInnerWindowId,
JS::Handle<JS::Value> aException)
{
if (aWorkerPrivate) {
aWorkerPrivate->AssertIsOnWorkerThread();
} else {
AssertIsOnMainThread();
}
// We should not fire error events for warnings but instead make sure that
// they show up in the error console.
if (!JSREPORT_IS_WARNING(aReport.mFlags)) {
// First fire an ErrorEvent at the worker.
RootedDictionary<ErrorEventInit> init(aCx);
if (aReport.mMutedError) {
init.mMessage.AssignLiteral("Script error.");
} else {
init.mMessage = aReport.mMessage;
init.mFilename = aReport.mFilename;
init.mLineno = aReport.mLineNumber;
init.mError = aException;
}
init.mCancelable = true;
init.mBubbles = false;
if (aTarget) {
RefPtr<ErrorEvent> event =
ErrorEvent::Constructor(aTarget, NS_LITERAL_STRING("error"), init);
event->SetTrusted(true);
bool defaultActionEnabled;
aTarget->DispatchEvent(event, &defaultActionEnabled);
if (!defaultActionEnabled) {
return;
}
}
// Now fire an event at the global object, but don't do that if the error
// code is too much recursion and this is the same script threw the error.
// XXXbz the interaction of this with worker errors seems kinda broken.
// An overrecursion in the debugger or debugger sandbox will get turned
// into an error event on our parent worker!
// https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks making this
// better.
if (aFireAtScope &&
(aTarget || aReport.mErrorNumber != JSMSG_OVER_RECURSED)) {
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
NS_ASSERTION(global, "This should never be null!");
nsEventStatus status = nsEventStatus_eIgnore;
nsIScriptGlobalObject* sgo;
if (aWorkerPrivate) {
WorkerGlobalScope* globalScope = nullptr;
UNWRAP_OBJECT(WorkerGlobalScope, &global, globalScope);
if (!globalScope) {
WorkerDebuggerGlobalScope* globalScope = nullptr;
UNWRAP_OBJECT(WorkerDebuggerGlobalScope, &global, globalScope);
MOZ_ASSERT_IF(globalScope, globalScope->GetWrapperPreserveColor() == global);
if (globalScope || IsDebuggerSandbox(global)) {
aWorkerPrivate->ReportErrorToDebugger(aReport.mFilename, aReport.mLineNumber,
aReport.mMessage);
return;
}
MOZ_ASSERT(SimpleGlobalObject::SimpleGlobalType(global) ==
SimpleGlobalObject::GlobalType::BindingDetail);
// XXXbz We should really log this to console, but unwinding out of
// this stuff without ending up firing any events is ... hard. Just
// return for now.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks
// making this better.
return;
}
MOZ_ASSERT(globalScope->GetWrapperPreserveColor() == global);
nsIDOMEventTarget* target = static_cast<nsIDOMEventTarget*>(globalScope);
RefPtr<ErrorEvent> event =
ErrorEvent::Constructor(aTarget, NS_LITERAL_STRING("error"), init);
event->SetTrusted(true);
if (NS_FAILED(EventDispatcher::DispatchDOMEvent(target, nullptr,
event, nullptr,
&status))) {
NS_WARNING("Failed to dispatch worker thread error event!");
status = nsEventStatus_eIgnore;
}
}
else if ((sgo = nsJSUtils::GetStaticScriptGlobal(global))) {
MOZ_ASSERT(NS_IsMainThread());
if (NS_FAILED(sgo->HandleScriptError(init, &status))) {
NS_WARNING("Failed to dispatch main thread error event!");
status = nsEventStatus_eIgnore;
}
}
// Was preventDefault() called?
if (status == nsEventStatus_eConsumeNoDefault) {
return;
}
}
}
// Now fire a runnable to do the same on the parent's thread if we can.
if (aWorkerPrivate) {
RefPtr<ReportErrorRunnable> runnable =
new ReportErrorRunnable(aWorkerPrivate, aReport);
runnable->Dispatch();
return;
}
// Otherwise log an error to the error console.
LogErrorToConsole(aReport, aInnerWindowId);
}
void
LogErrorToConsole(const WorkerErrorReport& aReport, uint64_t aInnerWindowId)
{
AssertIsOnMainThread();
RefPtr<nsScriptErrorBase> scriptError = new nsScriptError();
NS_WARNING_ASSERTION(scriptError, "Failed to create script error!");
if (scriptError) {
nsAutoCString category("Web Worker");
if (NS_FAILED(scriptError->InitWithWindowID(aReport.mMessage,
aReport.mFilename,
aReport.mLine,
aReport.mLineNumber,
aReport.mColumnNumber,
aReport.mFlags,
category,
aInnerWindowId))) {
NS_WARNING("Failed to init script error!");
scriptError = nullptr;
}
for (size_t i = 0, len = aReport.mNotes.Length(); i < len; i++) {
const WorkerErrorNote& note = aReport.mNotes.ElementAt(i);
nsScriptErrorNote* noteObject = new nsScriptErrorNote();
noteObject->Init(note.mMessage, note.mFilename,
note.mLineNumber, note.mColumnNumber);
scriptError->AddNote(noteObject);
}
}
nsCOMPtr<nsIConsoleService> consoleService =
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
NS_WARNING_ASSERTION(consoleService, "Failed to get console service!");
if (consoleService) {
if (scriptError) {
if (NS_SUCCEEDED(consoleService->LogMessage(scriptError))) {
return;
}
NS_WARNING("LogMessage failed!");
} else if (NS_SUCCEEDED(consoleService->LogStringMessage(
aReport.mMessage.BeginReading()))) {
return;
}
NS_WARNING("LogStringMessage failed!");
}
NS_ConvertUTF16toUTF8 msg(aReport.mMessage);
NS_ConvertUTF16toUTF8 filename(aReport.mFilename);
static const char kErrorString[] = "JS error in Web Worker: %s [%s:%u]";
#ifdef ANDROID
__android_log_print(ANDROID_LOG_INFO, "Gecko", kErrorString, msg.get(),
filename.get(), aReport.mLineNumber);
#endif
fprintf(stderr, kErrorString, msg.get(), filename.get(), aReport.mLineNumber);
fflush(stderr);
}
} // workers namespace
} // dom namespace
} // mozilla namespace

82
dom/workers/WorkerError.h Normal file
View File

@ -0,0 +1,82 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_workers_WorkerError_h
#define mozilla_dom_workers_WorkerError_h
#include "WorkerCommon.h"
#include "jsapi.h"
namespace mozilla {
class DOMEventTargetHelper;
namespace dom {
class WorkerErrorBase
{
public:
nsString mMessage;
nsString mFilename;
uint32_t mLineNumber;
uint32_t mColumnNumber;
uint32_t mErrorNumber;
WorkerErrorBase()
: mLineNumber(0),
mColumnNumber(0),
mErrorNumber(0)
{ }
void AssignErrorBase(JSErrorBase* aReport);
};
class WorkerErrorNote : public WorkerErrorBase
{
public:
void AssignErrorNote(JSErrorNotes::Note* aNote);
};
class WorkerErrorReport : public WorkerErrorBase
{
public:
nsString mLine;
uint32_t mFlags;
JSExnType mExnType;
bool mMutedError;
nsTArray<WorkerErrorNote> mNotes;
WorkerErrorReport()
: mFlags(0),
mExnType(JSEXN_ERR),
mMutedError(false)
{ }
void AssignErrorReport(JSErrorReport* aReport);
};
} // dom namespace
} // mozilla namespace
BEGIN_WORKERS_NAMESPACE
class WorkerPrivate;
// aWorkerPrivate is the worker thread we're on (or the main thread, if null)
// aTarget is the worker object that we are going to fire an error at
// (if any).
void
ReportError(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
bool aFireAtScope, DOMEventTargetHelper* aTarget,
const WorkerErrorReport& aReport, uint64_t aInnerWindowId,
JS::Handle<JS::Value> aException = JS::NullHandleValue);
void
LogErrorToConsole(const WorkerErrorReport& aReport, uint64_t aInnerWindowId);
END_WORKERS_NAMESPACE
#endif // mozilla_dom_workers_WorkerError_h

View File

@ -66,12 +66,9 @@
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseDebugging.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/SimpleGlobalObject.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/StructuredCloneHolder.h"
#include "mozilla/dom/WorkerBinding.h"
#include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
#include "mozilla/dom/WorkerGlobalScopeBinding.h"
#include "mozilla/Preferences.h"
#include "mozilla/ThreadEventQueue.h"
#include "mozilla/ThrottledEventQueue.h"
@ -90,7 +87,6 @@
#include "nsProxyRelease.h"
#include "nsQueryObject.h"
#include "nsSandboxFlags.h"
#include "nsScriptError.h"
#include "nsUTF8Utils.h"
#include "prthread.h"
#include "xpcpublic.h"
@ -112,6 +108,7 @@
#include "SharedWorker.h"
#include "WorkerDebugger.h"
#include "WorkerDebuggerManager.h"
#include "WorkerError.h"
#include "WorkerHolder.h"
#include "WorkerNavigator.h"
#include "WorkerRunnable.h"
@ -665,223 +662,6 @@ private:
}
};
class ReportErrorRunnable final : public WorkerRunnable
{
WorkerErrorReport mReport;
public:
// aWorkerPrivate is the worker thread we're on (or the main thread, if null)
// aTarget is the worker object that we are going to fire an error at
// (if any).
static void
ReportError(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
bool aFireAtScope, WorkerPrivate* aTarget,
const WorkerErrorReport& aReport, uint64_t aInnerWindowId,
JS::Handle<JS::Value> aException = JS::NullHandleValue)
{
if (aWorkerPrivate) {
aWorkerPrivate->AssertIsOnWorkerThread();
} else {
AssertIsOnMainThread();
}
// We should not fire error events for warnings but instead make sure that
// they show up in the error console.
if (!JSREPORT_IS_WARNING(aReport.mFlags)) {
// First fire an ErrorEvent at the worker.
RootedDictionary<ErrorEventInit> init(aCx);
if (aReport.mMutedError) {
init.mMessage.AssignLiteral("Script error.");
} else {
init.mMessage = aReport.mMessage;
init.mFilename = aReport.mFilename;
init.mLineno = aReport.mLineNumber;
init.mError = aException;
}
init.mCancelable = true;
init.mBubbles = false;
if (aTarget) {
RefPtr<ErrorEvent> event =
ErrorEvent::Constructor(aTarget, NS_LITERAL_STRING("error"), init);
event->SetTrusted(true);
bool defaultActionEnabled;
aTarget->DispatchEvent(event, &defaultActionEnabled);
if (!defaultActionEnabled) {
return;
}
}
// Now fire an event at the global object, but don't do that if the error
// code is too much recursion and this is the same script threw the error.
// XXXbz the interaction of this with worker errors seems kinda broken.
// An overrecursion in the debugger or debugger sandbox will get turned
// into an error event on our parent worker!
// https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks making this
// better.
if (aFireAtScope &&
(aTarget || aReport.mErrorNumber != JSMSG_OVER_RECURSED)) {
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
NS_ASSERTION(global, "This should never be null!");
nsEventStatus status = nsEventStatus_eIgnore;
nsIScriptGlobalObject* sgo;
if (aWorkerPrivate) {
WorkerGlobalScope* globalScope = nullptr;
UNWRAP_OBJECT(WorkerGlobalScope, &global, globalScope);
if (!globalScope) {
WorkerDebuggerGlobalScope* globalScope = nullptr;
UNWRAP_OBJECT(WorkerDebuggerGlobalScope, &global, globalScope);
MOZ_ASSERT_IF(globalScope, globalScope->GetWrapperPreserveColor() == global);
if (globalScope || IsDebuggerSandbox(global)) {
aWorkerPrivate->ReportErrorToDebugger(aReport.mFilename, aReport.mLineNumber,
aReport.mMessage);
return;
}
MOZ_ASSERT(SimpleGlobalObject::SimpleGlobalType(global) ==
SimpleGlobalObject::GlobalType::BindingDetail);
// XXXbz We should really log this to console, but unwinding out of
// this stuff without ending up firing any events is ... hard. Just
// return for now.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks
// making this better.
return;
}
MOZ_ASSERT(globalScope->GetWrapperPreserveColor() == global);
nsIDOMEventTarget* target = static_cast<nsIDOMEventTarget*>(globalScope);
RefPtr<ErrorEvent> event =
ErrorEvent::Constructor(aTarget, NS_LITERAL_STRING("error"), init);
event->SetTrusted(true);
if (NS_FAILED(EventDispatcher::DispatchDOMEvent(target, nullptr,
event, nullptr,
&status))) {
NS_WARNING("Failed to dispatch worker thread error event!");
status = nsEventStatus_eIgnore;
}
}
else if ((sgo = nsJSUtils::GetStaticScriptGlobal(global))) {
MOZ_ASSERT(NS_IsMainThread());
if (NS_FAILED(sgo->HandleScriptError(init, &status))) {
NS_WARNING("Failed to dispatch main thread error event!");
status = nsEventStatus_eIgnore;
}
}
// Was preventDefault() called?
if (status == nsEventStatus_eConsumeNoDefault) {
return;
}
}
}
// Now fire a runnable to do the same on the parent's thread if we can.
if (aWorkerPrivate) {
RefPtr<ReportErrorRunnable> runnable =
new ReportErrorRunnable(aWorkerPrivate, aReport);
runnable->Dispatch();
return;
}
// Otherwise log an error to the error console.
LogErrorToConsole(aReport, aInnerWindowId);
}
private:
ReportErrorRunnable(WorkerPrivate* aWorkerPrivate,
const WorkerErrorReport& aReport)
: WorkerRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount),
mReport(aReport)
{ }
virtual void
PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
{
aWorkerPrivate->AssertIsOnWorkerThread();
// Dispatch may fail if the worker was canceled, no need to report that as
// an error, so don't call base class PostDispatch.
}
virtual bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
JS::Rooted<JSObject*> target(aCx, aWorkerPrivate->GetWrapper());
uint64_t innerWindowId;
bool fireAtScope = true;
bool workerIsAcceptingEvents = aWorkerPrivate->IsAcceptingEvents();
WorkerPrivate* parent = aWorkerPrivate->GetParent();
if (parent) {
innerWindowId = 0;
}
else {
AssertIsOnMainThread();
if (aWorkerPrivate->IsFrozen() ||
aWorkerPrivate->IsParentWindowPaused()) {
MOZ_ASSERT(!IsDebuggerRunnable());
aWorkerPrivate->QueueRunnable(this);
return true;
}
if (aWorkerPrivate->IsSharedWorker()) {
aWorkerPrivate->BroadcastErrorToSharedWorkers(aCx, &mReport,
/* isErrorEvent */ true);
return true;
}
// Service workers do not have a main thread parent global, so normal
// worker error reporting will crash. Instead, pass the error to
// the ServiceWorkerManager to report on any controlled documents.
if (aWorkerPrivate->IsServiceWorker()) {
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (swm) {
swm->HandleError(aCx, aWorkerPrivate->GetPrincipal(),
aWorkerPrivate->ServiceWorkerScope(),
aWorkerPrivate->ScriptURL(),
mReport.mMessage,
mReport.mFilename, mReport.mLine, mReport.mLineNumber,
mReport.mColumnNumber, mReport.mFlags,
mReport.mExnType);
}
return true;
}
// The innerWindowId is only required if we are going to ReportError
// below, which is gated on this condition. The inner window correctness
// check is only going to succeed when the worker is accepting events.
if (workerIsAcceptingEvents) {
aWorkerPrivate->AssertInnerWindowIsCorrect();
innerWindowId = aWorkerPrivate->WindowID();
}
}
// Don't fire this event if the JS object has been disconnected from the
// private object.
if (!workerIsAcceptingEvents) {
return true;
}
ReportError(aCx, parent, fireAtScope, aWorkerPrivate, mReport,
innerWindowId);
return true;
}
};
class TimerRunnable final : public WorkerRunnable,
public nsITimerCallback,
public nsINamed
@ -5272,47 +5052,6 @@ WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus)
return false;
}
void
WorkerErrorBase::AssignErrorBase(JSErrorBase* aReport)
{
mFilename = NS_ConvertUTF8toUTF16(aReport->filename);
mLineNumber = aReport->lineno;
mColumnNumber = aReport->column;
mErrorNumber = aReport->errorNumber;
}
void
WorkerErrorNote::AssignErrorNote(JSErrorNotes::Note* aNote)
{
WorkerErrorBase::AssignErrorBase(aNote);
xpc::ErrorNote::ErrorNoteToMessageString(aNote, mMessage);
}
void
WorkerErrorReport::AssignErrorReport(JSErrorReport* aReport)
{
WorkerErrorBase::AssignErrorBase(aReport);
xpc::ErrorReport::ErrorReportToMessageString(aReport, mMessage);
mLine.Assign(aReport->linebuf(), aReport->linebufLength());
mFlags = aReport->flags;
MOZ_ASSERT(aReport->exnType >= JSEXN_FIRST && aReport->exnType < JSEXN_LIMIT);
mExnType = JSExnType(aReport->exnType);
mMutedError = aReport->isMuted;
if (aReport->notes) {
if (!mNotes.SetLength(aReport->notes->length(), fallible)) {
return;
}
size_t i = 0;
for (auto&& note : *aReport->notes) {
mNotes.ElementAt(i).AssignErrorNote(note.get());
i++;
}
}
}
void
WorkerPrivate::ReportError(JSContext* aCx, JS::ConstUTF8CharsZ aToStringResult,
JSErrorReport* aReport)
@ -5367,8 +5106,7 @@ WorkerPrivate::ReportError(JSContext* aCx, JS::ConstUTF8CharsZ aToStringResult,
report.mErrorNumber != JSMSG_OUT_OF_MEMORY &&
JS::CurrentGlobalOrNull(aCx);
ReportErrorRunnable::ReportError(aCx, this, fireAtScope, nullptr, report, 0,
exn);
workers::ReportError(aCx, this, fireAtScope, nullptr, report, 0, exn);
mErrorHandlerRecursionCount--;
}
@ -6178,69 +5916,6 @@ EventTarget::IsOnCurrentThreadInfallible()
return mWorkerPrivate->IsOnCurrentThread();
}
void
LogErrorToConsole(const WorkerErrorReport& aReport, uint64_t aInnerWindowId)
{
AssertIsOnMainThread();
RefPtr<nsScriptErrorBase> scriptError = new nsScriptError();
NS_WARNING_ASSERTION(scriptError, "Failed to create script error!");
if (scriptError) {
nsAutoCString category("Web Worker");
if (NS_FAILED(scriptError->InitWithWindowID(aReport.mMessage,
aReport.mFilename,
aReport.mLine,
aReport.mLineNumber,
aReport.mColumnNumber,
aReport.mFlags,
category,
aInnerWindowId))) {
NS_WARNING("Failed to init script error!");
scriptError = nullptr;
}
for (size_t i = 0, len = aReport.mNotes.Length(); i < len; i++) {
const WorkerErrorNote& note = aReport.mNotes.ElementAt(i);
nsScriptErrorNote* noteObject = new nsScriptErrorNote();
noteObject->Init(note.mMessage, note.mFilename,
note.mLineNumber, note.mColumnNumber);
scriptError->AddNote(noteObject);
}
}
nsCOMPtr<nsIConsoleService> consoleService =
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
NS_WARNING_ASSERTION(consoleService, "Failed to get console service!");
if (consoleService) {
if (scriptError) {
if (NS_SUCCEEDED(consoleService->LogMessage(scriptError))) {
return;
}
NS_WARNING("LogMessage failed!");
} else if (NS_SUCCEEDED(consoleService->LogStringMessage(
aReport.mMessage.BeginReading()))) {
return;
}
NS_WARNING("LogStringMessage failed!");
}
NS_ConvertUTF16toUTF8 msg(aReport.mMessage);
NS_ConvertUTF16toUTF8 filename(aReport.mFilename);
static const char kErrorString[] = "JS error in Web Worker: %s [%s:%u]";
#ifdef ANDROID
__android_log_print(ANDROID_LOG_INFO, "Gecko", kErrorString, msg.get(),
filename.get(), aReport.mLineNumber);
#endif
fprintf(stderr, kErrorString, msg.get(), filename.get(), aReport.mLineNumber);
fflush(stderr);
}
BEGIN_WORKERS_NAMESPACE
// Force instantiation.

View File

@ -72,6 +72,7 @@ class PerformanceStorage;
class PromiseNativeHandler;
class StructuredCloneHolder;
class WorkerDebuggerGlobalScope;
class WorkerErrorReport;
class WorkerGlobalScope;
struct WorkerOptions;
} // namespace dom
@ -143,45 +144,6 @@ public:
}
};
class WorkerErrorBase {
public:
nsString mMessage;
nsString mFilename;
uint32_t mLineNumber;
uint32_t mColumnNumber;
uint32_t mErrorNumber;
WorkerErrorBase()
: mLineNumber(0),
mColumnNumber(0),
mErrorNumber(0)
{ }
void AssignErrorBase(JSErrorBase* aReport);
};
class WorkerErrorNote : public WorkerErrorBase {
public:
void AssignErrorNote(JSErrorNotes::Note* aNote);
};
class WorkerErrorReport : public WorkerErrorBase {
public:
nsString mLine;
uint32_t mFlags;
JSExnType mExnType;
bool mMutedError;
nsTArray<WorkerErrorNote> mNotes;
WorkerErrorReport()
: mFlags(0),
mExnType(JSEXN_ERR),
mMutedError(false)
{ }
void AssignErrorReport(JSErrorReport* aReport);
};
template <class Derived>
class WorkerPrivateParent : public DOMEventTargetHelper
{
@ -1656,10 +1618,6 @@ public:
}
};
// TODO: this will be removed in the next patch
void
LogErrorToConsole(const WorkerErrorReport& aReport, uint64_t aInnerWindowId);
END_WORKERS_NAMESPACE
#endif /* mozilla_dom_workers_workerprivate_h__ */

View File

@ -50,6 +50,7 @@ UNIFIED_SOURCES += [
'SharedWorker.cpp',
'WorkerDebugger.cpp',
'WorkerDebuggerManager.cpp',
'WorkerError.cpp',
'WorkerHolder.cpp',
'WorkerHolderToken.cpp',
'WorkerLoadInfo.cpp',