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 "WorkerDebugger.h"
9 #include "mozilla/dom/MessageEvent.h"
10 #include "mozilla/dom/MessageEventBinding.h"
11 #include "mozilla/PerformanceUtils.h"
12 #include "nsProxyRelease.h"
13 #include "nsQueryObject.h"
14 #include "nsThreadUtils.h"
15 #include "ScriptLoader.h"
16 #include "WorkerCommon.h"
17 #include "WorkerError.h"
18 #include "WorkerPrivate.h"
19 #include "WorkerRunnable.h"
20 #include "WorkerScope.h"
22 # include <processthreadsapi.h> // for GetCurrentProcessId()
24 # include <unistd.h> // for getpid()
25 #endif // defined(XP_WIN)
32 class DebuggerMessageEventRunnable
: public WorkerDebuggerRunnable
{
36 DebuggerMessageEventRunnable(WorkerPrivate
* aWorkerPrivate
,
37 const nsAString
& aMessage
)
38 : WorkerDebuggerRunnable(aWorkerPrivate
), mMessage(aMessage
) {}
41 virtual bool WorkerRun(JSContext
* aCx
,
42 WorkerPrivate
* aWorkerPrivate
) override
{
43 WorkerDebuggerGlobalScope
* globalScope
=
44 aWorkerPrivate
->DebuggerGlobalScope();
45 MOZ_ASSERT(globalScope
);
47 JS::Rooted
<JSString
*> message(
48 aCx
, JS_NewUCStringCopyN(aCx
, mMessage
.get(), mMessage
.Length()));
52 JS::Rooted
<JS::Value
> data(aCx
, JS::StringValue(message
));
54 RefPtr
<MessageEvent
> event
=
55 new MessageEvent(globalScope
, nullptr, nullptr);
56 event
->InitMessageEvent(nullptr, NS_LITERAL_STRING("message"),
57 CanBubble::eNo
, Cancelable::eYes
, data
,
58 EmptyString(), EmptyString(), nullptr,
59 Sequence
<OwningNonNull
<MessagePort
>>());
60 event
->SetTrusted(true);
62 globalScope
->DispatchEvent(*event
);
67 class CompileDebuggerScriptRunnable final
: public WorkerDebuggerRunnable
{
71 CompileDebuggerScriptRunnable(WorkerPrivate
* aWorkerPrivate
,
72 const nsAString
& aScriptURL
)
73 : WorkerDebuggerRunnable(aWorkerPrivate
), mScriptURL(aScriptURL
) {}
76 virtual bool WorkerRun(JSContext
* aCx
,
77 WorkerPrivate
* aWorkerPrivate
) override
{
78 aWorkerPrivate
->AssertIsOnWorkerThread();
80 WorkerDebuggerGlobalScope
* globalScope
=
81 aWorkerPrivate
->CreateDebuggerGlobalScope(aCx
);
83 NS_WARNING("Failed to make global!");
87 if (NS_WARN_IF(!aWorkerPrivate
->EnsureClientSource())) {
91 if (NS_WARN_IF(!aWorkerPrivate
->EnsureCSPEventListener())) {
95 // Initialize performance state which might be used on the main thread, as
96 // in CompileScriptRunnable. This runnable might execute first.
97 aWorkerPrivate
->EnsurePerformanceStorage();
98 aWorkerPrivate
->EnsurePerformanceCounter();
100 JS::Rooted
<JSObject
*> global(aCx
, globalScope
->GetWrapper());
103 JSAutoRealm
ar(aCx
, global
);
104 workerinternals::LoadMainScript(aWorkerPrivate
, nullptr, mScriptURL
,
106 rv
.WouldReportJSException();
107 // Explicitly ignore NS_BINDING_ABORTED on rv. Or more precisely, still
108 // return false and don't SetWorkerScriptExecutedSuccessfully() in that
109 // case, but don't throw anything on aCx. The idea is to not dispatch error
110 // events if our load is canceled with that error code.
111 if (rv
.ErrorCodeIs(NS_BINDING_ABORTED
)) {
112 rv
.SuppressException();
115 // Make sure to propagate exceptions from rv onto aCx, so that they will get
116 // reported after we return. We do this for all failures on rv, because now
117 // we're using rv to track all the state we care about.
118 if (rv
.MaybeSetPendingException(aCx
)) {
128 class WorkerDebugger::PostDebuggerMessageRunnable final
: public Runnable
{
129 WorkerDebugger
* mDebugger
;
133 PostDebuggerMessageRunnable(WorkerDebugger
* aDebugger
,
134 const nsAString
& aMessage
)
135 : mozilla::Runnable("PostDebuggerMessageRunnable"),
136 mDebugger(aDebugger
),
137 mMessage(aMessage
) {}
140 ~PostDebuggerMessageRunnable() {}
144 mDebugger
->PostMessageToDebuggerOnMainThread(mMessage
);
150 class WorkerDebugger::ReportDebuggerErrorRunnable final
: public Runnable
{
151 WorkerDebugger
* mDebugger
;
157 ReportDebuggerErrorRunnable(WorkerDebugger
* aDebugger
,
158 const nsAString
& aFilename
, uint32_t aLineno
,
159 const nsAString
& aMessage
)
160 : Runnable("ReportDebuggerErrorRunnable"),
161 mDebugger(aDebugger
),
162 mFilename(aFilename
),
164 mMessage(aMessage
) {}
167 ~ReportDebuggerErrorRunnable() {}
171 mDebugger
->ReportErrorToDebuggerOnMainThread(mFilename
, mLineno
, mMessage
);
177 WorkerDebugger::WorkerDebugger(WorkerPrivate
* aWorkerPrivate
)
178 : mWorkerPrivate(aWorkerPrivate
), mIsInitialized(false) {
179 AssertIsOnMainThread();
182 WorkerDebugger::~WorkerDebugger() {
183 MOZ_ASSERT(!mWorkerPrivate
);
185 if (!NS_IsMainThread()) {
186 for (size_t index
= 0; index
< mListeners
.Length(); ++index
) {
187 NS_ReleaseOnMainThreadSystemGroup("WorkerDebugger::mListeners",
188 mListeners
[index
].forget());
193 NS_IMPL_ISUPPORTS(WorkerDebugger
, nsIWorkerDebugger
)
196 WorkerDebugger::GetIsClosed(bool* aResult
) {
197 AssertIsOnMainThread();
199 *aResult
= !mWorkerPrivate
;
204 WorkerDebugger::GetIsChrome(bool* aResult
) {
205 AssertIsOnMainThread();
207 if (!mWorkerPrivate
) {
208 return NS_ERROR_UNEXPECTED
;
211 *aResult
= mWorkerPrivate
->IsChromeWorker();
216 WorkerDebugger::GetIsInitialized(bool* aResult
) {
217 AssertIsOnMainThread();
219 if (!mWorkerPrivate
) {
220 return NS_ERROR_UNEXPECTED
;
223 *aResult
= mIsInitialized
;
228 WorkerDebugger::GetParent(nsIWorkerDebugger
** aResult
) {
229 AssertIsOnMainThread();
231 if (!mWorkerPrivate
) {
232 return NS_ERROR_UNEXPECTED
;
235 WorkerPrivate
* parent
= mWorkerPrivate
->GetParent();
241 MOZ_ASSERT(mWorkerPrivate
->IsDedicatedWorker());
243 nsCOMPtr
<nsIWorkerDebugger
> debugger
= parent
->Debugger();
244 debugger
.forget(aResult
);
249 WorkerDebugger::GetType(uint32_t* aResult
) {
250 AssertIsOnMainThread();
252 if (!mWorkerPrivate
) {
253 return NS_ERROR_UNEXPECTED
;
256 *aResult
= mWorkerPrivate
->Type();
261 WorkerDebugger::GetUrl(nsAString
& aResult
) {
262 AssertIsOnMainThread();
264 if (!mWorkerPrivate
) {
265 return NS_ERROR_UNEXPECTED
;
268 aResult
= mWorkerPrivate
->ScriptURL();
273 WorkerDebugger::GetWindow(mozIDOMWindow
** aResult
) {
274 AssertIsOnMainThread();
276 if (!mWorkerPrivate
) {
277 return NS_ERROR_UNEXPECTED
;
280 if (mWorkerPrivate
->GetParent() || !mWorkerPrivate
->IsDedicatedWorker()) {
285 nsCOMPtr
<nsPIDOMWindowInner
> window
= mWorkerPrivate
->GetWindow();
286 window
.forget(aResult
);
291 WorkerDebugger::GetPrincipal(nsIPrincipal
** aResult
) {
292 AssertIsOnMainThread();
295 if (!mWorkerPrivate
) {
296 return NS_ERROR_UNEXPECTED
;
299 nsCOMPtr
<nsIPrincipal
> prin
= mWorkerPrivate
->GetPrincipal();
300 prin
.forget(aResult
);
306 WorkerDebugger::GetServiceWorkerID(uint32_t* aResult
) {
307 AssertIsOnMainThread();
310 if (!mWorkerPrivate
|| !mWorkerPrivate
->IsServiceWorker()) {
311 return NS_ERROR_UNEXPECTED
;
314 *aResult
= mWorkerPrivate
->ServiceWorkerID();
319 WorkerDebugger::GetId(nsAString
& aResult
) {
320 AssertIsOnMainThread();
322 if (!mWorkerPrivate
) {
323 return NS_ERROR_UNEXPECTED
;
326 aResult
= mWorkerPrivate
->Id();
331 WorkerDebugger::Initialize(const nsAString
& aURL
) {
332 AssertIsOnMainThread();
334 if (!mWorkerPrivate
) {
335 return NS_ERROR_UNEXPECTED
;
338 if (!mIsInitialized
) {
339 RefPtr
<CompileDebuggerScriptRunnable
> runnable
=
340 new CompileDebuggerScriptRunnable(mWorkerPrivate
, aURL
);
341 if (!runnable
->Dispatch()) {
342 return NS_ERROR_FAILURE
;
345 mIsInitialized
= true;
352 WorkerDebugger::PostMessageMoz(const nsAString
& aMessage
) {
353 AssertIsOnMainThread();
355 if (!mWorkerPrivate
|| !mIsInitialized
) {
356 return NS_ERROR_UNEXPECTED
;
359 RefPtr
<DebuggerMessageEventRunnable
> runnable
=
360 new DebuggerMessageEventRunnable(mWorkerPrivate
, aMessage
);
361 if (!runnable
->Dispatch()) {
362 return NS_ERROR_FAILURE
;
369 WorkerDebugger::AddListener(nsIWorkerDebuggerListener
* aListener
) {
370 AssertIsOnMainThread();
372 if (mListeners
.Contains(aListener
)) {
373 return NS_ERROR_INVALID_ARG
;
376 mListeners
.AppendElement(aListener
);
381 WorkerDebugger::RemoveListener(nsIWorkerDebuggerListener
* aListener
) {
382 AssertIsOnMainThread();
384 if (!mListeners
.Contains(aListener
)) {
385 return NS_ERROR_INVALID_ARG
;
388 mListeners
.RemoveElement(aListener
);
393 WorkerDebugger::SetDebuggerReady(bool aReady
) {
394 return mWorkerPrivate
->SetIsDebuggerReady(aReady
);
397 void WorkerDebugger::Close() {
398 MOZ_ASSERT(mWorkerPrivate
);
399 mWorkerPrivate
= nullptr;
401 nsTArray
<nsCOMPtr
<nsIWorkerDebuggerListener
>> listeners(mListeners
);
402 for (size_t index
= 0; index
< listeners
.Length(); ++index
) {
403 listeners
[index
]->OnClose();
407 void WorkerDebugger::PostMessageToDebugger(const nsAString
& aMessage
) {
408 mWorkerPrivate
->AssertIsOnWorkerThread();
410 RefPtr
<PostDebuggerMessageRunnable
> runnable
=
411 new PostDebuggerMessageRunnable(this, aMessage
);
412 if (NS_FAILED(mWorkerPrivate
->DispatchToMainThreadForMessaging(
413 runnable
.forget()))) {
414 NS_WARNING("Failed to post message to debugger on main thread!");
418 void WorkerDebugger::PostMessageToDebuggerOnMainThread(
419 const nsAString
& aMessage
) {
420 AssertIsOnMainThread();
422 nsTArray
<nsCOMPtr
<nsIWorkerDebuggerListener
>> listeners(mListeners
);
423 for (size_t index
= 0; index
< listeners
.Length(); ++index
) {
424 listeners
[index
]->OnMessage(aMessage
);
428 void WorkerDebugger::ReportErrorToDebugger(const nsAString
& aFilename
,
430 const nsAString
& aMessage
) {
431 mWorkerPrivate
->AssertIsOnWorkerThread();
433 RefPtr
<ReportDebuggerErrorRunnable
> runnable
=
434 new ReportDebuggerErrorRunnable(this, aFilename
, aLineno
, aMessage
);
435 if (NS_FAILED(mWorkerPrivate
->DispatchToMainThreadForMessaging(
436 runnable
.forget()))) {
437 NS_WARNING("Failed to report error to debugger on main thread!");
441 void WorkerDebugger::ReportErrorToDebuggerOnMainThread(
442 const nsAString
& aFilename
, uint32_t aLineno
, const nsAString
& aMessage
) {
443 AssertIsOnMainThread();
445 nsTArray
<nsCOMPtr
<nsIWorkerDebuggerListener
>> listeners(mListeners
);
446 for (size_t index
= 0; index
< listeners
.Length(); ++index
) {
447 listeners
[index
]->OnError(aFilename
, aLineno
, aMessage
);
450 // We need a JSContext to be able to read any stack associated with the error.
451 // This will not run any scripts.
453 DebugOnly
<bool> ok
= jsapi
.Init(xpc::UnprivilegedJunkScope());
454 MOZ_ASSERT(ok
, "UnprivilegedJunkScope should exist");
456 WorkerErrorReport report
;
457 report
.mMessage
= aMessage
;
458 report
.mFilename
= aFilename
;
459 WorkerErrorReport::LogErrorToConsole(jsapi
.cx(), report
, 0);
462 RefPtr
<PerformanceInfoPromise
> WorkerDebugger::ReportPerformanceInfo() {
463 AssertIsOnMainThread();
464 nsCOMPtr
<nsPIDOMWindowOuter
> top
;
465 RefPtr
<WorkerDebugger
> self
= this;
468 uint32_t pid
= GetCurrentProcessId();
470 uint32_t pid
= getpid();
472 bool isTopLevel
= false;
473 uint64_t windowID
= mWorkerPrivate
->WindowID();
474 PerformanceMemoryInfo memoryInfo
;
476 // Walk up to our containing page and its window
477 WorkerPrivate
* wp
= mWorkerPrivate
;
478 while (wp
->GetParent()) {
479 wp
= wp
->GetParent();
481 nsPIDOMWindowInner
* win
= wp
->GetWindow();
483 nsPIDOMWindowOuter
* outer
= win
->GetOuterWindow();
485 top
= outer
->GetTop();
487 windowID
= top
->WindowID();
488 isTopLevel
= outer
->IsTopLevelWindow();
493 // getting the worker URL
494 RefPtr
<nsIURI
> scriptURI
= mWorkerPrivate
->GetResolvedScriptURI();
495 if (NS_WARN_IF(!scriptURI
)) {
496 // This can happen at shutdown, let's stop here.
497 return PerformanceInfoPromise::CreateAndReject(NS_ERROR_FAILURE
, __func__
);
499 nsCString url
= scriptURI
->GetSpecOrDefault();
501 // Workers only produce metrics for a single category -
502 // DispatchCategory::Worker. We still return an array of CategoryDispatch so
503 // the PerformanceInfo struct is common to all performance counters throughout
505 FallibleTArray
<CategoryDispatch
> items
;
506 uint64_t duration
= 0;
510 RefPtr
<PerformanceCounter
> perf
= mWorkerPrivate
->GetPerformanceCounter();
512 perfId
= perf
->GetID();
513 count
= perf
->GetTotalDispatchCount();
514 duration
= perf
->GetExecutionDuration();
515 CategoryDispatch item
=
516 CategoryDispatch(DispatchCategory::Worker
.GetValue(), count
);
517 if (!items
.AppendElement(item
, fallible
)) {
518 NS_ERROR("Could not complete the operation");
523 return PerformanceInfoPromise::CreateAndResolve(
524 PerformanceInfo(url
, pid
, windowID
, duration
, perfId
, true, isTopLevel
,
529 // We need to keep a ref on workerPrivate, passed to the promise,
530 // to make sure it's still aloive when collecting the info.
531 RefPtr
<WorkerPrivate
> workerRef
= mWorkerPrivate
;
532 RefPtr
<AbstractThread
> mainThread
=
533 SystemGroup::AbstractMainThreadFor(TaskCategory::Performance
);
535 return CollectMemoryInfo(top
, mainThread
)
537 mainThread
, __func__
,
538 [workerRef
, url
, pid
, perfId
, windowID
, duration
, isTopLevel
,
539 items
](const PerformanceMemoryInfo
& aMemoryInfo
) {
540 return PerformanceInfoPromise::CreateAndResolve(
541 PerformanceInfo(url
, pid
, windowID
, duration
, perfId
, true,
542 isTopLevel
, aMemoryInfo
, items
),
546 return PerformanceInfoPromise::CreateAndReject(NS_ERROR_FAILURE
,
552 } // namespace mozilla