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 "nsGlobalWindowOuter.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
), mReport(std::move(aReport
)) {}
74 virtual void PostDispatch(WorkerPrivate
* aWorkerPrivate
,
75 bool aDispatchResult
) override
{
76 aWorkerPrivate
->AssertIsOnWorkerThread();
78 // Dispatch may fail if the worker was canceled, no need to report that as
79 // an error, so don't call base class PostDispatch.
82 virtual bool WorkerRun(JSContext
* aCx
,
83 WorkerPrivate
* aWorkerPrivate
) override
{
84 uint64_t innerWindowId
;
85 bool fireAtScope
= true;
87 bool workerIsAcceptingEvents
= aWorkerPrivate
->IsAcceptingEvents();
89 WorkerPrivate
* parent
= aWorkerPrivate
->GetParent();
93 AssertIsOnMainThread();
95 // Once a window has frozen its workers, their
96 // mMainThreadDebuggeeEventTargets should be paused, and their
97 // WorkerDebuggeeRunnables should not be being executed. The same goes for
98 // WorkerDebuggeeRunnables sent from child to parent workers, but since a
99 // frozen parent worker runs only control runnables anyway, that is taken
100 // care of naturally.
101 MOZ_ASSERT(!aWorkerPrivate
->IsFrozen());
103 // Similarly for paused windows; all its workers should have been
104 // informed. (Subworkers are unaffected by paused windows.)
105 MOZ_ASSERT(!aWorkerPrivate
->IsParentWindowPaused());
107 if (aWorkerPrivate
->IsSharedWorker()) {
108 aWorkerPrivate
->GetRemoteWorkerController()
109 ->ErrorPropagationOnMainThread(mReport
.get(),
110 /* isErrorEvent */ true);
114 // Service workers do not have a main thread parent global, so normal
115 // worker error reporting will crash. Instead, pass the error to
116 // the ServiceWorkerManager to report on any controlled documents.
117 if (aWorkerPrivate
->IsServiceWorker()) {
118 RefPtr
<RemoteWorkerChild
> actor(
119 aWorkerPrivate
->GetRemoteWorkerControllerWeakRef());
121 Unused
<< NS_WARN_IF(!actor
);
124 actor
->ErrorPropagationOnMainThread(nullptr, false);
130 // The innerWindowId is only required if we are going to ReportError
131 // below, which is gated on this condition. The inner window correctness
132 // check is only going to succeed when the worker is accepting events.
133 if (workerIsAcceptingEvents
) {
134 aWorkerPrivate
->AssertInnerWindowIsCorrect();
135 innerWindowId
= aWorkerPrivate
->WindowID();
139 // Don't fire this event if the JS object has been disconnected from the
141 if (!workerIsAcceptingEvents
) {
145 WorkerErrorReport::ReportError(aCx
, parent
, fireAtScope
,
146 aWorkerPrivate
->ParentEventTargetRef(),
147 std::move(mReport
), innerWindowId
);
152 class ReportGenericErrorRunnable final
: public WorkerDebuggeeRunnable
{
154 static void CreateAndDispatch(WorkerPrivate
* aWorkerPrivate
) {
155 MOZ_ASSERT(aWorkerPrivate
);
156 aWorkerPrivate
->AssertIsOnWorkerThread();
158 RefPtr
<ReportGenericErrorRunnable
> runnable
=
159 new ReportGenericErrorRunnable(aWorkerPrivate
);
160 runnable
->Dispatch();
164 explicit ReportGenericErrorRunnable(WorkerPrivate
* aWorkerPrivate
)
165 : WorkerDebuggeeRunnable(aWorkerPrivate
) {
166 aWorkerPrivate
->AssertIsOnWorkerThread();
169 void PostDispatch(WorkerPrivate
* aWorkerPrivate
,
170 bool aDispatchResult
) override
{
171 aWorkerPrivate
->AssertIsOnWorkerThread();
173 // Dispatch may fail if the worker was canceled, no need to report that as
174 // an error, so don't call base class PostDispatch.
177 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
178 // Once a window has frozen its workers, their
179 // mMainThreadDebuggeeEventTargets should be paused, and their
180 // WorkerDebuggeeRunnables should not be being executed. The same goes for
181 // WorkerDebuggeeRunnables sent from child to parent workers, but since a
182 // frozen parent worker runs only control runnables anyway, that is taken
183 // care of naturally.
184 MOZ_ASSERT(!aWorkerPrivate
->IsFrozen());
186 // Similarly for paused windows; all its workers should have been informed.
187 // (Subworkers are unaffected by paused windows.)
188 MOZ_ASSERT(!aWorkerPrivate
->IsParentWindowPaused());
190 if (aWorkerPrivate
->IsSharedWorker()) {
191 aWorkerPrivate
->GetRemoteWorkerController()->ErrorPropagationOnMainThread(
196 if (aWorkerPrivate
->IsServiceWorker()) {
197 RefPtr
<RemoteWorkerChild
> actor(
198 aWorkerPrivate
->GetRemoteWorkerControllerWeakRef());
200 Unused
<< NS_WARN_IF(!actor
);
203 actor
->ErrorPropagationOnMainThread(nullptr, false);
209 if (!aWorkerPrivate
->IsAcceptingEvents()) {
213 RefPtr
<mozilla::dom::EventTarget
> parentEventTarget
=
214 aWorkerPrivate
->ParentEventTargetRef();
215 RefPtr
<Event
> event
=
216 Event::Constructor(parentEventTarget
, u
"error"_ns
, EventInit());
217 event
->SetTrusted(true);
219 parentEventTarget
->DispatchEvent(*event
);
226 void WorkerErrorBase::AssignErrorBase(JSErrorBase
* aReport
) {
227 CopyUTF8toUTF16(MakeStringSpan(aReport
->filename
), mFilename
);
228 mLineNumber
= aReport
->lineno
;
229 mColumnNumber
= aReport
->column
;
230 mErrorNumber
= aReport
->errorNumber
;
233 void WorkerErrorNote::AssignErrorNote(JSErrorNotes::Note
* aNote
) {
234 WorkerErrorBase::AssignErrorBase(aNote
);
235 xpc::ErrorNote::ErrorNoteToMessageString(aNote
, mMessage
);
238 WorkerErrorReport::WorkerErrorReport()
239 : mIsWarning(false), mExnType(JSEXN_ERR
), mMutedError(false) {}
241 void WorkerErrorReport::AssignErrorReport(JSErrorReport
* aReport
) {
242 WorkerErrorBase::AssignErrorBase(aReport
);
243 xpc::ErrorReport::ErrorReportToMessageString(aReport
, mMessage
);
245 mLine
.Assign(aReport
->linebuf(), aReport
->linebufLength());
246 mIsWarning
= aReport
->isWarning();
247 MOZ_ASSERT(aReport
->exnType
>= JSEXN_FIRST
&& aReport
->exnType
< JSEXN_LIMIT
);
248 mExnType
= JSExnType(aReport
->exnType
);
249 mMutedError
= aReport
->isMuted
;
251 if (aReport
->notes
) {
252 if (!mNotes
.SetLength(aReport
->notes
->length(), fallible
)) {
257 for (auto&& note
: *aReport
->notes
) {
258 mNotes
.ElementAt(i
).AssignErrorNote(note
.get());
264 // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
265 // aTarget is the worker object that we are going to fire an error at
268 void WorkerErrorReport::ReportError(
269 JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
, bool aFireAtScope
,
270 DOMEventTargetHelper
* aTarget
, UniquePtr
<WorkerErrorReport
> aReport
,
271 uint64_t aInnerWindowId
, JS::Handle
<JS::Value
> aException
) {
272 if (aWorkerPrivate
) {
273 aWorkerPrivate
->AssertIsOnWorkerThread();
275 AssertIsOnMainThread();
278 // We should not fire error events for warnings but instead make sure that
279 // they show up in the error console.
280 if (!aReport
->mIsWarning
) {
281 // First fire an ErrorEvent at the worker.
282 RootedDictionary
<ErrorEventInit
> init(aCx
);
284 if (aReport
->mMutedError
) {
285 init
.mMessage
.AssignLiteral("Script error.");
287 init
.mMessage
= aReport
->mMessage
;
288 init
.mFilename
= aReport
->mFilename
;
289 init
.mLineno
= aReport
->mLineNumber
;
290 init
.mColno
= aReport
->mColumnNumber
;
291 init
.mError
= aException
;
294 init
.mCancelable
= true;
295 init
.mBubbles
= false;
298 RefPtr
<ErrorEvent
> event
=
299 ErrorEvent::Constructor(aTarget
, u
"error"_ns
, init
);
300 event
->SetTrusted(true);
302 bool defaultActionEnabled
=
303 aTarget
->DispatchEvent(*event
, CallerType::System
, IgnoreErrors());
304 if (!defaultActionEnabled
) {
309 // Now fire an event at the global object, but don't do that if the error
310 // code is too much recursion and this is the same script threw the error.
311 // XXXbz the interaction of this with worker errors seems kinda broken.
312 // An overrecursion in the debugger or debugger sandbox will get turned
313 // into an error event on our parent worker!
314 // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks making this
317 (aTarget
|| aReport
->mErrorNumber
!= JSMSG_OVER_RECURSED
)) {
318 JS::Rooted
<JSObject
*> global(aCx
, JS::CurrentGlobalOrNull(aCx
));
319 NS_ASSERTION(global
, "This should never be null!");
321 nsEventStatus status
= nsEventStatus_eIgnore
;
323 if (aWorkerPrivate
) {
324 RefPtr
<WorkerGlobalScope
> globalScope
;
325 UNWRAP_OBJECT(WorkerGlobalScope
, &global
, globalScope
);
328 WorkerDebuggerGlobalScope
* globalScope
= nullptr;
329 UNWRAP_OBJECT(WorkerDebuggerGlobalScope
, &global
, globalScope
);
331 MOZ_ASSERT_IF(globalScope
,
332 globalScope
->GetWrapperPreserveColor() == global
);
333 if (globalScope
|| IsWorkerDebuggerSandbox(global
)) {
334 aWorkerPrivate
->ReportErrorToDebugger(
335 aReport
->mFilename
, aReport
->mLineNumber
, aReport
->mMessage
);
339 MOZ_ASSERT(SimpleGlobalObject::SimpleGlobalType(global
) ==
340 SimpleGlobalObject::GlobalType::BindingDetail
);
341 // XXXbz We should really log this to console, but unwinding out of
342 // this stuff without ending up firing any events is ... hard. Just
344 // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks
345 // making this better.
349 MOZ_ASSERT(globalScope
->GetWrapperPreserveColor() == global
);
351 RefPtr
<ErrorEvent
> event
=
352 ErrorEvent::Constructor(aTarget
, u
"error"_ns
, init
);
353 event
->SetTrusted(true);
356 if (NS_FAILED(EventDispatcher::DispatchDOMEvent(
357 MOZ_KnownLive(ToSupports(globalScope
)), nullptr, event
, nullptr,
359 NS_WARNING("Failed to dispatch worker thread error event!");
360 status
= nsEventStatus_eIgnore
;
362 } else if (nsGlobalWindowInner
* win
= xpc::WindowOrNull(global
)) {
363 MOZ_ASSERT(NS_IsMainThread());
365 if (!win
->HandleScriptError(init
, &status
)) {
366 NS_WARNING("Failed to dispatch main thread error event!");
367 status
= nsEventStatus_eIgnore
;
371 // Was preventDefault() called?
372 if (status
== nsEventStatus_eConsumeNoDefault
) {
378 // Now fire a runnable to do the same on the parent's thread if we can.
379 if (aWorkerPrivate
) {
380 RefPtr
<ReportErrorRunnable
> runnable
=
381 new ReportErrorRunnable(aWorkerPrivate
, std::move(aReport
));
382 runnable
->Dispatch();
386 // Otherwise log an error to the error console.
387 WorkerErrorReport::LogErrorToConsole(aCx
, *aReport
, aInnerWindowId
);
391 void WorkerErrorReport::LogErrorToConsole(JSContext
* aCx
,
392 WorkerErrorReport
& aReport
,
393 uint64_t aInnerWindowId
) {
394 JS::Rooted
<JSObject
*> stack(aCx
, aReport
.ReadStack(aCx
));
395 JS::Rooted
<JSObject
*> stackGlobal(aCx
, JS::CurrentGlobalOrNull(aCx
));
398 aReport
.mIsWarning
, aReport
.mLineNumber
, aReport
.mColumnNumber
,
399 aReport
.mMessage
, aReport
.mFilename
, aReport
.mLine
,
400 TransformIntoNewArray(aReport
.mNotes
, [](const WorkerErrorNote
& note
) {
401 return ErrorDataNote(note
.mLineNumber
, note
.mColumnNumber
,
402 note
.mMessage
, note
.mFilename
);
404 LogErrorToConsole(errorData
, aInnerWindowId
, stack
, stackGlobal
);
408 void WorkerErrorReport::LogErrorToConsole(const ErrorData
& aReport
,
409 uint64_t aInnerWindowId
,
410 JS::Handle
<JSObject
*> aStack
,
411 JS::Handle
<JSObject
*> aStackGlobal
) {
412 AssertIsOnMainThread();
414 RefPtr
<nsScriptErrorBase
> scriptError
=
415 CreateScriptError(nullptr, JS::NothingHandleValue
, aStack
, aStackGlobal
);
417 NS_WARNING_ASSERTION(scriptError
, "Failed to create script error!");
420 nsAutoCString
category("Web Worker");
421 uint32_t flags
= aReport
.isWarning() ? nsIScriptError::warningFlag
422 : nsIScriptError::errorFlag
;
423 if (NS_FAILED(scriptError
->nsIScriptError::InitWithWindowID(
424 aReport
.message(), aReport
.filename(), aReport
.line(),
425 aReport
.lineNumber(), aReport
.columnNumber(), flags
, category
,
427 NS_WARNING("Failed to init script error!");
428 scriptError
= nullptr;
431 for (const ErrorDataNote
& note
: aReport
.notes()) {
432 nsScriptErrorNote
* noteObject
= new nsScriptErrorNote();
433 noteObject
->Init(note
.message(), note
.filename(), 0, note
.lineNumber(),
434 note
.columnNumber());
435 scriptError
->AddNote(noteObject
);
439 nsCOMPtr
<nsIConsoleService
> consoleService
=
440 do_GetService(NS_CONSOLESERVICE_CONTRACTID
);
441 NS_WARNING_ASSERTION(consoleService
, "Failed to get console service!");
443 if (consoleService
) {
445 if (NS_SUCCEEDED(consoleService
->LogMessage(scriptError
))) {
448 NS_WARNING("LogMessage failed!");
449 } else if (NS_SUCCEEDED(consoleService
->LogStringMessage(
450 aReport
.message().BeginReading()))) {
453 NS_WARNING("LogStringMessage failed!");
456 NS_ConvertUTF16toUTF8
msg(aReport
.message());
457 NS_ConvertUTF16toUTF8
filename(aReport
.filename());
459 static const char kErrorString
[] = "JS error in Web Worker: %s [%s:%u]";
462 __android_log_print(ANDROID_LOG_INFO
, "Gecko", kErrorString
, msg
.get(),
463 filename
.get(), aReport
.lineNumber());
466 fprintf(stderr
, kErrorString
, msg
.get(), filename
.get(),
467 aReport
.lineNumber());
472 void WorkerErrorReport::CreateAndDispatchGenericErrorRunnableToParent(
473 WorkerPrivate
* aWorkerPrivate
) {
474 ReportGenericErrorRunnable::CreateAndDispatch(aWorkerPrivate
);
477 } // namespace mozilla::dom