Bug 1874684 - Part 38: Enable now passing tests. r=allstarschh
[gecko.git] / dom / workers / WorkerError.cpp
blob1b7559921150ce27e99d34a3218d294cfa39cdd3
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"
9 #include <stdio.h>
10 #include <algorithm>
11 #include <utility>
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"
19 #include "jsapi.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"
49 #include "nsCOMPtr.h"
50 #include "nsDebug.h"
51 #include "nsGlobalWindowInner.h"
52 #include "nsIConsoleService.h"
53 #include "nsIScriptError.h"
54 #include "nsScriptError.h"
55 #include "nsServiceManagerUtils.h"
56 #include "nsString.h"
57 #include "nsWrapperCacheInlines.h"
58 #include "nscore.h"
59 #include "xpcpublic.h"
61 namespace mozilla::dom {
63 namespace {
65 class ReportErrorRunnable final : public WorkerDebuggeeRunnable {
66 UniquePtr<WorkerErrorReport> mReport;
68 public:
69 ReportErrorRunnable(WorkerPrivate* aWorkerPrivate,
70 UniquePtr<WorkerErrorReport> aReport)
71 : WorkerDebuggeeRunnable(aWorkerPrivate, "ReportErrorRunnable"),
72 mReport(std::move(aReport)) {}
74 private:
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();
91 if (parent) {
92 innerWindowId = 0;
93 } else {
94 AssertIsOnMainThread();
96 if (aWorkerPrivate->IsSharedWorker()) {
97 aWorkerPrivate->GetRemoteWorkerController()
98 ->ErrorPropagationOnMainThread(mReport.get(),
99 /* isErrorEvent */ true);
100 return 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);
112 if (actor) {
113 actor->ErrorPropagationOnMainThread(nullptr, false);
116 return true;
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
129 // private object.
130 if (!workerIsAcceptingEvents) {
131 return true;
134 WorkerErrorReport::ReportError(aCx, parent, fireAtScope,
135 aWorkerPrivate->ParentEventTargetRef(),
136 std::move(mReport), innerWindowId);
137 return true;
141 class ReportGenericErrorRunnable final : public WorkerDebuggeeRunnable {
142 public:
143 static void CreateAndDispatch(WorkerPrivate* aWorkerPrivate) {
144 MOZ_ASSERT(aWorkerPrivate);
145 aWorkerPrivate->AssertIsOnWorkerThread();
147 RefPtr<ReportGenericErrorRunnable> runnable =
148 new ReportGenericErrorRunnable(aWorkerPrivate);
149 runnable->Dispatch();
152 private:
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()) {
168 return true;
171 if (aWorkerPrivate->IsSharedWorker()) {
172 aWorkerPrivate->GetRemoteWorkerController()->ErrorPropagationOnMainThread(
173 nullptr, false);
174 return true;
177 if (aWorkerPrivate->IsServiceWorker()) {
178 RefPtr<RemoteWorkerChild> actor(
179 aWorkerPrivate->GetRemoteWorkerController());
181 Unused << NS_WARN_IF(!actor);
183 if (actor) {
184 actor->ErrorPropagationOnMainThread(nullptr, false);
187 return true;
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);
197 return true;
201 } // namespace
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)) {
230 return;
233 size_t i = 0;
234 for (auto&& note : *aReport->notes) {
235 mNotes.ElementAt(i).AssignErrorNote(note.get());
236 i++;
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
243 // (if any).
244 /* static */
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();
251 } else {
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.");
263 } else {
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;
274 if (aTarget) {
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) {
282 return;
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
292 // better.
293 if (aFireAtScope &&
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);
304 if (!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);
313 return;
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
320 // return for now.
321 // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks
322 // making this better.
323 return;
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) {
348 return;
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();
358 return;
361 // Otherwise log an error to the error console.
362 WorkerErrorReport::LogErrorToConsole(aCx, *aReport, aInnerWindowId);
365 /* static */
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));
372 ErrorData errorData(
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);
378 }));
379 LogErrorToConsole(errorData, aInnerWindowId, stack, stackGlobal);
382 /* static */
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!");
394 if (scriptError) {
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,
401 aInnerWindowId))) {
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) {
419 if (scriptError) {
420 if (NS_SUCCEEDED(consoleService->LogMessage(scriptError))) {
421 return;
423 NS_WARNING("LogMessage failed!");
424 } else if (NS_SUCCEEDED(consoleService->LogStringMessage(
425 aReport.message().BeginReading()))) {
426 return;
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]";
436 #ifdef ANDROID
437 __android_log_print(ANDROID_LOG_INFO, "Gecko", kErrorString, msg.get(),
438 filename.get(), aReport.lineNumber());
439 #endif
441 fprintf(stderr, kErrorString, msg.get(), filename.get(),
442 aReport.lineNumber());
443 fflush(stderr);
446 /* static */
447 void WorkerErrorReport::CreateAndDispatchGenericErrorRunnableToParent(
448 WorkerPrivate* aWorkerPrivate) {
449 ReportGenericErrorRunnable::CreateAndDispatch(aWorkerPrivate);
452 } // namespace mozilla::dom