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"
66 class ReportErrorRunnable final
: public WorkerDebuggeeRunnable
{
67 UniquePtr
<WorkerErrorReport
> mReport
;
70 ReportErrorRunnable(WorkerPrivate
* aWorkerPrivate
,
71 UniquePtr
<WorkerErrorReport
> aReport
)
72 : WorkerDebuggeeRunnable(aWorkerPrivate
), 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 // Once a window has frozen its workers, their
97 // mMainThreadDebuggeeEventTargets should be paused, and their
98 // WorkerDebuggeeRunnables should not be being executed. The same goes for
99 // WorkerDebuggeeRunnables sent from child to parent workers, but since a
100 // frozen parent worker runs only control runnables anyway, that is taken
101 // care of naturally.
102 MOZ_ASSERT(!aWorkerPrivate
->IsFrozen());
104 // Similarly for paused windows; all its workers should have been
105 // informed. (Subworkers are unaffected by paused windows.)
106 MOZ_ASSERT(!aWorkerPrivate
->IsParentWindowPaused());
108 if (aWorkerPrivate
->IsSharedWorker()) {
109 aWorkerPrivate
->GetRemoteWorkerController()
110 ->ErrorPropagationOnMainThread(mReport
.get(),
111 /* isErrorEvent */ true);
115 // Service workers do not have a main thread parent global, so normal
116 // worker error reporting will crash. Instead, pass the error to
117 // the ServiceWorkerManager to report on any controlled documents.
118 if (aWorkerPrivate
->IsServiceWorker()) {
119 if (ServiceWorkerParentInterceptEnabled()) {
120 RefPtr
<RemoteWorkerChild
> actor(
121 aWorkerPrivate
->GetRemoteWorkerControllerWeakRef());
123 Unused
<< NS_WARN_IF(!actor
);
126 actor
->ErrorPropagationOnMainThread(nullptr, false);
130 RefPtr
<ServiceWorkerManager
> swm
=
131 ServiceWorkerManager::GetInstance();
133 swm
->HandleError(aCx
, aWorkerPrivate
->GetPrincipal(),
134 aWorkerPrivate
->ServiceWorkerScope(),
135 aWorkerPrivate
->ScriptURL(), u
""_ns
, u
""_ns
,
136 u
""_ns
, 0, 0, nsIScriptError::errorFlag
,
144 // The innerWindowId is only required if we are going to ReportError
145 // below, which is gated on this condition. The inner window correctness
146 // check is only going to succeed when the worker is accepting events.
147 if (workerIsAcceptingEvents
) {
148 aWorkerPrivate
->AssertInnerWindowIsCorrect();
149 innerWindowId
= aWorkerPrivate
->WindowID();
153 // Don't fire this event if the JS object has been disconnected from the
155 if (!workerIsAcceptingEvents
) {
159 WorkerErrorReport::ReportError(aCx
, parent
, fireAtScope
,
160 aWorkerPrivate
->ParentEventTargetRef(),
161 std::move(mReport
), innerWindowId
);
166 class ReportGenericErrorRunnable final
: public WorkerDebuggeeRunnable
{
168 static void CreateAndDispatch(WorkerPrivate
* aWorkerPrivate
) {
169 MOZ_ASSERT(aWorkerPrivate
);
170 aWorkerPrivate
->AssertIsOnWorkerThread();
172 RefPtr
<ReportGenericErrorRunnable
> runnable
=
173 new ReportGenericErrorRunnable(aWorkerPrivate
);
174 runnable
->Dispatch();
178 explicit ReportGenericErrorRunnable(WorkerPrivate
* aWorkerPrivate
)
179 : WorkerDebuggeeRunnable(aWorkerPrivate
) {
180 aWorkerPrivate
->AssertIsOnWorkerThread();
183 void PostDispatch(WorkerPrivate
* aWorkerPrivate
,
184 bool aDispatchResult
) override
{
185 aWorkerPrivate
->AssertIsOnWorkerThread();
187 // Dispatch may fail if the worker was canceled, no need to report that as
188 // an error, so don't call base class PostDispatch.
191 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
192 // Once a window has frozen its workers, their
193 // mMainThreadDebuggeeEventTargets should be paused, and their
194 // WorkerDebuggeeRunnables should not be being executed. The same goes for
195 // WorkerDebuggeeRunnables sent from child to parent workers, but since a
196 // frozen parent worker runs only control runnables anyway, that is taken
197 // care of naturally.
198 MOZ_ASSERT(!aWorkerPrivate
->IsFrozen());
200 // Similarly for paused windows; all its workers should have been informed.
201 // (Subworkers are unaffected by paused windows.)
202 MOZ_ASSERT(!aWorkerPrivate
->IsParentWindowPaused());
204 if (aWorkerPrivate
->IsSharedWorker()) {
205 aWorkerPrivate
->GetRemoteWorkerController()->ErrorPropagationOnMainThread(
210 if (aWorkerPrivate
->IsServiceWorker()) {
211 if (ServiceWorkerParentInterceptEnabled()) {
212 RefPtr
<RemoteWorkerChild
> actor(
213 aWorkerPrivate
->GetRemoteWorkerControllerWeakRef());
215 Unused
<< NS_WARN_IF(!actor
);
218 actor
->ErrorPropagationOnMainThread(nullptr, false);
222 RefPtr
<ServiceWorkerManager
> swm
= ServiceWorkerManager::GetInstance();
224 swm
->HandleError(aCx
, aWorkerPrivate
->GetPrincipal(),
225 aWorkerPrivate
->ServiceWorkerScope(),
226 aWorkerPrivate
->ScriptURL(), u
""_ns
, u
""_ns
, u
""_ns
,
227 0, 0, nsIScriptError::errorFlag
, JSEXN_ERR
);
234 if (!aWorkerPrivate
->IsAcceptingEvents()) {
238 RefPtr
<mozilla::dom::EventTarget
> parentEventTarget
=
239 aWorkerPrivate
->ParentEventTargetRef();
240 RefPtr
<Event
> event
=
241 Event::Constructor(parentEventTarget
, u
"error"_ns
, EventInit());
242 event
->SetTrusted(true);
244 parentEventTarget
->DispatchEvent(*event
);
251 void WorkerErrorBase::AssignErrorBase(JSErrorBase
* aReport
) {
252 CopyUTF8toUTF16(MakeStringSpan(aReport
->filename
), mFilename
);
253 mLineNumber
= aReport
->lineno
;
254 mColumnNumber
= aReport
->column
;
255 mErrorNumber
= aReport
->errorNumber
;
258 void WorkerErrorNote::AssignErrorNote(JSErrorNotes::Note
* aNote
) {
259 WorkerErrorBase::AssignErrorBase(aNote
);
260 xpc::ErrorNote::ErrorNoteToMessageString(aNote
, mMessage
);
263 WorkerErrorReport::WorkerErrorReport()
264 : mIsWarning(false), mExnType(JSEXN_ERR
), mMutedError(false) {}
266 void WorkerErrorReport::AssignErrorReport(JSErrorReport
* aReport
) {
267 WorkerErrorBase::AssignErrorBase(aReport
);
268 xpc::ErrorReport::ErrorReportToMessageString(aReport
, mMessage
);
270 mLine
.Assign(aReport
->linebuf(), aReport
->linebufLength());
271 mIsWarning
= aReport
->isWarning();
272 MOZ_ASSERT(aReport
->exnType
>= JSEXN_FIRST
&& aReport
->exnType
< JSEXN_LIMIT
);
273 mExnType
= JSExnType(aReport
->exnType
);
274 mMutedError
= aReport
->isMuted
;
276 if (aReport
->notes
) {
277 if (!mNotes
.SetLength(aReport
->notes
->length(), fallible
)) {
282 for (auto&& note
: *aReport
->notes
) {
283 mNotes
.ElementAt(i
).AssignErrorNote(note
.get());
289 // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
290 // aTarget is the worker object that we are going to fire an error at
293 void WorkerErrorReport::ReportError(
294 JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
, bool aFireAtScope
,
295 DOMEventTargetHelper
* aTarget
, UniquePtr
<WorkerErrorReport
> aReport
,
296 uint64_t aInnerWindowId
, JS::Handle
<JS::Value
> aException
) {
297 if (aWorkerPrivate
) {
298 aWorkerPrivate
->AssertIsOnWorkerThread();
300 AssertIsOnMainThread();
303 // We should not fire error events for warnings but instead make sure that
304 // they show up in the error console.
305 if (!aReport
->mIsWarning
) {
306 // First fire an ErrorEvent at the worker.
307 RootedDictionary
<ErrorEventInit
> init(aCx
);
309 if (aReport
->mMutedError
) {
310 init
.mMessage
.AssignLiteral("Script error.");
312 init
.mMessage
= aReport
->mMessage
;
313 init
.mFilename
= aReport
->mFilename
;
314 init
.mLineno
= aReport
->mLineNumber
;
315 init
.mError
= aException
;
318 init
.mCancelable
= true;
319 init
.mBubbles
= false;
322 RefPtr
<ErrorEvent
> event
=
323 ErrorEvent::Constructor(aTarget
, u
"error"_ns
, init
);
324 event
->SetTrusted(true);
326 bool defaultActionEnabled
=
327 aTarget
->DispatchEvent(*event
, CallerType::System
, IgnoreErrors());
328 if (!defaultActionEnabled
) {
333 // Now fire an event at the global object, but don't do that if the error
334 // code is too much recursion and this is the same script threw the error.
335 // XXXbz the interaction of this with worker errors seems kinda broken.
336 // An overrecursion in the debugger or debugger sandbox will get turned
337 // into an error event on our parent worker!
338 // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks making this
341 (aTarget
|| aReport
->mErrorNumber
!= JSMSG_OVER_RECURSED
)) {
342 JS::Rooted
<JSObject
*> global(aCx
, JS::CurrentGlobalOrNull(aCx
));
343 NS_ASSERTION(global
, "This should never be null!");
345 nsEventStatus status
= nsEventStatus_eIgnore
;
347 if (aWorkerPrivate
) {
348 WorkerGlobalScope
* globalScope
= nullptr;
349 UNWRAP_OBJECT(WorkerGlobalScope
, &global
, globalScope
);
352 WorkerDebuggerGlobalScope
* globalScope
= nullptr;
353 UNWRAP_OBJECT(WorkerDebuggerGlobalScope
, &global
, globalScope
);
355 MOZ_ASSERT_IF(globalScope
,
356 globalScope
->GetWrapperPreserveColor() == global
);
357 if (globalScope
|| IsWorkerDebuggerSandbox(global
)) {
358 aWorkerPrivate
->ReportErrorToDebugger(
359 aReport
->mFilename
, aReport
->mLineNumber
, aReport
->mMessage
);
363 MOZ_ASSERT(SimpleGlobalObject::SimpleGlobalType(global
) ==
364 SimpleGlobalObject::GlobalType::BindingDetail
);
365 // XXXbz We should really log this to console, but unwinding out of
366 // this stuff without ending up firing any events is ... hard. Just
368 // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks
369 // making this better.
373 MOZ_ASSERT(globalScope
->GetWrapperPreserveColor() == global
);
375 RefPtr
<ErrorEvent
> event
=
376 ErrorEvent::Constructor(aTarget
, u
"error"_ns
, init
);
377 event
->SetTrusted(true);
379 if (NS_FAILED(EventDispatcher::DispatchDOMEvent(
380 ToSupports(globalScope
), nullptr, event
, nullptr, &status
))) {
381 NS_WARNING("Failed to dispatch worker thread error event!");
382 status
= nsEventStatus_eIgnore
;
384 } else if (nsGlobalWindowInner
* win
= xpc::WindowOrNull(global
)) {
385 MOZ_ASSERT(NS_IsMainThread());
387 if (!win
->HandleScriptError(init
, &status
)) {
388 NS_WARNING("Failed to dispatch main thread error event!");
389 status
= nsEventStatus_eIgnore
;
393 // Was preventDefault() called?
394 if (status
== nsEventStatus_eConsumeNoDefault
) {
400 // Now fire a runnable to do the same on the parent's thread if we can.
401 if (aWorkerPrivate
) {
402 RefPtr
<ReportErrorRunnable
> runnable
=
403 new ReportErrorRunnable(aWorkerPrivate
, std::move(aReport
));
404 runnable
->Dispatch();
408 // Otherwise log an error to the error console.
409 WorkerErrorReport::LogErrorToConsole(aCx
, *aReport
, aInnerWindowId
);
413 void WorkerErrorReport::LogErrorToConsole(JSContext
* aCx
,
414 WorkerErrorReport
& aReport
,
415 uint64_t aInnerWindowId
) {
416 JS::RootedObject
stack(aCx
, aReport
.ReadStack(aCx
));
417 JS::RootedObject
stackGlobal(aCx
, JS::CurrentGlobalOrNull(aCx
));
420 aReport
.mIsWarning
, aReport
.mLineNumber
, aReport
.mColumnNumber
,
421 aReport
.mMessage
, aReport
.mFilename
, aReport
.mLine
,
422 TransformIntoNewArray(aReport
.mNotes
, [](const WorkerErrorNote
& note
) {
423 return ErrorDataNote(note
.mLineNumber
, note
.mColumnNumber
,
424 note
.mMessage
, note
.mFilename
);
426 LogErrorToConsole(errorData
, aInnerWindowId
, stack
, stackGlobal
);
430 void WorkerErrorReport::LogErrorToConsole(const ErrorData
& aReport
,
431 uint64_t aInnerWindowId
,
432 JS::HandleObject aStack
,
433 JS::HandleObject aStackGlobal
) {
434 AssertIsOnMainThread();
436 RefPtr
<nsScriptErrorBase
> scriptError
=
437 CreateScriptError(nullptr, JS::NothingHandleValue
, aStack
, aStackGlobal
);
439 NS_WARNING_ASSERTION(scriptError
, "Failed to create script error!");
442 nsAutoCString
category("Web Worker");
443 uint32_t flags
= aReport
.isWarning() ? nsIScriptError::warningFlag
444 : nsIScriptError::errorFlag
;
445 if (NS_FAILED(scriptError
->nsIScriptError::InitWithWindowID(
446 aReport
.message(), aReport
.filename(), aReport
.line(),
447 aReport
.lineNumber(), aReport
.columnNumber(), flags
, category
,
449 NS_WARNING("Failed to init script error!");
450 scriptError
= nullptr;
453 for (const ErrorDataNote
& note
: aReport
.notes()) {
454 nsScriptErrorNote
* noteObject
= new nsScriptErrorNote();
455 noteObject
->Init(note
.message(), note
.filename(), 0, note
.lineNumber(),
456 note
.columnNumber());
457 scriptError
->AddNote(noteObject
);
461 nsCOMPtr
<nsIConsoleService
> consoleService
=
462 do_GetService(NS_CONSOLESERVICE_CONTRACTID
);
463 NS_WARNING_ASSERTION(consoleService
, "Failed to get console service!");
465 if (consoleService
) {
467 if (NS_SUCCEEDED(consoleService
->LogMessage(scriptError
))) {
470 NS_WARNING("LogMessage failed!");
471 } else if (NS_SUCCEEDED(consoleService
->LogStringMessage(
472 aReport
.message().BeginReading()))) {
475 NS_WARNING("LogStringMessage failed!");
478 NS_ConvertUTF16toUTF8
msg(aReport
.message());
479 NS_ConvertUTF16toUTF8
filename(aReport
.filename());
481 static const char kErrorString
[] = "JS error in Web Worker: %s [%s:%u]";
484 __android_log_print(ANDROID_LOG_INFO
, "Gecko", kErrorString
, msg
.get(),
485 filename
.get(), aReport
.lineNumber());
488 fprintf(stderr
, kErrorString
, msg
.get(), filename
.get(),
489 aReport
.lineNumber());
494 void WorkerErrorReport::CreateAndDispatchGenericErrorRunnableToParent(
495 WorkerPrivate
* aWorkerPrivate
) {
496 ReportGenericErrorRunnable::CreateAndDispatch(aWorkerPrivate
);
500 } // namespace mozilla