Bug 1691109 [wpt PR 27513] - Increase timeout duration for wpt/fetch/api/basic/keepal...
[gecko.git] / dom / workers / WorkerError.cpp
blobde10d7f6cd47945c14ecd8e319d43e7e50e04b45
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 "nsGlobalWindowOuter.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 {
62 namespace dom {
64 namespace {
66 class ReportErrorRunnable final : public WorkerDebuggeeRunnable {
67 UniquePtr<WorkerErrorReport> mReport;
69 public:
70 ReportErrorRunnable(WorkerPrivate* aWorkerPrivate,
71 UniquePtr<WorkerErrorReport> aReport)
72 : WorkerDebuggeeRunnable(aWorkerPrivate), 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 // 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);
112 return 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);
125 if (actor) {
126 actor->ErrorPropagationOnMainThread(nullptr, false);
129 } else {
130 RefPtr<ServiceWorkerManager> swm =
131 ServiceWorkerManager::GetInstance();
132 if (swm) {
133 swm->HandleError(aCx, aWorkerPrivate->GetPrincipal(),
134 aWorkerPrivate->ServiceWorkerScope(),
135 aWorkerPrivate->ScriptURL(), u""_ns, u""_ns,
136 u""_ns, 0, 0, nsIScriptError::errorFlag,
137 JSEXN_ERR);
141 return true;
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
154 // private object.
155 if (!workerIsAcceptingEvents) {
156 return true;
159 WorkerErrorReport::ReportError(aCx, parent, fireAtScope,
160 aWorkerPrivate->ParentEventTargetRef(),
161 std::move(mReport), innerWindowId);
162 return true;
166 class ReportGenericErrorRunnable final : public WorkerDebuggeeRunnable {
167 public:
168 static void CreateAndDispatch(WorkerPrivate* aWorkerPrivate) {
169 MOZ_ASSERT(aWorkerPrivate);
170 aWorkerPrivate->AssertIsOnWorkerThread();
172 RefPtr<ReportGenericErrorRunnable> runnable =
173 new ReportGenericErrorRunnable(aWorkerPrivate);
174 runnable->Dispatch();
177 private:
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(
206 nullptr, false);
207 return true;
210 if (aWorkerPrivate->IsServiceWorker()) {
211 if (ServiceWorkerParentInterceptEnabled()) {
212 RefPtr<RemoteWorkerChild> actor(
213 aWorkerPrivate->GetRemoteWorkerControllerWeakRef());
215 Unused << NS_WARN_IF(!actor);
217 if (actor) {
218 actor->ErrorPropagationOnMainThread(nullptr, false);
221 } else {
222 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
223 if (swm) {
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);
231 return true;
234 if (!aWorkerPrivate->IsAcceptingEvents()) {
235 return true;
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);
245 return true;
249 } // namespace
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)) {
278 return;
281 size_t i = 0;
282 for (auto&& note : *aReport->notes) {
283 mNotes.ElementAt(i).AssignErrorNote(note.get());
284 i++;
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
291 // (if any).
292 /* static */
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();
299 } else {
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.");
311 } else {
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;
321 if (aTarget) {
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) {
329 return;
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
339 // better.
340 if (aFireAtScope &&
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);
351 if (!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);
360 return;
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
367 // return for now.
368 // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks
369 // making this better.
370 return;
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) {
395 return;
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();
405 return;
408 // Otherwise log an error to the error console.
409 WorkerErrorReport::LogErrorToConsole(aCx, *aReport, aInnerWindowId);
412 /* static */
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));
419 ErrorData errorData(
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);
425 }));
426 LogErrorToConsole(errorData, aInnerWindowId, stack, stackGlobal);
429 /* static */
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!");
441 if (scriptError) {
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,
448 aInnerWindowId))) {
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) {
466 if (scriptError) {
467 if (NS_SUCCEEDED(consoleService->LogMessage(scriptError))) {
468 return;
470 NS_WARNING("LogMessage failed!");
471 } else if (NS_SUCCEEDED(consoleService->LogStringMessage(
472 aReport.message().BeginReading()))) {
473 return;
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]";
483 #ifdef ANDROID
484 __android_log_print(ANDROID_LOG_INFO, "Gecko", kErrorString, msg.get(),
485 filename.get(), aReport.lineNumber());
486 #endif
488 fprintf(stderr, kErrorString, msg.get(), filename.get(),
489 aReport.lineNumber());
490 fflush(stderr);
493 /* static */
494 void WorkerErrorReport::CreateAndDispatchGenericErrorRunnableToParent(
495 WorkerPrivate* aWorkerPrivate) {
496 ReportGenericErrorRunnable::CreateAndDispatch(aWorkerPrivate);
499 } // namespace dom
500 } // namespace mozilla