From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- dom/workers/ChromeWorker.cpp | 84 + dom/workers/ChromeWorker.h | 33 + dom/workers/ChromeWorkerScope.cpp | 67 + dom/workers/ChromeWorkerScope.h | 18 + dom/workers/JSExecutionManager.cpp | 251 + dom/workers/JSExecutionManager.h | 192 + dom/workers/JSSettings.h | 62 + dom/workers/MessageEventRunnable.cpp | 156 + dom/workers/MessageEventRunnable.h | 38 + dom/workers/Queue.h | 151 + dom/workers/RegisterBindings.cpp | 60 + dom/workers/RuntimeService.cpp | 2414 ++++++++ dom/workers/RuntimeService.h | 200 + dom/workers/ScriptLoader.cpp | 1840 ++++++ dom/workers/ScriptLoader.h | 375 ++ dom/workers/Worker.cpp | 211 + dom/workers/Worker.h | 67 + dom/workers/WorkerCSPEventListener.cpp | 104 + dom/workers/WorkerCSPEventListener.h | 40 + dom/workers/WorkerChannelInfo.cpp | 66 + dom/workers/WorkerChannelInfo.h | 50 + dom/workers/WorkerCommon.h | 57 + dom/workers/WorkerDebugger.cpp | 604 ++ dom/workers/WorkerDebugger.h | 66 + dom/workers/WorkerDebuggerManager.cpp | 330 ++ dom/workers/WorkerDebuggerManager.h | 116 + dom/workers/WorkerDocumentListener.cpp | 110 + dom/workers/WorkerDocumentListener.h | 40 + dom/workers/WorkerError.cpp | 477 ++ dom/workers/WorkerError.h | 78 + dom/workers/WorkerEventTarget.cpp | 167 + dom/workers/WorkerEventTarget.h | 48 + dom/workers/WorkerIPCUtils.h | 26 + dom/workers/WorkerLoadInfo.cpp | 508 ++ dom/workers/WorkerLoadInfo.h | 198 + dom/workers/WorkerLocation.cpp | 36 + dom/workers/WorkerLocation.h | 72 + dom/workers/WorkerNavigator.cpp | 293 + dom/workers/WorkerNavigator.h | 122 + dom/workers/WorkerPrivate.cpp | 6003 ++++++++++++++++++++ dom/workers/WorkerPrivate.h | 1625 ++++++ dom/workers/WorkerRef.cpp | 245 + dom/workers/WorkerRef.h | 237 + dom/workers/WorkerRunnable.cpp | 728 +++ dom/workers/WorkerRunnable.h | 508 ++ dom/workers/WorkerScope.cpp | 1414 +++++ dom/workers/WorkerScope.h | 562 ++ dom/workers/WorkerStatus.h | 58 + dom/workers/WorkerTestUtils.cpp | 21 + dom/workers/WorkerTestUtils.h | 40 + dom/workers/WorkerThread.cpp | 378 ++ dom/workers/WorkerThread.h | 112 + dom/workers/loader/CacheLoadHandler.cpp | 651 +++ dom/workers/loader/CacheLoadHandler.h | 221 + dom/workers/loader/NetworkLoadHandler.cpp | 393 ++ dom/workers/loader/NetworkLoadHandler.h | 79 + .../loader/ScriptResponseHeaderProcessor.cpp | 78 + dom/workers/loader/ScriptResponseHeaderProcessor.h | 94 + dom/workers/loader/WorkerLoadContext.cpp | 78 + dom/workers/loader/WorkerLoadContext.h | 219 + dom/workers/loader/WorkerModuleLoader.cpp | 215 + dom/workers/loader/WorkerModuleLoader.h | 87 + dom/workers/loader/moz.build | 34 + dom/workers/moz.build | 110 + dom/workers/nsIWorkerChannelInfo.idl | 22 + dom/workers/nsIWorkerDebugger.idl | 69 + dom/workers/nsIWorkerDebuggerManager.idl | 22 + dom/workers/remoteworkers/PRemoteWorker.ipdl | 90 + .../remoteworkers/PRemoteWorkerController.ipdl | 49 + .../remoteworkers/PRemoteWorkerService.ipdl | 25 + dom/workers/remoteworkers/RemoteWorkerChild.cpp | 1011 ++++ dom/workers/remoteworkers/RemoteWorkerChild.h | 237 + .../remoteworkers/RemoteWorkerController.cpp | 573 ++ dom/workers/remoteworkers/RemoteWorkerController.h | 323 ++ .../remoteworkers/RemoteWorkerControllerChild.cpp | 149 + .../remoteworkers/RemoteWorkerControllerChild.h | 64 + .../remoteworkers/RemoteWorkerControllerParent.cpp | 215 + .../remoteworkers/RemoteWorkerControllerParent.h | 86 + dom/workers/remoteworkers/RemoteWorkerManager.cpp | 735 +++ dom/workers/remoteworkers/RemoteWorkerManager.h | 118 + dom/workers/remoteworkers/RemoteWorkerParent.cpp | 201 + dom/workers/remoteworkers/RemoteWorkerParent.h | 62 + dom/workers/remoteworkers/RemoteWorkerService.cpp | 346 ++ dom/workers/remoteworkers/RemoteWorkerService.h | 123 + .../remoteworkers/RemoteWorkerServiceChild.cpp | 16 + .../remoteworkers/RemoteWorkerServiceChild.h | 34 + .../remoteworkers/RemoteWorkerServiceParent.cpp | 34 + .../remoteworkers/RemoteWorkerServiceParent.h | 41 + dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh | 123 + dom/workers/remoteworkers/moz.build | 45 + dom/workers/sharedworkers/PSharedWorker.ipdl | 40 + dom/workers/sharedworkers/SharedWorker.cpp | 426 ++ dom/workers/sharedworkers/SharedWorker.h | 94 + dom/workers/sharedworkers/SharedWorkerChild.cpp | 155 + dom/workers/sharedworkers/SharedWorkerChild.h | 61 + dom/workers/sharedworkers/SharedWorkerManager.cpp | 348 ++ dom/workers/sharedworkers/SharedWorkerManager.h | 164 + dom/workers/sharedworkers/SharedWorkerParent.cpp | 165 + dom/workers/sharedworkers/SharedWorkerParent.h | 81 + dom/workers/sharedworkers/SharedWorkerService.cpp | 263 + dom/workers/sharedworkers/SharedWorkerService.h | 74 + dom/workers/sharedworkers/moz.build | 28 + dom/workers/test/404_server.sjs | 9 + .../test/WorkerDebugger.console_childWorker.js | 3 + .../test/WorkerDebugger.console_debugger.js | 49 + dom/workers/test/WorkerDebugger.console_worker.js | 10 + .../test/WorkerDebugger.initialize_childWorker.js | 6 + .../test/WorkerDebugger.initialize_debugger.js | 6 + ...WorkerDebugger.initialize_debugger_es_worker.js | 10 + .../test/WorkerDebugger.initialize_es_worker.js | 10 + .../test/WorkerDebugger.initialize_worker.js | 9 + .../test/WorkerDebugger.postMessage_childWorker.js | 3 + .../test/WorkerDebugger.postMessage_debugger.js | 9 + .../test/WorkerDebugger.postMessage_worker.js | 3 + ...erDebuggerGlobalScope.createSandbox_debugger.js | 9 + ...kerDebuggerGlobalScope.createSandbox_sandbox.js | 9 + ...rkerDebuggerGlobalScope.createSandbox_worker.js | 3 + ...buggerGlobalScope.enterEventLoop_childWorker.js | 14 + ...rDebuggerGlobalScope.enterEventLoop_debugger.js | 29 + ...kerDebuggerGlobalScope.enterEventLoop_worker.js | 27 + ...rDebuggerGlobalScope.reportError_childWorker.js | 5 + ...rkerDebuggerGlobalScope.reportError_debugger.js | 12 + ...WorkerDebuggerGlobalScope.reportError_worker.js | 11 + ...kerDebuggerGlobalScope.setImmediate_debugger.js | 12 + ...orkerDebuggerGlobalScope.setImmediate_worker.js | 3 + .../test/WorkerDebuggerManager_childWorker.js | 3 + dom/workers/test/WorkerDebuggerManager_worker.js | 3 + dom/workers/test/WorkerDebugger_childWorker.js | 3 + .../test/WorkerDebugger_frozen_window1.html | 15 + .../test/WorkerDebugger_frozen_window2.html | 15 + dom/workers/test/WorkerDebugger_frozen_worker1.js | 5 + dom/workers/test/WorkerDebugger_frozen_worker2.js | 5 + .../test/WorkerDebugger_promise_debugger.js | 34 + dom/workers/test/WorkerDebugger_promise_worker.js | 25 + dom/workers/test/WorkerDebugger_sharedWorker.js | 17 + .../test/WorkerDebugger_suspended_debugger.js | 6 + .../test/WorkerDebugger_suspended_worker.js | 6 + dom/workers/test/WorkerDebugger_worker.js | 8 + dom/workers/test/WorkerTest.jsm | 15 + dom/workers/test/WorkerTest_badworker.js | 1 + dom/workers/test/WorkerTest_subworker.js | 37 + dom/workers/test/WorkerTest_worker.js | 11 + dom/workers/test/atob_worker.js | 55 + dom/workers/test/blank.html | 14 + dom/workers/test/browser.ini | 36 + .../test/browser_WorkerDebugger.initialize.js | 54 + dom/workers/test/browser_bug1047663.js | 56 + dom/workers/test/browser_bug1104623.js | 60 + dom/workers/test/browser_consoleSharedWorkers.js | 70 + dom/workers/test/browser_fileURL.js | 73 + .../test/browser_privilegedmozilla_remoteworker.js | 116 + .../browser_serviceworker_fetch_new_process.js | 405 ++ dom/workers/test/browser_worker_use_counters.js | 176 + dom/workers/test/bug1014466_data1.txt | 1 + dom/workers/test/bug1014466_data2.txt | 1 + dom/workers/test/bug1014466_worker.js | 63 + dom/workers/test/bug1020226_frame.html | 19 + dom/workers/test/bug1020226_worker.js | 12 + dom/workers/test/bug1047663_tab.html | 8 + dom/workers/test/bug1047663_worker.sjs | 41 + dom/workers/test/bug1060621_worker.js | 2 + dom/workers/test/bug1062920_worker.js | 8 + dom/workers/test/bug1063538.sjs | 11 + dom/workers/test/bug1063538_worker.js | 25 + dom/workers/test/bug1104064_worker.js | 10 + dom/workers/test/bug1132395_sharedWorker.js | 12 + dom/workers/test/bug1132924_worker.js | 10 + dom/workers/test/bug978260_worker.js | 7 + dom/workers/test/bug998474_worker.js | 6 + dom/workers/test/chrome.ini | 96 + dom/workers/test/chromeWorker_subworker.js | 7 + dom/workers/test/chromeWorker_worker.js | 20 + dom/workers/test/chromeWorker_worker.sys.mjs | 16 + .../test/chromeWorker_worker_submod.sys.mjs | 9 + dom/workers/test/clearTimeoutsImplicit_worker.js | 11 + dom/workers/test/clearTimeouts_worker.js | 12 + dom/workers/test/consoleReplaceable_worker.js | 24 + dom/workers/test/console_worker.js | 112 + dom/workers/test/content_worker.js | 12 + dom/workers/test/crashtests/1153636.html | 5 + dom/workers/test/crashtests/1158031.html | 11 + dom/workers/test/crashtests/1228456.html | 14 + dom/workers/test/crashtests/1348882.html | 18 + dom/workers/test/crashtests/1819146.html | 23 + dom/workers/test/crashtests/1821066.html | 9 + dom/workers/test/crashtests/779707.html | 19 + dom/workers/test/crashtests/943516.html | 10 + dom/workers/test/crashtests/crashtests.list | 8 + dom/workers/test/csp_worker.js | 25 + dom/workers/test/csp_worker.js^headers^ | 1 + dom/workers/test/dom_worker_helper.js | 171 + dom/workers/test/dynamicImport_nested.mjs | 8 + dom/workers/test/dynamicImport_postMessage.mjs | 8 + dom/workers/test/dynamicImport_worker.js | 15 + dom/workers/test/empty.html | 0 dom/workers/test/empty_worker.js | 1 + dom/workers/test/errorPropagation_iframe.html | 55 + dom/workers/test/errorPropagation_worker.js | 50 + dom/workers/test/errorwarning_worker.js | 44 + dom/workers/test/eventDispatch_worker.js | 84 + dom/workers/test/fibonacci_worker.js | 24 + dom/workers/test/fileBlobSubWorker_worker.js | 17 + dom/workers/test/fileBlob_worker.js | 13 + dom/workers/test/filePosting_worker.js | 3 + dom/workers/test/fileReadSlice_worker.js | 16 + dom/workers/test/fileReaderSyncErrors_worker.js | 82 + dom/workers/test/fileReaderSync_worker.js | 25 + dom/workers/test/fileSlice_worker.js | 27 + dom/workers/test/fileSubWorker_worker.js | 17 + dom/workers/test/file_bug1010784_worker.js | 9 + dom/workers/test/file_service_worker.js | 3 + .../test/file_service_worker_container.html | 15 + .../test/file_service_worker_fetch_synthetic.js | 59 + .../test/file_use_counter_service_worker.js | 9 + dom/workers/test/file_use_counter_shared_worker.js | 10 + .../file_use_counter_shared_worker_microtask.js | 12 + dom/workers/test/file_use_counter_worker.html | 13 + dom/workers/test/file_use_counter_worker.js | 2 + dom/workers/test/file_worker.js | 16 + dom/workers/test/foreign.js | 1 + dom/workers/test/head.js | 75 + dom/workers/test/importForeignScripts_worker.js | 55 + dom/workers/test/importScripts_3rdParty_worker.js | 88 + dom/workers/test/importScripts_mixedcontent.html | 46 + dom/workers/test/importScripts_worker.js | 63 + dom/workers/test/importScripts_worker_imported1.js | 9 + dom/workers/test/importScripts_worker_imported2.js | 9 + dom/workers/test/importScripts_worker_imported3.js | 6 + dom/workers/test/importScripts_worker_imported4.js | 6 + dom/workers/test/instanceof_worker.js | 16 + dom/workers/test/invalid.js | 1 + dom/workers/test/json_worker.js | 354 ++ dom/workers/test/loadEncoding_worker.js | 7 + dom/workers/test/location_worker.js | 12 + dom/workers/test/longThread_worker.js | 14 + dom/workers/test/marionette/manifest.ini | 2 + .../test/marionette/service_worker_utils.py | 63 + .../marionette/test_service_workers_at_startup.py | 31 + .../marionette/test_service_workers_disabled.py | 37 + dom/workers/test/mochitest.ini | 249 + dom/workers/test/multi_sharedWorker_frame.html | 58 + .../test/multi_sharedWorker_frame_bfcache.html | 13 + .../test/multi_sharedWorker_frame_nobfcache.html | 13 + ...ulti_sharedWorker_frame_nobfcache.html^headers^ | 1 + dom/workers/test/multi_sharedWorker_manager.js | 58 + .../test/multi_sharedWorker_sharedWorker.js | 72 + dom/workers/test/navigate.html | 13 + dom/workers/test/navigator_languages_worker.js | 11 + dom/workers/test/navigator_worker.js | 85 + dom/workers/test/newError_worker.js | 5 + dom/workers/test/notification_permission_worker.js | 59 + dom/workers/test/notification_worker.js | 104 + .../test/notification_worker_child-child.js | 93 + .../test/notification_worker_child-parent.js | 26 + dom/workers/test/onLine_worker.js | 70 + dom/workers/test/onLine_worker_child.js | 91 + dom/workers/test/onLine_worker_head.js | 50 + dom/workers/test/promise_worker.js | 1007 ++++ dom/workers/test/recursion_worker.js | 46 + dom/workers/test/recursiveOnerror_worker.js | 11 + dom/workers/test/redirect_to_foreign.sjs | 7 + dom/workers/test/referrer.sjs | 14 + dom/workers/test/referrer_test_server.sjs | 99 + dom/workers/test/referrer_worker.html | 144 + dom/workers/test/rvals_worker.js | 13 + dom/workers/test/script_createFile.js | 42 + dom/workers/test/server_fetch_synthetic.sjs | 50 + dom/workers/test/sharedWorker_console.js | 11 + dom/workers/test/sharedWorker_lifetime.js | 5 + dom/workers/test/sharedWorker_ports.js | 30 + dom/workers/test/sharedWorker_privateBrowsing.js | 4 + dom/workers/test/sharedWorker_sharedWorker.js | 99 + .../test/sharedWorker_thirdparty_frame.html | 16 + .../test/sharedWorker_thirdparty_window.html | 26 + dom/workers/test/simpleThread_worker.js | 52 + dom/workers/test/sourcemap_header.js | 65 + dom/workers/test/sourcemap_header_debugger.js | 29 + dom/workers/test/sourcemap_header_iframe.html | 19 + dom/workers/test/sourcemap_header_worker.js | 8 + .../test/sourcemap_header_worker.js^headers^ | 1 + dom/workers/test/suspend_blank.html | 23 + dom/workers/test/suspend_window.html | 82 + dom/workers/test/suspend_worker.js | 13 + dom/workers/test/terminate_worker.js | 11 + dom/workers/test/test_404.html | 39 + .../test/test_WorkerDebugger.initialize.xhtml | 56 + .../test/test_WorkerDebugger.postMessage.xhtml | 59 + dom/workers/test/test_WorkerDebugger.xhtml | 145 + ...t_WorkerDebuggerGlobalScope.createSandbox.xhtml | 49 + ..._WorkerDebuggerGlobalScope.enterEventLoop.xhtml | 124 + ...est_WorkerDebuggerGlobalScope.reportError.xhtml | 95 + ...st_WorkerDebuggerGlobalScope.setImmediate.xhtml | 52 + dom/workers/test/test_WorkerDebuggerManager.xhtml | 104 + dom/workers/test/test_WorkerDebugger_console.xhtml | 98 + dom/workers/test/test_WorkerDebugger_frozen.xhtml | 77 + dom/workers/test/test_WorkerDebugger_promise.xhtml | 68 + .../test/test_WorkerDebugger_suspended.xhtml | 72 + dom/workers/test/test_atob.html | 57 + dom/workers/test/test_blobConstructor.html | 60 + dom/workers/test/test_blobWorkers.html | 31 + dom/workers/test/test_bug1002702.html | 26 + dom/workers/test/test_bug1010784.html | 35 + dom/workers/test/test_bug1014466.html | 42 + dom/workers/test/test_bug1020226.html | 38 + dom/workers/test/test_bug1036484.html | 52 + dom/workers/test/test_bug1060621.html | 30 + dom/workers/test/test_bug1062920.html | 70 + dom/workers/test/test_bug1062920.xhtml | 67 + dom/workers/test/test_bug1063538.html | 47 + dom/workers/test/test_bug1104064.html | 27 + dom/workers/test/test_bug1132395.html | 40 + dom/workers/test/test_bug1132924.html | 28 + dom/workers/test/test_bug1278777.html | 31 + dom/workers/test/test_bug1301094.html | 69 + dom/workers/test/test_bug1317725.html | 49 + dom/workers/test/test_bug1317725.js | 8 + dom/workers/test/test_bug1824498.html | 38 + dom/workers/test/test_bug949946.html | 26 + dom/workers/test/test_bug978260.html | 35 + dom/workers/test/test_bug998474.html | 40 + dom/workers/test/test_chromeWorker.html | 26 + dom/workers/test/test_chromeWorker.xhtml | 58 + dom/workers/test/test_chromeWorkerJSM.xhtml | 54 + dom/workers/test/test_clearTimeouts.html | 31 + dom/workers/test/test_clearTimeoutsImplicit.html | 31 + dom/workers/test/test_console.html | 44 + dom/workers/test/test_consoleAndBlobs.html | 46 + dom/workers/test/test_consoleReplaceable.html | 44 + dom/workers/test/test_contentWorker.html | 48 + dom/workers/test/test_csp.html | 18 + dom/workers/test/test_csp.html^headers^ | 2 + dom/workers/test/test_csp.js | 54 + dom/workers/test/test_dataURLWorker.html | 30 + dom/workers/test/test_dynamicImport.html | 76 + .../test/test_dynamicImport_and_terminate.html | 34 + .../test/test_dynamicImport_early_termination.html | 79 + dom/workers/test/test_errorPropagation.html | 65 + dom/workers/test/test_errorwarning.html | 95 + dom/workers/test/test_eventDispatch.html | 32 + dom/workers/test/test_fibonacci.html | 51 + dom/workers/test/test_file.xhtml | 96 + dom/workers/test/test_fileBlobPosting.xhtml | 85 + dom/workers/test/test_fileBlobSubWorker.xhtml | 97 + dom/workers/test/test_filePosting.xhtml | 85 + dom/workers/test/test_fileReadSlice.xhtml | 93 + dom/workers/test/test_fileReaderSync.xhtml | 198 + dom/workers/test/test_fileReaderSyncErrors.xhtml | 83 + .../test/test_fileReaderSync_when_closing.html | 54 + dom/workers/test/test_fileSlice.xhtml | 105 + dom/workers/test/test_fileSubWorker.xhtml | 98 + dom/workers/test/test_importScripts.html | 53 + dom/workers/test/test_importScripts_3rdparty.html | 136 + .../test/test_importScripts_mixedcontent.html | 50 + dom/workers/test/test_instanceof.html | 40 + dom/workers/test/test_json.html | 89 + dom/workers/test/test_loadEncoding.html | 50 + dom/workers/test/test_loadError.html | 67 + dom/workers/test/test_location.html | 72 + dom/workers/test/test_longThread.html | 58 + dom/workers/test/test_multi_sharedWorker.html | 241 + .../test_multi_sharedWorker_lifetimes_bfcache.html | 151 + ...est_multi_sharedWorker_lifetimes_nobfcache.html | 126 + dom/workers/test/test_navigator.html | 27 + dom/workers/test/test_navigator.js | 10 + dom/workers/test/test_navigator_iframe.html | 24 + dom/workers/test/test_navigator_iframe.js | 64 + dom/workers/test/test_navigator_languages.html | 58 + dom/workers/test/test_navigator_secureContext.html | 27 + ...test_navigator_workers_hardwareConcurrency.html | 56 + dom/workers/test/test_newError.html | 33 + dom/workers/test/test_notification.html | 47 + dom/workers/test/test_notification_child.html | 46 + dom/workers/test/test_notification_permission.html | 48 + dom/workers/test/test_onLine.html | 73 + dom/workers/test/test_promise.html | 43 + .../test/test_promise_resolved_with_string.html | 41 + .../test/test_readableStream_when_closing.html | 61 + dom/workers/test/test_recursion.html | 69 + dom/workers/test/test_recursiveOnerror.html | 44 + dom/workers/test/test_referrer.html | 58 + dom/workers/test/test_referrer_header_worker.html | 38 + .../test/test_resolveWorker-assignment.html | 30 + dom/workers/test/test_resolveWorker.html | 31 + dom/workers/test/test_rvals.html | 35 + dom/workers/test/test_setTimeoutWith0.html | 19 + dom/workers/test/test_sharedWorker.html | 71 + dom/workers/test/test_sharedWorker_lifetime.html | 30 + dom/workers/test/test_sharedWorker_ports.html | 41 + .../test/test_sharedWorker_privateBrowsing.html | 102 + dom/workers/test/test_sharedWorker_thirdparty.html | 54 + .../test_sharedworker_event_listener_leaks.html | 51 + dom/workers/test/test_shutdownCheck.xhtml | 61 + dom/workers/test/test_simpleThread.html | 74 + dom/workers/test/test_sourcemap_header.html | 22 + dom/workers/test/test_subworkers_suspended.html | 145 + dom/workers/test/test_suspend.html | 188 + dom/workers/test/test_terminate.html | 100 + dom/workers/test/test_threadErrors.html | 64 + dom/workers/test/test_threadTimeouts.html | 60 + dom/workers/test/test_throwingOnerror.html | 54 + dom/workers/test/test_timeoutTracing.html | 47 + dom/workers/test/test_transferable.html | 123 + dom/workers/test/test_worker_interfaces.html | 19 + dom/workers/test/test_worker_interfaces.js | 543 ++ .../test/test_worker_interfaces_secureContext.html | 19 + dom/workers/test/threadErrors_worker1.js | 8 + dom/workers/test/threadErrors_worker2.js | 8 + dom/workers/test/threadErrors_worker3.js | 8 + dom/workers/test/threadErrors_worker4.js | 8 + dom/workers/test/threadTimeouts_worker.js | 44 + dom/workers/test/throwingOnerror_worker.js | 15 + dom/workers/test/timeoutTracing_worker.js | 15 + dom/workers/test/transferable_worker.js | 40 + dom/workers/test/window_suspended.html | 71 + dom/workers/test/worker_bug1278777.js | 9 + dom/workers/test/worker_bug1301094.js | 11 + dom/workers/test/worker_bug1824498.mjs | 4 + dom/workers/test/worker_consoleAndBlobs.js | 8 + dom/workers/test/worker_driver.js | 84 + dom/workers/test/worker_dynamicImport.mjs | 2 + dom/workers/test/worker_referrer.js | 9 + dom/workers/test/worker_setTimeoutWith0.js | 3 + dom/workers/test/worker_shutdownCheck.js | 1 + dom/workers/test/worker_suspended.js | 39 + dom/workers/test/worker_wrapper.js | 79 + dom/workers/test/xpcshell/data/chrome.manifest | 1 + dom/workers/test/xpcshell/data/worker.js | 6 + .../test/xpcshell/data/worker_fileReader.js | 7 + .../test/xpcshell/test_ext_redirects_sw_scripts.js | 415 ++ dom/workers/test/xpcshell/test_fileReader.js | 33 + .../test_remoteworker_launch_new_process.js | 94 + dom/workers/test/xpcshell/test_workers.js | 37 + .../test/xpcshell/test_workers_clone_error.js | 42 + dom/workers/test/xpcshell/xpcshell.ini | 28 + 434 files changed, 46721 insertions(+) create mode 100644 dom/workers/ChromeWorker.cpp create mode 100644 dom/workers/ChromeWorker.h create mode 100644 dom/workers/ChromeWorkerScope.cpp create mode 100644 dom/workers/ChromeWorkerScope.h create mode 100644 dom/workers/JSExecutionManager.cpp create mode 100644 dom/workers/JSExecutionManager.h create mode 100644 dom/workers/JSSettings.h create mode 100644 dom/workers/MessageEventRunnable.cpp create mode 100644 dom/workers/MessageEventRunnable.h create mode 100644 dom/workers/Queue.h create mode 100644 dom/workers/RegisterBindings.cpp create mode 100644 dom/workers/RuntimeService.cpp create mode 100644 dom/workers/RuntimeService.h create mode 100644 dom/workers/ScriptLoader.cpp create mode 100644 dom/workers/ScriptLoader.h create mode 100644 dom/workers/Worker.cpp create mode 100644 dom/workers/Worker.h create mode 100644 dom/workers/WorkerCSPEventListener.cpp create mode 100644 dom/workers/WorkerCSPEventListener.h create mode 100644 dom/workers/WorkerChannelInfo.cpp create mode 100644 dom/workers/WorkerChannelInfo.h create mode 100644 dom/workers/WorkerCommon.h create mode 100644 dom/workers/WorkerDebugger.cpp create mode 100644 dom/workers/WorkerDebugger.h create mode 100644 dom/workers/WorkerDebuggerManager.cpp create mode 100644 dom/workers/WorkerDebuggerManager.h create mode 100644 dom/workers/WorkerDocumentListener.cpp create mode 100644 dom/workers/WorkerDocumentListener.h create mode 100644 dom/workers/WorkerError.cpp create mode 100644 dom/workers/WorkerError.h create mode 100644 dom/workers/WorkerEventTarget.cpp create mode 100644 dom/workers/WorkerEventTarget.h create mode 100644 dom/workers/WorkerIPCUtils.h create mode 100644 dom/workers/WorkerLoadInfo.cpp create mode 100644 dom/workers/WorkerLoadInfo.h create mode 100644 dom/workers/WorkerLocation.cpp create mode 100644 dom/workers/WorkerLocation.h create mode 100644 dom/workers/WorkerNavigator.cpp create mode 100644 dom/workers/WorkerNavigator.h create mode 100644 dom/workers/WorkerPrivate.cpp create mode 100644 dom/workers/WorkerPrivate.h create mode 100644 dom/workers/WorkerRef.cpp create mode 100644 dom/workers/WorkerRef.h create mode 100644 dom/workers/WorkerRunnable.cpp create mode 100644 dom/workers/WorkerRunnable.h create mode 100644 dom/workers/WorkerScope.cpp create mode 100644 dom/workers/WorkerScope.h create mode 100644 dom/workers/WorkerStatus.h create mode 100644 dom/workers/WorkerTestUtils.cpp create mode 100644 dom/workers/WorkerTestUtils.h create mode 100644 dom/workers/WorkerThread.cpp create mode 100644 dom/workers/WorkerThread.h create mode 100644 dom/workers/loader/CacheLoadHandler.cpp create mode 100644 dom/workers/loader/CacheLoadHandler.h create mode 100644 dom/workers/loader/NetworkLoadHandler.cpp create mode 100644 dom/workers/loader/NetworkLoadHandler.h create mode 100644 dom/workers/loader/ScriptResponseHeaderProcessor.cpp create mode 100644 dom/workers/loader/ScriptResponseHeaderProcessor.h create mode 100644 dom/workers/loader/WorkerLoadContext.cpp create mode 100644 dom/workers/loader/WorkerLoadContext.h create mode 100644 dom/workers/loader/WorkerModuleLoader.cpp create mode 100644 dom/workers/loader/WorkerModuleLoader.h create mode 100644 dom/workers/loader/moz.build create mode 100644 dom/workers/moz.build create mode 100644 dom/workers/nsIWorkerChannelInfo.idl create mode 100644 dom/workers/nsIWorkerDebugger.idl create mode 100644 dom/workers/nsIWorkerDebuggerManager.idl create mode 100644 dom/workers/remoteworkers/PRemoteWorker.ipdl create mode 100644 dom/workers/remoteworkers/PRemoteWorkerController.ipdl create mode 100644 dom/workers/remoteworkers/PRemoteWorkerService.ipdl create mode 100644 dom/workers/remoteworkers/RemoteWorkerChild.cpp create mode 100644 dom/workers/remoteworkers/RemoteWorkerChild.h create mode 100644 dom/workers/remoteworkers/RemoteWorkerController.cpp create mode 100644 dom/workers/remoteworkers/RemoteWorkerController.h create mode 100644 dom/workers/remoteworkers/RemoteWorkerControllerChild.cpp create mode 100644 dom/workers/remoteworkers/RemoteWorkerControllerChild.h create mode 100644 dom/workers/remoteworkers/RemoteWorkerControllerParent.cpp create mode 100644 dom/workers/remoteworkers/RemoteWorkerControllerParent.h create mode 100644 dom/workers/remoteworkers/RemoteWorkerManager.cpp create mode 100644 dom/workers/remoteworkers/RemoteWorkerManager.h create mode 100644 dom/workers/remoteworkers/RemoteWorkerParent.cpp create mode 100644 dom/workers/remoteworkers/RemoteWorkerParent.h create mode 100644 dom/workers/remoteworkers/RemoteWorkerService.cpp create mode 100644 dom/workers/remoteworkers/RemoteWorkerService.h create mode 100644 dom/workers/remoteworkers/RemoteWorkerServiceChild.cpp create mode 100644 dom/workers/remoteworkers/RemoteWorkerServiceChild.h create mode 100644 dom/workers/remoteworkers/RemoteWorkerServiceParent.cpp create mode 100644 dom/workers/remoteworkers/RemoteWorkerServiceParent.h create mode 100644 dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh create mode 100644 dom/workers/remoteworkers/moz.build create mode 100644 dom/workers/sharedworkers/PSharedWorker.ipdl create mode 100644 dom/workers/sharedworkers/SharedWorker.cpp create mode 100644 dom/workers/sharedworkers/SharedWorker.h create mode 100644 dom/workers/sharedworkers/SharedWorkerChild.cpp create mode 100644 dom/workers/sharedworkers/SharedWorkerChild.h create mode 100644 dom/workers/sharedworkers/SharedWorkerManager.cpp create mode 100644 dom/workers/sharedworkers/SharedWorkerManager.h create mode 100644 dom/workers/sharedworkers/SharedWorkerParent.cpp create mode 100644 dom/workers/sharedworkers/SharedWorkerParent.h create mode 100644 dom/workers/sharedworkers/SharedWorkerService.cpp create mode 100644 dom/workers/sharedworkers/SharedWorkerService.h create mode 100644 dom/workers/sharedworkers/moz.build create mode 100644 dom/workers/test/404_server.sjs create mode 100644 dom/workers/test/WorkerDebugger.console_childWorker.js create mode 100644 dom/workers/test/WorkerDebugger.console_debugger.js create mode 100644 dom/workers/test/WorkerDebugger.console_worker.js create mode 100644 dom/workers/test/WorkerDebugger.initialize_childWorker.js create mode 100644 dom/workers/test/WorkerDebugger.initialize_debugger.js create mode 100644 dom/workers/test/WorkerDebugger.initialize_debugger_es_worker.js create mode 100644 dom/workers/test/WorkerDebugger.initialize_es_worker.js create mode 100644 dom/workers/test/WorkerDebugger.initialize_worker.js create mode 100644 dom/workers/test/WorkerDebugger.postMessage_childWorker.js create mode 100644 dom/workers/test/WorkerDebugger.postMessage_debugger.js create mode 100644 dom/workers/test/WorkerDebugger.postMessage_worker.js create mode 100644 dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_debugger.js create mode 100644 dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_sandbox.js create mode 100644 dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_worker.js create mode 100644 dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js create mode 100644 dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js create mode 100644 dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js create mode 100644 dom/workers/test/WorkerDebuggerGlobalScope.reportError_childWorker.js create mode 100644 dom/workers/test/WorkerDebuggerGlobalScope.reportError_debugger.js create mode 100644 dom/workers/test/WorkerDebuggerGlobalScope.reportError_worker.js create mode 100644 dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_debugger.js create mode 100644 dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_worker.js create mode 100644 dom/workers/test/WorkerDebuggerManager_childWorker.js create mode 100644 dom/workers/test/WorkerDebuggerManager_worker.js create mode 100644 dom/workers/test/WorkerDebugger_childWorker.js create mode 100644 dom/workers/test/WorkerDebugger_frozen_window1.html create mode 100644 dom/workers/test/WorkerDebugger_frozen_window2.html create mode 100644 dom/workers/test/WorkerDebugger_frozen_worker1.js create mode 100644 dom/workers/test/WorkerDebugger_frozen_worker2.js create mode 100644 dom/workers/test/WorkerDebugger_promise_debugger.js create mode 100644 dom/workers/test/WorkerDebugger_promise_worker.js create mode 100644 dom/workers/test/WorkerDebugger_sharedWorker.js create mode 100644 dom/workers/test/WorkerDebugger_suspended_debugger.js create mode 100644 dom/workers/test/WorkerDebugger_suspended_worker.js create mode 100644 dom/workers/test/WorkerDebugger_worker.js create mode 100644 dom/workers/test/WorkerTest.jsm create mode 100644 dom/workers/test/WorkerTest_badworker.js create mode 100644 dom/workers/test/WorkerTest_subworker.js create mode 100644 dom/workers/test/WorkerTest_worker.js create mode 100644 dom/workers/test/atob_worker.js create mode 100644 dom/workers/test/blank.html create mode 100644 dom/workers/test/browser.ini create mode 100644 dom/workers/test/browser_WorkerDebugger.initialize.js create mode 100644 dom/workers/test/browser_bug1047663.js create mode 100644 dom/workers/test/browser_bug1104623.js create mode 100644 dom/workers/test/browser_consoleSharedWorkers.js create mode 100644 dom/workers/test/browser_fileURL.js create mode 100644 dom/workers/test/browser_privilegedmozilla_remoteworker.js create mode 100644 dom/workers/test/browser_serviceworker_fetch_new_process.js create mode 100644 dom/workers/test/browser_worker_use_counters.js create mode 100644 dom/workers/test/bug1014466_data1.txt create mode 100644 dom/workers/test/bug1014466_data2.txt create mode 100644 dom/workers/test/bug1014466_worker.js create mode 100644 dom/workers/test/bug1020226_frame.html create mode 100644 dom/workers/test/bug1020226_worker.js create mode 100644 dom/workers/test/bug1047663_tab.html create mode 100644 dom/workers/test/bug1047663_worker.sjs create mode 100644 dom/workers/test/bug1060621_worker.js create mode 100644 dom/workers/test/bug1062920_worker.js create mode 100644 dom/workers/test/bug1063538.sjs create mode 100644 dom/workers/test/bug1063538_worker.js create mode 100644 dom/workers/test/bug1104064_worker.js create mode 100644 dom/workers/test/bug1132395_sharedWorker.js create mode 100644 dom/workers/test/bug1132924_worker.js create mode 100644 dom/workers/test/bug978260_worker.js create mode 100644 dom/workers/test/bug998474_worker.js create mode 100644 dom/workers/test/chrome.ini create mode 100644 dom/workers/test/chromeWorker_subworker.js create mode 100644 dom/workers/test/chromeWorker_worker.js create mode 100644 dom/workers/test/chromeWorker_worker.sys.mjs create mode 100644 dom/workers/test/chromeWorker_worker_submod.sys.mjs create mode 100644 dom/workers/test/clearTimeoutsImplicit_worker.js create mode 100644 dom/workers/test/clearTimeouts_worker.js create mode 100644 dom/workers/test/consoleReplaceable_worker.js create mode 100644 dom/workers/test/console_worker.js create mode 100644 dom/workers/test/content_worker.js create mode 100644 dom/workers/test/crashtests/1153636.html create mode 100644 dom/workers/test/crashtests/1158031.html create mode 100644 dom/workers/test/crashtests/1228456.html create mode 100644 dom/workers/test/crashtests/1348882.html create mode 100644 dom/workers/test/crashtests/1819146.html create mode 100644 dom/workers/test/crashtests/1821066.html create mode 100644 dom/workers/test/crashtests/779707.html create mode 100644 dom/workers/test/crashtests/943516.html create mode 100644 dom/workers/test/crashtests/crashtests.list create mode 100644 dom/workers/test/csp_worker.js create mode 100644 dom/workers/test/csp_worker.js^headers^ create mode 100644 dom/workers/test/dom_worker_helper.js create mode 100644 dom/workers/test/dynamicImport_nested.mjs create mode 100644 dom/workers/test/dynamicImport_postMessage.mjs create mode 100644 dom/workers/test/dynamicImport_worker.js create mode 100644 dom/workers/test/empty.html create mode 100644 dom/workers/test/empty_worker.js create mode 100644 dom/workers/test/errorPropagation_iframe.html create mode 100644 dom/workers/test/errorPropagation_worker.js create mode 100644 dom/workers/test/errorwarning_worker.js create mode 100644 dom/workers/test/eventDispatch_worker.js create mode 100644 dom/workers/test/fibonacci_worker.js create mode 100644 dom/workers/test/fileBlobSubWorker_worker.js create mode 100644 dom/workers/test/fileBlob_worker.js create mode 100644 dom/workers/test/filePosting_worker.js create mode 100644 dom/workers/test/fileReadSlice_worker.js create mode 100644 dom/workers/test/fileReaderSyncErrors_worker.js create mode 100644 dom/workers/test/fileReaderSync_worker.js create mode 100644 dom/workers/test/fileSlice_worker.js create mode 100644 dom/workers/test/fileSubWorker_worker.js create mode 100644 dom/workers/test/file_bug1010784_worker.js create mode 100644 dom/workers/test/file_service_worker.js create mode 100644 dom/workers/test/file_service_worker_container.html create mode 100644 dom/workers/test/file_service_worker_fetch_synthetic.js create mode 100644 dom/workers/test/file_use_counter_service_worker.js create mode 100644 dom/workers/test/file_use_counter_shared_worker.js create mode 100644 dom/workers/test/file_use_counter_shared_worker_microtask.js create mode 100644 dom/workers/test/file_use_counter_worker.html create mode 100644 dom/workers/test/file_use_counter_worker.js create mode 100644 dom/workers/test/file_worker.js create mode 100644 dom/workers/test/foreign.js create mode 100644 dom/workers/test/head.js create mode 100644 dom/workers/test/importForeignScripts_worker.js create mode 100644 dom/workers/test/importScripts_3rdParty_worker.js create mode 100644 dom/workers/test/importScripts_mixedcontent.html create mode 100644 dom/workers/test/importScripts_worker.js create mode 100644 dom/workers/test/importScripts_worker_imported1.js create mode 100644 dom/workers/test/importScripts_worker_imported2.js create mode 100644 dom/workers/test/importScripts_worker_imported3.js create mode 100644 dom/workers/test/importScripts_worker_imported4.js create mode 100644 dom/workers/test/instanceof_worker.js create mode 100644 dom/workers/test/invalid.js create mode 100644 dom/workers/test/json_worker.js create mode 100644 dom/workers/test/loadEncoding_worker.js create mode 100644 dom/workers/test/location_worker.js create mode 100644 dom/workers/test/longThread_worker.js create mode 100644 dom/workers/test/marionette/manifest.ini create mode 100644 dom/workers/test/marionette/service_worker_utils.py create mode 100644 dom/workers/test/marionette/test_service_workers_at_startup.py create mode 100644 dom/workers/test/marionette/test_service_workers_disabled.py create mode 100644 dom/workers/test/mochitest.ini create mode 100644 dom/workers/test/multi_sharedWorker_frame.html create mode 100644 dom/workers/test/multi_sharedWorker_frame_bfcache.html create mode 100644 dom/workers/test/multi_sharedWorker_frame_nobfcache.html create mode 100644 dom/workers/test/multi_sharedWorker_frame_nobfcache.html^headers^ create mode 100644 dom/workers/test/multi_sharedWorker_manager.js create mode 100644 dom/workers/test/multi_sharedWorker_sharedWorker.js create mode 100644 dom/workers/test/navigate.html create mode 100644 dom/workers/test/navigator_languages_worker.js create mode 100644 dom/workers/test/navigator_worker.js create mode 100644 dom/workers/test/newError_worker.js create mode 100644 dom/workers/test/notification_permission_worker.js create mode 100644 dom/workers/test/notification_worker.js create mode 100644 dom/workers/test/notification_worker_child-child.js create mode 100644 dom/workers/test/notification_worker_child-parent.js create mode 100644 dom/workers/test/onLine_worker.js create mode 100644 dom/workers/test/onLine_worker_child.js create mode 100644 dom/workers/test/onLine_worker_head.js create mode 100644 dom/workers/test/promise_worker.js create mode 100644 dom/workers/test/recursion_worker.js create mode 100644 dom/workers/test/recursiveOnerror_worker.js create mode 100644 dom/workers/test/redirect_to_foreign.sjs create mode 100644 dom/workers/test/referrer.sjs create mode 100644 dom/workers/test/referrer_test_server.sjs create mode 100644 dom/workers/test/referrer_worker.html create mode 100644 dom/workers/test/rvals_worker.js create mode 100644 dom/workers/test/script_createFile.js create mode 100644 dom/workers/test/server_fetch_synthetic.sjs create mode 100644 dom/workers/test/sharedWorker_console.js create mode 100644 dom/workers/test/sharedWorker_lifetime.js create mode 100644 dom/workers/test/sharedWorker_ports.js create mode 100644 dom/workers/test/sharedWorker_privateBrowsing.js create mode 100644 dom/workers/test/sharedWorker_sharedWorker.js create mode 100644 dom/workers/test/sharedWorker_thirdparty_frame.html create mode 100644 dom/workers/test/sharedWorker_thirdparty_window.html create mode 100644 dom/workers/test/simpleThread_worker.js create mode 100644 dom/workers/test/sourcemap_header.js create mode 100644 dom/workers/test/sourcemap_header_debugger.js create mode 100644 dom/workers/test/sourcemap_header_iframe.html create mode 100644 dom/workers/test/sourcemap_header_worker.js create mode 100644 dom/workers/test/sourcemap_header_worker.js^headers^ create mode 100644 dom/workers/test/suspend_blank.html create mode 100644 dom/workers/test/suspend_window.html create mode 100644 dom/workers/test/suspend_worker.js create mode 100644 dom/workers/test/terminate_worker.js create mode 100644 dom/workers/test/test_404.html create mode 100644 dom/workers/test/test_WorkerDebugger.initialize.xhtml create mode 100644 dom/workers/test/test_WorkerDebugger.postMessage.xhtml create mode 100644 dom/workers/test/test_WorkerDebugger.xhtml create mode 100644 dom/workers/test/test_WorkerDebuggerGlobalScope.createSandbox.xhtml create mode 100644 dom/workers/test/test_WorkerDebuggerGlobalScope.enterEventLoop.xhtml create mode 100644 dom/workers/test/test_WorkerDebuggerGlobalScope.reportError.xhtml create mode 100644 dom/workers/test/test_WorkerDebuggerGlobalScope.setImmediate.xhtml create mode 100644 dom/workers/test/test_WorkerDebuggerManager.xhtml create mode 100644 dom/workers/test/test_WorkerDebugger_console.xhtml create mode 100644 dom/workers/test/test_WorkerDebugger_frozen.xhtml create mode 100644 dom/workers/test/test_WorkerDebugger_promise.xhtml create mode 100644 dom/workers/test/test_WorkerDebugger_suspended.xhtml create mode 100644 dom/workers/test/test_atob.html create mode 100644 dom/workers/test/test_blobConstructor.html create mode 100644 dom/workers/test/test_blobWorkers.html create mode 100644 dom/workers/test/test_bug1002702.html create mode 100644 dom/workers/test/test_bug1010784.html create mode 100644 dom/workers/test/test_bug1014466.html create mode 100644 dom/workers/test/test_bug1020226.html create mode 100644 dom/workers/test/test_bug1036484.html create mode 100644 dom/workers/test/test_bug1060621.html create mode 100644 dom/workers/test/test_bug1062920.html create mode 100644 dom/workers/test/test_bug1062920.xhtml create mode 100644 dom/workers/test/test_bug1063538.html create mode 100644 dom/workers/test/test_bug1104064.html create mode 100644 dom/workers/test/test_bug1132395.html create mode 100644 dom/workers/test/test_bug1132924.html create mode 100644 dom/workers/test/test_bug1278777.html create mode 100644 dom/workers/test/test_bug1301094.html create mode 100644 dom/workers/test/test_bug1317725.html create mode 100644 dom/workers/test/test_bug1317725.js create mode 100644 dom/workers/test/test_bug1824498.html create mode 100644 dom/workers/test/test_bug949946.html create mode 100644 dom/workers/test/test_bug978260.html create mode 100644 dom/workers/test/test_bug998474.html create mode 100644 dom/workers/test/test_chromeWorker.html create mode 100644 dom/workers/test/test_chromeWorker.xhtml create mode 100644 dom/workers/test/test_chromeWorkerJSM.xhtml create mode 100644 dom/workers/test/test_clearTimeouts.html create mode 100644 dom/workers/test/test_clearTimeoutsImplicit.html create mode 100644 dom/workers/test/test_console.html create mode 100644 dom/workers/test/test_consoleAndBlobs.html create mode 100644 dom/workers/test/test_consoleReplaceable.html create mode 100644 dom/workers/test/test_contentWorker.html create mode 100644 dom/workers/test/test_csp.html create mode 100644 dom/workers/test/test_csp.html^headers^ create mode 100644 dom/workers/test/test_csp.js create mode 100644 dom/workers/test/test_dataURLWorker.html create mode 100644 dom/workers/test/test_dynamicImport.html create mode 100644 dom/workers/test/test_dynamicImport_and_terminate.html create mode 100644 dom/workers/test/test_dynamicImport_early_termination.html create mode 100644 dom/workers/test/test_errorPropagation.html create mode 100644 dom/workers/test/test_errorwarning.html create mode 100644 dom/workers/test/test_eventDispatch.html create mode 100644 dom/workers/test/test_fibonacci.html create mode 100644 dom/workers/test/test_file.xhtml create mode 100644 dom/workers/test/test_fileBlobPosting.xhtml create mode 100644 dom/workers/test/test_fileBlobSubWorker.xhtml create mode 100644 dom/workers/test/test_filePosting.xhtml create mode 100644 dom/workers/test/test_fileReadSlice.xhtml create mode 100644 dom/workers/test/test_fileReaderSync.xhtml create mode 100644 dom/workers/test/test_fileReaderSyncErrors.xhtml create mode 100644 dom/workers/test/test_fileReaderSync_when_closing.html create mode 100644 dom/workers/test/test_fileSlice.xhtml create mode 100644 dom/workers/test/test_fileSubWorker.xhtml create mode 100644 dom/workers/test/test_importScripts.html create mode 100644 dom/workers/test/test_importScripts_3rdparty.html create mode 100644 dom/workers/test/test_importScripts_mixedcontent.html create mode 100644 dom/workers/test/test_instanceof.html create mode 100644 dom/workers/test/test_json.html create mode 100644 dom/workers/test/test_loadEncoding.html create mode 100644 dom/workers/test/test_loadError.html create mode 100644 dom/workers/test/test_location.html create mode 100644 dom/workers/test/test_longThread.html create mode 100644 dom/workers/test/test_multi_sharedWorker.html create mode 100644 dom/workers/test/test_multi_sharedWorker_lifetimes_bfcache.html create mode 100644 dom/workers/test/test_multi_sharedWorker_lifetimes_nobfcache.html create mode 100644 dom/workers/test/test_navigator.html create mode 100644 dom/workers/test/test_navigator.js create mode 100644 dom/workers/test/test_navigator_iframe.html create mode 100644 dom/workers/test/test_navigator_iframe.js create mode 100644 dom/workers/test/test_navigator_languages.html create mode 100644 dom/workers/test/test_navigator_secureContext.html create mode 100644 dom/workers/test/test_navigator_workers_hardwareConcurrency.html create mode 100644 dom/workers/test/test_newError.html create mode 100644 dom/workers/test/test_notification.html create mode 100644 dom/workers/test/test_notification_child.html create mode 100644 dom/workers/test/test_notification_permission.html create mode 100644 dom/workers/test/test_onLine.html create mode 100644 dom/workers/test/test_promise.html create mode 100644 dom/workers/test/test_promise_resolved_with_string.html create mode 100644 dom/workers/test/test_readableStream_when_closing.html create mode 100644 dom/workers/test/test_recursion.html create mode 100644 dom/workers/test/test_recursiveOnerror.html create mode 100644 dom/workers/test/test_referrer.html create mode 100644 dom/workers/test/test_referrer_header_worker.html create mode 100644 dom/workers/test/test_resolveWorker-assignment.html create mode 100644 dom/workers/test/test_resolveWorker.html create mode 100644 dom/workers/test/test_rvals.html create mode 100644 dom/workers/test/test_setTimeoutWith0.html create mode 100644 dom/workers/test/test_sharedWorker.html create mode 100644 dom/workers/test/test_sharedWorker_lifetime.html create mode 100644 dom/workers/test/test_sharedWorker_ports.html create mode 100644 dom/workers/test/test_sharedWorker_privateBrowsing.html create mode 100644 dom/workers/test/test_sharedWorker_thirdparty.html create mode 100644 dom/workers/test/test_sharedworker_event_listener_leaks.html create mode 100644 dom/workers/test/test_shutdownCheck.xhtml create mode 100644 dom/workers/test/test_simpleThread.html create mode 100644 dom/workers/test/test_sourcemap_header.html create mode 100644 dom/workers/test/test_subworkers_suspended.html create mode 100644 dom/workers/test/test_suspend.html create mode 100644 dom/workers/test/test_terminate.html create mode 100644 dom/workers/test/test_threadErrors.html create mode 100644 dom/workers/test/test_threadTimeouts.html create mode 100644 dom/workers/test/test_throwingOnerror.html create mode 100644 dom/workers/test/test_timeoutTracing.html create mode 100644 dom/workers/test/test_transferable.html create mode 100644 dom/workers/test/test_worker_interfaces.html create mode 100644 dom/workers/test/test_worker_interfaces.js create mode 100644 dom/workers/test/test_worker_interfaces_secureContext.html create mode 100644 dom/workers/test/threadErrors_worker1.js create mode 100644 dom/workers/test/threadErrors_worker2.js create mode 100644 dom/workers/test/threadErrors_worker3.js create mode 100644 dom/workers/test/threadErrors_worker4.js create mode 100644 dom/workers/test/threadTimeouts_worker.js create mode 100644 dom/workers/test/throwingOnerror_worker.js create mode 100644 dom/workers/test/timeoutTracing_worker.js create mode 100644 dom/workers/test/transferable_worker.js create mode 100644 dom/workers/test/window_suspended.html create mode 100644 dom/workers/test/worker_bug1278777.js create mode 100644 dom/workers/test/worker_bug1301094.js create mode 100644 dom/workers/test/worker_bug1824498.mjs create mode 100644 dom/workers/test/worker_consoleAndBlobs.js create mode 100644 dom/workers/test/worker_driver.js create mode 100644 dom/workers/test/worker_dynamicImport.mjs create mode 100644 dom/workers/test/worker_referrer.js create mode 100644 dom/workers/test/worker_setTimeoutWith0.js create mode 100644 dom/workers/test/worker_shutdownCheck.js create mode 100644 dom/workers/test/worker_suspended.js create mode 100644 dom/workers/test/worker_wrapper.js create mode 100644 dom/workers/test/xpcshell/data/chrome.manifest create mode 100644 dom/workers/test/xpcshell/data/worker.js create mode 100644 dom/workers/test/xpcshell/data/worker_fileReader.js create mode 100644 dom/workers/test/xpcshell/test_ext_redirects_sw_scripts.js create mode 100644 dom/workers/test/xpcshell/test_fileReader.js create mode 100644 dom/workers/test/xpcshell/test_remoteworker_launch_new_process.js create mode 100644 dom/workers/test/xpcshell/test_workers.js create mode 100644 dom/workers/test/xpcshell/test_workers_clone_error.js create mode 100644 dom/workers/test/xpcshell/xpcshell.ini (limited to 'dom/workers') diff --git a/dom/workers/ChromeWorker.cpp b/dom/workers/ChromeWorker.cpp new file mode 100644 index 0000000000..7fda84f7f5 --- /dev/null +++ b/dom/workers/ChromeWorker.cpp @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ChromeWorker.h" + +#include "mozilla/AppShutdown.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/WorkerBinding.h" +#include "nsContentUtils.h" +#include "nsIXPConnect.h" +#include "WorkerPrivate.h" + +namespace mozilla::dom { + +/* static */ +already_AddRefed ChromeWorker::Constructor( + const GlobalObject& aGlobal, const nsAString& aScriptURL, + const WorkerOptions& aOptions, ErrorResult& aRv) { + // Dump the JS stack if somebody's creating a ChromeWorker after shutdown has + // begun. See bug 1813353. + if (xpc::IsInAutomation() && + AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown)) { + NS_WARNING("ChromeWorker construction during shutdown"); + nsCOMPtr xpc = nsIXPConnect::XPConnect(); + Unused << xpc->DebugDumpJSStack(true, true, false); + } + + JSContext* cx = aGlobal.Context(); + + RefPtr workerPrivate = WorkerPrivate::Constructor( + cx, aScriptURL, true /* aIsChromeWorker */, WorkerKindDedicated, + RequestCredentials::Omit, aOptions.mType, aOptions.mName, VoidCString(), + nullptr /*aLoadInfo */, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + nsCOMPtr globalObject = + do_QueryInterface(aGlobal.GetAsSupports()); + + RefPtr worker = + new ChromeWorker(globalObject, workerPrivate.forget()); + return worker.forget(); +} + +/* static */ +bool ChromeWorker::WorkerAvailable(JSContext* aCx, JSObject* /* unused */) { + // Chrome is always allowed to use workers, and content is never + // allowed to use ChromeWorker, so all we have to check is the + // caller. However, chrome workers apparently might not have a + // system principal, so we have to check for them manually. + if (NS_IsMainThread()) { + return nsContentUtils::IsSystemCaller(aCx); + } + + return GetWorkerPrivateFromContext(aCx)->IsChromeWorker(); +} + +ChromeWorker::ChromeWorker(nsIGlobalObject* aGlobalObject, + already_AddRefed aWorkerPrivate) + : Worker(aGlobalObject, std::move(aWorkerPrivate)) {} + +ChromeWorker::~ChromeWorker() = default; + +JSObject* ChromeWorker::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + JS::Rooted wrapper( + aCx, ChromeWorker_Binding::Wrap(aCx, this, aGivenProto)); + if (wrapper) { + // Most DOM objects don't assume they have a reflector. If they don't have + // one and need one, they create it. But in workers code, we assume that the + // reflector is always present. In order to guarantee that it's always + // present, we have to preserve it. Otherwise the GC will happily collect it + // as needed. + MOZ_ALWAYS_TRUE(TryPreserveWrapper(wrapper)); + } + + return wrapper; +} + +} // namespace mozilla::dom diff --git a/dom/workers/ChromeWorker.h b/dom/workers/ChromeWorker.h new file mode 100644 index 0000000000..04265f4a31 --- /dev/null +++ b/dom/workers/ChromeWorker.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_ChromeWorker_h +#define mozilla_dom_ChromeWorker_h + +#include "mozilla/dom/Worker.h" + +namespace mozilla::dom { + +class ChromeWorker final : public Worker { + public: + static already_AddRefed Constructor( + const GlobalObject& aGlobal, const nsAString& aScriptURL, + const WorkerOptions& aOptions, ErrorResult& aRv); + + static bool WorkerAvailable(JSContext* aCx, JSObject* /* unused */); + + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + private: + ChromeWorker(nsIGlobalObject* aGlobalObject, + already_AddRefed aWorkerPrivate); + ~ChromeWorker(); +}; + +} // namespace mozilla::dom + +#endif /* mozilla_dom_ChromeWorker_h */ diff --git a/dom/workers/ChromeWorkerScope.cpp b/dom/workers/ChromeWorkerScope.cpp new file mode 100644 index 0000000000..e2ec334016 --- /dev/null +++ b/dom/workers/ChromeWorkerScope.cpp @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ChromeWorkerScope.h" + +#include "jsapi.h" +#include "js/PropertyAndElement.h" // JS_GetProperty +#include "js/experimental/CTypes.h" // JS::InitCTypesClass, JS::CTypesCallbacks, JS::SetCTypesCallbacks +#include "js/MemoryFunctions.h" + +#include "nsNativeCharsetUtils.h" +#include "nsString.h" + +namespace mozilla::dom { + +namespace { + +#ifdef BUILD_CTYPES + +char* UnicodeToNative(JSContext* aCx, const char16_t* aSource, + size_t aSourceLen) { + nsDependentSubstring unicode(aSource, aSourceLen); + + nsAutoCString native; + if (NS_FAILED(NS_CopyUnicodeToNative(unicode, native))) { + JS_ReportErrorASCII(aCx, "Could not convert string to native charset!"); + return nullptr; + } + + char* result = static_cast(JS_malloc(aCx, native.Length() + 1)); + if (!result) { + return nullptr; + } + + memcpy(result, native.get(), native.Length()); + result[native.Length()] = 0; + return result; +} + +#endif // BUILD_CTYPES + +} // namespace + +bool DefineChromeWorkerFunctions(JSContext* aCx, + JS::Handle aGlobal) { + // Currently ctypes is the only special property given to ChromeWorkers. +#ifdef BUILD_CTYPES + { + JS::Rooted ctypes(aCx); + if (!JS::InitCTypesClass(aCx, aGlobal) || + !JS_GetProperty(aCx, aGlobal, "ctypes", &ctypes)) { + return false; + } + + static const JS::CTypesCallbacks callbacks = {UnicodeToNative}; + + JS::SetCTypesCallbacks(ctypes.toObjectOrNull(), &callbacks); + } +#endif // BUILD_CTYPES + + return true; +} + +} // namespace mozilla::dom diff --git a/dom/workers/ChromeWorkerScope.h b/dom/workers/ChromeWorkerScope.h new file mode 100644 index 0000000000..be415a0403 --- /dev/null +++ b/dom/workers/ChromeWorkerScope.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workers_chromeworkerscope_h__ +#define mozilla_dom_workers_chromeworkerscope_h__ + +#include "js/TypeDecls.h" + +namespace mozilla::dom { + +bool DefineChromeWorkerFunctions(JSContext* aCx, JS::Handle aGlobal); + +} // namespace mozilla::dom + +#endif // mozilla_dom_workers_chromeworkerscope_h__ diff --git a/dom/workers/JSExecutionManager.cpp b/dom/workers/JSExecutionManager.cpp new file mode 100644 index 0000000000..4a67a3a3d2 --- /dev/null +++ b/dom/workers/JSExecutionManager.cpp @@ -0,0 +1,251 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/JSExecutionManager.h" + +#include "WorkerCommon.h" +#include "WorkerPrivate.h" + +#include "mozilla/dom/DocGroup.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPtr.h" + +namespace mozilla::dom { + +JSExecutionManager* JSExecutionManager::mCurrentMTManager; + +const uint32_t kTimeSliceExpirationMS = 50; + +using RequestState = JSExecutionManager::RequestState; + +static StaticRefPtr sSABSerializationManager; + +void JSExecutionManager::Initialize() { + if (StaticPrefs::dom_workers_serialized_sab_access()) { + sSABSerializationManager = MakeRefPtr(1); + } +} + +void JSExecutionManager::Shutdown() { sSABSerializationManager = nullptr; } + +JSExecutionManager* JSExecutionManager::GetSABSerializationManager() { + return sSABSerializationManager.get(); +} + +RequestState JSExecutionManager::RequestJSThreadExecution() { + if (NS_IsMainThread()) { + return RequestJSThreadExecutionMainThread(); + } + + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + + if (!workerPrivate || workerPrivate->GetExecutionGranted()) { + return RequestState::ExecutingAlready; + } + + MutexAutoLock lock(mExecutionQueueMutex); + MOZ_ASSERT(mMaxRunning >= mRunning); + + if ((mExecutionQueue.size() + (mMainThreadAwaitingExecution ? 1 : 0)) < + size_t(mMaxRunning - mRunning)) { + // There's slots ready for things to run, execute right away. + workerPrivate->SetExecutionGranted(true); + workerPrivate->ScheduleTimeSliceExpiration(kTimeSliceExpirationMS); + + mRunning++; + return RequestState::Granted; + } + + mExecutionQueue.push_back(workerPrivate); + + TimeStamp waitStart = TimeStamp::Now(); + + while (mRunning >= mMaxRunning || (workerPrivate != mExecutionQueue.front() || + mMainThreadAwaitingExecution)) { + // If there is no slots available, the main thread is awaiting permission + // or we are not first in line for execution, wait until notified. + mExecutionQueueCondVar.Wait(TimeDuration::FromMilliseconds(500)); + if ((TimeStamp::Now() - waitStart) > TimeDuration::FromSeconds(20)) { + // Crash so that these types of situations are actually caught in the + // crash reporter. + MOZ_CRASH(); + } + } + + workerPrivate->SetExecutionGranted(true); + workerPrivate->ScheduleTimeSliceExpiration(kTimeSliceExpirationMS); + + mExecutionQueue.pop_front(); + mRunning++; + if (mRunning < mMaxRunning) { + // If a thread woke up before that wasn't first in line it will have gone + // back to sleep, if there's more slots available, wake it now. + mExecutionQueueCondVar.NotifyAll(); + } + + return RequestState::Granted; +} + +void JSExecutionManager::YieldJSThreadExecution() { + if (NS_IsMainThread()) { + MOZ_ASSERT(mMainThreadIsExecuting); + mMainThreadIsExecuting = false; + + MutexAutoLock lock(mExecutionQueueMutex); + mRunning--; + mExecutionQueueCondVar.NotifyAll(); + return; + } + + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + + if (!workerPrivate) { + return; + } + + MOZ_ASSERT(workerPrivate->GetExecutionGranted()); + + workerPrivate->CancelTimeSliceExpiration(); + + MutexAutoLock lock(mExecutionQueueMutex); + mRunning--; + mExecutionQueueCondVar.NotifyAll(); + workerPrivate->SetExecutionGranted(false); +} + +bool JSExecutionManager::YieldJSThreadExecutionIfGranted() { + if (NS_IsMainThread()) { + if (mMainThreadIsExecuting) { + YieldJSThreadExecution(); + return true; + } + return false; + } + + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + + if (workerPrivate && workerPrivate->GetExecutionGranted()) { + YieldJSThreadExecution(); + return true; + } + + return false; +} + +RequestState JSExecutionManager::RequestJSThreadExecutionMainThread() { + MOZ_ASSERT(NS_IsMainThread()); + + if (mMainThreadIsExecuting) { + return RequestState::ExecutingAlready; + } + + MutexAutoLock lock(mExecutionQueueMutex); + MOZ_ASSERT(mMaxRunning >= mRunning); + + if ((mMaxRunning - mRunning) > 0) { + // If there's any slots available run, the main thread always takes + // precedence over any worker threads. + mRunning++; + mMainThreadIsExecuting = true; + return RequestState::Granted; + } + + mMainThreadAwaitingExecution = true; + + TimeStamp waitStart = TimeStamp::Now(); + + while (mRunning >= mMaxRunning) { + if ((TimeStamp::Now() - waitStart) > TimeDuration::FromSeconds(20)) { + // Crash so that these types of situations are actually caught in the + // crash reporter. + MOZ_CRASH(); + } + mExecutionQueueCondVar.Wait(TimeDuration::FromMilliseconds(500)); + } + + mMainThreadAwaitingExecution = false; + mMainThreadIsExecuting = true; + + mRunning++; + if (mRunning < mMaxRunning) { + // If a thread woke up before that wasn't first in line it will have gone + // back to sleep, if there's more slots available, wake it now. + mExecutionQueueCondVar.NotifyAll(); + } + + return RequestState::Granted; +} + +AutoRequestJSThreadExecution::AutoRequestJSThreadExecution( + nsIGlobalObject* aGlobalObject, bool aIsMainThread) { + JSExecutionManager* manager = nullptr; + + mIsMainThread = aIsMainThread; + if (mIsMainThread) { + mOldGrantingManager = JSExecutionManager::mCurrentMTManager; + + nsPIDOMWindowInner* innerWindow = nullptr; + if (aGlobalObject) { + innerWindow = aGlobalObject->AsInnerWindow(); + } + + DocGroup* docGroup = nullptr; + if (innerWindow) { + docGroup = innerWindow->GetDocGroup(); + } + + if (docGroup) { + manager = docGroup->GetExecutionManager(); + } + + if (JSExecutionManager::mCurrentMTManager == manager) { + return; + } + + if (JSExecutionManager::mCurrentMTManager) { + JSExecutionManager::mCurrentMTManager->YieldJSThreadExecution(); + JSExecutionManager::mCurrentMTManager = nullptr; + } + } else { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + + if (workerPrivate) { + manager = workerPrivate->GetExecutionManager(); + } + } + + if (manager && + (manager->RequestJSThreadExecution() == RequestState::Granted)) { + if (NS_IsMainThread()) { + // Make sure we restore permission on destruction if needed. + JSExecutionManager::mCurrentMTManager = manager; + } + mExecutionGrantingManager = std::move(manager); + } +} + +AutoYieldJSThreadExecution::AutoYieldJSThreadExecution() { + JSExecutionManager* manager = nullptr; + + if (NS_IsMainThread()) { + manager = JSExecutionManager::mCurrentMTManager; + } else { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + + if (workerPrivate) { + manager = workerPrivate->GetExecutionManager(); + } + } + + if (manager && manager->YieldJSThreadExecutionIfGranted()) { + mExecutionGrantingManager = std::move(manager); + if (NS_IsMainThread()) { + JSExecutionManager::mCurrentMTManager = nullptr; + } + } +} + +} // namespace mozilla::dom diff --git a/dom/workers/JSExecutionManager.h b/dom/workers/JSExecutionManager.h new file mode 100644 index 0000000000..132884bf53 --- /dev/null +++ b/dom/workers/JSExecutionManager.h @@ -0,0 +1,192 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workers_jsexecutionmanager_h__ +#define mozilla_dom_workers_jsexecutionmanager_h__ + +#include +#include +#include "MainThreadUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/CondVar.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "nsISupports.h" + +class nsIGlobalObject; +namespace mozilla { + +class ErrorResult; + +namespace dom { +class WorkerPrivate; + +// The code in this file is responsible for throttling JS execution. It does +// this by introducing a JSExecutionManager class. An execution manager may be +// applied to any number of worker threads or a DocGroup on the main thread. +// +// JS environments associated with a JS execution manager may only execute on a +// certain amount of CPU cores in parallel. +// +// Whenever the main thread, or a worker thread begins executing JS it should +// make sure AutoRequestJSThreadExecution is present on the stack, in practice +// this is done by it being part of AutoEntryScript. +// +// Whenever the main thread may end up blocking on the activity of a worker +// thread, it should make sure to have an AutoYieldJSThreadExecution object +// on the stack. +// +// Whenever a worker thread may end up blocking on the main thread or the +// activity of another worker thread, it should make sure to have an +// AutoYieldJSThreadExecution object on the stack. +// +// Failure to do this may result in a deadlock. When a deadlock occurs due +// to these circumstances we will crash after 20 seconds. +// +// For the main thread this class should only be used in the case of an +// emergency surrounding exploitability of SharedArrayBuffers. A single +// execution manager will then be shared between all Workers and the main +// thread doc group containing the SharedArrayBuffer and ensure all this code +// only runs in a serialized manner. On the main thread we therefore may have +// 1 execution manager per DocGroup, as this is the granularity at which +// SharedArrayBuffers may be present. + +class AutoRequestJSThreadExecution; +class AutoYieldJSThreadExecution; + +// This class is used to regulate JS execution when for whatever reason we wish +// to throttle execution of multiple JS environments to occur with a certain +// maximum of synchronously executing threads. This should be used through +// the stack helper classes. +class JSExecutionManager { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JSExecutionManager) + + explicit JSExecutionManager(int32_t aMaxRunning = 1) + : mMaxRunning(aMaxRunning) {} + + enum class RequestState { Granted, ExecutingAlready }; + + static void Initialize(); + static void Shutdown(); + + static JSExecutionManager* GetSABSerializationManager(); + + private: + friend class AutoRequestJSThreadExecution; + friend class AutoYieldJSThreadExecution; + ~JSExecutionManager() = default; + + // Methods used by Auto*JSThreadExecution + + // Request execution permission, returns ExecutingAlready if execution was + // already granted or does not apply to this thread. + RequestState RequestJSThreadExecution(); + + // Yield JS execution, this asserts that permission is actually granted. + void YieldJSThreadExecution(); + + // Yield JS execution if permission was granted. This returns false if no + // permission is granted. This method is needed because an execution manager + // may have been set in between the ctor and dtor of + // AutoYieldJSThreadExecution. + bool YieldJSThreadExecutionIfGranted(); + + RequestState RequestJSThreadExecutionMainThread(); + + // Execution manager currently managing the main thread. + // MainThread access only. + static JSExecutionManager* mCurrentMTManager; + + // Workers waiting to be given permission for execution. + // Guarded by mExecutionQueueMutex. + std::deque mExecutionQueue + MOZ_GUARDED_BY(mExecutionQueueMutex); + + // Number of threads currently executing concurrently for this manager. + // Guarded by mExecutionQueueMutex. + int32_t mRunning MOZ_GUARDED_BY(mExecutionQueueMutex) = 0; + + // Number of threads allowed to run concurrently for environments managed + // by this manager. + // Guarded by mExecutionQueueMutex. + int32_t mMaxRunning MOZ_GUARDED_BY(mExecutionQueueMutex) = 1; + + // Mutex that guards the execution queue and associated state. + Mutex mExecutionQueueMutex = + Mutex{"JSExecutionManager::sExecutionQueueMutex"}; + + // ConditionVariables that blocked threads wait for. + CondVar mExecutionQueueCondVar = + CondVar{mExecutionQueueMutex, "JSExecutionManager::sExecutionQueueMutex"}; + + // Whether the main thread is currently executing for this manager. + // MainThread access only. + bool mMainThreadIsExecuting = false; + + // Whether the main thread is currently awaiting permission to execute. Main + // thread execution is always prioritized. + // Guarded by mExecutionQueueMutex. + bool mMainThreadAwaitingExecution MOZ_GUARDED_BY(mExecutionQueueMutex) = + false; +}; + +// Helper for managing execution requests and allowing re-entrant permission +// requests. +class MOZ_STACK_CLASS AutoRequestJSThreadExecution { + public: + explicit AutoRequestJSThreadExecution(nsIGlobalObject* aGlobalObject, + bool aIsMainThread); + + ~AutoRequestJSThreadExecution() { + if (mExecutionGrantingManager) { + mExecutionGrantingManager->YieldJSThreadExecution(); + } + if (mIsMainThread) { + if (mOldGrantingManager) { + mOldGrantingManager->RequestJSThreadExecution(); + } + JSExecutionManager::mCurrentMTManager = mOldGrantingManager; + } + } + + private: + // The manager we obtained permission from. nullptr if permission was already + // granted. + RefPtr mExecutionGrantingManager; + // The manager we had permission from before, and where permission should be + // re-requested upon destruction. + RefPtr mOldGrantingManager; + + // We store this for performance reasons. + bool mIsMainThread; +}; + +// Class used to wrap code which essentially exits JS execution and may block +// on other threads. +class MOZ_STACK_CLASS AutoYieldJSThreadExecution { + public: + AutoYieldJSThreadExecution(); + + ~AutoYieldJSThreadExecution() { + if (mExecutionGrantingManager) { + mExecutionGrantingManager->RequestJSThreadExecution(); + if (NS_IsMainThread()) { + JSExecutionManager::mCurrentMTManager = mExecutionGrantingManager; + } + } + } + + private: + // Set to the granting manager if we were granted permission here. + RefPtr mExecutionGrantingManager; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_workers_jsexecutionmanager_h__ diff --git a/dom/workers/JSSettings.h b/dom/workers/JSSettings.h new file mode 100644 index 0000000000..af2247a847 --- /dev/null +++ b/dom/workers/JSSettings.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workerinternals_JSSettings_h +#define mozilla_dom_workerinternals_JSSettings_h + +#include + +#include "js/ContextOptions.h" +#include "js/GCAPI.h" +#include "js/RealmOptions.h" +#include "mozilla/Maybe.h" +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla::dom::workerinternals { + +// Random unique constant to facilitate JSPrincipal debugging +const uint32_t kJSPrincipalsDebugToken = 0x7e2df9d2; + +struct JSSettings { + struct JSGCSetting { + JSGCParamKey key; + // Nothing() represents the default value, the result of calling + // JS_ResetGCParameter. + Maybe value; + + // For the IndexOf call below. + bool operator==(JSGCParamKey k) const { return key == k; } + }; + + JS::RealmOptions chromeRealmOptions; + JS::RealmOptions contentRealmOptions; + CopyableTArray gcSettings; + JS::ContextOptions contextOptions; + +#ifdef JS_GC_ZEAL + uint8_t gcZeal = 0; + uint32_t gcZealFrequency = 0; +#endif + + // Returns whether there was a change in the setting. + bool ApplyGCSetting(JSGCParamKey aKey, Maybe aValue) { + size_t index = gcSettings.IndexOf(aKey); + if (index == gcSettings.NoIndex) { + gcSettings.AppendElement(JSGCSetting{aKey, aValue}); + return true; + } + if (gcSettings[index].value != aValue) { + gcSettings[index].value = aValue; + return true; + } + return false; + } +}; + +} // namespace mozilla::dom::workerinternals + +#endif // mozilla_dom_workerinternals_JSSettings_h diff --git a/dom/workers/MessageEventRunnable.cpp b/dom/workers/MessageEventRunnable.cpp new file mode 100644 index 0000000000..9ead17b470 --- /dev/null +++ b/dom/workers/MessageEventRunnable.cpp @@ -0,0 +1,156 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MessageEventRunnable.h" + +#include "mozilla/dom/MessageEvent.h" +#include "mozilla/dom/MessageEventBinding.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/TimelineConsumers.h" +#include "mozilla/WorkerTimelineMarker.h" +#include "nsQueryObject.h" +#include "WorkerScope.h" + +namespace mozilla::dom { + +MessageEventRunnable::MessageEventRunnable(WorkerPrivate* aWorkerPrivate, + TargetAndBusyBehavior aBehavior) + : WorkerDebuggeeRunnable(aWorkerPrivate, aBehavior), + StructuredCloneHolder(CloningSupported, TransferringSupported, + StructuredCloneScope::SameProcess) {} + +bool MessageEventRunnable::DispatchDOMEvent(JSContext* aCx, + WorkerPrivate* aWorkerPrivate, + DOMEventTargetHelper* aTarget, + bool aIsMainThread) { + nsCOMPtr parent = aTarget->GetParentObject(); + + // For some workers without window, parent is null and we try to find it + // from the JS Context. + if (!parent) { + JS::Rooted globalObject(aCx, JS::CurrentGlobalOrNull(aCx)); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + parent = xpc::NativeGlobal(globalObject); + if (NS_WARN_IF(!parent)) { + return false; + } + } + + MOZ_ASSERT(parent); + + JS::Rooted messageData(aCx); + IgnoredErrorResult rv; + + UniquePtr start; + UniquePtr end; + bool isTimelineRecording = !TimelineConsumers::IsEmpty(); + + if (isTimelineRecording) { + start = MakeUnique( + aIsMainThread + ? ProfileTimelineWorkerOperationType::DeserializeDataOnMainThread + : ProfileTimelineWorkerOperationType::DeserializeDataOffMainThread, + MarkerTracingType::START); + } + + JS::CloneDataPolicy cloneDataPolicy; + if (parent->GetClientInfo().isSome() && + parent->GetClientInfo()->AgentClusterId().isSome() && + parent->GetClientInfo()->AgentClusterId()->Equals( + aWorkerPrivate->AgentClusterId())) { + cloneDataPolicy.allowIntraClusterClonableSharedObjects(); + } + + if (aWorkerPrivate->IsSharedMemoryAllowed()) { + cloneDataPolicy.allowSharedMemoryObjects(); + } + + Read(parent, aCx, &messageData, cloneDataPolicy, rv); + + if (isTimelineRecording) { + end = MakeUnique( + aIsMainThread + ? ProfileTimelineWorkerOperationType::DeserializeDataOnMainThread + : ProfileTimelineWorkerOperationType::DeserializeDataOffMainThread, + MarkerTracingType::END); + TimelineConsumers::AddMarkerForAllObservedDocShells(start); + TimelineConsumers::AddMarkerForAllObservedDocShells(end); + } + + if (NS_WARN_IF(rv.Failed())) { + DispatchError(aCx, aTarget); + return false; + } + + Sequence> ports; + if (!TakeTransferredPortsAsSequence(ports)) { + DispatchError(aCx, aTarget); + return false; + } + + RefPtr event = new MessageEvent(aTarget, nullptr, nullptr); + event->InitMessageEvent(nullptr, u"message"_ns, CanBubble::eNo, + Cancelable::eNo, messageData, u""_ns, u""_ns, nullptr, + ports); + + event->SetTrusted(true); + + aTarget->DispatchEvent(*event); + + return true; +} + +bool MessageEventRunnable::WorkerRun(JSContext* aCx, + WorkerPrivate* aWorkerPrivate) { + if (mBehavior == ParentThreadUnchangedBusyCount) { + // Don't fire this event if the JS object has been disconnected from the + // private object. + if (!aWorkerPrivate->IsAcceptingEvents()) { + return true; + } + + // Once a window has frozen its workers, their + // mMainThreadDebuggeeEventTargets should be paused, and their + // WorkerDebuggeeRunnables should not be being executed. The same goes for + // WorkerDebuggeeRunnables sent from child to parent workers, but since a + // frozen parent worker runs only control runnables anyway, that is taken + // care of naturally. + MOZ_ASSERT(!aWorkerPrivate->IsFrozen()); + + // Similarly for paused windows; all its workers should have been informed. + // (Subworkers are unaffected by paused windows.) + MOZ_ASSERT(!aWorkerPrivate->IsParentWindowPaused()); + + aWorkerPrivate->AssertInnerWindowIsCorrect(); + + return DispatchDOMEvent(aCx, aWorkerPrivate, + aWorkerPrivate->ParentEventTargetRef(), + !aWorkerPrivate->GetParent()); + } + + MOZ_ASSERT(aWorkerPrivate == GetWorkerPrivateFromContext(aCx)); + + return DispatchDOMEvent(aCx, aWorkerPrivate, aWorkerPrivate->GlobalScope(), + false); +} + +void MessageEventRunnable::DispatchError(JSContext* aCx, + DOMEventTargetHelper* aTarget) { + RootedDictionary init(aCx); + init.mBubbles = false; + init.mCancelable = false; + + RefPtr event = + MessageEvent::Constructor(aTarget, u"messageerror"_ns, init); + event->SetTrusted(true); + + aTarget->DispatchEvent(*event); +} + +} // namespace mozilla::dom diff --git a/dom/workers/MessageEventRunnable.h b/dom/workers/MessageEventRunnable.h new file mode 100644 index 0000000000..dcf0024b44 --- /dev/null +++ b/dom/workers/MessageEventRunnable.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workers_MessageEventRunnable_h +#define mozilla_dom_workers_MessageEventRunnable_h + +#include "WorkerCommon.h" +#include "WorkerRunnable.h" +#include "mozilla/dom/StructuredCloneHolder.h" + +namespace mozilla { + +class DOMEventTargetHelper; + +namespace dom { + +class MessageEventRunnable final : public WorkerDebuggeeRunnable, + public StructuredCloneHolder { + public: + MessageEventRunnable(WorkerPrivate* aWorkerPrivate, + TargetAndBusyBehavior aBehavior); + + bool DispatchDOMEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + DOMEventTargetHelper* aTarget, bool aIsMainThread); + + private: + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override; + + void DispatchError(JSContext* aCx, DOMEventTargetHelper* aTarget); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_workers_MessageEventRunnable_h diff --git a/dom/workers/Queue.h b/dom/workers/Queue.h new file mode 100644 index 0000000000..cf65932698 --- /dev/null +++ b/dom/workers/Queue.h @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workerinternal_Queue_h +#define mozilla_dom_workerinternal_Queue_h + +#include "mozilla/Mutex.h" +#include "nsTArray.h" + +namespace mozilla::dom::workerinternals { + +template +struct StorageWithTArray { + typedef AutoTArray StorageType; + + static void Reverse(StorageType& aStorage) { + uint32_t length = aStorage.Length(); + for (uint32_t index = 0; index < length / 2; index++) { + uint32_t reverseIndex = length - 1 - index; + + T t1 = aStorage.ElementAt(index); + T t2 = aStorage.ElementAt(reverseIndex); + + aStorage.ReplaceElementsAt(index, 1, t2); + aStorage.ReplaceElementsAt(reverseIndex, 1, t1); + } + } + + static bool IsEmpty(const StorageType& aStorage) { + return !!aStorage.IsEmpty(); + } + + static void Push(StorageType& aStorage, const T& aEntry) { + aStorage.AppendElement(aEntry); + } + + static bool Pop(StorageType& aStorage, T& aEntry) { + if (IsEmpty(aStorage)) { + return false; + } + + aEntry = aStorage.PopLastElement(); + return true; + } + + static void Clear(StorageType& aStorage) { aStorage.Clear(); } + + static void Compact(StorageType& aStorage) { aStorage.Compact(); } +}; + +class MOZ_CAPABILITY("mutex") LockingWithMutex { + mozilla::Mutex mMutex; + + protected: + LockingWithMutex() : mMutex("LockingWithMutex::mMutex") {} + + void Lock() MOZ_CAPABILITY_ACQUIRE() { mMutex.Lock(); } + + void Unlock() MOZ_CAPABILITY_RELEASE() { mMutex.Unlock(); } + + class MOZ_SCOPED_CAPABILITY AutoLock { + LockingWithMutex& mHost; + + public: + explicit AutoLock(LockingWithMutex& aHost) MOZ_CAPABILITY_ACQUIRE(aHost) + : mHost(aHost) { + mHost.Lock(); + } + + ~AutoLock() MOZ_CAPABILITY_RELEASE() { mHost.Unlock(); } + }; + + friend class AutoLock; +}; + +class NoLocking { + protected: + void Lock() {} + + void Unlock() {} + + class AutoLock { + public: + explicit AutoLock(NoLocking& aHost) {} + + ~AutoLock() = default; + }; +}; + +template > +class Queue : public LockingPolicy { + typedef typename StoragePolicy::StorageType StorageType; + typedef typename LockingPolicy::AutoLock AutoLock; + + StorageType mStorage1; + StorageType mStorage2; + + StorageType* mFront; + StorageType* mBack; + + public: + Queue() : mFront(&mStorage1), mBack(&mStorage2) {} + + bool IsEmpty() { + AutoLock lock(*this); + return StoragePolicy::IsEmpty(*mFront) && StoragePolicy::IsEmpty(*mBack); + } + + void Push(const T& aEntry) { + AutoLock lock(*this); + StoragePolicy::Push(*mBack, aEntry); + } + + bool Pop(T& aEntry) { + AutoLock lock(*this); + if (StoragePolicy::IsEmpty(*mFront)) { + StoragePolicy::Compact(*mFront); + StoragePolicy::Reverse(*mBack); + StorageType* tmp = mFront; + mFront = mBack; + mBack = tmp; + } + return StoragePolicy::Pop(*mFront, aEntry); + } + + void Clear() { + AutoLock lock(*this); + StoragePolicy::Clear(*mFront); + StoragePolicy::Clear(*mBack); + } + + // XXX Do we need this? + void Lock() { LockingPolicy::Lock(); } + + // XXX Do we need this? + void Unlock() { LockingPolicy::Unlock(); } + + private: + // Queue is not copyable. + Queue(const Queue&); + Queue& operator=(const Queue&); +}; + +} // namespace mozilla::dom::workerinternals + +#endif /* mozilla_dom_workerinternals_Queue_h*/ diff --git a/dom/workers/RegisterBindings.cpp b/dom/workers/RegisterBindings.cpp new file mode 100644 index 0000000000..efede2ba49 --- /dev/null +++ b/dom/workers/RegisterBindings.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WorkerPrivate.h" +#include "ChromeWorkerScope.h" + +#include "jsapi.h" +#include "mozilla/dom/DebuggerNotificationObserverBinding.h" +#include "mozilla/dom/RegisterWorkerBindings.h" +#include "mozilla/dom/RegisterWorkerDebuggerBindings.h" +#include "mozilla/OSFileConstants.h" + +using namespace mozilla::dom; + +bool WorkerPrivate::RegisterBindings(JSContext* aCx, + JS::Handle aGlobal) { + // Init Web IDL bindings + if (!RegisterWorkerBindings(aCx, aGlobal)) { + return false; + } + + if (IsChromeWorker()) { + if (!DefineChromeWorkerFunctions(aCx, aGlobal)) { + return false; + } + + RefPtr service = + OSFileConstantsService::GetOrCreate(); + if (!service->DefineOSFileConstants(aCx, aGlobal)) { + return false; + } + } + + return true; +} + +bool WorkerPrivate::RegisterDebuggerBindings(JSContext* aCx, + JS::Handle aGlobal) { + // Init Web IDL bindings + if (!RegisterWorkerDebuggerBindings(aCx, aGlobal)) { + return false; + } + + if (!ChromeUtils_Binding::GetConstructorObject(aCx)) { + return false; + } + + if (!DebuggerNotificationObserver_Binding::GetConstructorObject(aCx)) { + return false; + } + + if (!JS_DefineDebuggerObject(aCx, aGlobal)) { + return false; + } + + return true; +} diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp new file mode 100644 index 0000000000..51a497f929 --- /dev/null +++ b/dom/workers/RuntimeService.cpp @@ -0,0 +1,2414 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RuntimeService.h" + +#include "nsContentSecurityUtils.h" +#include "nsIContentSecurityPolicy.h" +#include "mozilla/dom/Document.h" +#include "nsIObserverService.h" +#include "nsIScriptContext.h" +#include "nsIStreamTransportService.h" +#include "nsISupportsPriority.h" +#include "nsITimer.h" +#include "nsIURI.h" +#include "nsIXULRuntime.h" +#include "nsPIDOMWindow.h" + +#include +#include "mozilla/ipc/BackgroundChild.h" +#include "GeckoProfiler.h" +#include "js/experimental/CTypes.h" // JS::CTypesActivityType, JS::SetCTypesActivityCallback +#include "jsfriendapi.h" +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/ContextOptions.h" +#include "js/Initialization.h" +#include "js/LocaleSensitive.h" +#include "js/WasmFeatures.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/CycleCollectedJSRuntime.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/dom/AtomList.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/ErrorEventBinding.h" +#include "mozilla/dom/EventTargetBinding.h" +#include "mozilla/dom/FetchUtil.h" +#include "mozilla/dom/MessageChannel.h" +#include "mozilla/dom/MessageEventBinding.h" +#include "mozilla/dom/PerformanceService.h" +#include "mozilla/dom/RemoteWorkerChild.h" +#include "mozilla/dom/WorkerBinding.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/ShadowRealmGlobalScope.h" +#include "mozilla/dom/IndexedDatabaseManager.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Preferences.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/dom/Navigator.h" +#include "mozilla/Monitor.h" +#include "nsContentUtils.h" +#include "nsCycleCollector.h" +#include "nsDOMJSUtils.h" +#include "nsISupportsImpl.h" +#include "nsLayoutStatics.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsXPCOM.h" +#include "nsXPCOMPrivate.h" +#include "OSFileConstants.h" +#include "xpcpublic.h" +#include "XPCSelfHostedShmem.h" + +#if defined(XP_MACOSX) +# include "nsMacUtilsImpl.h" +#endif + +#include "WorkerDebuggerManager.h" +#include "WorkerError.h" +#include "WorkerLoadInfo.h" +#include "WorkerRunnable.h" +#include "WorkerScope.h" +#include "WorkerThread.h" +#include "prsystem.h" + +#ifdef DEBUG +# include "nsICookieJarSettings.h" +#endif + +#define WORKERS_SHUTDOWN_TOPIC "web-workers-shutdown" + +static mozilla::LazyLogModule gWorkerShutdownDumpLog("WorkerShutdownDump"); + +#ifdef SHUTDOWN_LOG +# undef SHUTDOWN_LOG +#endif +#define SHUTDOWN_LOG(msg) MOZ_LOG(gWorkerShutdownDumpLog, LogLevel::Debug, msg); + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +using namespace workerinternals; + +namespace workerinternals { + +// The size of the worker runtime heaps in bytes. May be changed via pref. +#define WORKER_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024 + +// The size of the worker JS allocation threshold in MB. May be changed via +// pref. +#define WORKER_DEFAULT_ALLOCATION_THRESHOLD 30 + +// Half the size of the actual C stack, to be safe. +#define WORKER_CONTEXT_NATIVE_STACK_LIMIT 128 * sizeof(size_t) * 1024 + +// The maximum number of threads to use for workers, overridable via pref. +#define MAX_WORKERS_PER_DOMAIN 512 + +static_assert(MAX_WORKERS_PER_DOMAIN >= 1, + "We should allow at least one worker per domain."); + +#define PREF_WORKERS_PREFIX "dom.workers." +#define PREF_WORKERS_MAX_PER_DOMAIN PREF_WORKERS_PREFIX "maxPerDomain" + +#define GC_REQUEST_OBSERVER_TOPIC "child-gc-request" +#define CC_REQUEST_OBSERVER_TOPIC "child-cc-request" +#define MEMORY_PRESSURE_OBSERVER_TOPIC "memory-pressure" +#define LOW_MEMORY_DATA "low-memory" +#define LOW_MEMORY_ONGOING_DATA "low-memory-ongoing" +#define MEMORY_PRESSURE_STOP_OBSERVER_TOPIC "memory-pressure-stop" + +// Prefixes for observing preference changes. +#define PREF_JS_OPTIONS_PREFIX "javascript.options." +#define PREF_MEM_OPTIONS_PREFIX "mem." +#define PREF_GCZEAL "gczeal" + +static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID); + +namespace { + +const uint32_t kNoIndex = uint32_t(-1); + +uint32_t gMaxWorkersPerDomain = MAX_WORKERS_PER_DOMAIN; + +// Does not hold an owning reference. +Atomic gRuntimeService(nullptr); + +// Only true during the call to Init. +bool gRuntimeServiceDuringInit = false; + +class LiteralRebindingCString : public nsDependentCString { + public: + template + void RebindLiteral(const char (&aStr)[N]) { + Rebind(aStr, N - 1); + } +}; + +template +struct PrefTraits; + +template <> +struct PrefTraits { + using PrefValueType = bool; + + static inline PrefValueType Get(const char* aPref) { + AssertIsOnMainThread(); + return Preferences::GetBool(aPref); + } + + static inline bool Exists(const char* aPref) { + AssertIsOnMainThread(); + return Preferences::GetType(aPref) == nsIPrefBranch::PREF_BOOL; + } +}; + +template <> +struct PrefTraits { + using PrefValueType = int32_t; + + static inline PrefValueType Get(const char* aPref) { + AssertIsOnMainThread(); + return Preferences::GetInt(aPref); + } + + static inline bool Exists(const char* aPref) { + AssertIsOnMainThread(); + return Preferences::GetType(aPref) == nsIPrefBranch::PREF_INT; + } +}; + +template +T GetPref(const char* aFullPref, const T aDefault, bool* aPresent = nullptr) { + AssertIsOnMainThread(); + + using PrefHelper = PrefTraits; + + T result; + bool present = true; + + if (PrefHelper::Exists(aFullPref)) { + result = PrefHelper::Get(aFullPref); + } else { + result = aDefault; + present = false; + } + + if (aPresent) { + *aPresent = present; + } + return result; +} + +void LoadContextOptions(const char* aPrefName, void* /* aClosure */) { + AssertIsOnMainThread(); + + RuntimeService* rts = RuntimeService::GetService(); + if (!rts) { + // May be shutting down, just bail. + return; + } + + const nsDependentCString prefName(aPrefName); + + // Several other pref branches will get included here so bail out if there is + // another callback that will handle this change. + if (StringBeginsWith( + prefName, + nsLiteralCString(PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX))) { + return; + } + +#ifdef JS_GC_ZEAL + if (prefName.EqualsLiteral(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL)) { + return; + } +#endif + + JS::ContextOptions contextOptions; + xpc::SetPrefableContextOptions(contextOptions); + + nsCOMPtr xr = do_GetService("@mozilla.org/xre/runtime;1"); + if (xr) { + bool safeMode = false; + xr->GetInSafeMode(&safeMode); + if (safeMode) { + contextOptions.disableOptionsForSafeMode(); + } + } + + RuntimeService::SetDefaultContextOptions(contextOptions); + + if (rts) { + rts->UpdateAllWorkerContextOptions(); + } +} + +#ifdef JS_GC_ZEAL +void LoadGCZealOptions(const char* /* aPrefName */, void* /* aClosure */) { + AssertIsOnMainThread(); + + RuntimeService* rts = RuntimeService::GetService(); + if (!rts) { + // May be shutting down, just bail. + return; + } + + int32_t gczeal = GetPref(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL, -1); + if (gczeal < 0) { + gczeal = 0; + } + + int32_t frequency = + GetPref(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL ".frequency", -1); + if (frequency < 0) { + frequency = JS_DEFAULT_ZEAL_FREQ; + } + + RuntimeService::SetDefaultGCZeal(uint8_t(gczeal), uint32_t(frequency)); + + if (rts) { + rts->UpdateAllWorkerGCZeal(); + } +} +#endif + +void UpdateCommonJSGCMemoryOption(RuntimeService* aRuntimeService, + const char* aPrefName, JSGCParamKey aKey) { + AssertIsOnMainThread(); + NS_ASSERTION(aPrefName, "Null pref name!"); + + int32_t prefValue = GetPref(aPrefName, -1); + Maybe value = (prefValue < 0 || prefValue >= 10000) + ? Nothing() + : Some(uint32_t(prefValue)); + + RuntimeService::SetDefaultJSGCSettings(aKey, value); + + if (aRuntimeService) { + aRuntimeService->UpdateAllWorkerMemoryParameter(aKey, value); + } +} + +void UpdateOtherJSGCMemoryOption(RuntimeService* aRuntimeService, + JSGCParamKey aKey, Maybe aValue) { + AssertIsOnMainThread(); + + RuntimeService::SetDefaultJSGCSettings(aKey, aValue); + + if (aRuntimeService) { + aRuntimeService->UpdateAllWorkerMemoryParameter(aKey, aValue); + } +} + +void LoadJSGCMemoryOptions(const char* aPrefName, void* /* aClosure */) { + AssertIsOnMainThread(); + + RuntimeService* rts = RuntimeService::GetService(); + + if (!rts) { + // May be shutting down, just bail. + return; + } + + constexpr auto memPrefix = + nsLiteralCString{PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX}; + const nsDependentCString fullPrefName(aPrefName); + + // Pull out the string that actually distinguishes the parameter we need to + // change. + nsDependentCSubstring memPrefName; + if (StringBeginsWith(fullPrefName, memPrefix)) { + memPrefName.Rebind(fullPrefName, memPrefix.Length()); + } else { + NS_ERROR("Unknown pref name!"); + return; + } + + struct WorkerGCPref { + nsLiteralCString memName; + const char* fullName; + JSGCParamKey key; + }; + +#define PREF(suffix_, key_) \ + { \ + nsLiteralCString(PREF_MEM_OPTIONS_PREFIX suffix_), \ + PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX suffix_, key_ \ + } + constexpr WorkerGCPref kWorkerPrefs[] = { + PREF("max", JSGC_MAX_BYTES), + PREF("gc_high_frequency_time_limit_ms", JSGC_HIGH_FREQUENCY_TIME_LIMIT), + PREF("gc_low_frequency_heap_growth", JSGC_LOW_FREQUENCY_HEAP_GROWTH), + PREF("gc_high_frequency_large_heap_growth", + JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH), + PREF("gc_high_frequency_small_heap_growth", + JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH), + PREF("gc_small_heap_size_max_mb", JSGC_SMALL_HEAP_SIZE_MAX), + PREF("gc_large_heap_size_min_mb", JSGC_LARGE_HEAP_SIZE_MIN), + PREF("gc_balanced_heap_limits", JSGC_BALANCED_HEAP_LIMITS_ENABLED), + PREF("gc_heap_growth_factor", JSGC_HEAP_GROWTH_FACTOR), + PREF("gc_allocation_threshold_mb", JSGC_ALLOCATION_THRESHOLD), + PREF("gc_malloc_threshold_base_mb", JSGC_MALLOC_THRESHOLD_BASE), + PREF("gc_small_heap_incremental_limit", + JSGC_SMALL_HEAP_INCREMENTAL_LIMIT), + PREF("gc_large_heap_incremental_limit", + JSGC_LARGE_HEAP_INCREMENTAL_LIMIT), + PREF("gc_urgent_threshold_mb", JSGC_URGENT_THRESHOLD_MB), + PREF("gc_incremental_slice_ms", JSGC_SLICE_TIME_BUDGET_MS), + PREF("gc_min_empty_chunk_count", JSGC_MIN_EMPTY_CHUNK_COUNT), + PREF("gc_max_empty_chunk_count", JSGC_MAX_EMPTY_CHUNK_COUNT), + PREF("gc_compacting", JSGC_COMPACTING_ENABLED), + }; +#undef PREF + + auto pref = kWorkerPrefs; + auto end = kWorkerPrefs + ArrayLength(kWorkerPrefs); + + if (gRuntimeServiceDuringInit) { + // During init, we want to update every pref in kWorkerPrefs. + MOZ_ASSERT(memPrefName.IsEmpty(), + "Pref branch prefix only expected during init"); + } else { + // Otherwise, find the single pref that changed. + while (pref != end) { + if (pref->memName == memPrefName) { + end = pref + 1; + break; + } + ++pref; + } +#ifdef DEBUG + if (pref == end) { + nsAutoCString message("Workers don't support the '"); + message.Append(memPrefName); + message.AppendLiteral("' preference!"); + NS_WARNING(message.get()); + } +#endif + } + + while (pref != end) { + switch (pref->key) { + case JSGC_MAX_BYTES: { + int32_t prefValue = GetPref(pref->fullName, -1); + Maybe value = (prefValue <= 0 || prefValue >= 0x1000) + ? Nothing() + : Some(uint32_t(prefValue) * 1024 * 1024); + UpdateOtherJSGCMemoryOption(rts, pref->key, value); + break; + } + case JSGC_SLICE_TIME_BUDGET_MS: { + int32_t prefValue = GetPref(pref->fullName, -1); + Maybe value = (prefValue <= 0 || prefValue >= 100000) + ? Nothing() + : Some(uint32_t(prefValue)); + UpdateOtherJSGCMemoryOption(rts, pref->key, value); + break; + } + case JSGC_COMPACTING_ENABLED: + case JSGC_PARALLEL_MARKING_ENABLED: + case JSGC_BALANCED_HEAP_LIMITS_ENABLED: { + bool present; + bool prefValue = GetPref(pref->fullName, false, &present); + Maybe value = present ? Some(prefValue ? 1 : 0) : Nothing(); + UpdateOtherJSGCMemoryOption(rts, pref->key, value); + break; + } + case JSGC_HIGH_FREQUENCY_TIME_LIMIT: + case JSGC_LOW_FREQUENCY_HEAP_GROWTH: + case JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH: + case JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH: + case JSGC_SMALL_HEAP_SIZE_MAX: + case JSGC_LARGE_HEAP_SIZE_MIN: + case JSGC_ALLOCATION_THRESHOLD: + case JSGC_MALLOC_THRESHOLD_BASE: + case JSGC_SMALL_HEAP_INCREMENTAL_LIMIT: + case JSGC_LARGE_HEAP_INCREMENTAL_LIMIT: + case JSGC_URGENT_THRESHOLD_MB: + case JSGC_MIN_EMPTY_CHUNK_COUNT: + case JSGC_MAX_EMPTY_CHUNK_COUNT: + case JSGC_HEAP_GROWTH_FACTOR: + UpdateCommonJSGCMemoryOption(rts, pref->fullName, pref->key); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown JSGCParamKey value"); + break; + } + ++pref; + } +} + +bool InterruptCallback(JSContext* aCx) { + WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); + MOZ_ASSERT(worker); + + // Now is a good time to turn on profiling if it's pending. + PROFILER_JS_INTERRUPT_CALLBACK(); + + return worker->InterruptCallback(aCx); +} + +class LogViolationDetailsRunnable final : public WorkerMainThreadRunnable { + uint16_t mViolationType; + nsString mFileName; + uint32_t mLineNum; + uint32_t mColumnNum; + nsString mScriptSample; + + public: + LogViolationDetailsRunnable(WorkerPrivate* aWorker, uint16_t aViolationType, + const nsString& aFileName, uint32_t aLineNum, + uint32_t aColumnNum, + const nsAString& aScriptSample) + : WorkerMainThreadRunnable(aWorker, + "RuntimeService :: LogViolationDetails"_ns), + mViolationType(aViolationType), + mFileName(aFileName), + mLineNum(aLineNum), + mColumnNum(aColumnNum), + mScriptSample(aScriptSample) { + MOZ_ASSERT(aWorker); + } + + virtual bool MainThreadRun() override; + + private: + ~LogViolationDetailsRunnable() = default; +}; + +bool ContentSecurityPolicyAllows(JSContext* aCx, JS::RuntimeCode aKind, + JS::Handle aCode) { + WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); + worker->AssertIsOnWorkerThread(); + + bool evalOK; + bool reportViolation; + uint16_t violationType; + nsAutoJSString scriptSample; + if (aKind == JS::RuntimeCode::JS) { + if (NS_WARN_IF(!scriptSample.init(aCx, aCode))) { + JS_ClearPendingException(aCx); + return false; + } + + if (!nsContentSecurityUtils::IsEvalAllowed( + aCx, worker->UsesSystemPrincipal(), scriptSample)) { + return false; + } + + evalOK = worker->IsEvalAllowed(); + reportViolation = worker->GetReportEvalCSPViolations(); + violationType = nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL; + } else { + evalOK = worker->IsWasmEvalAllowed(); + reportViolation = worker->GetReportWasmEvalCSPViolations(); + violationType = nsIContentSecurityPolicy::VIOLATION_TYPE_WASM_EVAL; + } + + if (reportViolation) { + nsString fileName; + uint32_t lineNum = 0; + uint32_t columnNum = 0; + + JS::AutoFilename file; + if (JS::DescribeScriptedCaller(aCx, &file, &lineNum, &columnNum) && + file.get()) { + CopyUTF8toUTF16(MakeStringSpan(file.get()), fileName); + } else { + MOZ_ASSERT(!JS_IsExceptionPending(aCx)); + } + + RefPtr runnable = + new LogViolationDetailsRunnable(worker, violationType, fileName, + lineNum, columnNum, scriptSample); + + ErrorResult rv; + runnable->Dispatch(Killing, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + } + } + + return evalOK; +} + +void CTypesActivityCallback(JSContext* aCx, JS::CTypesActivityType aType) { + WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); + worker->AssertIsOnWorkerThread(); + + switch (aType) { + case JS::CTypesActivityType::BeginCall: + worker->BeginCTypesCall(); + break; + + case JS::CTypesActivityType::EndCall: + worker->EndCTypesCall(); + break; + + case JS::CTypesActivityType::BeginCallback: + worker->BeginCTypesCallback(); + break; + + case JS::CTypesActivityType::EndCallback: + worker->EndCTypesCallback(); + break; + + default: + MOZ_CRASH("Unknown type flag!"); + } +} + +// JSDispatchableRunnables are WorkerRunnables used to dispatch JS::Dispatchable +// back to their worker thread. A WorkerRunnable is used for two reasons: +// +// 1. The JS::Dispatchable::run() callback may run JS so we cannot use a control +// runnable since they use async interrupts and break JS run-to-completion. +// +// 2. The DispatchToEventLoopCallback interface is *required* to fail during +// shutdown (see jsapi.h) which is exactly what WorkerRunnable::Dispatch() will +// do. Moreover, JS_DestroyContext() does *not* block on JS::Dispatchable::run +// being called, DispatchToEventLoopCallback failure is expected to happen +// during shutdown. +class JSDispatchableRunnable final : public WorkerRunnable { + JS::Dispatchable* mDispatchable; + + ~JSDispatchableRunnable() { MOZ_ASSERT(!mDispatchable); } + + // Disable the usual pre/post-dispatch thread assertions since we are + // dispatching from some random JS engine internal thread: + + bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { return true; } + + void PostDispatch(WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) override { + // For the benefit of the destructor assert. + if (!aDispatchResult) { + mDispatchable = nullptr; + } + } + + public: + JSDispatchableRunnable(WorkerPrivate* aWorkerPrivate, + JS::Dispatchable* aDispatchable) + : WorkerRunnable(aWorkerPrivate, + WorkerRunnable::WorkerThreadUnchangedBusyCount), + mDispatchable(aDispatchable) { + MOZ_ASSERT(mDispatchable); + } + + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { + MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate); + MOZ_ASSERT(aCx == mWorkerPrivate->GetJSContext()); + MOZ_ASSERT(mDispatchable); + + AutoJSAPI jsapi; + jsapi.Init(); + + mDispatchable->run(mWorkerPrivate->GetJSContext(), + JS::Dispatchable::NotShuttingDown); + mDispatchable = nullptr; // mDispatchable may delete itself + + return true; + } + + nsresult Cancel() override { + // We need to check first if cancel is called twice + nsresult rv = WorkerRunnable::Cancel(); + NS_ENSURE_SUCCESS(rv, rv); + + MOZ_ASSERT(mDispatchable); + + AutoJSAPI jsapi; + jsapi.Init(); + + mDispatchable->run(mWorkerPrivate->GetJSContext(), + JS::Dispatchable::ShuttingDown); + mDispatchable = nullptr; // mDispatchable may delete itself + + return NS_OK; + } +}; + +static bool DispatchToEventLoop(void* aClosure, + JS::Dispatchable* aDispatchable) { + // This callback may execute either on the worker thread or a random + // JS-internal helper thread. + + // See comment at JS::InitDispatchToEventLoop() below for how we know the + // WorkerPrivate is alive. + WorkerPrivate* workerPrivate = reinterpret_cast(aClosure); + + // Dispatch is expected to fail during shutdown for the reasons outlined in + // the JSDispatchableRunnable comment above. + RefPtr r = + new JSDispatchableRunnable(workerPrivate, aDispatchable); + return r->Dispatch(); +} + +static bool ConsumeStream(JSContext* aCx, JS::Handle aObj, + JS::MimeType aMimeType, + JS::StreamConsumer* aConsumer) { + WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); + if (!worker) { + JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, + JSMSG_WASM_ERROR_CONSUMING_RESPONSE); + return false; + } + + return FetchUtil::StreamResponseToJS(aCx, aObj, aMimeType, aConsumer, worker); +} + +bool InitJSContextForWorker(WorkerPrivate* aWorkerPrivate, + JSContext* aWorkerCx) { + aWorkerPrivate->AssertIsOnWorkerThread(); + NS_ASSERTION(!aWorkerPrivate->GetJSContext(), "Already has a context!"); + + JSSettings settings; + aWorkerPrivate->CopyJSSettings(settings); + + JS::ContextOptionsRef(aWorkerCx) = settings.contextOptions; + + // This is the real place where we set the max memory for the runtime. + for (const auto& setting : settings.gcSettings) { + if (setting.value) { + JS_SetGCParameter(aWorkerCx, setting.key, *setting.value); + } else { + JS_ResetGCParameter(aWorkerCx, setting.key); + } + } + + JS_SetNativeStackQuota(aWorkerCx, WORKER_CONTEXT_NATIVE_STACK_LIMIT); + + // Security policy: + static const JSSecurityCallbacks securityCallbacks = { + ContentSecurityPolicyAllows}; + JS_SetSecurityCallbacks(aWorkerCx, &securityCallbacks); + + // A WorkerPrivate lives strictly longer than its JSRuntime so we can safely + // store a raw pointer as the callback's closure argument on the JSRuntime. + JS::InitDispatchToEventLoop(aWorkerCx, DispatchToEventLoop, + (void*)aWorkerPrivate); + + JS::InitConsumeStreamCallback(aWorkerCx, ConsumeStream, + FetchUtil::ReportJSStreamError); + + // When available, set the self-hosted shared memory to be read, so that we + // can decode the self-hosted content instead of parsing it. + auto& shm = xpc::SelfHostedShmem::GetSingleton(); + JS::SelfHostedCache selfHostedContent = shm.Content(); + + if (!JS::InitSelfHostedCode(aWorkerCx, selfHostedContent)) { + NS_WARNING("Could not init self-hosted code!"); + return false; + } + + JS_AddInterruptCallback(aWorkerCx, InterruptCallback); + + JS::SetCTypesActivityCallback(aWorkerCx, CTypesActivityCallback); + +#ifdef JS_GC_ZEAL + JS_SetGCZeal(aWorkerCx, settings.gcZeal, settings.gcZealFrequency); +#endif + + return true; +} + +static bool PreserveWrapper(JSContext* cx, JS::Handle obj) { + MOZ_ASSERT(cx); + MOZ_ASSERT(obj); + MOZ_ASSERT(mozilla::dom::IsDOMObject(obj)); + + return mozilla::dom::TryPreserveWrapper(obj); +} + +static bool IsWorkerDebuggerGlobalOrSandbox(JS::Handle aGlobal) { + return IsWorkerDebuggerGlobal(aGlobal) || IsWorkerDebuggerSandbox(aGlobal); +} + +JSObject* Wrap(JSContext* cx, JS::Handle existing, + JS::Handle obj) { + JS::Rooted targetGlobal(cx, JS::CurrentGlobalOrNull(cx)); + + // Note: the JS engine unwraps CCWs before calling this callback. + JS::Rooted originGlobal(cx, JS::GetNonCCWObjectGlobal(obj)); + + const js::Wrapper* wrapper = nullptr; + if (IsWorkerDebuggerGlobalOrSandbox(targetGlobal) && + IsWorkerDebuggerGlobalOrSandbox(originGlobal)) { + wrapper = &js::CrossCompartmentWrapper::singleton; + } else { + wrapper = &js::OpaqueCrossCompartmentWrapper::singleton; + } + + if (existing) { + js::Wrapper::Renew(existing, obj, wrapper); + } + return js::Wrapper::New(cx, obj, wrapper); +} + +static const JSWrapObjectCallbacks WrapObjectCallbacks = { + Wrap, + nullptr, +}; + +class WorkerJSRuntime final : public mozilla::CycleCollectedJSRuntime { + public: + // The heap size passed here doesn't matter, we will change it later in the + // call to JS_SetGCParameter inside InitJSContextForWorker. + explicit WorkerJSRuntime(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + : CycleCollectedJSRuntime(aCx), mWorkerPrivate(aWorkerPrivate) { + MOZ_COUNT_CTOR_INHERITED(WorkerJSRuntime, CycleCollectedJSRuntime); + MOZ_ASSERT(aWorkerPrivate); + + { + JS::UniqueChars defaultLocale = aWorkerPrivate->AdoptDefaultLocale(); + MOZ_ASSERT(defaultLocale, + "failure of a WorkerPrivate to have a default locale should " + "have made the worker fail to spawn"); + + if (!JS_SetDefaultLocale(Runtime(), defaultLocale.get())) { + NS_WARNING("failed to set workerCx's default locale"); + } + } + } + + void Shutdown(JSContext* cx) override { + // The CC is shut down, and the superclass destructor will GC, so make sure + // we don't try to CC again. + mWorkerPrivate = nullptr; + + CycleCollectedJSRuntime::Shutdown(cx); + } + + ~WorkerJSRuntime() { + MOZ_COUNT_DTOR_INHERITED(WorkerJSRuntime, CycleCollectedJSRuntime); + } + + virtual void PrepareForForgetSkippable() override {} + + virtual void BeginCycleCollectionCallback( + mozilla::CCReason aReason) override {} + + virtual void EndCycleCollectionCallback( + CycleCollectorResults& aResults) override {} + + void DispatchDeferredDeletion(bool aContinuation, bool aPurge) override { + MOZ_ASSERT(!aContinuation); + + // Do it immediately, no need for asynchronous behavior here. + nsCycleCollector_doDeferredDeletion(); + } + + virtual void CustomGCCallback(JSGCStatus aStatus) override { + if (!mWorkerPrivate) { + // We're shutting down, no need to do anything. + return; + } + + mWorkerPrivate->AssertIsOnWorkerThread(); + + if (aStatus == JSGC_END) { + bool collectedAnything = + nsCycleCollector_collect(CCReason::GC_FINISHED, nullptr); + mWorkerPrivate->SetCCCollectedAnything(collectedAnything); + } + } + + private: + WorkerPrivate* mWorkerPrivate; +}; + +} // anonymous namespace + +} // namespace workerinternals + +class WorkerJSContext final : public mozilla::CycleCollectedJSContext { + public: + // The heap size passed here doesn't matter, we will change it later in the + // call to JS_SetGCParameter inside InitJSContextForWorker. + explicit WorkerJSContext(WorkerPrivate* aWorkerPrivate) + : mWorkerPrivate(aWorkerPrivate) { + MOZ_COUNT_CTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext); + MOZ_ASSERT(aWorkerPrivate); + // Magical number 2. Workers have the base recursion depth 1, and normal + // runnables run at level 2, and we don't want to process microtasks + // at any other level. + SetTargetedMicroTaskRecursionDepth(2); + } + + // MOZ_CAN_RUN_SCRIPT_BOUNDARY because otherwise we have to annotate the + // SpiderMonkey JS::JobQueue's destructor as MOZ_CAN_RUN_SCRIPT, which is a + // bit of a pain. + MOZ_CAN_RUN_SCRIPT_BOUNDARY ~WorkerJSContext() { + MOZ_COUNT_DTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext); + JSContext* cx = MaybeContext(); + if (!cx) { + return; // Initialize() must have failed + } + + // We expect to come here with the cycle collector already shut down. + // The superclass destructor will run the GC one final time and finalize any + // JSObjects that were participating in cycles that were broken during CC + // shutdown. + // Make sure we don't try to CC again. + mWorkerPrivate = nullptr; + } + + WorkerJSContext* GetAsWorkerJSContext() override { return this; } + + CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) override { + return new WorkerJSRuntime(aCx, mWorkerPrivate); + } + + nsresult Initialize(JSRuntime* aParentRuntime) { + nsresult rv = CycleCollectedJSContext::Initialize( + aParentRuntime, WORKER_DEFAULT_RUNTIME_HEAPSIZE); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + JSContext* cx = Context(); + + js::SetPreserveWrapperCallbacks(cx, PreserveWrapper, HasReleasedWrapper); + JS_InitDestroyPrincipalsCallback(cx, nsJSPrincipals::Destroy); + JS_InitReadPrincipalsCallback(cx, nsJSPrincipals::ReadPrincipals); + JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks); + if (mWorkerPrivate->IsDedicatedWorker()) { + JS_SetFutexCanWait(cx); + } + + return NS_OK; + } + + virtual void DispatchToMicroTask( + already_AddRefed aRunnable) override { + RefPtr runnable(aRunnable); + + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(runnable); + + std::deque>* microTaskQueue = nullptr; + + JSContext* cx = Context(); + NS_ASSERTION(cx, "This should never be null!"); + + JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); + NS_ASSERTION(global, "This should never be null!"); + + // On worker threads, if the current global is the worker global or + // ShadowRealm global, we use the main micro task queue. Otherwise, the + // current global must be either the debugger global or a debugger sandbox, + // and we use the debugger micro task queue instead. + if (IsWorkerGlobal(global) || IsShadowRealmGlobal(global)) { + microTaskQueue = &GetMicroTaskQueue(); + } else { + MOZ_ASSERT(IsWorkerDebuggerGlobal(global) || + IsWorkerDebuggerSandbox(global)); + + microTaskQueue = &GetDebuggerMicroTaskQueue(); + } + + JS::JobQueueMayNotBeEmpty(cx); + microTaskQueue->push_back(std::move(runnable)); + } + + bool IsSystemCaller() const override { + return mWorkerPrivate->UsesSystemPrincipal(); + } + + void ReportError(JSErrorReport* aReport, + JS::ConstUTF8CharsZ aToStringResult) override { + mWorkerPrivate->ReportError(Context(), aToStringResult, aReport); + } + + WorkerPrivate* GetWorkerPrivate() const { return mWorkerPrivate; } + + private: + WorkerPrivate* mWorkerPrivate; +}; + +namespace workerinternals { + +namespace { + +class WorkerThreadPrimaryRunnable final : public Runnable { + WorkerPrivate* mWorkerPrivate; + SafeRefPtr mThread; + JSRuntime* mParentRuntime; + + class FinishedRunnable final : public Runnable { + SafeRefPtr mThread; + + public: + explicit FinishedRunnable(SafeRefPtr aThread) + : Runnable("WorkerThreadPrimaryRunnable::FinishedRunnable"), + mThread(std::move(aThread)) { + MOZ_ASSERT(mThread); + } + + NS_INLINE_DECL_REFCOUNTING_INHERITED(FinishedRunnable, Runnable) + + private: + ~FinishedRunnable() = default; + + NS_DECL_NSIRUNNABLE + }; + + public: + WorkerThreadPrimaryRunnable(WorkerPrivate* aWorkerPrivate, + SafeRefPtr aThread, + JSRuntime* aParentRuntime) + : mozilla::Runnable("WorkerThreadPrimaryRunnable"), + mWorkerPrivate(aWorkerPrivate), + mThread(std::move(aThread)), + mParentRuntime(aParentRuntime) { + MOZ_ASSERT(aWorkerPrivate); + MOZ_ASSERT(mThread); + } + + NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerThreadPrimaryRunnable, Runnable) + + private: + ~WorkerThreadPrimaryRunnable() = default; + + NS_DECL_NSIRUNNABLE +}; + +void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) { + AssertIsOnMainThread(); + + nsTArray languages; + Navigator::GetAcceptLanguages(languages); + + RuntimeService* runtime = RuntimeService::GetService(); + if (runtime) { + runtime->UpdateAllWorkerLanguages(languages); + } +} + +void AppNameOverrideChanged(const char* /* aPrefName */, void* /* aClosure */) { + AssertIsOnMainThread(); + + nsAutoString override; + Preferences::GetString("general.appname.override", override); + + RuntimeService* runtime = RuntimeService::GetService(); + if (runtime) { + runtime->UpdateAppNameOverridePreference(override); + } +} + +void AppVersionOverrideChanged(const char* /* aPrefName */, + void* /* aClosure */) { + AssertIsOnMainThread(); + + nsAutoString override; + Preferences::GetString("general.appversion.override", override); + + RuntimeService* runtime = RuntimeService::GetService(); + if (runtime) { + runtime->UpdateAppVersionOverridePreference(override); + } +} + +void PlatformOverrideChanged(const char* /* aPrefName */, + void* /* aClosure */) { + AssertIsOnMainThread(); + + nsAutoString override; + Preferences::GetString("general.platform.override", override); + + RuntimeService* runtime = RuntimeService::GetService(); + if (runtime) { + runtime->UpdatePlatformOverridePreference(override); + } +} + +} /* anonymous namespace */ + +// This is only touched on the main thread. Initialized in Init() below. +UniquePtr RuntimeService::sDefaultJSSettings; + +RuntimeService::RuntimeService() + : mMutex("RuntimeService::mMutex"), + mObserved(false), + mShuttingDown(false), + mNavigatorPropertiesLoaded(false) { + AssertIsOnMainThread(); + MOZ_ASSERT(!GetService(), "More than one service!"); +} + +RuntimeService::~RuntimeService() { + AssertIsOnMainThread(); + + // gRuntimeService can be null if Init() fails. + MOZ_ASSERT(!GetService() || GetService() == this, "More than one service!"); + + gRuntimeService = nullptr; +} + +// static +RuntimeService* RuntimeService::GetOrCreateService() { + AssertIsOnMainThread(); + + if (!gRuntimeService) { + // The observer service now owns us until shutdown. + gRuntimeService = new RuntimeService(); + if (NS_FAILED((*gRuntimeService).Init())) { + NS_WARNING("Failed to initialize!"); + (*gRuntimeService).Cleanup(); + gRuntimeService = nullptr; + return nullptr; + } + } + + return gRuntimeService; +} + +// static +RuntimeService* RuntimeService::GetService() { return gRuntimeService; } + +bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) { + aWorkerPrivate.AssertIsOnParentThread(); + + WorkerPrivate* parent = aWorkerPrivate.GetParent(); + if (!parent) { + AssertIsOnMainThread(); + + if (mShuttingDown) { + return false; + } + } + + const bool isServiceWorker = aWorkerPrivate.IsServiceWorker(); + const bool isSharedWorker = aWorkerPrivate.IsSharedWorker(); + const bool isDedicatedWorker = aWorkerPrivate.IsDedicatedWorker(); + if (isServiceWorker) { + AssertIsOnMainThread(); + Telemetry::Accumulate(Telemetry::SERVICE_WORKER_SPAWN_ATTEMPTS, 1); + } + + nsCString sharedWorkerScriptSpec; + if (isSharedWorker) { + AssertIsOnMainThread(); + + nsCOMPtr scriptURI = aWorkerPrivate.GetResolvedScriptURI(); + NS_ASSERTION(scriptURI, "Null script URI!"); + + nsresult rv = scriptURI->GetSpec(sharedWorkerScriptSpec); + if (NS_FAILED(rv)) { + NS_WARNING("GetSpec failed?!"); + return false; + } + + NS_ASSERTION(!sharedWorkerScriptSpec.IsEmpty(), "Empty spec!"); + } + + bool exemptFromPerDomainMax = false; + if (isServiceWorker) { + AssertIsOnMainThread(); + exemptFromPerDomainMax = Preferences::GetBool( + "dom.serviceWorkers.exemptFromPerDomainMax", false); + } + + const nsCString& domain = aWorkerPrivate.Domain(); + + bool queued = false; + { + MutexAutoLock lock(mMutex); + + auto* const domainInfo = + mDomainMap + .LookupOrInsertWith( + domain, + [&domain, parent] { + NS_ASSERTION(!parent, "Shouldn't have a parent here!"); + Unused << parent; // silence clang -Wunused-lambda-capture in + // opt builds + auto wdi = MakeUnique(); + wdi->mDomain = domain; + return wdi; + }) + .get(); + + queued = gMaxWorkersPerDomain && + domainInfo->ActiveWorkerCount() >= gMaxWorkersPerDomain && + !domain.IsEmpty() && !exemptFromPerDomainMax; + + if (queued) { + domainInfo->mQueuedWorkers.AppendElement(&aWorkerPrivate); + + // Worker spawn gets queued due to hitting max workers per domain + // limit so let's log a warning. + WorkerPrivate::ReportErrorToConsole("HittingMaxWorkersPerDomain2"); + + if (isServiceWorker) { + Telemetry::Accumulate(Telemetry::SERVICE_WORKER_SPAWN_GETS_QUEUED, 1); + } else if (isSharedWorker) { + Telemetry::Accumulate(Telemetry::SHARED_WORKER_SPAWN_GETS_QUEUED, 1); + } else if (isDedicatedWorker) { + Telemetry::Accumulate(Telemetry::DEDICATED_WORKER_SPAWN_GETS_QUEUED, 1); + } + } else if (parent) { + domainInfo->mChildWorkerCount++; + } else if (isServiceWorker) { + domainInfo->mActiveServiceWorkers.AppendElement(&aWorkerPrivate); + } else { + domainInfo->mActiveWorkers.AppendElement(&aWorkerPrivate); + } + } + + // From here on out we must call UnregisterWorker if something fails! + if (parent) { + if (!parent->AddChildWorker(aWorkerPrivate)) { + UnregisterWorker(aWorkerPrivate); + return false; + } + } else { + if (!mNavigatorPropertiesLoaded) { + Navigator::AppName(mNavigatorProperties.mAppName, + aWorkerPrivate.GetDocument(), + false /* aUsePrefOverriddenValue */); + if (NS_FAILED(Navigator::GetAppVersion( + mNavigatorProperties.mAppVersion, aWorkerPrivate.GetDocument(), + false /* aUsePrefOverriddenValue */)) || + NS_FAILED(Navigator::GetPlatform( + mNavigatorProperties.mPlatform, aWorkerPrivate.GetDocument(), + false /* aUsePrefOverriddenValue */))) { + UnregisterWorker(aWorkerPrivate); + return false; + } + + // The navigator overridden properties should have already been read. + + Navigator::GetAcceptLanguages(mNavigatorProperties.mLanguages); + mNavigatorPropertiesLoaded = true; + } + + nsPIDOMWindowInner* window = aWorkerPrivate.GetWindow(); + + if (!isServiceWorker) { + // Service workers are excluded since their lifetime is separate from + // that of dom windows. + if (auto* const windowArray = mWindowMap.GetOrInsertNew(window, 1); + !windowArray->Contains(&aWorkerPrivate)) { + windowArray->AppendElement(&aWorkerPrivate); + } else { + MOZ_ASSERT(aWorkerPrivate.IsSharedWorker()); + } + } + } + + if (!queued && !ScheduleWorker(aWorkerPrivate)) { + return false; + } + + if (isServiceWorker) { + AssertIsOnMainThread(); + Telemetry::Accumulate(Telemetry::SERVICE_WORKER_WAS_SPAWNED, 1); + } + return true; +} + +void RuntimeService::UnregisterWorker(WorkerPrivate& aWorkerPrivate) { + aWorkerPrivate.AssertIsOnParentThread(); + + WorkerPrivate* parent = aWorkerPrivate.GetParent(); + if (!parent) { + AssertIsOnMainThread(); + } + + const nsCString& domain = aWorkerPrivate.Domain(); + + WorkerPrivate* queuedWorker = nullptr; + { + MutexAutoLock lock(mMutex); + + WorkerDomainInfo* domainInfo; + if (!mDomainMap.Get(domain, &domainInfo)) { + NS_ERROR("Don't have an entry for this domain!"); + } + + // Remove old worker from everywhere. + uint32_t index = domainInfo->mQueuedWorkers.IndexOf(&aWorkerPrivate); + if (index != kNoIndex) { + // Was queued, remove from the list. + domainInfo->mQueuedWorkers.RemoveElementAt(index); + } else if (parent) { + MOZ_ASSERT(domainInfo->mChildWorkerCount, "Must be non-zero!"); + domainInfo->mChildWorkerCount--; + } else if (aWorkerPrivate.IsServiceWorker()) { + MOZ_ASSERT(domainInfo->mActiveServiceWorkers.Contains(&aWorkerPrivate), + "Don't know about this worker!"); + domainInfo->mActiveServiceWorkers.RemoveElement(&aWorkerPrivate); + } else { + MOZ_ASSERT(domainInfo->mActiveWorkers.Contains(&aWorkerPrivate), + "Don't know about this worker!"); + domainInfo->mActiveWorkers.RemoveElement(&aWorkerPrivate); + } + + // See if there's a queued worker we can schedule. + if (domainInfo->ActiveWorkerCount() < gMaxWorkersPerDomain && + !domainInfo->mQueuedWorkers.IsEmpty()) { + queuedWorker = domainInfo->mQueuedWorkers[0]; + domainInfo->mQueuedWorkers.RemoveElementAt(0); + + if (queuedWorker->GetParent()) { + domainInfo->mChildWorkerCount++; + } else if (queuedWorker->IsServiceWorker()) { + domainInfo->mActiveServiceWorkers.AppendElement(queuedWorker); + } else { + domainInfo->mActiveWorkers.AppendElement(queuedWorker); + } + } + + if (domainInfo->HasNoWorkers()) { + MOZ_ASSERT(domainInfo->mQueuedWorkers.IsEmpty()); + mDomainMap.Remove(domain); + } + } + + // NB: For Shared Workers we used to call ShutdownOnMainThread on the + // RemoteWorkerController; however, that was redundant because + // RemoteWorkerChild uses a WeakWorkerRef which notifies at about the + // same time as us calling into the code here and would race with us. + + if (parent) { + parent->RemoveChildWorker(aWorkerPrivate); + } else if (aWorkerPrivate.IsSharedWorker()) { + AssertIsOnMainThread(); + + mWindowMap.RemoveIf([&aWorkerPrivate](const auto& iter) { + const auto& workers = iter.Data(); + MOZ_ASSERT(workers); + + if (workers->RemoveElement(&aWorkerPrivate)) { + MOZ_ASSERT(!workers->Contains(&aWorkerPrivate), + "Added worker more than once!"); + + return workers->IsEmpty(); + } + + return false; + }); + } else if (aWorkerPrivate.IsDedicatedWorker()) { + // May be null. + nsPIDOMWindowInner* window = aWorkerPrivate.GetWindow(); + if (auto entry = mWindowMap.Lookup(window)) { + MOZ_ALWAYS_TRUE(entry.Data()->RemoveElement(&aWorkerPrivate)); + if (entry.Data()->IsEmpty()) { + entry.Remove(); + } + } else { + MOZ_ASSERT_UNREACHABLE("window is not in mWindowMap"); + } + } + + if (queuedWorker && !ScheduleWorker(*queuedWorker)) { + UnregisterWorker(*queuedWorker); + } +} + +bool RuntimeService::ScheduleWorker(WorkerPrivate& aWorkerPrivate) { + if (!aWorkerPrivate.Start()) { + // This is ok, means that we didn't need to make a thread for this worker. + return true; + } + + const WorkerThreadFriendKey friendKey; + + SafeRefPtr thread = WorkerThread::Create(friendKey); + if (!thread) { + UnregisterWorker(aWorkerPrivate); + return false; + } + + if (NS_FAILED(thread->SetPriority(nsISupportsPriority::PRIORITY_NORMAL))) { + NS_WARNING("Could not set the thread's priority!"); + } + + aWorkerPrivate.SetThread(thread.unsafeGetRawPtr()); + JSContext* cx = CycleCollectedJSContext::Get()->Context(); + nsCOMPtr runnable = new WorkerThreadPrimaryRunnable( + &aWorkerPrivate, thread.clonePtr(), JS_GetParentRuntime(cx)); + if (NS_FAILED( + thread->DispatchPrimaryRunnable(friendKey, runnable.forget()))) { + UnregisterWorker(aWorkerPrivate); + return false; + } + + return true; +} + +nsresult RuntimeService::Init() { + AssertIsOnMainThread(); + + nsLayoutStatics::AddRef(); + + // Initialize JSSettings. + sDefaultJSSettings = MakeUnique(); + SetDefaultJSGCSettings(JSGC_MAX_BYTES, Some(WORKER_DEFAULT_RUNTIME_HEAPSIZE)); + SetDefaultJSGCSettings(JSGC_ALLOCATION_THRESHOLD, + Some(WORKER_DEFAULT_ALLOCATION_THRESHOLD)); + + // nsIStreamTransportService is thread-safe but it must be initialized on the + // main-thread. FileReader needs it, so, let's initialize it now. + nsresult rv; + nsCOMPtr sts = + do_GetService(kStreamTransportServiceCID, &rv); + NS_ENSURE_TRUE(sts, NS_ERROR_FAILURE); + + nsCOMPtr obs = services::GetObserverService(); + NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE); + + rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + NS_ENSURE_SUCCESS(rv, rv); + + mObserved = true; + + if (NS_FAILED(obs->AddObserver(this, GC_REQUEST_OBSERVER_TOPIC, false))) { + NS_WARNING("Failed to register for GC request notifications!"); + } + + if (NS_FAILED(obs->AddObserver(this, CC_REQUEST_OBSERVER_TOPIC, false))) { + NS_WARNING("Failed to register for CC request notifications!"); + } + + if (NS_FAILED( + obs->AddObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC, false))) { + NS_WARNING("Failed to register for memory pressure notifications!"); + } + + if (NS_FAILED( + obs->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false))) { + NS_WARNING("Failed to register for offline notification event!"); + } + + MOZ_ASSERT(!gRuntimeServiceDuringInit, "This should be false!"); + gRuntimeServiceDuringInit = true; + +#define WORKER_PREF(name, callback) \ + NS_FAILED(Preferences::RegisterCallbackAndCall(callback, name)) + + if (NS_FAILED(Preferences::RegisterPrefixCallbackAndCall( + LoadJSGCMemoryOptions, + PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX)) || +#ifdef JS_GC_ZEAL + NS_FAILED(Preferences::RegisterCallback( + LoadGCZealOptions, PREF_JS_OPTIONS_PREFIX PREF_GCZEAL)) || +#endif + WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) || + WORKER_PREF("general.appname.override", AppNameOverrideChanged) || + WORKER_PREF("general.appversion.override", AppVersionOverrideChanged) || + WORKER_PREF("general.platform.override", PlatformOverrideChanged) || + NS_FAILED(Preferences::RegisterPrefixCallbackAndCall( + LoadContextOptions, PREF_JS_OPTIONS_PREFIX))) { + NS_WARNING("Failed to register pref callbacks!"); + } + +#undef WORKER_PREF + + MOZ_ASSERT(gRuntimeServiceDuringInit, "Should be true!"); + gRuntimeServiceDuringInit = false; + + int32_t maxPerDomain = + Preferences::GetInt(PREF_WORKERS_MAX_PER_DOMAIN, MAX_WORKERS_PER_DOMAIN); + gMaxWorkersPerDomain = std::max(0, maxPerDomain); + + RefPtr osFileConstantsService = + OSFileConstantsService::GetOrCreate(); + if (NS_WARN_IF(!osFileConstantsService)) { + return NS_ERROR_FAILURE; + } + + if (NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate())) { + return NS_ERROR_UNEXPECTED; + } + + // PerformanceService must be initialized on the main-thread. + PerformanceService::GetOrCreate(); + + return NS_OK; +} + +void RuntimeService::Shutdown() { + AssertIsOnMainThread(); + + MOZ_ASSERT(!mShuttingDown); + // That's it, no more workers. + mShuttingDown = true; + + nsCOMPtr obs = services::GetObserverService(); + NS_WARNING_ASSERTION(obs, "Failed to get observer service?!"); + + // Tell anyone that cares that they're about to lose worker support. + if (obs && NS_FAILED(obs->NotifyObservers(nullptr, WORKERS_SHUTDOWN_TOPIC, + nullptr))) { + NS_WARNING("NotifyObservers failed!"); + } + + { + AutoTArray workers; + + { + MutexAutoLock lock(mMutex); + + AddAllTopLevelWorkersToArray(workers); + } + + // Cancel all top-level workers. + for (const auto& worker : workers) { + if (!worker->Cancel()) { + NS_WARNING("Failed to cancel worker!"); + } + } + } + + sDefaultJSSettings = nullptr; +} + +namespace { + +class DumpCrashInfoRunnable : public WorkerControlRunnable { + public: + explicit DumpCrashInfoRunnable(WorkerPrivate* aWorkerPrivate) + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), + mMonitor("DumpCrashInfoRunnable::mMonitor") {} + + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { + MonitorAutoLock lock(mMonitor); + if (!mHasMsg) { + aWorkerPrivate->DumpCrashInformation(mMsg); + mHasMsg.Flip(); + } + lock.Notify(); + return true; + } + + nsresult Cancel() override { + // We need to check first if cancel is called twice + nsresult rv = WorkerRunnable::Cancel(); + NS_ENSURE_SUCCESS(rv, rv); + + MonitorAutoLock lock(mMonitor); + if (!mHasMsg) { + mMsg.Assign("Canceled"); + mHasMsg.Flip(); + } + lock.Notify(); + + return NS_OK; + } + + bool DispatchAndWait() { + MonitorAutoLock lock(mMonitor); + + if (!Dispatch()) { + // The worker is already dead but the main thread still didn't remove it + // from RuntimeService's registry. + return false; + } + + // To avoid any possibility of process hangs we never receive reports on + // we give the worker 1sec to react. + lock.Wait(TimeDuration::FromMilliseconds(1000)); + if (!mHasMsg) { + mMsg.Append("NoResponse"); + mHasMsg.Flip(); + } + return true; + } + + const nsCString& MsgData() const { return mMsg; } + + private: + bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { return true; } + + void PostDispatch(WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) override {} + + Monitor mMonitor MOZ_UNANNOTATED; + nsCString mMsg; + FlippedOnce mHasMsg; +}; + +struct ActiveWorkerStats { + template + void Update(const nsTArray& aWorkers) { + for (const auto worker : aWorkers) { + RefPtr runnable = + new DumpCrashInfoRunnable(worker); + if (runnable->DispatchAndWait()) { + ++(this->*Category); + + // BC: Busy Count + mMessage.AppendPrintf("-BC:%d", worker->BusyCount()); + mMessage.Append(runnable->MsgData()); + } else { + mMessage.AppendPrintf("-BC:%d DispatchFailed", worker->BusyCount()); + } + } + } + + uint32_t mWorkers = 0; + uint32_t mServiceWorkers = 0; + nsCString mMessage; +}; + +} // namespace + +void RuntimeService::CrashIfHanging() { + MutexAutoLock lock(mMutex); + + // If we never wanted to shut down we cannot hang. + if (!mShuttingDown) { + return; + } + + ActiveWorkerStats activeStats; + uint32_t inactiveWorkers = 0; + + for (const auto& aData : mDomainMap.Values()) { + activeStats.Update<&ActiveWorkerStats::mWorkers>(aData->mActiveWorkers); + activeStats.Update<&ActiveWorkerStats::mServiceWorkers>( + aData->mActiveServiceWorkers); + + // These might not be top-level workers... + inactiveWorkers += std::count_if( + aData->mQueuedWorkers.begin(), aData->mQueuedWorkers.end(), + [](const auto* const worker) { return !worker->GetParent(); }); + } + + if (activeStats.mWorkers + activeStats.mServiceWorkers + inactiveWorkers == + 0) { + return; + } + + nsCString msg; + + // A: active Workers | S: active ServiceWorkers | Q: queued Workers + msg.AppendPrintf("Workers Hanging - %d|A:%d|S:%d|Q:%d", mShuttingDown ? 1 : 0, + activeStats.mWorkers, activeStats.mServiceWorkers, + inactiveWorkers); + msg.Append(activeStats.mMessage); + + // This string will be leaked. + MOZ_CRASH_UNSAFE(strdup(msg.BeginReading())); +} + +// This spins the event loop until all workers are finished and their threads +// have been joined. +void RuntimeService::Cleanup() { + AssertIsOnMainThread(); + + if (!mShuttingDown) { + Shutdown(); + } + + nsCOMPtr obs = services::GetObserverService(); + NS_WARNING_ASSERTION(obs, "Failed to get observer service?!"); + + { + MutexAutoLock lock(mMutex); + + AutoTArray workers; + AddAllTopLevelWorkersToArray(workers); + + if (!workers.IsEmpty()) { + nsIThread* currentThread = NS_GetCurrentThread(); + NS_ASSERTION(currentThread, "This should never be null!"); + + // If the loop below takes too long, we probably have a problematic + // worker. MOZ_LOG some info before the parent process forcibly + // terminates us so that in the event we are a content process, the log + // output can provide useful context about the workers that did not + // cleanly shut down. + nsCOMPtr timer; + RefPtr self = this; + nsresult rv = NS_NewTimerWithCallback( + getter_AddRefs(timer), + [self](nsITimer*) { self->DumpRunningWorkers(); }, + TimeDuration::FromSeconds(1), nsITimer::TYPE_ONE_SHOT, + "RuntimeService::WorkerShutdownDump"); + Unused << NS_WARN_IF(NS_FAILED(rv)); + + // And make sure all their final messages have run and all their threads + // have joined. + while (mDomainMap.Count()) { + MutexAutoUnlock unlock(mMutex); + + if (!NS_ProcessNextEvent(currentThread)) { + NS_WARNING("Something bad happened!"); + break; + } + } + + if (NS_SUCCEEDED(rv)) { + timer->Cancel(); + } + } + } + + NS_ASSERTION(!mWindowMap.Count(), "All windows should have been released!"); + +#define WORKER_PREF(name, callback) \ + NS_FAILED(Preferences::UnregisterCallback(callback, name)) + + if (mObserved) { + if (NS_FAILED(Preferences::UnregisterPrefixCallback( + LoadContextOptions, PREF_JS_OPTIONS_PREFIX)) || + WORKER_PREF("intl.accept_languages", PrefLanguagesChanged) || + WORKER_PREF("general.appname.override", AppNameOverrideChanged) || + WORKER_PREF("general.appversion.override", AppVersionOverrideChanged) || + WORKER_PREF("general.platform.override", PlatformOverrideChanged) || +#ifdef JS_GC_ZEAL + NS_FAILED(Preferences::UnregisterCallback( + LoadGCZealOptions, PREF_JS_OPTIONS_PREFIX PREF_GCZEAL)) || +#endif + NS_FAILED(Preferences::UnregisterPrefixCallback( + LoadJSGCMemoryOptions, + PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX))) { + NS_WARNING("Failed to unregister pref callbacks!"); + } + +#undef WORKER_PREF + + if (obs) { + if (NS_FAILED(obs->RemoveObserver(this, GC_REQUEST_OBSERVER_TOPIC))) { + NS_WARNING("Failed to unregister for GC request notifications!"); + } + + if (NS_FAILED(obs->RemoveObserver(this, CC_REQUEST_OBSERVER_TOPIC))) { + NS_WARNING("Failed to unregister for CC request notifications!"); + } + + if (NS_FAILED( + obs->RemoveObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC))) { + NS_WARNING("Failed to unregister for memory pressure notifications!"); + } + + if (NS_FAILED( + obs->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC))) { + NS_WARNING("Failed to unregister for offline notification event!"); + } + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID); + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + mObserved = false; + } + } + + nsLayoutStatics::Release(); +} + +void RuntimeService::AddAllTopLevelWorkersToArray( + nsTArray& aWorkers) { + for (const auto& aData : mDomainMap.Values()) { +#ifdef DEBUG + for (const auto& activeWorker : aData->mActiveWorkers) { + MOZ_ASSERT(!activeWorker->GetParent(), + "Shouldn't have a parent in this list!"); + } + for (const auto& activeServiceWorker : aData->mActiveServiceWorkers) { + MOZ_ASSERT(!activeServiceWorker->GetParent(), + "Shouldn't have a parent in this list!"); + } +#endif + + aWorkers.AppendElements(aData->mActiveWorkers); + aWorkers.AppendElements(aData->mActiveServiceWorkers); + + // These might not be top-level workers... + std::copy_if(aData->mQueuedWorkers.begin(), aData->mQueuedWorkers.end(), + MakeBackInserter(aWorkers), + [](const auto& worker) { return !worker->GetParent(); }); + } +} + +nsTArray RuntimeService::GetWorkersForWindow( + const nsPIDOMWindowInner& aWindow) const { + AssertIsOnMainThread(); + + nsTArray result; + if (nsTArray* const workers = mWindowMap.Get(&aWindow)) { + NS_ASSERTION(!workers->IsEmpty(), "Should have been removed!"); + result.AppendElements(*workers); + } + return result; +} + +void RuntimeService::CancelWorkersForWindow(const nsPIDOMWindowInner& aWindow) { + AssertIsOnMainThread(); + + for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { + MOZ_ASSERT(!worker->IsSharedWorker()); + worker->Cancel(); + } +} + +void RuntimeService::FreezeWorkersForWindow(const nsPIDOMWindowInner& aWindow) { + AssertIsOnMainThread(); + + for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { + MOZ_ASSERT(!worker->IsSharedWorker()); + worker->Freeze(&aWindow); + } +} + +void RuntimeService::ThawWorkersForWindow(const nsPIDOMWindowInner& aWindow) { + AssertIsOnMainThread(); + + for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { + MOZ_ASSERT(!worker->IsSharedWorker()); + worker->Thaw(&aWindow); + } +} + +void RuntimeService::SuspendWorkersForWindow( + const nsPIDOMWindowInner& aWindow) { + AssertIsOnMainThread(); + + for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { + MOZ_ASSERT(!worker->IsSharedWorker()); + worker->ParentWindowPaused(); + } +} + +void RuntimeService::ResumeWorkersForWindow(const nsPIDOMWindowInner& aWindow) { + AssertIsOnMainThread(); + + for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { + MOZ_ASSERT(!worker->IsSharedWorker()); + worker->ParentWindowResumed(); + } +} + +void RuntimeService::PropagateStorageAccessPermissionGranted( + const nsPIDOMWindowInner& aWindow) { + AssertIsOnMainThread(); + MOZ_ASSERT_IF(aWindow.GetExtantDoc(), aWindow.GetExtantDoc() + ->CookieJarSettings() + ->GetRejectThirdPartyContexts()); + + for (WorkerPrivate* const worker : GetWorkersForWindow(aWindow)) { + worker->PropagateStorageAccessPermissionGranted(); + } +} + +template +void RuntimeService::BroadcastAllWorkers(const Func& aFunc) { + AssertIsOnMainThread(); + + AutoTArray workers; + { + MutexAutoLock lock(mMutex); + + AddAllTopLevelWorkersToArray(workers); + } + + for (const auto& worker : workers) { + aFunc(*worker); + } +} + +void RuntimeService::UpdateAllWorkerContextOptions() { + BroadcastAllWorkers([](auto& worker) { + worker.UpdateContextOptions(sDefaultJSSettings->contextOptions); + }); +} + +void RuntimeService::UpdateAppNameOverridePreference(const nsAString& aValue) { + AssertIsOnMainThread(); + mNavigatorProperties.mAppNameOverridden = aValue; +} + +void RuntimeService::UpdateAppVersionOverridePreference( + const nsAString& aValue) { + AssertIsOnMainThread(); + mNavigatorProperties.mAppVersionOverridden = aValue; +} + +void RuntimeService::UpdatePlatformOverridePreference(const nsAString& aValue) { + AssertIsOnMainThread(); + mNavigatorProperties.mPlatformOverridden = aValue; +} + +void RuntimeService::UpdateAllWorkerLanguages( + const nsTArray& aLanguages) { + MOZ_ASSERT(NS_IsMainThread()); + + mNavigatorProperties.mLanguages = aLanguages.Clone(); + BroadcastAllWorkers( + [&aLanguages](auto& worker) { worker.UpdateLanguages(aLanguages); }); +} + +void RuntimeService::UpdateAllWorkerMemoryParameter(JSGCParamKey aKey, + Maybe aValue) { + BroadcastAllWorkers([aKey, aValue](auto& worker) { + worker.UpdateJSWorkerMemoryParameter(aKey, aValue); + }); +} + +#ifdef JS_GC_ZEAL +void RuntimeService::UpdateAllWorkerGCZeal() { + BroadcastAllWorkers([](auto& worker) { + worker.UpdateGCZeal(sDefaultJSSettings->gcZeal, + sDefaultJSSettings->gcZealFrequency); + }); +} +#endif + +void RuntimeService::SetLowMemoryStateAllWorkers(bool aState) { + BroadcastAllWorkers( + [aState](auto& worker) { worker.SetLowMemoryState(aState); }); +} + +void RuntimeService::GarbageCollectAllWorkers(bool aShrinking) { + BroadcastAllWorkers( + [aShrinking](auto& worker) { worker.GarbageCollect(aShrinking); }); +} + +void RuntimeService::CycleCollectAllWorkers() { + BroadcastAllWorkers([](auto& worker) { worker.CycleCollect(); }); +} + +void RuntimeService::SendOfflineStatusChangeEventToAllWorkers(bool aIsOffline) { + BroadcastAllWorkers([aIsOffline](auto& worker) { + worker.OfflineStatusChangeEvent(aIsOffline); + }); +} + +void RuntimeService::MemoryPressureAllWorkers() { + BroadcastAllWorkers([](auto& worker) { worker.MemoryPressure(); }); +} + +uint32_t RuntimeService::ClampedHardwareConcurrency( + bool aShouldResistFingerprinting) const { + // The Firefox Hardware Report says 70% of Firefox users have exactly 2 cores. + // When the resistFingerprinting pref is set, we want to blend into the crowd + // so spoof navigator.hardwareConcurrency = 2 to reduce user uniqueness. + if (MOZ_UNLIKELY(aShouldResistFingerprinting)) { + return 2; + } + + // This needs to be atomic, because multiple workers, and even mainthread, + // could race to initialize it at once. + static Atomic unclampedHardwareConcurrency; + + // No need to loop here: if compareExchange fails, that just means that some + // other worker has initialized numberOfProcessors, so we're good to go. + if (!unclampedHardwareConcurrency) { + int32_t numberOfProcessors = 0; +#if defined(XP_MACOSX) + if (nsMacUtilsImpl::IsTCSMAvailable()) { + // On failure, zero is returned from GetPhysicalCPUCount() + // and we fallback to PR_GetNumberOfProcessors below. + numberOfProcessors = nsMacUtilsImpl::GetPhysicalCPUCount(); + } +#endif + if (numberOfProcessors == 0) { + numberOfProcessors = PR_GetNumberOfProcessors(); + } + if (numberOfProcessors <= 0) { + numberOfProcessors = 1; // Must be one there somewhere + } + Unused << unclampedHardwareConcurrency.compareExchange(0, + numberOfProcessors); + } + + return std::min(uint32_t(unclampedHardwareConcurrency), + StaticPrefs::dom_maxHardwareConcurrency()); +} + +// nsISupports +NS_IMPL_ISUPPORTS(RuntimeService, nsIObserver) + +// nsIObserver +NS_IMETHODIMP +RuntimeService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + AssertIsOnMainThread(); + + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + Shutdown(); + return NS_OK; + } + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)) { + Cleanup(); + return NS_OK; + } + if (!strcmp(aTopic, GC_REQUEST_OBSERVER_TOPIC)) { + GarbageCollectAllWorkers(/* shrinking = */ false); + return NS_OK; + } + if (!strcmp(aTopic, CC_REQUEST_OBSERVER_TOPIC)) { + CycleCollectAllWorkers(); + return NS_OK; + } + if (!strcmp(aTopic, MEMORY_PRESSURE_OBSERVER_TOPIC)) { + nsDependentString data(aData); + // Don't continue to GC/CC if we are in an ongoing low-memory state since + // its very slow and it likely won't help us anyway. + if (data.EqualsLiteral(LOW_MEMORY_ONGOING_DATA)) { + return NS_OK; + } + if (data.EqualsLiteral(LOW_MEMORY_DATA)) { + SetLowMemoryStateAllWorkers(true); + } + GarbageCollectAllWorkers(/* shrinking = */ true); + CycleCollectAllWorkers(); + MemoryPressureAllWorkers(); + return NS_OK; + } + if (!strcmp(aTopic, MEMORY_PRESSURE_STOP_OBSERVER_TOPIC)) { + SetLowMemoryStateAllWorkers(false); + return NS_OK; + } + if (!strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) { + SendOfflineStatusChangeEventToAllWorkers(NS_IsOffline()); + return NS_OK; + } + + MOZ_ASSERT_UNREACHABLE("Unknown observer topic!"); + return NS_OK; +} + +namespace { +const char* WorkerKindToString(WorkerKind kind) { + switch (kind) { + case WorkerKindDedicated: + return "dedicated"; + case WorkerKindShared: + return "shared"; + case WorkerKindService: + return "service"; + default: + NS_WARNING("Unknown worker type"); + return "unknown worker type"; + } +} + +void LogWorker(WorkerPrivate* worker, const char* category) { + AssertIsOnMainThread(); + + SHUTDOWN_LOG(("Found %s (%s): %s", category, + WorkerKindToString(worker->Kind()), + NS_ConvertUTF16toUTF8(worker->ScriptURL()).get())); + + if (worker->Kind() == WorkerKindService) { + SHUTDOWN_LOG(("Scope: %s", worker->ServiceWorkerScope().get())); + } + + nsCString origin; + worker->GetPrincipal()->GetOrigin(origin); + SHUTDOWN_LOG(("Principal: %s", origin.get())); + + nsCString loadingOrigin; + worker->GetLoadingPrincipal()->GetOrigin(loadingOrigin); + SHUTDOWN_LOG(("LoadingPrincipal: %s", loadingOrigin.get())); + + SHUTDOWN_LOG(("BusyCount: %d", worker->BusyCount())); + + RefPtr runnable = new DumpCrashInfoRunnable(worker); + if (runnable->DispatchAndWait()) { + SHUTDOWN_LOG(("CrashInfo: %s", runnable->MsgData().get())); + } else { + SHUTDOWN_LOG(("CrashInfo: dispatch failed")); + } +} +} // namespace + +void RuntimeService::DumpRunningWorkers() { + // Temporarily set the LogLevel high enough to be certain the messages are + // visible. + LogModule* module = gWorkerShutdownDumpLog; + LogLevel prevLevel = module->Level(); + + const auto cleanup = + MakeScopeExit([module, prevLevel] { module->SetLevel(prevLevel); }); + + if (prevLevel < LogLevel::Debug) { + module->SetLevel(LogLevel::Debug); + } + + MutexAutoLock lock(mMutex); + + for (const auto& info : mDomainMap.Values()) { + for (WorkerPrivate* worker : info->mActiveWorkers) { + LogWorker(worker, "ActiveWorker"); + } + + for (WorkerPrivate* worker : info->mActiveServiceWorkers) { + LogWorker(worker, "ActiveServiceWorker"); + } + + for (WorkerPrivate* worker : info->mQueuedWorkers) { + LogWorker(worker, "QueuedWorker"); + } + } +} + +bool LogViolationDetailsRunnable::MainThreadRun() { + AssertIsOnMainThread(); + + nsIContentSecurityPolicy* csp = mWorkerPrivate->GetCsp(); + if (csp) { + csp->LogViolationDetails(mViolationType, + nullptr, // triggering element + mWorkerPrivate->CSPEventListener(), mFileName, + mScriptSample, mLineNum, mColumnNum, u""_ns, + u""_ns); + } + + return true; +} + +// MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See +// bug 1535398. +MOZ_CAN_RUN_SCRIPT_BOUNDARY +NS_IMETHODIMP +WorkerThreadPrimaryRunnable::Run() { + NS_ConvertUTF16toUTF8 url(mWorkerPrivate->ScriptURL()); + AUTO_PROFILER_LABEL_DYNAMIC_CSTR("WorkerThreadPrimaryRunnable::Run", OTHER, + url.get()); + + using mozilla::ipc::BackgroundChild; + + { + auto failureCleanup = MakeScopeExit([&]() { + // The creation of threadHelper above is the point at which a worker is + // considered to have run, because the `mPreStartRunnables` are all + // re-dispatched after `mThread` is set. We need to let the WorkerPrivate + // know so it can clean up the various event loops and delete the worker. + mWorkerPrivate->RunLoopNeverRan(); + }); + + mWorkerPrivate->SetWorkerPrivateInWorkerThread(mThread.unsafeGetRawPtr()); + + const auto threadCleanup = MakeScopeExit([&] { + // This must be called before ScheduleDeletion, which is either called + // from failureCleanup leaving scope, or from the outer scope. + mWorkerPrivate->ResetWorkerPrivateInWorkerThread(); + }); + + mWorkerPrivate->AssertIsOnWorkerThread(); + + // This needs to be initialized on the worker thread before being used on + // the main thread and calling BackgroundChild::GetOrCreateForCurrentThread + // exposes it to the main thread. + mWorkerPrivate->EnsurePerformanceStorage(); + + if (NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread())) { + return NS_ERROR_FAILURE; + } + + nsWeakPtr globalScopeSentinel; + nsWeakPtr debuggerScopeSentinel; + // Never use the following pointers without checking their corresponding + // nsWeakPtr sentinel, defined above and initialized after DoRunLoop ends. + WorkerGlobalScopeBase* globalScopeRawPtr = nullptr; + WorkerGlobalScopeBase* debuggerScopeRawPtr = nullptr; + { + nsCycleCollector_startup(); + + auto context = MakeUnique(mWorkerPrivate); + nsresult rv = context->Initialize(mParentRuntime); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + JSContext* cx = context->Context(); + + if (!InitJSContextForWorker(mWorkerPrivate, cx)) { + return NS_ERROR_FAILURE; + } + + failureCleanup.release(); + + { + PROFILER_SET_JS_CONTEXT(cx); + + { + // We're on the worker thread here, and WorkerPrivate's refcounting is + // non-threadsafe: you can only do it on the parent thread. What that + // means in practice is that we're relying on it being kept alive + // while we run. Hopefully. + MOZ_KnownLive(mWorkerPrivate)->DoRunLoop(cx); + // The AutoJSAPI in DoRunLoop should have reported any exceptions left + // on cx. + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + } + + mWorkerPrivate->ShutdownModuleLoader(); + + mWorkerPrivate->RunShutdownTasks(); + + BackgroundChild::CloseForCurrentThread(); + + PROFILER_CLEAR_JS_CONTEXT(); + } + + // There may still be runnables on the debugger event queue that hold a + // strong reference to the debugger global scope. These runnables are not + // visible to the cycle collector, so we need to make sure to clear the + // debugger event queue before we try to destroy the context. If we don't, + // the garbage collector will crash. + // Note that this just releases the runnables and does not execute them. + mWorkerPrivate->ClearDebuggerEventQueue(); + + // Before shutting down the cycle collector we need to do one more pass + // through the event loop to clean up any C++ objects that need deferred + // cleanup. + NS_ProcessPendingEvents(nullptr); + + // At this point we expect the scopes to be alive if they were ever + // created successfully, keep weak references and set up the sentinels. + globalScopeRawPtr = mWorkerPrivate->GlobalScope(); + if (globalScopeRawPtr) { + globalScopeSentinel = do_GetWeakReference(globalScopeRawPtr); + } + MOZ_ASSERT(!globalScopeRawPtr || globalScopeSentinel); + debuggerScopeRawPtr = mWorkerPrivate->DebuggerGlobalScope(); + if (debuggerScopeRawPtr) { + debuggerScopeSentinel = do_GetWeakReference(debuggerScopeRawPtr); + } + MOZ_ASSERT(!debuggerScopeRawPtr || debuggerScopeSentinel); + + // To our best knowledge nobody should need a reference to our globals + // now (NS_ProcessPendingEvents is the last expected potential usage) + // and we can unroot them. + mWorkerPrivate->UnrootGlobalScopes(); + + // Perform a full GC until we collect the main worker global and CC, + // which should break all cycles that touch JS. + bool repeatGCCC = true; + while (repeatGCCC) { + JS::PrepareForFullGC(cx); + JS::NonIncrementalGC(cx, JS::GCOptions::Shutdown, + JS::GCReason::WORKER_SHUTDOWN); + + // If we CCed something or got new events as a side effect, repeat. + repeatGCCC = mWorkerPrivate->isLastCCCollectedAnything() || + NS_HasPendingEvents(nullptr); + NS_ProcessPendingEvents(nullptr); + } + + // The worker global should be unrooted and the shutdown of cycle + // collection should break all the remaining cycles. + nsCycleCollector_shutdown(); + + // If ever the CC shutdown run caused side effects, process them. + NS_ProcessPendingEvents(nullptr); + + // Now WorkerJSContext goes out of scope. Do not use any cycle + // collectable objects nor JS after this point! + } + + // Check sentinels if we actually removed all global scope references. + // In case use the earlier set-aside raw pointers to not mess with the + // ref counting after the cycle collector has gone away. + if (globalScopeSentinel) { + MOZ_ASSERT(!globalScopeSentinel->IsAlive()); + if (NS_WARN_IF(globalScopeSentinel->IsAlive())) { + globalScopeRawPtr->NoteWorkerTerminated(); + globalScopeRawPtr = nullptr; + } + } + if (debuggerScopeSentinel) { + MOZ_ASSERT(!debuggerScopeSentinel->IsAlive()); + if (NS_WARN_IF(debuggerScopeSentinel->IsAlive())) { + debuggerScopeRawPtr->NoteWorkerTerminated(); + debuggerScopeRawPtr = nullptr; + } + } + } + + mWorkerPrivate->ScheduleDeletion(WorkerPrivate::WorkerRan); + + // It is no longer safe to touch mWorkerPrivate. + mWorkerPrivate = nullptr; + + // Now recycle this thread. + nsCOMPtr mainTarget = GetMainThreadSerialEventTarget(); + MOZ_ASSERT(mainTarget); + + RefPtr finishedRunnable = + new FinishedRunnable(std::move(mThread)); + MOZ_ALWAYS_SUCCEEDS( + mainTarget->Dispatch(finishedRunnable, NS_DISPATCH_NORMAL)); + + return NS_OK; +} + +NS_IMETHODIMP +WorkerThreadPrimaryRunnable::FinishedRunnable::Run() { + AssertIsOnMainThread(); + + SafeRefPtr thread = std::move(mThread); + if (thread->ShutdownRequired()) { + MOZ_ALWAYS_SUCCEEDS(thread->Shutdown()); + } + + return NS_OK; +} + +} // namespace workerinternals + +// This is mostly for invoking within a debugger. +void DumpRunningWorkers() { + RuntimeService* runtimeService = RuntimeService::GetService(); + if (runtimeService) { + runtimeService->DumpRunningWorkers(); + } else { + NS_WARNING("RuntimeService not found"); + } +} + +void CancelWorkersForWindow(const nsPIDOMWindowInner& aWindow) { + AssertIsOnMainThread(); + RuntimeService* runtime = RuntimeService::GetService(); + if (runtime) { + runtime->CancelWorkersForWindow(aWindow); + } +} + +void FreezeWorkersForWindow(const nsPIDOMWindowInner& aWindow) { + AssertIsOnMainThread(); + RuntimeService* runtime = RuntimeService::GetService(); + if (runtime) { + runtime->FreezeWorkersForWindow(aWindow); + } +} + +void ThawWorkersForWindow(const nsPIDOMWindowInner& aWindow) { + AssertIsOnMainThread(); + RuntimeService* runtime = RuntimeService::GetService(); + if (runtime) { + runtime->ThawWorkersForWindow(aWindow); + } +} + +void SuspendWorkersForWindow(const nsPIDOMWindowInner& aWindow) { + AssertIsOnMainThread(); + RuntimeService* runtime = RuntimeService::GetService(); + if (runtime) { + runtime->SuspendWorkersForWindow(aWindow); + } +} + +void ResumeWorkersForWindow(const nsPIDOMWindowInner& aWindow) { + AssertIsOnMainThread(); + RuntimeService* runtime = RuntimeService::GetService(); + if (runtime) { + runtime->ResumeWorkersForWindow(aWindow); + } +} + +void PropagateStorageAccessPermissionGrantedToWorkers( + const nsPIDOMWindowInner& aWindow) { + AssertIsOnMainThread(); + MOZ_ASSERT_IF(aWindow.GetExtantDoc(), aWindow.GetExtantDoc() + ->CookieJarSettings() + ->GetRejectThirdPartyContexts()); + + RuntimeService* runtime = RuntimeService::GetService(); + if (runtime) { + runtime->PropagateStorageAccessPermissionGranted(aWindow); + } +} + +WorkerPrivate* GetWorkerPrivateFromContext(JSContext* aCx) { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aCx); + + CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::GetFor(aCx); + if (!ccjscx) { + return nullptr; + } + + WorkerJSContext* workerjscx = ccjscx->GetAsWorkerJSContext(); + // GetWorkerPrivateFromContext is called only for worker contexts. The + // context private is cleared early in ~CycleCollectedJSContext() and so + // GetFor() returns null above if called after ccjscx is no longer a + // WorkerJSContext. + MOZ_ASSERT(workerjscx); + return workerjscx->GetWorkerPrivate(); +} + +WorkerPrivate* GetCurrentThreadWorkerPrivate() { + if (NS_IsMainThread()) { + return nullptr; + } + + CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get(); + if (!ccjscx) { + return nullptr; + } + + WorkerJSContext* workerjscx = ccjscx->GetAsWorkerJSContext(); + // Even when GetCurrentThreadWorkerPrivate() is called on worker + // threads, the ccjscx will no longer be a WorkerJSContext if called from + // stable state events during ~CycleCollectedJSContext(). + if (!workerjscx) { + return nullptr; + } + + return workerjscx->GetWorkerPrivate(); +} + +bool IsCurrentThreadRunningWorker() { + return !NS_IsMainThread() && !!GetCurrentThreadWorkerPrivate(); +} + +bool IsCurrentThreadRunningChromeWorker() { + WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); + return wp && wp->UsesSystemPrincipal(); +} + +JSContext* GetCurrentWorkerThreadJSContext() { + WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); + if (!wp) { + return nullptr; + } + return wp->GetJSContext(); +} + +JSObject* GetCurrentThreadWorkerGlobal() { + WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); + if (!wp) { + return nullptr; + } + WorkerGlobalScope* scope = wp->GlobalScope(); + if (!scope) { + return nullptr; + } + return scope->GetGlobalJSObject(); +} + +JSObject* GetCurrentThreadWorkerDebuggerGlobal() { + WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); + if (!wp) { + return nullptr; + } + WorkerDebuggerGlobalScope* scope = wp->DebuggerGlobalScope(); + if (!scope) { + return nullptr; + } + return scope->GetGlobalJSObject(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/workers/RuntimeService.h b/dom/workers/RuntimeService.h new file mode 100644 index 0000000000..a770d7330e --- /dev/null +++ b/dom/workers/RuntimeService.h @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workers_runtimeservice_h__ +#define mozilla_dom_workers_runtimeservice_h__ + +#include "mozilla/dom/WorkerCommon.h" + +#include "nsIObserver.h" + +#include "js/ContextOptions.h" +#include "MainThreadUtils.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/SafeRefPtr.h" +#include "mozilla/dom/workerinternals/JSSettings.h" +#include "mozilla/Atomics.h" +#include "mozilla/Mutex.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "nsTArray.h" + +class nsPIDOMWindowInner; + +namespace mozilla::dom { +struct WorkerLoadInfo; +class WorkerThread; + +namespace workerinternals { + +class RuntimeService final : public nsIObserver { + struct WorkerDomainInfo { + nsCString mDomain; + nsTArray mActiveWorkers; + nsTArray mActiveServiceWorkers; + nsTArray mQueuedWorkers; + uint32_t mChildWorkerCount; + + WorkerDomainInfo() : mActiveWorkers(1), mChildWorkerCount(0) {} + + uint32_t ActiveWorkerCount() const { + return mActiveWorkers.Length() + mChildWorkerCount; + } + + uint32_t ActiveServiceWorkerCount() const { + return mActiveServiceWorkers.Length(); + } + + bool HasNoWorkers() const { + return ActiveWorkerCount() == 0 && ActiveServiceWorkerCount() == 0; + } + }; + + mozilla::Mutex mMutex; + + // Protected by mMutex. + nsClassHashtable mDomainMap + MOZ_GUARDED_BY(mMutex); + + // *Not* protected by mMutex. + nsClassHashtable, + nsTArray > + mWindowMap; + + static UniquePtr sDefaultJSSettings; + + public: + struct NavigatorProperties { + nsString mAppName; + nsString mAppNameOverridden; + nsString mAppVersion; + nsString mAppVersionOverridden; + nsString mPlatform; + nsString mPlatformOverridden; + CopyableTArray mLanguages; + }; + + private: + NavigatorProperties mNavigatorProperties; + + // True when the observer service holds a reference to this object. + bool mObserved; + bool mShuttingDown; + bool mNavigatorPropertiesLoaded; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + static RuntimeService* GetOrCreateService(); + + static RuntimeService* GetService(); + + bool RegisterWorker(WorkerPrivate& aWorkerPrivate); + + void UnregisterWorker(WorkerPrivate& aWorkerPrivate); + + void CancelWorkersForWindow(const nsPIDOMWindowInner& aWindow); + + void FreezeWorkersForWindow(const nsPIDOMWindowInner& aWindow); + + void ThawWorkersForWindow(const nsPIDOMWindowInner& aWindow); + + void SuspendWorkersForWindow(const nsPIDOMWindowInner& aWindow); + + void ResumeWorkersForWindow(const nsPIDOMWindowInner& aWindow); + + void PropagateStorageAccessPermissionGranted( + const nsPIDOMWindowInner& aWindow); + + const NavigatorProperties& GetNavigatorProperties() const { + return mNavigatorProperties; + } + + static void GetDefaultJSSettings(workerinternals::JSSettings& aSettings) { + AssertIsOnMainThread(); + aSettings = *sDefaultJSSettings; + } + + static void SetDefaultContextOptions( + const JS::ContextOptions& aContextOptions) { + AssertIsOnMainThread(); + sDefaultJSSettings->contextOptions = aContextOptions; + } + + void UpdateAppNameOverridePreference(const nsAString& aValue); + + void UpdateAppVersionOverridePreference(const nsAString& aValue); + + void UpdatePlatformOverridePreference(const nsAString& aValue); + + void UpdateAllWorkerContextOptions(); + + void UpdateAllWorkerLanguages(const nsTArray& aLanguages); + + static void SetDefaultJSGCSettings(JSGCParamKey aKey, + Maybe aValue) { + AssertIsOnMainThread(); + sDefaultJSSettings->ApplyGCSetting(aKey, aValue); + } + + void UpdateAllWorkerMemoryParameter(JSGCParamKey aKey, + Maybe aValue); + +#ifdef JS_GC_ZEAL + static void SetDefaultGCZeal(uint8_t aGCZeal, uint32_t aFrequency) { + AssertIsOnMainThread(); + sDefaultJSSettings->gcZeal = aGCZeal; + sDefaultJSSettings->gcZealFrequency = aFrequency; + } + + void UpdateAllWorkerGCZeal(); +#endif + + void SetLowMemoryStateAllWorkers(bool aState); + + void GarbageCollectAllWorkers(bool aShrinking); + + void CycleCollectAllWorkers(); + + void SendOfflineStatusChangeEventToAllWorkers(bool aIsOffline); + + void MemoryPressureAllWorkers(); + + uint32_t ClampedHardwareConcurrency(bool aShouldResistFingerprinting) const; + + void CrashIfHanging(); + + bool IsShuttingDown() const { return mShuttingDown; } + + void DumpRunningWorkers(); + + private: + RuntimeService(); + ~RuntimeService(); + + nsresult Init(); + + void Shutdown(); + + void Cleanup(); + + void AddAllTopLevelWorkersToArray(nsTArray& aWorkers) + MOZ_REQUIRES(mMutex); + + nsTArray GetWorkersForWindow( + const nsPIDOMWindowInner& aWindow) const; + + bool ScheduleWorker(WorkerPrivate& aWorkerPrivate); + + template + void BroadcastAllWorkers(const Func& aFunc); +}; + +} // namespace workerinternals +} // namespace mozilla::dom + +#endif /* mozilla_dom_workers_runtimeservice_h__ */ diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp new file mode 100644 index 0000000000..315be95ffe --- /dev/null +++ b/dom/workers/ScriptLoader.cpp @@ -0,0 +1,1840 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScriptLoader.h" + +#include +#include + +#include "nsIChannel.h" +#include "nsIContentPolicy.h" +#include "nsIContentSecurityPolicy.h" +#include "nsICookieJarSettings.h" +#include "nsIDocShell.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIIOService.h" +#include "nsIOService.h" +#include "nsIPrincipal.h" +#include "nsIProtocolHandler.h" +#include "nsIScriptError.h" +#include "nsIScriptSecurityManager.h" +#include "nsIStreamListenerTee.h" +#include "nsIThreadRetargetableRequest.h" +#include "nsIURI.h" +#include "nsIXPConnect.h" + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/CompilationAndEvaluation.h" +#include "js/Exception.h" +#include "js/SourceText.h" +#include "js/TypeDecls.h" +#include "nsError.h" +#include "nsComponentManagerUtils.h" +#include "nsContentSecurityManager.h" +#include "nsContentPolicyUtils.h" +#include "nsContentUtils.h" +#include "nsDocShellCID.h" +#include "nsJSEnvironment.h" +#include "nsNetUtil.h" +#include "nsIPipe.h" +#include "nsIOutputStream.h" +#include "nsPrintfCString.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "nsXPCOM.h" +#include "xpcpublic.h" + +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/ArrayAlgorithm.h" +#include "mozilla/Assertions.h" +#include "mozilla/Encoding.h" +#include "mozilla/LoadContext.h" +#include "mozilla/Maybe.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/dom/ClientChannelHelper.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/Exceptions.h" +#include "mozilla/dom/nsCSPService.h" +#include "mozilla/dom/nsCSPUtils.h" +#include "mozilla/dom/PerformanceStorage.h" +#include "mozilla/dom/Response.h" +#include "mozilla/dom/ReferrerInfo.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/SerializedStackHolder.h" +#include "mozilla/dom/workerinternals/CacheLoadHandler.h" +#include "mozilla/dom/workerinternals/NetworkLoadHandler.h" +#include "mozilla/dom/workerinternals/ScriptResponseHeaderProcessor.h" +#include "mozilla/Result.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/UniquePtr.h" +#include "WorkerRunnable.h" +#include "WorkerScope.h" + +#define MAX_CONCURRENT_SCRIPTS 1000 + +using JS::loader::ScriptKind; +using JS::loader::ScriptLoadRequest; +using mozilla::ipc::PrincipalInfo; + +namespace mozilla::dom::workerinternals { +namespace { + +nsresult ConstructURI(const nsAString& aScriptURL, nsIURI* baseURI, + const mozilla::Encoding* aDocumentEncoding, + nsIURI** aResult) { + nsresult rv; + // Only top level workers' main script use the document charset for the + // script uri encoding. Otherwise, default encoding (UTF-8) is applied. + if (aDocumentEncoding) { + nsAutoCString charset; + aDocumentEncoding->Name(charset); + rv = NS_NewURI(aResult, aScriptURL, charset.get(), baseURI); + } else { + rv = NS_NewURI(aResult, aScriptURL, nullptr, baseURI); + } + + if (NS_FAILED(rv)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + return NS_OK; +} + +nsresult ChannelFromScriptURL( + nsIPrincipal* principal, Document* parentDoc, WorkerPrivate* aWorkerPrivate, + nsILoadGroup* loadGroup, nsIIOService* ios, + nsIScriptSecurityManager* secMan, nsIURI* aScriptURL, + const Maybe& aClientInfo, + const Maybe& aController, bool aIsMainScript, + WorkerScriptType aWorkerScriptType, nsContentPolicyType aContentPolicyType, + nsLoadFlags aLoadFlags, uint32_t aSecFlags, + nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo, + nsIChannel** aChannel) { + AssertIsOnMainThread(); + + nsresult rv; + nsCOMPtr uri = aScriptURL; + + // Only use the document when its principal matches the principal of the + // current request. This means scripts fetched using the Workers' own + // principal won't inherit properties of the document, in particular the CSP. + if (parentDoc && parentDoc->NodePrincipal() != principal) { + parentDoc = nullptr; + } + + // The main service worker script should never be loaded over the network + // in this path. It should always be offlined by ServiceWorkerScriptCache. + // We assert here since this error should also be caught by the runtime + // check in CacheLoadHandler. + // + // Note, if we ever allow service worker scripts to be loaded from network + // here we need to configure the channel properly. For example, it must + // not allow redirects. + MOZ_DIAGNOSTIC_ASSERT(aContentPolicyType != + nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER); + + nsCOMPtr channel; + if (parentDoc) { + // This is the path for top level dedicated worker scripts with a document + rv = NS_NewChannel(getter_AddRefs(channel), uri, parentDoc, aSecFlags, + aContentPolicyType, + nullptr, // aPerformanceStorage + loadGroup, + nullptr, // aCallbacks + aLoadFlags, ios); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); + } else { + // This branch is used in the following cases: + // * Shared and ServiceWorkers (who do not have a doc) + // * Static Module Imports + // * ImportScripts + + // We must have a loadGroup with a load context for the principal to + // traverse the channel correctly. + + MOZ_ASSERT(loadGroup); + MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal)); + + RefPtr performanceStorage; + nsCOMPtr cspEventListener; + if (aWorkerPrivate && !aIsMainScript) { + performanceStorage = aWorkerPrivate->GetPerformanceStorage(); + cspEventListener = aWorkerPrivate->CSPEventListener(); + } + + if (aClientInfo.isSome()) { + // If we have an existing clientInfo (true for all modules and + // importScripts), we will use this branch + rv = NS_NewChannel(getter_AddRefs(channel), uri, principal, + aClientInfo.ref(), aController, aSecFlags, + aContentPolicyType, aCookieJarSettings, + performanceStorage, loadGroup, nullptr, // aCallbacks + aLoadFlags, ios); + } else { + rv = NS_NewChannel(getter_AddRefs(channel), uri, principal, aSecFlags, + aContentPolicyType, aCookieJarSettings, + performanceStorage, loadGroup, nullptr, // aCallbacks + aLoadFlags, ios); + } + + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); + + if (cspEventListener) { + nsCOMPtr loadInfo = channel->LoadInfo(); + rv = loadInfo->SetCspEventListener(cspEventListener); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + if (aReferrerInfo) { + nsCOMPtr httpChannel = do_QueryInterface(channel); + if (httpChannel) { + rv = httpChannel->SetReferrerInfo(aReferrerInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } + + channel.forget(aChannel); + return rv; +} + +void LoadAllScripts(WorkerPrivate* aWorkerPrivate, + UniquePtr aOriginStack, + const nsTArray& aScriptURLs, bool aIsMainScript, + WorkerScriptType aWorkerScriptType, ErrorResult& aRv, + const mozilla::Encoding* aDocumentEncoding = nullptr) { + aWorkerPrivate->AssertIsOnWorkerThread(); + NS_ASSERTION(!aScriptURLs.IsEmpty(), "Bad arguments!"); + + AutoSyncLoopHolder syncLoop(aWorkerPrivate, Canceling); + nsCOMPtr syncLoopTarget = + syncLoop.GetSerialEventTarget(); + if (!syncLoopTarget) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + RefPtr loader = + new loader::WorkerScriptLoader(aWorkerPrivate, std::move(aOriginStack), + syncLoopTarget, aWorkerScriptType, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + bool ok = loader->CreateScriptRequests(aScriptURLs, aDocumentEncoding, + aIsMainScript); + + if (!ok) { + return; + } + // Bug 1817259 - For now, we force loading the debugger script as Classic, + // even if the debugged worker is a Module. + if (aWorkerPrivate->WorkerType() == WorkerType::Module && + aWorkerScriptType != DebuggerScript) { + if (!StaticPrefs::dom_workers_modules_enabled()) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + MOZ_ASSERT(aIsMainScript); + // Module Load + RefPtr mainScript = loader->GetMainScript(); + if (mainScript && mainScript->IsModuleRequest()) { + if (NS_FAILED(mainScript->AsModuleRequest()->StartModuleLoad())) { + return; + } + syncLoop.Run(); + return; + } + } + + if (loader->DispatchLoadScripts()) { + syncLoop.Run(); + } +} + +class ChannelGetterRunnable final : public WorkerMainThreadRunnable { + const nsAString& mScriptURL; + const WorkerType& mWorkerType; + const RequestCredentials& mCredentials; + const ClientInfo mClientInfo; + WorkerLoadInfo& mLoadInfo; + nsresult mResult; + + public: + ChannelGetterRunnable(WorkerPrivate* aParentWorker, + const nsAString& aScriptURL, + const WorkerType& aWorkerType, + const RequestCredentials& aCredentials, + WorkerLoadInfo& aLoadInfo) + : WorkerMainThreadRunnable(aParentWorker, + "ScriptLoader :: ChannelGetter"_ns), + mScriptURL(aScriptURL) + // ClientInfo should always be present since this should not be called + // if parent's status is greater than Running. + , + mWorkerType(aWorkerType), + mCredentials(aCredentials), + mClientInfo(aParentWorker->GlobalScope()->GetClientInfo().ref()), + mLoadInfo(aLoadInfo), + mResult(NS_ERROR_FAILURE) { + MOZ_ASSERT(aParentWorker); + aParentWorker->AssertIsOnWorkerThread(); + } + + virtual bool MainThreadRun() override { + AssertIsOnMainThread(); + + // Initialize the WorkerLoadInfo principal to our triggering principal + // before doing anything else. Normally we do this in the WorkerPrivate + // Constructor, but we can't do so off the main thread when creating + // a nested worker. So do it here instead. + mLoadInfo.mLoadingPrincipal = mWorkerPrivate->GetPrincipal(); + MOZ_DIAGNOSTIC_ASSERT(mLoadInfo.mLoadingPrincipal); + + mLoadInfo.mPrincipal = mLoadInfo.mLoadingPrincipal; + + // Figure out our base URI. + nsCOMPtr baseURI = mWorkerPrivate->GetBaseURI(); + MOZ_ASSERT(baseURI); + + // May be null. + nsCOMPtr parentDoc = mWorkerPrivate->GetDocument(); + + mLoadInfo.mLoadGroup = mWorkerPrivate->GetLoadGroup(); + mLoadInfo.mCookieJarSettings = mWorkerPrivate->CookieJarSettings(); + + // Nested workers use default uri encoding. + nsCOMPtr url; + mResult = ConstructURI(mScriptURL, baseURI, nullptr, getter_AddRefs(url)); + NS_ENSURE_SUCCESS(mResult, true); + + Maybe clientInfo; + clientInfo.emplace(mClientInfo); + + nsCOMPtr channel; + nsCOMPtr referrerInfo = + ReferrerInfo::CreateForFetch(mLoadInfo.mLoadingPrincipal, nullptr); + mLoadInfo.mReferrerInfo = + static_cast(referrerInfo.get()) + ->CloneWithNewPolicy(mWorkerPrivate->GetReferrerPolicy()); + + mResult = workerinternals::ChannelFromScriptURLMainThread( + mLoadInfo.mLoadingPrincipal, parentDoc, mLoadInfo.mLoadGroup, url, + mWorkerType, mCredentials, clientInfo, + // Nested workers are always dedicated. + nsIContentPolicy::TYPE_INTERNAL_WORKER, mLoadInfo.mCookieJarSettings, + mLoadInfo.mReferrerInfo, getter_AddRefs(channel)); + NS_ENSURE_SUCCESS(mResult, true); + + mResult = mLoadInfo.SetPrincipalsAndCSPFromChannel(channel); + NS_ENSURE_SUCCESS(mResult, true); + + mLoadInfo.mChannel = std::move(channel); + return true; + } + + nsresult GetResult() const { return mResult; } + + private: + virtual ~ChannelGetterRunnable() = default; +}; + +nsresult GetCommonSecFlags(bool aIsMainScript, nsIURI* uri, + nsIPrincipal* principal, + WorkerScriptType aWorkerScriptType, + uint32_t& secFlags) { + bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal( + principal, uri, true /* aInheritForAboutBlank */, + false /* aForceInherit */); + + bool isData = uri->SchemeIs("data"); + if (inheritAttrs && !isData) { + secFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + } + + if (aWorkerScriptType == DebuggerScript) { + // A DebuggerScript needs to be a local resource like chrome: or resource: + bool isUIResource = false; + nsresult rv = NS_URIChainHasFlags( + uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, &isUIResource); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!isUIResource) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + secFlags |= nsILoadInfo::SEC_ALLOW_CHROME; + } + + // Note: this is for backwards compatibility and goes against spec. + // We should find a better solution. + if (aIsMainScript && isData) { + secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL; + } + + return NS_OK; +} + +nsresult GetModuleSecFlags(bool aIsTopLevel, nsIPrincipal* principal, + WorkerScriptType aWorkerScriptType, nsIURI* aURI, + RequestCredentials aCredentials, + uint32_t& secFlags) { + // Implements "To fetch a single module script," + // Step 9. If destination is "worker", "sharedworker", or "serviceworker", + // and the top-level module fetch flag is set, then set request's + // mode to "same-origin". + secFlags = aIsTopLevel + ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED + : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT; + + // Step 8. Let request be a new request whose [...] mode is "cors" [...] + // This implements the same Cookie settings as nsContentSecurityManager's + // ComputeSecurityFlags. The main difference is the line above, Step 9, + // setting to same origin. + if (aCredentials == RequestCredentials::Include) { + secFlags |= nsILoadInfo::nsILoadInfo::SEC_COOKIES_INCLUDE; + } else if (aCredentials == RequestCredentials::Same_origin) { + secFlags |= nsILoadInfo::nsILoadInfo::SEC_COOKIES_SAME_ORIGIN; + } else if (aCredentials == RequestCredentials::Omit) { + secFlags |= nsILoadInfo::nsILoadInfo::SEC_COOKIES_OMIT; + } + + return GetCommonSecFlags(aIsTopLevel, aURI, principal, aWorkerScriptType, + secFlags); +} + +nsresult GetClassicSecFlags(bool aIsMainScript, nsIURI* uri, + nsIPrincipal* principal, + WorkerScriptType aWorkerScriptType, + uint32_t& secFlags) { + secFlags = aIsMainScript + ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED + : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT; + + return GetCommonSecFlags(aIsMainScript, uri, principal, aWorkerScriptType, + secFlags); +} + +} // anonymous namespace + +namespace loader { + +class ScriptExecutorRunnable final : public MainThreadWorkerSyncRunnable { + RefPtr mScriptLoader; + const Span> mLoadedRequests; + + public: + ScriptExecutorRunnable(WorkerScriptLoader* aScriptLoader, + WorkerPrivate* aWorkerPrivate, + nsISerialEventTarget* aSyncLoopTarget, + Span> aLoadedRequests); + + private: + ~ScriptExecutorRunnable() = default; + + virtual bool IsDebuggerRunnable() const override; + + virtual bool PreRun(WorkerPrivate* aWorkerPrivate) override; + + bool ProcessModuleScript(JSContext* aCx, WorkerPrivate* aWorkerPrivate); + + bool ProcessClassicScripts(JSContext* aCx, WorkerPrivate* aWorkerPrivate); + + virtual bool WorkerRun(JSContext* aCx, + WorkerPrivate* aWorkerPrivate) override; + + nsresult Cancel() override; +}; + +template +static bool EvaluateSourceBuffer(JSContext* aCx, + const JS::CompileOptions& aOptions, + JS::loader::ClassicScript* aClassicScript, + JS::SourceText& aSourceBuffer) { + static_assert(std::is_same::value || + std::is_same::value, + "inferred units must be UTF-8 or UTF-16"); + + JS::Rooted script(aCx, JS::Compile(aCx, aOptions, aSourceBuffer)); + + if (!script) { + return false; + } + + if (aClassicScript) { + aClassicScript->AssociateWithScript(script); + } + + JS::Rooted unused(aCx); + return JS_ExecuteScript(aCx, script, &unused); +} + +WorkerScriptLoader::WorkerScriptLoader( + WorkerPrivate* aWorkerPrivate, + UniquePtr aOriginStack, + nsISerialEventTarget* aSyncLoopTarget, WorkerScriptType aWorkerScriptType, + ErrorResult& aRv) + : mOriginStack(std::move(aOriginStack)), + mSyncLoopTarget(aSyncLoopTarget), + mWorkerScriptType(aWorkerScriptType), + mRv(aRv), + mLoadingModuleRequestCount(0), + mCleanedUp(false), + mCleanUpLock("cleanUpLock") { + aWorkerPrivate->AssertIsOnWorkerThread(); + + RefPtr workerRef = + StrongWorkerRef::Create(aWorkerPrivate, "ScriptLoader"); + + if (workerRef) { + mWorkerRef = new ThreadSafeWorkerRef(workerRef); + } else { + mRv.Throw(NS_ERROR_FAILURE); + return; + } + + nsIGlobalObject* global = GetGlobal(); + mController = global->GetController(); + + if (!StaticPrefs::dom_workers_modules_enabled()) { + return; + } + + // Set up the module loader, if it has not been initialzied yet. + if (!aWorkerPrivate->IsServiceWorker()) { + InitModuleLoader(); + } +} + +ScriptLoadRequest* WorkerScriptLoader::GetMainScript() { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + ScriptLoadRequest* request = mLoadingRequests.getFirst(); + if (request->GetWorkerLoadContext()->IsTopLevel()) { + return request; + } + return nullptr; +} + +void WorkerScriptLoader::InitModuleLoader() { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + if (GetGlobal()->GetModuleLoader(nullptr)) { + return; + } + RefPtr moduleLoader = + new WorkerModuleLoader(this, GetGlobal(), mSyncLoopTarget.get()); + if (mWorkerScriptType == WorkerScript) { + mWorkerRef->Private()->GlobalScope()->InitModuleLoader(moduleLoader); + return; + } + mWorkerRef->Private()->DebuggerGlobalScope()->InitModuleLoader(moduleLoader); +} + +bool WorkerScriptLoader::CreateScriptRequests( + const nsTArray& aScriptURLs, + const mozilla::Encoding* aDocumentEncoding, bool aIsMainScript) { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + // If a worker has been loaded as a module worker, ImportScripts calls are + // disallowed -- then the operation is invalid. + // + // 10.3.1 Importing scripts and libraries. + // Step 1. If worker global scope's type is "module", throw a TypeError + // exception. + // + // Also, for now, the debugger script is always loaded as Classic, + // even if the debugged worker is a Module. We still want to allow + // it to use importScripts. + if (mWorkerRef->Private()->WorkerType() == WorkerType::Module && + !aIsMainScript && !IsDebuggerScript()) { + // This should only run for non-main scripts, as only these are + // importScripts + mRv.ThrowTypeError( + "Using `ImportScripts` inside a Module Worker is " + "disallowed."); + return false; + } + for (const nsString& scriptURL : aScriptURLs) { + RefPtr request = + CreateScriptLoadRequest(scriptURL, aDocumentEncoding, aIsMainScript); + if (!request) { + return false; + } + mLoadingRequests.AppendElement(request); + } + + return true; +} + +nsTArray> WorkerScriptLoader::GetLoadingList() { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + nsTArray> list; + for (ScriptLoadRequest* req = mLoadingRequests.getFirst(); req; + req = req->getNext()) { + RefPtr handle = + new ThreadSafeRequestHandle(req, mSyncLoopTarget.get()); + list.AppendElement(handle.forget()); + } + return list; +} + +bool WorkerScriptLoader::IsDynamicImport(ScriptLoadRequest* aRequest) { + return aRequest->IsModuleRequest() && + aRequest->AsModuleRequest()->IsDynamicImport(); +} + +nsContentPolicyType WorkerScriptLoader::GetContentPolicyType( + ScriptLoadRequest* aRequest) { + if (aRequest->GetWorkerLoadContext()->IsTopLevel()) { + // Implements https://html.spec.whatwg.org/#worker-processing-model + // Step 13: Let destination be "sharedworker" if is shared is true, and + // "worker" otherwise. + return mWorkerRef->Private()->ContentPolicyType(); + } + if (aRequest->IsModuleRequest()) { + // Implements the destination for Step 14 in + // https://html.spec.whatwg.org/#worker-processing-model + // + // We need a special subresource type in order to correctly implement + // the graph fetch, where the destination is set to "worker" or + // "sharedworker". + return nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE; + } + return nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS; +} + +already_AddRefed WorkerScriptLoader::CreateScriptLoadRequest( + const nsString& aScriptURL, const mozilla::Encoding* aDocumentEncoding, + bool aIsMainScript) { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + WorkerLoadContext::Kind kind = + WorkerLoadContext::GetKind(aIsMainScript, IsDebuggerScript()); + + Maybe clientInfo = GetGlobal()->GetClientInfo(); + + // (For non-serviceworkers, this variable does not matter, but false best + // captures their behavior.) + bool onlyExistingCachedResourcesAllowed = false; + if (mWorkerRef->Private()->IsServiceWorker()) { + // https://w3c.github.io/ServiceWorker/#importscripts step 4: + // > 4. If serviceWorker’s state is not "parsed" or "installing": + // > 1. Return map[url] if it exists and a network error otherwise. + // + // So if our state is beyond installing, it's too late to make a request + // that would perform a new fetch which would be cached. + onlyExistingCachedResourcesAllowed = + mWorkerRef->Private()->GetServiceWorkerDescriptor().State() > + ServiceWorkerState::Installing; + } + RefPtr loadContext = new WorkerLoadContext( + kind, clientInfo, this, onlyExistingCachedResourcesAllowed); + + // Create ScriptLoadRequests for this WorkerScriptLoader + ReferrerPolicy referrerPolicy = mWorkerRef->Private()->GetReferrerPolicy(); + + // Only top level workers' main script use the document charset for the + // script uri encoding. Otherwise, default encoding (UTF-8) is applied. + MOZ_ASSERT_IF(bool(aDocumentEncoding), + aIsMainScript && !mWorkerRef->Private()->GetParent()); + nsCOMPtr baseURI = aIsMainScript ? GetInitialBaseURI() : GetBaseURI(); + nsCOMPtr uri; + nsresult rv = + ConstructURI(aScriptURL, baseURI, aDocumentEncoding, getter_AddRefs(uri)); + // If we failed to construct the URI, handle it in the LoadContext so it is + // thrown in the right order. + if (NS_WARN_IF(NS_FAILED(rv))) { + loadContext->mLoadResult = rv; + } + + RefPtr fetchOptions = + new ScriptFetchOptions(CORSMode::CORS_NONE, referrerPolicy, nullptr); + + RefPtr request = nullptr; + // Bug 1817259 - For now the debugger scripts are always loaded a Classic. + if (mWorkerRef->Private()->WorkerType() == WorkerType::Classic || + IsDebuggerScript()) { + request = new ScriptLoadRequest(ScriptKind::eClassic, uri, fetchOptions, + SRIMetadata(), nullptr, // mReferrer + loadContext); + } else { + // Implements part of "To fetch a worklet/module worker script graph" + // including, setting up the request with a credentials mode, + // destination. + + // Step 1. Let options be a script fetch options. + // We currently don't track credentials in our ScriptFetchOptions + // implementation, so we are defaulting the fetchOptions object defined + // above. This behavior is handled fully in GetModuleSecFlags. + + if (!StaticPrefs::dom_workers_modules_enabled()) { + mRv.ThrowTypeError("Modules in workers are currently disallowed."); + return nullptr; + } + RefPtr moduleLoader = + GetGlobal()->GetModuleLoader(nullptr); + + // Implements the referrer for "To fetch a single module script" + // Our implementation does not have a "client" as a referrer. + // However, when client is resolved (per 8.3. Determine request’s + // Referrer in + // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer) + // This should result in the referrer source being the creation URL. + // + // In subresource modules, the referrer is the importing script. + nsCOMPtr referrer = + mWorkerRef->Private()->GetReferrerInfo()->GetOriginalReferrer(); + + // Part of Step 2. This sets the Top-level flag to true + request = new ModuleLoadRequest( + uri, fetchOptions, SRIMetadata(), referrer, loadContext, + true, /* is top level */ + false, /* is dynamic import */ + moduleLoader, ModuleLoadRequest::NewVisitedSetForTopLevelImport(uri), + nullptr); + } + + // Set the mURL, it will be used for error handling and debugging. + request->mURL = NS_ConvertUTF16toUTF8(aScriptURL); + + return request.forget(); +} + +bool WorkerScriptLoader::DispatchLoadScript(ScriptLoadRequest* aRequest) { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + + IncreaseLoadingModuleRequestCount(); + + nsTArray> scriptLoadList; + RefPtr handle = + new ThreadSafeRequestHandle(aRequest, mSyncLoopTarget.get()); + scriptLoadList.AppendElement(handle.forget()); + + RefPtr runnable = + new ScriptLoaderRunnable(this, std::move(scriptLoadList)); + + RefPtr workerRef = StrongWorkerRef::Create( + mWorkerRef->Private(), "ScriptLoader", [runnable]() { + NS_DispatchToMainThread(NewRunnableMethod( + "ScriptLoaderRunnable::CancelMainThreadWithBindingAborted", + runnable, + &ScriptLoaderRunnable::CancelMainThreadWithBindingAborted)); + }); + + if (NS_FAILED(NS_DispatchToMainThread(runnable))) { + NS_ERROR("Failed to dispatch!"); + mRv.Throw(NS_ERROR_FAILURE); + return false; + } + return true; +} + +bool WorkerScriptLoader::DispatchLoadScripts() { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + + nsTArray> scriptLoadList = GetLoadingList(); + + RefPtr runnable = + new ScriptLoaderRunnable(this, std::move(scriptLoadList)); + + RefPtr workerRef = StrongWorkerRef::Create( + mWorkerRef->Private(), "ScriptLoader", [runnable]() { + NS_DispatchToMainThread(NewRunnableMethod( + "ScriptLoaderRunnable::CancelMainThreadWithBindingAborted", + runnable, + &ScriptLoaderRunnable::CancelMainThreadWithBindingAborted)); + }); + + if (NS_FAILED(NS_DispatchToMainThread(runnable))) { + NS_ERROR("Failed to dispatch!"); + mRv.Throw(NS_ERROR_FAILURE); + return false; + } + return true; +} + +nsIURI* WorkerScriptLoader::GetInitialBaseURI() { + MOZ_ASSERT(mWorkerRef->Private()); + nsIURI* baseURI; + WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent(); + if (parentWorker) { + baseURI = parentWorker->GetBaseURI(); + } else { + // May be null. + baseURI = mWorkerRef->Private()->GetBaseURI(); + } + + return baseURI; +} + +nsIURI* WorkerScriptLoader::GetBaseURI() const { + MOZ_ASSERT(mWorkerRef); + nsIURI* baseURI; + baseURI = mWorkerRef->Private()->GetBaseURI(); + NS_ASSERTION(baseURI, "Should have been set already!"); + + return baseURI; +} + +nsIGlobalObject* WorkerScriptLoader::GetGlobal() { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + return mWorkerScriptType == WorkerScript + ? static_cast( + mWorkerRef->Private()->GlobalScope()) + : mWorkerRef->Private()->DebuggerGlobalScope(); +} + +void WorkerScriptLoader::MaybeMoveToLoadedList(ScriptLoadRequest* aRequest) { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + // Only set to ready for regular scripts. Module loader will set the script to + // ready if it is a Module Request. + if (!aRequest->IsModuleRequest()) { + aRequest->SetReady(); + } + + // If the request is not in a list, we are in an illegal state. + MOZ_RELEASE_ASSERT(aRequest->isInList()); + + while (!mLoadingRequests.isEmpty()) { + ScriptLoadRequest* request = mLoadingRequests.getFirst(); + // We need to move requests in post order. If prior requests have not + // completed, delay execution. + if (!request->IsReadyToRun()) { + break; + } + + RefPtr req = mLoadingRequests.Steal(request); + mLoadedRequests.AppendElement(req); + } +} + +bool WorkerScriptLoader::StoreCSP() { + // We must be on the same worker as we started on. + mWorkerRef->Private()->AssertIsOnWorkerThread(); + + if (!mWorkerRef->Private()->GetJSContext()) { + return false; + } + + MOZ_ASSERT(!mRv.Failed()); + + // Move the CSP from the workerLoadInfo in the corresponding Client + // where the CSP code expects it! + mWorkerRef->Private()->StoreCSPOnClient(); + return true; +} + +bool WorkerScriptLoader::ProcessPendingRequests(JSContext* aCx) { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + // Don't run if something else has already failed. + if (mExecutionAborted) { + mLoadedRequests.CancelRequestsAndClear(); + TryShutdown(); + return true; + } + + // If nothing else has failed, our ErrorResult better not be a failure + // either. + MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?"); + + // Slightly icky action at a distance, but there's no better place to stash + // this value, really. + JS::Rooted global(aCx, JS::CurrentGlobalOrNull(aCx)); + MOZ_ASSERT(global); + + while (!mLoadedRequests.isEmpty()) { + // Take a reference, but do not remove it from the list yet. There is a + // possibility that this will need to be cancelled. + RefPtr req = mLoadedRequests.getFirst(); + // We don't have a ProcessRequest method (like we do on the DOM), as there + // isn't much processing that we need to do per request that isn't related + // to evaluation (the processsing done for the DOM is handled in + // DataRecievedFrom{Cache,Network} for workers. + // So, this inner loop calls EvaluateScript directly. This will change + // once modules are introduced as we will have some extra work to do. + if (!EvaluateScript(aCx, req)) { + mExecutionAborted = true; + WorkerLoadContext* loadContext = req->GetWorkerLoadContext(); + mMutedErrorFlag = loadContext->mMutedErrorFlag.valueOr(true); + mLoadedRequests.CancelRequestsAndClear(); + break; + } + // remove the element from the list. + mLoadedRequests.Remove(req); + } + + TryShutdown(); + return true; +} + +nsresult WorkerScriptLoader::LoadScript( + ThreadSafeRequestHandle* aRequestHandle) { + AssertIsOnMainThread(); + + WorkerLoadContext* loadContext = aRequestHandle->GetContext(); + ScriptLoadRequest* request = aRequestHandle->GetRequest(); + MOZ_ASSERT_IF(loadContext->IsTopLevel(), !IsDebuggerScript()); + + // The URL passed to us for loading was invalid, stop loading at this point. + if (loadContext->mLoadResult != NS_ERROR_NOT_INITIALIZED) { + return loadContext->mLoadResult; + } + + WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent(); + + // For JavaScript debugging, the devtools server must run on the same + // thread as the debuggee, indicating the worker uses content principal. + // However, in Bug 863246, web content will no longer be able to load + // resource:// URIs by default, so we need system principal to load + // debugger scripts. + nsIPrincipal* principal = (IsDebuggerScript()) + ? nsContentUtils::GetSystemPrincipal() + : mWorkerRef->Private()->GetPrincipal(); + + nsCOMPtr loadGroup = mWorkerRef->Private()->GetLoadGroup(); + MOZ_DIAGNOSTIC_ASSERT(principal); + + NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadGroup, principal), + NS_ERROR_FAILURE); + + // May be null. + nsCOMPtr parentDoc = mWorkerRef->Private()->GetDocument(); + + nsCOMPtr channel; + if (loadContext->IsTopLevel()) { + // May be null. + channel = mWorkerRef->Private()->ForgetWorkerChannel(); + } + + nsCOMPtr ios(do_GetIOService()); + + nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(secMan, "This should never be null!"); + + nsresult& rv = loadContext->mLoadResult; + + nsLoadFlags loadFlags = mWorkerRef->Private()->GetLoadFlags(); + + // Get the top-level worker. + WorkerPrivate* topWorkerPrivate = mWorkerRef->Private(); + WorkerPrivate* parent = topWorkerPrivate->GetParent(); + while (parent) { + topWorkerPrivate = parent; + parent = topWorkerPrivate->GetParent(); + } + + // If the top-level worker is a dedicated worker and has a window, and the + // window has a docshell, the caching behavior of this worker should match + // that of that docshell. + if (topWorkerPrivate->IsDedicatedWorker()) { + nsCOMPtr window = topWorkerPrivate->GetWindow(); + if (window) { + nsCOMPtr docShell = window->GetDocShell(); + if (docShell) { + nsresult rv = docShell->GetDefaultLoadFlags(&loadFlags); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + if (!channel) { + nsCOMPtr referrerInfo; + uint32_t secFlags; + if (request->IsModuleRequest()) { + // https://fetch.spec.whatwg.org/#concept-main-fetch + // Step 8. If request’s referrer policy is the empty string, then set + // request’s referrer policy to request’s policy container’s + // referrer policy. + ReferrerPolicy policy = + request->ReferrerPolicy() == ReferrerPolicy::_empty + ? mWorkerRef->Private()->GetReferrerPolicy() + : request->ReferrerPolicy(); + + referrerInfo = new ReferrerInfo(request->mReferrer, policy); + rv = GetModuleSecFlags( + loadContext->IsTopLevel(), principal, mWorkerScriptType, + request->mURI, mWorkerRef->Private()->WorkerCredentials(), secFlags); + } else { + referrerInfo = ReferrerInfo::CreateForFetch(principal, nullptr); + if (parentWorker && !loadContext->IsTopLevel()) { + referrerInfo = + static_cast(referrerInfo.get()) + ->CloneWithNewPolicy(parentWorker->GetReferrerPolicy()); + } + rv = GetClassicSecFlags(loadContext->IsTopLevel(), request->mURI, + principal, mWorkerScriptType, secFlags); + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsContentPolicyType contentPolicyType = GetContentPolicyType(request); + + rv = ChannelFromScriptURL( + principal, parentDoc, mWorkerRef->Private(), loadGroup, ios, secMan, + request->mURI, loadContext->mClientInfo, mController, + loadContext->IsTopLevel(), mWorkerScriptType, contentPolicyType, + loadFlags, secFlags, mWorkerRef->Private()->CookieJarSettings(), + referrerInfo, getter_AddRefs(channel)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + // Associate any originating stack with the channel. + if (!mOriginStackJSON.IsEmpty()) { + NotifyNetworkMonitorAlternateStack(channel, mOriginStackJSON); + } + + // We need to know which index we're on in OnStreamComplete so we know + // where to put the result. + RefPtr listener = + new NetworkLoadHandler(this, aRequestHandle); + + RefPtr headerProcessor = nullptr; + + // For each debugger script, a non-debugger script load of the same script + // should have occured prior that processed the headers. + if (!IsDebuggerScript()) { + headerProcessor = MakeRefPtr( + mWorkerRef->Private(), + loadContext->IsTopLevel() && !IsDynamicImport(request), + GetContentPolicyType(request) == + nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS); + } + + nsCOMPtr loader; + rv = NS_NewStreamLoader(getter_AddRefs(loader), listener, headerProcessor); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (loadContext->IsTopLevel()) { + MOZ_DIAGNOSTIC_ASSERT(loadContext->mClientInfo.isSome()); + + // In order to get the correct foreign partitioned prinicpal, we need to + // set the `IsThirdPartyContextToTopWindow` to the channel's loadInfo. + // This flag reflects the fact that if the worker is created under a + // third-party context. + nsCOMPtr loadInfo = channel->LoadInfo(); + loadInfo->SetIsThirdPartyContextToTopWindow( + mWorkerRef->Private()->IsThirdPartyContextToTopWindow()); + + Maybe clientInfo; + clientInfo.emplace(loadContext->mClientInfo.ref()); + rv = AddClientChannelHelper(channel, std::move(clientInfo), + Maybe(), + mWorkerRef->Private()->HybridEventTarget()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + if (StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) { + nsILoadInfo::CrossOriginEmbedderPolicy respectedCOEP = + mWorkerRef->Private()->GetEmbedderPolicy(); + if (mWorkerRef->Private()->IsDedicatedWorker() && + respectedCOEP == nsILoadInfo::EMBEDDER_POLICY_NULL) { + respectedCOEP = mWorkerRef->Private()->GetOwnerEmbedderPolicy(); + } + + nsCOMPtr channelLoadInfo = channel->LoadInfo(); + channelLoadInfo->SetLoadingEmbedderPolicy(respectedCOEP); + } + + if (loadContext->mCacheStatus != WorkerLoadContext::ToBeCached) { + rv = channel->AsyncOpen(loader); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + nsCOMPtr writer; + + // In case we return early. + loadContext->mCacheStatus = WorkerLoadContext::Cancel; + + NS_NewPipe(getter_AddRefs(loadContext->mCacheReadStream), + getter_AddRefs(writer), 0, + UINT32_MAX, // unlimited size to avoid writer WOULD_BLOCK case + true, false); // non-blocking reader, blocking writer + + nsCOMPtr tee = + do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID); + rv = tee->Init(loader, writer, listener); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsresult rv = channel->AsyncOpen(tee); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + loadContext->mChannel.swap(channel); + + return NS_OK; +} + +nsresult WorkerScriptLoader::FillCompileOptionsForRequest( + JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions, + JS::MutableHandle aIntroductionScript) { + // The full URL shouldn't be exposed to the debugger. See Bug 1634872 + aOptions->setFileAndLine(aRequest->mURL.get(), 1); + aOptions->setNoScriptRval(true); + + aOptions->setMutedErrors( + aRequest->GetWorkerLoadContext()->mMutedErrorFlag.value()); + + if (aRequest->mSourceMapURL) { + aOptions->setSourceMapURL(aRequest->mSourceMapURL->get()); + } + + return NS_OK; +} + +bool WorkerScriptLoader::EvaluateScript(JSContext* aCx, + ScriptLoadRequest* aRequest) { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + MOZ_ASSERT(!IsDynamicImport(aRequest)); + + WorkerLoadContext* loadContext = aRequest->GetWorkerLoadContext(); + + NS_ASSERTION(!loadContext->mChannel, "Should no longer have a channel!"); + NS_ASSERTION(aRequest->IsReadyToRun(), "Should be scheduled!"); + + MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?"); + mRv.MightThrowJSException(); + if (NS_FAILED(loadContext->mLoadResult)) { + ReportErrorToConsole(aRequest, loadContext->mLoadResult); + return false; + } + + // If this is a top level script that succeeded, then mark the + // Client execution ready and possible controlled by a service worker. + if (loadContext->IsTopLevel()) { + if (mController.isSome()) { + MOZ_ASSERT(mWorkerScriptType == WorkerScript, + "Debugger clients can't be controlled."); + mWorkerRef->Private()->GlobalScope()->Control(mController.ref()); + } + mWorkerRef->Private()->ExecutionReady(); + } + + if (aRequest->IsModuleRequest()) { + // Only the top level module of the module graph will be executed from here, + // the rest will be executed from SpiderMonkey as part of the execution of + // the module graph. + MOZ_ASSERT(aRequest->IsTopLevel()); + ModuleLoadRequest* request = aRequest->AsModuleRequest(); + if (!request->mModuleScript) { + return false; + } + + // Implements To fetch a worklet/module worker script graph + // Step 5. Fetch the descendants of and link result. + if (!request->InstantiateModuleGraph()) { + return false; + } + + nsresult rv = request->EvaluateModule(); + return NS_SUCCEEDED(rv); + } + + JS::CompileOptions options(aCx); + // The introduction script is used by the DOM script loader as a way + // to fill the Debugger Metadata for the JS Execution context. We don't use + // the JS Execution context as we are not making use of async compilation + // (delegation to another worker to produce bytecode or compile a string to a + // JSScript), so it is not used in this context. + JS::Rooted unusedIntroductionScript(aCx); + nsresult rv = FillCompileOptionsForRequest(aCx, aRequest, &options, + &unusedIntroductionScript); + + MOZ_ASSERT(NS_SUCCEEDED(rv), "Filling compile options should not fail"); + + // Our ErrorResult still shouldn't be a failure. + MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?"); + + // Get the source text. + ScriptLoadRequest::MaybeSourceText maybeSource; + rv = aRequest->GetScriptSource(aCx, &maybeSource); + if (NS_FAILED(rv)) { + mRv.StealExceptionFromJSContext(aCx); + return false; + } + + RefPtr classicScript = nullptr; + if (StaticPrefs::dom_workers_modules_enabled() && + !mWorkerRef->Private()->IsServiceWorker()) { + // We need a LoadedScript to be associated with the JSScript in order to + // correctly resolve the referencing private for dynamic imports. In turn + // this allows us to correctly resolve the BaseURL. + // + // Dynamic import is disallowed on service workers. Additionally, causes + // crashes because the life cycle isn't completed for service workers. To + // keep things simple, we don't create a classic script for ServiceWorkers. + // If this changes then we will need to ensure that the reference that is + // held is released appropriately. + nsCOMPtr requestBaseURI; + if (loadContext->mMutedErrorFlag.valueOr(false)) { + NS_NewURI(getter_AddRefs(requestBaseURI), "about:blank"_ns); + } else { + requestBaseURI = aRequest->mBaseURL; + } + classicScript = + new JS::loader::ClassicScript(aRequest->mFetchOptions, requestBaseURI); + } + + bool successfullyEvaluated = + aRequest->IsUTF8Text() + ? EvaluateSourceBuffer(aCx, options, classicScript, + maybeSource.ref>()) + : EvaluateSourceBuffer(aCx, options, classicScript, + maybeSource.ref>()); + + if (aRequest->IsCanceled()) { + return false; + } + if (!successfullyEvaluated) { + mRv.StealExceptionFromJSContext(aCx); + return false; + } + // steal the loadContext so that the cycle is broken and cycle collector can + // collect the scriptLoadRequest. + return true; +} + +void WorkerScriptLoader::TryShutdown() { + if (AllScriptsExecuted() && AllModuleRequestsLoaded()) { + ShutdownScriptLoader(!mExecutionAborted, mMutedErrorFlag); + } +} + +void WorkerScriptLoader::ShutdownScriptLoader(bool aResult, bool aMutedError) { + MOZ_ASSERT(AllScriptsExecuted()); + MOZ_ASSERT(AllModuleRequestsLoaded()); + mWorkerRef->Private()->AssertIsOnWorkerThread(); + + if (!aResult) { + // At this point there are two possibilities: + // + // 1) mRv.Failed(). In that case we just want to leave it + // as-is, except if it has a JS exception and we need to mute JS + // exceptions. In that case, we log the exception without firing any + // events and then replace it on the ErrorResult with a NetworkError, + // per spec. + // + // 2) mRv succeeded. As far as I can tell, this can only + // happen when loading the main worker script and + // GetOrCreateGlobalScope() fails or if ScriptExecutorRunnable::Cancel + // got called. Does it matter what we throw in this case? I'm not + // sure... + if (mRv.Failed()) { + if (aMutedError && mRv.IsJSException()) { + LogExceptionToConsole(mWorkerRef->Private()->GetJSContext(), + mWorkerRef->Private()); + mRv.Throw(NS_ERROR_DOM_NETWORK_ERR); + } + } else { + mRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + } + } + + // Lock, shutdown, and cleanup state. After this the Loader is closed. + { + MutexAutoLock lock(CleanUpLock()); + + if (CleanedUp()) { + return; + } + + mWorkerRef->Private()->AssertIsOnWorkerThread(); + // Module loader doesn't use sync loop for dynamic import + if (mSyncLoopTarget) { + mWorkerRef->Private()->StopSyncLoop(mSyncLoopTarget, + aResult ? NS_OK : NS_ERROR_FAILURE); + } + + // Signal cleanup + mCleanedUp = true; + + // Allow worker shutdown. + mWorkerRef = nullptr; + } +} + +void WorkerScriptLoader::ReportErrorToConsole(ScriptLoadRequest* aRequest, + nsresult aResult) const { + nsAutoString url = NS_ConvertUTF8toUTF16(aRequest->mURL); + workerinternals::ReportLoadError(mRv, aResult, url); +} + +void WorkerScriptLoader::LogExceptionToConsole(JSContext* aCx, + WorkerPrivate* aWorkerPrivate) { + aWorkerPrivate->AssertIsOnWorkerThread(); + + MOZ_ASSERT(mRv.IsJSException()); + + JS::Rooted exn(aCx); + if (!ToJSValue(aCx, std::move(mRv), &exn)) { + return; + } + + // Now the exception state should all be in exn. + MOZ_ASSERT(!JS_IsExceptionPending(aCx)); + MOZ_ASSERT(!mRv.Failed()); + + JS::ExceptionStack exnStack(aCx, exn, nullptr); + JS::ErrorReportBuilder report(aCx); + if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) { + JS_ClearPendingException(aCx); + return; + } + + RefPtr xpcReport = new xpc::ErrorReport(); + xpcReport->Init(report.report(), report.toStringResult().c_str(), + aWorkerPrivate->IsChromeWorker(), aWorkerPrivate->WindowID()); + + RefPtr r = new AsyncErrorReporter(xpcReport); + NS_DispatchToMainThread(r); +} + +bool WorkerScriptLoader::AllModuleRequestsLoaded() const { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + return mLoadingModuleRequestCount == 0; +} + +void WorkerScriptLoader::IncreaseLoadingModuleRequestCount() { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + ++mLoadingModuleRequestCount; +} + +void WorkerScriptLoader::DecreaseLoadingModuleRequestCount() { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + --mLoadingModuleRequestCount; +} + +NS_IMPL_ISUPPORTS(ScriptLoaderRunnable, nsIRunnable, nsINamed) + +NS_IMPL_ISUPPORTS(WorkerScriptLoader, nsINamed) + +ScriptLoaderRunnable::ScriptLoaderRunnable( + WorkerScriptLoader* aScriptLoader, + nsTArray> aLoadingRequests) + : mScriptLoader(aScriptLoader), + mWorkerRef(aScriptLoader->mWorkerRef), + mLoadingRequests(std::move(aLoadingRequests)), + mCancelMainThread(Nothing()) { + MOZ_ASSERT(aScriptLoader); +} + +nsresult ScriptLoaderRunnable::Run() { + AssertIsOnMainThread(); + + // Convert the origin stack to JSON (which must be done on the main + // thread) explicitly, so that we can use the stack to notify the net + // monitor about every script we load. We do this, rather than pass + // the stack directly to the netmonitor, in order to be able to use this + // for all subsequent scripts. + if (mScriptLoader->mOriginStack && + mScriptLoader->mOriginStackJSON.IsEmpty()) { + ConvertSerializedStackToJSON(std::move(mScriptLoader->mOriginStack), + mScriptLoader->mOriginStackJSON); + } + + if (!mWorkerRef->Private()->IsServiceWorker() || + mScriptLoader->IsDebuggerScript()) { + for (ThreadSafeRequestHandle* handle : mLoadingRequests) { + handle->mRunnable = this; + } + + for (ThreadSafeRequestHandle* handle : mLoadingRequests) { + nsresult rv = mScriptLoader->LoadScript(handle); + if (NS_WARN_IF(NS_FAILED(rv))) { + LoadingFinished(handle, rv); + CancelMainThread(rv); + return rv; + } + } + + return NS_OK; + } + + MOZ_ASSERT(!mCacheCreator); + mCacheCreator = new CacheCreator(mWorkerRef->Private()); + + for (ThreadSafeRequestHandle* handle : mLoadingRequests) { + handle->mRunnable = this; + WorkerLoadContext* loadContext = handle->GetContext(); + mCacheCreator->AddLoader(MakeNotNull>( + mWorkerRef, handle, loadContext->IsTopLevel(), + loadContext->mOnlyExistingCachedResourcesAllowed, mScriptLoader)); + } + + // The worker may have a null principal on first load, but in that case its + // parent definitely will have one. + nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal(); + if (!principal) { + WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent(); + MOZ_ASSERT(parentWorker, "Must have a parent!"); + principal = parentWorker->GetPrincipal(); + } + + nsresult rv = mCacheCreator->Load(principal); + if (NS_WARN_IF(NS_FAILED(rv))) { + CancelMainThread(rv); + return rv; + } + + return NS_OK; +} + +nsresult ScriptLoaderRunnable::OnStreamComplete( + ThreadSafeRequestHandle* aRequestHandle, nsresult aStatus) { + AssertIsOnMainThread(); + + LoadingFinished(aRequestHandle, aStatus); + return NS_OK; +} + +void ScriptLoaderRunnable::LoadingFinished( + ThreadSafeRequestHandle* aRequestHandle, nsresult aRv) { + AssertIsOnMainThread(); + + WorkerLoadContext* loadContext = aRequestHandle->GetContext(); + + loadContext->mLoadResult = aRv; + MOZ_ASSERT(!loadContext->mLoadingFinished); + loadContext->mLoadingFinished = true; + + if (loadContext->IsTopLevel() && NS_SUCCEEDED(aRv)) { + MOZ_DIAGNOSTIC_ASSERT( + mWorkerRef->Private()->PrincipalURIMatchesScriptURL()); + } + + MaybeExecuteFinishedScripts(aRequestHandle); +} + +void ScriptLoaderRunnable::MaybeExecuteFinishedScripts( + ThreadSafeRequestHandle* aRequestHandle) { + AssertIsOnMainThread(); + + // We execute the last step if we don't have a pending operation with the + // cache and the loading is completed. + WorkerLoadContext* loadContext = aRequestHandle->GetContext(); + if (!loadContext->IsAwaitingPromise()) { + if (aRequestHandle->GetContext()->IsTopLevel()) { + mWorkerRef->Private()->WorkerScriptLoaded(); + } + DispatchProcessPendingRequests(); + } +} + +void ScriptLoaderRunnable::CancelMainThreadWithBindingAborted() { + AssertIsOnMainThread(); + CancelMainThread(NS_BINDING_ABORTED); +} + +void ScriptLoaderRunnable::CancelMainThread(nsresult aCancelResult) { + AssertIsOnMainThread(); + if (IsCancelled()) { + return; + } + + { + MutexAutoLock lock(mScriptLoader->CleanUpLock()); + + // Check if we have already cancelled, or if the worker has been killed + // before we cancel. + if (mScriptLoader->CleanedUp()) { + return; + } + + mCancelMainThread = Some(aCancelResult); + + for (ThreadSafeRequestHandle* handle : mLoadingRequests) { + if (handle->IsEmpty()) { + continue; + } + + bool callLoadingFinished = true; + + WorkerLoadContext* loadContext = handle->GetContext(); + if (!loadContext) { + continue; + } + + if (loadContext->IsAwaitingPromise()) { + MOZ_ASSERT(mWorkerRef->Private()->IsServiceWorker()); + loadContext->mCachePromise->MaybeReject(NS_BINDING_ABORTED); + loadContext->mCachePromise = nullptr; + callLoadingFinished = false; + } + if (loadContext->mChannel) { + if (NS_SUCCEEDED(loadContext->mChannel->Cancel(aCancelResult))) { + callLoadingFinished = false; + } else { + NS_WARNING("Failed to cancel channel!"); + } + } + if (callLoadingFinished && !loadContext->mLoadingFinished) { + LoadingFinished(handle, aCancelResult); + } + } + DispatchProcessPendingRequests(); + } +} + +void ScriptLoaderRunnable::DispatchProcessPendingRequests() { + AssertIsOnMainThread(); + + const auto begin = mLoadingRequests.begin(); + const auto end = mLoadingRequests.end(); + using Iterator = decltype(begin); + const auto maybeRangeToExecute = + [begin, end]() -> Maybe> { + // firstItToExecute is the first loadInfo where mExecutionScheduled is + // unset. + auto firstItToExecute = std::find_if( + begin, end, [](const RefPtr& requestHandle) { + return !requestHandle->mExecutionScheduled; + }); + + if (firstItToExecute == end) { + return Nothing(); + } + + // firstItUnexecutable is the first loadInfo that is not yet finished. + // Update mExecutionScheduled on the ones we're about to schedule for + // execution. + const auto firstItUnexecutable = + std::find_if(firstItToExecute, end, + [](RefPtr& requestHandle) { + MOZ_ASSERT(!requestHandle->IsEmpty()); + if (!requestHandle->Finished()) { + return true; + } + + // We can execute this one. + requestHandle->mExecutionScheduled = true; + + return false; + }); + + return firstItUnexecutable == firstItToExecute + ? Nothing() + : Some(std::pair(firstItToExecute, firstItUnexecutable)); + }(); + + // If there are no unexecutable load infos, we can unuse things before the + // execution of the scripts and the stopping of the sync loop. + if (maybeRangeToExecute) { + if (maybeRangeToExecute->second == end) { + mCacheCreator = nullptr; + } + + RefPtr runnable = new ScriptExecutorRunnable( + mScriptLoader, mWorkerRef->Private(), mScriptLoader->mSyncLoopTarget, + Span>{maybeRangeToExecute->first, + maybeRangeToExecute->second}); + + if (!runnable->Dispatch() && mScriptLoader->mSyncLoopTarget) { + MOZ_ASSERT(false, "This should never fail!"); + } + } +} + +ScriptExecutorRunnable::ScriptExecutorRunnable( + WorkerScriptLoader* aScriptLoader, WorkerPrivate* aWorkerPrivate, + nsISerialEventTarget* aSyncLoopTarget, + Span> aLoadedRequests) + : MainThreadWorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget), + mScriptLoader(aScriptLoader), + mLoadedRequests(aLoadedRequests) {} + +bool ScriptExecutorRunnable::IsDebuggerRunnable() const { + // ScriptExecutorRunnable is used to execute both worker and debugger scripts. + // In the latter case, the runnable needs to be dispatched to the debugger + // queue. + return mScriptLoader->IsDebuggerScript(); +} + +bool ScriptExecutorRunnable::PreRun(WorkerPrivate* aWorkerPrivate) { + aWorkerPrivate->AssertIsOnWorkerThread(); + + // We must be on the same worker as we started on. + MOZ_ASSERT( + mScriptLoader->mSyncLoopTarget == mSyncLoopTarget, + "Unexpected SyncLoopTarget. Check if the sync loop was closed early"); + + { + // There is a possibility that we cleaned up while this task was waiting to + // run. If this has happened, return and exit. + MutexAutoLock lock(mScriptLoader->CleanUpLock()); + if (mScriptLoader->CleanedUp()) { + return true; + } + + const auto& requestHandle = mLoadedRequests[0]; + // Check if the request is still valid. + if (requestHandle->IsEmpty() || + !requestHandle->GetContext()->IsTopLevel()) { + return true; + } + } + + return mScriptLoader->StoreCSP(); +} + +bool ScriptExecutorRunnable::ProcessModuleScript( + JSContext* aCx, WorkerPrivate* aWorkerPrivate) { + // We should only ever have one script when processing modules + MOZ_ASSERT(mLoadedRequests.Length() == 1); + RefPtr request; + { + // There is a possibility that we cleaned up while this task was waiting to + // run. If this has happened, return and exit. + MutexAutoLock lock(mScriptLoader->CleanUpLock()); + if (mScriptLoader->CleanedUp()) { + return true; + } + + MOZ_ASSERT(mLoadedRequests.Length() == 1); + const auto& requestHandle = mLoadedRequests[0]; + // The request must be valid. + MOZ_ASSERT(!requestHandle->IsEmpty()); + + // Release the request to the worker. From this point on, the Request Handle + // is empty. + request = requestHandle->ReleaseRequest(); + + // release lock. We will need it later if we cleanup. + } + + MOZ_ASSERT(request->IsModuleRequest()); + + WorkerLoadContext* loadContext = request->GetWorkerLoadContext(); + ModuleLoadRequest* moduleRequest = request->AsModuleRequest(); + + // DecreaseLoadingModuleRequestCount must be called before OnFetchComplete. + // OnFetchComplete will call ProcessPendingRequests, and in + // ProcessPendingRequests it will try to shutdown if + // AllModuleRequestsLoaded() returns true. + mScriptLoader->DecreaseLoadingModuleRequestCount(); + moduleRequest->OnFetchComplete(loadContext->mLoadResult); + + if (NS_FAILED(loadContext->mLoadResult)) { + if (moduleRequest->IsDynamicImport()) { + if (request->isInList()) { + moduleRequest->CancelDynamicImport(loadContext->mLoadResult); + mScriptLoader->TryShutdown(); + } + } else if (!moduleRequest->IsTopLevel()) { + moduleRequest->Cancel(); + mScriptLoader->TryShutdown(); + } else { + moduleRequest->LoadFailed(); + } + } + return true; +} + +bool ScriptExecutorRunnable::ProcessClassicScripts( + JSContext* aCx, WorkerPrivate* aWorkerPrivate) { + // There is a possibility that we cleaned up while this task was waiting to + // run. If this has happened, return and exit. + { + MutexAutoLock lock(mScriptLoader->CleanUpLock()); + if (mScriptLoader->CleanedUp()) { + return true; + } + + for (const auto& requestHandle : mLoadedRequests) { + // The request must be valid. + MOZ_ASSERT(!requestHandle->IsEmpty()); + + // Release the request to the worker. From this point on, the Request + // Handle is empty. + RefPtr request = requestHandle->ReleaseRequest(); + mScriptLoader->MaybeMoveToLoadedList(request); + } + } + return mScriptLoader->ProcessPendingRequests(aCx); +} + +bool ScriptExecutorRunnable::WorkerRun(JSContext* aCx, + WorkerPrivate* aWorkerPrivate) { + aWorkerPrivate->AssertIsOnWorkerThread(); + + // We must be on the same worker as we started on. + MOZ_ASSERT( + mScriptLoader->mSyncLoopTarget == mSyncLoopTarget, + "Unexpected SyncLoopTarget. Check if the sync loop was closed early"); + + if (mLoadedRequests.begin()->get()->GetRequest()->IsModuleRequest()) { + return ProcessModuleScript(aCx, aWorkerPrivate); + } + + return ProcessClassicScripts(aCx, aWorkerPrivate); +} + +nsresult ScriptExecutorRunnable::Cancel() { + // We need to check first if cancel is called twice + nsresult rv = MainThreadWorkerSyncRunnable::Cancel(); + NS_ENSURE_SUCCESS(rv, rv); + + if (mScriptLoader->AllScriptsExecuted() && + mScriptLoader->AllModuleRequestsLoaded()) { + mScriptLoader->ShutdownScriptLoader(false, false); + } + return NS_OK; +} + +} /* namespace loader */ + +nsresult ChannelFromScriptURLMainThread( + nsIPrincipal* aPrincipal, Document* aParentDoc, nsILoadGroup* aLoadGroup, + nsIURI* aScriptURL, const WorkerType& aWorkerType, + const RequestCredentials& aCredentials, + const Maybe& aClientInfo, + nsContentPolicyType aMainScriptContentPolicyType, + nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo, + nsIChannel** aChannel) { + AssertIsOnMainThread(); + + nsCOMPtr ios(do_GetIOService()); + + nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(secMan, "This should never be null!"); + + uint32_t secFlags; + nsresult rv; + if (aWorkerType == WorkerType::Module) { + rv = GetModuleSecFlags(true, aPrincipal, WorkerScript, aScriptURL, + aCredentials, secFlags); + } else { + rv = GetClassicSecFlags(true, aScriptURL, aPrincipal, WorkerScript, + secFlags); + } + if (NS_FAILED(rv)) { + return rv; + } + + return ChannelFromScriptURL( + aPrincipal, aParentDoc, nullptr, aLoadGroup, ios, secMan, aScriptURL, + aClientInfo, Maybe(), true, WorkerScript, + aMainScriptContentPolicyType, nsIRequest::LOAD_NORMAL, secFlags, + aCookieJarSettings, aReferrerInfo, aChannel); +} + +nsresult ChannelFromScriptURLWorkerThread( + JSContext* aCx, WorkerPrivate* aParent, const nsAString& aScriptURL, + const WorkerType& aWorkerType, const RequestCredentials& aCredentials, + WorkerLoadInfo& aLoadInfo) { + aParent->AssertIsOnWorkerThread(); + + RefPtr getter = new ChannelGetterRunnable( + aParent, aScriptURL, aWorkerType, aCredentials, aLoadInfo); + + ErrorResult rv; + getter->Dispatch(Canceling, rv); + if (rv.Failed()) { + NS_ERROR("Failed to dispatch!"); + return rv.StealNSResult(); + } + + return getter->GetResult(); +} + +void ReportLoadError(ErrorResult& aRv, nsresult aLoadResult, + const nsAString& aScriptURL) { + MOZ_ASSERT(!aRv.Failed()); + + nsPrintfCString err("Failed to load worker script at \"%s\"", + NS_ConvertUTF16toUTF8(aScriptURL).get()); + + switch (aLoadResult) { + case NS_ERROR_FILE_NOT_FOUND: + case NS_ERROR_NOT_AVAILABLE: + case NS_ERROR_CORRUPTED_CONTENT: + aRv.Throw(NS_ERROR_DOM_NETWORK_ERR); + break; + + case NS_ERROR_MALFORMED_URI: + case NS_ERROR_DOM_SYNTAX_ERR: + aRv.ThrowSyntaxError(err); + break; + + case NS_BINDING_ABORTED: + // Note: we used to pretend like we didn't set an exception for + // NS_BINDING_ABORTED, but then ShutdownScriptLoader did it anyway. The + // other callsite, in WorkerPrivate::Constructor, never passed in + // NS_BINDING_ABORTED. So just throw it directly here. Consumers will + // deal as needed. But note that we do NOT want to use one of the + // Throw*Error() methods on ErrorResult for this case, because that will + // make it impossible for consumers to realize that our error was + // NS_BINDING_ABORTED. + aRv.Throw(aLoadResult); + return; + + case NS_ERROR_DOM_BAD_URI: + // This is actually a security error. + case NS_ERROR_DOM_SECURITY_ERR: + aRv.ThrowSecurityError(err); + break; + + default: + // For lack of anything better, go ahead and throw a NetworkError here. + // We don't want to throw a JS exception, because for toplevel script + // loads that would get squelched. + aRv.ThrowNetworkError(nsPrintfCString( + "Failed to load worker script at %s (nsresult = 0x%" PRIx32 ")", + NS_ConvertUTF16toUTF8(aScriptURL).get(), + static_cast(aLoadResult))); + return; + } +} + +void LoadMainScript(WorkerPrivate* aWorkerPrivate, + UniquePtr aOriginStack, + const nsAString& aScriptURL, + WorkerScriptType aWorkerScriptType, ErrorResult& aRv, + const mozilla::Encoding* aDocumentEncoding) { + nsTArray scriptURLs; + + scriptURLs.AppendElement(aScriptURL); + + LoadAllScripts(aWorkerPrivate, std::move(aOriginStack), scriptURLs, true, + aWorkerScriptType, aRv, aDocumentEncoding); +} + +void Load(WorkerPrivate* aWorkerPrivate, + UniquePtr aOriginStack, + const nsTArray& aScriptURLs, + WorkerScriptType aWorkerScriptType, ErrorResult& aRv) { + const uint32_t urlCount = aScriptURLs.Length(); + + if (!urlCount) { + return; + } + + if (urlCount > MAX_CONCURRENT_SCRIPTS) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + + LoadAllScripts(aWorkerPrivate, std::move(aOriginStack), aScriptURLs, false, + aWorkerScriptType, aRv); +} + +} // namespace mozilla::dom::workerinternals diff --git a/dom/workers/ScriptLoader.h b/dom/workers/ScriptLoader.h new file mode 100644 index 0000000000..37b43fc0c6 --- /dev/null +++ b/dom/workers/ScriptLoader.h @@ -0,0 +1,375 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workers_scriptloader_h__ +#define mozilla_dom_workers_scriptloader_h__ + +#include "js/loader/ScriptLoadRequest.h" +#include "js/loader/ModuleLoadRequest.h" +#include "js/loader/ModuleLoaderBase.h" +#include "mozilla/dom/WorkerBinding.h" +#include "mozilla/dom/WorkerCommon.h" +#include "mozilla/dom/WorkerLoadContext.h" +#include "mozilla/dom/WorkerRef.h" +#include "mozilla/dom/workerinternals/WorkerModuleLoader.h" +#include "mozilla/Maybe.h" +#include "nsIContentPolicy.h" +#include "nsStringFwd.h" +#include "nsTArrayForwardDeclare.h" + +class nsIChannel; +class nsICookieJarSettings; +class nsILoadGroup; +class nsIPrincipal; +class nsIReferrerInfo; +class nsIURI; + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +class ClientInfo; +class Document; +struct WorkerLoadInfo; +class WorkerPrivate; +class SerializedStackHolder; + +enum WorkerScriptType { WorkerScript, DebuggerScript }; + +namespace workerinternals { + +namespace loader { +class ScriptExecutorRunnable; +class ScriptLoaderRunnable; +class CachePromiseHandler; +class CacheLoadHandler; +class CacheCreator; +class NetworkLoadHandler; + +/* + * [DOMDOC] WorkerScriptLoader + * + * The WorkerScriptLoader is the primary class responsible for loading all + * Workers, including: ServiceWorkers, SharedWorkers, RemoteWorkers, and + * dedicated Workers. Our implementation also includes a subtype of dedicated + * workers: ChromeWorker, which exposes information that isn't normally + * accessible on a dedicated worker. See [1] for more information. + * + * Due to constraints around fetching, this class currently delegates the + * "Fetch" portion of its work load to the main thread. Unlike the DOM + * ScriptLoader, the WorkerScriptLoader is not persistent and is not reused for + * subsequent loads. That means for each iteration of loading (for example, + * loading the main script, followed by a load triggered by ImportScripts), we + * recreate this class, and handle the case independently. + * + * The flow of requests across the boundaries looks like this: + * + * +----------------------------+ + * | new WorkerScriptLoader(..) | + * +----------------------------+ + * | + * V + * +-------------------------------------------+ + * | WorkerScriptLoader::DispatchLoadScripts() | + * +-------------------------------------------+ + * | + * V + * +............................+ + * | new ScriptLoaderRunnable() | + * +............................+ + * : + * V + * ##################################################################### + * Enter Main thread + * ##################################################################### + * : + * V + * +.............................+ For each: Is a normal Worker? + * | ScriptLoaderRunnable::Run() |----------------------------------+ + * +.............................+ | + * | V + * | +----------------------------------+ + * | | WorkerScriptLoader::LoadScript() | + * | +----------------------------------+ + * | | + * | For each request: Is a ServiceWorker? | + * | | + * V V + * +==================+ No script in cache? +====================+ + * | CacheLoadHandler |------------------------>| NetworkLoadHandler | + * +==================+ +====================+ + * : : + * : Loaded from Cache : Loaded by Network + * : +..........................................+ : + * +---| ScriptLoaderRunnable::OnStreamComplete() |<----+ + * +..........................................+ + * | + * | A request is ready, is it in post order? + * | + * | call DispatchPendingProcessRequests() + * | This creates ScriptExecutorRunnable + * +..............................+ + * | new ScriptLoaderExecutable() | + * +..............................+ + * : + * V + * ##################################################################### + * Enter worker thread + * ##################################################################### + * : + * V + * +...............................+ All Scripts Executed? + * | ScriptLoaderExecutable::Run() | -------------+ + * +...............................+ : + * : + * : yes. Do execution + * : then shutdown. + * : + * V + * +--------------------------------------------+ + * | WorkerScriptLoader::ShutdownScriptLoader() | + * +--------------------------------------------+ + */ + +class WorkerScriptLoader : public JS::loader::ScriptLoaderInterface, + public nsINamed { + friend class ScriptExecutorRunnable; + friend class ScriptLoaderRunnable; + friend class CachePromiseHandler; + friend class CacheLoadHandler; + friend class CacheCreator; + friend class NetworkLoadHandler; + friend class WorkerModuleLoader; + + RefPtr mWorkerRef; + UniquePtr mOriginStack; + nsString mOriginStackJSON; + nsCOMPtr mSyncLoopTarget; + ScriptLoadRequestList mLoadingRequests; + ScriptLoadRequestList mLoadedRequests; + Maybe mController; + WorkerScriptType mWorkerScriptType; + ErrorResult& mRv; + bool mExecutionAborted = false; + bool mMutedErrorFlag = false; + + // Count of loading module requests. mLoadingRequests doesn't keep track of + // child module requests. + // This member should be accessed on worker thread. + uint32_t mLoadingModuleRequestCount; + + // Worker cancellation related Mutex + // + // Modified on the worker thread. + // It is ok to *read* this without a lock on the worker. + // Main thread must always acquire a lock. + bool mCleanedUp MOZ_GUARDED_BY( + mCleanUpLock); // To specify if the cleanUp() has been done. + + Mutex& CleanUpLock() MOZ_RETURN_CAPABILITY(mCleanUpLock) { + return mCleanUpLock; + } + + bool CleanedUp() const MOZ_REQUIRES(mCleanUpLock) { + mCleanUpLock.AssertCurrentThreadOwns(); + return mCleanedUp; + } + + // Ensure the worker and the main thread won't race to access |mCleanedUp|. + // Should be a MutexSingleWriter, but that causes a lot of issues when you + // expose the lock via Lock(). + Mutex mCleanUpLock; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + WorkerScriptLoader(WorkerPrivate* aWorkerPrivate, + UniquePtr aOriginStack, + nsISerialEventTarget* aSyncLoopTarget, + WorkerScriptType aWorkerScriptType, ErrorResult& aRv); + + bool CreateScriptRequests(const nsTArray& aScriptURLs, + const mozilla::Encoding* aDocumentEncoding, + bool aIsMainScript); + + ScriptLoadRequest* GetMainScript(); + + already_AddRefed CreateScriptLoadRequest( + const nsString& aScriptURL, const mozilla::Encoding* aDocumentEncoding, + bool aIsMainScript); + + bool DispatchLoadScript(ScriptLoadRequest* aRequest); + + bool DispatchLoadScripts(); + + void TryShutdown(); + + WorkerScriptType GetWorkerScriptType() { return mWorkerScriptType; } + + protected: + nsIURI* GetBaseURI() const override; + + nsIURI* GetInitialBaseURI(); + + nsIGlobalObject* GetGlobal(); + + void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest); + + bool StoreCSP(); + + bool ProcessPendingRequests(JSContext* aCx); + + bool AllScriptsExecuted() { + return mLoadingRequests.isEmpty() && mLoadedRequests.isEmpty(); + } + + bool IsDebuggerScript() const { return mWorkerScriptType == DebuggerScript; } + + void SetController(const Maybe& aDescriptor) { + mController = aDescriptor; + } + + Maybe& GetController() { return mController; } + + nsresult LoadScript(ThreadSafeRequestHandle* aRequestHandle); + + void ShutdownScriptLoader(bool aResult, bool aMutedError); + + private: + ~WorkerScriptLoader() = default; + + NS_IMETHOD + GetName(nsACString& aName) override { + aName.AssignLiteral("WorkerScriptLoader"); + return NS_OK; + } + + void InitModuleLoader(); + + nsTArray> GetLoadingList(); + + bool IsDynamicImport(ScriptLoadRequest* aRequest); + + nsContentPolicyType GetContentPolicyType(ScriptLoadRequest* aRequest); + + bool EvaluateScript(JSContext* aCx, ScriptLoadRequest* aRequest); + + nsresult FillCompileOptionsForRequest( + JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions, + JS::MutableHandle aIntroductionScript) override; + + void ReportErrorToConsole(ScriptLoadRequest* aRequest, + nsresult aResult) const override; + + // Only used by import maps, crash if we get here. + void ReportWarningToConsole( + ScriptLoadRequest* aRequest, const char* aMessageName, + const nsTArray& aParams = nsTArray()) const override { + MOZ_CRASH("Import maps have not been implemented for this context"); + } + + void LogExceptionToConsole(JSContext* aCx, WorkerPrivate* aWorkerPrivate); + + bool AllModuleRequestsLoaded() const; + void IncreaseLoadingModuleRequestCount(); + void DecreaseLoadingModuleRequestCount(); +}; + +/* ScriptLoaderRunnable + * + * Responsibilities of this class: + * - the actual dispatch + * - delegating the load back to WorkerScriptLoader + * - handling the collections of scripts being requested + * - handling main thread cancellation + * - dispatching back to the worker thread + */ +class ScriptLoaderRunnable final : public nsIRunnable, public nsINamed { + RefPtr mScriptLoader; + RefPtr mWorkerRef; + nsTArrayView> mLoadingRequests; + Maybe mCancelMainThread; + RefPtr mCacheCreator; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit ScriptLoaderRunnable( + WorkerScriptLoader* aScriptLoader, + nsTArray> aLoadingRequests); + + nsresult OnStreamComplete(ThreadSafeRequestHandle* aRequestHandle, + nsresult aStatus); + + void LoadingFinished(ThreadSafeRequestHandle* aRequestHandle, nsresult aRv); + + void MaybeExecuteFinishedScripts(ThreadSafeRequestHandle* aRequestHandle); + + bool IsCancelled() { return mCancelMainThread.isSome(); } + + nsresult GetCancelResult() { + return (IsCancelled()) ? mCancelMainThread.ref() : NS_OK; + } + + void CancelMainThreadWithBindingAborted(); + + CacheCreator* GetCacheCreator() { return mCacheCreator; }; + + private: + ~ScriptLoaderRunnable() = default; + + void CancelMainThread(nsresult aCancelResult); + + void DispatchProcessPendingRequests(); + + NS_IMETHOD + Run() override; + + NS_IMETHOD + GetName(nsACString& aName) override { + aName.AssignLiteral("ScriptLoaderRunnable"); + return NS_OK; + } +}; + +} // namespace loader + +nsresult ChannelFromScriptURLMainThread( + nsIPrincipal* aPrincipal, Document* aParentDoc, nsILoadGroup* aLoadGroup, + nsIURI* aScriptURL, const WorkerType& aWorkerType, + const RequestCredentials& aCredentials, + const Maybe& aClientInfo, + nsContentPolicyType aContentPolicyType, + nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo, + nsIChannel** aChannel); + +nsresult ChannelFromScriptURLWorkerThread( + JSContext* aCx, WorkerPrivate* aParent, const nsAString& aScriptURL, + const WorkerType& aWorkerType, const RequestCredentials& aCredentials, + WorkerLoadInfo& aLoadInfo); + +void ReportLoadError(ErrorResult& aRv, nsresult aLoadResult, + const nsAString& aScriptURL); + +void LoadMainScript(WorkerPrivate* aWorkerPrivate, + UniquePtr aOriginStack, + const nsAString& aScriptURL, + WorkerScriptType aWorkerScriptType, ErrorResult& aRv, + const mozilla::Encoding* aDocumentEncoding); + +void Load(WorkerPrivate* aWorkerPrivate, + UniquePtr aOriginStack, + const nsTArray& aScriptURLs, + WorkerScriptType aWorkerScriptType, ErrorResult& aRv); + +} // namespace workerinternals + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_workers_scriptloader_h__ */ diff --git a/dom/workers/Worker.cpp b/dom/workers/Worker.cpp new file mode 100644 index 0000000000..3452859c4c --- /dev/null +++ b/dom/workers/Worker.cpp @@ -0,0 +1,211 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Worker.h" + +#include "MessageEventRunnable.h" +#include "mozilla/dom/WorkerBinding.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/TimelineConsumers.h" +#include "mozilla/Unused.h" +#include "mozilla/WorkerTimelineMarker.h" +#include "nsContentUtils.h" +#include "nsGlobalWindowOuter.h" +#include "WorkerPrivate.h" + +#ifdef XP_WIN +# undef PostMessage +#endif + +namespace mozilla::dom { + +/* static */ +already_AddRefed Worker::Constructor(const GlobalObject& aGlobal, + const nsAString& aScriptURL, + const WorkerOptions& aOptions, + ErrorResult& aRv) { + JSContext* cx = aGlobal.Context(); + + nsCOMPtr globalObject = + do_QueryInterface(aGlobal.GetAsSupports()); + + if (globalObject->AsInnerWindow() && + !globalObject->AsInnerWindow()->IsCurrentInnerWindow()) { + aRv.ThrowInvalidStateError( + "Cannot create worker for a going to be discarded document"); + return nullptr; + } + + RefPtr workerPrivate = WorkerPrivate::Constructor( + cx, aScriptURL, false /* aIsChromeWorker */, WorkerKindDedicated, + aOptions.mCredentials, aOptions.mType, aOptions.mName, VoidCString(), + nullptr /*aLoadInfo */, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + RefPtr worker = new Worker(globalObject, workerPrivate.forget()); + return worker.forget(); +} + +Worker::Worker(nsIGlobalObject* aGlobalObject, + already_AddRefed aWorkerPrivate) + : DOMEventTargetHelper(aGlobalObject), + mWorkerPrivate(std::move(aWorkerPrivate)) { + MOZ_ASSERT(mWorkerPrivate); + mWorkerPrivate->SetParentEventTargetRef(this); +} + +Worker::~Worker() { Terminate(); } + +JSObject* Worker::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + JS::Rooted wrapper(aCx, + Worker_Binding::Wrap(aCx, this, aGivenProto)); + if (wrapper) { + // Most DOM objects don't assume they have a reflector. If they don't have + // one and need one, they create it. But in workers code, we assume that the + // reflector is always present. In order to guarantee that it's always + // present, we have to preserve it. Otherwise the GC will happily collect it + // as needed. + MOZ_ALWAYS_TRUE(TryPreserveWrapper(wrapper)); + } + + return wrapper; +} + +void Worker::PostMessage(JSContext* aCx, JS::Handle aMessage, + const Sequence& aTransferable, + ErrorResult& aRv) { + NS_ASSERT_OWNINGTHREAD(Worker); + + if (!mWorkerPrivate || mWorkerPrivate->ParentStatusProtected() > Running) { + return; + } + RefPtr workerPrivate = mWorkerPrivate; + Unused << workerPrivate; + + JS::Rooted transferable(aCx, JS::UndefinedValue()); + + aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable, + &transferable); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + NS_ConvertUTF16toUTF8 nameOrScriptURL( + mWorkerPrivate->WorkerName().IsEmpty() + ? Substring( + mWorkerPrivate->ScriptURL(), 0, + std::min(size_t(1024), mWorkerPrivate->ScriptURL().Length())) + : Substring( + mWorkerPrivate->WorkerName(), 0, + std::min(size_t(1024), mWorkerPrivate->WorkerName().Length()))); + AUTO_PROFILER_MARKER_TEXT("Worker.postMessage", DOM, {}, nameOrScriptURL); + uint32_t flags = uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS); + if (mWorkerPrivate->IsChromeWorker()) { + flags |= uint32_t(js::ProfilingStackFrame::Flags::NONSENSITIVE); + } + mozilla::AutoProfilerLabel PROFILER_RAII( + "Worker.postMessage", nameOrScriptURL.get(), + JS::ProfilingCategoryPair::DOM, flags); + + RefPtr runnable = new MessageEventRunnable( + mWorkerPrivate, WorkerRunnable::WorkerThreadModifyBusyCount); + + UniquePtr start; + UniquePtr end; + bool isTimelineRecording = !TimelineConsumers::IsEmpty(); + + if (isTimelineRecording) { + start = MakeUnique( + NS_IsMainThread() + ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread + : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread, + MarkerTracingType::START); + } + + JS::CloneDataPolicy clonePolicy; + // DedicatedWorkers are always part of the same agent cluster. + clonePolicy.allowIntraClusterClonableSharedObjects(); + + if (NS_IsMainThread()) { + nsGlobalWindowInner* win = nsContentUtils::IncumbentInnerWindow(); + if (win && win->IsSharedMemoryAllowed()) { + clonePolicy.allowSharedMemoryObjects(); + } + } else { + WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); + if (worker && worker->IsSharedMemoryAllowed()) { + clonePolicy.allowSharedMemoryObjects(); + } + } + + runnable->Write(aCx, aMessage, transferable, clonePolicy, aRv); + + if (!mWorkerPrivate || mWorkerPrivate->ParentStatusProtected() > Running) { + return; + } + + if (isTimelineRecording) { + end = MakeUnique( + NS_IsMainThread() + ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread + : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread, + MarkerTracingType::END); + TimelineConsumers::AddMarkerForAllObservedDocShells(start); + TimelineConsumers::AddMarkerForAllObservedDocShells(end); + } + + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + // The worker could have closed between the time we entered this function and + // checked ParentStatusProtected and now, which could cause the dispatch to + // fail. + Unused << NS_WARN_IF(!runnable->Dispatch()); +} + +void Worker::PostMessage(JSContext* aCx, JS::Handle aMessage, + const StructuredSerializeOptions& aOptions, + ErrorResult& aRv) { + PostMessage(aCx, aMessage, aOptions.mTransfer, aRv); +} + +void Worker::Terminate() { + NS_ASSERT_OWNINGTHREAD(Worker); + + if (mWorkerPrivate) { + mWorkerPrivate->Cancel(); + mWorkerPrivate = nullptr; + } +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(Worker) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Worker, DOMEventTargetHelper) + if (tmp->mWorkerPrivate) { + tmp->mWorkerPrivate->Traverse(cb); + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Worker, DOMEventTargetHelper) + tmp->Terminate(); + NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Worker, DOMEventTargetHelper) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Worker) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(Worker, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(Worker, DOMEventTargetHelper) + +} // namespace mozilla::dom diff --git a/dom/workers/Worker.h b/dom/workers/Worker.h new file mode 100644 index 0000000000..b1b0686688 --- /dev/null +++ b/dom/workers/Worker.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_Worker_h +#define mozilla_dom_Worker_h + +#include "mozilla/Attributes.h" +#include "mozilla/dom/DebuggerNotificationBinding.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/RefPtr.h" +#include "mozilla/WeakPtr.h" + +#ifdef XP_WIN +# undef PostMessage +#endif + +namespace mozilla::dom { + +struct StructuredSerializeOptions; +struct WorkerOptions; +class WorkerPrivate; + +class Worker : public DOMEventTargetHelper, public SupportsWeakPtr { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(Worker, + DOMEventTargetHelper) + static already_AddRefed Constructor(const GlobalObject& aGlobal, + const nsAString& aScriptURL, + const WorkerOptions& aOptions, + ErrorResult& aRv); + + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + Maybe GetDebuggerNotificationType() + const override { + return Some(EventCallbackDebuggerNotificationType::Worker); + } + + void PostMessage(JSContext* aCx, JS::Handle aMessage, + const Sequence& aTransferable, ErrorResult& aRv); + + void PostMessage(JSContext* aCx, JS::Handle aMessage, + const StructuredSerializeOptions& aOptions, + ErrorResult& aRv); + + void Terminate(); + + IMPL_EVENT_HANDLER(error) + IMPL_EVENT_HANDLER(message) + IMPL_EVENT_HANDLER(messageerror) + + protected: + Worker(nsIGlobalObject* aGlobalObject, + already_AddRefed aWorkerPrivate); + ~Worker(); + + RefPtr mWorkerPrivate; +}; + +} // namespace mozilla::dom + +#endif /* mozilla_dom_Worker_h */ diff --git a/dom/workers/WorkerCSPEventListener.cpp b/dom/workers/WorkerCSPEventListener.cpp new file mode 100644 index 0000000000..bcfe219cc7 --- /dev/null +++ b/dom/workers/WorkerCSPEventListener.cpp @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WorkerCSPEventListener.h" +#include "WorkerRef.h" +#include "WorkerRunnable.h" +#include "WorkerScope.h" +#include "mozilla/dom/SecurityPolicyViolationEvent.h" +#include "mozilla/dom/SecurityPolicyViolationEventBinding.h" +#include "mozilla/dom/WorkerRunnable.h" + +using namespace mozilla::dom; + +namespace { + +class WorkerCSPEventRunnable final : public MainThreadWorkerRunnable { + public: + WorkerCSPEventRunnable(WorkerPrivate* aWorkerPrivate, const nsAString& aJSON) + : MainThreadWorkerRunnable(aWorkerPrivate), mJSON(aJSON) {} + + private: + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { + SecurityPolicyViolationEventInit violationEventInit; + if (NS_WARN_IF(!violationEventInit.Init(mJSON))) { + return true; + } + + RefPtr event = + mozilla::dom::SecurityPolicyViolationEvent::Constructor( + aWorkerPrivate->GlobalScope(), u"securitypolicyviolation"_ns, + violationEventInit); + event->SetTrusted(true); + + aWorkerPrivate->GlobalScope()->DispatchEvent(*event); + return true; + } + + const nsString mJSON; +}; + +} // namespace + +NS_IMPL_ISUPPORTS(WorkerCSPEventListener, nsICSPEventListener) + +/* static */ +already_AddRefed WorkerCSPEventListener::Create( + WorkerPrivate* aWorkerPrivate) { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + RefPtr listener = new WorkerCSPEventListener(); + + MutexAutoLock lock(listener->mMutex); + listener->mWorkerRef = WeakWorkerRef::Create(aWorkerPrivate, [listener]() { + MutexAutoLock lock(listener->mMutex); + listener->mWorkerRef = nullptr; + }); + + if (NS_WARN_IF(!listener->mWorkerRef)) { + return nullptr; + } + + return listener.forget(); +} + +WorkerCSPEventListener::WorkerCSPEventListener() + : mMutex("WorkerCSPEventListener::mMutex") {} + +NS_IMETHODIMP +WorkerCSPEventListener::OnCSPViolationEvent(const nsAString& aJSON) { + MutexAutoLock lock(mMutex); + if (!mWorkerRef) { + return NS_OK; + } + + WorkerPrivate* workerPrivate = mWorkerRef->GetUnsafePrivate(); + MOZ_ASSERT(workerPrivate); + + if (NS_IsMainThread()) { + RefPtr runnable = + new WorkerCSPEventRunnable(workerPrivate, aJSON); + runnable->Dispatch(); + + return NS_OK; + } + + SecurityPolicyViolationEventInit violationEventInit; + if (NS_WARN_IF(!violationEventInit.Init(aJSON))) { + return NS_ERROR_UNEXPECTED; + } + + RefPtr event = + mozilla::dom::SecurityPolicyViolationEvent::Constructor( + workerPrivate->GlobalScope(), u"securitypolicyviolation"_ns, + violationEventInit); + event->SetTrusted(true); + + workerPrivate->GlobalScope()->DispatchEvent(*event); + + return NS_OK; +} diff --git a/dom/workers/WorkerCSPEventListener.h b/dom/workers/WorkerCSPEventListener.h new file mode 100644 index 0000000000..1265f0ed17 --- /dev/null +++ b/dom/workers/WorkerCSPEventListener.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_WorkerCSPEventListener_h +#define mozilla_dom_WorkerCSPEventListener_h + +#include "mozilla/dom/WorkerRef.h" +#include "mozilla/Mutex.h" +#include "nsIContentSecurityPolicy.h" + +namespace mozilla::dom { + +class WeakWorkerRef; +class WorkerRef; +class WorkerPrivate; + +class WorkerCSPEventListener final : public nsICSPEventListener { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICSPEVENTLISTENER + + static already_AddRefed Create( + WorkerPrivate* aWorkerPrivate); + + private: + WorkerCSPEventListener(); + ~WorkerCSPEventListener() = default; + + Mutex mMutex; + + // Protected by mutex. + RefPtr mWorkerRef MOZ_GUARDED_BY(mMutex); +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_WorkerCSPEventListener_h diff --git a/dom/workers/WorkerChannelInfo.cpp b/dom/workers/WorkerChannelInfo.cpp new file mode 100644 index 0000000000..cea6acf1ef --- /dev/null +++ b/dom/workers/WorkerChannelInfo.cpp @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WorkerChannelInfo.h" +#include "mozilla/dom/BrowsingContext.h" + +namespace mozilla::dom { + +// WorkerChannelLoadInfo + +NS_IMPL_ISUPPORTS(WorkerChannelLoadInfo, nsIWorkerChannelLoadInfo) + +NS_IMETHODIMP +WorkerChannelLoadInfo::GetWorkerAssociatedBrowsingContextID(uint64_t* aResult) { + *aResult = mWorkerAssociatedBrowsingContextID; + return NS_OK; +} + +NS_IMETHODIMP +WorkerChannelLoadInfo::SetWorkerAssociatedBrowsingContextID(uint64_t aID) { + mWorkerAssociatedBrowsingContextID = aID; + return NS_OK; +} + +NS_IMETHODIMP +WorkerChannelLoadInfo::GetWorkerAssociatedBrowsingContext( + dom::BrowsingContext** aResult) { + *aResult = BrowsingContext::Get(mWorkerAssociatedBrowsingContextID).take(); + return NS_OK; +} + +// WorkerChannelInfo + +NS_IMPL_ISUPPORTS(WorkerChannelInfo, nsIWorkerChannelInfo) + +WorkerChannelInfo::WorkerChannelInfo(uint64_t aChannelID, + uint64_t aBrowsingContextID) + : mChannelID(aChannelID) { + mLoadInfo = new WorkerChannelLoadInfo(); + mLoadInfo->SetWorkerAssociatedBrowsingContextID(aBrowsingContextID); +} + +NS_IMETHODIMP +WorkerChannelInfo::SetLoadInfo(nsIWorkerChannelLoadInfo* aLoadInfo) { + MOZ_ASSERT(aLoadInfo); + mLoadInfo = aLoadInfo; + return NS_OK; +} + +NS_IMETHODIMP +WorkerChannelInfo::GetLoadInfo(nsIWorkerChannelLoadInfo** aLoadInfo) { + *aLoadInfo = do_AddRef(mLoadInfo).take(); + return NS_OK; +} + +NS_IMETHODIMP +WorkerChannelInfo::GetChannelId(uint64_t* aChannelID) { + NS_ENSURE_ARG_POINTER(aChannelID); + *aChannelID = mChannelID; + return NS_OK; +} + +} // end of namespace mozilla::dom diff --git a/dom/workers/WorkerChannelInfo.h b/dom/workers/WorkerChannelInfo.h new file mode 100644 index 0000000000..66b28bf685 --- /dev/null +++ b/dom/workers/WorkerChannelInfo.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_WorkerChannel_h +#define mozilla_dom_WorkerChannel_h + +#include "nsIWorkerChannelInfo.h" +#include "nsILoadInfo.h" +#include "nsIChannel.h" +#include "nsTArray.h" +#include "nsIURI.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/ServiceWorkerDescriptor.h" +#include "mozilla/OriginAttributes.h" + +namespace mozilla::dom { + +class WorkerChannelLoadInfo final : public nsIWorkerChannelLoadInfo { + public: + NS_DECL_THREADSAFE_ISUPPORTS; + NS_DECL_NSIWORKERCHANNELLOADINFO; + + private: + ~WorkerChannelLoadInfo() = default; + + uint64_t mWorkerAssociatedBrowsingContextID; +}; + +class WorkerChannelInfo final : public nsIWorkerChannelInfo { + public: + NS_DECL_THREADSAFE_ISUPPORTS; + NS_DECL_NSIWORKERCHANNELINFO; + + WorkerChannelInfo(uint64_t aChannelID, + uint64_t aWorkerAssociatedBrowsingContextID); + WorkerChannelInfo() = delete; + + private: + ~WorkerChannelInfo() = default; + + nsCOMPtr mLoadInfo; + uint64_t mChannelID; +}; + +} // end of namespace mozilla::dom + +#endif diff --git a/dom/workers/WorkerCommon.h b/dom/workers/WorkerCommon.h new file mode 100644 index 0000000000..d10dabb5c5 --- /dev/null +++ b/dom/workers/WorkerCommon.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workers_WorkerCommon_h +#define mozilla_dom_workers_WorkerCommon_h + +#include "js/TypeDecls.h" + +class nsPIDOMWindowInner; + +namespace mozilla::dom { + +class WorkerPrivate; + +// All of these are implemented in RuntimeService.cpp + +WorkerPrivate* GetWorkerPrivateFromContext(JSContext* aCx); + +WorkerPrivate* GetCurrentThreadWorkerPrivate(); + +bool IsCurrentThreadRunningWorker(); + +bool IsCurrentThreadRunningChromeWorker(); + +JSContext* GetCurrentWorkerThreadJSContext(); + +JSObject* GetCurrentThreadWorkerGlobal(); + +JSObject* GetCurrentThreadWorkerDebuggerGlobal(); + +void CancelWorkersForWindow(const nsPIDOMWindowInner& aWindow); + +void FreezeWorkersForWindow(const nsPIDOMWindowInner& aWindow); + +void ThawWorkersForWindow(const nsPIDOMWindowInner& aWindow); + +void SuspendWorkersForWindow(const nsPIDOMWindowInner& aWindow); + +void ResumeWorkersForWindow(const nsPIDOMWindowInner& aWindow); + +void PropagateStorageAccessPermissionGrantedToWorkers( + const nsPIDOMWindowInner& aWindow); + +// All of these are implemented in WorkerScope.cpp + +bool IsWorkerGlobal(JSObject* global); + +bool IsWorkerDebuggerGlobal(JSObject* global); + +bool IsWorkerDebuggerSandbox(JSObject* object); + +} // namespace mozilla::dom + +#endif // mozilla_dom_workers_WorkerCommon_h diff --git a/dom/workers/WorkerDebugger.cpp b/dom/workers/WorkerDebugger.cpp new file mode 100644 index 0000000000..6068c627f1 --- /dev/null +++ b/dom/workers/WorkerDebugger.cpp @@ -0,0 +1,604 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/MessageEvent.h" +#include "mozilla/dom/MessageEventBinding.h" +#include "mozilla/dom/RemoteWorkerChild.h" +#include "mozilla/dom/WindowContext.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/Encoding.h" +#include "mozilla/PerformanceUtils.h" +#include "nsProxyRelease.h" +#include "nsQueryObject.h" +#include "nsThreadUtils.h" +#include "ScriptLoader.h" +#include "WorkerCommon.h" +#include "WorkerError.h" +#include "WorkerRunnable.h" +#include "WorkerDebugger.h" + +#if defined(XP_WIN) +# include // for GetCurrentProcessId() +#else +# include // for getpid() +#endif // defined(XP_WIN) + +namespace mozilla::dom { + +namespace { + +class DebuggerMessageEventRunnable : public WorkerDebuggerRunnable { + nsString mMessage; + + public: + DebuggerMessageEventRunnable(WorkerPrivate* aWorkerPrivate, + const nsAString& aMessage) + : WorkerDebuggerRunnable(aWorkerPrivate), mMessage(aMessage) {} + + private: + virtual bool WorkerRun(JSContext* aCx, + WorkerPrivate* aWorkerPrivate) override { + WorkerDebuggerGlobalScope* globalScope = + aWorkerPrivate->DebuggerGlobalScope(); + MOZ_ASSERT(globalScope); + + JS::Rooted message( + aCx, JS_NewUCStringCopyN(aCx, mMessage.get(), mMessage.Length())); + if (!message) { + return false; + } + JS::Rooted data(aCx, JS::StringValue(message)); + + RefPtr event = + new MessageEvent(globalScope, nullptr, nullptr); + event->InitMessageEvent(nullptr, u"message"_ns, CanBubble::eNo, + Cancelable::eYes, data, u""_ns, u""_ns, nullptr, + Sequence>()); + event->SetTrusted(true); + + globalScope->DispatchEvent(*event); + return true; + } +}; + +class CompileDebuggerScriptRunnable final : public WorkerDebuggerRunnable { + nsString mScriptURL; + const mozilla::Encoding* mDocumentEncoding; + + public: + CompileDebuggerScriptRunnable(WorkerPrivate* aWorkerPrivate, + const nsAString& aScriptURL, + const mozilla::Encoding* aDocumentEncoding) + : WorkerDebuggerRunnable(aWorkerPrivate), + mScriptURL(aScriptURL), + mDocumentEncoding(aDocumentEncoding) {} + + private: + virtual bool WorkerRun(JSContext* aCx, + WorkerPrivate* aWorkerPrivate) override { + aWorkerPrivate->AssertIsOnWorkerThread(); + + WorkerDebuggerGlobalScope* globalScope = + aWorkerPrivate->CreateDebuggerGlobalScope(aCx); + if (!globalScope) { + NS_WARNING("Failed to make global!"); + return false; + } + + if (NS_WARN_IF(!aWorkerPrivate->EnsureCSPEventListener())) { + return false; + } + + JS::Rooted global(aCx, globalScope->GetWrapper()); + + ErrorResult rv; + JSAutoRealm ar(aCx, global); + workerinternals::LoadMainScript(aWorkerPrivate, nullptr, mScriptURL, + DebuggerScript, rv, mDocumentEncoding); + rv.WouldReportJSException(); + // Explicitly ignore NS_BINDING_ABORTED on rv. Or more precisely, still + // return false and don't SetWorkerScriptExecutedSuccessfully() in that + // case, but don't throw anything on aCx. The idea is to not dispatch error + // events if our load is canceled with that error code. + if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) { + rv.SuppressException(); + return false; + } + // Make sure to propagate exceptions from rv onto aCx, so that they will get + // reported after we return. We do this for all failures on rv, because now + // we're using rv to track all the state we care about. + if (rv.MaybeSetPendingException(aCx)) { + return false; + } + + return true; + } +}; + +} // namespace + +class WorkerDebugger::PostDebuggerMessageRunnable final : public Runnable { + WorkerDebugger* mDebugger; + nsString mMessage; + + public: + PostDebuggerMessageRunnable(WorkerDebugger* aDebugger, + const nsAString& aMessage) + : mozilla::Runnable("PostDebuggerMessageRunnable"), + mDebugger(aDebugger), + mMessage(aMessage) {} + + private: + ~PostDebuggerMessageRunnable() = default; + + NS_IMETHOD + Run() override { + mDebugger->PostMessageToDebuggerOnMainThread(mMessage); + + return NS_OK; + } +}; + +class WorkerDebugger::ReportDebuggerErrorRunnable final : public Runnable { + WorkerDebugger* mDebugger; + nsString mFilename; + uint32_t mLineno; + nsString mMessage; + + public: + ReportDebuggerErrorRunnable(WorkerDebugger* aDebugger, + const nsAString& aFilename, uint32_t aLineno, + const nsAString& aMessage) + : Runnable("ReportDebuggerErrorRunnable"), + mDebugger(aDebugger), + mFilename(aFilename), + mLineno(aLineno), + mMessage(aMessage) {} + + private: + ~ReportDebuggerErrorRunnable() = default; + + NS_IMETHOD + Run() override { + mDebugger->ReportErrorToDebuggerOnMainThread(mFilename, mLineno, mMessage); + + return NS_OK; + } +}; + +WorkerDebugger::WorkerDebugger(WorkerPrivate* aWorkerPrivate) + : mWorkerPrivate(aWorkerPrivate), mIsInitialized(false) { + AssertIsOnMainThread(); +} + +WorkerDebugger::~WorkerDebugger() { + MOZ_ASSERT(!mWorkerPrivate); + + if (!NS_IsMainThread()) { + for (auto& listener : mListeners) { + NS_ReleaseOnMainThread("WorkerDebugger::mListeners", listener.forget()); + } + } +} + +NS_IMPL_ISUPPORTS(WorkerDebugger, nsIWorkerDebugger) + +NS_IMETHODIMP +WorkerDebugger::GetIsClosed(bool* aResult) { + AssertIsOnMainThread(); + + *aResult = !mWorkerPrivate; + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebugger::GetIsChrome(bool* aResult) { + AssertIsOnMainThread(); + + if (!mWorkerPrivate) { + return NS_ERROR_UNEXPECTED; + } + + *aResult = mWorkerPrivate->IsChromeWorker(); + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebugger::GetIsInitialized(bool* aResult) { + AssertIsOnMainThread(); + + if (!mWorkerPrivate) { + return NS_ERROR_UNEXPECTED; + } + + *aResult = mIsInitialized; + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebugger::GetParent(nsIWorkerDebugger** aResult) { + AssertIsOnMainThread(); + + if (!mWorkerPrivate) { + return NS_ERROR_UNEXPECTED; + } + + WorkerPrivate* parent = mWorkerPrivate->GetParent(); + if (!parent) { + *aResult = nullptr; + return NS_OK; + } + + MOZ_ASSERT(mWorkerPrivate->IsDedicatedWorker()); + + nsCOMPtr debugger = parent->Debugger(); + debugger.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebugger::GetType(uint32_t* aResult) { + AssertIsOnMainThread(); + + if (!mWorkerPrivate) { + return NS_ERROR_UNEXPECTED; + } + + *aResult = mWorkerPrivate->Kind(); + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebugger::GetUrl(nsAString& aResult) { + AssertIsOnMainThread(); + + if (!mWorkerPrivate) { + return NS_ERROR_UNEXPECTED; + } + + aResult = mWorkerPrivate->ScriptURL(); + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebugger::GetWindow(mozIDOMWindow** aResult) { + AssertIsOnMainThread(); + + if (!mWorkerPrivate) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr window = DedicatedWorkerWindow(); + window.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebugger::GetWindowIDs(nsTArray& aResult) { + AssertIsOnMainThread(); + + if (!mWorkerPrivate) { + return NS_ERROR_UNEXPECTED; + } + + if (mWorkerPrivate->IsDedicatedWorker()) { + if (const auto window = DedicatedWorkerWindow()) { + aResult.AppendElement(window->WindowID()); + } + } else if (mWorkerPrivate->IsSharedWorker()) { + const RemoteWorkerChild* const controller = + mWorkerPrivate->GetRemoteWorkerController(); + MOZ_ASSERT(controller); + aResult = controller->WindowIDs().Clone(); + } + + return NS_OK; +} + +nsCOMPtr WorkerDebugger::DedicatedWorkerWindow() { + MOZ_ASSERT(mWorkerPrivate); + + WorkerPrivate* worker = mWorkerPrivate; + while (worker->GetParent()) { + worker = worker->GetParent(); + } + + if (!worker->IsDedicatedWorker()) { + return nullptr; + } + + return worker->GetWindow(); +} + +NS_IMETHODIMP +WorkerDebugger::GetPrincipal(nsIPrincipal** aResult) { + AssertIsOnMainThread(); + MOZ_ASSERT(aResult); + + if (!mWorkerPrivate) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr prin = mWorkerPrivate->GetPrincipal(); + prin.forget(aResult); + + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebugger::GetServiceWorkerID(uint32_t* aResult) { + AssertIsOnMainThread(); + MOZ_ASSERT(aResult); + + if (!mWorkerPrivate || !mWorkerPrivate->IsServiceWorker()) { + return NS_ERROR_UNEXPECTED; + } + + *aResult = mWorkerPrivate->ServiceWorkerID(); + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebugger::GetId(nsAString& aResult) { + AssertIsOnMainThread(); + + if (!mWorkerPrivate) { + return NS_ERROR_UNEXPECTED; + } + + aResult = mWorkerPrivate->Id(); + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebugger::Initialize(const nsAString& aURL) { + AssertIsOnMainThread(); + + if (!mWorkerPrivate) { + return NS_ERROR_UNEXPECTED; + } + + // This should be non-null for dedicated workers and null for Shared and + // Service workers. All Encoding values are static and will live as long + // as the process and the convention is to therefore use raw pointers. + const mozilla::Encoding* aDocumentEncoding = + NS_IsMainThread() && !mWorkerPrivate->GetParent() && + mWorkerPrivate->GetDocument() + ? mWorkerPrivate->GetDocument()->GetDocumentCharacterSet().get() + : nullptr; + + if (!mIsInitialized) { + RefPtr runnable = + new CompileDebuggerScriptRunnable(mWorkerPrivate, aURL, + aDocumentEncoding); + if (!runnable->Dispatch()) { + return NS_ERROR_FAILURE; + } + + mIsInitialized = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebugger::PostMessageMoz(const nsAString& aMessage) { + AssertIsOnMainThread(); + + if (!mWorkerPrivate || !mIsInitialized) { + return NS_ERROR_UNEXPECTED; + } + + RefPtr runnable = + new DebuggerMessageEventRunnable(mWorkerPrivate, aMessage); + if (!runnable->Dispatch()) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebugger::AddListener(nsIWorkerDebuggerListener* aListener) { + AssertIsOnMainThread(); + + if (mListeners.Contains(aListener)) { + return NS_ERROR_INVALID_ARG; + } + + mListeners.AppendElement(aListener); + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebugger::RemoveListener(nsIWorkerDebuggerListener* aListener) { + AssertIsOnMainThread(); + + if (!mListeners.Contains(aListener)) { + return NS_ERROR_INVALID_ARG; + } + + mListeners.RemoveElement(aListener); + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebugger::SetDebuggerReady(bool aReady) { + return mWorkerPrivate->SetIsDebuggerReady(aReady); +} + +void WorkerDebugger::Close() { + MOZ_ASSERT(mWorkerPrivate); + mWorkerPrivate = nullptr; + + for (const auto& listener : mListeners.Clone()) { + listener->OnClose(); + } +} + +void WorkerDebugger::PostMessageToDebugger(const nsAString& aMessage) { + mWorkerPrivate->AssertIsOnWorkerThread(); + + RefPtr runnable = + new PostDebuggerMessageRunnable(this, aMessage); + if (NS_FAILED(mWorkerPrivate->DispatchToMainThreadForMessaging( + runnable.forget()))) { + NS_WARNING("Failed to post message to debugger on main thread!"); + } +} + +void WorkerDebugger::PostMessageToDebuggerOnMainThread( + const nsAString& aMessage) { + AssertIsOnMainThread(); + + for (const auto& listener : mListeners.Clone()) { + listener->OnMessage(aMessage); + } +} + +void WorkerDebugger::ReportErrorToDebugger(const nsAString& aFilename, + uint32_t aLineno, + const nsAString& aMessage) { + mWorkerPrivate->AssertIsOnWorkerThread(); + + RefPtr runnable = + new ReportDebuggerErrorRunnable(this, aFilename, aLineno, aMessage); + if (NS_FAILED(mWorkerPrivate->DispatchToMainThreadForMessaging( + runnable.forget()))) { + NS_WARNING("Failed to report error to debugger on main thread!"); + } +} + +void WorkerDebugger::ReportErrorToDebuggerOnMainThread( + const nsAString& aFilename, uint32_t aLineno, const nsAString& aMessage) { + AssertIsOnMainThread(); + + for (const auto& listener : mListeners.Clone()) { + listener->OnError(aFilename, aLineno, aMessage); + } + + AutoJSAPI jsapi; + // We're only using this context to deserialize a stack to report to the + // console, so the scope we use doesn't matter. Stack frame filtering happens + // based on the principal encoded into the frame and the caller compartment, + // not the compartment of the frame object, and the console reporting code + // will not be using our context, and therefore will not care what compartment + // it has entered. + DebugOnly ok = jsapi.Init(xpc::PrivilegedJunkScope()); + MOZ_ASSERT(ok, "PrivilegedJunkScope should exist"); + + WorkerErrorReport report; + report.mMessage = aMessage; + report.mFilename = aFilename; + WorkerErrorReport::LogErrorToConsole(jsapi.cx(), report, 0); +} + +RefPtr WorkerDebugger::ReportPerformanceInfo() { + AssertIsOnMainThread(); + RefPtr self = this; + +#if defined(XP_WIN) + uint32_t pid = GetCurrentProcessId(); +#else + uint32_t pid = getpid(); +#endif + bool isTopLevel = false; + uint64_t windowID = mWorkerPrivate->WindowID(); + + // Walk up to our containing page and its window + WorkerPrivate* wp = mWorkerPrivate; + while (wp->GetParent()) { + wp = wp->GetParent(); + } + nsPIDOMWindowInner* win = wp->GetWindow(); + if (win) { + BrowsingContext* context = win->GetBrowsingContext(); + if (context) { + RefPtr top = context->Top(); + if (top && top->GetCurrentWindowContext()) { + windowID = top->GetCurrentWindowContext()->OuterWindowId(); + isTopLevel = context->IsTop(); + } + } + } + + // getting the worker URL + RefPtr scriptURI = mWorkerPrivate->GetResolvedScriptURI(); + if (NS_WARN_IF(!scriptURI)) { + // This can happen at shutdown, let's stop here. + return PerformanceInfoPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + nsCString url = scriptURI->GetSpecOrDefault(); + + const auto& perf = mWorkerPrivate->PerformanceCounterRef(); + uint64_t perfId = perf.GetID(); + uint16_t count = perf.GetTotalDispatchCount(); + uint64_t duration = perf.GetExecutionDuration(); + + // Workers only produce metrics for a single category - + // DispatchCategory::Worker. We still return an array of CategoryDispatch so + // the PerformanceInfo struct is common to all performance counters throughout + // Firefox. + FallibleTArray items; + + if (mWorkerPrivate->GetParent()) { + // We cannot properly measure the memory usage of nested workers + // (https://phabricator.services.mozilla.com/D146673#4948924) + return PerformanceInfoPromise::CreateAndResolve( + PerformanceInfo(url, pid, windowID, duration, perfId, true, isTopLevel, + PerformanceMemoryInfo(), items), + __func__); + } + + CategoryDispatch item = + CategoryDispatch(DispatchCategory::Worker.GetValue(), count); + if (!items.AppendElement(item, fallible)) { + NS_ERROR("Could not complete the operation"); + } + + // Switch to the worker thread to gather the JS Runtime's memory usage. + RefPtr memoryUsagePromise = + mWorkerPrivate->GetJSMemoryUsage(); + if (!memoryUsagePromise) { + // The worker is shutting down, so we don't count the JavaScript memory. + return PerformanceInfoPromise::CreateAndResolve( + PerformanceInfo(url, pid, windowID, duration, perfId, true, isTopLevel, + PerformanceMemoryInfo(), items), + __func__); + } + + // We need to keep a ref on workerPrivate, passed to the promise, + // to make sure it's still alive when collecting the info, and we can't do + // this in WorkerPrivate::GetJSMemoryUsage() since that could cause it to be + // freed on the worker thread. + // Because CheckedUnsafePtr does not convert directly to RefPtr, we have an + // extra step here. + WorkerPrivate* workerPtr = mWorkerPrivate; + RefPtr workerRef = workerPtr; + + // This captures an unused reference to memoryUsagePromise because the worker + // can be released while this promise is still alive. + return memoryUsagePromise->Then( + GetCurrentSerialEventTarget(), __func__, + [url, pid, perfId, windowID, duration, isTopLevel, + items = std::move(items), _w = std::move(workerRef), + memoryUsagePromise](uint64_t jsMem) { + PerformanceMemoryInfo memInfo; + memInfo.jsMemUsage() = jsMem; + return PerformanceInfoPromise::CreateAndResolve( + PerformanceInfo(url, pid, windowID, duration, perfId, true, + isTopLevel, memInfo, items), + __func__); + }, + []() { + return PerformanceInfoPromise::CreateAndReject(NS_ERROR_FAILURE, + __func__); + }); +} + +} // namespace mozilla::dom diff --git a/dom/workers/WorkerDebugger.h b/dom/workers/WorkerDebugger.h new file mode 100644 index 0000000000..73efe402c8 --- /dev/null +++ b/dom/workers/WorkerDebugger.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workers_WorkerDebugger_h +#define mozilla_dom_workers_WorkerDebugger_h + +#include "mozilla/PerformanceTypes.h" +#include "mozilla/dom/WorkerScope.h" +#include "nsCOMPtr.h" +#include "nsIWorkerDebugger.h" + +class mozIDOMWindow; +class nsIPrincipal; +class nsPIDOMWindowInner; + +namespace mozilla::dom { + +class WorkerPrivate; + +class WorkerDebugger : public nsIWorkerDebugger { + class ReportDebuggerErrorRunnable; + class PostDebuggerMessageRunnable; + + CheckedUnsafePtr mWorkerPrivate; + bool mIsInitialized; + nsTArray> mListeners; + + public: + explicit WorkerDebugger(WorkerPrivate* aWorkerPrivate); + + NS_DECL_ISUPPORTS + NS_DECL_NSIWORKERDEBUGGER + + void AssertIsOnParentThread(); + + void Close(); + + void PostMessageToDebugger(const nsAString& aMessage); + + void ReportErrorToDebugger(const nsAString& aFilename, uint32_t aLineno, + const nsAString& aMessage); + + /* + * Sends back a PerformanceInfo struct from the counters + * in mWorkerPrivate. Counters are reset to zero after this call. + */ + RefPtr ReportPerformanceInfo(); + + private: + virtual ~WorkerDebugger(); + + void PostMessageToDebuggerOnMainThread(const nsAString& aMessage); + + void ReportErrorToDebuggerOnMainThread(const nsAString& aFilename, + uint32_t aLineno, + const nsAString& aMessage); + + nsCOMPtr DedicatedWorkerWindow(); +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_workers_WorkerDebugger_h diff --git a/dom/workers/WorkerDebuggerManager.cpp b/dom/workers/WorkerDebuggerManager.cpp new file mode 100644 index 0000000000..dfca4748b7 --- /dev/null +++ b/dom/workers/WorkerDebuggerManager.cpp @@ -0,0 +1,330 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WorkerDebuggerManager.h" + +#include "nsSimpleEnumerator.h" + +#include "mozilla/dom/JSExecutionManager.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" + +#include "WorkerDebugger.h" +#include "WorkerPrivate.h" +#include "nsIObserverService.h" + +namespace mozilla::dom { + +namespace { + +class RegisterDebuggerMainThreadRunnable final : public mozilla::Runnable { + WorkerPrivate* mWorkerPrivate; + bool mNotifyListeners; + + public: + RegisterDebuggerMainThreadRunnable(WorkerPrivate* aWorkerPrivate, + bool aNotifyListeners) + : mozilla::Runnable("RegisterDebuggerMainThreadRunnable"), + mWorkerPrivate(aWorkerPrivate), + mNotifyListeners(aNotifyListeners) {} + + private: + ~RegisterDebuggerMainThreadRunnable() = default; + + NS_IMETHOD + Run() override { + WorkerDebuggerManager* manager = WorkerDebuggerManager::Get(); + MOZ_ASSERT(manager); + + manager->RegisterDebuggerMainThread(mWorkerPrivate, mNotifyListeners); + return NS_OK; + } +}; + +class UnregisterDebuggerMainThreadRunnable final : public mozilla::Runnable { + WorkerPrivate* mWorkerPrivate; + + public: + explicit UnregisterDebuggerMainThreadRunnable(WorkerPrivate* aWorkerPrivate) + : mozilla::Runnable("UnregisterDebuggerMainThreadRunnable"), + mWorkerPrivate(aWorkerPrivate) {} + + private: + ~UnregisterDebuggerMainThreadRunnable() = default; + + NS_IMETHOD + Run() override { + WorkerDebuggerManager* manager = WorkerDebuggerManager::Get(); + MOZ_ASSERT(manager); + + manager->UnregisterDebuggerMainThread(mWorkerPrivate); + return NS_OK; + } +}; + +static StaticRefPtr gWorkerDebuggerManager; + +} /* anonymous namespace */ + +class WorkerDebuggerEnumerator final : public nsSimpleEnumerator { + nsTArray> mDebuggers; + uint32_t mIndex; + + public: + explicit WorkerDebuggerEnumerator( + const nsTArray>& aDebuggers) + : mDebuggers(aDebuggers.Clone()), mIndex(0) {} + + NS_DECL_NSISIMPLEENUMERATOR + + const nsID& DefaultInterface() override { + return NS_GET_IID(nsIWorkerDebugger); + } + + private: + ~WorkerDebuggerEnumerator() override = default; +}; + +NS_IMETHODIMP +WorkerDebuggerEnumerator::HasMoreElements(bool* aResult) { + *aResult = mIndex < mDebuggers.Length(); + return NS_OK; +}; + +NS_IMETHODIMP +WorkerDebuggerEnumerator::GetNext(nsISupports** aResult) { + if (mIndex == mDebuggers.Length()) { + return NS_ERROR_FAILURE; + } + + mDebuggers.ElementAt(mIndex++).forget(aResult); + return NS_OK; +}; + +WorkerDebuggerManager::WorkerDebuggerManager() + : mMutex("WorkerDebuggerManager::mMutex") { + AssertIsOnMainThread(); +} + +WorkerDebuggerManager::~WorkerDebuggerManager() { AssertIsOnMainThread(); } + +// static +already_AddRefed WorkerDebuggerManager::GetInstance() { + RefPtr manager = WorkerDebuggerManager::GetOrCreate(); + return manager.forget(); +} + +// static +WorkerDebuggerManager* WorkerDebuggerManager::GetOrCreate() { + AssertIsOnMainThread(); + + if (!gWorkerDebuggerManager) { + // The observer service now owns us until shutdown. + gWorkerDebuggerManager = new WorkerDebuggerManager(); + if (NS_SUCCEEDED(gWorkerDebuggerManager->Init())) { + ClearOnShutdown(&gWorkerDebuggerManager); + } else { + NS_WARNING("Failed to initialize worker debugger manager!"); + gWorkerDebuggerManager = nullptr; + } + } + + return gWorkerDebuggerManager; +} + +WorkerDebuggerManager* WorkerDebuggerManager::Get() { + MOZ_ASSERT(gWorkerDebuggerManager); + return gWorkerDebuggerManager; +} + +NS_IMPL_ISUPPORTS(WorkerDebuggerManager, nsIObserver, nsIWorkerDebuggerManager); + +NS_IMETHODIMP +WorkerDebuggerManager::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + Shutdown(); + return NS_OK; + } + + MOZ_ASSERT_UNREACHABLE("Unknown observer topic!"); + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebuggerManager::GetWorkerDebuggerEnumerator( + nsISimpleEnumerator** aResult) { + AssertIsOnMainThread(); + + RefPtr enumerator = + new WorkerDebuggerEnumerator(mDebuggers); + enumerator.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebuggerManager::AddListener( + nsIWorkerDebuggerManagerListener* aListener) { + AssertIsOnMainThread(); + + MutexAutoLock lock(mMutex); + + if (mListeners.Contains(aListener)) { + return NS_ERROR_INVALID_ARG; + } + + mListeners.AppendElement(aListener); + return NS_OK; +} + +NS_IMETHODIMP +WorkerDebuggerManager::RemoveListener( + nsIWorkerDebuggerManagerListener* aListener) { + AssertIsOnMainThread(); + + MutexAutoLock lock(mMutex); + + if (!mListeners.Contains(aListener)) { + return NS_OK; + } + + mListeners.RemoveElement(aListener); + return NS_OK; +} + +nsresult WorkerDebuggerManager::Init() { + nsCOMPtr obs = services::GetObserverService(); + NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE); + + nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +void WorkerDebuggerManager::Shutdown() { + AssertIsOnMainThread(); + + MutexAutoLock lock(mMutex); + + mListeners.Clear(); +} + +void WorkerDebuggerManager::RegisterDebugger(WorkerPrivate* aWorkerPrivate) { + aWorkerPrivate->AssertIsOnParentThread(); + + if (NS_IsMainThread()) { + // When the parent thread is the main thread, it will always block until all + // register liseners have been called, since it cannot continue until the + // call to RegisterDebuggerMainThread returns. + // + // In this case, it is always safe to notify all listeners on the main + // thread, even if there were no listeners at the time this method was + // called, so we can always pass true for the value of aNotifyListeners. + // This avoids having to lock mMutex to check whether mListeners is empty. + RegisterDebuggerMainThread(aWorkerPrivate, true); + } else { + // We guarantee that if any register listeners are called, the worker does + // not start running until all register listeners have been called. To + // guarantee this, the parent thread should block until all register + // listeners have been called. + // + // However, to avoid overhead when the debugger is not being used, the + // parent thread will only block if there were any listeners at the time + // this method was called. As a result, we should not notify any listeners + // on the main thread if there were no listeners at the time this method was + // called, because the parent will not be blocking in that case. + bool hasListeners = false; + { + MutexAutoLock lock(mMutex); + + hasListeners = !mListeners.IsEmpty(); + } + + nsCOMPtr runnable = + new RegisterDebuggerMainThreadRunnable(aWorkerPrivate, hasListeners); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL)); + + if (hasListeners) { + aWorkerPrivate->WaitForIsDebuggerRegistered(true); + } + } +} + +void WorkerDebuggerManager::UnregisterDebugger(WorkerPrivate* aWorkerPrivate) { + aWorkerPrivate->AssertIsOnParentThread(); + + if (NS_IsMainThread()) { + UnregisterDebuggerMainThread(aWorkerPrivate); + } else { + nsCOMPtr runnable = + new UnregisterDebuggerMainThreadRunnable(aWorkerPrivate); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL)); + + aWorkerPrivate->WaitForIsDebuggerRegistered(false); + } +} + +void WorkerDebuggerManager::RegisterDebuggerMainThread( + WorkerPrivate* aWorkerPrivate, bool aNotifyListeners) { + AssertIsOnMainThread(); + + RefPtr debugger = new WorkerDebugger(aWorkerPrivate); + mDebuggers.AppendElement(debugger); + + aWorkerPrivate->SetDebugger(debugger); + + if (aNotifyListeners) { + for (const auto& listener : CloneListeners()) { + listener->OnRegister(debugger); + } + } + + aWorkerPrivate->SetIsDebuggerRegistered(true); +} + +void WorkerDebuggerManager::UnregisterDebuggerMainThread( + WorkerPrivate* aWorkerPrivate) { + AssertIsOnMainThread(); + + // There is nothing to do here if the debugger was never succesfully + // registered. We need to check this on the main thread because the worker + // does not wait for the registration to complete if there were no listeners + // installed when it started. + if (!aWorkerPrivate->IsDebuggerRegistered()) { + return; + } + + RefPtr debugger = aWorkerPrivate->Debugger(); + mDebuggers.RemoveElement(debugger); + + aWorkerPrivate->SetDebugger(nullptr); + + for (const auto& listener : CloneListeners()) { + listener->OnUnregister(debugger); + } + + debugger->Close(); + aWorkerPrivate->SetIsDebuggerRegistered(false); +} + +uint32_t WorkerDebuggerManager::GetDebuggersLength() const { + return mDebuggers.Length(); +} + +WorkerDebugger* WorkerDebuggerManager::GetDebuggerAt(uint32_t aIndex) const { + return mDebuggers.SafeElementAt(aIndex, nullptr); +} + +nsTArray> +WorkerDebuggerManager::CloneListeners() { + MutexAutoLock lock(mMutex); + + return mListeners.Clone(); +} + +} // namespace mozilla::dom diff --git a/dom/workers/WorkerDebuggerManager.h b/dom/workers/WorkerDebuggerManager.h new file mode 100644 index 0000000000..7c22a6b62c --- /dev/null +++ b/dom/workers/WorkerDebuggerManager.h @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workers_workerdebuggermanager_h +#define mozilla_dom_workers_workerdebuggermanager_h + +#include "MainThreadUtils.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Mutex.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsIObserver.h" +#include "nsISupports.h" +#include "nsIWorkerDebuggerManager.h" +#include "nsTArray.h" + +#define WORKERDEBUGGERMANAGER_CID \ + { \ + 0x62ec8731, 0x55ad, 0x4246, { \ + 0xb2, 0xea, 0xf2, 0x6c, 0x1f, 0xe1, 0x9d, 0x2d \ + } \ + } +#define WORKERDEBUGGERMANAGER_CONTRACTID \ + "@mozilla.org/dom/workers/workerdebuggermanager;1" + +namespace mozilla::dom { + +class WorkerDebugger; +class WorkerPrivate; + +class WorkerDebuggerManager final : public nsIObserver, + public nsIWorkerDebuggerManager { + Mutex mMutex MOZ_UNANNOTATED; + + // Protected by mMutex. + nsTArray> mListeners; + + // Only touched on the main thread. + nsTArray> mDebuggers; + + public: + static already_AddRefed GetInstance(); + + static WorkerDebuggerManager* GetOrCreate(); + + static WorkerDebuggerManager* Get(); + + WorkerDebuggerManager(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + NS_DECL_NSIWORKERDEBUGGERMANAGER + + nsresult Init(); + + void Shutdown(); + + void RegisterDebugger(WorkerPrivate* aWorkerPrivate); + + void RegisterDebuggerMainThread(WorkerPrivate* aWorkerPrivate, + bool aNotifyListeners); + + void UnregisterDebugger(WorkerPrivate* aWorkerPrivate); + + void UnregisterDebuggerMainThread(WorkerPrivate* aWorkerPrivate); + + uint32_t GetDebuggersLength() const; + + WorkerDebugger* GetDebuggerAt(uint32_t aIndex) const; + + private: + nsTArray> CloneListeners(); + + virtual ~WorkerDebuggerManager(); +}; + +inline nsresult RegisterWorkerDebugger(WorkerPrivate* aWorkerPrivate) { + WorkerDebuggerManager* manager; + + if (NS_IsMainThread()) { + manager = WorkerDebuggerManager::GetOrCreate(); + if (!manager) { + NS_WARNING("Failed to create worker debugger manager!"); + return NS_ERROR_FAILURE; + } + } else { + manager = WorkerDebuggerManager::Get(); + } + + manager->RegisterDebugger(aWorkerPrivate); + return NS_OK; +} + +inline nsresult UnregisterWorkerDebugger(WorkerPrivate* aWorkerPrivate) { + WorkerDebuggerManager* manager; + + if (NS_IsMainThread()) { + manager = WorkerDebuggerManager::GetOrCreate(); + if (!manager) { + NS_WARNING("Failed to create worker debugger manager!"); + return NS_ERROR_FAILURE; + } + } else { + manager = WorkerDebuggerManager::Get(); + } + + manager->UnregisterDebugger(aWorkerPrivate); + return NS_OK; +} + +} // namespace mozilla::dom + +#endif // mozilla_dom_workers_workerdebuggermanager_h diff --git a/dom/workers/WorkerDocumentListener.cpp b/dom/workers/WorkerDocumentListener.cpp new file mode 100644 index 0000000000..dfc8544daf --- /dev/null +++ b/dom/workers/WorkerDocumentListener.cpp @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WorkerDocumentListener.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRef.h" +#include "mozilla/dom/WorkerRunnable.h" +#include "mozilla/dom/WorkerScope.h" +#include "nsGlobalWindowInner.h" + +namespace mozilla::dom { + +WorkerDocumentListener::WorkerDocumentListener() + : mMutex("mozilla::dom::WorkerDocumentListener::mMutex") {} + +WorkerDocumentListener::~WorkerDocumentListener() = default; + +RefPtr WorkerDocumentListener::Create( + WorkerPrivate* aWorkerPrivate) { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + auto listener = MakeRefPtr(); + + RefPtr strongWorkerRef = + StrongWorkerRef::Create(aWorkerPrivate, "WorkerDocumentListener", + [listener]() { listener->Destroy(); }); + if (NS_WARN_IF(!strongWorkerRef)) { + return nullptr; + } + + listener->mWorkerRef = new ThreadSafeWorkerRef(strongWorkerRef); + uint64_t windowID = aWorkerPrivate->WindowID(); + + aWorkerPrivate->DispatchToMainThread(NS_NewRunnableFunction( + "WorkerDocumentListener::Create", + [listener, windowID] { listener->SetListening(windowID, true); })); + + return listener; +} + +void WorkerDocumentListener::OnVisible(bool aVisible) { + MOZ_ASSERT(NS_IsMainThread()); + + MutexAutoLock lock(mMutex); + if (!mWorkerRef) { + // We haven't handled the runnable to release this yet. + return; + } + + class VisibleRunnable final : public WorkerRunnable { + public: + VisibleRunnable(WorkerPrivate* aWorkerPrivate, bool aVisible) + : WorkerRunnable(aWorkerPrivate), mVisible(aVisible) {} + + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { + WorkerGlobalScope* scope = aWorkerPrivate->GlobalScope(); + MOZ_ASSERT(scope); + scope->OnDocumentVisible(mVisible); + return true; + } + + private: + const bool mVisible; + }; + + auto runnable = MakeRefPtr(mWorkerRef->Private(), aVisible); + runnable->Dispatch(); +} + +void WorkerDocumentListener::SetListening(uint64_t aWindowID, bool aListen) { + MOZ_ASSERT(NS_IsMainThread()); + + auto* window = nsGlobalWindowInner::GetInnerWindowWithId(aWindowID); + Document* doc = window->GetExtantDoc(); + if (NS_WARN_IF(!doc)) { + // This would typically happen during shutdown if there is an active worker + // listening for document events. The Document may already be freed when we + // try to deregister for notifications. + return; + } + + if (aListen) { + doc->AddWorkerDocumentListener(this); + } else { + doc->RemoveWorkerDocumentListener(this); + } +} + +void WorkerDocumentListener::Destroy() { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(mWorkerRef); + WorkerPrivate* workerPrivate = mWorkerRef->Private(); + MOZ_ASSERT(workerPrivate); + workerPrivate->AssertIsOnWorkerThread(); + + uint64_t windowID = workerPrivate->WindowID(); + workerPrivate->DispatchToMainThread(NS_NewRunnableFunction( + "WorkerDocumentListener::Destroy", [self = RefPtr{this}, windowID] { + self->SetListening(windowID, false); + })); + mWorkerRef = nullptr; +} + +} // namespace mozilla::dom diff --git a/dom/workers/WorkerDocumentListener.h b/dom/workers/WorkerDocumentListener.h new file mode 100644 index 0000000000..4bb46cadf2 --- /dev/null +++ b/dom/workers/WorkerDocumentListener.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_WorkerDocumentListener_h__ +#define mozilla_dom_WorkerDocumentListener_h__ + +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "nsISupportsImpl.h" + +namespace mozilla::dom { +class ThreadSafeWorkerRef; +class WorkerPrivate; +class WeakWorkerRef; + +class WorkerDocumentListener final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WorkerDocumentListener) + + public: + WorkerDocumentListener(); + + void OnVisible(bool aVisible); + void SetListening(uint64_t aWindowID, bool aListen); + void Destroy(); + + static RefPtr Create(WorkerPrivate* aWorkerPrivate); + + private: + ~WorkerDocumentListener(); + + Mutex mMutex MOZ_UNANNOTATED; // protects mWorkerRef + RefPtr mWorkerRef; +}; + +} // namespace mozilla::dom + +#endif /* mozilla_dom_WorkerDocumentListener_h__ */ diff --git a/dom/workers/WorkerError.cpp b/dom/workers/WorkerError.cpp new file mode 100644 index 0000000000..43f039614a --- /dev/null +++ b/dom/workers/WorkerError.cpp @@ -0,0 +1,477 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WorkerError.h" + +#include +#include +#include +#include "MainThreadUtils.h" +#include "WorkerPrivate.h" +#include "WorkerRunnable.h" +#include "WorkerScope.h" +#include "js/ComparisonOperators.h" +#include "js/UniquePtr.h" +#include "js/friend/ErrorMessages.h" +#include "jsapi.h" +#include "mozilla/ArrayAlgorithm.h" +#include "mozilla/ArrayIterator.h" +#include "mozilla/Assertions.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Span.h" +#include "mozilla/ThreadSafeWeakPtr.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/ErrorEvent.h" +#include "mozilla/dom/ErrorEventBinding.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/EventBinding.h" +#include "mozilla/dom/EventTarget.h" +#include "mozilla/dom/RemoteWorkerChild.h" +#include "mozilla/dom/RemoteWorkerTypes.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/ServiceWorkerManager.h" +#include "mozilla/dom/ServiceWorkerUtils.h" +#include "mozilla/dom/SimpleGlobalObject.h" +#include "mozilla/dom/Worker.h" +#include "mozilla/dom/WorkerCommon.h" +#include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h" +#include "mozilla/dom/WorkerGlobalScopeBinding.h" +#include "mozilla/fallible.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsGlobalWindowOuter.h" +#include "nsIConsoleService.h" +#include "nsIScriptError.h" +#include "nsScriptError.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsWrapperCacheInlines.h" +#include "nscore.h" +#include "xpcpublic.h" + +namespace mozilla::dom { + +namespace { + +class ReportErrorRunnable final : public WorkerDebuggeeRunnable { + UniquePtr mReport; + + public: + ReportErrorRunnable(WorkerPrivate* aWorkerPrivate, + UniquePtr aReport) + : WorkerDebuggeeRunnable(aWorkerPrivate), mReport(std::move(aReport)) {} + + private: + virtual void PostDispatch(WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) override { + aWorkerPrivate->AssertIsOnWorkerThread(); + + // Dispatch may fail if the worker was canceled, no need to report that as + // an error, so don't call base class PostDispatch. + } + + virtual bool WorkerRun(JSContext* aCx, + WorkerPrivate* aWorkerPrivate) override { + uint64_t innerWindowId; + bool fireAtScope = true; + + bool workerIsAcceptingEvents = aWorkerPrivate->IsAcceptingEvents(); + + WorkerPrivate* parent = aWorkerPrivate->GetParent(); + if (parent) { + innerWindowId = 0; + } else { + AssertIsOnMainThread(); + + // Once a window has frozen its workers, their + // mMainThreadDebuggeeEventTargets should be paused, and their + // WorkerDebuggeeRunnables should not be being executed. The same goes for + // WorkerDebuggeeRunnables sent from child to parent workers, but since a + // frozen parent worker runs only control runnables anyway, that is taken + // care of naturally. + MOZ_ASSERT(!aWorkerPrivate->IsFrozen()); + + // Similarly for paused windows; all its workers should have been + // informed. (Subworkers are unaffected by paused windows.) + MOZ_ASSERT(!aWorkerPrivate->IsParentWindowPaused()); + + if (aWorkerPrivate->IsSharedWorker()) { + aWorkerPrivate->GetRemoteWorkerController() + ->ErrorPropagationOnMainThread(mReport.get(), + /* isErrorEvent */ true); + return true; + } + + // Service workers do not have a main thread parent global, so normal + // worker error reporting will crash. Instead, pass the error to + // the ServiceWorkerManager to report on any controlled documents. + if (aWorkerPrivate->IsServiceWorker()) { + RefPtr actor( + aWorkerPrivate->GetRemoteWorkerController()); + + Unused << NS_WARN_IF(!actor); + + if (actor) { + actor->ErrorPropagationOnMainThread(nullptr, false); + } + + return true; + } + + // The innerWindowId is only required if we are going to ReportError + // below, which is gated on this condition. The inner window correctness + // check is only going to succeed when the worker is accepting events. + if (workerIsAcceptingEvents) { + aWorkerPrivate->AssertInnerWindowIsCorrect(); + innerWindowId = aWorkerPrivate->WindowID(); + } + } + + // Don't fire this event if the JS object has been disconnected from the + // private object. + if (!workerIsAcceptingEvents) { + return true; + } + + WorkerErrorReport::ReportError(aCx, parent, fireAtScope, + aWorkerPrivate->ParentEventTargetRef(), + std::move(mReport), innerWindowId); + return true; + } +}; + +class ReportGenericErrorRunnable final : public WorkerDebuggeeRunnable { + public: + static void CreateAndDispatch(WorkerPrivate* aWorkerPrivate) { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + RefPtr runnable = + new ReportGenericErrorRunnable(aWorkerPrivate); + runnable->Dispatch(); + } + + private: + explicit ReportGenericErrorRunnable(WorkerPrivate* aWorkerPrivate) + : WorkerDebuggeeRunnable(aWorkerPrivate) { + aWorkerPrivate->AssertIsOnWorkerThread(); + } + + void PostDispatch(WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) override { + aWorkerPrivate->AssertIsOnWorkerThread(); + + // Dispatch may fail if the worker was canceled, no need to report that as + // an error, so don't call base class PostDispatch. + } + + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { + // Once a window has frozen its workers, their + // mMainThreadDebuggeeEventTargets should be paused, and their + // WorkerDebuggeeRunnables should not be being executed. The same goes for + // WorkerDebuggeeRunnables sent from child to parent workers, but since a + // frozen parent worker runs only control runnables anyway, that is taken + // care of naturally. + MOZ_ASSERT(!aWorkerPrivate->IsFrozen()); + + // Similarly for paused windows; all its workers should have been informed. + // (Subworkers are unaffected by paused windows.) + MOZ_ASSERT(!aWorkerPrivate->IsParentWindowPaused()); + + if (aWorkerPrivate->IsSharedWorker()) { + aWorkerPrivate->GetRemoteWorkerController()->ErrorPropagationOnMainThread( + nullptr, false); + return true; + } + + if (aWorkerPrivate->IsServiceWorker()) { + RefPtr actor( + aWorkerPrivate->GetRemoteWorkerController()); + + Unused << NS_WARN_IF(!actor); + + if (actor) { + actor->ErrorPropagationOnMainThread(nullptr, false); + } + + return true; + } + + if (!aWorkerPrivate->IsAcceptingEvents()) { + return true; + } + + RefPtr parentEventTarget = + aWorkerPrivate->ParentEventTargetRef(); + RefPtr event = + Event::Constructor(parentEventTarget, u"error"_ns, EventInit()); + event->SetTrusted(true); + + parentEventTarget->DispatchEvent(*event); + return true; + } +}; + +} // namespace + +void WorkerErrorBase::AssignErrorBase(JSErrorBase* aReport) { + CopyUTF8toUTF16(MakeStringSpan(aReport->filename), mFilename); + mLineNumber = aReport->lineno; + mColumnNumber = aReport->column; + mErrorNumber = aReport->errorNumber; +} + +void WorkerErrorNote::AssignErrorNote(JSErrorNotes::Note* aNote) { + WorkerErrorBase::AssignErrorBase(aNote); + xpc::ErrorNote::ErrorNoteToMessageString(aNote, mMessage); +} + +WorkerErrorReport::WorkerErrorReport() + : mIsWarning(false), mExnType(JSEXN_ERR), mMutedError(false) {} + +void WorkerErrorReport::AssignErrorReport(JSErrorReport* aReport) { + WorkerErrorBase::AssignErrorBase(aReport); + xpc::ErrorReport::ErrorReportToMessageString(aReport, mMessage); + + mLine.Assign(aReport->linebuf(), aReport->linebufLength()); + mIsWarning = aReport->isWarning(); + MOZ_ASSERT(aReport->exnType >= JSEXN_FIRST && aReport->exnType < JSEXN_LIMIT); + mExnType = JSExnType(aReport->exnType); + mMutedError = aReport->isMuted; + + if (aReport->notes) { + if (!mNotes.SetLength(aReport->notes->length(), fallible)) { + return; + } + + size_t i = 0; + for (auto&& note : *aReport->notes) { + mNotes.ElementAt(i).AssignErrorNote(note.get()); + i++; + } + } +} + +// aWorkerPrivate is the worker thread we're on (or the main thread, if null) +// aTarget is the worker object that we are going to fire an error at +// (if any). +/* static */ +void WorkerErrorReport::ReportError( + JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aFireAtScope, + DOMEventTargetHelper* aTarget, UniquePtr aReport, + uint64_t aInnerWindowId, JS::Handle aException) { + if (aWorkerPrivate) { + aWorkerPrivate->AssertIsOnWorkerThread(); + } else { + AssertIsOnMainThread(); + } + + // We should not fire error events for warnings but instead make sure that + // they show up in the error console. + if (!aReport->mIsWarning) { + // First fire an ErrorEvent at the worker. + RootedDictionary init(aCx); + + if (aReport->mMutedError) { + init.mMessage.AssignLiteral("Script error."); + } else { + init.mMessage = aReport->mMessage; + init.mFilename = aReport->mFilename; + init.mLineno = aReport->mLineNumber; + init.mColno = aReport->mColumnNumber; + init.mError = aException; + } + + init.mCancelable = true; + init.mBubbles = false; + + if (aTarget) { + RefPtr event = + ErrorEvent::Constructor(aTarget, u"error"_ns, init); + event->SetTrusted(true); + + bool defaultActionEnabled = + aTarget->DispatchEvent(*event, CallerType::System, IgnoreErrors()); + if (!defaultActionEnabled) { + return; + } + } + + // Now fire an event at the global object, but don't do that if the error + // code is too much recursion and this is the same script threw the error. + // XXXbz the interaction of this with worker errors seems kinda broken. + // An overrecursion in the debugger or debugger sandbox will get turned + // into an error event on our parent worker! + // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks making this + // better. + if (aFireAtScope && + (aTarget || aReport->mErrorNumber != JSMSG_OVER_RECURSED)) { + JS::Rooted global(aCx, JS::CurrentGlobalOrNull(aCx)); + NS_ASSERTION(global, "This should never be null!"); + + nsEventStatus status = nsEventStatus_eIgnore; + + if (aWorkerPrivate) { + RefPtr globalScope; + UNWRAP_OBJECT(WorkerGlobalScope, &global, globalScope); + + if (!globalScope) { + WorkerDebuggerGlobalScope* globalScope = nullptr; + UNWRAP_OBJECT(WorkerDebuggerGlobalScope, &global, globalScope); + + MOZ_ASSERT_IF(globalScope, + globalScope->GetWrapperPreserveColor() == global); + if (globalScope || IsWorkerDebuggerSandbox(global)) { + aWorkerPrivate->ReportErrorToDebugger( + aReport->mFilename, aReport->mLineNumber, aReport->mMessage); + return; + } + + MOZ_ASSERT(SimpleGlobalObject::SimpleGlobalType(global) == + SimpleGlobalObject::GlobalType::BindingDetail); + // XXXbz We should really log this to console, but unwinding out of + // this stuff without ending up firing any events is ... hard. Just + // return for now. + // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks + // making this better. + return; + } + + MOZ_ASSERT(globalScope->GetWrapperPreserveColor() == global); + + RefPtr event = + ErrorEvent::Constructor(aTarget, u"error"_ns, init); + event->SetTrusted(true); + + // TODO: Bug 1506441 + if (NS_FAILED(EventDispatcher::DispatchDOMEvent( + MOZ_KnownLive(ToSupports(globalScope)), nullptr, event, nullptr, + &status))) { + NS_WARNING("Failed to dispatch worker thread error event!"); + status = nsEventStatus_eIgnore; + } + } else if (nsGlobalWindowInner* win = xpc::WindowOrNull(global)) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!win->HandleScriptError(init, &status)) { + NS_WARNING("Failed to dispatch main thread error event!"); + status = nsEventStatus_eIgnore; + } + } + + // Was preventDefault() called? + if (status == nsEventStatus_eConsumeNoDefault) { + return; + } + } + } + + // Now fire a runnable to do the same on the parent's thread if we can. + if (aWorkerPrivate) { + RefPtr runnable = + new ReportErrorRunnable(aWorkerPrivate, std::move(aReport)); + runnable->Dispatch(); + return; + } + + // Otherwise log an error to the error console. + WorkerErrorReport::LogErrorToConsole(aCx, *aReport, aInnerWindowId); +} + +/* static */ +void WorkerErrorReport::LogErrorToConsole(JSContext* aCx, + WorkerErrorReport& aReport, + uint64_t aInnerWindowId) { + JS::Rooted stack(aCx, aReport.ReadStack(aCx)); + JS::Rooted stackGlobal(aCx, JS::CurrentGlobalOrNull(aCx)); + + ErrorData errorData( + aReport.mIsWarning, aReport.mLineNumber, aReport.mColumnNumber, + aReport.mMessage, aReport.mFilename, aReport.mLine, + TransformIntoNewArray(aReport.mNotes, [](const WorkerErrorNote& note) { + return ErrorDataNote(note.mLineNumber, note.mColumnNumber, + note.mMessage, note.mFilename); + })); + LogErrorToConsole(errorData, aInnerWindowId, stack, stackGlobal); +} + +/* static */ +void WorkerErrorReport::LogErrorToConsole(const ErrorData& aReport, + uint64_t aInnerWindowId, + JS::Handle aStack, + JS::Handle aStackGlobal) { + AssertIsOnMainThread(); + + RefPtr scriptError = + CreateScriptError(nullptr, JS::NothingHandleValue, aStack, aStackGlobal); + + NS_WARNING_ASSERTION(scriptError, "Failed to create script error!"); + + if (scriptError) { + nsAutoCString category("Web Worker"); + uint32_t flags = aReport.isWarning() ? nsIScriptError::warningFlag + : nsIScriptError::errorFlag; + if (NS_FAILED(scriptError->nsIScriptError::InitWithWindowID( + aReport.message(), aReport.filename(), aReport.line(), + aReport.lineNumber(), aReport.columnNumber(), flags, category, + aInnerWindowId))) { + NS_WARNING("Failed to init script error!"); + scriptError = nullptr; + } + + for (const ErrorDataNote& note : aReport.notes()) { + nsScriptErrorNote* noteObject = new nsScriptErrorNote(); + noteObject->Init(note.message(), note.filename(), 0, note.lineNumber(), + note.columnNumber()); + scriptError->AddNote(noteObject); + } + } + + nsCOMPtr consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + NS_WARNING_ASSERTION(consoleService, "Failed to get console service!"); + + if (consoleService) { + if (scriptError) { + if (NS_SUCCEEDED(consoleService->LogMessage(scriptError))) { + return; + } + NS_WARNING("LogMessage failed!"); + } else if (NS_SUCCEEDED(consoleService->LogStringMessage( + aReport.message().BeginReading()))) { + return; + } + NS_WARNING("LogStringMessage failed!"); + } + + NS_ConvertUTF16toUTF8 msg(aReport.message()); + NS_ConvertUTF16toUTF8 filename(aReport.filename()); + + static const char kErrorString[] = "JS error in Web Worker: %s [%s:%u]"; + +#ifdef ANDROID + __android_log_print(ANDROID_LOG_INFO, "Gecko", kErrorString, msg.get(), + filename.get(), aReport.lineNumber()); +#endif + + fprintf(stderr, kErrorString, msg.get(), filename.get(), + aReport.lineNumber()); + fflush(stderr); +} + +/* static */ +void WorkerErrorReport::CreateAndDispatchGenericErrorRunnableToParent( + WorkerPrivate* aWorkerPrivate) { + ReportGenericErrorRunnable::CreateAndDispatch(aWorkerPrivate); +} + +} // namespace mozilla::dom diff --git a/dom/workers/WorkerError.h b/dom/workers/WorkerError.h new file mode 100644 index 0000000000..5476d22e89 --- /dev/null +++ b/dom/workers/WorkerError.h @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workers_WorkerError_h +#define mozilla_dom_workers_WorkerError_h + +#include "mozilla/dom/SerializedStackHolder.h" +#include "mozilla/dom/WorkerCommon.h" +#include "jsapi.h" + +namespace mozilla { + +class DOMEventTargetHelper; + +namespace dom { + +class ErrorData; +class WorkerErrorBase { + public: + nsString mMessage; + nsString mFilename; + uint32_t mLineNumber; + uint32_t mColumnNumber; + uint32_t mErrorNumber; + + WorkerErrorBase() : mLineNumber(0), mColumnNumber(0), mErrorNumber(0) {} + + void AssignErrorBase(JSErrorBase* aReport); +}; + +class WorkerErrorNote : public WorkerErrorBase { + public: + void AssignErrorNote(JSErrorNotes::Note* aNote); +}; + +class WorkerPrivate; + +class WorkerErrorReport : public WorkerErrorBase, public SerializedStackHolder { + public: + nsString mLine; + bool mIsWarning; + JSExnType mExnType; + bool mMutedError; + nsTArray mNotes; + + WorkerErrorReport(); + + void AssignErrorReport(JSErrorReport* aReport); + + // aWorkerPrivate is the worker thread we're on (or the main thread, if null) + // aTarget is the worker object that we are going to fire an error at + // (if any). + // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1743443) + MOZ_CAN_RUN_SCRIPT_BOUNDARY static void ReportError( + JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aFireAtScope, + DOMEventTargetHelper* aTarget, UniquePtr aReport, + uint64_t aInnerWindowId, + JS::Handle aException = JS::NullHandleValue); + + static void LogErrorToConsole(JSContext* aCx, WorkerErrorReport& aReport, + uint64_t aInnerWindowId); + + static void LogErrorToConsole(const mozilla::dom::ErrorData& aReport, + uint64_t aInnerWindowId, + JS::Handle aStack = nullptr, + JS::Handle aStackGlobal = nullptr); + + static void CreateAndDispatchGenericErrorRunnableToParent( + WorkerPrivate* aWorkerPrivate); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_workers_WorkerError_h diff --git a/dom/workers/WorkerEventTarget.cpp b/dom/workers/WorkerEventTarget.cpp new file mode 100644 index 0000000000..446123334c --- /dev/null +++ b/dom/workers/WorkerEventTarget.cpp @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WorkerEventTarget.h" +#include "WorkerPrivate.h" +#include "WorkerRunnable.h" + +#include "mozilla/dom/ReferrerInfo.h" + +namespace mozilla::dom { + +namespace { + +class WrappedControlRunnable final : public WorkerControlRunnable { + nsCOMPtr mInner; + + ~WrappedControlRunnable() = default; + + public: + WrappedControlRunnable(WorkerPrivate* aWorkerPrivate, + nsCOMPtr&& aInner) + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), + mInner(std::move(aInner)) {} + + virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { + // Silence bad assertions, this can be dispatched from any thread. + return true; + } + + virtual void PostDispatch(WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) override { + // Silence bad assertions, this can be dispatched from any thread. + } + + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { + mInner->Run(); + return true; + } + + nsresult Cancel() override { + nsCOMPtr cr = do_QueryInterface(mInner); + + // If the inner runnable is not cancellable, then just do the normal + // WorkerControlRunnable thing. This will end up calling Run(). + if (!cr) { + WorkerControlRunnable::Cancel(); + return NS_OK; + } + + // Otherwise call the inner runnable's Cancel() and treat this like + // a WorkerRunnable cancel. We can't call WorkerControlRunnable::Cancel() + // in this case since that would result in both Run() and the inner + // Cancel() being called. + Unused << cr->Cancel(); + return WorkerRunnable::Cancel(); + } +}; + +} // anonymous namespace + +NS_IMPL_ISUPPORTS(WorkerEventTarget, nsIEventTarget, nsISerialEventTarget) + +WorkerEventTarget::WorkerEventTarget(WorkerPrivate* aWorkerPrivate, + Behavior aBehavior) + : mMutex("WorkerEventTarget"), + mWorkerPrivate(aWorkerPrivate), + mBehavior(aBehavior) { + MOZ_DIAGNOSTIC_ASSERT(mWorkerPrivate); +} + +void WorkerEventTarget::ForgetWorkerPrivate(WorkerPrivate* aWorkerPrivate) { + MutexAutoLock lock(mMutex); + MOZ_DIAGNOSTIC_ASSERT(!mWorkerPrivate || mWorkerPrivate == aWorkerPrivate); + mWorkerPrivate = nullptr; +} + +NS_IMETHODIMP +WorkerEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) { + nsCOMPtr runnable(aRunnable); + return Dispatch(runnable.forget(), aFlags); +} + +NS_IMETHODIMP +WorkerEventTarget::Dispatch(already_AddRefed aRunnable, + uint32_t aFlags) { + nsCOMPtr runnable(aRunnable); + + MutexAutoLock lock(mMutex); + + if (!mWorkerPrivate) { + return NS_ERROR_FAILURE; + } + + if (mBehavior == Behavior::Hybrid) { + RefPtr r = + mWorkerPrivate->MaybeWrapAsWorkerRunnable(runnable.forget()); + if (r->Dispatch()) { + return NS_OK; + } + + runnable = std::move(r); + } + + RefPtr r = + new WrappedControlRunnable(mWorkerPrivate, std::move(runnable)); + if (!r->Dispatch()) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +WorkerEventTarget::DelayedDispatch(already_AddRefed, uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WorkerEventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + NS_ENSURE_ARG(aTask); + + MutexAutoLock lock(mMutex); + + // If mWorkerPrivate is gone, the event target is already late during + // shutdown, return NS_ERROR_UNEXPECTED as documented in `nsIEventTarget.idl`. + if (!mWorkerPrivate) { + return NS_ERROR_UNEXPECTED; + } + + return mWorkerPrivate->RegisterShutdownTask(aTask); +} + +NS_IMETHODIMP +WorkerEventTarget::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + NS_ENSURE_ARG(aTask); + + MutexAutoLock lock(mMutex); + + if (!mWorkerPrivate) { + return NS_ERROR_UNEXPECTED; + } + + return mWorkerPrivate->UnregisterShutdownTask(aTask); +} + +NS_IMETHODIMP_(bool) +WorkerEventTarget::IsOnCurrentThreadInfallible() { + MutexAutoLock lock(mMutex); + + if (!mWorkerPrivate) { + return false; + } + + return mWorkerPrivate->IsOnCurrentThread(); +} + +NS_IMETHODIMP +WorkerEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) { + MOZ_ASSERT(aIsOnCurrentThread); + *aIsOnCurrentThread = IsOnCurrentThreadInfallible(); + return NS_OK; +} + +} // namespace mozilla::dom diff --git a/dom/workers/WorkerEventTarget.h b/dom/workers/WorkerEventTarget.h new file mode 100644 index 0000000000..dd9cb61748 --- /dev/null +++ b/dom/workers/WorkerEventTarget.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_WorkerEventTarget_h +#define mozilla_dom_WorkerEventTarget_h + +#include "nsISerialEventTarget.h" +#include "mozilla/Mutex.h" +#include "mozilla/dom/WorkerPrivate.h" + +namespace mozilla::dom { + +class WorkerEventTarget final : public nsISerialEventTarget { + public: + // The WorkerEventTarget supports different dispatch behaviors: + // + // * Hybrid targets will attempt to dispatch as a normal runnable, + // but fallback to a control runnable if that fails. This is + // often necessary for code that wants normal dispatch order, but + // also needs to execute while the worker is shutting down (possibly + // with a holder in place.) + // + // * ControlOnly targets will simply dispatch a control runnable. + enum class Behavior : uint8_t { Hybrid, ControlOnly }; + + private: + mozilla::Mutex mMutex; + CheckedUnsafePtr mWorkerPrivate MOZ_GUARDED_BY(mMutex); + const Behavior mBehavior MOZ_GUARDED_BY(mMutex); + + ~WorkerEventTarget() = default; + + public: + WorkerEventTarget(WorkerPrivate* aWorkerPrivate, Behavior aBehavior); + + void ForgetWorkerPrivate(WorkerPrivate* aWorkerPrivate); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET + NS_DECL_NSISERIALEVENTTARGET +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_WorkerEventTarget_h diff --git a/dom/workers/WorkerIPCUtils.h b/dom/workers/WorkerIPCUtils.h new file mode 100644 index 0000000000..0be45b307f --- /dev/null +++ b/dom/workers/WorkerIPCUtils.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _mozilla_dom_WorkerIPCUtils_h +#define _mozilla_dom_WorkerIPCUtils_h + +#include "ipc/EnumSerializer.h" + +// Undo X11/X.h's definition of None +#undef None + +#include "mozilla/dom/WorkerBinding.h" + +namespace IPC { + +template <> +struct ParamTraits + : public ContiguousEnumSerializer {}; + +} // namespace IPC + +#endif // _mozilla_dom_WorkerIPCUtils_h diff --git a/dom/workers/WorkerLoadInfo.cpp b/dom/workers/WorkerLoadInfo.cpp new file mode 100644 index 0000000000..5c12eecd86 --- /dev/null +++ b/dom/workers/WorkerLoadInfo.cpp @@ -0,0 +1,508 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WorkerLoadInfo.h" +#include "WorkerPrivate.h" + +#include "mozilla/BasePrincipal.h" +#include "mozilla/dom/nsCSPUtils.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/ReferrerInfo.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "mozilla/LoadContext.h" +#include "mozilla/StorageAccess.h" +#include "mozilla/StoragePrincipalHelper.h" +#include "nsContentUtils.h" +#include "nsIContentSecurityPolicy.h" +#include "nsICookieJarSettings.h" +#include "nsINetworkInterceptController.h" +#include "nsIProtocolHandler.h" +#include "nsIReferrerInfo.h" +#include "nsIBrowserChild.h" +#include "nsScriptSecurityManager.h" +#include "nsNetUtil.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +namespace { + +class MainThreadReleaseRunnable final : public Runnable { + nsTArray> mDoomed; + nsCOMPtr mLoadGroupToCancel; + + public: + MainThreadReleaseRunnable(nsTArray>&& aDoomed, + nsCOMPtr&& aLoadGroupToCancel) + : mozilla::Runnable("MainThreadReleaseRunnable"), + mDoomed(std::move(aDoomed)), + mLoadGroupToCancel(std::move(aLoadGroupToCancel)) {} + + NS_INLINE_DECL_REFCOUNTING_INHERITED(MainThreadReleaseRunnable, Runnable) + + NS_IMETHOD + Run() override { + if (mLoadGroupToCancel) { + mLoadGroupToCancel->CancelWithReason( + NS_BINDING_ABORTED, "WorkerLoadInfo::MainThreadReleaseRunnable"_ns); + mLoadGroupToCancel = nullptr; + } + + mDoomed.Clear(); + return NS_OK; + } + + private: + ~MainThreadReleaseRunnable() = default; +}; + +// Specialize this if there's some class that has multiple nsISupports bases. +template +struct ISupportsBaseInfo { + using ISupportsBase = T; +}; + +template