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 "mozilla/dom/BrowsingContext.h"
8 #include "mozilla/dom/Document.h"
9 #include "mozilla/dom/MessageEvent.h"
10 #include "mozilla/dom/MessageEventBinding.h"
11 #include "mozilla/dom/RemoteWorkerChild.h"
12 #include "mozilla/dom/WindowContext.h"
13 #include "mozilla/AbstractThread.h"
14 #include "mozilla/Encoding.h"
15 #include "mozilla/PerformanceUtils.h"
16 #include "nsProxyRelease.h"
17 #include "nsQueryObject.h"
18 #include "nsThreadUtils.h"
19 #include "ScriptLoader.h"
20 #include "WorkerCommon.h"
21 #include "WorkerError.h"
22 #include "WorkerRunnable.h"
23 #include "WorkerDebugger.h"
26 # include <processthreadsapi.h> // for GetCurrentProcessId()
28 # include <unistd.h> // for getpid()
29 #endif // defined(XP_WIN)
31 namespace mozilla::dom
{
35 class DebuggerMessageEventRunnable
: public WorkerDebuggerRunnable
{
39 DebuggerMessageEventRunnable(WorkerPrivate
* aWorkerPrivate
,
40 const nsAString
& aMessage
)
41 : WorkerDebuggerRunnable(aWorkerPrivate
), mMessage(aMessage
) {}
44 virtual bool WorkerRun(JSContext
* aCx
,
45 WorkerPrivate
* aWorkerPrivate
) override
{
46 WorkerDebuggerGlobalScope
* globalScope
=
47 aWorkerPrivate
->DebuggerGlobalScope();
48 MOZ_ASSERT(globalScope
);
50 JS::Rooted
<JSString
*> message(
51 aCx
, JS_NewUCStringCopyN(aCx
, mMessage
.get(), mMessage
.Length()));
55 JS::Rooted
<JS::Value
> data(aCx
, JS::StringValue(message
));
57 RefPtr
<MessageEvent
> event
=
58 new MessageEvent(globalScope
, nullptr, nullptr);
59 event
->InitMessageEvent(nullptr, u
"message"_ns
, CanBubble::eNo
,
60 Cancelable::eYes
, data
, u
""_ns
, u
""_ns
, nullptr,
61 Sequence
<OwningNonNull
<MessagePort
>>());
62 event
->SetTrusted(true);
64 globalScope
->DispatchEvent(*event
);
69 class CompileDebuggerScriptRunnable final
: public WorkerDebuggerRunnable
{
71 const mozilla::Encoding
* mDocumentEncoding
;
74 CompileDebuggerScriptRunnable(WorkerPrivate
* aWorkerPrivate
,
75 const nsAString
& aScriptURL
,
76 const mozilla::Encoding
* aDocumentEncoding
)
77 : WorkerDebuggerRunnable(aWorkerPrivate
),
78 mScriptURL(aScriptURL
),
79 mDocumentEncoding(aDocumentEncoding
) {}
82 virtual bool WorkerRun(JSContext
* aCx
,
83 WorkerPrivate
* aWorkerPrivate
) override
{
84 aWorkerPrivate
->AssertIsOnWorkerThread();
86 WorkerDebuggerGlobalScope
* globalScope
=
87 aWorkerPrivate
->CreateDebuggerGlobalScope(aCx
);
89 NS_WARNING("Failed to make global!");
93 if (NS_WARN_IF(!aWorkerPrivate
->EnsureCSPEventListener())) {
97 JS::Rooted
<JSObject
*> global(aCx
, globalScope
->GetWrapper());
100 JSAutoRealm
ar(aCx
, global
);
101 workerinternals::LoadMainScript(aWorkerPrivate
, nullptr, mScriptURL
,
102 DebuggerScript
, rv
, mDocumentEncoding
);
103 rv
.WouldReportJSException();
104 // Explicitly ignore NS_BINDING_ABORTED on rv. Or more precisely, still
105 // return false and don't SetWorkerScriptExecutedSuccessfully() in that
106 // case, but don't throw anything on aCx. The idea is to not dispatch error
107 // events if our load is canceled with that error code.
108 if (rv
.ErrorCodeIs(NS_BINDING_ABORTED
)) {
109 rv
.SuppressException();
112 // Make sure to propagate exceptions from rv onto aCx, so that they will get
113 // reported after we return. We do this for all failures on rv, because now
114 // we're using rv to track all the state we care about.
115 if (rv
.MaybeSetPendingException(aCx
)) {
125 class WorkerDebugger::PostDebuggerMessageRunnable final
: public Runnable
{
126 WorkerDebugger
* mDebugger
;
130 PostDebuggerMessageRunnable(WorkerDebugger
* aDebugger
,
131 const nsAString
& aMessage
)
132 : mozilla::Runnable("PostDebuggerMessageRunnable"),
133 mDebugger(aDebugger
),
134 mMessage(aMessage
) {}
137 ~PostDebuggerMessageRunnable() = default;
141 mDebugger
->PostMessageToDebuggerOnMainThread(mMessage
);
147 class WorkerDebugger::ReportDebuggerErrorRunnable final
: public Runnable
{
148 WorkerDebugger
* mDebugger
;
154 ReportDebuggerErrorRunnable(WorkerDebugger
* aDebugger
,
155 const nsAString
& aFilename
, uint32_t aLineno
,
156 const nsAString
& aMessage
)
157 : Runnable("ReportDebuggerErrorRunnable"),
158 mDebugger(aDebugger
),
159 mFilename(aFilename
),
161 mMessage(aMessage
) {}
164 ~ReportDebuggerErrorRunnable() = default;
168 mDebugger
->ReportErrorToDebuggerOnMainThread(mFilename
, mLineno
, mMessage
);
174 WorkerDebugger::WorkerDebugger(WorkerPrivate
* aWorkerPrivate
)
175 : mWorkerPrivate(aWorkerPrivate
), mIsInitialized(false) {
176 AssertIsOnMainThread();
179 WorkerDebugger::~WorkerDebugger() {
180 MOZ_ASSERT(!mWorkerPrivate
);
182 if (!NS_IsMainThread()) {
183 for (auto& listener
: mListeners
) {
184 NS_ReleaseOnMainThread("WorkerDebugger::mListeners", listener
.forget());
189 NS_IMPL_ISUPPORTS(WorkerDebugger
, nsIWorkerDebugger
)
192 WorkerDebugger::GetIsClosed(bool* aResult
) {
193 AssertIsOnMainThread();
195 *aResult
= !mWorkerPrivate
;
200 WorkerDebugger::GetIsChrome(bool* aResult
) {
201 AssertIsOnMainThread();
203 if (!mWorkerPrivate
) {
204 return NS_ERROR_UNEXPECTED
;
207 *aResult
= mWorkerPrivate
->IsChromeWorker();
212 WorkerDebugger::GetIsInitialized(bool* aResult
) {
213 AssertIsOnMainThread();
215 if (!mWorkerPrivate
) {
216 return NS_ERROR_UNEXPECTED
;
219 *aResult
= mIsInitialized
;
224 WorkerDebugger::GetParent(nsIWorkerDebugger
** aResult
) {
225 AssertIsOnMainThread();
227 if (!mWorkerPrivate
) {
228 return NS_ERROR_UNEXPECTED
;
231 WorkerPrivate
* parent
= mWorkerPrivate
->GetParent();
237 MOZ_ASSERT(mWorkerPrivate
->IsDedicatedWorker());
239 nsCOMPtr
<nsIWorkerDebugger
> debugger
= parent
->Debugger();
240 debugger
.forget(aResult
);
245 WorkerDebugger::GetType(uint32_t* aResult
) {
246 AssertIsOnMainThread();
248 if (!mWorkerPrivate
) {
249 return NS_ERROR_UNEXPECTED
;
252 *aResult
= mWorkerPrivate
->Kind();
257 WorkerDebugger::GetUrl(nsAString
& aResult
) {
258 AssertIsOnMainThread();
260 if (!mWorkerPrivate
) {
261 return NS_ERROR_UNEXPECTED
;
264 aResult
= mWorkerPrivate
->ScriptURL();
269 WorkerDebugger::GetWindow(mozIDOMWindow
** aResult
) {
270 AssertIsOnMainThread();
272 if (!mWorkerPrivate
) {
273 return NS_ERROR_UNEXPECTED
;
276 nsCOMPtr
<nsPIDOMWindowInner
> window
= DedicatedWorkerWindow();
277 window
.forget(aResult
);
282 WorkerDebugger::GetWindowIDs(nsTArray
<uint64_t>& aResult
) {
283 AssertIsOnMainThread();
285 if (!mWorkerPrivate
) {
286 return NS_ERROR_UNEXPECTED
;
289 if (mWorkerPrivate
->IsDedicatedWorker()) {
290 if (const auto window
= DedicatedWorkerWindow()) {
291 aResult
.AppendElement(window
->WindowID());
293 } else if (mWorkerPrivate
->IsSharedWorker()) {
294 const RemoteWorkerChild
* const controller
=
295 mWorkerPrivate
->GetRemoteWorkerController();
296 MOZ_ASSERT(controller
);
297 aResult
= controller
->WindowIDs().Clone();
303 nsCOMPtr
<nsPIDOMWindowInner
> WorkerDebugger::DedicatedWorkerWindow() {
304 MOZ_ASSERT(mWorkerPrivate
);
306 WorkerPrivate
* worker
= mWorkerPrivate
;
307 while (worker
->GetParent()) {
308 worker
= worker
->GetParent();
311 if (!worker
->IsDedicatedWorker()) {
315 return worker
->GetWindow();
319 WorkerDebugger::GetPrincipal(nsIPrincipal
** aResult
) {
320 AssertIsOnMainThread();
323 if (!mWorkerPrivate
) {
324 return NS_ERROR_UNEXPECTED
;
327 nsCOMPtr
<nsIPrincipal
> prin
= mWorkerPrivate
->GetPrincipal();
328 prin
.forget(aResult
);
334 WorkerDebugger::GetServiceWorkerID(uint32_t* aResult
) {
335 AssertIsOnMainThread();
338 if (!mWorkerPrivate
|| !mWorkerPrivate
->IsServiceWorker()) {
339 return NS_ERROR_UNEXPECTED
;
342 *aResult
= mWorkerPrivate
->ServiceWorkerID();
347 WorkerDebugger::GetId(nsAString
& aResult
) {
348 AssertIsOnMainThread();
350 if (!mWorkerPrivate
) {
351 return NS_ERROR_UNEXPECTED
;
354 aResult
= mWorkerPrivate
->Id();
359 WorkerDebugger::Initialize(const nsAString
& aURL
) {
360 AssertIsOnMainThread();
362 if (!mWorkerPrivate
) {
363 return NS_ERROR_UNEXPECTED
;
366 // This should be non-null for dedicated workers and null for Shared and
367 // Service workers. All Encoding values are static and will live as long
368 // as the process and the convention is to therefore use raw pointers.
369 const mozilla::Encoding
* aDocumentEncoding
=
370 NS_IsMainThread() && !mWorkerPrivate
->GetParent() &&
371 mWorkerPrivate
->GetDocument()
372 ? mWorkerPrivate
->GetDocument()->GetDocumentCharacterSet().get()
375 if (!mIsInitialized
) {
376 RefPtr
<CompileDebuggerScriptRunnable
> runnable
=
377 new CompileDebuggerScriptRunnable(mWorkerPrivate
, aURL
,
379 if (!runnable
->Dispatch()) {
380 return NS_ERROR_FAILURE
;
383 mIsInitialized
= true;
390 WorkerDebugger::PostMessageMoz(const nsAString
& aMessage
) {
391 AssertIsOnMainThread();
393 if (!mWorkerPrivate
|| !mIsInitialized
) {
394 return NS_ERROR_UNEXPECTED
;
397 RefPtr
<DebuggerMessageEventRunnable
> runnable
=
398 new DebuggerMessageEventRunnable(mWorkerPrivate
, aMessage
);
399 if (!runnable
->Dispatch()) {
400 return NS_ERROR_FAILURE
;
407 WorkerDebugger::AddListener(nsIWorkerDebuggerListener
* aListener
) {
408 AssertIsOnMainThread();
410 if (mListeners
.Contains(aListener
)) {
411 return NS_ERROR_INVALID_ARG
;
414 mListeners
.AppendElement(aListener
);
419 WorkerDebugger::RemoveListener(nsIWorkerDebuggerListener
* aListener
) {
420 AssertIsOnMainThread();
422 if (!mListeners
.Contains(aListener
)) {
423 return NS_ERROR_INVALID_ARG
;
426 mListeners
.RemoveElement(aListener
);
431 WorkerDebugger::SetDebuggerReady(bool aReady
) {
432 return mWorkerPrivate
->SetIsDebuggerReady(aReady
);
435 void WorkerDebugger::Close() {
436 MOZ_ASSERT(mWorkerPrivate
);
437 mWorkerPrivate
= nullptr;
439 for (const auto& listener
: mListeners
.Clone()) {
444 void WorkerDebugger::PostMessageToDebugger(const nsAString
& aMessage
) {
445 mWorkerPrivate
->AssertIsOnWorkerThread();
447 RefPtr
<PostDebuggerMessageRunnable
> runnable
=
448 new PostDebuggerMessageRunnable(this, aMessage
);
449 if (NS_FAILED(mWorkerPrivate
->DispatchToMainThreadForMessaging(
450 runnable
.forget()))) {
451 NS_WARNING("Failed to post message to debugger on main thread!");
455 void WorkerDebugger::PostMessageToDebuggerOnMainThread(
456 const nsAString
& aMessage
) {
457 AssertIsOnMainThread();
459 for (const auto& listener
: mListeners
.Clone()) {
460 listener
->OnMessage(aMessage
);
464 void WorkerDebugger::ReportErrorToDebugger(const nsAString
& aFilename
,
466 const nsAString
& aMessage
) {
467 mWorkerPrivate
->AssertIsOnWorkerThread();
469 RefPtr
<ReportDebuggerErrorRunnable
> runnable
=
470 new ReportDebuggerErrorRunnable(this, aFilename
, aLineno
, aMessage
);
471 if (NS_FAILED(mWorkerPrivate
->DispatchToMainThreadForMessaging(
472 runnable
.forget()))) {
473 NS_WARNING("Failed to report error to debugger on main thread!");
477 void WorkerDebugger::ReportErrorToDebuggerOnMainThread(
478 const nsAString
& aFilename
, uint32_t aLineno
, const nsAString
& aMessage
) {
479 AssertIsOnMainThread();
481 for (const auto& listener
: mListeners
.Clone()) {
482 listener
->OnError(aFilename
, aLineno
, aMessage
);
486 // We're only using this context to deserialize a stack to report to the
487 // console, so the scope we use doesn't matter. Stack frame filtering happens
488 // based on the principal encoded into the frame and the caller compartment,
489 // not the compartment of the frame object, and the console reporting code
490 // will not be using our context, and therefore will not care what compartment
492 DebugOnly
<bool> ok
= jsapi
.Init(xpc::PrivilegedJunkScope());
493 MOZ_ASSERT(ok
, "PrivilegedJunkScope should exist");
495 WorkerErrorReport report
;
496 report
.mMessage
= aMessage
;
497 report
.mFilename
= aFilename
;
498 WorkerErrorReport::LogErrorToConsole(jsapi
.cx(), report
, 0);
501 RefPtr
<PerformanceInfoPromise
> WorkerDebugger::ReportPerformanceInfo() {
502 AssertIsOnMainThread();
503 RefPtr
<WorkerDebugger
> self
= this;
506 uint32_t pid
= GetCurrentProcessId();
508 uint32_t pid
= getpid();
510 bool isTopLevel
= false;
511 uint64_t windowID
= mWorkerPrivate
->WindowID();
513 // Walk up to our containing page and its window
514 WorkerPrivate
* wp
= mWorkerPrivate
;
515 while (wp
->GetParent()) {
516 wp
= wp
->GetParent();
518 nsPIDOMWindowInner
* win
= wp
->GetWindow();
520 BrowsingContext
* context
= win
->GetBrowsingContext();
522 RefPtr
<BrowsingContext
> top
= context
->Top();
523 if (top
&& top
->GetCurrentWindowContext()) {
524 windowID
= top
->GetCurrentWindowContext()->OuterWindowId();
525 isTopLevel
= context
->IsTop();
530 // getting the worker URL
531 RefPtr
<nsIURI
> scriptURI
= mWorkerPrivate
->GetResolvedScriptURI();
532 if (NS_WARN_IF(!scriptURI
)) {
533 // This can happen at shutdown, let's stop here.
534 return PerformanceInfoPromise::CreateAndReject(NS_ERROR_FAILURE
, __func__
);
536 nsCString url
= scriptURI
->GetSpecOrDefault();
538 const auto& perf
= mWorkerPrivate
->PerformanceCounterRef();
539 uint64_t perfId
= perf
.GetID();
540 uint16_t count
= perf
.GetTotalDispatchCount();
541 uint64_t duration
= perf
.GetExecutionDuration();
543 // Workers only produce metrics for a single category -
544 // DispatchCategory::Worker. We still return an array of CategoryDispatch so
545 // the PerformanceInfo struct is common to all performance counters throughout
547 FallibleTArray
<CategoryDispatch
> items
;
549 if (mWorkerPrivate
->GetParent()) {
550 // We cannot properly measure the memory usage of nested workers
551 // (https://phabricator.services.mozilla.com/D146673#4948924)
552 return PerformanceInfoPromise::CreateAndResolve(
553 PerformanceInfo(url
, pid
, windowID
, duration
, perfId
, true, isTopLevel
,
554 PerformanceMemoryInfo(), items
),
558 CategoryDispatch item
=
559 CategoryDispatch(DispatchCategory::Worker
.GetValue(), count
);
560 if (!items
.AppendElement(item
, fallible
)) {
561 NS_ERROR("Could not complete the operation");
564 // Switch to the worker thread to gather the JS Runtime's memory usage.
565 RefPtr
<WorkerPrivate::JSMemoryUsagePromise
> memoryUsagePromise
=
566 mWorkerPrivate
->GetJSMemoryUsage();
567 if (!memoryUsagePromise
) {
568 // The worker is shutting down, so we don't count the JavaScript memory.
569 return PerformanceInfoPromise::CreateAndResolve(
570 PerformanceInfo(url
, pid
, windowID
, duration
, perfId
, true, isTopLevel
,
571 PerformanceMemoryInfo(), items
),
575 // We need to keep a ref on workerPrivate, passed to the promise,
576 // to make sure it's still alive when collecting the info, and we can't do
577 // this in WorkerPrivate::GetJSMemoryUsage() since that could cause it to be
578 // freed on the worker thread.
579 // Because CheckedUnsafePtr does not convert directly to RefPtr, we have an
581 WorkerPrivate
* workerPtr
= mWorkerPrivate
;
582 RefPtr
<WorkerPrivate
> workerRef
= workerPtr
;
584 // This captures an unused reference to memoryUsagePromise because the worker
585 // can be released while this promise is still alive.
586 return memoryUsagePromise
->Then(
587 GetCurrentSerialEventTarget(), __func__
,
588 [url
, pid
, perfId
, windowID
, duration
, isTopLevel
,
589 items
= std::move(items
), _w
= std::move(workerRef
),
590 memoryUsagePromise
](uint64_t jsMem
) {
591 PerformanceMemoryInfo memInfo
;
592 memInfo
.jsMemUsage() = jsMem
;
593 return PerformanceInfoPromise::CreateAndResolve(
594 PerformanceInfo(url
, pid
, windowID
, duration
, perfId
, true,
595 isTopLevel
, memInfo
, items
),
599 return PerformanceInfoPromise::CreateAndReject(NS_ERROR_FAILURE
,
604 } // namespace mozilla::dom