Bug 1825333 - Make toolkit/components/sessionstore buildable outside of a unified...
[gecko.git] / dom / workers / WorkerDebugger.cpp
blob6068c627f17ea76713e9ced97402aabd8ba2c257
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"
25 #if defined(XP_WIN)
26 # include <processthreadsapi.h> // for GetCurrentProcessId()
27 #else
28 # include <unistd.h> // for getpid()
29 #endif // defined(XP_WIN)
31 namespace mozilla::dom {
33 namespace {
35 class DebuggerMessageEventRunnable : public WorkerDebuggerRunnable {
36 nsString mMessage;
38 public:
39 DebuggerMessageEventRunnable(WorkerPrivate* aWorkerPrivate,
40 const nsAString& aMessage)
41 : WorkerDebuggerRunnable(aWorkerPrivate), mMessage(aMessage) {}
43 private:
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()));
52 if (!message) {
53 return false;
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);
65 return true;
69 class CompileDebuggerScriptRunnable final : public WorkerDebuggerRunnable {
70 nsString mScriptURL;
71 const mozilla::Encoding* mDocumentEncoding;
73 public:
74 CompileDebuggerScriptRunnable(WorkerPrivate* aWorkerPrivate,
75 const nsAString& aScriptURL,
76 const mozilla::Encoding* aDocumentEncoding)
77 : WorkerDebuggerRunnable(aWorkerPrivate),
78 mScriptURL(aScriptURL),
79 mDocumentEncoding(aDocumentEncoding) {}
81 private:
82 virtual bool WorkerRun(JSContext* aCx,
83 WorkerPrivate* aWorkerPrivate) override {
84 aWorkerPrivate->AssertIsOnWorkerThread();
86 WorkerDebuggerGlobalScope* globalScope =
87 aWorkerPrivate->CreateDebuggerGlobalScope(aCx);
88 if (!globalScope) {
89 NS_WARNING("Failed to make global!");
90 return false;
93 if (NS_WARN_IF(!aWorkerPrivate->EnsureCSPEventListener())) {
94 return false;
97 JS::Rooted<JSObject*> global(aCx, globalScope->GetWrapper());
99 ErrorResult rv;
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();
110 return false;
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)) {
116 return false;
119 return true;
123 } // namespace
125 class WorkerDebugger::PostDebuggerMessageRunnable final : public Runnable {
126 WorkerDebugger* mDebugger;
127 nsString mMessage;
129 public:
130 PostDebuggerMessageRunnable(WorkerDebugger* aDebugger,
131 const nsAString& aMessage)
132 : mozilla::Runnable("PostDebuggerMessageRunnable"),
133 mDebugger(aDebugger),
134 mMessage(aMessage) {}
136 private:
137 ~PostDebuggerMessageRunnable() = default;
139 NS_IMETHOD
140 Run() override {
141 mDebugger->PostMessageToDebuggerOnMainThread(mMessage);
143 return NS_OK;
147 class WorkerDebugger::ReportDebuggerErrorRunnable final : public Runnable {
148 WorkerDebugger* mDebugger;
149 nsString mFilename;
150 uint32_t mLineno;
151 nsString mMessage;
153 public:
154 ReportDebuggerErrorRunnable(WorkerDebugger* aDebugger,
155 const nsAString& aFilename, uint32_t aLineno,
156 const nsAString& aMessage)
157 : Runnable("ReportDebuggerErrorRunnable"),
158 mDebugger(aDebugger),
159 mFilename(aFilename),
160 mLineno(aLineno),
161 mMessage(aMessage) {}
163 private:
164 ~ReportDebuggerErrorRunnable() = default;
166 NS_IMETHOD
167 Run() override {
168 mDebugger->ReportErrorToDebuggerOnMainThread(mFilename, mLineno, mMessage);
170 return NS_OK;
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)
191 NS_IMETHODIMP
192 WorkerDebugger::GetIsClosed(bool* aResult) {
193 AssertIsOnMainThread();
195 *aResult = !mWorkerPrivate;
196 return NS_OK;
199 NS_IMETHODIMP
200 WorkerDebugger::GetIsChrome(bool* aResult) {
201 AssertIsOnMainThread();
203 if (!mWorkerPrivate) {
204 return NS_ERROR_UNEXPECTED;
207 *aResult = mWorkerPrivate->IsChromeWorker();
208 return NS_OK;
211 NS_IMETHODIMP
212 WorkerDebugger::GetIsInitialized(bool* aResult) {
213 AssertIsOnMainThread();
215 if (!mWorkerPrivate) {
216 return NS_ERROR_UNEXPECTED;
219 *aResult = mIsInitialized;
220 return NS_OK;
223 NS_IMETHODIMP
224 WorkerDebugger::GetParent(nsIWorkerDebugger** aResult) {
225 AssertIsOnMainThread();
227 if (!mWorkerPrivate) {
228 return NS_ERROR_UNEXPECTED;
231 WorkerPrivate* parent = mWorkerPrivate->GetParent();
232 if (!parent) {
233 *aResult = nullptr;
234 return NS_OK;
237 MOZ_ASSERT(mWorkerPrivate->IsDedicatedWorker());
239 nsCOMPtr<nsIWorkerDebugger> debugger = parent->Debugger();
240 debugger.forget(aResult);
241 return NS_OK;
244 NS_IMETHODIMP
245 WorkerDebugger::GetType(uint32_t* aResult) {
246 AssertIsOnMainThread();
248 if (!mWorkerPrivate) {
249 return NS_ERROR_UNEXPECTED;
252 *aResult = mWorkerPrivate->Kind();
253 return NS_OK;
256 NS_IMETHODIMP
257 WorkerDebugger::GetUrl(nsAString& aResult) {
258 AssertIsOnMainThread();
260 if (!mWorkerPrivate) {
261 return NS_ERROR_UNEXPECTED;
264 aResult = mWorkerPrivate->ScriptURL();
265 return NS_OK;
268 NS_IMETHODIMP
269 WorkerDebugger::GetWindow(mozIDOMWindow** aResult) {
270 AssertIsOnMainThread();
272 if (!mWorkerPrivate) {
273 return NS_ERROR_UNEXPECTED;
276 nsCOMPtr<nsPIDOMWindowInner> window = DedicatedWorkerWindow();
277 window.forget(aResult);
278 return NS_OK;
281 NS_IMETHODIMP
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();
300 return NS_OK;
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()) {
312 return nullptr;
315 return worker->GetWindow();
318 NS_IMETHODIMP
319 WorkerDebugger::GetPrincipal(nsIPrincipal** aResult) {
320 AssertIsOnMainThread();
321 MOZ_ASSERT(aResult);
323 if (!mWorkerPrivate) {
324 return NS_ERROR_UNEXPECTED;
327 nsCOMPtr<nsIPrincipal> prin = mWorkerPrivate->GetPrincipal();
328 prin.forget(aResult);
330 return NS_OK;
333 NS_IMETHODIMP
334 WorkerDebugger::GetServiceWorkerID(uint32_t* aResult) {
335 AssertIsOnMainThread();
336 MOZ_ASSERT(aResult);
338 if (!mWorkerPrivate || !mWorkerPrivate->IsServiceWorker()) {
339 return NS_ERROR_UNEXPECTED;
342 *aResult = mWorkerPrivate->ServiceWorkerID();
343 return NS_OK;
346 NS_IMETHODIMP
347 WorkerDebugger::GetId(nsAString& aResult) {
348 AssertIsOnMainThread();
350 if (!mWorkerPrivate) {
351 return NS_ERROR_UNEXPECTED;
354 aResult = mWorkerPrivate->Id();
355 return NS_OK;
358 NS_IMETHODIMP
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()
373 : nullptr;
375 if (!mIsInitialized) {
376 RefPtr<CompileDebuggerScriptRunnable> runnable =
377 new CompileDebuggerScriptRunnable(mWorkerPrivate, aURL,
378 aDocumentEncoding);
379 if (!runnable->Dispatch()) {
380 return NS_ERROR_FAILURE;
383 mIsInitialized = true;
386 return NS_OK;
389 NS_IMETHODIMP
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;
403 return NS_OK;
406 NS_IMETHODIMP
407 WorkerDebugger::AddListener(nsIWorkerDebuggerListener* aListener) {
408 AssertIsOnMainThread();
410 if (mListeners.Contains(aListener)) {
411 return NS_ERROR_INVALID_ARG;
414 mListeners.AppendElement(aListener);
415 return NS_OK;
418 NS_IMETHODIMP
419 WorkerDebugger::RemoveListener(nsIWorkerDebuggerListener* aListener) {
420 AssertIsOnMainThread();
422 if (!mListeners.Contains(aListener)) {
423 return NS_ERROR_INVALID_ARG;
426 mListeners.RemoveElement(aListener);
427 return NS_OK;
430 NS_IMETHODIMP
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()) {
440 listener->OnClose();
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,
465 uint32_t aLineno,
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);
485 AutoJSAPI jsapi;
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
491 // it has entered.
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;
505 #if defined(XP_WIN)
506 uint32_t pid = GetCurrentProcessId();
507 #else
508 uint32_t pid = getpid();
509 #endif
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();
519 if (win) {
520 BrowsingContext* context = win->GetBrowsingContext();
521 if (context) {
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
546 // Firefox.
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),
555 __func__);
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),
572 __func__);
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
580 // extra step here.
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),
596 __func__);
598 []() {
599 return PerformanceInfoPromise::CreateAndReject(NS_ERROR_FAILURE,
600 __func__);
604 } // namespace mozilla::dom