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/. */
6 
7 #include "WorkerError.h"
8 
9 #include "mozilla/DOMEventTargetHelper.h"
10 #include "mozilla/dom/ErrorEvent.h"
11 #include "mozilla/dom/ErrorEventBinding.h"
12 #include "mozilla/dom/RemoteWorkerChild.h"
13 #include "mozilla/dom/ServiceWorkerManager.h"
14 #include "mozilla/dom/ServiceWorkerUtils.h"
15 #include "mozilla/dom/SimpleGlobalObject.h"
16 #include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
17 #include "mozilla/dom/WorkerGlobalScopeBinding.h"
18 #include "mozilla/EventDispatcher.h"
19 #include "nsGlobalWindowInner.h"
20 #include "nsIConsoleService.h"
21 #include "nsScriptError.h"
22 #include "WorkerRunnable.h"
23 #include "WorkerPrivate.h"
24 #include "WorkerScope.h"
25 
26 namespace mozilla {
27 namespace dom {
28 
29 namespace {
30 
31 class ReportErrorRunnable final : public WorkerDebuggeeRunnable {
32   UniquePtr<WorkerErrorReport> mReport;
33 
34  public:
ReportErrorRunnable(WorkerPrivate * aWorkerPrivate,UniquePtr<WorkerErrorReport> aReport)35   ReportErrorRunnable(WorkerPrivate* aWorkerPrivate,
36                       UniquePtr<WorkerErrorReport> aReport)
37       : WorkerDebuggeeRunnable(aWorkerPrivate), mReport(std::move(aReport)) {}
38 
39  private:
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)40   virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
41                             bool aDispatchResult) override {
42     aWorkerPrivate->AssertIsOnWorkerThread();
43 
44     // Dispatch may fail if the worker was canceled, no need to report that as
45     // an error, so don't call base class PostDispatch.
46   }
47 
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)48   virtual bool WorkerRun(JSContext* aCx,
49                          WorkerPrivate* aWorkerPrivate) override {
50     uint64_t innerWindowId;
51     bool fireAtScope = true;
52 
53     bool workerIsAcceptingEvents = aWorkerPrivate->IsAcceptingEvents();
54 
55     WorkerPrivate* parent = aWorkerPrivate->GetParent();
56     if (parent) {
57       innerWindowId = 0;
58     } else {
59       AssertIsOnMainThread();
60 
61       // Once a window has frozen its workers, their
62       // mMainThreadDebuggeeEventTargets should be paused, and their
63       // WorkerDebuggeeRunnables should not be being executed. The same goes for
64       // WorkerDebuggeeRunnables sent from child to parent workers, but since a
65       // frozen parent worker runs only control runnables anyway, that is taken
66       // care of naturally.
67       MOZ_ASSERT(!aWorkerPrivate->IsFrozen());
68 
69       // Similarly for paused windows; all its workers should have been
70       // informed. (Subworkers are unaffected by paused windows.)
71       MOZ_ASSERT(!aWorkerPrivate->IsParentWindowPaused());
72 
73       if (aWorkerPrivate->IsSharedWorker()) {
74         aWorkerPrivate->GetRemoteWorkerController()
75             ->ErrorPropagationOnMainThread(mReport.get(),
76                                            /* isErrorEvent */ true);
77         return true;
78       }
79 
80       // Service workers do not have a main thread parent global, so normal
81       // worker error reporting will crash.  Instead, pass the error to
82       // the ServiceWorkerManager to report on any controlled documents.
83       if (aWorkerPrivate->IsServiceWorker()) {
84         if (ServiceWorkerParentInterceptEnabled()) {
85           RefPtr<RemoteWorkerChild> actor(
86               aWorkerPrivate->GetRemoteWorkerControllerWeakRef());
87 
88           Unused << NS_WARN_IF(!actor);
89 
90           if (actor) {
91             actor->ErrorPropagationOnMainThread(nullptr, false);
92           }
93 
94         } else {
95           RefPtr<ServiceWorkerManager> swm =
96               ServiceWorkerManager::GetInstance();
97           if (swm) {
98             swm->HandleError(aCx, aWorkerPrivate->GetPrincipal(),
99                              aWorkerPrivate->ServiceWorkerScope(),
100                              aWorkerPrivate->ScriptURL(), EmptyString(),
101                              EmptyString(), EmptyString(), 0, 0,
102                              nsIScriptError::errorFlag, JSEXN_ERR);
103           }
104         }
105 
106         return true;
107       }
108 
109       // The innerWindowId is only required if we are going to ReportError
110       // below, which is gated on this condition. The inner window correctness
111       // check is only going to succeed when the worker is accepting events.
112       if (workerIsAcceptingEvents) {
113         aWorkerPrivate->AssertInnerWindowIsCorrect();
114         innerWindowId = aWorkerPrivate->WindowID();
115       }
116     }
117 
118     // Don't fire this event if the JS object has been disconnected from the
119     // private object.
120     if (!workerIsAcceptingEvents) {
121       return true;
122     }
123 
124     WorkerErrorReport::ReportError(aCx, parent, fireAtScope,
125                                    aWorkerPrivate->ParentEventTargetRef(),
126                                    std::move(mReport), innerWindowId);
127     return true;
128   }
129 };
130 
131 class ReportGenericErrorRunnable final : public WorkerDebuggeeRunnable {
132  public:
CreateAndDispatch(WorkerPrivate * aWorkerPrivate)133   static void CreateAndDispatch(WorkerPrivate* aWorkerPrivate) {
134     MOZ_ASSERT(aWorkerPrivate);
135     aWorkerPrivate->AssertIsOnWorkerThread();
136 
137     RefPtr<ReportGenericErrorRunnable> runnable =
138         new ReportGenericErrorRunnable(aWorkerPrivate);
139     runnable->Dispatch();
140   }
141 
142  private:
ReportGenericErrorRunnable(WorkerPrivate * aWorkerPrivate)143   explicit ReportGenericErrorRunnable(WorkerPrivate* aWorkerPrivate)
144       : WorkerDebuggeeRunnable(aWorkerPrivate) {
145     aWorkerPrivate->AssertIsOnWorkerThread();
146   }
147 
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)148   void PostDispatch(WorkerPrivate* aWorkerPrivate,
149                     bool aDispatchResult) override {
150     aWorkerPrivate->AssertIsOnWorkerThread();
151 
152     // Dispatch may fail if the worker was canceled, no need to report that as
153     // an error, so don't call base class PostDispatch.
154   }
155 
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)156   bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
157     // Once a window has frozen its workers, their
158     // mMainThreadDebuggeeEventTargets should be paused, and their
159     // WorkerDebuggeeRunnables should not be being executed. The same goes for
160     // WorkerDebuggeeRunnables sent from child to parent workers, but since a
161     // frozen parent worker runs only control runnables anyway, that is taken
162     // care of naturally.
163     MOZ_ASSERT(!aWorkerPrivate->IsFrozen());
164 
165     // Similarly for paused windows; all its workers should have been informed.
166     // (Subworkers are unaffected by paused windows.)
167     MOZ_ASSERT(!aWorkerPrivate->IsParentWindowPaused());
168 
169     if (aWorkerPrivate->IsSharedWorker()) {
170       aWorkerPrivate->GetRemoteWorkerController()->ErrorPropagationOnMainThread(
171           nullptr, false);
172       return true;
173     }
174 
175     if (aWorkerPrivate->IsServiceWorker()) {
176       if (ServiceWorkerParentInterceptEnabled()) {
177         RefPtr<RemoteWorkerChild> actor(
178             aWorkerPrivate->GetRemoteWorkerControllerWeakRef());
179 
180         Unused << NS_WARN_IF(!actor);
181 
182         if (actor) {
183           actor->ErrorPropagationOnMainThread(nullptr, false);
184         }
185 
186       } else {
187         RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
188         if (swm) {
189           swm->HandleError(aCx, aWorkerPrivate->GetPrincipal(),
190                            aWorkerPrivate->ServiceWorkerScope(),
191                            aWorkerPrivate->ScriptURL(), EmptyString(),
192                            EmptyString(), EmptyString(), 0, 0,
193                            nsIScriptError::errorFlag, JSEXN_ERR);
194         }
195       }
196 
197       return true;
198     }
199 
200     if (!aWorkerPrivate->IsAcceptingEvents()) {
201       return true;
202     }
203 
204     RefPtr<mozilla::dom::EventTarget> parentEventTarget =
205         aWorkerPrivate->ParentEventTargetRef();
206     RefPtr<Event> event = Event::Constructor(
207         parentEventTarget, NS_LITERAL_STRING("error"), EventInit());
208     event->SetTrusted(true);
209 
210     parentEventTarget->DispatchEvent(*event);
211     return true;
212   }
213 };
214 
215 }  // namespace
216 
AssignErrorBase(JSErrorBase * aReport)217 void WorkerErrorBase::AssignErrorBase(JSErrorBase* aReport) {
218   mFilename = NS_ConvertUTF8toUTF16(aReport->filename);
219   mLineNumber = aReport->lineno;
220   mColumnNumber = aReport->column;
221   mErrorNumber = aReport->errorNumber;
222 }
223 
AssignErrorNote(JSErrorNotes::Note * aNote)224 void WorkerErrorNote::AssignErrorNote(JSErrorNotes::Note* aNote) {
225   WorkerErrorBase::AssignErrorBase(aNote);
226   xpc::ErrorNote::ErrorNoteToMessageString(aNote, mMessage);
227 }
228 
WorkerErrorReport()229 WorkerErrorReport::WorkerErrorReport()
230     : mIsWarning(false), mExnType(JSEXN_ERR), mMutedError(false) {}
231 
AssignErrorReport(JSErrorReport * aReport)232 void WorkerErrorReport::AssignErrorReport(JSErrorReport* aReport) {
233   WorkerErrorBase::AssignErrorBase(aReport);
234   xpc::ErrorReport::ErrorReportToMessageString(aReport, mMessage);
235 
236   mLine.Assign(aReport->linebuf(), aReport->linebufLength());
237   mIsWarning = aReport->isWarning();
238   MOZ_ASSERT(aReport->exnType >= JSEXN_FIRST && aReport->exnType < JSEXN_LIMIT);
239   mExnType = JSExnType(aReport->exnType);
240   mMutedError = aReport->isMuted;
241 
242   if (aReport->notes) {
243     if (!mNotes.SetLength(aReport->notes->length(), fallible)) {
244       return;
245     }
246 
247     size_t i = 0;
248     for (auto&& note : *aReport->notes) {
249       mNotes.ElementAt(i).AssignErrorNote(note.get());
250       i++;
251     }
252   }
253 }
254 
255 // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
256 // aTarget is the worker object that we are going to fire an error at
257 // (if any).
258 /* static */
ReportError(JSContext * aCx,WorkerPrivate * aWorkerPrivate,bool aFireAtScope,DOMEventTargetHelper * aTarget,UniquePtr<WorkerErrorReport> aReport,uint64_t aInnerWindowId,JS::Handle<JS::Value> aException)259 void WorkerErrorReport::ReportError(
260     JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aFireAtScope,
261     DOMEventTargetHelper* aTarget, UniquePtr<WorkerErrorReport> aReport,
262     uint64_t aInnerWindowId, JS::Handle<JS::Value> aException) {
263   if (aWorkerPrivate) {
264     aWorkerPrivate->AssertIsOnWorkerThread();
265   } else {
266     AssertIsOnMainThread();
267   }
268 
269   // We should not fire error events for warnings but instead make sure that
270   // they show up in the error console.
271   if (!aReport->mIsWarning) {
272     // First fire an ErrorEvent at the worker.
273     RootedDictionary<ErrorEventInit> init(aCx);
274 
275     if (aReport->mMutedError) {
276       init.mMessage.AssignLiteral("Script error.");
277     } else {
278       init.mMessage = aReport->mMessage;
279       init.mFilename = aReport->mFilename;
280       init.mLineno = aReport->mLineNumber;
281       init.mError = aException;
282     }
283 
284     init.mCancelable = true;
285     init.mBubbles = false;
286 
287     if (aTarget) {
288       RefPtr<ErrorEvent> event =
289           ErrorEvent::Constructor(aTarget, NS_LITERAL_STRING("error"), init);
290       event->SetTrusted(true);
291 
292       bool defaultActionEnabled =
293           aTarget->DispatchEvent(*event, CallerType::System, IgnoreErrors());
294       if (!defaultActionEnabled) {
295         return;
296       }
297     }
298 
299     // Now fire an event at the global object, but don't do that if the error
300     // code is too much recursion and this is the same script threw the error.
301     // XXXbz the interaction of this with worker errors seems kinda broken.
302     // An overrecursion in the debugger or debugger sandbox will get turned
303     // into an error event on our parent worker!
304     // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks making this
305     // better.
306     if (aFireAtScope &&
307         (aTarget || aReport->mErrorNumber != JSMSG_OVER_RECURSED)) {
308       JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
309       NS_ASSERTION(global, "This should never be null!");
310 
311       nsEventStatus status = nsEventStatus_eIgnore;
312 
313       if (aWorkerPrivate) {
314         WorkerGlobalScope* globalScope = nullptr;
315         UNWRAP_OBJECT(WorkerGlobalScope, &global, globalScope);
316 
317         if (!globalScope) {
318           WorkerDebuggerGlobalScope* globalScope = nullptr;
319           UNWRAP_OBJECT(WorkerDebuggerGlobalScope, &global, globalScope);
320 
321           MOZ_ASSERT_IF(globalScope,
322                         globalScope->GetWrapperPreserveColor() == global);
323           if (globalScope || IsWorkerDebuggerSandbox(global)) {
324             aWorkerPrivate->ReportErrorToDebugger(
325                 aReport->mFilename, aReport->mLineNumber, aReport->mMessage);
326             return;
327           }
328 
329           MOZ_ASSERT(SimpleGlobalObject::SimpleGlobalType(global) ==
330                      SimpleGlobalObject::GlobalType::BindingDetail);
331           // XXXbz We should really log this to console, but unwinding out of
332           // this stuff without ending up firing any events is ... hard.  Just
333           // return for now.
334           // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks
335           // making this better.
336           return;
337         }
338 
339         MOZ_ASSERT(globalScope->GetWrapperPreserveColor() == global);
340 
341         RefPtr<ErrorEvent> event =
342             ErrorEvent::Constructor(aTarget, NS_LITERAL_STRING("error"), init);
343         event->SetTrusted(true);
344 
345         if (NS_FAILED(EventDispatcher::DispatchDOMEvent(
346                 ToSupports(globalScope), nullptr, event, nullptr, &status))) {
347           NS_WARNING("Failed to dispatch worker thread error event!");
348           status = nsEventStatus_eIgnore;
349         }
350       } else if (nsGlobalWindowInner* win = xpc::WindowOrNull(global)) {
351         MOZ_ASSERT(NS_IsMainThread());
352 
353         if (!win->HandleScriptError(init, &status)) {
354           NS_WARNING("Failed to dispatch main thread error event!");
355           status = nsEventStatus_eIgnore;
356         }
357       }
358 
359       // Was preventDefault() called?
360       if (status == nsEventStatus_eConsumeNoDefault) {
361         return;
362       }
363     }
364   }
365 
366   // Now fire a runnable to do the same on the parent's thread if we can.
367   if (aWorkerPrivate) {
368     RefPtr<ReportErrorRunnable> runnable =
369         new ReportErrorRunnable(aWorkerPrivate, std::move(aReport));
370     runnable->Dispatch();
371     return;
372   }
373 
374   // Otherwise log an error to the error console.
375   WorkerErrorReport::LogErrorToConsole(aCx, *aReport, aInnerWindowId);
376 }
377 
378 /* static */
LogErrorToConsole(JSContext * aCx,WorkerErrorReport & aReport,uint64_t aInnerWindowId)379 void WorkerErrorReport::LogErrorToConsole(JSContext* aCx,
380                                           WorkerErrorReport& aReport,
381                                           uint64_t aInnerWindowId) {
382   nsTArray<ErrorDataNote> notes;
383   for (size_t i = 0, len = aReport.mNotes.Length(); i < len; i++) {
384     const WorkerErrorNote& note = aReport.mNotes.ElementAt(i);
385     notes.AppendElement(ErrorDataNote(note.mLineNumber, note.mColumnNumber,
386                                       note.mMessage, note.mFilename));
387   }
388 
389   JS::RootedObject stack(aCx, aReport.ReadStack(aCx));
390   JS::RootedObject stackGlobal(aCx, JS::CurrentGlobalOrNull(aCx));
391 
392   ErrorData errorData(aReport.mIsWarning, aReport.mLineNumber,
393                       aReport.mColumnNumber, aReport.mMessage,
394                       aReport.mFilename, aReport.mLine, notes);
395   LogErrorToConsole(errorData, aInnerWindowId, stack, stackGlobal);
396 }
397 
398 /* static */
LogErrorToConsole(const ErrorData & aReport,uint64_t aInnerWindowId,JS::HandleObject aStack,JS::HandleObject aStackGlobal)399 void WorkerErrorReport::LogErrorToConsole(const ErrorData& aReport,
400                                           uint64_t aInnerWindowId,
401                                           JS::HandleObject aStack,
402                                           JS::HandleObject aStackGlobal) {
403   AssertIsOnMainThread();
404 
405   RefPtr<nsScriptErrorBase> scriptError =
406       CreateScriptError(nullptr, JS::NothingHandleValue, aStack, aStackGlobal);
407 
408   NS_WARNING_ASSERTION(scriptError, "Failed to create script error!");
409 
410   if (scriptError) {
411     nsAutoCString category("Web Worker");
412     uint32_t flags = aReport.isWarning() ? nsIScriptError::warningFlag
413                                          : nsIScriptError::errorFlag;
414     if (NS_FAILED(scriptError->nsIScriptError::InitWithWindowID(
415             aReport.message(), aReport.filename(), aReport.line(),
416             aReport.lineNumber(), aReport.columnNumber(), flags, category,
417             aInnerWindowId))) {
418       NS_WARNING("Failed to init script error!");
419       scriptError = nullptr;
420     }
421 
422     for (size_t i = 0, len = aReport.notes().Length(); i < len; i++) {
423       const ErrorDataNote& note = aReport.notes().ElementAt(i);
424 
425       nsScriptErrorNote* noteObject = new nsScriptErrorNote();
426       noteObject->Init(note.message(), note.filename(), 0, note.lineNumber(),
427                        note.columnNumber());
428       scriptError->AddNote(noteObject);
429     }
430   }
431 
432   nsCOMPtr<nsIConsoleService> consoleService =
433       do_GetService(NS_CONSOLESERVICE_CONTRACTID);
434   NS_WARNING_ASSERTION(consoleService, "Failed to get console service!");
435 
436   if (consoleService) {
437     if (scriptError) {
438       if (NS_SUCCEEDED(consoleService->LogMessage(scriptError))) {
439         return;
440       }
441       NS_WARNING("LogMessage failed!");
442     } else if (NS_SUCCEEDED(consoleService->LogStringMessage(
443                    aReport.message().BeginReading()))) {
444       return;
445     }
446     NS_WARNING("LogStringMessage failed!");
447   }
448 
449   NS_ConvertUTF16toUTF8 msg(aReport.message());
450   NS_ConvertUTF16toUTF8 filename(aReport.filename());
451 
452   static const char kErrorString[] = "JS error in Web Worker: %s [%s:%u]";
453 
454 #ifdef ANDROID
455   __android_log_print(ANDROID_LOG_INFO, "Gecko", kErrorString, msg.get(),
456                       filename.get(), aReport.lineNumber());
457 #endif
458 
459   fprintf(stderr, kErrorString, msg.get(), filename.get(),
460           aReport.lineNumber());
461   fflush(stderr);
462 }
463 
464 /* static */
CreateAndDispatchGenericErrorRunnableToParent(WorkerPrivate * aWorkerPrivate)465 void WorkerErrorReport::CreateAndDispatchGenericErrorRunnableToParent(
466     WorkerPrivate* aWorkerPrivate) {
467   ReportGenericErrorRunnable::CreateAndDispatch(aWorkerPrivate);
468 }
469 
470 }  // namespace dom
471 }  // namespace mozilla
472