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/. */
7 #include "WorkerError.h"
12 #include "MainThreadUtils.h"
13 #include "WorkerPrivate.h"
14 #include "WorkerRunnable.h"
15 #include "WorkerScope.h"
16 #include "js/ComparisonOperators.h"
17 #include "js/UniquePtr.h"
18 #include "js/friend/ErrorMessages.h"
20 #include "mozilla/ArrayAlgorithm.h"
21 #include "mozilla/ArrayIterator.h"
22 #include "mozilla/Assertions.h"
23 #include "mozilla/BasicEvents.h"
24 #include "mozilla/DOMEventTargetHelper.h"
25 #include "mozilla/ErrorResult.h"
26 #include "mozilla/EventDispatcher.h"
27 #include "mozilla/RefPtr.h"
28 #include "mozilla/Span.h"
29 #include "mozilla/ThreadSafeWeakPtr.h"
30 #include "mozilla/Unused.h"
31 #include "mozilla/dom/BindingDeclarations.h"
32 #include "mozilla/dom/BindingUtils.h"
33 #include "mozilla/dom/ErrorEvent.h"
34 #include "mozilla/dom/ErrorEventBinding.h"
35 #include "mozilla/dom/Event.h"
36 #include "mozilla/dom/EventBinding.h"
37 #include "mozilla/dom/EventTarget.h"
38 #include "mozilla/dom/RemoteWorkerChild.h"
39 #include "mozilla/dom/RemoteWorkerTypes.h"
40 #include "mozilla/dom/RootedDictionary.h"
41 #include "mozilla/dom/ServiceWorkerManager.h"
42 #include "mozilla/dom/ServiceWorkerUtils.h"
43 #include "mozilla/dom/SimpleGlobalObject.h"
44 #include "mozilla/dom/Worker.h"
45 #include "mozilla/dom/WorkerCommon.h"
46 #include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
47 #include "mozilla/dom/WorkerGlobalScopeBinding.h"
48 #include "mozilla/fallible.h"
51 #include "nsGlobalWindowInner.h"
52 #include "nsIConsoleService.h"
53 #include "nsIScriptError.h"
54 #include "nsScriptError.h"
55 #include "nsServiceManagerUtils.h"
57 #include "nsWrapperCacheInlines.h"
59 #include "xpcpublic.h"
61 namespace mozilla::dom
{
65 class ReportErrorRunnable final
: public WorkerDebuggeeRunnable
{
66 UniquePtr
<WorkerErrorReport
> mReport
;
69 ReportErrorRunnable(WorkerPrivate
* aWorkerPrivate
,
70 UniquePtr
<WorkerErrorReport
> aReport
)
71 : WorkerDebuggeeRunnable(aWorkerPrivate
, "ReportErrorRunnable"),
72 mReport(std::move(aReport
)) {}
75 virtual void PostDispatch(WorkerPrivate
* aWorkerPrivate
,
76 bool aDispatchResult
) override
{
77 aWorkerPrivate
->AssertIsOnWorkerThread();
79 // Dispatch may fail if the worker was canceled, no need to report that as
80 // an error, so don't call base class PostDispatch.
83 virtual bool WorkerRun(JSContext
* aCx
,
84 WorkerPrivate
* aWorkerPrivate
) override
{
85 uint64_t innerWindowId
;
86 bool fireAtScope
= true;
88 bool workerIsAcceptingEvents
= aWorkerPrivate
->IsAcceptingEvents();
90 WorkerPrivate
* parent
= aWorkerPrivate
->GetParent();
94 AssertIsOnMainThread();
96 if (aWorkerPrivate
->IsSharedWorker()) {
97 aWorkerPrivate
->GetRemoteWorkerController()
98 ->ErrorPropagationOnMainThread(mReport
.get(),
99 /* isErrorEvent */ true);
103 // Service workers do not have a main thread parent global, so normal
104 // worker error reporting will crash. Instead, pass the error to
105 // the ServiceWorkerManager to report on any controlled documents.
106 if (aWorkerPrivate
->IsServiceWorker()) {
107 RefPtr
<RemoteWorkerChild
> actor(
108 aWorkerPrivate
->GetRemoteWorkerController());
110 Unused
<< NS_WARN_IF(!actor
);
113 actor
->ErrorPropagationOnMainThread(nullptr, false);
119 // The innerWindowId is only required if we are going to ReportError
120 // below, which is gated on this condition. The inner window correctness
121 // check is only going to succeed when the worker is accepting events.
122 if (workerIsAcceptingEvents
) {
123 aWorkerPrivate
->AssertInnerWindowIsCorrect();
124 innerWindowId
= aWorkerPrivate
->WindowID();
128 // Don't fire this event if the JS object has been disconnected from the
130 if (!workerIsAcceptingEvents
) {
134 WorkerErrorReport::ReportError(aCx
, parent
, fireAtScope
,
135 aWorkerPrivate
->ParentEventTargetRef(),
136 std::move(mReport
), innerWindowId
);
141 class ReportGenericErrorRunnable final
: public WorkerDebuggeeRunnable
{
143 static void CreateAndDispatch(WorkerPrivate
* aWorkerPrivate
) {
144 MOZ_ASSERT(aWorkerPrivate
);
145 aWorkerPrivate
->AssertIsOnWorkerThread();
147 RefPtr
<ReportGenericErrorRunnable
> runnable
=
148 new ReportGenericErrorRunnable(aWorkerPrivate
);
149 runnable
->Dispatch();
153 explicit ReportGenericErrorRunnable(WorkerPrivate
* aWorkerPrivate
)
154 : WorkerDebuggeeRunnable(aWorkerPrivate
, "ReportGenericErrorRunnable") {
155 aWorkerPrivate
->AssertIsOnWorkerThread();
158 void PostDispatch(WorkerPrivate
* aWorkerPrivate
,
159 bool aDispatchResult
) override
{
160 aWorkerPrivate
->AssertIsOnWorkerThread();
162 // Dispatch may fail if the worker was canceled, no need to report that as
163 // an error, so don't call base class PostDispatch.
166 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
167 if (!aWorkerPrivate
->IsAcceptingEvents()) {
171 if (aWorkerPrivate
->IsSharedWorker()) {
172 aWorkerPrivate
->GetRemoteWorkerController()->ErrorPropagationOnMainThread(
177 if (aWorkerPrivate
->IsServiceWorker()) {
178 RefPtr
<RemoteWorkerChild
> actor(
179 aWorkerPrivate
->GetRemoteWorkerController());
181 Unused
<< NS_WARN_IF(!actor
);
184 actor
->ErrorPropagationOnMainThread(nullptr, false);
190 RefPtr
<mozilla::dom::EventTarget
> parentEventTarget
=
191 aWorkerPrivate
->ParentEventTargetRef();
192 RefPtr
<Event
> event
=
193 Event::Constructor(parentEventTarget
, u
"error"_ns
, EventInit());
194 event
->SetTrusted(true);
196 parentEventTarget
->DispatchEvent(*event
);
203 void WorkerErrorBase::AssignErrorBase(JSErrorBase
* aReport
) {
204 CopyUTF8toUTF16(MakeStringSpan(aReport
->filename
.c_str()), mFilename
);
205 mLineNumber
= aReport
->lineno
;
206 mColumnNumber
= aReport
->column
.oneOriginValue();
207 mErrorNumber
= aReport
->errorNumber
;
210 void WorkerErrorNote::AssignErrorNote(JSErrorNotes::Note
* aNote
) {
211 WorkerErrorBase::AssignErrorBase(aNote
);
212 xpc::ErrorNote::ErrorNoteToMessageString(aNote
, mMessage
);
215 WorkerErrorReport::WorkerErrorReport()
216 : mIsWarning(false), mExnType(JSEXN_ERR
), mMutedError(false) {}
218 void WorkerErrorReport::AssignErrorReport(JSErrorReport
* aReport
) {
219 WorkerErrorBase::AssignErrorBase(aReport
);
220 xpc::ErrorReport::ErrorReportToMessageString(aReport
, mMessage
);
222 mLine
.Assign(aReport
->linebuf(), aReport
->linebufLength());
223 mIsWarning
= aReport
->isWarning();
224 MOZ_ASSERT(aReport
->exnType
>= JSEXN_FIRST
&& aReport
->exnType
< JSEXN_LIMIT
);
225 mExnType
= JSExnType(aReport
->exnType
);
226 mMutedError
= aReport
->isMuted
;
228 if (aReport
->notes
) {
229 if (!mNotes
.SetLength(aReport
->notes
->length(), fallible
)) {
234 for (auto&& note
: *aReport
->notes
) {
235 mNotes
.ElementAt(i
).AssignErrorNote(note
.get());
241 // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
242 // aTarget is the worker object that we are going to fire an error at
245 void WorkerErrorReport::ReportError(
246 JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
, bool aFireAtScope
,
247 DOMEventTargetHelper
* aTarget
, UniquePtr
<WorkerErrorReport
> aReport
,
248 uint64_t aInnerWindowId
, JS::Handle
<JS::Value
> aException
) {
249 if (aWorkerPrivate
) {
250 aWorkerPrivate
->AssertIsOnWorkerThread();
252 AssertIsOnMainThread();
255 // We should not fire error events for warnings but instead make sure that
256 // they show up in the error console.
257 if (!aReport
->mIsWarning
) {
258 // First fire an ErrorEvent at the worker.
259 RootedDictionary
<ErrorEventInit
> init(aCx
);
261 if (aReport
->mMutedError
) {
262 init
.mMessage
.AssignLiteral("Script error.");
264 init
.mMessage
= aReport
->mMessage
;
265 init
.mFilename
= aReport
->mFilename
;
266 init
.mLineno
= aReport
->mLineNumber
;
267 init
.mColno
= aReport
->mColumnNumber
;
268 init
.mError
= aException
;
271 init
.mCancelable
= true;
272 init
.mBubbles
= false;
275 RefPtr
<ErrorEvent
> event
=
276 ErrorEvent::Constructor(aTarget
, u
"error"_ns
, init
);
277 event
->SetTrusted(true);
279 bool defaultActionEnabled
=
280 aTarget
->DispatchEvent(*event
, CallerType::System
, IgnoreErrors());
281 if (!defaultActionEnabled
) {
286 // Now fire an event at the global object, but don't do that if the error
287 // code is too much recursion and this is the same script threw the error.
288 // XXXbz the interaction of this with worker errors seems kinda broken.
289 // An overrecursion in the debugger or debugger sandbox will get turned
290 // into an error event on our parent worker!
291 // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks making this
294 (aTarget
|| aReport
->mErrorNumber
!= JSMSG_OVER_RECURSED
)) {
295 JS::Rooted
<JSObject
*> global(aCx
, JS::CurrentGlobalOrNull(aCx
));
296 NS_ASSERTION(global
, "This should never be null!");
298 nsEventStatus status
= nsEventStatus_eIgnore
;
300 if (aWorkerPrivate
) {
301 RefPtr
<WorkerGlobalScope
> globalScope
;
302 UNWRAP_OBJECT(WorkerGlobalScope
, &global
, globalScope
);
305 WorkerDebuggerGlobalScope
* globalScope
= nullptr;
306 UNWRAP_OBJECT(WorkerDebuggerGlobalScope
, &global
, globalScope
);
308 MOZ_ASSERT_IF(globalScope
,
309 globalScope
->GetWrapperPreserveColor() == global
);
310 if (globalScope
|| IsWorkerDebuggerSandbox(global
)) {
311 aWorkerPrivate
->ReportErrorToDebugger(
312 aReport
->mFilename
, aReport
->mLineNumber
, aReport
->mMessage
);
316 MOZ_ASSERT(SimpleGlobalObject::SimpleGlobalType(global
) ==
317 SimpleGlobalObject::GlobalType::BindingDetail
);
318 // XXXbz We should really log this to console, but unwinding out of
319 // this stuff without ending up firing any events is ... hard. Just
321 // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks
322 // making this better.
326 MOZ_ASSERT(globalScope
->GetWrapperPreserveColor() == global
);
328 RefPtr
<ErrorEvent
> event
=
329 ErrorEvent::Constructor(aTarget
, u
"error"_ns
, init
);
330 event
->SetTrusted(true);
332 if (NS_FAILED(EventDispatcher::DispatchDOMEvent(
333 globalScope
, nullptr, event
, nullptr, &status
))) {
334 NS_WARNING("Failed to dispatch worker thread error event!");
335 status
= nsEventStatus_eIgnore
;
337 } else if (nsGlobalWindowInner
* win
= xpc::WindowOrNull(global
)) {
338 MOZ_ASSERT(NS_IsMainThread());
340 if (!win
->HandleScriptError(init
, &status
)) {
341 NS_WARNING("Failed to dispatch main thread error event!");
342 status
= nsEventStatus_eIgnore
;
346 // Was preventDefault() called?
347 if (status
== nsEventStatus_eConsumeNoDefault
) {
353 // Now fire a runnable to do the same on the parent's thread if we can.
354 if (aWorkerPrivate
) {
355 RefPtr
<ReportErrorRunnable
> runnable
=
356 new ReportErrorRunnable(aWorkerPrivate
, std::move(aReport
));
357 runnable
->Dispatch();
361 // Otherwise log an error to the error console.
362 WorkerErrorReport::LogErrorToConsole(aCx
, *aReport
, aInnerWindowId
);
366 void WorkerErrorReport::LogErrorToConsole(JSContext
* aCx
,
367 WorkerErrorReport
& aReport
,
368 uint64_t aInnerWindowId
) {
369 JS::Rooted
<JSObject
*> stack(aCx
, aReport
.ReadStack(aCx
));
370 JS::Rooted
<JSObject
*> stackGlobal(aCx
, JS::CurrentGlobalOrNull(aCx
));
373 aReport
.mIsWarning
, aReport
.mLineNumber
, aReport
.mColumnNumber
,
374 aReport
.mMessage
, aReport
.mFilename
, aReport
.mLine
,
375 TransformIntoNewArray(aReport
.mNotes
, [](const WorkerErrorNote
& note
) {
376 return ErrorDataNote(note
.mLineNumber
, note
.mColumnNumber
,
377 note
.mMessage
, note
.mFilename
);
379 LogErrorToConsole(errorData
, aInnerWindowId
, stack
, stackGlobal
);
383 void WorkerErrorReport::LogErrorToConsole(const ErrorData
& aReport
,
384 uint64_t aInnerWindowId
,
385 JS::Handle
<JSObject
*> aStack
,
386 JS::Handle
<JSObject
*> aStackGlobal
) {
387 AssertIsOnMainThread();
389 RefPtr
<nsScriptErrorBase
> scriptError
=
390 CreateScriptError(nullptr, JS::NothingHandleValue
, aStack
, aStackGlobal
);
392 NS_WARNING_ASSERTION(scriptError
, "Failed to create script error!");
395 nsAutoCString
category("Web Worker");
396 uint32_t flags
= aReport
.isWarning() ? nsIScriptError::warningFlag
397 : nsIScriptError::errorFlag
;
398 if (NS_FAILED(scriptError
->nsIScriptError::InitWithWindowID(
399 aReport
.message(), aReport
.filename(), aReport
.line(),
400 aReport
.lineNumber(), aReport
.columnNumber(), flags
, category
,
402 NS_WARNING("Failed to init script error!");
403 scriptError
= nullptr;
406 for (const ErrorDataNote
& note
: aReport
.notes()) {
407 nsScriptErrorNote
* noteObject
= new nsScriptErrorNote();
408 noteObject
->Init(note
.message(), note
.filename(), 0, note
.lineNumber(),
409 note
.columnNumber());
410 scriptError
->AddNote(noteObject
);
414 nsCOMPtr
<nsIConsoleService
> consoleService
=
415 do_GetService(NS_CONSOLESERVICE_CONTRACTID
);
416 NS_WARNING_ASSERTION(consoleService
, "Failed to get console service!");
418 if (consoleService
) {
420 if (NS_SUCCEEDED(consoleService
->LogMessage(scriptError
))) {
423 NS_WARNING("LogMessage failed!");
424 } else if (NS_SUCCEEDED(consoleService
->LogStringMessage(
425 aReport
.message().BeginReading()))) {
428 NS_WARNING("LogStringMessage failed!");
431 NS_ConvertUTF16toUTF8
msg(aReport
.message());
432 NS_ConvertUTF16toUTF8
filename(aReport
.filename());
434 static const char kErrorString
[] = "JS error in Web Worker: %s [%s:%u]";
437 __android_log_print(ANDROID_LOG_INFO
, "Gecko", kErrorString
, msg
.get(),
438 filename
.get(), aReport
.lineNumber());
441 fprintf(stderr
, kErrorString
, msg
.get(), filename
.get(),
442 aReport
.lineNumber());
447 void WorkerErrorReport::CreateAndDispatchGenericErrorRunnableToParent(
448 WorkerPrivate
* aWorkerPrivate
) {
449 ReportGenericErrorRunnable::CreateAndDispatch(aWorkerPrivate
);
452 } // namespace mozilla::dom