WorkerDebugger.cpp (13931B)
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 "WorkerDebugger.h" 8 9 #include "ScriptLoader.h" 10 #include "WorkerCommon.h" 11 #include "WorkerError.h" 12 #include "WorkerRunnable.h" 13 #include "mozilla/AbstractThread.h" 14 #include "mozilla/Encoding.h" 15 #include "mozilla/dom/BrowsingContext.h" 16 #include "mozilla/dom/Document.h" 17 #include "mozilla/dom/MessageEvent.h" 18 #include "mozilla/dom/MessageEventBinding.h" 19 #include "mozilla/dom/RemoteWorkerChild.h" 20 #include "mozilla/dom/WindowContext.h" 21 #include "nsProxyRelease.h" 22 #include "nsQueryObject.h" 23 #include "nsThreadUtils.h" 24 25 #if defined(XP_WIN) 26 # include <processthreadsapi.h> // for GetCurrentProcessId() 27 #else 28 # include <unistd.h> // for getpid() 29 #endif // defined(XP_WIN) 30 31 namespace mozilla::dom { 32 33 namespace { 34 35 class DebuggerMessageEventRunnable final : public WorkerDebuggerRunnable { 36 nsString mMessage; 37 38 public: 39 DebuggerMessageEventRunnable(WorkerPrivate* aWorkerPrivate, 40 const nsAString& aMessage) 41 : WorkerDebuggerRunnable("DebuggerMessageEventRunnable"), 42 mMessage(aMessage) {} 43 44 private: 45 virtual bool WorkerRun(JSContext* aCx, 46 WorkerPrivate* aWorkerPrivate) override { 47 WorkerDebuggerGlobalScope* globalScope = 48 aWorkerPrivate->DebuggerGlobalScope(); 49 MOZ_ASSERT(globalScope); 50 51 JS::Rooted<JSString*> message( 52 aCx, JS_NewUCStringCopyN(aCx, mMessage.get(), mMessage.Length())); 53 if (!message) { 54 return false; 55 } 56 JS::Rooted<JS::Value> data(aCx, JS::StringValue(message)); 57 58 RefPtr<MessageEvent> event = 59 new MessageEvent(globalScope, nullptr, nullptr); 60 event->InitMessageEvent(nullptr, u"message"_ns, CanBubble::eNo, 61 Cancelable::eYes, data, u""_ns, u""_ns, nullptr, 62 Sequence<OwningNonNull<MessagePort>>()); 63 event->SetTrusted(true); 64 65 globalScope->DispatchEvent(*event); 66 return true; 67 } 68 }; 69 70 class CompileDebuggerScriptRunnable final : public WorkerDebuggerRunnable { 71 nsString mScriptURL; 72 const mozilla::Encoding* mDocumentEncoding; 73 74 public: 75 CompileDebuggerScriptRunnable(WorkerPrivate* aWorkerPrivate, 76 const nsAString& aScriptURL, 77 const mozilla::Encoding* aDocumentEncoding) 78 : WorkerDebuggerRunnable("CompileDebuggerScriptRunnable"), 79 mScriptURL(aScriptURL), 80 mDocumentEncoding(aDocumentEncoding) {} 81 82 private: 83 virtual bool WorkerRun(JSContext* aCx, 84 WorkerPrivate* aWorkerPrivate) override { 85 aWorkerPrivate->AssertIsOnWorkerThread(); 86 87 WorkerDebuggerGlobalScope* globalScope = 88 aWorkerPrivate->CreateDebuggerGlobalScope(aCx); 89 if (!globalScope) { 90 NS_WARNING("Failed to make global!"); 91 return false; 92 } 93 94 if (NS_WARN_IF(!aWorkerPrivate->EnsureCSPEventListener())) { 95 return false; 96 } 97 98 JS::Rooted<JSObject*> global(aCx, globalScope->GetWrapper()); 99 100 ErrorResult rv; 101 JSAutoRealm ar(aCx, global); 102 workerinternals::LoadMainScript(aWorkerPrivate, nullptr, mScriptURL, 103 DebuggerScript, rv, mDocumentEncoding); 104 rv.WouldReportJSException(); 105 // Explicitly ignore NS_BINDING_ABORTED on rv. Or more precisely, still 106 // return false and don't SetWorkerScriptExecutedSuccessfully() in that 107 // case, but don't throw anything on aCx. The idea is to not dispatch error 108 // events if our load is canceled with that error code. 109 if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) { 110 rv.SuppressException(); 111 return false; 112 } 113 // Make sure to propagate exceptions from rv onto aCx, so that they will get 114 // reported after we return. We do this for all failures on rv, because now 115 // we're using rv to track all the state we care about. 116 if (rv.MaybeSetPendingException(aCx)) { 117 return false; 118 } 119 120 return true; 121 } 122 }; 123 124 } // namespace 125 126 class WorkerDebugger::PostDebuggerMessageRunnable final : public Runnable { 127 WorkerDebugger* mDebugger; 128 nsString mMessage; 129 130 public: 131 PostDebuggerMessageRunnable(WorkerDebugger* aDebugger, 132 const nsAString& aMessage) 133 : mozilla::Runnable("PostDebuggerMessageRunnable"), 134 mDebugger(aDebugger), 135 mMessage(aMessage) {} 136 137 private: 138 ~PostDebuggerMessageRunnable() = default; 139 140 NS_IMETHOD 141 Run() override { 142 mDebugger->PostMessageToDebuggerOnMainThread(mMessage); 143 144 return NS_OK; 145 } 146 }; 147 148 class WorkerDebugger::ReportDebuggerErrorRunnable final : public Runnable { 149 WorkerDebugger* mDebugger; 150 nsCString mFilename; 151 uint32_t mLineno; 152 nsString mMessage; 153 154 public: 155 ReportDebuggerErrorRunnable(WorkerDebugger* aDebugger, 156 const nsACString& aFilename, uint32_t aLineno, 157 const nsAString& aMessage) 158 : Runnable("ReportDebuggerErrorRunnable"), 159 mDebugger(aDebugger), 160 mFilename(aFilename), 161 mLineno(aLineno), 162 mMessage(aMessage) {} 163 164 private: 165 ~ReportDebuggerErrorRunnable() = default; 166 167 NS_IMETHOD 168 Run() override { 169 mDebugger->ReportErrorToDebuggerOnMainThread(mFilename, mLineno, mMessage); 170 171 return NS_OK; 172 } 173 }; 174 175 WorkerDebugger::WorkerDebugger(WorkerPrivate* aWorkerPrivate) 176 : mWorkerPrivate(aWorkerPrivate), mIsInitialized(false) { 177 AssertIsOnMainThread(); 178 } 179 180 WorkerDebugger::~WorkerDebugger() { 181 MOZ_ASSERT(!mWorkerPrivate); 182 183 if (!NS_IsMainThread()) { 184 for (auto& listener : mListeners) { 185 NS_ReleaseOnMainThread("WorkerDebugger::mListeners", listener.forget()); 186 } 187 } 188 } 189 190 NS_IMPL_ISUPPORTS(WorkerDebugger, nsIWorkerDebugger) 191 192 NS_IMETHODIMP 193 WorkerDebugger::GetIsClosed(bool* aResult) { 194 AssertIsOnMainThread(); 195 196 *aResult = !mWorkerPrivate || mWorkerPrivate->IsDead(); 197 return NS_OK; 198 } 199 200 NS_IMETHODIMP 201 WorkerDebugger::GetIsChrome(bool* aResult) { 202 AssertIsOnMainThread(); 203 204 if (!mWorkerPrivate) { 205 return NS_ERROR_UNEXPECTED; 206 } 207 208 *aResult = mWorkerPrivate->IsChromeWorker(); 209 return NS_OK; 210 } 211 212 NS_IMETHODIMP 213 WorkerDebugger::GetIsRemote(bool* aResult) { 214 AssertIsOnMainThread(); 215 216 *aResult = false; 217 return NS_OK; 218 } 219 220 NS_IMETHODIMP 221 WorkerDebugger::GetIsInitialized(bool* aResult) { 222 AssertIsOnMainThread(); 223 224 if (!mWorkerPrivate) { 225 return NS_ERROR_UNEXPECTED; 226 } 227 228 *aResult = mIsInitialized; 229 return NS_OK; 230 } 231 232 NS_IMETHODIMP 233 WorkerDebugger::GetParent(nsIWorkerDebugger** aResult) { 234 AssertIsOnMainThread(); 235 236 if (!mWorkerPrivate) { 237 return NS_ERROR_UNEXPECTED; 238 } 239 240 WorkerPrivate* parent = mWorkerPrivate->GetParent(); 241 if (!parent) { 242 *aResult = nullptr; 243 return NS_OK; 244 } 245 246 MOZ_ASSERT(mWorkerPrivate->IsDedicatedWorker()); 247 248 nsCOMPtr<nsIWorkerDebugger> debugger = parent->Debugger(); 249 debugger.forget(aResult); 250 return NS_OK; 251 } 252 253 NS_IMETHODIMP 254 WorkerDebugger::GetType(uint32_t* aResult) { 255 AssertIsOnMainThread(); 256 257 if (!mWorkerPrivate) { 258 return NS_ERROR_UNEXPECTED; 259 } 260 261 *aResult = mWorkerPrivate->Kind(); 262 return NS_OK; 263 } 264 265 NS_IMETHODIMP 266 WorkerDebugger::GetUrl(nsAString& aResult) { 267 AssertIsOnMainThread(); 268 269 if (!mWorkerPrivate) { 270 return NS_ERROR_UNEXPECTED; 271 } 272 273 aResult = mWorkerPrivate->ScriptURL(); 274 return NS_OK; 275 } 276 277 NS_IMETHODIMP 278 WorkerDebugger::GetWindow(mozIDOMWindow** aResult) { 279 AssertIsOnMainThread(); 280 281 if (!mWorkerPrivate) { 282 return NS_ERROR_UNEXPECTED; 283 } 284 285 nsCOMPtr<nsPIDOMWindowInner> window = DedicatedWorkerWindow(); 286 window.forget(aResult); 287 return NS_OK; 288 } 289 290 NS_IMETHODIMP 291 WorkerDebugger::GetWindowIDs(nsTArray<uint64_t>& aResult) { 292 AssertIsOnMainThread(); 293 294 if (!mWorkerPrivate) { 295 return NS_ERROR_UNEXPECTED; 296 } 297 298 if (mWorkerPrivate->IsDedicatedWorker()) { 299 if (const auto window = DedicatedWorkerWindow()) { 300 aResult.AppendElement(window->WindowID()); 301 } 302 } else if (mWorkerPrivate->IsSharedWorker()) { 303 const RemoteWorkerChild* const controller = 304 mWorkerPrivate->GetRemoteWorkerController(); 305 MOZ_ASSERT(controller); 306 aResult = controller->WindowIDs().Clone(); 307 } 308 309 return NS_OK; 310 } 311 312 nsCOMPtr<nsPIDOMWindowInner> WorkerDebugger::DedicatedWorkerWindow() { 313 MOZ_ASSERT(mWorkerPrivate); 314 315 WorkerPrivate* worker = mWorkerPrivate; 316 while (worker->GetParent()) { 317 worker = worker->GetParent(); 318 } 319 320 if (!worker->IsDedicatedWorker()) { 321 return nullptr; 322 } 323 324 return worker->GetWindow(); 325 } 326 327 NS_IMETHODIMP 328 WorkerDebugger::GetPrincipal(nsIPrincipal** aResult) { 329 AssertIsOnMainThread(); 330 MOZ_ASSERT(aResult); 331 332 if (!mWorkerPrivate) { 333 return NS_ERROR_UNEXPECTED; 334 } 335 336 nsCOMPtr<nsIPrincipal> prin = mWorkerPrivate->GetPrincipal(); 337 prin.forget(aResult); 338 339 return NS_OK; 340 } 341 342 NS_IMETHODIMP 343 WorkerDebugger::GetServiceWorkerID(uint32_t* aResult) { 344 AssertIsOnMainThread(); 345 MOZ_ASSERT(aResult); 346 347 if (!mWorkerPrivate || !mWorkerPrivate->IsServiceWorker()) { 348 return NS_ERROR_UNEXPECTED; 349 } 350 351 *aResult = mWorkerPrivate->ServiceWorkerID(); 352 return NS_OK; 353 } 354 355 NS_IMETHODIMP 356 WorkerDebugger::GetId(nsAString& aResult) { 357 AssertIsOnMainThread(); 358 359 if (!mWorkerPrivate) { 360 return NS_ERROR_UNEXPECTED; 361 } 362 363 aResult = mWorkerPrivate->Id(); 364 return NS_OK; 365 } 366 367 NS_IMETHODIMP 368 WorkerDebugger::GetName(nsAString& aResult) { 369 AssertIsOnMainThread(); 370 371 if (!mWorkerPrivate) { 372 return NS_ERROR_UNEXPECTED; 373 } 374 375 aResult = mWorkerPrivate->WorkerName(); 376 return NS_OK; 377 } 378 379 NS_IMETHODIMP 380 WorkerDebugger::Initialize(const nsAString& aURL) { 381 AssertIsOnMainThread(); 382 383 if (!mWorkerPrivate) { 384 return NS_ERROR_UNEXPECTED; 385 } 386 387 // This should be non-null for dedicated workers and null for Shared and 388 // Service workers. All Encoding values are static and will live as long 389 // as the process and the convention is to therefore use raw pointers. 390 const mozilla::Encoding* aDocumentEncoding = 391 NS_IsMainThread() && !mWorkerPrivate->GetParent() && 392 mWorkerPrivate->GetDocument() 393 ? mWorkerPrivate->GetDocument()->GetDocumentCharacterSet().get() 394 : nullptr; 395 396 if (!mIsInitialized) { 397 RefPtr<CompileDebuggerScriptRunnable> runnable = 398 new CompileDebuggerScriptRunnable(mWorkerPrivate, aURL, 399 aDocumentEncoding); 400 if (!runnable->Dispatch(mWorkerPrivate)) { 401 return NS_ERROR_FAILURE; 402 } 403 404 mIsInitialized = true; 405 } 406 407 return NS_OK; 408 } 409 410 NS_IMETHODIMP 411 WorkerDebugger::PostMessageMoz(const nsAString& aMessage) { 412 AssertIsOnMainThread(); 413 414 if (!mWorkerPrivate || !mIsInitialized) { 415 return NS_ERROR_UNEXPECTED; 416 } 417 418 RefPtr<DebuggerMessageEventRunnable> runnable = 419 new DebuggerMessageEventRunnable(mWorkerPrivate, aMessage); 420 if (!runnable->Dispatch(mWorkerPrivate)) { 421 return NS_ERROR_FAILURE; 422 } 423 424 return NS_OK; 425 } 426 427 NS_IMETHODIMP 428 WorkerDebugger::AddListener(nsIWorkerDebuggerListener* aListener) { 429 AssertIsOnMainThread(); 430 431 if (mListeners.Contains(aListener)) { 432 return NS_ERROR_INVALID_ARG; 433 } 434 435 mListeners.AppendElement(aListener); 436 return NS_OK; 437 } 438 439 NS_IMETHODIMP 440 WorkerDebugger::RemoveListener(nsIWorkerDebuggerListener* aListener) { 441 AssertIsOnMainThread(); 442 443 if (!mListeners.Contains(aListener)) { 444 return NS_ERROR_INVALID_ARG; 445 } 446 447 mListeners.RemoveElement(aListener); 448 return NS_OK; 449 } 450 451 NS_IMETHODIMP 452 WorkerDebugger::SetDebuggerReady(bool aReady) { 453 return mWorkerPrivate->SetIsDebuggerReady(aReady); 454 } 455 456 void WorkerDebugger::Close() { 457 MOZ_ASSERT(mWorkerPrivate); 458 mWorkerPrivate = nullptr; 459 460 for (const auto& listener : mListeners.Clone()) { 461 listener->OnClose(); 462 } 463 } 464 465 void WorkerDebugger::PostMessageToDebugger(const nsAString& aMessage) { 466 mWorkerPrivate->AssertIsOnWorkerThread(); 467 468 RefPtr<PostDebuggerMessageRunnable> runnable = 469 new PostDebuggerMessageRunnable(this, aMessage); 470 if (NS_FAILED(mWorkerPrivate->DispatchToMainThreadForMessaging( 471 runnable.forget()))) { 472 NS_WARNING("Failed to post message to debugger on main thread!"); 473 } 474 } 475 476 void WorkerDebugger::PostMessageToDebuggerOnMainThread( 477 const nsAString& aMessage) { 478 AssertIsOnMainThread(); 479 480 for (const auto& listener : mListeners.Clone()) { 481 listener->OnMessage(aMessage); 482 } 483 } 484 485 void WorkerDebugger::ReportErrorToDebugger(const nsACString& aFilename, 486 uint32_t aLineno, 487 const nsAString& aMessage) { 488 mWorkerPrivate->AssertIsOnWorkerThread(); 489 490 RefPtr<ReportDebuggerErrorRunnable> runnable = 491 new ReportDebuggerErrorRunnable(this, aFilename, aLineno, aMessage); 492 if (NS_FAILED(mWorkerPrivate->DispatchToMainThreadForMessaging( 493 runnable.forget()))) { 494 NS_WARNING("Failed to report error to debugger on main thread!"); 495 } 496 } 497 498 void WorkerDebugger::ReportErrorToDebuggerOnMainThread( 499 const nsACString& aFilename, uint32_t aLineno, const nsAString& aMessage) { 500 AssertIsOnMainThread(); 501 502 for (const auto& listener : mListeners.Clone()) { 503 listener->OnError(aFilename, aLineno, aMessage); 504 } 505 506 AutoJSAPI jsapi; 507 // We're only using this context to deserialize a stack to report to the 508 // console, so the scope we use doesn't matter. Stack frame filtering happens 509 // based on the principal encoded into the frame and the caller compartment, 510 // not the compartment of the frame object, and the console reporting code 511 // will not be using our context, and therefore will not care what compartment 512 // it has entered. 513 DebugOnly<bool> ok = jsapi.Init(xpc::PrivilegedJunkScope()); 514 MOZ_ASSERT(ok, "PrivilegedJunkScope should exist"); 515 516 WorkerErrorReport report; 517 report.mMessage = aMessage; 518 report.mFilename = aFilename; 519 WorkerErrorReport::LogErrorToConsole(jsapi.cx(), report, 0); 520 } 521 522 } // namespace mozilla::dom