summaryrefslogtreecommitdiffstats
path: root/dom/workers
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/workers
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/workers')
-rw-r--r--dom/workers/ChromeWorker.cpp84
-rw-r--r--dom/workers/ChromeWorker.h33
-rw-r--r--dom/workers/ChromeWorkerScope.cpp67
-rw-r--r--dom/workers/ChromeWorkerScope.h18
-rw-r--r--dom/workers/JSExecutionManager.cpp251
-rw-r--r--dom/workers/JSExecutionManager.h192
-rw-r--r--dom/workers/JSSettings.h62
-rw-r--r--dom/workers/MessageEventRunnable.cpp156
-rw-r--r--dom/workers/MessageEventRunnable.h38
-rw-r--r--dom/workers/Queue.h151
-rw-r--r--dom/workers/RegisterBindings.cpp60
-rw-r--r--dom/workers/RuntimeService.cpp2414
-rw-r--r--dom/workers/RuntimeService.h200
-rw-r--r--dom/workers/ScriptLoader.cpp1840
-rw-r--r--dom/workers/ScriptLoader.h375
-rw-r--r--dom/workers/Worker.cpp211
-rw-r--r--dom/workers/Worker.h67
-rw-r--r--dom/workers/WorkerCSPEventListener.cpp104
-rw-r--r--dom/workers/WorkerCSPEventListener.h40
-rw-r--r--dom/workers/WorkerChannelInfo.cpp66
-rw-r--r--dom/workers/WorkerChannelInfo.h50
-rw-r--r--dom/workers/WorkerCommon.h57
-rw-r--r--dom/workers/WorkerDebugger.cpp604
-rw-r--r--dom/workers/WorkerDebugger.h66
-rw-r--r--dom/workers/WorkerDebuggerManager.cpp330
-rw-r--r--dom/workers/WorkerDebuggerManager.h116
-rw-r--r--dom/workers/WorkerDocumentListener.cpp110
-rw-r--r--dom/workers/WorkerDocumentListener.h40
-rw-r--r--dom/workers/WorkerError.cpp477
-rw-r--r--dom/workers/WorkerError.h78
-rw-r--r--dom/workers/WorkerEventTarget.cpp167
-rw-r--r--dom/workers/WorkerEventTarget.h48
-rw-r--r--dom/workers/WorkerIPCUtils.h26
-rw-r--r--dom/workers/WorkerLoadInfo.cpp508
-rw-r--r--dom/workers/WorkerLoadInfo.h198
-rw-r--r--dom/workers/WorkerLocation.cpp36
-rw-r--r--dom/workers/WorkerLocation.h72
-rw-r--r--dom/workers/WorkerNavigator.cpp293
-rw-r--r--dom/workers/WorkerNavigator.h122
-rw-r--r--dom/workers/WorkerPrivate.cpp6003
-rw-r--r--dom/workers/WorkerPrivate.h1625
-rw-r--r--dom/workers/WorkerRef.cpp245
-rw-r--r--dom/workers/WorkerRef.h237
-rw-r--r--dom/workers/WorkerRunnable.cpp728
-rw-r--r--dom/workers/WorkerRunnable.h508
-rw-r--r--dom/workers/WorkerScope.cpp1414
-rw-r--r--dom/workers/WorkerScope.h562
-rw-r--r--dom/workers/WorkerStatus.h58
-rw-r--r--dom/workers/WorkerTestUtils.cpp21
-rw-r--r--dom/workers/WorkerTestUtils.h40
-rw-r--r--dom/workers/WorkerThread.cpp378
-rw-r--r--dom/workers/WorkerThread.h112
-rw-r--r--dom/workers/loader/CacheLoadHandler.cpp651
-rw-r--r--dom/workers/loader/CacheLoadHandler.h221
-rw-r--r--dom/workers/loader/NetworkLoadHandler.cpp393
-rw-r--r--dom/workers/loader/NetworkLoadHandler.h79
-rw-r--r--dom/workers/loader/ScriptResponseHeaderProcessor.cpp78
-rw-r--r--dom/workers/loader/ScriptResponseHeaderProcessor.h94
-rw-r--r--dom/workers/loader/WorkerLoadContext.cpp78
-rw-r--r--dom/workers/loader/WorkerLoadContext.h219
-rw-r--r--dom/workers/loader/WorkerModuleLoader.cpp215
-rw-r--r--dom/workers/loader/WorkerModuleLoader.h87
-rw-r--r--dom/workers/loader/moz.build34
-rw-r--r--dom/workers/moz.build110
-rw-r--r--dom/workers/nsIWorkerChannelInfo.idl22
-rw-r--r--dom/workers/nsIWorkerDebugger.idl69
-rw-r--r--dom/workers/nsIWorkerDebuggerManager.idl22
-rw-r--r--dom/workers/remoteworkers/PRemoteWorker.ipdl90
-rw-r--r--dom/workers/remoteworkers/PRemoteWorkerController.ipdl49
-rw-r--r--dom/workers/remoteworkers/PRemoteWorkerService.ipdl25
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerChild.cpp1011
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerChild.h237
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerController.cpp573
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerController.h323
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerControllerChild.cpp149
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerControllerChild.h64
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerControllerParent.cpp215
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerControllerParent.h86
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerManager.cpp735
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerManager.h118
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerParent.cpp201
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerParent.h62
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerService.cpp346
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerService.h123
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerServiceChild.cpp16
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerServiceChild.h34
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerServiceParent.cpp34
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerServiceParent.h41
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh123
-rw-r--r--dom/workers/remoteworkers/moz.build45
-rw-r--r--dom/workers/sharedworkers/PSharedWorker.ipdl40
-rw-r--r--dom/workers/sharedworkers/SharedWorker.cpp426
-rw-r--r--dom/workers/sharedworkers/SharedWorker.h94
-rw-r--r--dom/workers/sharedworkers/SharedWorkerChild.cpp155
-rw-r--r--dom/workers/sharedworkers/SharedWorkerChild.h61
-rw-r--r--dom/workers/sharedworkers/SharedWorkerManager.cpp348
-rw-r--r--dom/workers/sharedworkers/SharedWorkerManager.h164
-rw-r--r--dom/workers/sharedworkers/SharedWorkerParent.cpp165
-rw-r--r--dom/workers/sharedworkers/SharedWorkerParent.h81
-rw-r--r--dom/workers/sharedworkers/SharedWorkerService.cpp263
-rw-r--r--dom/workers/sharedworkers/SharedWorkerService.h74
-rw-r--r--dom/workers/sharedworkers/moz.build28
-rw-r--r--dom/workers/test/404_server.sjs9
-rw-r--r--dom/workers/test/WorkerDebugger.console_childWorker.js3
-rw-r--r--dom/workers/test/WorkerDebugger.console_debugger.js49
-rw-r--r--dom/workers/test/WorkerDebugger.console_worker.js10
-rw-r--r--dom/workers/test/WorkerDebugger.initialize_childWorker.js6
-rw-r--r--dom/workers/test/WorkerDebugger.initialize_debugger.js6
-rw-r--r--dom/workers/test/WorkerDebugger.initialize_debugger_es_worker.js10
-rw-r--r--dom/workers/test/WorkerDebugger.initialize_es_worker.js10
-rw-r--r--dom/workers/test/WorkerDebugger.initialize_worker.js9
-rw-r--r--dom/workers/test/WorkerDebugger.postMessage_childWorker.js3
-rw-r--r--dom/workers/test/WorkerDebugger.postMessage_debugger.js9
-rw-r--r--dom/workers/test/WorkerDebugger.postMessage_worker.js3
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_debugger.js9
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_sandbox.js9
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_worker.js3
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js14
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js29
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js27
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.reportError_childWorker.js5
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.reportError_debugger.js12
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.reportError_worker.js11
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_debugger.js12
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_worker.js3
-rw-r--r--dom/workers/test/WorkerDebuggerManager_childWorker.js3
-rw-r--r--dom/workers/test/WorkerDebuggerManager_worker.js3
-rw-r--r--dom/workers/test/WorkerDebugger_childWorker.js3
-rw-r--r--dom/workers/test/WorkerDebugger_frozen_window1.html15
-rw-r--r--dom/workers/test/WorkerDebugger_frozen_window2.html15
-rw-r--r--dom/workers/test/WorkerDebugger_frozen_worker1.js5
-rw-r--r--dom/workers/test/WorkerDebugger_frozen_worker2.js5
-rw-r--r--dom/workers/test/WorkerDebugger_promise_debugger.js34
-rw-r--r--dom/workers/test/WorkerDebugger_promise_worker.js25
-rw-r--r--dom/workers/test/WorkerDebugger_sharedWorker.js17
-rw-r--r--dom/workers/test/WorkerDebugger_suspended_debugger.js6
-rw-r--r--dom/workers/test/WorkerDebugger_suspended_worker.js6
-rw-r--r--dom/workers/test/WorkerDebugger_worker.js8
-rw-r--r--dom/workers/test/WorkerTest.jsm15
-rw-r--r--dom/workers/test/WorkerTest_badworker.js1
-rw-r--r--dom/workers/test/WorkerTest_subworker.js37
-rw-r--r--dom/workers/test/WorkerTest_worker.js11
-rw-r--r--dom/workers/test/atob_worker.js55
-rw-r--r--dom/workers/test/blank.html14
-rw-r--r--dom/workers/test/browser.ini36
-rw-r--r--dom/workers/test/browser_WorkerDebugger.initialize.js54
-rw-r--r--dom/workers/test/browser_bug1047663.js56
-rw-r--r--dom/workers/test/browser_bug1104623.js60
-rw-r--r--dom/workers/test/browser_consoleSharedWorkers.js70
-rw-r--r--dom/workers/test/browser_fileURL.js73
-rw-r--r--dom/workers/test/browser_privilegedmozilla_remoteworker.js116
-rw-r--r--dom/workers/test/browser_serviceworker_fetch_new_process.js405
-rw-r--r--dom/workers/test/browser_worker_use_counters.js176
-rw-r--r--dom/workers/test/bug1014466_data1.txt1
-rw-r--r--dom/workers/test/bug1014466_data2.txt1
-rw-r--r--dom/workers/test/bug1014466_worker.js63
-rw-r--r--dom/workers/test/bug1020226_frame.html19
-rw-r--r--dom/workers/test/bug1020226_worker.js12
-rw-r--r--dom/workers/test/bug1047663_tab.html8
-rw-r--r--dom/workers/test/bug1047663_worker.sjs41
-rw-r--r--dom/workers/test/bug1060621_worker.js2
-rw-r--r--dom/workers/test/bug1062920_worker.js8
-rw-r--r--dom/workers/test/bug1063538.sjs11
-rw-r--r--dom/workers/test/bug1063538_worker.js25
-rw-r--r--dom/workers/test/bug1104064_worker.js10
-rw-r--r--dom/workers/test/bug1132395_sharedWorker.js12
-rw-r--r--dom/workers/test/bug1132924_worker.js10
-rw-r--r--dom/workers/test/bug978260_worker.js7
-rw-r--r--dom/workers/test/bug998474_worker.js6
-rw-r--r--dom/workers/test/chrome.ini96
-rw-r--r--dom/workers/test/chromeWorker_subworker.js7
-rw-r--r--dom/workers/test/chromeWorker_worker.js20
-rw-r--r--dom/workers/test/chromeWorker_worker.sys.mjs16
-rw-r--r--dom/workers/test/chromeWorker_worker_submod.sys.mjs9
-rw-r--r--dom/workers/test/clearTimeoutsImplicit_worker.js11
-rw-r--r--dom/workers/test/clearTimeouts_worker.js12
-rw-r--r--dom/workers/test/consoleReplaceable_worker.js24
-rw-r--r--dom/workers/test/console_worker.js112
-rw-r--r--dom/workers/test/content_worker.js12
-rw-r--r--dom/workers/test/crashtests/1153636.html5
-rw-r--r--dom/workers/test/crashtests/1158031.html11
-rw-r--r--dom/workers/test/crashtests/1228456.html14
-rw-r--r--dom/workers/test/crashtests/1348882.html18
-rw-r--r--dom/workers/test/crashtests/1819146.html23
-rw-r--r--dom/workers/test/crashtests/1821066.html9
-rw-r--r--dom/workers/test/crashtests/779707.html19
-rw-r--r--dom/workers/test/crashtests/943516.html10
-rw-r--r--dom/workers/test/crashtests/crashtests.list8
-rw-r--r--dom/workers/test/csp_worker.js25
-rw-r--r--dom/workers/test/csp_worker.js^headers^1
-rw-r--r--dom/workers/test/dom_worker_helper.js171
-rw-r--r--dom/workers/test/dynamicImport_nested.mjs8
-rw-r--r--dom/workers/test/dynamicImport_postMessage.mjs8
-rw-r--r--dom/workers/test/dynamicImport_worker.js15
-rw-r--r--dom/workers/test/empty.html0
-rw-r--r--dom/workers/test/empty_worker.js1
-rw-r--r--dom/workers/test/errorPropagation_iframe.html55
-rw-r--r--dom/workers/test/errorPropagation_worker.js50
-rw-r--r--dom/workers/test/errorwarning_worker.js44
-rw-r--r--dom/workers/test/eventDispatch_worker.js84
-rw-r--r--dom/workers/test/fibonacci_worker.js24
-rw-r--r--dom/workers/test/fileBlobSubWorker_worker.js17
-rw-r--r--dom/workers/test/fileBlob_worker.js13
-rw-r--r--dom/workers/test/filePosting_worker.js3
-rw-r--r--dom/workers/test/fileReadSlice_worker.js16
-rw-r--r--dom/workers/test/fileReaderSyncErrors_worker.js82
-rw-r--r--dom/workers/test/fileReaderSync_worker.js25
-rw-r--r--dom/workers/test/fileSlice_worker.js27
-rw-r--r--dom/workers/test/fileSubWorker_worker.js17
-rw-r--r--dom/workers/test/file_bug1010784_worker.js9
-rw-r--r--dom/workers/test/file_service_worker.js3
-rw-r--r--dom/workers/test/file_service_worker_container.html15
-rw-r--r--dom/workers/test/file_service_worker_fetch_synthetic.js59
-rw-r--r--dom/workers/test/file_use_counter_service_worker.js9
-rw-r--r--dom/workers/test/file_use_counter_shared_worker.js10
-rw-r--r--dom/workers/test/file_use_counter_shared_worker_microtask.js12
-rw-r--r--dom/workers/test/file_use_counter_worker.html13
-rw-r--r--dom/workers/test/file_use_counter_worker.js2
-rw-r--r--dom/workers/test/file_worker.js16
-rw-r--r--dom/workers/test/foreign.js1
-rw-r--r--dom/workers/test/head.js75
-rw-r--r--dom/workers/test/importForeignScripts_worker.js55
-rw-r--r--dom/workers/test/importScripts_3rdParty_worker.js88
-rw-r--r--dom/workers/test/importScripts_mixedcontent.html46
-rw-r--r--dom/workers/test/importScripts_worker.js63
-rw-r--r--dom/workers/test/importScripts_worker_imported1.js9
-rw-r--r--dom/workers/test/importScripts_worker_imported2.js9
-rw-r--r--dom/workers/test/importScripts_worker_imported3.js6
-rw-r--r--dom/workers/test/importScripts_worker_imported4.js6
-rw-r--r--dom/workers/test/instanceof_worker.js16
-rw-r--r--dom/workers/test/invalid.js1
-rw-r--r--dom/workers/test/json_worker.js354
-rw-r--r--dom/workers/test/loadEncoding_worker.js7
-rw-r--r--dom/workers/test/location_worker.js12
-rw-r--r--dom/workers/test/longThread_worker.js14
-rw-r--r--dom/workers/test/marionette/manifest.ini2
-rw-r--r--dom/workers/test/marionette/service_worker_utils.py63
-rw-r--r--dom/workers/test/marionette/test_service_workers_at_startup.py31
-rw-r--r--dom/workers/test/marionette/test_service_workers_disabled.py37
-rw-r--r--dom/workers/test/mochitest.ini249
-rw-r--r--dom/workers/test/multi_sharedWorker_frame.html58
-rw-r--r--dom/workers/test/multi_sharedWorker_frame_bfcache.html13
-rw-r--r--dom/workers/test/multi_sharedWorker_frame_nobfcache.html13
-rw-r--r--dom/workers/test/multi_sharedWorker_frame_nobfcache.html^headers^1
-rw-r--r--dom/workers/test/multi_sharedWorker_manager.js58
-rw-r--r--dom/workers/test/multi_sharedWorker_sharedWorker.js72
-rw-r--r--dom/workers/test/navigate.html13
-rw-r--r--dom/workers/test/navigator_languages_worker.js11
-rw-r--r--dom/workers/test/navigator_worker.js85
-rw-r--r--dom/workers/test/newError_worker.js5
-rw-r--r--dom/workers/test/notification_permission_worker.js59
-rw-r--r--dom/workers/test/notification_worker.js104
-rw-r--r--dom/workers/test/notification_worker_child-child.js93
-rw-r--r--dom/workers/test/notification_worker_child-parent.js26
-rw-r--r--dom/workers/test/onLine_worker.js70
-rw-r--r--dom/workers/test/onLine_worker_child.js91
-rw-r--r--dom/workers/test/onLine_worker_head.js50
-rw-r--r--dom/workers/test/promise_worker.js1007
-rw-r--r--dom/workers/test/recursion_worker.js46
-rw-r--r--dom/workers/test/recursiveOnerror_worker.js11
-rw-r--r--dom/workers/test/redirect_to_foreign.sjs7
-rw-r--r--dom/workers/test/referrer.sjs14
-rw-r--r--dom/workers/test/referrer_test_server.sjs99
-rw-r--r--dom/workers/test/referrer_worker.html144
-rw-r--r--dom/workers/test/rvals_worker.js13
-rw-r--r--dom/workers/test/script_createFile.js42
-rw-r--r--dom/workers/test/server_fetch_synthetic.sjs50
-rw-r--r--dom/workers/test/sharedWorker_console.js11
-rw-r--r--dom/workers/test/sharedWorker_lifetime.js5
-rw-r--r--dom/workers/test/sharedWorker_ports.js30
-rw-r--r--dom/workers/test/sharedWorker_privateBrowsing.js4
-rw-r--r--dom/workers/test/sharedWorker_sharedWorker.js99
-rw-r--r--dom/workers/test/sharedWorker_thirdparty_frame.html16
-rw-r--r--dom/workers/test/sharedWorker_thirdparty_window.html26
-rw-r--r--dom/workers/test/simpleThread_worker.js52
-rw-r--r--dom/workers/test/sourcemap_header.js65
-rw-r--r--dom/workers/test/sourcemap_header_debugger.js29
-rw-r--r--dom/workers/test/sourcemap_header_iframe.html19
-rw-r--r--dom/workers/test/sourcemap_header_worker.js8
-rw-r--r--dom/workers/test/sourcemap_header_worker.js^headers^1
-rw-r--r--dom/workers/test/suspend_blank.html23
-rw-r--r--dom/workers/test/suspend_window.html82
-rw-r--r--dom/workers/test/suspend_worker.js13
-rw-r--r--dom/workers/test/terminate_worker.js11
-rw-r--r--dom/workers/test/test_404.html39
-rw-r--r--dom/workers/test/test_WorkerDebugger.initialize.xhtml56
-rw-r--r--dom/workers/test/test_WorkerDebugger.postMessage.xhtml59
-rw-r--r--dom/workers/test/test_WorkerDebugger.xhtml145
-rw-r--r--dom/workers/test/test_WorkerDebuggerGlobalScope.createSandbox.xhtml49
-rw-r--r--dom/workers/test/test_WorkerDebuggerGlobalScope.enterEventLoop.xhtml124
-rw-r--r--dom/workers/test/test_WorkerDebuggerGlobalScope.reportError.xhtml95
-rw-r--r--dom/workers/test/test_WorkerDebuggerGlobalScope.setImmediate.xhtml52
-rw-r--r--dom/workers/test/test_WorkerDebuggerManager.xhtml104
-rw-r--r--dom/workers/test/test_WorkerDebugger_console.xhtml98
-rw-r--r--dom/workers/test/test_WorkerDebugger_frozen.xhtml77
-rw-r--r--dom/workers/test/test_WorkerDebugger_promise.xhtml68
-rw-r--r--dom/workers/test/test_WorkerDebugger_suspended.xhtml72
-rw-r--r--dom/workers/test/test_atob.html57
-rw-r--r--dom/workers/test/test_blobConstructor.html60
-rw-r--r--dom/workers/test/test_blobWorkers.html31
-rw-r--r--dom/workers/test/test_bug1002702.html26
-rw-r--r--dom/workers/test/test_bug1010784.html35
-rw-r--r--dom/workers/test/test_bug1014466.html42
-rw-r--r--dom/workers/test/test_bug1020226.html38
-rw-r--r--dom/workers/test/test_bug1036484.html52
-rw-r--r--dom/workers/test/test_bug1060621.html30
-rw-r--r--dom/workers/test/test_bug1062920.html70
-rw-r--r--dom/workers/test/test_bug1062920.xhtml67
-rw-r--r--dom/workers/test/test_bug1063538.html47
-rw-r--r--dom/workers/test/test_bug1104064.html27
-rw-r--r--dom/workers/test/test_bug1132395.html40
-rw-r--r--dom/workers/test/test_bug1132924.html28
-rw-r--r--dom/workers/test/test_bug1278777.html31
-rw-r--r--dom/workers/test/test_bug1301094.html69
-rw-r--r--dom/workers/test/test_bug1317725.html49
-rw-r--r--dom/workers/test/test_bug1317725.js8
-rw-r--r--dom/workers/test/test_bug1824498.html38
-rw-r--r--dom/workers/test/test_bug949946.html26
-rw-r--r--dom/workers/test/test_bug978260.html35
-rw-r--r--dom/workers/test/test_bug998474.html40
-rw-r--r--dom/workers/test/test_chromeWorker.html26
-rw-r--r--dom/workers/test/test_chromeWorker.xhtml58
-rw-r--r--dom/workers/test/test_chromeWorkerJSM.xhtml54
-rw-r--r--dom/workers/test/test_clearTimeouts.html31
-rw-r--r--dom/workers/test/test_clearTimeoutsImplicit.html31
-rw-r--r--dom/workers/test/test_console.html44
-rw-r--r--dom/workers/test/test_consoleAndBlobs.html46
-rw-r--r--dom/workers/test/test_consoleReplaceable.html44
-rw-r--r--dom/workers/test/test_contentWorker.html48
-rw-r--r--dom/workers/test/test_csp.html18
-rw-r--r--dom/workers/test/test_csp.html^headers^2
-rw-r--r--dom/workers/test/test_csp.js54
-rw-r--r--dom/workers/test/test_dataURLWorker.html30
-rw-r--r--dom/workers/test/test_dynamicImport.html76
-rw-r--r--dom/workers/test/test_dynamicImport_and_terminate.html34
-rw-r--r--dom/workers/test/test_dynamicImport_early_termination.html79
-rw-r--r--dom/workers/test/test_errorPropagation.html65
-rw-r--r--dom/workers/test/test_errorwarning.html95
-rw-r--r--dom/workers/test/test_eventDispatch.html32
-rw-r--r--dom/workers/test/test_fibonacci.html51
-rw-r--r--dom/workers/test/test_file.xhtml96
-rw-r--r--dom/workers/test/test_fileBlobPosting.xhtml85
-rw-r--r--dom/workers/test/test_fileBlobSubWorker.xhtml97
-rw-r--r--dom/workers/test/test_filePosting.xhtml85
-rw-r--r--dom/workers/test/test_fileReadSlice.xhtml93
-rw-r--r--dom/workers/test/test_fileReaderSync.xhtml198
-rw-r--r--dom/workers/test/test_fileReaderSyncErrors.xhtml83
-rw-r--r--dom/workers/test/test_fileReaderSync_when_closing.html54
-rw-r--r--dom/workers/test/test_fileSlice.xhtml105
-rw-r--r--dom/workers/test/test_fileSubWorker.xhtml98
-rw-r--r--dom/workers/test/test_importScripts.html53
-rw-r--r--dom/workers/test/test_importScripts_3rdparty.html136
-rw-r--r--dom/workers/test/test_importScripts_mixedcontent.html50
-rw-r--r--dom/workers/test/test_instanceof.html40
-rw-r--r--dom/workers/test/test_json.html89
-rw-r--r--dom/workers/test/test_loadEncoding.html50
-rw-r--r--dom/workers/test/test_loadError.html67
-rw-r--r--dom/workers/test/test_location.html72
-rw-r--r--dom/workers/test/test_longThread.html58
-rw-r--r--dom/workers/test/test_multi_sharedWorker.html241
-rw-r--r--dom/workers/test/test_multi_sharedWorker_lifetimes_bfcache.html151
-rw-r--r--dom/workers/test/test_multi_sharedWorker_lifetimes_nobfcache.html126
-rw-r--r--dom/workers/test/test_navigator.html27
-rw-r--r--dom/workers/test/test_navigator.js10
-rw-r--r--dom/workers/test/test_navigator_iframe.html24
-rw-r--r--dom/workers/test/test_navigator_iframe.js64
-rw-r--r--dom/workers/test/test_navigator_languages.html58
-rw-r--r--dom/workers/test/test_navigator_secureContext.html27
-rw-r--r--dom/workers/test/test_navigator_workers_hardwareConcurrency.html56
-rw-r--r--dom/workers/test/test_newError.html33
-rw-r--r--dom/workers/test/test_notification.html47
-rw-r--r--dom/workers/test/test_notification_child.html46
-rw-r--r--dom/workers/test/test_notification_permission.html48
-rw-r--r--dom/workers/test/test_onLine.html73
-rw-r--r--dom/workers/test/test_promise.html43
-rw-r--r--dom/workers/test/test_promise_resolved_with_string.html41
-rw-r--r--dom/workers/test/test_readableStream_when_closing.html61
-rw-r--r--dom/workers/test/test_recursion.html69
-rw-r--r--dom/workers/test/test_recursiveOnerror.html44
-rw-r--r--dom/workers/test/test_referrer.html58
-rw-r--r--dom/workers/test/test_referrer_header_worker.html38
-rw-r--r--dom/workers/test/test_resolveWorker-assignment.html30
-rw-r--r--dom/workers/test/test_resolveWorker.html31
-rw-r--r--dom/workers/test/test_rvals.html35
-rw-r--r--dom/workers/test/test_setTimeoutWith0.html19
-rw-r--r--dom/workers/test/test_sharedWorker.html71
-rw-r--r--dom/workers/test/test_sharedWorker_lifetime.html30
-rw-r--r--dom/workers/test/test_sharedWorker_ports.html41
-rw-r--r--dom/workers/test/test_sharedWorker_privateBrowsing.html102
-rw-r--r--dom/workers/test/test_sharedWorker_thirdparty.html54
-rw-r--r--dom/workers/test/test_sharedworker_event_listener_leaks.html51
-rw-r--r--dom/workers/test/test_shutdownCheck.xhtml61
-rw-r--r--dom/workers/test/test_simpleThread.html74
-rw-r--r--dom/workers/test/test_sourcemap_header.html22
-rw-r--r--dom/workers/test/test_subworkers_suspended.html145
-rw-r--r--dom/workers/test/test_suspend.html188
-rw-r--r--dom/workers/test/test_terminate.html100
-rw-r--r--dom/workers/test/test_threadErrors.html64
-rw-r--r--dom/workers/test/test_threadTimeouts.html60
-rw-r--r--dom/workers/test/test_throwingOnerror.html54
-rw-r--r--dom/workers/test/test_timeoutTracing.html47
-rw-r--r--dom/workers/test/test_transferable.html123
-rw-r--r--dom/workers/test/test_worker_interfaces.html19
-rw-r--r--dom/workers/test/test_worker_interfaces.js543
-rw-r--r--dom/workers/test/test_worker_interfaces_secureContext.html19
-rw-r--r--dom/workers/test/threadErrors_worker1.js8
-rw-r--r--dom/workers/test/threadErrors_worker2.js8
-rw-r--r--dom/workers/test/threadErrors_worker3.js8
-rw-r--r--dom/workers/test/threadErrors_worker4.js8
-rw-r--r--dom/workers/test/threadTimeouts_worker.js44
-rw-r--r--dom/workers/test/throwingOnerror_worker.js15
-rw-r--r--dom/workers/test/timeoutTracing_worker.js15
-rw-r--r--dom/workers/test/transferable_worker.js40
-rw-r--r--dom/workers/test/window_suspended.html71
-rw-r--r--dom/workers/test/worker_bug1278777.js9
-rw-r--r--dom/workers/test/worker_bug1301094.js11
-rw-r--r--dom/workers/test/worker_bug1824498.mjs4
-rw-r--r--dom/workers/test/worker_consoleAndBlobs.js8
-rw-r--r--dom/workers/test/worker_driver.js84
-rw-r--r--dom/workers/test/worker_dynamicImport.mjs2
-rw-r--r--dom/workers/test/worker_referrer.js9
-rw-r--r--dom/workers/test/worker_setTimeoutWith0.js3
-rw-r--r--dom/workers/test/worker_shutdownCheck.js1
-rw-r--r--dom/workers/test/worker_suspended.js39
-rw-r--r--dom/workers/test/worker_wrapper.js79
-rw-r--r--dom/workers/test/xpcshell/data/chrome.manifest1
-rw-r--r--dom/workers/test/xpcshell/data/worker.js6
-rw-r--r--dom/workers/test/xpcshell/data/worker_fileReader.js7
-rw-r--r--dom/workers/test/xpcshell/test_ext_redirects_sw_scripts.js415
-rw-r--r--dom/workers/test/xpcshell/test_fileReader.js33
-rw-r--r--dom/workers/test/xpcshell/test_remoteworker_launch_new_process.js94
-rw-r--r--dom/workers/test/xpcshell/test_workers.js37
-rw-r--r--dom/workers/test/xpcshell/test_workers_clone_error.js42
-rw-r--r--dom/workers/test/xpcshell/xpcshell.ini28
434 files changed, 46721 insertions, 0 deletions
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> 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<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
+ Unused << xpc->DebugDumpJSStack(true, true, false);
+ }
+
+ JSContext* cx = aGlobal.Context();
+
+ RefPtr<WorkerPrivate> 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<nsIGlobalObject> globalObject =
+ do_QueryInterface(aGlobal.GetAsSupports());
+
+ RefPtr<ChromeWorker> 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<WorkerPrivate> aWorkerPrivate)
+ : Worker(aGlobalObject, std::move(aWorkerPrivate)) {}
+
+ChromeWorker::~ChromeWorker() = default;
+
+JSObject* ChromeWorker::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ JS::Rooted<JSObject*> 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<ChromeWorker> 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<JSObject*> aGivenProto) override;
+
+ private:
+ ChromeWorker(nsIGlobalObject* aGlobalObject,
+ already_AddRefed<WorkerPrivate> 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<char*>(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<JSObject*> aGlobal) {
+ // Currently ctypes is the only special property given to ChromeWorkers.
+#ifdef BUILD_CTYPES
+ {
+ JS::Rooted<JS::Value> 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<JSObject*> 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<JSExecutionManager> sSABSerializationManager;
+
+void JSExecutionManager::Initialize() {
+ if (StaticPrefs::dom_workers_serialized_sab_access()) {
+ sSABSerializationManager = MakeRefPtr<JSExecutionManager>(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 <stdint.h>
+#include <deque>
+#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<WorkerPrivate*> 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<JSExecutionManager> mExecutionGrantingManager;
+ // The manager we had permission from before, and where permission should be
+ // re-requested upon destruction.
+ RefPtr<JSExecutionManager> 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<JSExecutionManager> 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 <stdint.h>
+
+#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<uint32_t> value;
+
+ // For the IndexOf call below.
+ bool operator==(JSGCParamKey k) const { return key == k; }
+ };
+
+ JS::RealmOptions chromeRealmOptions;
+ JS::RealmOptions contentRealmOptions;
+ CopyableTArray<JSGCSetting> 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<uint32_t> 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<nsIGlobalObject> 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<JSObject*> 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<JS::Value> messageData(aCx);
+ IgnoredErrorResult rv;
+
+ UniquePtr<AbstractTimelineMarker> start;
+ UniquePtr<AbstractTimelineMarker> end;
+ bool isTimelineRecording = !TimelineConsumers::IsEmpty();
+
+ if (isTimelineRecording) {
+ start = MakeUnique<WorkerTimelineMarker>(
+ 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<WorkerTimelineMarker>(
+ 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<OwningNonNull<MessagePort>> ports;
+ if (!TakeTransferredPortsAsSequence(ports)) {
+ DispatchError(aCx, aTarget);
+ return false;
+ }
+
+ RefPtr<MessageEvent> 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<MessageEventInit> init(aCx);
+ init.mBubbles = false;
+ init.mCancelable = false;
+
+ RefPtr<Event> 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 <typename T, int TCount>
+struct StorageWithTArray {
+ typedef AutoTArray<T, TCount> 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 <typename T, int TCount = 256, class LockingPolicy = NoLocking,
+ class StoragePolicy =
+ StorageWithTArray<T, TCount % 2 ? TCount / 2 + 1 : TCount / 2> >
+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<JSObject*> aGlobal) {
+ // Init Web IDL bindings
+ if (!RegisterWorkerBindings(aCx, aGlobal)) {
+ return false;
+ }
+
+ if (IsChromeWorker()) {
+ if (!DefineChromeWorkerFunctions(aCx, aGlobal)) {
+ return false;
+ }
+
+ RefPtr<OSFileConstantsService> service =
+ OSFileConstantsService::GetOrCreate();
+ if (!service->DefineOSFileConstants(aCx, aGlobal)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool WorkerPrivate::RegisterDebuggerBindings(JSContext* aCx,
+ JS::Handle<JSObject*> 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 <algorithm>
+#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<RuntimeService*> gRuntimeService(nullptr);
+
+// Only true during the call to Init.
+bool gRuntimeServiceDuringInit = false;
+
+class LiteralRebindingCString : public nsDependentCString {
+ public:
+ template <int N>
+ void RebindLiteral(const char (&aStr)[N]) {
+ Rebind(aStr, N - 1);
+ }
+};
+
+template <typename T>
+struct PrefTraits;
+
+template <>
+struct PrefTraits<bool> {
+ 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<int32_t> {
+ 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 <typename T>
+T GetPref(const char* aFullPref, const T aDefault, bool* aPresent = nullptr) {
+ AssertIsOnMainThread();
+
+ using PrefHelper = PrefTraits<T>;
+
+ 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<nsIXULRuntime> 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<int32_t>(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL, -1);
+ if (gczeal < 0) {
+ gczeal = 0;
+ }
+
+ int32_t frequency =
+ GetPref<int32_t>(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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t> 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<JSString*> 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<LogViolationDetailsRunnable> 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<WorkerPrivate*>(aClosure);
+
+ // Dispatch is expected to fail during shutdown for the reasons outlined in
+ // the JSDispatchableRunnable comment above.
+ RefPtr<JSDispatchableRunnable> r =
+ new JSDispatchableRunnable(workerPrivate, aDispatchable);
+ return r->Dispatch();
+}
+
+static bool ConsumeStream(JSContext* aCx, JS::Handle<JSObject*> 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<JSObject*> obj) {
+ MOZ_ASSERT(cx);
+ MOZ_ASSERT(obj);
+ MOZ_ASSERT(mozilla::dom::IsDOMObject(obj));
+
+ return mozilla::dom::TryPreserveWrapper(obj);
+}
+
+static bool IsWorkerDebuggerGlobalOrSandbox(JS::Handle<JSObject*> aGlobal) {
+ return IsWorkerDebuggerGlobal(aGlobal) || IsWorkerDebuggerSandbox(aGlobal);
+}
+
+JSObject* Wrap(JSContext* cx, JS::Handle<JSObject*> existing,
+ JS::Handle<JSObject*> obj) {
+ JS::Rooted<JSObject*> targetGlobal(cx, JS::CurrentGlobalOrNull(cx));
+
+ // Note: the JS engine unwraps CCWs before calling this callback.
+ JS::Rooted<JSObject*> 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<MicroTaskRunnable> aRunnable) override {
+ RefPtr<MicroTaskRunnable> runnable(aRunnable);
+
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(runnable);
+
+ std::deque<RefPtr<MicroTaskRunnable>>* microTaskQueue = nullptr;
+
+ JSContext* cx = Context();
+ NS_ASSERTION(cx, "This should never be null!");
+
+ JS::Rooted<JSObject*> 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<WorkerThread> mThread;
+ JSRuntime* mParentRuntime;
+
+ class FinishedRunnable final : public Runnable {
+ SafeRefPtr<WorkerThread> mThread;
+
+ public:
+ explicit FinishedRunnable(SafeRefPtr<WorkerThread> 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<WorkerThread> 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<nsString> 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<JSSettings> 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<nsIURI> 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<WorkerDomainInfo>();
+ 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<WorkerThread> 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<nsIRunnable> 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<JSSettings>();
+ 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<nsIStreamTransportService> sts =
+ do_GetService(kStreamTransportServiceCID, &rv);
+ NS_ENSURE_TRUE(sts, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIObserverService> 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 =
+ 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<nsIObserverService> 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<WorkerPrivate*, 100> 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<false> mHasMsg;
+};
+
+struct ActiveWorkerStats {
+ template <uint32_t ActiveWorkerStats::*Category>
+ void Update(const nsTArray<WorkerPrivate*>& aWorkers) {
+ for (const auto worker : aWorkers) {
+ RefPtr<DumpCrashInfoRunnable> 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<nsIObserverService> obs = services::GetObserverService();
+ NS_WARNING_ASSERTION(obs, "Failed to get observer service?!");
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ AutoTArray<WorkerPrivate*, 100> 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<nsITimer> timer;
+ RefPtr<RuntimeService> 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<WorkerPrivate*>& 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<WorkerPrivate*> RuntimeService::GetWorkersForWindow(
+ const nsPIDOMWindowInner& aWindow) const {
+ AssertIsOnMainThread();
+
+ nsTArray<WorkerPrivate*> result;
+ if (nsTArray<WorkerPrivate*>* 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 <typename Func>
+void RuntimeService::BroadcastAllWorkers(const Func& aFunc) {
+ AssertIsOnMainThread();
+
+ AutoTArray<WorkerPrivate*, 100> 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<nsString>& aLanguages) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mNavigatorProperties.mLanguages = aLanguages.Clone();
+ BroadcastAllWorkers(
+ [&aLanguages](auto& worker) { worker.UpdateLanguages(aLanguages); });
+}
+
+void RuntimeService::UpdateAllWorkerMemoryParameter(JSGCParamKey aKey,
+ Maybe<uint32_t> 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<uint32_t> 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<DumpCrashInfoRunnable> 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<WorkerJSContext>(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<nsIEventTarget> mainTarget = GetMainThreadSerialEventTarget();
+ MOZ_ASSERT(mainTarget);
+
+ RefPtr<FinishedRunnable> 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<WorkerThread> 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<WorkerPrivate*> mActiveWorkers;
+ nsTArray<WorkerPrivate*> mActiveServiceWorkers;
+ nsTArray<WorkerPrivate*> 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<nsCStringHashKey, WorkerDomainInfo> mDomainMap
+ MOZ_GUARDED_BY(mMutex);
+
+ // *Not* protected by mMutex.
+ nsClassHashtable<nsPtrHashKey<const nsPIDOMWindowInner>,
+ nsTArray<WorkerPrivate*> >
+ mWindowMap;
+
+ static UniquePtr<workerinternals::JSSettings> sDefaultJSSettings;
+
+ public:
+ struct NavigatorProperties {
+ nsString mAppName;
+ nsString mAppNameOverridden;
+ nsString mAppVersion;
+ nsString mAppVersionOverridden;
+ nsString mPlatform;
+ nsString mPlatformOverridden;
+ CopyableTArray<nsString> 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<nsString>& aLanguages);
+
+ static void SetDefaultJSGCSettings(JSGCParamKey aKey,
+ Maybe<uint32_t> aValue) {
+ AssertIsOnMainThread();
+ sDefaultJSSettings->ApplyGCSetting(aKey, aValue);
+ }
+
+ void UpdateAllWorkerMemoryParameter(JSGCParamKey aKey,
+ Maybe<uint32_t> 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<WorkerPrivate*>& aWorkers)
+ MOZ_REQUIRES(mMutex);
+
+ nsTArray<WorkerPrivate*> GetWorkersForWindow(
+ const nsPIDOMWindowInner& aWindow) const;
+
+ bool ScheduleWorker(WorkerPrivate& aWorkerPrivate);
+
+ template <typename Func>
+ 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 <algorithm>
+#include <type_traits>
+
+#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<ClientInfo>& aClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController, bool aIsMainScript,
+ WorkerScriptType aWorkerScriptType, nsContentPolicyType aContentPolicyType,
+ nsLoadFlags aLoadFlags, uint32_t aSecFlags,
+ nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo,
+ nsIChannel** aChannel) {
+ AssertIsOnMainThread();
+
+ nsresult rv;
+ nsCOMPtr<nsIURI> 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<nsIChannel> 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> performanceStorage;
+ nsCOMPtr<nsICSPEventListener> 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<nsILoadInfo> loadInfo = channel->LoadInfo();
+ rv = loadInfo->SetCspEventListener(cspEventListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ if (aReferrerInfo) {
+ nsCOMPtr<nsIHttpChannel> 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<SerializedStackHolder> aOriginStack,
+ const nsTArray<nsString>& 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<nsISerialEventTarget> syncLoopTarget =
+ syncLoop.GetSerialEventTarget();
+ if (!syncLoopTarget) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ RefPtr<loader::WorkerScriptLoader> 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<JS::loader::ScriptLoadRequest> 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<nsIURI> baseURI = mWorkerPrivate->GetBaseURI();
+ MOZ_ASSERT(baseURI);
+
+ // May be null.
+ nsCOMPtr<Document> parentDoc = mWorkerPrivate->GetDocument();
+
+ mLoadInfo.mLoadGroup = mWorkerPrivate->GetLoadGroup();
+ mLoadInfo.mCookieJarSettings = mWorkerPrivate->CookieJarSettings();
+
+ // Nested workers use default uri encoding.
+ nsCOMPtr<nsIURI> url;
+ mResult = ConstructURI(mScriptURL, baseURI, nullptr, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(mResult, true);
+
+ Maybe<ClientInfo> clientInfo;
+ clientInfo.emplace(mClientInfo);
+
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ ReferrerInfo::CreateForFetch(mLoadInfo.mLoadingPrincipal, nullptr);
+ mLoadInfo.mReferrerInfo =
+ static_cast<ReferrerInfo*>(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<WorkerScriptLoader> mScriptLoader;
+ const Span<RefPtr<ThreadSafeRequestHandle>> mLoadedRequests;
+
+ public:
+ ScriptExecutorRunnable(WorkerScriptLoader* aScriptLoader,
+ WorkerPrivate* aWorkerPrivate,
+ nsISerialEventTarget* aSyncLoopTarget,
+ Span<RefPtr<ThreadSafeRequestHandle>> 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 <typename Unit>
+static bool EvaluateSourceBuffer(JSContext* aCx,
+ const JS::CompileOptions& aOptions,
+ JS::loader::ClassicScript* aClassicScript,
+ JS::SourceText<Unit>& aSourceBuffer) {
+ static_assert(std::is_same<Unit, char16_t>::value ||
+ std::is_same<Unit, Utf8Unit>::value,
+ "inferred units must be UTF-8 or UTF-16");
+
+ JS::Rooted<JSScript*> script(aCx, JS::Compile(aCx, aOptions, aSourceBuffer));
+
+ if (!script) {
+ return false;
+ }
+
+ if (aClassicScript) {
+ aClassicScript->AssociateWithScript(script);
+ }
+
+ JS::Rooted<JS::Value> unused(aCx);
+ return JS_ExecuteScript(aCx, script, &unused);
+}
+
+WorkerScriptLoader::WorkerScriptLoader(
+ WorkerPrivate* aWorkerPrivate,
+ UniquePtr<SerializedStackHolder> 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<StrongWorkerRef> 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<WorkerModuleLoader> 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<nsString>& 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<ScriptLoadRequest> request =
+ CreateScriptLoadRequest(scriptURL, aDocumentEncoding, aIsMainScript);
+ if (!request) {
+ return false;
+ }
+ mLoadingRequests.AppendElement(request);
+ }
+
+ return true;
+}
+
+nsTArray<RefPtr<ThreadSafeRequestHandle>> WorkerScriptLoader::GetLoadingList() {
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+ nsTArray<RefPtr<ThreadSafeRequestHandle>> list;
+ for (ScriptLoadRequest* req = mLoadingRequests.getFirst(); req;
+ req = req->getNext()) {
+ RefPtr<ThreadSafeRequestHandle> 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<ScriptLoadRequest> WorkerScriptLoader::CreateScriptLoadRequest(
+ const nsString& aScriptURL, const mozilla::Encoding* aDocumentEncoding,
+ bool aIsMainScript) {
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+ WorkerLoadContext::Kind kind =
+ WorkerLoadContext::GetKind(aIsMainScript, IsDebuggerScript());
+
+ Maybe<ClientInfo> 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<WorkerLoadContext> 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<nsIURI> baseURI = aIsMainScript ? GetInitialBaseURI() : GetBaseURI();
+ nsCOMPtr<nsIURI> 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<ScriptFetchOptions> fetchOptions =
+ new ScriptFetchOptions(CORSMode::CORS_NONE, referrerPolicy, nullptr);
+
+ RefPtr<ScriptLoadRequest> 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<WorkerModuleLoader::ModuleLoaderBase> 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<nsIURI> 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<RefPtr<ThreadSafeRequestHandle>> scriptLoadList;
+ RefPtr<ThreadSafeRequestHandle> handle =
+ new ThreadSafeRequestHandle(aRequest, mSyncLoopTarget.get());
+ scriptLoadList.AppendElement(handle.forget());
+
+ RefPtr<ScriptLoaderRunnable> runnable =
+ new ScriptLoaderRunnable(this, std::move(scriptLoadList));
+
+ RefPtr<StrongWorkerRef> 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<RefPtr<ThreadSafeRequestHandle>> scriptLoadList = GetLoadingList();
+
+ RefPtr<ScriptLoaderRunnable> runnable =
+ new ScriptLoaderRunnable(this, std::move(scriptLoadList));
+
+ RefPtr<StrongWorkerRef> 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<nsIGlobalObject*>(
+ 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<ScriptLoadRequest> 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<JSObject*> 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<ScriptLoadRequest> 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<nsILoadGroup> loadGroup = mWorkerRef->Private()->GetLoadGroup();
+ MOZ_DIAGNOSTIC_ASSERT(principal);
+
+ NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadGroup, principal),
+ NS_ERROR_FAILURE);
+
+ // May be null.
+ nsCOMPtr<Document> parentDoc = mWorkerRef->Private()->GetDocument();
+
+ nsCOMPtr<nsIChannel> channel;
+ if (loadContext->IsTopLevel()) {
+ // May be null.
+ channel = mWorkerRef->Private()->ForgetWorkerChannel();
+ }
+
+ nsCOMPtr<nsIIOService> 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<nsPIDOMWindowInner> window = topWorkerPrivate->GetWindow();
+ if (window) {
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ if (docShell) {
+ nsresult rv = docShell->GetDefaultLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ if (!channel) {
+ nsCOMPtr<nsIReferrerInfo> 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*>(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<NetworkLoadHandler> listener =
+ new NetworkLoadHandler(this, aRequestHandle);
+
+ RefPtr<ScriptResponseHeaderProcessor> 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<ScriptResponseHeaderProcessor>(
+ mWorkerRef->Private(),
+ loadContext->IsTopLevel() && !IsDynamicImport(request),
+ GetContentPolicyType(request) ==
+ nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS);
+ }
+
+ nsCOMPtr<nsIStreamLoader> 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<nsILoadInfo> loadInfo = channel->LoadInfo();
+ loadInfo->SetIsThirdPartyContextToTopWindow(
+ mWorkerRef->Private()->IsThirdPartyContextToTopWindow());
+
+ Maybe<ClientInfo> clientInfo;
+ clientInfo.emplace(loadContext->mClientInfo.ref());
+ rv = AddClientChannelHelper(channel, std::move(clientInfo),
+ Maybe<ClientInfo>(),
+ 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<nsILoadInfo> 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<nsIOutputStream> 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<nsIStreamListenerTee> 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<JSScript*> 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<JSScript*> 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<JS::loader::ClassicScript> 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<nsIURI> 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<JS::SourceText<Utf8Unit>>())
+ : EvaluateSourceBuffer(aCx, options, classicScript,
+ maybeSource.ref<JS::SourceText<char16_t>>());
+
+ 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<JS::Value> 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<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
+ xpcReport->Init(report.report(), report.toStringResult().c_str(),
+ aWorkerPrivate->IsChromeWorker(), aWorkerPrivate->WindowID());
+
+ RefPtr<AsyncErrorReporter> 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<RefPtr<ThreadSafeRequestHandle>> 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<RefPtr<CacheLoadHandler>>(
+ 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<std::pair<Iterator, Iterator>> {
+ // firstItToExecute is the first loadInfo where mExecutionScheduled is
+ // unset.
+ auto firstItToExecute = std::find_if(
+ begin, end, [](const RefPtr<ThreadSafeRequestHandle>& 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<ThreadSafeRequestHandle>& 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<ScriptExecutorRunnable> runnable = new ScriptExecutorRunnable(
+ mScriptLoader, mWorkerRef->Private(), mScriptLoader->mSyncLoopTarget,
+ Span<RefPtr<ThreadSafeRequestHandle>>{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<RefPtr<ThreadSafeRequestHandle>> 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<ScriptLoadRequest> 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<ScriptLoadRequest> 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<ClientInfo>& aClientInfo,
+ nsContentPolicyType aMainScriptContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo,
+ nsIChannel** aChannel) {
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIIOService> 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<ServiceWorkerDescriptor>(), 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<ChannelGetterRunnable> 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<uint32_t>(aLoadResult)));
+ return;
+ }
+}
+
+void LoadMainScript(WorkerPrivate* aWorkerPrivate,
+ UniquePtr<SerializedStackHolder> aOriginStack,
+ const nsAString& aScriptURL,
+ WorkerScriptType aWorkerScriptType, ErrorResult& aRv,
+ const mozilla::Encoding* aDocumentEncoding) {
+ nsTArray<nsString> scriptURLs;
+
+ scriptURLs.AppendElement(aScriptURL);
+
+ LoadAllScripts(aWorkerPrivate, std::move(aOriginStack), scriptURLs, true,
+ aWorkerScriptType, aRv, aDocumentEncoding);
+}
+
+void Load(WorkerPrivate* aWorkerPrivate,
+ UniquePtr<SerializedStackHolder> aOriginStack,
+ const nsTArray<nsString>& 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<ThreadSafeWorkerRef> mWorkerRef;
+ UniquePtr<SerializedStackHolder> mOriginStack;
+ nsString mOriginStackJSON;
+ nsCOMPtr<nsISerialEventTarget> mSyncLoopTarget;
+ ScriptLoadRequestList mLoadingRequests;
+ ScriptLoadRequestList mLoadedRequests;
+ Maybe<ServiceWorkerDescriptor> 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<SerializedStackHolder> aOriginStack,
+ nsISerialEventTarget* aSyncLoopTarget,
+ WorkerScriptType aWorkerScriptType, ErrorResult& aRv);
+
+ bool CreateScriptRequests(const nsTArray<nsString>& aScriptURLs,
+ const mozilla::Encoding* aDocumentEncoding,
+ bool aIsMainScript);
+
+ ScriptLoadRequest* GetMainScript();
+
+ already_AddRefed<ScriptLoadRequest> 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<ServiceWorkerDescriptor>& aDescriptor) {
+ mController = aDescriptor;
+ }
+
+ Maybe<ServiceWorkerDescriptor>& 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<RefPtr<ThreadSafeRequestHandle>> 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<JSScript*> 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<nsString>& aParams = nsTArray<nsString>()) 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<WorkerScriptLoader> mScriptLoader;
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+ nsTArrayView<RefPtr<ThreadSafeRequestHandle>> mLoadingRequests;
+ Maybe<nsresult> mCancelMainThread;
+ RefPtr<CacheCreator> mCacheCreator;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit ScriptLoaderRunnable(
+ WorkerScriptLoader* aScriptLoader,
+ nsTArray<RefPtr<ThreadSafeRequestHandle>> 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<ClientInfo>& 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<SerializedStackHolder> aOriginStack,
+ const nsAString& aScriptURL,
+ WorkerScriptType aWorkerScriptType, ErrorResult& aRv,
+ const mozilla::Encoding* aDocumentEncoding);
+
+void Load(WorkerPrivate* aWorkerPrivate,
+ UniquePtr<SerializedStackHolder> aOriginStack,
+ const nsTArray<nsString>& 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> Worker::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aScriptURL,
+ const WorkerOptions& aOptions,
+ ErrorResult& aRv) {
+ JSContext* cx = aGlobal.Context();
+
+ nsCOMPtr<nsIGlobalObject> 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 = 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> worker = new Worker(globalObject, workerPrivate.forget());
+ return worker.forget();
+}
+
+Worker::Worker(nsIGlobalObject* aGlobalObject,
+ already_AddRefed<WorkerPrivate> aWorkerPrivate)
+ : DOMEventTargetHelper(aGlobalObject),
+ mWorkerPrivate(std::move(aWorkerPrivate)) {
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->SetParentEventTargetRef(this);
+}
+
+Worker::~Worker() { Terminate(); }
+
+JSObject* Worker::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ JS::Rooted<JSObject*> 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<JS::Value> aMessage,
+ const Sequence<JSObject*>& aTransferable,
+ ErrorResult& aRv) {
+ NS_ASSERT_OWNINGTHREAD(Worker);
+
+ if (!mWorkerPrivate || mWorkerPrivate->ParentStatusProtected() > Running) {
+ return;
+ }
+ RefPtr<WorkerPrivate> workerPrivate = mWorkerPrivate;
+ Unused << workerPrivate;
+
+ JS::Rooted<JS::Value> 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<MessageEventRunnable> runnable = new MessageEventRunnable(
+ mWorkerPrivate, WorkerRunnable::WorkerThreadModifyBusyCount);
+
+ UniquePtr<AbstractTimelineMarker> start;
+ UniquePtr<AbstractTimelineMarker> end;
+ bool isTimelineRecording = !TimelineConsumers::IsEmpty();
+
+ if (isTimelineRecording) {
+ start = MakeUnique<WorkerTimelineMarker>(
+ 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<WorkerTimelineMarker>(
+ 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<JS::Value> 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<Worker> Constructor(const GlobalObject& aGlobal,
+ const nsAString& aScriptURL,
+ const WorkerOptions& aOptions,
+ ErrorResult& aRv);
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ Maybe<EventCallbackDebuggerNotificationType> GetDebuggerNotificationType()
+ const override {
+ return Some(EventCallbackDebuggerNotificationType::Worker);
+ }
+
+ void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Sequence<JSObject*>& aTransferable, ErrorResult& aRv);
+
+ void PostMessage(JSContext* aCx, JS::Handle<JS::Value> 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<WorkerPrivate> aWorkerPrivate);
+ ~Worker();
+
+ RefPtr<WorkerPrivate> 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<mozilla::dom::Event> 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> WorkerCSPEventListener::Create(
+ WorkerPrivate* aWorkerPrivate) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<WorkerCSPEventListener> 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<WorkerCSPEventRunnable> runnable =
+ new WorkerCSPEventRunnable(workerPrivate, aJSON);
+ runnable->Dispatch();
+
+ return NS_OK;
+ }
+
+ SecurityPolicyViolationEventInit violationEventInit;
+ if (NS_WARN_IF(!violationEventInit.Init(aJSON))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<mozilla::dom::Event> 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<WorkerCSPEventListener> Create(
+ WorkerPrivate* aWorkerPrivate);
+
+ private:
+ WorkerCSPEventListener();
+ ~WorkerCSPEventListener() = default;
+
+ Mutex mMutex;
+
+ // Protected by mutex.
+ RefPtr<WeakWorkerRef> 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<nsIWorkerChannelLoadInfo> 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 <processthreadsapi.h> // for GetCurrentProcessId()
+#else
+# include <unistd.h> // 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<JSString*> message(
+ aCx, JS_NewUCStringCopyN(aCx, mMessage.get(), mMessage.Length()));
+ if (!message) {
+ return false;
+ }
+ JS::Rooted<JS::Value> data(aCx, JS::StringValue(message));
+
+ RefPtr<MessageEvent> event =
+ new MessageEvent(globalScope, nullptr, nullptr);
+ event->InitMessageEvent(nullptr, u"message"_ns, CanBubble::eNo,
+ Cancelable::eYes, data, u""_ns, u""_ns, nullptr,
+ Sequence<OwningNonNull<MessagePort>>());
+ 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<JSObject*> 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<nsIWorkerDebugger> 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<nsPIDOMWindowInner> window = DedicatedWorkerWindow();
+ window.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetWindowIDs(nsTArray<uint64_t>& 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<nsPIDOMWindowInner> 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<nsIPrincipal> 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<CompileDebuggerScriptRunnable> 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<DebuggerMessageEventRunnable> 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<PostDebuggerMessageRunnable> 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<ReportDebuggerErrorRunnable> 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<bool> 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<PerformanceInfoPromise> WorkerDebugger::ReportPerformanceInfo() {
+ AssertIsOnMainThread();
+ RefPtr<WorkerDebugger> 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<BrowsingContext> top = context->Top();
+ if (top && top->GetCurrentWindowContext()) {
+ windowID = top->GetCurrentWindowContext()->OuterWindowId();
+ isTopLevel = context->IsTop();
+ }
+ }
+ }
+
+ // getting the worker URL
+ RefPtr<nsIURI> 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<CategoryDispatch> 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<WorkerPrivate::JSMemoryUsagePromise> 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<WorkerPrivate> 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<WorkerPrivate> mWorkerPrivate;
+ bool mIsInitialized;
+ nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> 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<PerformanceInfoPromise> ReportPerformanceInfo();
+
+ private:
+ virtual ~WorkerDebugger();
+
+ void PostMessageToDebuggerOnMainThread(const nsAString& aMessage);
+
+ void ReportErrorToDebuggerOnMainThread(const nsAString& aFilename,
+ uint32_t aLineno,
+ const nsAString& aMessage);
+
+ nsCOMPtr<nsPIDOMWindowInner> 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<WorkerDebuggerManager> gWorkerDebuggerManager;
+
+} /* anonymous namespace */
+
+class WorkerDebuggerEnumerator final : public nsSimpleEnumerator {
+ nsTArray<RefPtr<WorkerDebugger>> mDebuggers;
+ uint32_t mIndex;
+
+ public:
+ explicit WorkerDebuggerEnumerator(
+ const nsTArray<RefPtr<WorkerDebugger>>& 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> WorkerDebuggerManager::GetInstance() {
+ RefPtr<WorkerDebuggerManager> 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<WorkerDebuggerEnumerator> 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<nsIObserverService> 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<nsIRunnable> 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<nsIRunnable> 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<WorkerDebugger> 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<WorkerDebugger> 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<nsCOMPtr<nsIWorkerDebuggerManagerListener>>
+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<nsCOMPtr<nsIWorkerDebuggerManagerListener>> mListeners;
+
+ // Only touched on the main thread.
+ nsTArray<RefPtr<WorkerDebugger>> mDebuggers;
+
+ public:
+ static already_AddRefed<WorkerDebuggerManager> 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<nsCOMPtr<nsIWorkerDebuggerManagerListener>> 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> WorkerDocumentListener::Create(
+ WorkerPrivate* aWorkerPrivate) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ auto listener = MakeRefPtr<WorkerDocumentListener>();
+
+ RefPtr<StrongWorkerRef> 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<VisibleRunnable>(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<WorkerDocumentListener> Create(WorkerPrivate* aWorkerPrivate);
+
+ private:
+ ~WorkerDocumentListener();
+
+ Mutex mMutex MOZ_UNANNOTATED; // protects mWorkerRef
+ RefPtr<ThreadSafeWorkerRef> 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 <stdio.h>
+#include <algorithm>
+#include <utility>
+#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<WorkerErrorReport> mReport;
+
+ public:
+ ReportErrorRunnable(WorkerPrivate* aWorkerPrivate,
+ UniquePtr<WorkerErrorReport> 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<RemoteWorkerChild> 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<ReportGenericErrorRunnable> 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<RemoteWorkerChild> actor(
+ aWorkerPrivate->GetRemoteWorkerController());
+
+ Unused << NS_WARN_IF(!actor);
+
+ if (actor) {
+ actor->ErrorPropagationOnMainThread(nullptr, false);
+ }
+
+ return true;
+ }
+
+ if (!aWorkerPrivate->IsAcceptingEvents()) {
+ return true;
+ }
+
+ RefPtr<mozilla::dom::EventTarget> parentEventTarget =
+ aWorkerPrivate->ParentEventTargetRef();
+ RefPtr<Event> 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<WorkerErrorReport> aReport,
+ uint64_t aInnerWindowId, JS::Handle<JS::Value> 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<ErrorEventInit> 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<ErrorEvent> 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<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+ NS_ASSERTION(global, "This should never be null!");
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+
+ if (aWorkerPrivate) {
+ RefPtr<WorkerGlobalScope> 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<ErrorEvent> 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<ReportErrorRunnable> 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<JSObject*> stack(aCx, aReport.ReadStack(aCx));
+ JS::Rooted<JSObject*> 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<JSObject*> aStack,
+ JS::Handle<JSObject*> aStackGlobal) {
+ AssertIsOnMainThread();
+
+ RefPtr<nsScriptErrorBase> 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<nsIConsoleService> 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<WorkerErrorNote> 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<WorkerErrorReport> aReport,
+ uint64_t aInnerWindowId,
+ JS::Handle<JS::Value> 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<JSObject*> aStack = nullptr,
+ JS::Handle<JSObject*> 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<nsIRunnable> mInner;
+
+ ~WrappedControlRunnable() = default;
+
+ public:
+ WrappedControlRunnable(WorkerPrivate* aWorkerPrivate,
+ nsCOMPtr<nsIRunnable>&& 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<nsICancelableRunnable> 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<nsIRunnable> runnable(aRunnable);
+ return Dispatch(runnable.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+WorkerEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aFlags) {
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+
+ MutexAutoLock lock(mMutex);
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mBehavior == Behavior::Hybrid) {
+ RefPtr<WorkerRunnable> r =
+ mWorkerPrivate->MaybeWrapAsWorkerRunnable(runnable.forget());
+ if (r->Dispatch()) {
+ return NS_OK;
+ }
+
+ runnable = std::move(r);
+ }
+
+ RefPtr<WorkerControlRunnable> r =
+ new WrappedControlRunnable(mWorkerPrivate, std::move(runnable));
+ if (!r->Dispatch()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, 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<WorkerPrivate> 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<mozilla::dom::WorkerType>
+ : public ContiguousEnumSerializer<mozilla::dom::WorkerType,
+ mozilla::dom::WorkerType::Classic,
+ mozilla::dom::WorkerType::EndGuard_> {};
+
+} // 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<nsCOMPtr<nsISupports>> mDoomed;
+ nsCOMPtr<nsILoadGroup> mLoadGroupToCancel;
+
+ public:
+ MainThreadReleaseRunnable(nsTArray<nsCOMPtr<nsISupports>>&& aDoomed,
+ nsCOMPtr<nsILoadGroup>&& 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 <class T>
+struct ISupportsBaseInfo {
+ using ISupportsBase = T;
+};
+
+template <template <class> class SmartPtr, class T>
+inline void SwapToISupportsArray(SmartPtr<T>& aSrc,
+ nsTArray<nsCOMPtr<nsISupports>>& aDest) {
+ nsCOMPtr<nsISupports>* dest = aDest.AppendElement();
+
+ T* raw = nullptr;
+ aSrc.swap(raw);
+
+ nsISupports* rawSupports =
+ static_cast<typename ISupportsBaseInfo<T>::ISupportsBase*>(raw);
+ dest->swap(rawSupports);
+}
+
+} // namespace
+
+WorkerLoadInfoData::WorkerLoadInfoData()
+ : mLoadFlags(nsIRequest::LOAD_NORMAL),
+ mWindowID(UINT64_MAX),
+ mAssociatedBrowsingContextID(0),
+ mReferrerInfo(new ReferrerInfo(nullptr)),
+ mFromWindow(false),
+ mEvalAllowed(false),
+ mReportEvalCSPViolations(false),
+ mWasmEvalAllowed(false),
+ mReportWasmEvalCSPViolations(false),
+ mXHRParamsAllowed(false),
+ mWatchedByDevTools(false),
+ mStorageAccess(StorageAccess::eDeny),
+ mUseRegularPrincipal(false),
+ mHasStorageAccessPermissionGranted(false),
+ mServiceWorkersTestingInWindow(false),
+ mShouldResistFingerprinting(false),
+ mIsThirdPartyContextToTopWindow(true),
+ mSecureContext(eNotSet) {}
+
+nsresult WorkerLoadInfo::SetPrincipalsAndCSPOnMainThread(
+ nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal,
+ nsILoadGroup* aLoadGroup, nsIContentSecurityPolicy* aCsp) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadGroup, aPrincipal));
+
+ mPrincipal = aPrincipal;
+ mPartitionedPrincipal = aPartitionedPrincipal;
+
+ mCSP = aCsp;
+
+ if (mCSP) {
+ mCSP->GetAllowsEval(&mReportEvalCSPViolations, &mEvalAllowed);
+ mCSP->GetAllowsWasmEval(&mReportWasmEvalCSPViolations, &mWasmEvalAllowed);
+ mCSPInfo = MakeUnique<CSPInfo>();
+ nsresult rv = CSPToCSPInfo(aCsp, mCSPInfo.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ mEvalAllowed = true;
+ mReportEvalCSPViolations = false;
+ mWasmEvalAllowed = true;
+ mReportWasmEvalCSPViolations = false;
+ }
+
+ mLoadGroup = aLoadGroup;
+
+ mPrincipalInfo = MakeUnique<PrincipalInfo>();
+ mPartitionedPrincipalInfo = MakeUnique<PrincipalInfo>();
+ StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(
+ aLoadGroup, mOriginAttributes);
+
+ nsresult rv = PrincipalToPrincipalInfo(aPrincipal, mPrincipalInfo.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aPrincipal->Equals(aPartitionedPrincipal)) {
+ *mPartitionedPrincipalInfo = *mPrincipalInfo;
+ } else {
+ mPartitionedPrincipalInfo = MakeUnique<PrincipalInfo>();
+ rv = PrincipalToPrincipalInfo(aPartitionedPrincipal,
+ mPartitionedPrincipalInfo.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+nsresult WorkerLoadInfo::GetPrincipalsAndLoadGroupFromChannel(
+ nsIChannel* aChannel, nsIPrincipal** aPrincipalOut,
+ nsIPrincipal** aPartitionedPrincipalOut, nsILoadGroup** aLoadGroupOut) {
+ AssertIsOnMainThread();
+ MOZ_DIAGNOSTIC_ASSERT(aChannel);
+ MOZ_DIAGNOSTIC_ASSERT(aPrincipalOut);
+ MOZ_DIAGNOSTIC_ASSERT(aPartitionedPrincipalOut);
+ MOZ_DIAGNOSTIC_ASSERT(aLoadGroupOut);
+
+ // Initial triggering principal should be set
+ NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_DOM_INVALID_STATE_ERR);
+
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ MOZ_DIAGNOSTIC_ASSERT(ssm);
+
+ nsCOMPtr<nsIPrincipal> channelPrincipal;
+ nsCOMPtr<nsIPrincipal> channelPartitionedPrincipal;
+ nsresult rv = ssm->GetChannelResultPrincipals(
+ aChannel, getter_AddRefs(channelPrincipal),
+ getter_AddRefs(channelPartitionedPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Every time we call GetChannelResultPrincipal() it will return a different
+ // null principal for a data URL. We don't want to change the worker's
+ // principal again, though. Instead just keep the original null principal we
+ // first got from the channel.
+ //
+ // Note, we don't do this by setting principalToInherit on the channel's
+ // load info because we don't yet have the first null principal when we
+ // create the channel.
+ if (mPrincipal && mPrincipal->GetIsNullPrincipal() &&
+ channelPrincipal->GetIsNullPrincipal()) {
+ channelPrincipal = mPrincipal;
+ channelPartitionedPrincipal = mPrincipal;
+ }
+
+ nsCOMPtr<nsILoadGroup> channelLoadGroup;
+ rv = aChannel->GetLoadGroup(getter_AddRefs(channelLoadGroup));
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(channelLoadGroup);
+
+ // If the loading principal is the system principal then the channel
+ // principal must also be the system principal (we do not allow chrome
+ // code to create workers with non-chrome scripts, and if we ever decide
+ // to change this we need to make sure we don't always set
+ // mPrincipalIsSystem to true in WorkerPrivate::GetLoadInfo()). Otherwise
+ // this channel principal must be same origin with the load principal (we
+ // check again here in case redirects changed the location of the script).
+ if (mLoadingPrincipal->IsSystemPrincipal()) {
+ if (!channelPrincipal->IsSystemPrincipal()) {
+ nsCOMPtr<nsIURI> finalURI;
+ rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(finalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // See if this is a resource URI. Since JSMs usually come from
+ // resource:// URIs we're currently considering all URIs with the
+ // URI_IS_UI_RESOURCE flag as valid for creating privileged workers.
+ bool isResource;
+ rv = NS_URIChainHasFlags(finalURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
+ &isResource);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isResource) {
+ // Assign the system principal to the resource:// worker only if it
+ // was loaded from code using the system principal.
+ channelPrincipal = mLoadingPrincipal;
+ channelPartitionedPrincipal = mLoadingPrincipal;
+ } else {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+ }
+
+ // The principal can change, but it should still match the original
+ // load group's browser element flag.
+ MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(channelLoadGroup, channelPrincipal));
+
+ channelPrincipal.forget(aPrincipalOut);
+ channelPartitionedPrincipal.forget(aPartitionedPrincipalOut);
+ channelLoadGroup.forget(aLoadGroupOut);
+
+ return NS_OK;
+}
+
+nsresult WorkerLoadInfo::SetPrincipalsAndCSPFromChannel(nsIChannel* aChannel) {
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsCOMPtr<nsIPrincipal> partitionedPrincipal;
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsresult rv = GetPrincipalsAndLoadGroupFromChannel(
+ aChannel, getter_AddRefs(principal), getter_AddRefs(partitionedPrincipal),
+ getter_AddRefs(loadGroup));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Workers themselves can have their own CSP - Workers of an opaque origin
+ // however inherit the CSP of the document that spawned the worker.
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ if (CSP_ShouldResponseInheritCSP(aChannel)) {
+ nsCOMPtr<nsILoadInfo> loadinfo = aChannel->LoadInfo();
+ csp = loadinfo->GetCsp();
+ }
+ return SetPrincipalsAndCSPOnMainThread(principal, partitionedPrincipal,
+ loadGroup, csp);
+}
+
+bool WorkerLoadInfo::FinalChannelPrincipalIsValid(nsIChannel* aChannel) {
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsCOMPtr<nsIPrincipal> partitionedPrincipal;
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsresult rv = GetPrincipalsAndLoadGroupFromChannel(
+ aChannel, getter_AddRefs(principal), getter_AddRefs(partitionedPrincipal),
+ getter_AddRefs(loadGroup));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Verify that the channel is still a null principal. We don't care
+ // if these are the exact same null principal object, though. From
+ // the worker's perspective its the same effect.
+ if (principal->GetIsNullPrincipal() && mPrincipal->GetIsNullPrincipal()) {
+ return true;
+ }
+
+ // Otherwise we require exact equality. Redirects can happen, but they
+ // are not allowed to change our principal.
+ if (principal->Equals(mPrincipal)) {
+ return true;
+ }
+
+ return false;
+}
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+bool WorkerLoadInfo::PrincipalIsValid() const {
+ return mPrincipal && mPrincipalInfo &&
+ mPrincipalInfo->type() != PrincipalInfo::T__None &&
+ mPrincipalInfo->type() <= PrincipalInfo::T__Last &&
+ mPartitionedPrincipal && mPartitionedPrincipalInfo &&
+ mPartitionedPrincipalInfo->type() != PrincipalInfo::T__None &&
+ mPartitionedPrincipalInfo->type() <= PrincipalInfo::T__Last;
+}
+
+bool WorkerLoadInfo::PrincipalURIMatchesScriptURL() {
+ AssertIsOnMainThread();
+
+ nsAutoCString scheme;
+ nsresult rv = mBaseURI->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // A system principal must either be a blob URL or a resource JSM.
+ if (mPrincipal->IsSystemPrincipal()) {
+ if (scheme == "blob"_ns) {
+ return true;
+ }
+
+ bool isResource = false;
+ nsresult rv = NS_URIChainHasFlags(
+ mBaseURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, &isResource);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return isResource;
+ }
+
+ // A null principal can occur for a data URL worker script or a blob URL
+ // worker script from a sandboxed iframe.
+ if (mPrincipal->GetIsNullPrincipal()) {
+ return scheme == "data"_ns || scheme == "blob"_ns;
+ }
+
+ // The principal for a blob: URL worker script does not have a matching URL.
+ // This is likely a bug in our referer setting logic, but exempt it for now.
+ // This is another reason we should fix bug 1340694 so that referer does not
+ // depend on the principal URI.
+ if (scheme == "blob"_ns) {
+ return true;
+ }
+
+ if (mPrincipal->IsSameOrigin(mBaseURI)) {
+ return true;
+ }
+
+ // If strict file origin policy is in effect, local files will always fail
+ // IsSameOrigin unless they are identical. Explicitly check file origin
+ // policy, in that case.
+
+ bool allowsRelaxedOriginPolicy = false;
+ rv = mPrincipal->AllowsRelaxStrictFileOriginPolicy(
+ mBaseURI, &allowsRelaxedOriginPolicy);
+
+ if (nsScriptSecurityManager::GetStrictFileOriginPolicy() &&
+ NS_URIIsLocalFile(mBaseURI) &&
+ (NS_SUCCEEDED(rv) && allowsRelaxedOriginPolicy)) {
+ return true;
+ }
+
+ return false;
+}
+#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
+
+bool WorkerLoadInfo::ProxyReleaseMainThreadObjects(
+ WorkerPrivate* aWorkerPrivate) {
+ nsCOMPtr<nsILoadGroup> nullLoadGroup;
+ return ProxyReleaseMainThreadObjects(aWorkerPrivate,
+ std::move(nullLoadGroup));
+}
+
+bool WorkerLoadInfo::ProxyReleaseMainThreadObjects(
+ WorkerPrivate* aWorkerPrivate,
+ nsCOMPtr<nsILoadGroup>&& aLoadGroupToCancel) {
+ static const uint32_t kDoomedCount = 11;
+ nsTArray<nsCOMPtr<nsISupports>> doomed(kDoomedCount);
+
+ SwapToISupportsArray(mWindow, doomed);
+ SwapToISupportsArray(mScriptContext, doomed);
+ SwapToISupportsArray(mBaseURI, doomed);
+ SwapToISupportsArray(mResolvedScriptURI, doomed);
+ SwapToISupportsArray(mPrincipal, doomed);
+ SwapToISupportsArray(mPartitionedPrincipal, doomed);
+ SwapToISupportsArray(mLoadingPrincipal, doomed);
+ SwapToISupportsArray(mChannel, doomed);
+ SwapToISupportsArray(mCSP, doomed);
+ SwapToISupportsArray(mLoadGroup, doomed);
+ SwapToISupportsArray(mInterfaceRequestor, doomed);
+ // Before adding anything here update kDoomedCount above!
+
+ MOZ_ASSERT(doomed.Length() == kDoomedCount);
+
+ RefPtr<MainThreadReleaseRunnable> runnable = new MainThreadReleaseRunnable(
+ std::move(doomed), std::move(aLoadGroupToCancel));
+ return NS_SUCCEEDED(aWorkerPrivate->DispatchToMainThread(runnable.forget()));
+}
+
+WorkerLoadInfo::InterfaceRequestor::InterfaceRequestor(
+ nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+
+ // Look for an existing LoadContext. This is optional and it's ok if
+ // we don't find one.
+ nsCOMPtr<nsILoadContext> baseContext;
+ if (aLoadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ callbacks->GetInterface(NS_GET_IID(nsILoadContext),
+ getter_AddRefs(baseContext));
+ }
+ mOuterRequestor = callbacks;
+ }
+
+ mLoadContext = new LoadContext(aPrincipal, baseContext);
+}
+
+void WorkerLoadInfo::InterfaceRequestor::MaybeAddBrowserChild(
+ nsILoadGroup* aLoadGroup) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aLoadGroup) {
+ return;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (!callbacks) {
+ return;
+ }
+
+ nsCOMPtr<nsIBrowserChild> browserChild;
+ callbacks->GetInterface(NS_GET_IID(nsIBrowserChild),
+ getter_AddRefs(browserChild));
+ if (!browserChild) {
+ return;
+ }
+
+ // Use weak references to the tab child. Holding a strong reference will
+ // not prevent an ActorDestroy() from being called on the BrowserChild.
+ // Therefore, we should let the BrowserChild destroy itself as soon as
+ // possible.
+ mBrowserChildList.AppendElement(do_GetWeakReference(browserChild));
+}
+
+NS_IMETHODIMP
+WorkerLoadInfo::InterfaceRequestor::GetInterface(const nsIID& aIID,
+ void** aSink) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mLoadContext);
+
+ if (aIID.Equals(NS_GET_IID(nsILoadContext))) {
+ nsCOMPtr<nsILoadContext> ref = mLoadContext;
+ ref.forget(aSink);
+ return NS_OK;
+ }
+
+ // If we still have an active nsIBrowserChild, then return it. Its possible,
+ // though, that all of the BrowserChild objects have been destroyed. In that
+ // case we return NS_NOINTERFACE.
+ if (aIID.Equals(NS_GET_IID(nsIBrowserChild))) {
+ nsCOMPtr<nsIBrowserChild> browserChild = GetAnyLiveBrowserChild();
+ if (!browserChild) {
+ return NS_NOINTERFACE;
+ }
+ browserChild.forget(aSink);
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) &&
+ mOuterRequestor) {
+ // If asked for the network intercept controller, ask the outer requestor,
+ // which could be the docshell.
+ return mOuterRequestor->GetInterface(aIID, aSink);
+ }
+
+ return NS_NOINTERFACE;
+}
+
+already_AddRefed<nsIBrowserChild>
+WorkerLoadInfo::InterfaceRequestor::GetAnyLiveBrowserChild() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Search our list of known BrowserChild objects for one that still exists.
+ while (!mBrowserChildList.IsEmpty()) {
+ nsCOMPtr<nsIBrowserChild> browserChild =
+ do_QueryReferent(mBrowserChildList.LastElement());
+
+ // Does this tab child still exist? If so, return it. We are done. If the
+ // PBrowser actor is no longer useful, don't bother returning this tab.
+ if (browserChild &&
+ !static_cast<BrowserChild*>(browserChild.get())->IsDestroyed()) {
+ return browserChild.forget();
+ }
+
+ // Otherwise remove the stale weak reference and check the next one
+ mBrowserChildList.RemoveLastElement();
+ }
+
+ return nullptr;
+}
+
+NS_IMPL_ADDREF(WorkerLoadInfo::InterfaceRequestor)
+NS_IMPL_RELEASE(WorkerLoadInfo::InterfaceRequestor)
+NS_IMPL_QUERY_INTERFACE(WorkerLoadInfo::InterfaceRequestor,
+ nsIInterfaceRequestor)
+
+WorkerLoadInfo::WorkerLoadInfo() { MOZ_COUNT_CTOR(WorkerLoadInfo); }
+
+WorkerLoadInfo::WorkerLoadInfo(WorkerLoadInfo&& aOther) noexcept
+ : WorkerLoadInfoData(std::move(aOther)) {
+ MOZ_COUNT_CTOR(WorkerLoadInfo);
+}
+
+WorkerLoadInfo::~WorkerLoadInfo() { MOZ_COUNT_DTOR(WorkerLoadInfo); }
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/WorkerLoadInfo.h b/dom/workers/WorkerLoadInfo.h
new file mode 100644
index 0000000000..e55284baa9
--- /dev/null
+++ b/dom/workers/WorkerLoadInfo.h
@@ -0,0 +1,198 @@
+/* -*- 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_WorkerLoadInfo_h
+#define mozilla_dom_workers_WorkerLoadInfo_h
+
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/OriginTrials.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/ChannelInfo.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/dom/ServiceWorkerRegistrationDescriptor.h"
+#include "mozilla/dom/WorkerCommon.h"
+
+#include "nsIInterfaceRequestor.h"
+#include "nsILoadContext.h"
+#include "nsIRequest.h"
+#include "nsISupportsImpl.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsTArray.h"
+
+class nsIChannel;
+class nsIContentSecurityPolicy;
+class nsICookieJarSettings;
+class nsILoadGroup;
+class nsIPrincipal;
+class nsIReferrerInfo;
+class nsIRunnable;
+class nsIScriptContext;
+class nsIBrowserChild;
+class nsIURI;
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+class CSPInfo;
+} // namespace ipc
+
+namespace dom {
+
+class WorkerPrivate;
+
+struct WorkerLoadInfoData {
+ // All of these should be released in
+ // WorkerPrivateParent::ForgetMainThreadObjects.
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsCOMPtr<nsIURI> mResolvedScriptURI;
+
+ // This is the principal of the global (parent worker or a window) loading
+ // the worker. It can be null if we are executing a ServiceWorker, otherwise,
+ // except for data: URL, it must subsumes the worker principal.
+ // If we load a data: URL, mPrincipal will be a null principal.
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsIPrincipal> mPartitionedPrincipal;
+
+ // Taken from the parent context.
+ nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
+
+ // The CookieJarSettingsArgs of mCookieJarSettings.
+ // This is specific for accessing on worker thread.
+ net::CookieJarSettingsArgs mCookieJarSettingsArgs;
+
+ nsCOMPtr<nsIScriptContext> mScriptContext;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ nsCOMPtr<nsIContentSecurityPolicy> mCSP;
+ // Thread boundaries require us to not only store a CSP object, but also a
+ // serialized version of the CSP. Reason being: Serializing a CSP to a CSPInfo
+ // needs to happen on the main thread, but storing the CSPInfo needs to happen
+ // on the worker thread. We move the CSPInfo into the Client within
+ // ScriptLoader::PreRun().
+ UniquePtr<mozilla::ipc::CSPInfo> mCSPInfo;
+
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+
+ class InterfaceRequestor final : public nsIInterfaceRequestor {
+ NS_DECL_ISUPPORTS
+
+ public:
+ InterfaceRequestor(nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup);
+ void MaybeAddBrowserChild(nsILoadGroup* aLoadGroup);
+ NS_IMETHOD GetInterface(const nsIID& aIID, void** aSink) override;
+
+ void SetOuterRequestor(nsIInterfaceRequestor* aOuterRequestor) {
+ MOZ_ASSERT(!mOuterRequestor);
+ MOZ_ASSERT(aOuterRequestor);
+ mOuterRequestor = aOuterRequestor;
+ }
+
+ private:
+ ~InterfaceRequestor() = default;
+
+ already_AddRefed<nsIBrowserChild> GetAnyLiveBrowserChild();
+
+ nsCOMPtr<nsILoadContext> mLoadContext;
+ nsCOMPtr<nsIInterfaceRequestor> mOuterRequestor;
+
+ // Array of weak references to nsIBrowserChild. We do not want to keep
+ // BrowserChild actors alive for long after their ActorDestroy() methods are
+ // called.
+ nsTArray<nsWeakPtr> mBrowserChildList;
+ };
+
+ // Only set if we have a custom overriden load group
+ RefPtr<InterfaceRequestor> mInterfaceRequestor;
+
+ UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
+ UniquePtr<mozilla::ipc::PrincipalInfo> mPartitionedPrincipalInfo;
+ nsCString mDomain;
+
+ nsString mServiceWorkerCacheName;
+ Maybe<ServiceWorkerDescriptor> mServiceWorkerDescriptor;
+ Maybe<ServiceWorkerRegistrationDescriptor>
+ mServiceWorkerRegistrationDescriptor;
+
+ Maybe<ServiceWorkerDescriptor> mParentController;
+
+ nsID mAgentClusterId;
+
+ ChannelInfo mChannelInfo;
+ nsLoadFlags mLoadFlags;
+
+ uint64_t mWindowID;
+ uint64_t mAssociatedBrowsingContextID;
+
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+ OriginTrials mTrials;
+ bool mFromWindow;
+ bool mEvalAllowed;
+ bool mReportEvalCSPViolations;
+ bool mWasmEvalAllowed;
+ bool mReportWasmEvalCSPViolations;
+ bool mXHRParamsAllowed;
+ bool mWatchedByDevTools;
+ StorageAccess mStorageAccess;
+ bool mUseRegularPrincipal;
+ bool mHasStorageAccessPermissionGranted;
+ bool mServiceWorkersTestingInWindow;
+ bool mShouldResistFingerprinting;
+ OriginAttributes mOriginAttributes;
+ bool mIsThirdPartyContextToTopWindow;
+
+ enum {
+ eNotSet,
+ eInsecureContext,
+ eSecureContext,
+ } mSecureContext;
+
+ WorkerLoadInfoData();
+ WorkerLoadInfoData(WorkerLoadInfoData&& aOther) = default;
+
+ WorkerLoadInfoData& operator=(WorkerLoadInfoData&& aOther) = default;
+};
+
+struct WorkerLoadInfo : WorkerLoadInfoData {
+ WorkerLoadInfo();
+ WorkerLoadInfo(WorkerLoadInfo&& aOther) noexcept;
+ ~WorkerLoadInfo();
+
+ WorkerLoadInfo& operator=(WorkerLoadInfo&& aOther) = default;
+
+ nsresult SetPrincipalsAndCSPOnMainThread(nsIPrincipal* aPrincipal,
+ nsIPrincipal* aPartitionedPrincipal,
+ nsILoadGroup* aLoadGroup,
+ nsIContentSecurityPolicy* aCSP);
+
+ nsresult GetPrincipalsAndLoadGroupFromChannel(
+ nsIChannel* aChannel, nsIPrincipal** aPrincipalOut,
+ nsIPrincipal** aPartitionedPrincipalOut, nsILoadGroup** aLoadGroupOut);
+
+ nsresult SetPrincipalsAndCSPFromChannel(nsIChannel* aChannel);
+
+ bool FinalChannelPrincipalIsValid(nsIChannel* aChannel);
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ bool PrincipalIsValid() const;
+
+ bool PrincipalURIMatchesScriptURL();
+#endif
+
+ bool ProxyReleaseMainThreadObjects(WorkerPrivate* aWorkerPrivate);
+
+ bool ProxyReleaseMainThreadObjects(
+ WorkerPrivate* aWorkerPrivate,
+ nsCOMPtr<nsILoadGroup>&& aLoadGroupToCancel);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_WorkerLoadInfo_h
diff --git a/dom/workers/WorkerLocation.cpp b/dom/workers/WorkerLocation.cpp
new file mode 100644
index 0000000000..94b662efb1
--- /dev/null
+++ b/dom/workers/WorkerLocation.cpp
@@ -0,0 +1,36 @@
+/* -*- 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/WorkerLocation.h"
+
+#include "mozilla/dom/WorkerLocationBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WorkerLocation)
+
+/* static */
+already_AddRefed<WorkerLocation> WorkerLocation::Create(
+ WorkerPrivate::LocationInfo& aInfo) {
+ RefPtr<WorkerLocation> location =
+ new WorkerLocation(NS_ConvertUTF8toUTF16(aInfo.mHref),
+ NS_ConvertUTF8toUTF16(aInfo.mProtocol),
+ NS_ConvertUTF8toUTF16(aInfo.mHost),
+ NS_ConvertUTF8toUTF16(aInfo.mHostname),
+ NS_ConvertUTF8toUTF16(aInfo.mPort),
+ NS_ConvertUTF8toUTF16(aInfo.mPathname),
+ NS_ConvertUTF8toUTF16(aInfo.mSearch),
+ NS_ConvertUTF8toUTF16(aInfo.mHash), aInfo.mOrigin);
+
+ return location.forget();
+}
+
+JSObject* WorkerLocation::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return WorkerLocation_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/workers/WorkerLocation.h b/dom/workers/WorkerLocation.h
new file mode 100644
index 0000000000..d51e73cde5
--- /dev/null
+++ b/dom/workers/WorkerLocation.h
@@ -0,0 +1,72 @@
+/* -*- 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_location_h__
+#define mozilla_dom_location_h__
+
+#include "WorkerCommon.h"
+#include "WorkerPrivate.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class WorkerLocation final : public nsWrapperCache {
+ nsString mHref;
+ nsString mProtocol;
+ nsString mHost;
+ nsString mHostname;
+ nsString mPort;
+ nsString mPathname;
+ nsString mSearch;
+ nsString mHash;
+ nsString mOrigin;
+
+ WorkerLocation(const nsAString& aHref, const nsAString& aProtocol,
+ const nsAString& aHost, const nsAString& aHostname,
+ const nsAString& aPort, const nsAString& aPathname,
+ const nsAString& aSearch, const nsAString& aHash,
+ const nsAString& aOrigin)
+ : mHref(aHref),
+ mProtocol(aProtocol),
+ mHost(aHost),
+ mHostname(aHostname),
+ mPort(aPort),
+ mPathname(aPathname),
+ mSearch(aSearch),
+ mHash(aHash),
+ mOrigin(aOrigin) {
+ MOZ_COUNT_CTOR(WorkerLocation);
+ }
+
+ MOZ_COUNTED_DTOR(WorkerLocation)
+
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WorkerLocation)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(WorkerLocation)
+
+ static already_AddRefed<WorkerLocation> Create(
+ WorkerPrivate::LocationInfo& aInfo);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ nsISupports* GetParentObject() const { return nullptr; }
+
+ void Stringify(nsString& aHref) const { aHref = mHref; }
+ void GetHref(nsString& aHref) const { aHref = mHref; }
+ void GetProtocol(nsString& aProtocol) const { aProtocol = mProtocol; }
+ void GetHost(nsString& aHost) const { aHost = mHost; }
+ void GetHostname(nsString& aHostname) const { aHostname = mHostname; }
+ void GetPort(nsString& aPort) const { aPort = mPort; }
+ void GetPathname(nsString& aPathname) const { aPathname = mPathname; }
+ void GetSearch(nsString& aSearch) const { aSearch = mSearch; }
+ void GetHash(nsString& aHash) const { aHash = mHash; }
+ void GetOrigin(nsString& aOrigin) const { aOrigin = mOrigin; }
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_location_h__
diff --git a/dom/workers/WorkerNavigator.cpp b/dom/workers/WorkerNavigator.cpp
new file mode 100644
index 0000000000..fc16071f74
--- /dev/null
+++ b/dom/workers/WorkerNavigator.cpp
@@ -0,0 +1,293 @@
+/* -*- 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/WorkerNavigator.h"
+
+#include <utility>
+
+#include "ErrorList.h"
+#include "MainThreadUtils.h"
+#include "RuntimeService.h"
+#include "WorkerRunnable.h"
+#include "WorkerScope.h"
+#include "mozilla/dom/LockManager.h"
+#include "mozilla/dom/MediaCapabilities.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/StorageManager.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerNavigatorBinding.h"
+#include "mozilla/dom/WorkerStatus.h"
+#include "mozilla/dom/network/Connection.h"
+#include "mozilla/webgpu/Instance.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsIGlobalObject.h"
+#include "nsLiteralString.h"
+#include "nsPIDOMWindow.h"
+#include "nsRFPService.h"
+#include "nsString.h"
+
+class JSObject;
+struct JSContext;
+
+namespace mozilla::dom {
+
+using namespace workerinternals;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WorkerNavigator)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WorkerNavigator)
+ tmp->Invalidate();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WorkerNavigator)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConnection)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaCapabilities)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebGpu)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocks)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+WorkerNavigator::WorkerNavigator(const NavigatorProperties& aProperties,
+ bool aOnline)
+ : mProperties(aProperties), mOnline(aOnline) {}
+
+WorkerNavigator::~WorkerNavigator() { Invalidate(); }
+
+/* static */
+already_AddRefed<WorkerNavigator> WorkerNavigator::Create(bool aOnLine) {
+ RuntimeService* rts = RuntimeService::GetService();
+ MOZ_ASSERT(rts);
+
+ const RuntimeService::NavigatorProperties& properties =
+ rts->GetNavigatorProperties();
+
+ RefPtr<WorkerNavigator> navigator = new WorkerNavigator(properties, aOnLine);
+
+ return navigator.forget();
+}
+
+void WorkerNavigator::Invalidate() {
+ if (mStorageManager) {
+ mStorageManager->Shutdown();
+ mStorageManager = nullptr;
+ }
+
+ mConnection = nullptr;
+
+ mMediaCapabilities = nullptr;
+
+ mWebGpu = nullptr;
+
+ mLocks = nullptr;
+}
+
+JSObject* WorkerNavigator::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return WorkerNavigator_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void WorkerNavigator::SetLanguages(const nsTArray<nsString>& aLanguages) {
+ WorkerNavigator_Binding::ClearCachedLanguagesValue(this);
+ mProperties.mLanguages = aLanguages.Clone();
+}
+
+void WorkerNavigator::GetAppName(nsString& aAppName,
+ CallerType aCallerType) const {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ if (aCallerType != CallerType::System) {
+ if (workerPrivate->GlobalScope()->ShouldResistFingerprinting(
+ RFPTarget::NavigatorAppName)) {
+ // See nsRFPService.h for spoofed value.
+ aAppName.AssignLiteral(SPOOFED_APPNAME);
+ return;
+ }
+
+ if (!mProperties.mAppNameOverridden.IsEmpty()) {
+ aAppName = mProperties.mAppNameOverridden;
+ return;
+ }
+ }
+
+ aAppName = mProperties.mAppName;
+}
+
+void WorkerNavigator::GetAppVersion(nsString& aAppVersion,
+ CallerType aCallerType,
+ ErrorResult& aRv) const {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ if (aCallerType != CallerType::System) {
+ if (workerPrivate->GlobalScope()->ShouldResistFingerprinting(
+ RFPTarget::NavigatorAppVersion)) {
+ // See nsRFPService.h for spoofed value.
+ aAppVersion.AssignLiteral(SPOOFED_APPVERSION);
+ return;
+ }
+
+ if (!mProperties.mAppVersionOverridden.IsEmpty()) {
+ aAppVersion = mProperties.mAppVersionOverridden;
+ return;
+ }
+ }
+
+ aAppVersion = mProperties.mAppVersion;
+}
+
+void WorkerNavigator::GetPlatform(nsString& aPlatform, CallerType aCallerType,
+ ErrorResult& aRv) const {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ if (aCallerType != CallerType::System) {
+ if (workerPrivate->GlobalScope()->ShouldResistFingerprinting(
+ RFPTarget::NavigatorPlatform)) {
+ // See nsRFPService.h for spoofed value.
+ aPlatform.AssignLiteral(SPOOFED_PLATFORM);
+ return;
+ }
+
+ if (!mProperties.mPlatformOverridden.IsEmpty()) {
+ aPlatform = mProperties.mPlatformOverridden;
+ return;
+ }
+ }
+
+ aPlatform = mProperties.mPlatform;
+}
+
+namespace {
+
+/*
+ * This Worker Runnable needs to check RFP; but our standard way of doing so
+ * relies on accessing GlobalScope() - which can only be accessed on the worker
+ * thread. So we need to pass it in.
+ */
+class GetUserAgentRunnable final : public WorkerMainThreadRunnable {
+ nsString& mUA;
+ bool mShouldResistFingerprinting;
+
+ public:
+ GetUserAgentRunnable(WorkerPrivate* aWorkerPrivate, nsString& aUA,
+ bool aShouldResistFingerprinting)
+ : WorkerMainThreadRunnable(aWorkerPrivate, "UserAgent getter"_ns),
+ mUA(aUA),
+ mShouldResistFingerprinting(aShouldResistFingerprinting) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ virtual bool MainThreadRun() override {
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsPIDOMWindowInner> window = mWorkerPrivate->GetWindow();
+
+ nsresult rv =
+ dom::Navigator::GetUserAgent(window, mWorkerPrivate->GetDocument(),
+ Some(mShouldResistFingerprinting), mUA);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to retrieve user-agent from the worker thread.");
+ }
+
+ return true;
+ }
+};
+
+} // namespace
+
+void WorkerNavigator::GetUserAgent(nsString& aUserAgent, CallerType aCallerType,
+ ErrorResult& aRv) const {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ RefPtr<GetUserAgentRunnable> runnable = new GetUserAgentRunnable(
+ workerPrivate, aUserAgent,
+ workerPrivate->GlobalScope()->ShouldResistFingerprinting(
+ RFPTarget::NavigatorUserAgent));
+
+ runnable->Dispatch(Canceling, aRv);
+}
+
+uint64_t WorkerNavigator::HardwareConcurrency() const {
+ RuntimeService* rts = RuntimeService::GetService();
+ MOZ_ASSERT(rts);
+
+ WorkerPrivate* aWorkerPrivate = GetCurrentThreadWorkerPrivate();
+ bool rfp = aWorkerPrivate->GlobalScope()->ShouldResistFingerprinting(
+ RFPTarget::NavigatorHWConcurrency);
+
+ return rts->ClampedHardwareConcurrency(rfp);
+}
+
+StorageManager* WorkerNavigator::Storage() {
+ if (!mStorageManager) {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ RefPtr<nsIGlobalObject> global = workerPrivate->GlobalScope();
+ MOZ_ASSERT(global);
+
+ mStorageManager = new StorageManager(global);
+ }
+
+ return mStorageManager;
+}
+
+network::Connection* WorkerNavigator::GetConnection(ErrorResult& aRv) {
+ if (!mConnection) {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ mConnection = network::Connection::CreateForWorker(workerPrivate, aRv);
+ }
+
+ return mConnection;
+}
+
+dom::MediaCapabilities* WorkerNavigator::MediaCapabilities() {
+ if (!mMediaCapabilities) {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ nsIGlobalObject* global = workerPrivate->GlobalScope();
+ MOZ_ASSERT(global);
+
+ mMediaCapabilities = new dom::MediaCapabilities(global);
+ }
+ return mMediaCapabilities;
+}
+
+webgpu::Instance* WorkerNavigator::Gpu() {
+ if (!mWebGpu) {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ nsIGlobalObject* global = workerPrivate->GlobalScope();
+ MOZ_ASSERT(global);
+
+ mWebGpu = webgpu::Instance::Create(global);
+ }
+ return mWebGpu;
+}
+
+dom::LockManager* WorkerNavigator::Locks() {
+ if (!mLocks) {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ nsIGlobalObject* global = workerPrivate->GlobalScope();
+ MOZ_ASSERT(global);
+
+ mLocks = new dom::LockManager(global);
+ }
+ return mLocks;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/workers/WorkerNavigator.h b/dom/workers/WorkerNavigator.h
new file mode 100644
index 0000000000..6a43090f66
--- /dev/null
+++ b/dom/workers/WorkerNavigator.h
@@ -0,0 +1,122 @@
+/* -*- 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_workernavigator_h__
+#define mozilla_dom_workernavigator_h__
+
+#include <stdint.h>
+#include "js/RootingAPI.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/workerinternals/RuntimeService.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace webgpu {
+class Instance;
+} // namespace webgpu
+namespace dom {
+class StorageManager;
+class MediaCapabilities;
+class LockManager;
+
+namespace network {
+class Connection;
+} // namespace network
+
+class WorkerNavigator final : public nsWrapperCache {
+ using NavigatorProperties =
+ workerinternals::RuntimeService::NavigatorProperties;
+
+ NavigatorProperties mProperties;
+ RefPtr<StorageManager> mStorageManager;
+ RefPtr<network::Connection> mConnection;
+ RefPtr<dom::MediaCapabilities> mMediaCapabilities;
+ RefPtr<webgpu::Instance> mWebGpu;
+ RefPtr<dom::LockManager> mLocks;
+ bool mOnline;
+
+ WorkerNavigator(const NavigatorProperties& aProperties, bool aOnline);
+ ~WorkerNavigator();
+
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WorkerNavigator)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(WorkerNavigator)
+
+ static already_AddRefed<WorkerNavigator> Create(bool aOnLine);
+
+ void Invalidate();
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ nsISupports* GetParentObject() const { return nullptr; }
+
+ void GetAppCodeName(nsString& aAppCodeName, ErrorResult& /* unused */) const {
+ aAppCodeName.AssignLiteral("Mozilla");
+ }
+ void GetAppName(nsString& aAppName, CallerType aCallerType) const;
+
+ void GetAppVersion(nsString& aAppVersion, CallerType aCallerType,
+ ErrorResult& aRv) const;
+
+ void GetPlatform(nsString& aPlatform, CallerType aCallerType,
+ ErrorResult& aRv) const;
+
+ void GetProduct(nsString& aProduct) const { aProduct.AssignLiteral("Gecko"); }
+
+ bool TaintEnabled() const { return false; }
+
+ void GetLanguage(nsString& aLanguage) const {
+ MOZ_ASSERT(mProperties.mLanguages.Length() >= 1);
+ aLanguage.Assign(mProperties.mLanguages[0]);
+ }
+
+ void GetLanguages(nsTArray<nsString>& aLanguages) const {
+ aLanguages = mProperties.mLanguages.Clone();
+ }
+
+ void GetUserAgent(nsString& aUserAgent, CallerType aCallerType,
+ ErrorResult& aRv) const;
+
+ bool OnLine() const { return mOnline; }
+
+ // Worker thread only!
+ void SetOnLine(bool aOnline) { mOnline = aOnline; }
+
+ bool GlobalPrivacyControl() const {
+ return StaticPrefs::privacy_globalprivacycontrol_enabled() &&
+ StaticPrefs::privacy_globalprivacycontrol_functionality_enabled();
+ }
+
+ void SetLanguages(const nsTArray<nsString>& aLanguages);
+
+ uint64_t HardwareConcurrency() const;
+
+ StorageManager* Storage();
+
+ network::Connection* GetConnection(ErrorResult& aRv);
+
+ dom::MediaCapabilities* MediaCapabilities();
+
+ webgpu::Instance* Gpu();
+
+ dom::LockManager* Locks();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workernavigator_h__
diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
new file mode 100644
index 0000000000..fc349c1642
--- /dev/null
+++ b/dom/workers/WorkerPrivate.cpp
@@ -0,0 +1,6003 @@
+/* -*- 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 <utility>
+
+#include "js/CallAndConstruct.h" // JS_CallFunctionValue
+#include "js/CompilationAndEvaluation.h"
+#include "js/ContextOptions.h"
+#include "js/Exception.h"
+#include "js/friend/ErrorMessages.h" // JSMSG_OUT_OF_MEMORY
+#include "js/LocaleSensitive.h"
+#include "js/MemoryMetrics.h"
+#include "js/SourceText.h"
+#include "MessageEventRunnable.h"
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/ExtensionPolicyService.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/Result.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/CallbackDebuggerNotification.h"
+#include "mozilla/dom/ClientManager.h"
+#include "mozilla/dom/ClientState.h"
+#include "mozilla/dom/Console.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/FunctionBinding.h"
+#include "mozilla/dom/IndexedDatabaseManager.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/MessagePortBinding.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PerformanceStorageWorker.h"
+#include "mozilla/dom/PromiseDebugging.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/dom/RemoteWorkerChild.h"
+#include "mozilla/dom/RemoteWorkerService.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/TimeoutHandler.h"
+#include "mozilla/dom/WorkerBinding.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/dom/WebTaskScheduler.h"
+#include "mozilla/dom/JSExecutionManager.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/extensions/ExtensionBrowser.h" // extensions::Create{AndDispatchInitWorkerContext,WorkerLoaded,WorkerDestroyed}Runnable
+#include "mozilla/extensions/WebExtensionPolicy.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/ThreadEventQueue.h"
+#include "mozilla/ThrottledEventQueue.h"
+#include "mozilla/TimelineConsumers.h"
+#include "mozilla/WorkerTimelineMarker.h"
+#include "nsCycleCollector.h"
+#include "nsGlobalWindowInner.h"
+#include "nsIDUtils.h"
+#include "nsNetUtil.h"
+#include "nsIFile.h"
+#include "nsIMemoryReporter.h"
+#include "nsIPermissionManager.h"
+#include "nsIProtocolHandler.h"
+#include "nsIScriptError.h"
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsIUUIDGenerator.h"
+#include "nsPrintfCString.h"
+#include "nsProxyRelease.h"
+#include "nsQueryObject.h"
+#include "nsRFPService.h"
+#include "nsSandboxFlags.h"
+#include "nsUTF8Utils.h"
+
+#include "RuntimeService.h"
+#include "ScriptLoader.h"
+#include "mozilla/dom/ServiceWorkerEvents.h"
+#include "mozilla/dom/ServiceWorkerManager.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "WorkerCSPEventListener.h"
+#include "WorkerDebugger.h"
+#include "WorkerDebuggerManager.h"
+#include "WorkerError.h"
+#include "WorkerEventTarget.h"
+#include "WorkerNavigator.h"
+#include "WorkerRef.h"
+#include "WorkerRunnable.h"
+#include "WorkerThread.h"
+#include "nsContentSecurityManager.h"
+
+#include "nsThreadManager.h"
+
+#ifdef XP_WIN
+# undef PostMessage
+#endif
+
+// JS_MaybeGC will run once every second during normal execution.
+#define PERIODIC_GC_TIMER_DELAY_SEC 1
+
+// A shrinking GC will run five seconds after the last event is processed.
+#define IDLE_GC_TIMER_DELAY_SEC 5
+
+static mozilla::LazyLogModule sWorkerPrivateLog("WorkerPrivate");
+static mozilla::LazyLogModule sWorkerTimeoutsLog("WorkerTimeouts");
+
+mozilla::LogModule* WorkerLog() { return sWorkerPrivateLog; }
+
+mozilla::LogModule* TimeoutsLog() { return sWorkerTimeoutsLog; }
+
+#ifdef LOG
+# undef LOG
+#endif
+#define LOG(log, _args) MOZ_LOG(log, LogLevel::Debug, _args);
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+using namespace workerinternals;
+
+MOZ_DEFINE_MALLOC_SIZE_OF(JsWorkerMallocSizeOf)
+
+namespace {
+
+#ifdef DEBUG
+
+const nsIID kDEBUGWorkerEventTargetIID = {
+ 0xccaba3fa,
+ 0x5be2,
+ 0x4de2,
+ {0xba, 0x87, 0x3b, 0x3b, 0x5b, 0x1d, 0x5, 0xfb}};
+
+#endif
+
+template <class T>
+class UniquePtrComparator {
+ using A = UniquePtr<T>;
+ using B = T*;
+
+ public:
+ bool Equals(const A& a, const A& b) const {
+ return (a && b) ? (*a == *b) : (!a && !b);
+ }
+ bool LessThan(const A& a, const A& b) const {
+ return (a && b) ? (*a < *b) : !!b;
+ }
+};
+
+template <class T>
+inline UniquePtrComparator<T> GetUniquePtrComparator(
+ const nsTArray<UniquePtr<T>>&) {
+ return UniquePtrComparator<T>();
+}
+
+// This class is used to wrap any runnables that the worker receives via the
+// nsIEventTarget::Dispatch() method (either from NS_DispatchToCurrentThread or
+// from the worker's EventTarget).
+class ExternalRunnableWrapper final : public WorkerRunnable {
+ nsCOMPtr<nsIRunnable> mWrappedRunnable;
+
+ public:
+ ExternalRunnableWrapper(WorkerPrivate* aWorkerPrivate,
+ nsIRunnable* aWrappedRunnable)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mWrappedRunnable(aWrappedRunnable) {
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aWrappedRunnable);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(ExternalRunnableWrapper, WorkerRunnable)
+
+ private:
+ ~ExternalRunnableWrapper() = default;
+
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
+ // Silence bad assertions.
+ return true;
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {
+ // Silence bad assertions.
+ }
+
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ nsresult rv = mWrappedRunnable->Run();
+ mWrappedRunnable = nullptr;
+ if (NS_FAILED(rv)) {
+ if (!JS_IsExceptionPending(aCx)) {
+ Throw(aCx, rv);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ nsresult Cancel() override {
+ // We need to check first if cancel is called twice
+ nsresult rv = WorkerRunnable::Cancel();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDiscardableRunnable> doomed =
+ do_QueryInterface(mWrappedRunnable);
+ MOZ_ASSERT(doomed); // We checked this earlier!
+ doomed->OnDiscard();
+ mWrappedRunnable = nullptr;
+ return NS_OK;
+ }
+};
+
+struct WindowAction {
+ nsPIDOMWindowInner* mWindow;
+ bool mDefaultAction;
+
+ MOZ_IMPLICIT WindowAction(nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow), mDefaultAction(true) {}
+
+ bool operator==(const WindowAction& aOther) const {
+ return mWindow == aOther.mWindow;
+ }
+};
+
+class WorkerFinishedRunnable final : public WorkerControlRunnable {
+ WorkerPrivate* mFinishedWorker;
+
+ public:
+ WorkerFinishedRunnable(WorkerPrivate* aWorkerPrivate,
+ WorkerPrivate* aFinishedWorker)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mFinishedWorker(aFinishedWorker) {
+ aFinishedWorker->IncreaseWorkerFinishedRunnableCount();
+ }
+
+ private:
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
+ // Silence bad assertions.
+ return true;
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {
+ // Silence bad assertions.
+ }
+
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ // This may block on the main thread.
+ AutoYieldJSThreadExecution yield;
+
+ mFinishedWorker->DecreaseWorkerFinishedRunnableCount();
+
+ if (!mFinishedWorker->ProxyReleaseMainThreadObjects()) {
+ NS_WARNING("Failed to dispatch, going to leak!");
+ }
+
+ RuntimeService* runtime = RuntimeService::GetService();
+ NS_ASSERTION(runtime, "This should never be null!");
+
+ mFinishedWorker->DisableDebugger();
+
+ runtime->UnregisterWorker(*mFinishedWorker);
+
+ mFinishedWorker->ClearSelfAndParentEventTargetRef();
+ return true;
+ }
+};
+
+class TopLevelWorkerFinishedRunnable final : public Runnable {
+ WorkerPrivate* mFinishedWorker;
+
+ public:
+ explicit TopLevelWorkerFinishedRunnable(WorkerPrivate* aFinishedWorker)
+ : mozilla::Runnable("TopLevelWorkerFinishedRunnable"),
+ mFinishedWorker(aFinishedWorker) {
+ aFinishedWorker->AssertIsOnWorkerThread();
+ aFinishedWorker->IncreaseTopLevelWorkerFinishedRunnableCount();
+ }
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(TopLevelWorkerFinishedRunnable, Runnable)
+
+ private:
+ ~TopLevelWorkerFinishedRunnable() = default;
+
+ NS_IMETHOD
+ Run() override {
+ AssertIsOnMainThread();
+
+ mFinishedWorker->DecreaseTopLevelWorkerFinishedRunnableCount();
+
+ RuntimeService* runtime = RuntimeService::GetService();
+ MOZ_ASSERT(runtime);
+
+ mFinishedWorker->DisableDebugger();
+
+ runtime->UnregisterWorker(*mFinishedWorker);
+
+ if (!mFinishedWorker->ProxyReleaseMainThreadObjects()) {
+ NS_WARNING("Failed to dispatch, going to leak!");
+ }
+
+ mFinishedWorker->ClearSelfAndParentEventTargetRef();
+ return NS_OK;
+ }
+};
+
+class ModifyBusyCountRunnable final : public WorkerControlRunnable {
+ bool mIncrease;
+
+ public:
+ ModifyBusyCountRunnable(WorkerPrivate* aWorkerPrivate, bool aIncrease)
+ : WorkerControlRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount),
+ mIncrease(aIncrease) {}
+
+ private:
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ return aWorkerPrivate->ModifyBusyCount(mIncrease);
+ }
+
+ virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult) override {
+ if (mIncrease) {
+ WorkerControlRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
+ return;
+ }
+ // Don't do anything here as it's possible that aWorkerPrivate has been
+ // deleted.
+ }
+};
+
+class CompileScriptRunnable final : public WorkerDebuggeeRunnable {
+ nsString mScriptURL;
+ const mozilla::Encoding* mDocumentEncoding;
+ UniquePtr<SerializedStackHolder> mOriginStack;
+
+ public:
+ explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate,
+ UniquePtr<SerializedStackHolder> aOriginStack,
+ const nsAString& aScriptURL,
+ const mozilla::Encoding* aDocumentEncoding)
+ : WorkerDebuggeeRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount),
+ mScriptURL(aScriptURL),
+ mDocumentEncoding(aDocumentEncoding),
+ mOriginStack(aOriginStack.release()) {}
+
+ private:
+ // We can't implement PreRun effectively, because at the point when that would
+ // run we have not yet done our load so don't know things like our final
+ // principal and whatnot.
+
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ WorkerGlobalScope* globalScope =
+ aWorkerPrivate->GetOrCreateGlobalScope(aCx);
+ if (NS_WARN_IF(!globalScope)) {
+ return false;
+ }
+
+ if (NS_WARN_IF(!aWorkerPrivate->EnsureCSPEventListener())) {
+ return false;
+ }
+
+ ErrorResult rv;
+ workerinternals::LoadMainScript(aWorkerPrivate, std::move(mOriginStack),
+ mScriptURL, WorkerScript, rv,
+ mDocumentEncoding);
+
+ if (aWorkerPrivate->ExtensionAPIAllowed()) {
+ MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
+ RefPtr<Runnable> extWorkerRunnable =
+ extensions::CreateWorkerLoadedRunnable(
+ aWorkerPrivate->ServiceWorkerID(), aWorkerPrivate->GetBaseURI());
+ // Dispatch as a low priority runnable.
+ if (NS_FAILED(aWorkerPrivate->DispatchToMainThreadForMessaging(
+ extWorkerRunnable.forget()))) {
+ NS_WARNING(
+ "Failed to dispatch runnable to notify extensions worker loaded");
+ }
+ }
+
+ 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 want to propagate just JS exceptions,
+ // because all the other errors are handled when the script is loaded.
+ // See: https://dom.spec.whatwg.org/#concept-event-fire
+ if (rv.Failed() && !rv.IsJSException()) {
+ WorkerErrorReport::CreateAndDispatchGenericErrorRunnableToParent(
+ aWorkerPrivate);
+ rv.SuppressException();
+ return false;
+ }
+
+ // This is a little dumb, but aCx is in the null realm here because we
+ // set it up that way in our Run(), since we had not created the global at
+ // that point yet. So we need to enter the realm of our global,
+ // because setting a pending exception on aCx involves wrapping into its
+ // current compartment. Luckily we have a global now.
+ JSAutoRealm ar(aCx, globalScope->GetGlobalJSObject());
+ if (rv.MaybeSetPendingException(aCx)) {
+ // In the event of an uncaught exception, the worker should still keep
+ // running (return true) but should not be marked as having executed
+ // successfully (which will cause ServiceWorker installation to fail).
+ // In previous error handling cases in this method, we return false (to
+ // trigger CloseInternal) because the global is not in an operable
+ // state at all.
+ //
+ // For ServiceWorkers, this would correspond to the "Run Service Worker"
+ // algorithm returning an "abrupt completion" and _not_ failure.
+ //
+ // For DedicatedWorkers and SharedWorkers, this would correspond to the
+ // "run a worker" algorithm disregarding the return value of "run the
+ // classic script"/"run the module script" in step 24:
+ //
+ // "If script is a classic script, then run the classic script script.
+ // Otherwise, it is a module script; run the module script script."
+ return true;
+ }
+
+ aWorkerPrivate->SetWorkerScriptExecutedSuccessfully();
+ return true;
+ }
+
+ void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult) override {
+ if (!aRunResult) {
+ aWorkerPrivate->CloseInternal();
+ }
+ WorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
+ }
+};
+
+class NotifyRunnable final : public WorkerControlRunnable {
+ WorkerStatus mStatus;
+
+ public:
+ NotifyRunnable(WorkerPrivate* aWorkerPrivate, WorkerStatus aStatus)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mStatus(aStatus) {
+ MOZ_ASSERT(aStatus == Closing || aStatus == Canceling ||
+ aStatus == Killing);
+ }
+
+ private:
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->AssertIsOnParentThread();
+ return aWorkerPrivate->ModifyBusyCount(true);
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {
+ aWorkerPrivate->AssertIsOnParentThread();
+ if (!aDispatchResult) {
+ // We couldn't dispatch to the worker, which means it's already dead.
+ // Undo the busy count modification.
+ aWorkerPrivate->ModifyBusyCount(false);
+ }
+ }
+
+ virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult) override {
+ aWorkerPrivate->ModifyBusyCountFromWorker(false);
+ }
+
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ return aWorkerPrivate->NotifyInternal(mStatus);
+ }
+};
+
+class FreezeRunnable final : public WorkerControlRunnable {
+ public:
+ explicit FreezeRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
+
+ private:
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ return aWorkerPrivate->FreezeInternal();
+ }
+};
+
+class ThawRunnable final : public WorkerControlRunnable {
+ public:
+ explicit ThawRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
+
+ private:
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ return aWorkerPrivate->ThawInternal();
+ }
+};
+
+class PropagateStorageAccessPermissionGrantedRunnable final
+ : public WorkerControlRunnable {
+ public:
+ explicit PropagateStorageAccessPermissionGrantedRunnable(
+ WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
+
+ private:
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->PropagateStorageAccessPermissionGrantedInternal();
+ return true;
+ }
+};
+
+class ReportErrorToConsoleRunnable final : public WorkerRunnable {
+ const char* mMessage;
+ const nsTArray<nsString> mParams;
+
+ public:
+ // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
+ static void Report(WorkerPrivate* aWorkerPrivate, const char* aMessage,
+ const nsTArray<nsString>& aParams) {
+ if (aWorkerPrivate) {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ } else {
+ AssertIsOnMainThread();
+ }
+
+ // Now fire a runnable to do the same on the parent's thread if we can.
+ if (aWorkerPrivate) {
+ RefPtr<ReportErrorToConsoleRunnable> runnable =
+ new ReportErrorToConsoleRunnable(aWorkerPrivate, aMessage, aParams);
+ runnable->Dispatch();
+ return;
+ }
+
+ // Log a warning to the console.
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
+ nullptr, nsContentUtils::eDOM_PROPERTIES,
+ aMessage, aParams);
+ }
+
+ private:
+ ReportErrorToConsoleRunnable(WorkerPrivate* aWorkerPrivate,
+ const char* aMessage,
+ const nsTArray<nsString>& aParams)
+ : WorkerRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount),
+ mMessage(aMessage),
+ mParams(aParams.Clone()) {}
+
+ 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 {
+ WorkerPrivate* parent = aWorkerPrivate->GetParent();
+ MOZ_ASSERT_IF(!parent, NS_IsMainThread());
+ Report(parent, mMessage, mParams);
+ return true;
+ }
+};
+
+class TimerRunnable final : public WorkerRunnable,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit TimerRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
+
+ private:
+ ~TimerRunnable() = default;
+
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
+ // Silence bad assertions.
+ return true;
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {
+ // Silence bad assertions.
+ }
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY until worker runnables are generally
+ // MOZ_CAN_RUN_SCRIPT.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ return aWorkerPrivate->RunExpiredTimeouts(aCx);
+ }
+
+ NS_IMETHOD
+ Notify(nsITimer* aTimer) override { return Run(); }
+
+ NS_IMETHOD
+ GetName(nsACString& aName) override {
+ aName.AssignLiteral("TimerRunnable");
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(TimerRunnable, WorkerRunnable, nsITimerCallback,
+ nsINamed)
+
+class DebuggerImmediateRunnable : public WorkerRunnable {
+ RefPtr<dom::Function> mHandler;
+
+ public:
+ explicit DebuggerImmediateRunnable(WorkerPrivate* aWorkerPrivate,
+ dom::Function& aHandler)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mHandler(&aHandler) {}
+
+ private:
+ virtual bool IsDebuggerRunnable() const override { return true; }
+
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
+ // Silence bad assertions.
+ return true;
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {
+ // Silence bad assertions.
+ }
+
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+ JS::Rooted<JS::Value> callable(
+ aCx, JS::ObjectOrNullValue(mHandler->CallableOrNull()));
+ JS::HandleValueArray args = JS::HandleValueArray::empty();
+ JS::Rooted<JS::Value> rval(aCx);
+
+ // WorkerRunnable::Run will report the exception if it happens.
+ return JS_CallFunctionValue(aCx, global, callable, args, &rval);
+ }
+};
+
+// GetJSContext() is safe on the worker thread
+void PeriodicGCTimerCallback(nsITimer* aTimer,
+ void* aClosure) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ auto* workerPrivate = static_cast<WorkerPrivate*>(aClosure);
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+ workerPrivate->AssertIsOnWorkerThread();
+ workerPrivate->GarbageCollectInternal(workerPrivate->GetJSContext(),
+ false /* shrinking */,
+ false /* collect children */);
+ LOG(WorkerLog(), ("Worker %p run periodic GC\n", workerPrivate));
+}
+
+void IdleGCTimerCallback(nsITimer* aTimer,
+ void* aClosure) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ auto* workerPrivate = static_cast<WorkerPrivate*>(aClosure);
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+ workerPrivate->AssertIsOnWorkerThread();
+ workerPrivate->GarbageCollectInternal(workerPrivate->GetJSContext(),
+ true /* shrinking */,
+ false /* collect children */);
+ LOG(WorkerLog(), ("Worker %p run idle GC\n", workerPrivate));
+
+ // After running idle GC we can cancel the current timers.
+ workerPrivate->CancelGCTimers();
+}
+
+class UpdateContextOptionsRunnable final : public WorkerControlRunnable {
+ JS::ContextOptions mContextOptions;
+
+ public:
+ UpdateContextOptionsRunnable(WorkerPrivate* aWorkerPrivate,
+ const JS::ContextOptions& aContextOptions)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mContextOptions(aContextOptions) {}
+
+ private:
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->UpdateContextOptionsInternal(aCx, mContextOptions);
+ return true;
+ }
+};
+
+class UpdateLanguagesRunnable final : public WorkerRunnable {
+ nsTArray<nsString> mLanguages;
+
+ public:
+ UpdateLanguagesRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsTArray<nsString>& aLanguages)
+ : WorkerRunnable(aWorkerPrivate), mLanguages(aLanguages.Clone()) {}
+
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->UpdateLanguagesInternal(mLanguages);
+ return true;
+ }
+};
+
+class UpdateJSWorkerMemoryParameterRunnable final
+ : public WorkerControlRunnable {
+ Maybe<uint32_t> mValue;
+ JSGCParamKey mKey;
+
+ public:
+ UpdateJSWorkerMemoryParameterRunnable(WorkerPrivate* aWorkerPrivate,
+ JSGCParamKey aKey,
+ Maybe<uint32_t> aValue)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mValue(aValue),
+ mKey(aKey) {}
+
+ private:
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->UpdateJSWorkerMemoryParameterInternal(aCx, mKey, mValue);
+ return true;
+ }
+};
+
+#ifdef JS_GC_ZEAL
+class UpdateGCZealRunnable final : public WorkerControlRunnable {
+ uint8_t mGCZeal;
+ uint32_t mFrequency;
+
+ public:
+ UpdateGCZealRunnable(WorkerPrivate* aWorkerPrivate, uint8_t aGCZeal,
+ uint32_t aFrequency)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mGCZeal(aGCZeal),
+ mFrequency(aFrequency) {}
+
+ private:
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->UpdateGCZealInternal(aCx, mGCZeal, mFrequency);
+ return true;
+ }
+};
+#endif
+
+class SetLowMemoryStateRunnable final : public WorkerControlRunnable {
+ bool mState;
+
+ public:
+ SetLowMemoryStateRunnable(WorkerPrivate* aWorkerPrivate, bool aState)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mState(aState) {}
+
+ private:
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->SetLowMemoryStateInternal(aCx, mState);
+ return true;
+ }
+};
+
+class GarbageCollectRunnable final : public WorkerControlRunnable {
+ bool mShrinking;
+ bool mCollectChildren;
+
+ public:
+ GarbageCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aShrinking,
+ bool aCollectChildren)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mShrinking(aShrinking),
+ mCollectChildren(aCollectChildren) {}
+
+ private:
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
+ // Silence bad assertions, this can be dispatched from either the main
+ // thread or the timer thread..
+ return true;
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {
+ // Silence bad assertions, this can be dispatched from either the main
+ // thread or the timer thread..
+ }
+
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->GarbageCollectInternal(aCx, mShrinking, mCollectChildren);
+ if (mShrinking) {
+ // Either we've run the idle GC or explicit GC call from the parent,
+ // we can cancel the current timers.
+ aWorkerPrivate->CancelGCTimers();
+ }
+ return true;
+ }
+};
+
+class CycleCollectRunnable : public WorkerControlRunnable {
+ bool mCollectChildren;
+
+ public:
+ CycleCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aCollectChildren)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mCollectChildren(aCollectChildren) {}
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->CycleCollectInternal(mCollectChildren);
+ return true;
+ }
+};
+
+class OfflineStatusChangeRunnable : public WorkerRunnable {
+ public:
+ OfflineStatusChangeRunnable(WorkerPrivate* aWorkerPrivate, bool aIsOffline)
+ : WorkerRunnable(aWorkerPrivate), mIsOffline(aIsOffline) {}
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->OfflineStatusChangeEventInternal(mIsOffline);
+ return true;
+ }
+
+ private:
+ bool mIsOffline;
+};
+
+class MemoryPressureRunnable : public WorkerControlRunnable {
+ public:
+ explicit MemoryPressureRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->MemoryPressureInternal();
+ return true;
+ }
+};
+
+#ifdef DEBUG
+static bool StartsWithExplicit(nsACString& s) {
+ return StringBeginsWith(s, "explicit/"_ns);
+}
+#endif
+
+PRThread* PRThreadFromThread(nsIThread* aThread) {
+ MOZ_ASSERT(aThread);
+
+ PRThread* result;
+ MOZ_ALWAYS_SUCCEEDS(aThread->GetPRThread(&result));
+ MOZ_ASSERT(result);
+
+ return result;
+}
+
+// A runnable to cancel the worker from the parent thread when self.close() is
+// called. This runnable is executed on the parent process in order to cancel
+// the current runnable. It uses a normal WorkerDebuggeeRunnable in order to be
+// sure that all the pending WorkerDebuggeeRunnables are executed before this.
+class CancelingOnParentRunnable final : public WorkerDebuggeeRunnable {
+ public:
+ explicit CancelingOnParentRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerDebuggeeRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount) {
+ }
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->Cancel();
+ return true;
+ }
+};
+
+// A runnable to cancel the worker from the parent process.
+class CancelingWithTimeoutOnParentRunnable final
+ : public WorkerControlRunnable {
+ public:
+ explicit CancelingWithTimeoutOnParentRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount) {}
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->AssertIsOnParentThread();
+ aWorkerPrivate->StartCancelingTimer();
+ return true;
+ }
+};
+
+class CancelingTimerCallback final : public nsITimerCallback {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit CancelingTimerCallback(WorkerPrivate* aWorkerPrivate)
+ : mWorkerPrivate(aWorkerPrivate) {}
+
+ NS_IMETHOD
+ Notify(nsITimer* aTimer) override {
+ mWorkerPrivate->AssertIsOnParentThread();
+ mWorkerPrivate->Cancel();
+ return NS_OK;
+ }
+
+ private:
+ ~CancelingTimerCallback() = default;
+
+ // Raw pointer here is OK because the timer is canceled during the shutdown
+ // steps.
+ WorkerPrivate* mWorkerPrivate;
+};
+
+NS_IMPL_ISUPPORTS(CancelingTimerCallback, nsITimerCallback)
+
+// This runnable starts the canceling of a worker after a self.close().
+class CancelingRunnable final : public Runnable {
+ public:
+ CancelingRunnable() : Runnable("CancelingRunnable") {}
+
+ NS_IMETHOD
+ Run() override {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ workerPrivate->AssertIsOnWorkerThread();
+
+ // Now we can cancel the this worker from the parent process.
+ RefPtr<CancelingOnParentRunnable> r =
+ new CancelingOnParentRunnable(workerPrivate);
+ r->Dispatch();
+
+ return NS_OK;
+ }
+};
+
+} /* anonymous namespace */
+
+nsString ComputeWorkerPrivateId() {
+ nsID uuid = nsID::GenerateUUID();
+ return NSID_TrimBracketsUTF16(uuid);
+}
+
+class WorkerPrivate::EventTarget final : public nsISerialEventTarget {
+ // This mutex protects mWorkerPrivate and must be acquired *before* the
+ // WorkerPrivate's mutex whenever they must both be held.
+ mozilla::Mutex mMutex;
+ WorkerPrivate* mWorkerPrivate MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsIEventTarget> mNestedEventTarget MOZ_GUARDED_BY(mMutex);
+ bool mDisabled MOZ_GUARDED_BY(mMutex);
+ bool mShutdown MOZ_GUARDED_BY(mMutex);
+
+ public:
+ EventTarget(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aNestedEventTarget)
+ : mMutex("WorkerPrivate::EventTarget::mMutex"),
+ mWorkerPrivate(aWorkerPrivate),
+ mNestedEventTarget(aNestedEventTarget),
+ mDisabled(false),
+ mShutdown(false) {
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aNestedEventTarget);
+ }
+
+ void Disable() {
+ {
+ MutexAutoLock lock(mMutex);
+
+ // Note, Disable() can be called more than once safely.
+ mDisabled = true;
+ }
+ }
+
+ void Shutdown() {
+ nsCOMPtr<nsIEventTarget> nestedEventTarget;
+ {
+ MutexAutoLock lock(mMutex);
+
+ mWorkerPrivate = nullptr;
+ mNestedEventTarget.swap(nestedEventTarget);
+ MOZ_ASSERT(mDisabled);
+ mShutdown = true;
+ }
+ }
+
+ RefPtr<nsIEventTarget> GetNestedEventTarget() {
+ RefPtr<nsIEventTarget> nestedEventTarget = nullptr;
+ {
+ MutexAutoLock lock(mMutex);
+ if (mWorkerPrivate) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ nestedEventTarget = mNestedEventTarget.get();
+ }
+ }
+ return nestedEventTarget;
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIEVENTTARGET_FULL
+
+ private:
+ ~EventTarget() = default;
+};
+
+struct WorkerPrivate::TimeoutInfo {
+ TimeoutInfo()
+ : mId(0),
+ mNestingLevel(0),
+ mReason(Timeout::Reason::eTimeoutOrInterval),
+ mIsInterval(false),
+ mCanceled(false),
+ mOnChromeWorker(false) {
+ MOZ_COUNT_CTOR(mozilla::dom::WorkerPrivate::TimeoutInfo);
+ }
+
+ ~TimeoutInfo() { MOZ_COUNT_DTOR(mozilla::dom::WorkerPrivate::TimeoutInfo); }
+
+ bool operator==(const TimeoutInfo& aOther) const {
+ return mTargetTime == aOther.mTargetTime;
+ }
+
+ bool operator<(const TimeoutInfo& aOther) const {
+ return mTargetTime < aOther.mTargetTime;
+ }
+
+ void AccumulateNestingLevel(const uint32_t& aBaseLevel) {
+ if (aBaseLevel < StaticPrefs::dom_clamp_timeout_nesting_level_AtStartup()) {
+ mNestingLevel = aBaseLevel + 1;
+ return;
+ }
+ mNestingLevel = StaticPrefs::dom_clamp_timeout_nesting_level_AtStartup();
+ }
+
+ void CalculateTargetTime() {
+ auto target = mInterval;
+ // Don't clamp timeout for chrome workers
+ if (mNestingLevel >=
+ StaticPrefs::dom_clamp_timeout_nesting_level_AtStartup() &&
+ !mOnChromeWorker) {
+ target = TimeDuration::Max(
+ mInterval,
+ TimeDuration::FromMilliseconds(StaticPrefs::dom_min_timeout_value()));
+ }
+ mTargetTime = TimeStamp::Now() + target;
+ }
+
+ RefPtr<TimeoutHandler> mHandler;
+ mozilla::TimeStamp mTargetTime;
+ mozilla::TimeDuration mInterval;
+ int32_t mId;
+ uint32_t mNestingLevel;
+ Timeout::Reason mReason;
+ bool mIsInterval;
+ bool mCanceled;
+ bool mOnChromeWorker;
+};
+
+class WorkerJSContextStats final : public JS::RuntimeStats {
+ const nsCString mRtPath;
+
+ public:
+ explicit WorkerJSContextStats(const nsACString& aRtPath)
+ : JS::RuntimeStats(JsWorkerMallocSizeOf), mRtPath(aRtPath) {}
+
+ ~WorkerJSContextStats() {
+ for (JS::ZoneStats& stats : zoneStatsVector) {
+ delete static_cast<xpc::ZoneStatsExtras*>(stats.extra);
+ }
+
+ for (JS::RealmStats& stats : realmStatsVector) {
+ delete static_cast<xpc::RealmStatsExtras*>(stats.extra);
+ }
+ }
+
+ const nsCString& Path() const { return mRtPath; }
+
+ virtual void initExtraZoneStats(JS::Zone* aZone, JS::ZoneStats* aZoneStats,
+ const JS::AutoRequireNoGC& nogc) override {
+ MOZ_ASSERT(!aZoneStats->extra);
+
+ // ReportJSRuntimeExplicitTreeStats expects that
+ // aZoneStats->extra is a xpc::ZoneStatsExtras pointer.
+ xpc::ZoneStatsExtras* extras = new xpc::ZoneStatsExtras;
+ extras->pathPrefix = mRtPath;
+ extras->pathPrefix += nsPrintfCString("zone(0x%p)/", (void*)aZone);
+
+ MOZ_ASSERT(StartsWithExplicit(extras->pathPrefix));
+
+ aZoneStats->extra = extras;
+ }
+
+ virtual void initExtraRealmStats(JS::Realm* aRealm,
+ JS::RealmStats* aRealmStats,
+ const JS::AutoRequireNoGC& nogc) override {
+ MOZ_ASSERT(!aRealmStats->extra);
+
+ // ReportJSRuntimeExplicitTreeStats expects that
+ // aRealmStats->extra is a xpc::RealmStatsExtras pointer.
+ xpc::RealmStatsExtras* extras = new xpc::RealmStatsExtras;
+
+ // This is the |jsPathPrefix|. Each worker has exactly one realm.
+ extras->jsPathPrefix.Assign(mRtPath);
+ extras->jsPathPrefix +=
+ nsPrintfCString("zone(0x%p)/", (void*)js::GetRealmZone(aRealm));
+ extras->jsPathPrefix += "realm(web-worker)/"_ns;
+
+ // This should never be used when reporting with workers (hence the "?!").
+ extras->domPathPrefix.AssignLiteral("explicit/workers/?!/");
+
+ MOZ_ASSERT(StartsWithExplicit(extras->jsPathPrefix));
+ MOZ_ASSERT(StartsWithExplicit(extras->domPathPrefix));
+
+ extras->location = nullptr;
+
+ aRealmStats->extra = extras;
+ }
+};
+
+class WorkerPrivate::MemoryReporter final : public nsIMemoryReporter {
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ friend class WorkerPrivate;
+
+ SharedMutex mMutex;
+ WorkerPrivate* mWorkerPrivate;
+
+ public:
+ explicit MemoryReporter(WorkerPrivate* aWorkerPrivate)
+ : mMutex(aWorkerPrivate->mMutex), mWorkerPrivate(aWorkerPrivate) {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) override;
+
+ private:
+ class FinishCollectRunnable;
+
+ class CollectReportsRunnable final : public MainThreadWorkerControlRunnable {
+ RefPtr<FinishCollectRunnable> mFinishCollectRunnable;
+ const bool mAnonymize;
+
+ public:
+ CollectReportsRunnable(WorkerPrivate* aWorkerPrivate,
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aHandlerData, bool aAnonymize,
+ const nsACString& aPath);
+
+ private:
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
+
+ ~CollectReportsRunnable() {
+ if (NS_IsMainThread()) {
+ mFinishCollectRunnable->Run();
+ return;
+ }
+
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThreadForMessaging(
+ mFinishCollectRunnable.forget()));
+ }
+ };
+
+ class FinishCollectRunnable final : public Runnable {
+ nsCOMPtr<nsIHandleReportCallback> mHandleReport;
+ nsCOMPtr<nsISupports> mHandlerData;
+ size_t mPerformanceUserEntries;
+ size_t mPerformanceResourceEntries;
+ const bool mAnonymize;
+ bool mSuccess;
+
+ public:
+ WorkerJSContextStats mCxStats;
+
+ explicit FinishCollectRunnable(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aHandlerData, bool aAnonymize,
+ const nsACString& aPath);
+
+ NS_IMETHOD Run() override;
+
+ void SetPerformanceSizes(size_t userEntries, size_t resourceEntries) {
+ mPerformanceUserEntries = userEntries;
+ mPerformanceResourceEntries = resourceEntries;
+ }
+
+ void SetSuccess(bool success) { mSuccess = success; }
+
+ FinishCollectRunnable(const FinishCollectRunnable&) = delete;
+ FinishCollectRunnable& operator=(const FinishCollectRunnable&) = delete;
+ FinishCollectRunnable& operator=(const FinishCollectRunnable&&) = delete;
+
+ private:
+ ~FinishCollectRunnable() {
+ // mHandleReport and mHandlerData are released on the main thread.
+ AssertIsOnMainThread();
+ }
+ };
+
+ ~MemoryReporter() = default;
+
+ void Disable() {
+ // Called from WorkerPrivate::DisableMemoryReporter.
+ mMutex.AssertCurrentThreadOwns();
+
+ NS_ASSERTION(mWorkerPrivate, "Disabled more than once!");
+ mWorkerPrivate = nullptr;
+ }
+};
+
+NS_IMPL_ISUPPORTS(WorkerPrivate::MemoryReporter, nsIMemoryReporter)
+
+NS_IMETHODIMP
+WorkerPrivate::MemoryReporter::CollectReports(
+ nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) {
+ AssertIsOnMainThread();
+
+ RefPtr<CollectReportsRunnable> runnable;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (!mWorkerPrivate) {
+ // This will effectively report 0 memory.
+ nsCOMPtr<nsIMemoryReporterManager> manager =
+ do_GetService("@mozilla.org/memory-reporter-manager;1");
+ if (manager) {
+ manager->EndReport();
+ }
+ return NS_OK;
+ }
+
+ nsAutoCString path;
+ path.AppendLiteral("explicit/workers/workers(");
+ if (aAnonymize && !mWorkerPrivate->Domain().IsEmpty()) {
+ path.AppendLiteral("<anonymized-domain>)/worker(<anonymized-url>");
+ } else {
+ nsAutoCString escapedDomain(mWorkerPrivate->Domain());
+ if (escapedDomain.IsEmpty()) {
+ escapedDomain += "chrome";
+ } else {
+ escapedDomain.ReplaceChar('/', '\\');
+ }
+ path.Append(escapedDomain);
+ path.AppendLiteral(")/worker(");
+ NS_ConvertUTF16toUTF8 escapedURL(mWorkerPrivate->ScriptURL());
+ escapedURL.ReplaceChar('/', '\\');
+ path.Append(escapedURL);
+ }
+ path.AppendPrintf(", 0x%p)/", static_cast<void*>(mWorkerPrivate));
+
+ runnable = new CollectReportsRunnable(mWorkerPrivate, aHandleReport, aData,
+ aAnonymize, path);
+ }
+
+ if (!runnable->Dispatch()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+WorkerPrivate::MemoryReporter::CollectReportsRunnable::CollectReportsRunnable(
+ WorkerPrivate* aWorkerPrivate, nsIHandleReportCallback* aHandleReport,
+ nsISupports* aHandlerData, bool aAnonymize, const nsACString& aPath)
+ : MainThreadWorkerControlRunnable(aWorkerPrivate),
+ mFinishCollectRunnable(new FinishCollectRunnable(
+ aHandleReport, aHandlerData, aAnonymize, aPath)),
+ mAnonymize(aAnonymize) {}
+
+bool WorkerPrivate::MemoryReporter::CollectReportsRunnable::WorkerRun(
+ JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<WorkerGlobalScope> scope = aWorkerPrivate->GlobalScope();
+ RefPtr<Performance> performance =
+ scope ? scope->GetPerformanceIfExists() : nullptr;
+ if (performance) {
+ size_t userEntries = performance->SizeOfUserEntries(JsWorkerMallocSizeOf);
+ size_t resourceEntries =
+ performance->SizeOfResourceEntries(JsWorkerMallocSizeOf);
+ mFinishCollectRunnable->SetPerformanceSizes(userEntries, resourceEntries);
+ }
+
+ mFinishCollectRunnable->SetSuccess(aWorkerPrivate->CollectRuntimeStats(
+ &mFinishCollectRunnable->mCxStats, mAnonymize));
+
+ return true;
+}
+
+WorkerPrivate::MemoryReporter::FinishCollectRunnable::FinishCollectRunnable(
+ nsIHandleReportCallback* aHandleReport, nsISupports* aHandlerData,
+ bool aAnonymize, const nsACString& aPath)
+ : mozilla::Runnable(
+ "dom::WorkerPrivate::MemoryReporter::FinishCollectRunnable"),
+ mHandleReport(aHandleReport),
+ mHandlerData(aHandlerData),
+ mPerformanceUserEntries(0),
+ mPerformanceResourceEntries(0),
+ mAnonymize(aAnonymize),
+ mSuccess(false),
+ mCxStats(aPath) {}
+
+NS_IMETHODIMP
+WorkerPrivate::MemoryReporter::FinishCollectRunnable::Run() {
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIMemoryReporterManager> manager =
+ do_GetService("@mozilla.org/memory-reporter-manager;1");
+
+ if (!manager) return NS_OK;
+
+ if (mSuccess) {
+ xpc::ReportJSRuntimeExplicitTreeStats(
+ mCxStats, mCxStats.Path(), mHandleReport, mHandlerData, mAnonymize);
+
+ if (mPerformanceUserEntries) {
+ nsCString path = mCxStats.Path();
+ path.AppendLiteral("dom/performance/user-entries");
+ mHandleReport->Callback(""_ns, path, nsIMemoryReporter::KIND_HEAP,
+ nsIMemoryReporter::UNITS_BYTES,
+ static_cast<int64_t>(mPerformanceUserEntries),
+ "Memory used for performance user entries."_ns,
+ mHandlerData);
+ }
+
+ if (mPerformanceResourceEntries) {
+ nsCString path = mCxStats.Path();
+ path.AppendLiteral("dom/performance/resource-entries");
+ mHandleReport->Callback(
+ ""_ns, path, nsIMemoryReporter::KIND_HEAP,
+ nsIMemoryReporter::UNITS_BYTES,
+ static_cast<int64_t>(mPerformanceResourceEntries),
+ "Memory used for performance resource entries."_ns, mHandlerData);
+ }
+ }
+
+ manager->EndReport();
+
+ return NS_OK;
+}
+
+WorkerPrivate::SyncLoopInfo::SyncLoopInfo(EventTarget* aEventTarget)
+ : mEventTarget(aEventTarget),
+ mResult(NS_ERROR_FAILURE),
+ mCompleted(false)
+#ifdef DEBUG
+ ,
+ mHasRun(false)
+#endif
+{
+}
+
+Document* WorkerPrivate::GetDocument() const {
+ AssertIsOnMainThread();
+ if (nsPIDOMWindowInner* window = GetAncestorWindow()) {
+ return window->GetExtantDoc();
+ }
+ // couldn't query a document, give up and return nullptr
+ return nullptr;
+}
+
+nsPIDOMWindowInner* WorkerPrivate::GetAncestorWindow() const {
+ AssertIsOnMainThread();
+ if (mLoadInfo.mWindow) {
+ return mLoadInfo.mWindow;
+ }
+ // if we don't have a document, we should query the document
+ // from the parent in case of a nested worker
+ WorkerPrivate* parent = mParent;
+ while (parent) {
+ if (parent->mLoadInfo.mWindow) {
+ return parent->mLoadInfo.mWindow;
+ }
+ parent = parent->GetParent();
+ }
+ // couldn't query a window, give up and return nullptr
+ return nullptr;
+}
+
+class EvictFromBFCacheRunnable final : public WorkerProxyToMainThreadRunnable {
+ public:
+ void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override {
+ MOZ_ASSERT(aWorkerPrivate);
+ AssertIsOnMainThread();
+ if (nsCOMPtr<nsPIDOMWindowInner> win =
+ aWorkerPrivate->GetAncestorWindow()) {
+ win->RemoveFromBFCacheSync();
+ }
+ }
+
+ void RunBackOnWorkerThreadForCleanup(WorkerPrivate* aWorkerPrivate) override {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+};
+
+void WorkerPrivate::EvictFromBFCache() {
+ AssertIsOnWorkerThread();
+ RefPtr<EvictFromBFCacheRunnable> runnable = new EvictFromBFCacheRunnable();
+ runnable->Dispatch(this);
+}
+
+void WorkerPrivate::SetCsp(nsIContentSecurityPolicy* aCSP) {
+ AssertIsOnMainThread();
+ if (!aCSP) {
+ return;
+ }
+ aCSP->EnsureEventTarget(mMainThreadEventTarget);
+
+ mLoadInfo.mCSP = aCSP;
+ mLoadInfo.mCSPInfo = MakeUnique<CSPInfo>();
+ nsresult rv = CSPToCSPInfo(mLoadInfo.mCSP, mLoadInfo.mCSPInfo.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+}
+
+nsresult WorkerPrivate::SetCSPFromHeaderValues(
+ const nsACString& aCSPHeaderValue,
+ const nsACString& aCSPReportOnlyHeaderValue) {
+ AssertIsOnMainThread();
+ MOZ_DIAGNOSTIC_ASSERT(!mLoadInfo.mCSP);
+
+ NS_ConvertASCIItoUTF16 cspHeaderValue(aCSPHeaderValue);
+ NS_ConvertASCIItoUTF16 cspROHeaderValue(aCSPReportOnlyHeaderValue);
+
+ nsresult rv;
+ nsCOMPtr<nsIContentSecurityPolicy> csp = new nsCSPContext();
+
+ // First, we try to query the URI from the Principal, but
+ // in case selfURI remains empty (e.g in case the Principal
+ // is a SystemPrincipal) then we fall back and use the
+ // base URI as selfURI for CSP.
+ nsCOMPtr<nsIURI> selfURI;
+ // Its not recommended to use the BasePrincipal to get the URI
+ // but in this case we need to make an exception
+ auto* basePrin = BasePrincipal::Cast(mLoadInfo.mPrincipal);
+ if (basePrin) {
+ basePrin->GetURI(getter_AddRefs(selfURI));
+ }
+ if (!selfURI) {
+ selfURI = mLoadInfo.mBaseURI;
+ }
+ MOZ_ASSERT(selfURI, "need a self URI for CSP");
+
+ rv = csp->SetRequestContextWithPrincipal(mLoadInfo.mPrincipal, selfURI,
+ u""_ns, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ csp->EnsureEventTarget(mMainThreadEventTarget);
+
+ // If there's a CSP header, apply it.
+ if (!cspHeaderValue.IsEmpty()) {
+ rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // If there's a report-only CSP header, apply it.
+ if (!cspROHeaderValue.IsEmpty()) {
+ rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ RefPtr<extensions::WebExtensionPolicy> addonPolicy;
+
+ if (basePrin) {
+ addonPolicy = basePrin->AddonPolicy();
+ }
+
+ // For extension workers there aren't any csp header values,
+ // instead it will inherit the Extension CSP.
+ if (addonPolicy) {
+ csp->AppendPolicy(addonPolicy->BaseCSP(), false, false);
+ csp->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
+ }
+
+ mLoadInfo.mCSP = csp;
+
+ // Set evalAllowed, default value is set in GetAllowsEval
+ bool evalAllowed = false;
+ bool reportEvalViolations = false;
+ rv = csp->GetAllowsEval(&reportEvalViolations, &evalAllowed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mLoadInfo.mEvalAllowed = evalAllowed;
+ mLoadInfo.mReportEvalCSPViolations = reportEvalViolations;
+
+ // Set wasmEvalAllowed
+ bool wasmEvalAllowed = false;
+ bool reportWasmEvalViolations = false;
+ rv = csp->GetAllowsWasmEval(&reportWasmEvalViolations, &wasmEvalAllowed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // As for nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction,
+ // for MV2 extensions we have to allow wasm by default and report violations
+ // for historical reasons.
+ // TODO bug 1770909: remove this exception.
+ if (!wasmEvalAllowed && addonPolicy && addonPolicy->ManifestVersion() == 2) {
+ wasmEvalAllowed = true;
+ reportWasmEvalViolations = true;
+ }
+
+ mLoadInfo.mWasmEvalAllowed = wasmEvalAllowed;
+ mLoadInfo.mReportWasmEvalCSPViolations = reportWasmEvalViolations;
+
+ mLoadInfo.mCSPInfo = MakeUnique<CSPInfo>();
+ rv = CSPToCSPInfo(csp, mLoadInfo.mCSPInfo.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+void WorkerPrivate::StoreCSPOnClient() {
+ auto data = mWorkerThreadAccessible.Access();
+ MOZ_ASSERT(data->mScope);
+ if (mLoadInfo.mCSPInfo) {
+ data->mScope->MutableClientSourceRef().SetCspInfo(*mLoadInfo.mCSPInfo);
+ }
+}
+
+void WorkerPrivate::UpdateReferrerInfoFromHeader(
+ const nsACString& aReferrerPolicyHeaderValue) {
+ NS_ConvertUTF8toUTF16 headerValue(aReferrerPolicyHeaderValue);
+
+ if (headerValue.IsEmpty()) {
+ return;
+ }
+
+ ReferrerPolicy policy =
+ ReferrerInfo::ReferrerPolicyFromHeaderString(headerValue);
+ if (policy == ReferrerPolicy::_empty) {
+ return;
+ }
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ static_cast<ReferrerInfo*>(GetReferrerInfo())->CloneWithNewPolicy(policy);
+ SetReferrerInfo(referrerInfo);
+}
+
+void WorkerPrivate::Traverse(nsCycleCollectionTraversalCallback& aCb) {
+ AssertIsOnParentThread();
+
+ // The WorkerPrivate::mParentEventTargetRef has a reference to the exposed
+ // Worker object, which is really held by the worker thread. We traverse this
+ // reference if and only if our busy count is zero and we have not released
+ // the main thread reference. We do not unlink it. This allows the CC to
+ // break cycles involving the Worker and begin shutting it down (which does
+ // happen in unlink) but ensures that the WorkerPrivate won't be deleted
+ // before we're done shutting down the thread.
+ if (!mBusyCount && !mMainThreadObjectsForgotten) {
+ nsCycleCollectionTraversalCallback& cb = aCb;
+ WorkerPrivate* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentEventTargetRef);
+ }
+}
+
+nsresult WorkerPrivate::Dispatch(already_AddRefed<WorkerRunnable> aRunnable,
+ nsIEventTarget* aSyncLoopTarget) {
+ // May be called on any thread!
+ MutexAutoLock lock(mMutex);
+ return DispatchLockHeld(std::move(aRunnable), aSyncLoopTarget, lock);
+}
+
+nsresult WorkerPrivate::DispatchLockHeld(
+ already_AddRefed<WorkerRunnable> aRunnable, nsIEventTarget* aSyncLoopTarget,
+ const MutexAutoLock& aProofOfLock) {
+ // May be called on any thread!
+ RefPtr<WorkerRunnable> runnable(aRunnable);
+
+ MOZ_ASSERT_IF(aSyncLoopTarget, mThread);
+
+ if (mStatus == Dead || (!aSyncLoopTarget && ParentStatus() > Running)) {
+ NS_WARNING(
+ "A runnable was posted to a worker that is already shutting "
+ "down!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (runnable->IsDebuggeeRunnable() && !mDebuggerReady) {
+ MOZ_RELEASE_ASSERT(!aSyncLoopTarget);
+ mDelayedDebuggeeRunnables.AppendElement(runnable);
+ return NS_OK;
+ }
+
+ if (!mThread) {
+ if (ParentStatus() == Pending || mStatus == Pending) {
+ mPreStartRunnables.AppendElement(runnable);
+ return NS_OK;
+ }
+
+ NS_WARNING(
+ "Using a worker event target after the thread has already"
+ "been released!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv;
+ if (aSyncLoopTarget) {
+ rv = aSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ } else {
+ // WorkerDebuggeeRunnables don't need any special treatment here. True,
+ // they should not be delivered to a frozen worker. But frozen workers
+ // aren't drawing from the thread's main event queue anyway, only from
+ // mControlQueue.
+ rv = mThread->DispatchAnyThread(WorkerThreadFriendKey(), runnable.forget());
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mCondVar.Notify();
+ return NS_OK;
+}
+
+void WorkerPrivate::EnableDebugger() {
+ AssertIsOnParentThread();
+
+ if (NS_FAILED(RegisterWorkerDebugger(this))) {
+ NS_WARNING("Failed to register worker debugger!");
+ return;
+ }
+}
+
+void WorkerPrivate::DisableDebugger() {
+ AssertIsOnParentThread();
+
+ // RegisterDebuggerMainThreadRunnable might be dispatched but not executed.
+ // Wait for its execution before unregistraion.
+ if (!NS_IsMainThread()) {
+ WaitForIsDebuggerRegistered(true);
+ }
+
+ if (NS_FAILED(UnregisterWorkerDebugger(this))) {
+ NS_WARNING("Failed to unregister worker debugger!");
+ }
+}
+
+nsresult WorkerPrivate::DispatchControlRunnable(
+ already_AddRefed<WorkerControlRunnable> aWorkerControlRunnable) {
+ // May be called on any thread!
+ RefPtr<WorkerControlRunnable> runnable(aWorkerControlRunnable);
+ MOZ_ASSERT(runnable);
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mStatus == Dead) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Transfer ownership to the control queue.
+ mControlQueue.Push(runnable.forget().take());
+
+ if (JSContext* cx = mJSContext) {
+ MOZ_ASSERT(mThread);
+ JS_RequestInterruptCallback(cx);
+ }
+
+ mCondVar.Notify();
+ }
+
+ return NS_OK;
+}
+
+nsresult WorkerPrivate::DispatchDebuggerRunnable(
+ already_AddRefed<WorkerRunnable> aDebuggerRunnable) {
+ // May be called on any thread!
+
+ RefPtr<WorkerRunnable> runnable(aDebuggerRunnable);
+
+ MOZ_ASSERT(runnable);
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mStatus == Dead) {
+ NS_WARNING(
+ "A debugger runnable was posted to a worker that is already "
+ "shutting down!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Transfer ownership to the debugger queue.
+ mDebuggerQueue.Push(runnable.forget().take());
+
+ mCondVar.Notify();
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<WorkerRunnable> WorkerPrivate::MaybeWrapAsWorkerRunnable(
+ already_AddRefed<nsIRunnable> aRunnable) {
+ // May be called on any thread!
+
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+ MOZ_ASSERT(runnable);
+
+ RefPtr<WorkerRunnable> workerRunnable =
+ WorkerRunnable::FromRunnable(runnable);
+ if (workerRunnable) {
+ return workerRunnable.forget();
+ }
+
+ nsCOMPtr<nsIDiscardableRunnable> maybe = do_QueryInterface(runnable);
+ if (!maybe) {
+ MOZ_CRASH(
+ "All runnables destined for a worker thread must be "
+ "nsIDiscardableRunnable!");
+ }
+
+ workerRunnable = new ExternalRunnableWrapper(this, runnable);
+ return workerRunnable.forget();
+}
+
+bool WorkerPrivate::Start() {
+ // May be called on any thread!
+ {
+ MutexAutoLock lock(mMutex);
+ NS_ASSERTION(mParentStatus != Running, "How can this be?!");
+
+ if (mParentStatus == Pending) {
+ mParentStatus = Running;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// aCx is null when called from the finalizer
+bool WorkerPrivate::Notify(WorkerStatus aStatus) {
+ AssertIsOnParentThread();
+ // This method is only called for Canceling or later.
+ MOZ_DIAGNOSTIC_ASSERT(aStatus >= Canceling);
+
+ bool pending;
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mParentStatus >= aStatus) {
+ return true;
+ }
+
+ pending = mParentStatus == Pending;
+ mParentStatus = aStatus;
+ }
+
+ if (mCancellationCallback) {
+ mCancellationCallback(!pending);
+ mCancellationCallback = nullptr;
+ }
+
+ if (pending) {
+#ifdef DEBUG
+ {
+ // Fake a thread here just so that our assertions don't go off for no
+ // reason.
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+
+ MOZ_ASSERT(!mPRThread);
+ mPRThread = PRThreadFromThread(currentThread);
+ MOZ_ASSERT(mPRThread);
+ }
+#endif
+
+ // Worker never got a chance to run, go ahead and delete it.
+ ScheduleDeletion(WorkerPrivate::WorkerNeverRan);
+ return true;
+ }
+
+ // No Canceling timeout is needed.
+ if (mCancelingTimer) {
+ mCancelingTimer->Cancel();
+ mCancelingTimer = nullptr;
+ }
+
+ RefPtr<NotifyRunnable> runnable = new NotifyRunnable(this, aStatus);
+ return runnable->Dispatch();
+}
+
+bool WorkerPrivate::Freeze(const nsPIDOMWindowInner* aWindow) {
+ AssertIsOnParentThread();
+
+ mParentFrozen = true;
+
+ // WorkerDebuggeeRunnables sent from a worker to content must not be delivered
+ // while the worker is frozen.
+ //
+ // Since a top-level worker and all its children share the same
+ // mMainThreadDebuggeeEventTarget, it's sufficient to do this only in the
+ // top-level worker.
+ if (aWindow) {
+ // This is called from WorkerPrivate construction, and We may not have
+ // allocated mMainThreadDebuggeeEventTarget yet.
+ if (mMainThreadDebuggeeEventTarget) {
+ // Pausing a ThrottledEventQueue is infallible.
+ MOZ_ALWAYS_SUCCEEDS(mMainThreadDebuggeeEventTarget->SetIsPaused(true));
+ }
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mParentStatus >= Canceling) {
+ return true;
+ }
+ }
+
+ DisableDebugger();
+
+ RefPtr<FreezeRunnable> runnable = new FreezeRunnable(this);
+ return runnable->Dispatch();
+}
+
+bool WorkerPrivate::Thaw(const nsPIDOMWindowInner* aWindow) {
+ AssertIsOnParentThread();
+ MOZ_ASSERT(mParentFrozen);
+
+ mParentFrozen = false;
+
+ // Delivery of WorkerDebuggeeRunnables to the window may resume.
+ //
+ // Since a top-level worker and all its children share the same
+ // mMainThreadDebuggeeEventTarget, it's sufficient to do this only in the
+ // top-level worker.
+ if (aWindow) {
+ // Since the worker is no longer frozen, only a paused parent window should
+ // require the queue to remain paused.
+ //
+ // This can only fail if the ThrottledEventQueue cannot dispatch its
+ // executor to the main thread, in which case the main thread was never
+ // going to draw runnables from it anyway, so the failure doesn't matter.
+ Unused << mMainThreadDebuggeeEventTarget->SetIsPaused(
+ IsParentWindowPaused());
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mParentStatus >= Canceling) {
+ return true;
+ }
+ }
+
+ EnableDebugger();
+
+ RefPtr<ThawRunnable> runnable = new ThawRunnable(this);
+ return runnable->Dispatch();
+}
+
+void WorkerPrivate::ParentWindowPaused() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mParentWindowPaused);
+ mParentWindowPaused = true;
+
+ // This is called from WorkerPrivate construction, and we may not have
+ // allocated mMainThreadDebuggeeEventTarget yet.
+ if (mMainThreadDebuggeeEventTarget) {
+ // Pausing a ThrottledEventQueue is infallible.
+ MOZ_ALWAYS_SUCCEEDS(mMainThreadDebuggeeEventTarget->SetIsPaused(true));
+ }
+}
+
+void WorkerPrivate::ParentWindowResumed() {
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(mParentWindowPaused);
+ mParentWindowPaused = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mParentStatus >= Canceling) {
+ return;
+ }
+ }
+
+ // Since the window is no longer paused, the queue should only remain paused
+ // if the worker is frozen.
+ //
+ // This can only fail if the ThrottledEventQueue cannot dispatch its executor
+ // to the main thread, in which case the main thread was never going to draw
+ // runnables from it anyway, so the failure doesn't matter.
+ Unused << mMainThreadDebuggeeEventTarget->SetIsPaused(IsFrozen());
+}
+
+void WorkerPrivate::PropagateStorageAccessPermissionGranted() {
+ AssertIsOnParentThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mParentStatus >= Canceling) {
+ return;
+ }
+ }
+
+ RefPtr<PropagateStorageAccessPermissionGrantedRunnable> runnable =
+ new PropagateStorageAccessPermissionGrantedRunnable(this);
+ Unused << NS_WARN_IF(!runnable->Dispatch());
+}
+
+bool WorkerPrivate::Close() {
+ mMutex.AssertCurrentThreadOwns();
+ if (mParentStatus < Closing) {
+ mParentStatus = Closing;
+ }
+
+ return true;
+}
+
+bool WorkerPrivate::ModifyBusyCount(bool aIncrease) {
+ AssertIsOnParentThread();
+
+ MOZ_ASSERT(aIncrease || mBusyCount, "Mismatched busy count mods!");
+
+ if (aIncrease) {
+ mBusyCount++;
+ return true;
+ }
+
+ if (--mBusyCount == 0) {
+ bool shouldCancel;
+ {
+ MutexAutoLock lock(mMutex);
+ shouldCancel = mParentStatus == Canceling;
+ }
+
+ if (shouldCancel && !Cancel()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool WorkerPrivate::ProxyReleaseMainThreadObjects() {
+ AssertIsOnParentThread();
+ MOZ_ASSERT(!mMainThreadObjectsForgotten);
+
+ nsCOMPtr<nsILoadGroup> loadGroupToCancel;
+ // If we're not overriden, then do nothing here. Let the load group get
+ // handled in ForgetMainThreadObjects().
+ if (mLoadInfo.mInterfaceRequestor) {
+ mLoadInfo.mLoadGroup.swap(loadGroupToCancel);
+ }
+
+ bool result = mLoadInfo.ProxyReleaseMainThreadObjects(
+ this, std::move(loadGroupToCancel));
+
+ mMainThreadObjectsForgotten = true;
+
+ return result;
+}
+
+void WorkerPrivate::UpdateContextOptions(
+ const JS::ContextOptions& aContextOptions) {
+ AssertIsOnParentThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+ mJSSettings.contextOptions = aContextOptions;
+ }
+
+ RefPtr<UpdateContextOptionsRunnable> runnable =
+ new UpdateContextOptionsRunnable(this, aContextOptions);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to update worker context options!");
+ }
+}
+
+void WorkerPrivate::UpdateLanguages(const nsTArray<nsString>& aLanguages) {
+ AssertIsOnParentThread();
+
+ RefPtr<UpdateLanguagesRunnable> runnable =
+ new UpdateLanguagesRunnable(this, aLanguages);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to update worker languages!");
+ }
+}
+
+void WorkerPrivate::UpdateJSWorkerMemoryParameter(JSGCParamKey aKey,
+ Maybe<uint32_t> aValue) {
+ AssertIsOnParentThread();
+
+ bool changed = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+ changed = mJSSettings.ApplyGCSetting(aKey, aValue);
+ }
+
+ if (changed) {
+ RefPtr<UpdateJSWorkerMemoryParameterRunnable> runnable =
+ new UpdateJSWorkerMemoryParameterRunnable(this, aKey, aValue);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to update memory parameter!");
+ }
+ }
+}
+
+#ifdef JS_GC_ZEAL
+void WorkerPrivate::UpdateGCZeal(uint8_t aGCZeal, uint32_t aFrequency) {
+ AssertIsOnParentThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+ mJSSettings.gcZeal = aGCZeal;
+ mJSSettings.gcZealFrequency = aFrequency;
+ }
+
+ RefPtr<UpdateGCZealRunnable> runnable =
+ new UpdateGCZealRunnable(this, aGCZeal, aFrequency);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to update worker gczeal!");
+ }
+}
+#endif
+
+void WorkerPrivate::SetLowMemoryState(bool aState) {
+ AssertIsOnParentThread();
+
+ RefPtr<SetLowMemoryStateRunnable> runnable =
+ new SetLowMemoryStateRunnable(this, aState);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to set low memory state!");
+ }
+}
+
+void WorkerPrivate::GarbageCollect(bool aShrinking) {
+ AssertIsOnParentThread();
+
+ RefPtr<GarbageCollectRunnable> runnable = new GarbageCollectRunnable(
+ this, aShrinking, /* aCollectChildren = */ true);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to GC worker!");
+ }
+}
+
+void WorkerPrivate::CycleCollect() {
+ AssertIsOnParentThread();
+
+ RefPtr<CycleCollectRunnable> runnable =
+ new CycleCollectRunnable(this, /* aCollectChildren = */ true);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to CC worker!");
+ }
+}
+
+void WorkerPrivate::OfflineStatusChangeEvent(bool aIsOffline) {
+ AssertIsOnParentThread();
+
+ RefPtr<OfflineStatusChangeRunnable> runnable =
+ new OfflineStatusChangeRunnable(this, aIsOffline);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to dispatch offline status change event!");
+ }
+}
+
+void WorkerPrivate::OfflineStatusChangeEventInternal(bool aIsOffline) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ // The worker is already in this state. No need to dispatch an event.
+ if (data->mOnLine == !aIsOffline) {
+ return;
+ }
+
+ for (uint32_t index = 0; index < data->mChildWorkers.Length(); ++index) {
+ data->mChildWorkers[index]->OfflineStatusChangeEvent(aIsOffline);
+ }
+
+ data->mOnLine = !aIsOffline;
+ WorkerGlobalScope* globalScope = GlobalScope();
+ RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
+ if (nav) {
+ nav->SetOnLine(data->mOnLine);
+ }
+
+ nsString eventType;
+ if (aIsOffline) {
+ eventType.AssignLiteral("offline");
+ } else {
+ eventType.AssignLiteral("online");
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(globalScope, nullptr, nullptr);
+
+ event->InitEvent(eventType, false, false);
+ event->SetTrusted(true);
+
+ globalScope->DispatchEvent(*event);
+}
+
+void WorkerPrivate::MemoryPressure() {
+ AssertIsOnParentThread();
+
+ RefPtr<MemoryPressureRunnable> runnable = new MemoryPressureRunnable(this);
+ Unused << NS_WARN_IF(!runnable->Dispatch());
+}
+
+RefPtr<WorkerPrivate::JSMemoryUsagePromise> WorkerPrivate::GetJSMemoryUsage() {
+ AssertIsOnMainThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+ // If we have started shutting down the worker, do not dispatch a runnable
+ // to measure its memory.
+ if (ParentStatus() > Running) {
+ return nullptr;
+ }
+ }
+
+ return InvokeAsync(ControlEventTarget(), __func__, []() {
+ WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(wp);
+ wp->AssertIsOnWorkerThread();
+ MutexAutoLock lock(wp->mMutex);
+ return JSMemoryUsagePromise::CreateAndResolve(
+ js::GetGCHeapUsage(wp->mJSContext), __func__);
+ });
+}
+
+void WorkerPrivate::WorkerScriptLoaded() {
+ AssertIsOnMainThread();
+
+ if (IsSharedWorker() || IsServiceWorker()) {
+ // No longer need to hold references to the window or document we came from.
+ mLoadInfo.mWindow = nullptr;
+ mLoadInfo.mScriptContext = nullptr;
+ }
+}
+
+void WorkerPrivate::SetBaseURI(nsIURI* aBaseURI) {
+ AssertIsOnMainThread();
+
+ if (!mLoadInfo.mBaseURI) {
+ NS_ASSERTION(GetParent(), "Shouldn't happen without a parent!");
+ mLoadInfo.mResolvedScriptURI = aBaseURI;
+ }
+
+ mLoadInfo.mBaseURI = aBaseURI;
+
+ if (NS_FAILED(aBaseURI->GetSpec(mLocationInfo.mHref))) {
+ mLocationInfo.mHref.Truncate();
+ }
+
+ mLocationInfo.mHostname.Truncate();
+ nsContentUtils::GetHostOrIPv6WithBrackets(aBaseURI, mLocationInfo.mHostname);
+
+ nsCOMPtr<nsIURL> url(do_QueryInterface(aBaseURI));
+ if (!url || NS_FAILED(url->GetFilePath(mLocationInfo.mPathname))) {
+ mLocationInfo.mPathname.Truncate();
+ }
+
+ nsCString temp;
+
+ if (url && NS_SUCCEEDED(url->GetQuery(temp)) && !temp.IsEmpty()) {
+ mLocationInfo.mSearch.Assign('?');
+ mLocationInfo.mSearch.Append(temp);
+ }
+
+ if (NS_SUCCEEDED(aBaseURI->GetRef(temp)) && !temp.IsEmpty()) {
+ if (mLocationInfo.mHash.IsEmpty()) {
+ mLocationInfo.mHash.Assign('#');
+ mLocationInfo.mHash.Append(temp);
+ }
+ }
+
+ if (NS_SUCCEEDED(aBaseURI->GetScheme(mLocationInfo.mProtocol))) {
+ mLocationInfo.mProtocol.Append(':');
+ } else {
+ mLocationInfo.mProtocol.Truncate();
+ }
+
+ int32_t port;
+ if (NS_SUCCEEDED(aBaseURI->GetPort(&port)) && port != -1) {
+ mLocationInfo.mPort.AppendInt(port);
+
+ nsAutoCString host(mLocationInfo.mHostname);
+ host.Append(':');
+ host.Append(mLocationInfo.mPort);
+
+ mLocationInfo.mHost.Assign(host);
+ } else {
+ mLocationInfo.mHost.Assign(mLocationInfo.mHostname);
+ }
+
+ nsContentUtils::GetUTFOrigin(aBaseURI, mLocationInfo.mOrigin);
+}
+
+nsresult WorkerPrivate::SetPrincipalsAndCSPOnMainThread(
+ nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal,
+ nsILoadGroup* aLoadGroup, nsIContentSecurityPolicy* aCsp) {
+ return mLoadInfo.SetPrincipalsAndCSPOnMainThread(
+ aPrincipal, aPartitionedPrincipal, aLoadGroup, aCsp);
+}
+
+nsresult WorkerPrivate::SetPrincipalsAndCSPFromChannel(nsIChannel* aChannel) {
+ return mLoadInfo.SetPrincipalsAndCSPFromChannel(aChannel);
+}
+
+bool WorkerPrivate::FinalChannelPrincipalIsValid(nsIChannel* aChannel) {
+ return mLoadInfo.FinalChannelPrincipalIsValid(aChannel);
+}
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+bool WorkerPrivate::PrincipalURIMatchesScriptURL() {
+ return mLoadInfo.PrincipalURIMatchesScriptURL();
+}
+#endif
+
+void WorkerPrivate::UpdateOverridenLoadGroup(nsILoadGroup* aBaseLoadGroup) {
+ AssertIsOnMainThread();
+
+ // The load group should have been overriden at init time.
+ mLoadInfo.mInterfaceRequestor->MaybeAddBrowserChild(aBaseLoadGroup);
+}
+
+#ifdef DEBUG
+
+void WorkerPrivate::AssertIsOnParentThread() const {
+ if (GetParent()) {
+ GetParent()->AssertIsOnWorkerThread();
+ } else {
+ AssertIsOnMainThread();
+ }
+}
+
+void WorkerPrivate::AssertInnerWindowIsCorrect() const {
+ AssertIsOnParentThread();
+
+ // Only care about top level workers from windows.
+ if (mParent || !mLoadInfo.mWindow) {
+ return;
+ }
+
+ AssertIsOnMainThread();
+
+ nsPIDOMWindowOuter* outer = mLoadInfo.mWindow->GetOuterWindow();
+ NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mLoadInfo.mWindow,
+ "Inner window no longer correct!");
+}
+
+#endif
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+bool WorkerPrivate::PrincipalIsValid() const {
+ return mLoadInfo.PrincipalIsValid();
+}
+#endif
+
+WorkerPrivate::WorkerThreadAccessible::WorkerThreadAccessible(
+ WorkerPrivate* const aParent)
+ : mNumWorkerRefsPreventingShutdownStart(0),
+ mDebuggerEventLoopLevel(0),
+ mErrorHandlerRecursionCount(0),
+ mNextTimeoutId(1),
+ mCurrentTimerNestingLevel(0),
+ mFrozen(false),
+ mTimerRunning(false),
+ mRunningExpiredTimeouts(false),
+ mPeriodicGCTimerRunning(false),
+ mIdleGCTimerRunning(false),
+ mOnLine(aParent ? aParent->OnLine() : !NS_IsOffline()),
+ mJSThreadExecutionGranted(false),
+ mCCCollectedAnything(false) {}
+
+namespace {
+
+bool IsNewWorkerSecureContext(const WorkerPrivate* const aParent,
+ const WorkerKind aWorkerKind,
+ const WorkerLoadInfo& aLoadInfo) {
+ if (aParent) {
+ return aParent->IsSecureContext();
+ }
+
+ // Our secure context state depends on the kind of worker we have.
+
+ if (aLoadInfo.mPrincipal && aLoadInfo.mPrincipal->IsSystemPrincipal()) {
+ return true;
+ }
+
+ if (aWorkerKind == WorkerKindService) {
+ return true;
+ }
+
+ if (aLoadInfo.mSecureContext != WorkerLoadInfo::eNotSet) {
+ return aLoadInfo.mSecureContext == WorkerLoadInfo::eSecureContext;
+ }
+
+ MOZ_ASSERT_UNREACHABLE(
+ "non-chrome worker that is not a service worker "
+ "that has no parent and no associated window");
+
+ return false;
+}
+
+} // namespace
+
+WorkerPrivate::WorkerPrivate(
+ WorkerPrivate* aParent, const nsAString& aScriptURL, bool aIsChromeWorker,
+ WorkerKind aWorkerKind, RequestCredentials aRequestCredentials,
+ enum WorkerType aWorkerType, const nsAString& aWorkerName,
+ const nsACString& aServiceWorkerScope, WorkerLoadInfo& aLoadInfo,
+ nsString&& aId, const nsID& aAgentClusterId,
+ const nsILoadInfo::CrossOriginOpenerPolicy aAgentClusterOpenerPolicy,
+ CancellationCallback&& aCancellationCallback,
+ TerminationCallback&& aTerminationCallback)
+ : mMutex("WorkerPrivate Mutex"),
+ mCondVar(mMutex, "WorkerPrivate CondVar"),
+ mParent(aParent),
+ mScriptURL(aScriptURL),
+ mWorkerName(aWorkerName),
+ mCredentialsMode(aRequestCredentials),
+ mWorkerType(aWorkerType), // If the worker runs as a script or a module
+ mWorkerKind(aWorkerKind),
+ mCancellationCallback(std::move(aCancellationCallback)),
+ mTerminationCallback(std::move(aTerminationCallback)),
+ mLoadInfo(std::move(aLoadInfo)),
+ mDebugger(nullptr),
+ mJSContext(nullptr),
+ mPRThread(nullptr),
+ mWorkerControlEventTarget(new WorkerEventTarget(
+ this, WorkerEventTarget::Behavior::ControlOnly)),
+ mWorkerHybridEventTarget(
+ new WorkerEventTarget(this, WorkerEventTarget::Behavior::Hybrid)),
+ mParentStatus(Pending),
+ mStatus(Pending),
+ mBusyCount(0),
+ mCreationTimeStamp(TimeStamp::Now()),
+ mCreationTimeHighRes((double)PR_Now() / PR_USEC_PER_MSEC),
+ mReportedUseCounters(false),
+ mAgentClusterId(aAgentClusterId),
+ mWorkerThreadAccessible(aParent),
+ mPostSyncLoopOperations(0),
+ mParentWindowPaused(false),
+ mCancelAllPendingRunnables(false),
+ mWorkerScriptExecutedSuccessfully(false),
+ mFetchHandlerWasAdded(false),
+ mMainThreadObjectsForgotten(false),
+ mIsChromeWorker(aIsChromeWorker),
+ mParentFrozen(false),
+ mIsSecureContext(
+ IsNewWorkerSecureContext(mParent, mWorkerKind, mLoadInfo)),
+ mDebuggerRegistered(false),
+ mDebuggerReady(true),
+ mExtensionAPIAllowed(false),
+ mIsInAutomation(false),
+ mId(std::move(aId)),
+ mAgentClusterOpenerPolicy(aAgentClusterOpenerPolicy),
+ mIsPrivilegedAddonGlobal(false),
+ mTopLevelWorkerFinishedRunnableCount(0),
+ mWorkerFinishedRunnableCount(0) {
+ MOZ_ASSERT_IF(!IsDedicatedWorker(), NS_IsMainThread());
+
+ if (aParent) {
+ aParent->AssertIsOnWorkerThread();
+
+ // Note that this copies our parent's secure context state into mJSSettings.
+ aParent->CopyJSSettings(mJSSettings);
+
+ MOZ_ASSERT_IF(mIsChromeWorker, mIsSecureContext);
+
+ mIsInAutomation = aParent->IsInAutomation();
+
+ MOZ_ASSERT(IsDedicatedWorker());
+
+ if (aParent->mParentFrozen) {
+ Freeze(nullptr);
+ }
+
+ mIsPrivilegedAddonGlobal = aParent->mIsPrivilegedAddonGlobal;
+ } else {
+ AssertIsOnMainThread();
+
+ RuntimeService::GetDefaultJSSettings(mJSSettings);
+
+ {
+ JS::RealmOptions& chromeRealmOptions = mJSSettings.chromeRealmOptions;
+ JS::RealmOptions& contentRealmOptions = mJSSettings.contentRealmOptions;
+
+ JS::RealmBehaviors& chromeRealmBehaviors = chromeRealmOptions.behaviors();
+ JS::RealmBehaviors& contentRealmBehaviors =
+ contentRealmOptions.behaviors();
+
+ bool usesSystemPrincipal = UsesSystemPrincipal();
+
+ // Make timing imprecise in unprivileged code to blunt Spectre timing
+ // attacks.
+ bool clampAndJitterTime = !usesSystemPrincipal;
+ chromeRealmBehaviors.setClampAndJitterTime(clampAndJitterTime)
+ .setShouldResistFingerprinting(false);
+ contentRealmBehaviors.setClampAndJitterTime(clampAndJitterTime)
+ .setShouldResistFingerprinting(mLoadInfo.mShouldResistFingerprinting);
+
+ JS::RealmCreationOptions& chromeCreationOptions =
+ chromeRealmOptions.creationOptions();
+ JS::RealmCreationOptions& contentCreationOptions =
+ contentRealmOptions.creationOptions();
+
+ // Expose uneval and toSource functions only if this is privileged code.
+ bool toSourceEnabled = usesSystemPrincipal;
+ chromeCreationOptions.setToSourceEnabled(toSourceEnabled);
+ contentCreationOptions.setToSourceEnabled(toSourceEnabled);
+
+ if (mIsSecureContext) {
+ chromeCreationOptions.setSecureContext(true);
+ contentCreationOptions.setSecureContext(true);
+ }
+
+ // Check if it's a privileged addon executing in order to allow access
+ // to SharedArrayBuffer
+ if (mLoadInfo.mPrincipal) {
+ if (auto* policy =
+ BasePrincipal::Cast(mLoadInfo.mPrincipal)->AddonPolicy()) {
+ if (policy->IsPrivileged() &&
+ ExtensionPolicyService::GetSingleton().IsExtensionProcess()) {
+ // Privileged extensions are allowed to use SharedArrayBuffer in
+ // their extension process, but never in content scripts in
+ // content processes.
+ mIsPrivilegedAddonGlobal = true;
+ }
+
+ if (StaticPrefs::
+ extensions_backgroundServiceWorker_enabled_AtStartup() &&
+ mWorkerKind == WorkerKindService &&
+ policy->IsManifestBackgroundWorker(mScriptURL)) {
+ // Only allows ExtensionAPI for extension service workers
+ // that are declared in the extension manifest json as
+ // the background service worker.
+ mExtensionAPIAllowed = true;
+ }
+ }
+ }
+
+ // The SharedArrayBuffer global constructor property should not be present
+ // in a fresh global object when shared memory objects aren't allowed
+ // (because COOP/COEP support isn't enabled, or because COOP/COEP don't
+ // act to isolate this worker to a separate process).
+ const bool defineSharedArrayBufferConstructor = IsSharedMemoryAllowed();
+ chromeCreationOptions.setDefineSharedArrayBufferConstructor(
+ defineSharedArrayBufferConstructor);
+ contentCreationOptions.setDefineSharedArrayBufferConstructor(
+ defineSharedArrayBufferConstructor);
+ }
+
+ mIsInAutomation = xpc::IsInAutomation();
+
+ // Our parent can get suspended after it initiates the async creation
+ // of a new worker thread. In this case suspend the new worker as well.
+ if (mLoadInfo.mWindow && mLoadInfo.mWindow->IsSuspended()) {
+ ParentWindowPaused();
+ }
+
+ if (mLoadInfo.mWindow && mLoadInfo.mWindow->IsFrozen()) {
+ Freeze(mLoadInfo.mWindow);
+ }
+ }
+
+ nsCOMPtr<nsISerialEventTarget> target;
+
+ // A child worker just inherits the parent workers ThrottledEventQueue
+ // and main thread target for now. This is mainly due to the restriction
+ // that ThrottledEventQueue can only be created on the main thread at the
+ // moment.
+ if (aParent) {
+ mMainThreadEventTargetForMessaging =
+ aParent->mMainThreadEventTargetForMessaging;
+ mMainThreadEventTarget = aParent->mMainThreadEventTarget;
+ mMainThreadDebuggeeEventTarget = aParent->mMainThreadDebuggeeEventTarget;
+ return;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ target = GetWindow()
+ ? GetWindow()->GetBrowsingContextGroup()->GetWorkerEventQueue()
+ : nullptr;
+
+ if (!target) {
+ target = GetMainThreadSerialEventTarget();
+ MOZ_DIAGNOSTIC_ASSERT(target);
+ }
+
+ // Throttle events to the main thread using a ThrottledEventQueue specific to
+ // this tree of worker threads.
+ mMainThreadEventTargetForMessaging =
+ ThrottledEventQueue::Create(target, "Worker queue for messaging");
+ if (StaticPrefs::dom_worker_use_medium_high_event_queue()) {
+ mMainThreadEventTarget = ThrottledEventQueue::Create(
+ GetMainThreadSerialEventTarget(), "Worker queue",
+ nsIRunnablePriority::PRIORITY_MEDIUMHIGH);
+ } else {
+ mMainThreadEventTarget = mMainThreadEventTargetForMessaging;
+ }
+ mMainThreadDebuggeeEventTarget =
+ ThrottledEventQueue::Create(target, "Worker debuggee queue");
+ if (IsParentWindowPaused() || IsFrozen()) {
+ MOZ_ALWAYS_SUCCEEDS(mMainThreadDebuggeeEventTarget->SetIsPaused(true));
+ }
+}
+
+WorkerPrivate::~WorkerPrivate() {
+ MOZ_DIAGNOSTIC_ASSERT(mTopLevelWorkerFinishedRunnableCount == 0);
+ MOZ_DIAGNOSTIC_ASSERT(mWorkerFinishedRunnableCount == 0);
+
+ mWorkerControlEventTarget->ForgetWorkerPrivate(this);
+
+ // We force the hybrid event target to forget the thread when we
+ // enter the Killing state, but we do it again here to be safe.
+ // Its possible that we may be created and destroyed without progressing
+ // to Killing via some obscure code path.
+ mWorkerHybridEventTarget->ForgetWorkerPrivate(this);
+}
+
+WorkerPrivate::AgentClusterIdAndCoop
+WorkerPrivate::ComputeAgentClusterIdAndCoop(WorkerPrivate* aParent,
+ WorkerKind aWorkerKind,
+ WorkerLoadInfo* aLoadInfo) {
+ nsILoadInfo::CrossOriginOpenerPolicy agentClusterCoop =
+ nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
+
+ if (aParent) {
+ MOZ_ASSERT(aWorkerKind == WorkerKind::WorkerKindDedicated);
+
+ return {aParent->AgentClusterId(), aParent->mAgentClusterOpenerPolicy};
+ }
+
+ AssertIsOnMainThread();
+
+ if (aWorkerKind == WorkerKind::WorkerKindService ||
+ aWorkerKind == WorkerKind::WorkerKindShared) {
+ return {aLoadInfo->mAgentClusterId, agentClusterCoop};
+ }
+
+ if (aLoadInfo->mWindow) {
+ Document* doc = aLoadInfo->mWindow->GetExtantDoc();
+ MOZ_DIAGNOSTIC_ASSERT(doc);
+ RefPtr<DocGroup> docGroup = doc->GetDocGroup();
+
+ nsID agentClusterId =
+ docGroup ? docGroup->AgentClusterId() : nsID::GenerateUUID();
+
+ BrowsingContext* bc = aLoadInfo->mWindow->GetBrowsingContext();
+ MOZ_DIAGNOSTIC_ASSERT(bc);
+ return {agentClusterId, bc->Top()->GetOpenerPolicy()};
+ }
+
+ // If the window object was failed to be set into the WorkerLoadInfo, we
+ // make the worker into another agent cluster group instead of failures.
+ return {nsID::GenerateUUID(), agentClusterCoop};
+}
+
+// static
+already_AddRefed<WorkerPrivate> WorkerPrivate::Constructor(
+ JSContext* aCx, const nsAString& aScriptURL, bool aIsChromeWorker,
+ WorkerKind aWorkerKind, RequestCredentials aRequestCredentials,
+ enum WorkerType aWorkerType, const nsAString& aWorkerName,
+ const nsACString& aServiceWorkerScope, WorkerLoadInfo* aLoadInfo,
+ ErrorResult& aRv, nsString aId,
+ CancellationCallback&& aCancellationCallback,
+ TerminationCallback&& aTerminationCallback) {
+ WorkerPrivate* parent =
+ NS_IsMainThread() ? nullptr : GetCurrentThreadWorkerPrivate();
+
+ // If this is a sub-worker, we need to keep the parent worker alive until this
+ // one is registered.
+ RefPtr<StrongWorkerRef> workerRef;
+ if (parent) {
+ parent->AssertIsOnWorkerThread();
+
+ workerRef = StrongWorkerRef::Create(parent, "WorkerPrivate::Constructor");
+ if (NS_WARN_IF(!workerRef)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+ } else {
+ AssertIsOnMainThread();
+ }
+
+ Maybe<WorkerLoadInfo> stackLoadInfo;
+ if (!aLoadInfo) {
+ stackLoadInfo.emplace();
+
+ nsresult rv = GetLoadInfo(
+ aCx, nullptr, parent, aScriptURL, aWorkerType, aRequestCredentials,
+ aIsChromeWorker, InheritLoadGroup, aWorkerKind, stackLoadInfo.ptr());
+ aRv.MightThrowJSException();
+ if (NS_FAILED(rv)) {
+ workerinternals::ReportLoadError(aRv, rv, aScriptURL);
+ return nullptr;
+ }
+
+ aLoadInfo = stackLoadInfo.ptr();
+ }
+
+ // NB: This has to be done before creating the WorkerPrivate, because it will
+ // attempt to use static variables that are initialized in the RuntimeService
+ // constructor.
+ RuntimeService* runtimeService;
+
+ if (!parent) {
+ runtimeService = RuntimeService::GetOrCreateService();
+ if (!runtimeService) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ } else {
+ runtimeService = RuntimeService::GetService();
+ }
+
+ MOZ_ASSERT(runtimeService);
+
+ // Don't create a worker with the shutting down RuntimeService.
+ if (runtimeService->IsShuttingDown()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ AgentClusterIdAndCoop idAndCoop =
+ ComputeAgentClusterIdAndCoop(parent, aWorkerKind, aLoadInfo);
+
+ RefPtr<WorkerPrivate> worker = new WorkerPrivate(
+ parent, aScriptURL, aIsChromeWorker, aWorkerKind, aRequestCredentials,
+ aWorkerType, aWorkerName, aServiceWorkerScope, *aLoadInfo, std::move(aId),
+ idAndCoop.mId, idAndCoop.mCoop, std::move(aCancellationCallback),
+ std::move(aTerminationCallback));
+
+ // Gecko contexts always have an explicitly-set default locale (set by
+ // XPJSRuntime::Initialize for the main thread, set by
+ // WorkerThreadPrimaryRunnable::Run for workers just before running worker
+ // code), so this is never SpiderMonkey's builtin default locale.
+ JS::UniqueChars defaultLocale = JS_GetDefaultLocale(aCx);
+ if (NS_WARN_IF(!defaultLocale)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ worker->mDefaultLocale = std::move(defaultLocale);
+
+ if (!runtimeService->RegisterWorker(*worker)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ // From this point on (worker thread has been started) we
+ // must keep ourself alive. We can now only be cleared by
+ // ClearSelfAndParentEventTargetRef().
+ worker->mSelfRef = worker;
+
+ worker->EnableDebugger();
+
+ MOZ_DIAGNOSTIC_ASSERT(worker->PrincipalIsValid());
+
+ UniquePtr<SerializedStackHolder> stack;
+ if (worker->IsWatchedByDevTools()) {
+ stack = GetCurrentStackForNetMonitor(aCx);
+ }
+
+ // 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() && !worker->GetParent() && worker->GetDocument()
+ ? worker->GetDocument()->GetDocumentCharacterSet().get()
+ : nullptr;
+
+ RefPtr<CompileScriptRunnable> compiler = new CompileScriptRunnable(
+ worker, std::move(stack), aScriptURL, aDocumentEncoding);
+ if (!compiler->Dispatch()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ return worker.forget();
+}
+
+nsresult WorkerPrivate::SetIsDebuggerReady(bool aReady) {
+ AssertIsOnMainThread();
+ MutexAutoLock lock(mMutex);
+
+ if (mDebuggerReady == aReady) {
+ return NS_OK;
+ }
+
+ if (!aReady && mDebuggerRegistered) {
+ // The debugger can only be marked as not ready during registration.
+ return NS_ERROR_FAILURE;
+ }
+
+ mDebuggerReady = aReady;
+
+ if (aReady && mDebuggerRegistered) {
+ // Dispatch all the delayed runnables without releasing the lock, to ensure
+ // that the order in which debuggee runnables execute is the same as the
+ // order in which they were originally dispatched.
+ auto pending = std::move(mDelayedDebuggeeRunnables);
+ for (uint32_t i = 0; i < pending.Length(); i++) {
+ RefPtr<WorkerRunnable> runnable = std::move(pending[i]);
+ nsresult rv = DispatchLockHeld(runnable.forget(), nullptr, lock);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ MOZ_RELEASE_ASSERT(mDelayedDebuggeeRunnables.IsEmpty());
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult WorkerPrivate::GetLoadInfo(
+ JSContext* aCx, nsPIDOMWindowInner* aWindow, WorkerPrivate* aParent,
+ const nsAString& aScriptURL, const enum WorkerType& aWorkerType,
+ const RequestCredentials& aCredentials, bool aIsChromeWorker,
+ LoadGroupBehavior aLoadGroupBehavior, WorkerKind aWorkerKind,
+ WorkerLoadInfo* aLoadInfo) {
+ using namespace mozilla::dom::workerinternals;
+
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT_IF(NS_IsMainThread(),
+ aCx == nsContentUtils::GetCurrentJSContext());
+
+ if (aWindow) {
+ AssertIsOnMainThread();
+ }
+
+ WorkerLoadInfo loadInfo;
+ nsresult rv;
+
+ if (aParent) {
+ aParent->AssertIsOnWorkerThread();
+
+ // If the parent is going away give up now.
+ WorkerStatus parentStatus;
+ {
+ MutexAutoLock lock(aParent->mMutex);
+ parentStatus = aParent->mStatus;
+ }
+
+ if (parentStatus > Running) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Passing a pointer to our stack loadInfo is safe here because this
+ // method uses a sync runnable to get the channel from the main thread.
+ rv = ChannelFromScriptURLWorkerThread(aCx, aParent, aScriptURL, aWorkerType,
+ aCredentials, loadInfo);
+ if (NS_FAILED(rv)) {
+ MOZ_ALWAYS_TRUE(loadInfo.ProxyReleaseMainThreadObjects(aParent));
+ return rv;
+ }
+
+ // Now that we've spun the loop there's no guarantee that our parent is
+ // still alive. We may have received control messages initiating shutdown.
+ {
+ MutexAutoLock lock(aParent->mMutex);
+ parentStatus = aParent->mStatus;
+ }
+
+ if (parentStatus > Running) {
+ MOZ_ALWAYS_TRUE(loadInfo.ProxyReleaseMainThreadObjects(aParent));
+ return NS_ERROR_FAILURE;
+ }
+
+ loadInfo.mTrials = aParent->Trials();
+ loadInfo.mDomain = aParent->Domain();
+ loadInfo.mFromWindow = aParent->IsFromWindow();
+ loadInfo.mWindowID = aParent->WindowID();
+ loadInfo.mAssociatedBrowsingContextID =
+ aParent->AssociatedBrowsingContextID();
+ loadInfo.mStorageAccess = aParent->StorageAccess();
+ loadInfo.mUseRegularPrincipal = aParent->UseRegularPrincipal();
+ loadInfo.mHasStorageAccessPermissionGranted =
+ aParent->HasStorageAccessPermissionGranted();
+ loadInfo.mCookieJarSettings = aParent->CookieJarSettings();
+ if (loadInfo.mCookieJarSettings) {
+ loadInfo.mCookieJarSettingsArgs = aParent->CookieJarSettingsArgs();
+ }
+ loadInfo.mOriginAttributes = aParent->GetOriginAttributes();
+ loadInfo.mServiceWorkersTestingInWindow =
+ aParent->ServiceWorkersTestingInWindow();
+ loadInfo.mIsThirdPartyContextToTopWindow =
+ aParent->IsThirdPartyContextToTopWindow();
+ loadInfo.mShouldResistFingerprinting =
+ aParent->GlobalScope()->ShouldResistFingerprinting(
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+ loadInfo.mParentController = aParent->GlobalScope()->GetController();
+ loadInfo.mWatchedByDevTools = aParent->IsWatchedByDevTools();
+ } else {
+ AssertIsOnMainThread();
+
+ // Make sure that the IndexedDatabaseManager is set up
+ Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate());
+
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ MOZ_ASSERT(ssm);
+
+ bool isChrome = nsContentUtils::IsSystemCaller(aCx);
+
+ // First check to make sure the caller has permission to make a privileged
+ // worker if they called the ChromeWorker/ChromeSharedWorker constructor.
+ if (aIsChromeWorker && !isChrome) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // Chrome callers (whether creating a ChromeWorker or Worker) always get the
+ // system principal here as they're allowed to load anything. The script
+ // loader will refuse to run any script that does not also have the system
+ // principal.
+ if (isChrome) {
+ rv = ssm->GetSystemPrincipal(getter_AddRefs(loadInfo.mLoadingPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // See if we're being called from a window.
+ nsCOMPtr<nsPIDOMWindowInner> globalWindow = aWindow;
+ if (!globalWindow) {
+ globalWindow = xpc::CurrentWindowOrNull(aCx);
+ }
+
+ nsCOMPtr<Document> document;
+ Maybe<ClientInfo> clientInfo;
+
+ if (globalWindow) {
+ // Only use the current inner window, and only use it if the caller can
+ // access it.
+ if (nsPIDOMWindowOuter* outerWindow = globalWindow->GetOuterWindow()) {
+ loadInfo.mWindow = outerWindow->GetCurrentInnerWindow();
+ }
+
+ loadInfo.mTrials =
+ OriginTrials::FromWindow(nsGlobalWindowInner::Cast(loadInfo.mWindow));
+
+ BrowsingContext* browsingContext = globalWindow->GetBrowsingContext();
+
+ // TODO: fix this for SharedWorkers with multiple documents (bug
+ // 1177935)
+ loadInfo.mServiceWorkersTestingInWindow =
+ browsingContext &&
+ browsingContext->Top()->ServiceWorkersTestingEnabled();
+
+ if (!loadInfo.mWindow ||
+ (globalWindow != loadInfo.mWindow &&
+ !nsContentUtils::CanCallerAccess(loadInfo.mWindow))) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(loadInfo.mWindow);
+ MOZ_ASSERT(sgo);
+
+ loadInfo.mScriptContext = sgo->GetContext();
+ NS_ENSURE_TRUE(loadInfo.mScriptContext, NS_ERROR_FAILURE);
+
+ // If we're called from a window then we can dig out the principal and URI
+ // from the document.
+ document = loadInfo.mWindow->GetExtantDoc();
+ NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+
+ loadInfo.mBaseURI = document->GetDocBaseURI();
+ loadInfo.mLoadGroup = document->GetDocumentLoadGroup();
+ NS_ENSURE_TRUE(loadInfo.mLoadGroup, NS_ERROR_FAILURE);
+
+ clientInfo = globalWindow->GetClientInfo();
+
+ // Use the document's NodePrincipal as loading principal if we're not
+ // being called from chrome.
+ if (!loadInfo.mLoadingPrincipal) {
+ loadInfo.mLoadingPrincipal = document->NodePrincipal();
+ NS_ENSURE_TRUE(loadInfo.mLoadingPrincipal, NS_ERROR_FAILURE);
+
+ // We use the document's base domain to limit the number of workers
+ // each domain can create. For sandboxed documents, we use the domain
+ // of their first non-sandboxed document, walking up until we find
+ // one. If we can't find one, we fall back to using the GUID of the
+ // null principal as the base domain.
+ if (document->GetSandboxFlags() & SANDBOXED_ORIGIN) {
+ nsCOMPtr<Document> tmpDoc = document;
+ do {
+ tmpDoc = tmpDoc->GetInProcessParentDocument();
+ } while (tmpDoc && tmpDoc->GetSandboxFlags() & SANDBOXED_ORIGIN);
+
+ if (tmpDoc) {
+ // There was an unsandboxed ancestor, yay!
+ nsCOMPtr<nsIPrincipal> tmpPrincipal = tmpDoc->NodePrincipal();
+ rv = tmpPrincipal->GetBaseDomain(loadInfo.mDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // No unsandboxed ancestor, use our GUID.
+ rv = loadInfo.mLoadingPrincipal->GetBaseDomain(loadInfo.mDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ // Document creating the worker is not sandboxed.
+ rv = loadInfo.mLoadingPrincipal->GetBaseDomain(loadInfo.mDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadInfo.mLoadGroup,
+ loadInfo.mLoadingPrincipal),
+ NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t perm;
+ rv = permMgr->TestPermissionFromPrincipal(loadInfo.mLoadingPrincipal,
+ "systemXHR"_ns, &perm);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ loadInfo.mXHRParamsAllowed = perm == nsIPermissionManager::ALLOW_ACTION;
+
+ loadInfo.mWatchedByDevTools =
+ browsingContext && browsingContext->WatchedByDevTools();
+
+ loadInfo.mReferrerInfo =
+ ReferrerInfo::CreateForFetch(loadInfo.mLoadingPrincipal, document);
+ loadInfo.mFromWindow = true;
+ loadInfo.mWindowID = globalWindow->WindowID();
+ loadInfo.mAssociatedBrowsingContextID =
+ globalWindow->GetBrowsingContext()->Id();
+ loadInfo.mStorageAccess = StorageAllowedForWindow(globalWindow);
+ loadInfo.mUseRegularPrincipal = document->UseRegularPrincipal();
+ loadInfo.mHasStorageAccessPermissionGranted =
+ document->HasStorageAccessPermissionGranted();
+ loadInfo.mShouldResistFingerprinting =
+ document->ShouldResistFingerprinting(
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+
+ // This is an hack to deny the storage-access-permission for workers of
+ // sub-iframes.
+ if (loadInfo.mHasStorageAccessPermissionGranted &&
+ StorageAllowedForDocument(document) != StorageAccess::eAllow) {
+ loadInfo.mHasStorageAccessPermissionGranted = false;
+ }
+ loadInfo.mIsThirdPartyContextToTopWindow =
+ AntiTrackingUtils::IsThirdPartyWindow(globalWindow, nullptr);
+ loadInfo.mCookieJarSettings = document->CookieJarSettings();
+ if (loadInfo.mCookieJarSettings) {
+ auto* cookieJarSettings =
+ net::CookieJarSettings::Cast(loadInfo.mCookieJarSettings);
+ cookieJarSettings->Serialize(loadInfo.mCookieJarSettingsArgs);
+ }
+ StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(
+ document, loadInfo.mOriginAttributes);
+ loadInfo.mParentController = globalWindow->GetController();
+ loadInfo.mSecureContext = loadInfo.mWindow->IsSecureContext()
+ ? WorkerLoadInfo::eSecureContext
+ : WorkerLoadInfo::eInsecureContext;
+ } else {
+ // Not a window
+ MOZ_ASSERT(isChrome);
+
+ // We're being created outside of a window. Need to figure out the script
+ // that is creating us in order for us to use relative URIs later on.
+ JS::AutoFilename fileName;
+ if (JS::DescribeScriptedCaller(aCx, &fileName)) {
+ // In most cases, fileName is URI. In a few other cases
+ // (e.g. xpcshell), fileName is a file path. Ideally, we would
+ // prefer testing whether fileName parses as an URI and fallback
+ // to file path in case of error, but Windows file paths have
+ // the interesting property that they can be parsed as bogus
+ // URIs (e.g. C:/Windows/Tmp is interpreted as scheme "C",
+ // hostname "Windows", path "Tmp"), which defeats this algorithm.
+ // Therefore, we adopt the opposite convention.
+ nsCOMPtr<nsIFile> scriptFile =
+ do_CreateInstance("@mozilla.org/file/local;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = scriptFile->InitWithPath(NS_ConvertUTF8toUTF16(fileName.get()));
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_NewFileURI(getter_AddRefs(loadInfo.mBaseURI), scriptFile);
+ }
+ if (NS_FAILED(rv)) {
+ // As expected, fileName is not a path, so proceed with
+ // a uri.
+ rv = NS_NewURI(getter_AddRefs(loadInfo.mBaseURI), fileName.get());
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ loadInfo.mXHRParamsAllowed = true;
+ loadInfo.mFromWindow = false;
+ loadInfo.mWindowID = UINT64_MAX;
+ loadInfo.mStorageAccess = StorageAccess::eAllow;
+ loadInfo.mUseRegularPrincipal = true;
+ loadInfo.mHasStorageAccessPermissionGranted = false;
+ loadInfo.mCookieJarSettings =
+ mozilla::net::CookieJarSettings::Create(loadInfo.mLoadingPrincipal);
+ loadInfo.mShouldResistFingerprinting =
+ nsContentUtils::ShouldResistFingerprinting_dangerous(
+ loadInfo.mLoadingPrincipal,
+ "Unusual situation - we have no document or CookieJarSettings",
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+ MOZ_ASSERT(loadInfo.mCookieJarSettings);
+ auto* cookieJarSettings =
+ net::CookieJarSettings::Cast(loadInfo.mCookieJarSettings);
+ cookieJarSettings->Serialize(loadInfo.mCookieJarSettingsArgs);
+
+ loadInfo.mOriginAttributes = OriginAttributes();
+ loadInfo.mIsThirdPartyContextToTopWindow = false;
+ }
+
+ MOZ_ASSERT(loadInfo.mLoadingPrincipal);
+ MOZ_ASSERT(isChrome || !loadInfo.mDomain.IsEmpty());
+
+ if (!loadInfo.mLoadGroup || aLoadGroupBehavior == OverrideLoadGroup) {
+ OverrideLoadInfoLoadGroup(loadInfo, loadInfo.mLoadingPrincipal);
+ }
+ MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadInfo.mLoadGroup,
+ loadInfo.mLoadingPrincipal));
+
+ // Top level workers' main script use the document charset for the script
+ // uri encoding.
+ nsCOMPtr<nsIURI> url;
+ rv = nsContentUtils::NewURIWithDocumentCharset(
+ getter_AddRefs(url), aScriptURL, document, loadInfo.mBaseURI);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
+ rv = ChannelFromScriptURLMainThread(
+ loadInfo.mLoadingPrincipal, document, loadInfo.mLoadGroup, url,
+ aWorkerType, aCredentials, clientInfo, ContentPolicyType(aWorkerKind),
+ loadInfo.mCookieJarSettings, loadInfo.mReferrerInfo,
+ getter_AddRefs(loadInfo.mChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_GetFinalChannelURI(loadInfo.mChannel,
+ getter_AddRefs(loadInfo.mResolvedScriptURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We need the correct hasStoragePermission flag for the channel here since
+ // we will do a content blocking check later when we set the storage
+ // principal for the worker. The channel here won't be opened when we do the
+ // check later, so the hasStoragePermission flag is incorrect. To address
+ // this, We copy the hasStoragePermission flag from the document if there is
+ // a window. The worker is created as the same origin of the window. So, the
+ // worker is supposed to have the same storage permission as the window as
+ // well as the hasStoragePermission flag.
+ nsCOMPtr<nsILoadInfo> channelLoadInfo = loadInfo.mChannel->LoadInfo();
+ rv = channelLoadInfo->SetStoragePermission(
+ loadInfo.mHasStorageAccessPermissionGranted
+ ? nsILoadInfo::HasStoragePermission
+ : nsILoadInfo::NoStoragePermission);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = loadInfo.SetPrincipalsAndCSPFromChannel(loadInfo.mChannel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(loadInfo.mLoadingPrincipal);
+ MOZ_DIAGNOSTIC_ASSERT(loadInfo.PrincipalIsValid());
+
+ *aLoadInfo = std::move(loadInfo);
+ return NS_OK;
+}
+
+// static
+void WorkerPrivate::OverrideLoadInfoLoadGroup(WorkerLoadInfo& aLoadInfo,
+ nsIPrincipal* aPrincipal) {
+ MOZ_ASSERT(!aLoadInfo.mInterfaceRequestor);
+ MOZ_ASSERT(aLoadInfo.mLoadingPrincipal == aPrincipal);
+
+ aLoadInfo.mInterfaceRequestor =
+ new WorkerLoadInfo::InterfaceRequestor(aPrincipal, aLoadInfo.mLoadGroup);
+ aLoadInfo.mInterfaceRequestor->MaybeAddBrowserChild(aLoadInfo.mLoadGroup);
+
+ // NOTE: this defaults the load context to:
+ // - private browsing = false
+ // - content = true
+ // - use remote tabs = false
+ nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+
+ nsresult rv =
+ loadGroup->SetNotificationCallbacks(aLoadInfo.mInterfaceRequestor);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+
+ aLoadInfo.mLoadGroup = std::move(loadGroup);
+
+ MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadInfo.mLoadGroup, aPrincipal));
+}
+
+void WorkerPrivate::RunLoopNeverRan() {
+ {
+ MutexAutoLock lock(mMutex);
+
+ mStatus = Dead;
+ }
+
+ // After mStatus is set to Dead there can be no more
+ // WorkerControlRunnables so no need to lock here.
+ if (!mControlQueue.IsEmpty()) {
+ WorkerControlRunnable* runnable = nullptr;
+ while (mControlQueue.Pop(runnable)) {
+ runnable->Cancel();
+ runnable->Release();
+ }
+ }
+
+ NotifyWorkerRefs(Killing);
+
+ ScheduleDeletion(WorkerPrivate::WorkerRan);
+}
+
+void WorkerPrivate::UnrootGlobalScopes() {
+ auto data = mWorkerThreadAccessible.Access();
+
+ RefPtr<WorkerDebuggerGlobalScope> debugScope = data->mDebuggerScope.forget();
+ if (debugScope) {
+ MOZ_ASSERT(debugScope->mWorkerPrivate == this);
+ }
+ RefPtr<WorkerGlobalScope> scope = data->mScope.forget();
+ if (scope) {
+ MOZ_ASSERT(scope->mWorkerPrivate == this);
+ }
+}
+
+void WorkerPrivate::DoRunLoop(JSContext* aCx) {
+ auto data = mWorkerThreadAccessible.Access();
+ MOZ_RELEASE_ASSERT(!GetExecutionManager());
+
+ RefPtr<WorkerThread> thread;
+ {
+ MutexAutoLock lock(mMutex);
+ mJSContext = aCx;
+ // mThread is set before we enter, and is never changed during DoRunLoop.
+ // copy to local so we don't trigger mutex analysis
+ MOZ_ASSERT(mThread);
+ thread = mThread;
+
+ MOZ_ASSERT(mStatus == Pending);
+ mStatus = Running;
+ }
+
+ // Now that we've done that, we can go ahead and set up our AutoJSAPI. We
+ // can't before this point, because it can't find the right JSContext before
+ // then, since it gets it from our mJSContext.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ MOZ_ASSERT(jsapi.cx() == aCx);
+
+ EnableMemoryReporter();
+
+ InitializeGCTimers();
+
+ for (;;) {
+ WorkerStatus currentStatus;
+ bool debuggerRunnablesPending = false;
+ bool normalRunnablesPending = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ // Wait for a runnable to arrive that we can execute, or for it to be okay
+ // to shutdown this worker once all holders have been removed.
+ // Holders may be removed from inside normal runnables, but we don't check
+ // for that after processing normal runnables, so we need to let control
+ // flow to the shutdown logic without blocking.
+ while (mControlQueue.IsEmpty() &&
+ !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
+ !(normalRunnablesPending = NS_HasPendingEvents(thread)) &&
+ !(mStatus != Running && !HasActiveWorkerRefs())) {
+ // We pop out to this loop when there are no pending events.
+ // If we don't reset these, we may not re-enter ProcessNextEvent()
+ // until we have events to process, and it may seem like we have
+ // an event running for a very long time.
+ thread->SetRunningEventDelay(TimeDuration(), TimeStamp());
+
+ WaitForWorkerEvents();
+ }
+
+ auto result = ProcessAllControlRunnablesLocked();
+ if (result != ProcessAllControlRunnablesResult::Nothing) {
+ // NB: There's no JS on the stack here, so Abort vs MayContinue is
+ // irrelevant
+
+ // The state of the world may have changed, recheck it.
+ normalRunnablesPending = NS_HasPendingEvents(thread);
+ // The debugger queue doesn't get cleared, so we can ignore that.
+ }
+
+ currentStatus = mStatus;
+ }
+
+ // if all holders are done then we can kill this thread.
+ if (currentStatus != Running && !HasActiveWorkerRefs()) {
+ // Now we are ready to kill the worker thread.
+ if (currentStatus == Canceling) {
+ NotifyInternal(Killing);
+
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(mMutex);
+ currentStatus = mStatus;
+ }
+ MOZ_ASSERT(currentStatus == Killing);
+#else
+ currentStatus = Killing;
+#endif
+ }
+
+ // If we're supposed to die then we should exit the loop.
+ if (currentStatus == Killing) {
+ // We are about to destroy worker, report all use counters.
+ ReportUseCounters();
+
+ // Flush uncaught rejections immediately, without
+ // waiting for a next tick.
+ PromiseDebugging::FlushUncaughtRejections();
+
+ ShutdownGCTimers();
+
+ DisableMemoryReporter();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ mStatus = Dead;
+ mJSContext = nullptr;
+ }
+
+ // After mStatus is set to Dead there can be no more
+ // WorkerControlRunnables so no need to lock here.
+ if (!mControlQueue.IsEmpty()) {
+ WorkerControlRunnable* runnable = nullptr;
+ while (mControlQueue.Pop(runnable)) {
+ runnable->Cancel();
+ runnable->Release();
+ }
+ }
+
+ // We do not need the timeouts any more, they have been canceled
+ // by NotifyInternal(Killing) above if they were active.
+ UnlinkTimeouts();
+
+ return;
+ }
+ }
+
+ if (debuggerRunnablesPending || normalRunnablesPending) {
+ // Start the periodic GC timer if it is not already running.
+ SetGCTimerMode(PeriodicTimer);
+ }
+
+ if (debuggerRunnablesPending) {
+ WorkerRunnable* runnable = nullptr;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ mDebuggerQueue.Pop(runnable);
+ debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
+ }
+
+ MOZ_ASSERT(runnable);
+ static_cast<nsIRunnable*>(runnable)->Run();
+ runnable->Release();
+
+ CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+ ccjs->PerformDebuggerMicroTaskCheckpoint();
+
+ if (debuggerRunnablesPending) {
+ WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope();
+ MOZ_ASSERT(globalScope);
+
+ // Now *might* be a good time to GC. Let the JS engine make the
+ // decision.
+ JSAutoRealm ar(aCx, globalScope->GetGlobalJSObject());
+ JS_MaybeGC(aCx);
+ }
+ } else if (normalRunnablesPending) {
+ // Process a single runnable from the main queue.
+ NS_ProcessNextEvent(thread, false);
+
+ normalRunnablesPending = NS_HasPendingEvents(thread);
+ if (normalRunnablesPending && GlobalScope()) {
+ // Now *might* be a good time to GC. Let the JS engine make the
+ // decision.
+ JSAutoRealm ar(aCx, GlobalScope()->GetGlobalJSObject());
+ JS_MaybeGC(aCx);
+ }
+ }
+
+ if (!debuggerRunnablesPending && !normalRunnablesPending) {
+ // Both the debugger event queue and the normal event queue has been
+ // exhausted, cancel the periodic GC timer and schedule the idle GC timer.
+ SetGCTimerMode(IdleTimer);
+ }
+
+ // If the worker thread is spamming the main thread faster than it can
+ // process the work, then pause the worker thread until the main thread
+ // catches up.
+ size_t queuedEvents = mMainThreadEventTargetForMessaging->Length() +
+ mMainThreadDebuggeeEventTarget->Length();
+ if (queuedEvents > 5000) {
+ // Note, postMessage uses mMainThreadDebuggeeEventTarget!
+ mMainThreadDebuggeeEventTarget->AwaitIdle();
+ }
+ }
+
+ MOZ_CRASH("Shouldn't get here!");
+}
+
+namespace {
+/**
+ * If there is a current CycleCollectedJSContext, return its recursion depth,
+ * otherwise return 1.
+ *
+ * In the edge case where a worker is starting up so late that PBackground is
+ * already shutting down, the cycle collected context will never be created,
+ * but we will need to drain the event loop in ClearMainEventQueue. This will
+ * result in a normal NS_ProcessPendingEvents invocation which will call
+ * WorkerPrivate::OnProcessNextEvent and WorkerPrivate::AfterProcessNextEvent
+ * which want to handle the need to process control runnables and perform a
+ * sanity check assertion, respectively.
+ *
+ * We claim a depth of 1 when there's no CCJS because this most corresponds to
+ * reality, but this doesn't meant that other code might want to drain various
+ * runnable queues as part of this cleanup.
+ */
+uint32_t GetEffectiveEventLoopRecursionDepth() {
+ auto* ccjs = CycleCollectedJSContext::Get();
+ if (ccjs) {
+ return ccjs->RecursionDepth();
+ }
+
+ return 1;
+}
+
+} // namespace
+
+void WorkerPrivate::OnProcessNextEvent() {
+ AssertIsOnWorkerThread();
+
+ uint32_t recursionDepth = GetEffectiveEventLoopRecursionDepth();
+ MOZ_ASSERT(recursionDepth);
+
+ // Normally we process control runnables in DoRunLoop or RunCurrentSyncLoop.
+ // However, it's possible that non-worker C++ could spin its own nested event
+ // loop, and in that case we must ensure that we continue to process control
+ // runnables here.
+ if (recursionDepth > 1 && mSyncLoopStack.Length() < recursionDepth - 1) {
+ Unused << ProcessAllControlRunnables();
+ // There's no running JS, and no state to revalidate, so we can ignore the
+ // return value.
+ }
+}
+
+void WorkerPrivate::AfterProcessNextEvent() {
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(GetEffectiveEventLoopRecursionDepth());
+}
+
+nsISerialEventTarget* WorkerPrivate::MainThreadEventTargetForMessaging() {
+ return mMainThreadEventTargetForMessaging;
+}
+
+nsresult WorkerPrivate::DispatchToMainThreadForMessaging(nsIRunnable* aRunnable,
+ uint32_t aFlags) {
+ nsCOMPtr<nsIRunnable> r = aRunnable;
+ return DispatchToMainThreadForMessaging(r.forget(), aFlags);
+}
+
+nsresult WorkerPrivate::DispatchToMainThreadForMessaging(
+ already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) {
+ return mMainThreadEventTargetForMessaging->Dispatch(std::move(aRunnable),
+ aFlags);
+}
+
+nsISerialEventTarget* WorkerPrivate::MainThreadEventTarget() {
+ return mMainThreadEventTarget;
+}
+
+nsresult WorkerPrivate::DispatchToMainThread(nsIRunnable* aRunnable,
+ uint32_t aFlags) {
+ nsCOMPtr<nsIRunnable> r = aRunnable;
+ return DispatchToMainThread(r.forget(), aFlags);
+}
+
+nsresult WorkerPrivate::DispatchToMainThread(
+ already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) {
+ return mMainThreadEventTarget->Dispatch(std::move(aRunnable), aFlags);
+}
+
+nsresult WorkerPrivate::DispatchDebuggeeToMainThread(
+ already_AddRefed<WorkerDebuggeeRunnable> aRunnable, uint32_t aFlags) {
+ return mMainThreadDebuggeeEventTarget->Dispatch(std::move(aRunnable), aFlags);
+}
+
+nsISerialEventTarget* WorkerPrivate::ControlEventTarget() {
+ return mWorkerControlEventTarget;
+}
+
+nsISerialEventTarget* WorkerPrivate::HybridEventTarget() {
+ return mWorkerHybridEventTarget;
+}
+
+ClientType WorkerPrivate::GetClientType() const {
+ switch (Kind()) {
+ case WorkerKindDedicated:
+ return ClientType::Worker;
+ case WorkerKindShared:
+ return ClientType::Sharedworker;
+ case WorkerKindService:
+ return ClientType::Serviceworker;
+ default:
+ MOZ_CRASH("unknown worker type!");
+ }
+}
+
+UniquePtr<ClientSource> WorkerPrivate::CreateClientSource() {
+ auto data = mWorkerThreadAccessible.Access();
+ MOZ_ASSERT(!data->mScope, "Client should be created before the global");
+
+ auto clientSource = ClientManager::CreateSource(
+ GetClientType(), mWorkerHybridEventTarget,
+ StoragePrincipalHelper::ShouldUsePartitionPrincipalForServiceWorker(this)
+ ? GetPartitionedPrincipalInfo()
+ : GetPrincipalInfo());
+ MOZ_DIAGNOSTIC_ASSERT(clientSource);
+
+ clientSource->SetAgentClusterId(mAgentClusterId);
+
+ if (data->mFrozen) {
+ clientSource->Freeze();
+ }
+
+ // Shortly after the client is reserved we will try loading the main script
+ // for the worker. This may get intercepted by the ServiceWorkerManager
+ // which will then try to create a ClientHandle. Its actually possible for
+ // the main thread to create this ClientHandle before our IPC message creating
+ // the ClientSource completes. To avoid this race we synchronously ping our
+ // parent Client actor here. This ensure the worker ClientSource is created
+ // in the parent before the main thread might try reaching it with a
+ // ClientHandle.
+ //
+ // An alternative solution would have been to handle the out-of-order
+ // operations on the parent side. We could have created a small window where
+ // we allow ClientHandle objects to exist without a ClientSource. We would
+ // then time out these handles if they stayed orphaned for too long. This
+ // approach would be much more complex, but also avoid this extra bit of
+ // latency when starting workers.
+ //
+ // Note, we only have to do this for workers that can be controlled by a
+ // service worker. So avoid the sync overhead here if we are starting a
+ // service worker or a chrome worker.
+ if (Kind() != WorkerKindService && !IsChromeWorker()) {
+ clientSource->WorkerSyncPing(this);
+ }
+
+ return clientSource;
+}
+
+bool WorkerPrivate::EnsureCSPEventListener() {
+ if (!mCSPEventListener) {
+ mCSPEventListener = WorkerCSPEventListener::Create(this);
+ if (NS_WARN_IF(!mCSPEventListener)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+nsICSPEventListener* WorkerPrivate::CSPEventListener() const {
+ MOZ_ASSERT(mCSPEventListener);
+ return mCSPEventListener;
+}
+
+void WorkerPrivate::EnsurePerformanceStorage() {
+ AssertIsOnWorkerThread();
+
+ if (!mPerformanceStorage) {
+ mPerformanceStorage = PerformanceStorageWorker::Create(this);
+ }
+}
+
+bool WorkerPrivate::GetExecutionGranted() const {
+ auto data = mWorkerThreadAccessible.Access();
+ return data->mJSThreadExecutionGranted;
+}
+
+void WorkerPrivate::SetExecutionGranted(bool aGranted) {
+ auto data = mWorkerThreadAccessible.Access();
+ data->mJSThreadExecutionGranted = aGranted;
+}
+
+void WorkerPrivate::ScheduleTimeSliceExpiration(uint32_t aDelay) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ if (!data->mTSTimer) {
+ data->mTSTimer = NS_NewTimer();
+ MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->SetTarget(mWorkerControlEventTarget));
+ }
+
+ // Whenever an event is scheduled on the WorkerControlEventTarget an
+ // interrupt is automatically requested which causes us to yield JS execution
+ // and the next JS execution in the queue to execute.
+ // This allows for simple code reuse of the existing interrupt callback code
+ // used for control events.
+ MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->InitWithNamedFuncCallback(
+ [](nsITimer* Timer, void* aClosure) { return; }, nullptr, aDelay,
+ nsITimer::TYPE_ONE_SHOT, "TimeSliceExpirationTimer"));
+}
+
+void WorkerPrivate::CancelTimeSliceExpiration() {
+ auto data = mWorkerThreadAccessible.Access();
+ MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->Cancel());
+}
+
+JSExecutionManager* WorkerPrivate::GetExecutionManager() const {
+ auto data = mWorkerThreadAccessible.Access();
+ return data->mExecutionManager.get();
+}
+
+void WorkerPrivate::SetExecutionManager(JSExecutionManager* aManager) {
+ auto data = mWorkerThreadAccessible.Access();
+ data->mExecutionManager = aManager;
+}
+
+void WorkerPrivate::ExecutionReady() {
+ auto data = mWorkerThreadAccessible.Access();
+ {
+ MutexAutoLock lock(mMutex);
+ if (mStatus >= Canceling) {
+ return;
+ }
+ }
+
+ data->mScope->MutableClientSourceRef().WorkerExecutionReady(this);
+
+ if (ExtensionAPIAllowed()) {
+ extensions::CreateAndDispatchInitWorkerContextRunnable();
+ }
+}
+
+void WorkerPrivate::InitializeGCTimers() {
+ auto data = mWorkerThreadAccessible.Access();
+
+ // We need timers for GC. The basic plan is to run a non-shrinking GC
+ // periodically (PERIODIC_GC_TIMER_DELAY_SEC) while the worker is running.
+ // Once the worker goes idle we set a short (IDLE_GC_TIMER_DELAY_SEC) timer to
+ // run a shrinking GC.
+ data->mPeriodicGCTimer = NS_NewTimer();
+ data->mIdleGCTimer = NS_NewTimer();
+
+ data->mPeriodicGCTimerRunning = false;
+ data->mIdleGCTimerRunning = false;
+}
+
+void WorkerPrivate::SetGCTimerMode(GCTimerMode aMode) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ if (!data->mPeriodicGCTimer || !data->mIdleGCTimer) {
+ // GC timers have been cleared already.
+ return;
+ }
+
+ if (aMode == NoTimer) {
+ MOZ_ALWAYS_SUCCEEDS(data->mPeriodicGCTimer->Cancel());
+ data->mPeriodicGCTimerRunning = false;
+ MOZ_ALWAYS_SUCCEEDS(data->mIdleGCTimer->Cancel());
+ data->mIdleGCTimerRunning = false;
+ return;
+ }
+
+ WorkerStatus status;
+ {
+ MutexAutoLock lock(mMutex);
+ status = mStatus;
+ }
+
+ if (status >= Killing) {
+ ShutdownGCTimers();
+ return;
+ }
+
+ // If the idle timer is running, don't cancel it when the periodic timer
+ // is scheduled since we do want shrinking GC to be called occasionally.
+ if (aMode == PeriodicTimer && data->mPeriodicGCTimerRunning) {
+ return;
+ }
+
+ if (aMode == IdleTimer) {
+ if (!data->mPeriodicGCTimerRunning) {
+ // Since running idle GC cancels both GC timers, after that we want
+ // first at least periodic GC timer getting activated, since that tells
+ // us that there have been some non-control tasks to process. Otherwise
+ // idle GC timer would keep running all the time.
+ return;
+ }
+
+ // Cancel the periodic timer now, since the event loop is (in the common
+ // case) empty now.
+ MOZ_ALWAYS_SUCCEEDS(data->mPeriodicGCTimer->Cancel());
+ data->mPeriodicGCTimerRunning = false;
+
+ if (data->mIdleGCTimerRunning) {
+ return;
+ }
+ }
+
+ MOZ_ASSERT(aMode == PeriodicTimer || aMode == IdleTimer);
+
+ uint32_t delay = 0;
+ int16_t type = nsITimer::TYPE_ONE_SHOT;
+ nsTimerCallbackFunc callback = nullptr;
+ const char* name = nullptr;
+ nsITimer* timer = nullptr;
+
+ if (aMode == PeriodicTimer) {
+ delay = PERIODIC_GC_TIMER_DELAY_SEC * 1000;
+ type = nsITimer::TYPE_REPEATING_SLACK;
+ callback = PeriodicGCTimerCallback;
+ name = "dom::PeriodicGCTimerCallback";
+ timer = data->mPeriodicGCTimer;
+ data->mPeriodicGCTimerRunning = true;
+ LOG(WorkerLog(), ("Worker %p scheduled periodic GC timer\n", this));
+ } else {
+ delay = IDLE_GC_TIMER_DELAY_SEC * 1000;
+ type = nsITimer::TYPE_ONE_SHOT;
+ callback = IdleGCTimerCallback;
+ name = "dom::IdleGCTimerCallback";
+ timer = data->mIdleGCTimer;
+ data->mIdleGCTimerRunning = true;
+ LOG(WorkerLog(), ("Worker %p scheduled idle GC timer\n", this));
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(timer->SetTarget(mWorkerControlEventTarget));
+ MOZ_ALWAYS_SUCCEEDS(
+ timer->InitWithNamedFuncCallback(callback, this, delay, type, name));
+}
+
+void WorkerPrivate::ShutdownGCTimers() {
+ auto data = mWorkerThreadAccessible.Access();
+
+ MOZ_ASSERT(!data->mPeriodicGCTimer == !data->mIdleGCTimer);
+
+ if (!data->mPeriodicGCTimer && !data->mIdleGCTimer) {
+ return;
+ }
+
+ // Always make sure the timers are canceled.
+ MOZ_ALWAYS_SUCCEEDS(data->mPeriodicGCTimer->Cancel());
+ MOZ_ALWAYS_SUCCEEDS(data->mIdleGCTimer->Cancel());
+
+ LOG(WorkerLog(), ("Worker %p killed the GC timers\n", this));
+
+ data->mPeriodicGCTimer = nullptr;
+ data->mIdleGCTimer = nullptr;
+ data->mPeriodicGCTimerRunning = false;
+ data->mIdleGCTimerRunning = false;
+}
+
+bool WorkerPrivate::InterruptCallback(JSContext* aCx) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ AutoYieldJSThreadExecution yield;
+
+ // If we are here it's because a WorkerControlRunnable has been dispatched.
+ // The runnable could be processed here or it could have already been
+ // processed by a sync event loop.
+ // The most important thing this method must do, is to decide if the JS
+ // execution should continue or not. If the runnable returns an error or if
+ // the worker status is >= Canceling, we should stop the JS execution.
+
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+
+ bool mayContinue = true;
+ bool scheduledIdleGC = false;
+
+ for (;;) {
+ // Run all control events now.
+ auto result = ProcessAllControlRunnables();
+ if (result == ProcessAllControlRunnablesResult::Abort) {
+ mayContinue = false;
+ }
+
+ bool mayFreeze = data->mFrozen;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mayFreeze) {
+ mayFreeze = mStatus <= Running;
+ }
+
+ if (mStatus >= Canceling) {
+ mayContinue = false;
+ }
+ }
+
+ if (!mayContinue || !mayFreeze) {
+ break;
+ }
+
+ // Cancel the periodic GC timer here before freezing. The idle GC timer
+ // will clean everything up once it runs.
+ if (!scheduledIdleGC) {
+ SetGCTimerMode(IdleTimer);
+ scheduledIdleGC = true;
+ }
+
+ while ((mayContinue = MayContinueRunning())) {
+ MutexAutoLock lock(mMutex);
+ if (!mControlQueue.IsEmpty()) {
+ break;
+ }
+
+ WaitForWorkerEvents();
+ }
+ }
+
+ if (!mayContinue) {
+ // We want only uncatchable exceptions here.
+ NS_ASSERTION(!JS_IsExceptionPending(aCx),
+ "Should not have an exception set here!");
+ return false;
+ }
+
+ // Make sure the periodic timer gets turned back on here.
+ SetGCTimerMode(PeriodicTimer);
+
+ return true;
+}
+
+void WorkerPrivate::CloseInternal() {
+ AssertIsOnWorkerThread();
+ NotifyInternal(Closing);
+}
+
+bool WorkerPrivate::IsOnCurrentThread() {
+ // May be called on any thread!
+
+ MOZ_ASSERT(mPRThread);
+ return PR_GetCurrentThread() == mPRThread;
+}
+
+void WorkerPrivate::ScheduleDeletion(WorkerRanOrNot aRanOrNot) {
+ AssertIsOnWorkerThread();
+ {
+ // mWorkerThreadAccessible's accessor must be destructed before
+ // the scheduled Runnable gets to run.
+ auto data = mWorkerThreadAccessible.Access();
+ MOZ_ASSERT(data->mChildWorkers.IsEmpty());
+
+ MOZ_RELEASE_ASSERT(!data->mDeletionScheduled);
+ data->mDeletionScheduled.Flip();
+ }
+ MOZ_ASSERT(mSyncLoopStack.IsEmpty());
+ MOZ_ASSERT(mPostSyncLoopOperations == 0);
+
+ ClearMainEventQueue(aRanOrNot);
+#ifdef DEBUG
+ if (WorkerRan == aRanOrNot) {
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+ MOZ_ASSERT(!NS_HasPendingEvents(currentThread));
+ }
+#endif
+
+ if (WorkerPrivate* parent = GetParent()) {
+ RefPtr<WorkerFinishedRunnable> runnable =
+ new WorkerFinishedRunnable(parent, this);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to dispatch runnable!");
+ }
+ } else {
+ if (ExtensionAPIAllowed()) {
+ MOZ_ASSERT(IsServiceWorker());
+ RefPtr<Runnable> extWorkerRunnable =
+ extensions::CreateWorkerDestroyedRunnable(ServiceWorkerID(),
+ GetBaseURI());
+ // Dispatch as a low priority runnable.
+ if (NS_FAILED(
+ DispatchToMainThreadForMessaging(extWorkerRunnable.forget()))) {
+ NS_WARNING(
+ "Failed to dispatch runnable to notify extensions worker "
+ "destroyed");
+ }
+ }
+
+ // Note, this uses the lower priority DispatchToMainThreadForMessaging for
+ // dispatching TopLevelWorkerFinishedRunnable to the main thread so that
+ // other relevant runnables are guaranteed to run before it.
+ RefPtr<TopLevelWorkerFinishedRunnable> runnable =
+ new TopLevelWorkerFinishedRunnable(this);
+ if (NS_FAILED(DispatchToMainThreadForMessaging(runnable.forget()))) {
+ NS_WARNING("Failed to dispatch runnable!");
+ }
+
+ // NOTE: Calling any WorkerPrivate methods (or accessing member data) after
+ // this point is unsafe (the TopLevelWorkerFinishedRunnable just dispatched
+ // may be able to call ClearSelfAndParentEventTargetRef on this
+ // WorkerPrivate instance and by the time we get here the WorkerPrivate
+ // instance destructor may have been already called).
+ }
+}
+
+bool WorkerPrivate::CollectRuntimeStats(
+ JS::RuntimeStats* aRtStats, bool aAnonymize) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ // We don't have a lock to access mJSContext, but it's safe to access on this
+ // thread.
+ AssertIsOnWorkerThread();
+ NS_ASSERTION(aRtStats, "Null RuntimeStats!");
+ // We don't really own it, but it's safe to access on this thread
+ NS_ASSERTION(mJSContext, "This must never be null!");
+
+ return JS::CollectRuntimeStats(mJSContext, aRtStats, nullptr, aAnonymize);
+}
+
+void WorkerPrivate::EnableMemoryReporter() {
+ auto data = mWorkerThreadAccessible.Access();
+ MOZ_ASSERT(!data->mMemoryReporter);
+
+ // No need to lock here since the main thread can't race until we've
+ // successfully registered the reporter.
+ data->mMemoryReporter = new MemoryReporter(this);
+
+ if (NS_FAILED(RegisterWeakAsyncMemoryReporter(data->mMemoryReporter))) {
+ NS_WARNING("Failed to register memory reporter!");
+ // No need to lock here since a failed registration means our memory
+ // reporter can't start running. Just clean up.
+ data->mMemoryReporter = nullptr;
+ }
+}
+
+void WorkerPrivate::DisableMemoryReporter() {
+ auto data = mWorkerThreadAccessible.Access();
+
+ RefPtr<MemoryReporter> memoryReporter;
+ {
+ // Mutex protectes MemoryReporter::mWorkerPrivate which is cleared by
+ // MemoryReporter::Disable() below.
+ MutexAutoLock lock(mMutex);
+
+ // There is nothing to do here if the memory reporter was never successfully
+ // registered.
+ if (!data->mMemoryReporter) {
+ return;
+ }
+
+ // We don't need this set any longer. Swap it out so that we can unregister
+ // below.
+ data->mMemoryReporter.swap(memoryReporter);
+
+ // Next disable the memory reporter so that the main thread stops trying to
+ // signal us.
+ memoryReporter->Disable();
+ }
+
+ // Finally unregister the memory reporter.
+ if (NS_FAILED(UnregisterWeakMemoryReporter(memoryReporter))) {
+ NS_WARNING("Failed to unregister memory reporter!");
+ }
+}
+
+void WorkerPrivate::WaitForWorkerEvents() {
+ AUTO_PROFILER_LABEL("WorkerPrivate::WaitForWorkerEvents", IDLE);
+
+ AssertIsOnWorkerThread();
+ mMutex.AssertCurrentThreadOwns();
+
+ // Wait for a worker event.
+ mCondVar.Wait();
+}
+
+WorkerPrivate::ProcessAllControlRunnablesResult
+WorkerPrivate::ProcessAllControlRunnablesLocked() {
+ AssertIsOnWorkerThread();
+ mMutex.AssertCurrentThreadOwns();
+
+ AutoYieldJSThreadExecution yield;
+
+ auto result = ProcessAllControlRunnablesResult::Nothing;
+
+ for (;;) {
+ WorkerControlRunnable* event;
+ if (!mControlQueue.Pop(event)) {
+ break;
+ }
+
+ MutexAutoUnlock unlock(mMutex);
+
+ MOZ_ASSERT(event);
+ if (NS_FAILED(static_cast<nsIRunnable*>(event)->Run())) {
+ result = ProcessAllControlRunnablesResult::Abort;
+ }
+
+ if (result == ProcessAllControlRunnablesResult::Nothing) {
+ // We ran at least one thing.
+ result = ProcessAllControlRunnablesResult::MayContinue;
+ }
+ event->Release();
+ }
+
+ return result;
+}
+
+void WorkerPrivate::ShutdownModuleLoader() {
+ AssertIsOnWorkerThread();
+
+ WorkerGlobalScope* globalScope = GlobalScope();
+ if (globalScope) {
+ if (globalScope->GetModuleLoader(nullptr)) {
+ globalScope->GetModuleLoader(nullptr)->Shutdown();
+ }
+ }
+ WorkerDebuggerGlobalScope* debugGlobalScope = DebuggerGlobalScope();
+ if (debugGlobalScope) {
+ if (debugGlobalScope->GetModuleLoader(nullptr)) {
+ debugGlobalScope->GetModuleLoader(nullptr)->Shutdown();
+ }
+ }
+}
+
+void WorkerPrivate::ClearMainEventQueue(WorkerRanOrNot aRanOrNot) {
+ AssertIsOnWorkerThread();
+
+ MOZ_ASSERT((mPostSyncLoopOperations & ePendingEventQueueClearing)
+ ? (mSyncLoopStack.Length() == 1)
+ : mSyncLoopStack.IsEmpty());
+ MOZ_ASSERT(!mCancelAllPendingRunnables);
+
+ mCancelAllPendingRunnables = true;
+ WorkerGlobalScope* globalScope = GlobalScope();
+ if (globalScope) {
+ // It's appropriate to disconnect event targets at the point that it's no
+ // longer possible for new tasks to be dispatched at the global, and this is
+ // that point.
+ globalScope->DisconnectGlobalTeardownObservers();
+
+ globalScope->WorkerPrivateSaysForbidScript();
+ }
+
+ if (WorkerNeverRan == aRanOrNot) {
+ nsTArray<RefPtr<WorkerRunnable>> prestart;
+ {
+ MutexAutoLock lock(mMutex);
+ mPreStartRunnables.SwapElements(prestart);
+ }
+ for (uint32_t count = prestart.Length(), index = 0; index < count;
+ index++) {
+ RefPtr<WorkerRunnable> runnable = std::move(prestart[index]);
+ static_cast<nsIRunnable*>(runnable.get())->Run();
+ }
+ } else {
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+
+ NS_ProcessPendingEvents(currentThread);
+ }
+
+ if (globalScope) {
+ globalScope->WorkerPrivateSaysAllowScript();
+ }
+ MOZ_ASSERT(mCancelAllPendingRunnables);
+ mCancelAllPendingRunnables = false;
+}
+
+void WorkerPrivate::ClearDebuggerEventQueue() {
+ while (!mDebuggerQueue.IsEmpty()) {
+ WorkerRunnable* runnable = nullptr;
+ mDebuggerQueue.Pop(runnable);
+ // It should be ok to simply release the runnable, without running it.
+ runnable->Release();
+ }
+}
+
+bool WorkerPrivate::FreezeInternal() {
+ auto data = mWorkerThreadAccessible.Access();
+ NS_ASSERTION(!data->mFrozen, "Already frozen!");
+
+ AutoYieldJSThreadExecution yield;
+
+ // The worker can freeze even if it failed to run (and doesn't have a global).
+ if (data->mScope) {
+ data->mScope->MutableClientSourceRef().Freeze();
+ }
+
+ data->mFrozen = true;
+
+ for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
+ data->mChildWorkers[index]->Freeze(nullptr);
+ }
+
+ return true;
+}
+
+bool WorkerPrivate::ThawInternal() {
+ auto data = mWorkerThreadAccessible.Access();
+ NS_ASSERTION(data->mFrozen, "Not yet frozen!");
+
+ for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
+ data->mChildWorkers[index]->Thaw(nullptr);
+ }
+
+ data->mFrozen = false;
+
+ // The worker can thaw even if it failed to run (and doesn't have a global).
+ if (data->mScope) {
+ data->mScope->MutableClientSourceRef().Thaw();
+ }
+
+ return true;
+}
+
+void WorkerPrivate::PropagateStorageAccessPermissionGrantedInternal() {
+ auto data = mWorkerThreadAccessible.Access();
+
+ mLoadInfo.mUseRegularPrincipal = true;
+ mLoadInfo.mHasStorageAccessPermissionGranted = true;
+
+ WorkerGlobalScope* globalScope = GlobalScope();
+ if (globalScope) {
+ globalScope->StorageAccessPermissionGranted();
+ }
+
+ for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
+ data->mChildWorkers[index]->PropagateStorageAccessPermissionGranted();
+ }
+}
+
+void WorkerPrivate::TraverseTimeouts(nsCycleCollectionTraversalCallback& cb) {
+ auto data = mWorkerThreadAccessible.Access();
+ for (uint32_t i = 0; i < data->mTimeouts.Length(); ++i) {
+ // TODO(erahm): No idea what's going on here.
+ TimeoutInfo* tmp = data->mTimeouts[i].get();
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHandler)
+ }
+}
+
+void WorkerPrivate::UnlinkTimeouts() {
+ auto data = mWorkerThreadAccessible.Access();
+ data->mTimeouts.Clear();
+}
+
+bool WorkerPrivate::ModifyBusyCountFromWorker(bool aIncrease) {
+ AssertIsOnWorkerThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ // If we're in shutdown then the busy count is no longer being considered so
+ // just return now.
+ if (mStatus >= Killing) {
+ return true;
+ }
+ }
+
+ RefPtr<ModifyBusyCountRunnable> runnable =
+ new ModifyBusyCountRunnable(this, aIncrease);
+ return runnable->Dispatch();
+}
+
+bool WorkerPrivate::AddChildWorker(WorkerPrivate& aChildWorker) {
+ auto data = mWorkerThreadAccessible.Access();
+
+#ifdef DEBUG
+ {
+ WorkerStatus currentStatus;
+ {
+ MutexAutoLock lock(mMutex);
+ currentStatus = mStatus;
+ }
+
+ MOZ_ASSERT(currentStatus == Running);
+ }
+#endif
+
+ NS_ASSERTION(!data->mChildWorkers.Contains(&aChildWorker),
+ "Already know about this one!");
+ data->mChildWorkers.AppendElement(&aChildWorker);
+
+ return data->mChildWorkers.Length() == 1 ? ModifyBusyCountFromWorker(true)
+ : true;
+}
+
+void WorkerPrivate::RemoveChildWorker(WorkerPrivate& aChildWorker) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ NS_ASSERTION(data->mChildWorkers.Contains(&aChildWorker),
+ "Didn't know about this one!");
+ data->mChildWorkers.RemoveElement(&aChildWorker);
+
+ if (data->mChildWorkers.IsEmpty() && !ModifyBusyCountFromWorker(false)) {
+ NS_WARNING("Failed to modify busy count!");
+ }
+}
+
+bool WorkerPrivate::AddWorkerRef(WorkerRef* aWorkerRef,
+ WorkerStatus aFailStatus) {
+ MOZ_ASSERT(aWorkerRef);
+ auto data = mWorkerThreadAccessible.Access();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mStatus >= aFailStatus) {
+ return false;
+ }
+
+ // We shouldn't create strong references to workers before their main loop
+ // begins running. Strong references must be disposed of on the worker
+ // thread, so strong references from other threads use a control runnable
+ // for that purpose. If the worker fails to reach the main loop stage then
+ // no control runnables get run and it would be impossible to get rid of the
+ // reference properly.
+ MOZ_DIAGNOSTIC_ASSERT_IF(aWorkerRef->IsPreventingShutdown(),
+ mStatus >= WorkerStatus::Running);
+ }
+
+ MOZ_ASSERT(!data->mWorkerRefs.Contains(aWorkerRef),
+ "Already know about this one!");
+
+ if (aWorkerRef->IsPreventingShutdown()) {
+ if (!data->mNumWorkerRefsPreventingShutdownStart &&
+ !ModifyBusyCountFromWorker(true)) {
+ return false;
+ }
+ data->mNumWorkerRefsPreventingShutdownStart += 1;
+ }
+
+ data->mWorkerRefs.AppendElement(aWorkerRef);
+ return true;
+}
+
+void WorkerPrivate::RemoveWorkerRef(WorkerRef* aWorkerRef) {
+ MOZ_ASSERT(aWorkerRef);
+ auto data = mWorkerThreadAccessible.Access();
+
+ MOZ_ASSERT(data->mWorkerRefs.Contains(aWorkerRef),
+ "Didn't know about this one!");
+ data->mWorkerRefs.RemoveElement(aWorkerRef);
+
+ if (aWorkerRef->IsPreventingShutdown()) {
+ data->mNumWorkerRefsPreventingShutdownStart -= 1;
+ if (!data->mNumWorkerRefsPreventingShutdownStart &&
+ !ModifyBusyCountFromWorker(false)) {
+ NS_WARNING("Failed to modify busy count!");
+ }
+ }
+}
+
+void WorkerPrivate::NotifyWorkerRefs(WorkerStatus aStatus) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ NS_ASSERTION(aStatus > Closing, "Bad status!");
+
+ for (auto* workerRef : data->mWorkerRefs.ForwardRange()) {
+ workerRef->Notify();
+ }
+
+ AutoTArray<CheckedUnsafePtr<WorkerPrivate>, 10> children;
+ children.AppendElements(data->mChildWorkers);
+
+ for (uint32_t index = 0; index < children.Length(); index++) {
+ if (!children[index]->Notify(aStatus)) {
+ NS_WARNING("Failed to notify child worker!");
+ }
+ }
+}
+
+nsresult WorkerPrivate::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
+ NS_ENSURE_ARG(aTask);
+
+ MutexAutoLock lock(mMutex);
+
+ // If we've already started running shutdown tasks, don't allow registering
+ // new ones.
+ if (mShutdownTasksRun) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(!mShutdownTasks.Contains(aTask));
+ mShutdownTasks.AppendElement(aTask);
+ return NS_OK;
+}
+
+nsresult WorkerPrivate::UnregisterShutdownTask(nsITargetShutdownTask* aTask) {
+ NS_ENSURE_ARG(aTask);
+
+ MutexAutoLock lock(mMutex);
+
+ // We've already started running shutdown tasks, so can't unregister them
+ // anymore.
+ if (mShutdownTasksRun) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return mShutdownTasks.RemoveElement(aTask) ? NS_OK : NS_ERROR_UNEXPECTED;
+}
+
+void WorkerPrivate::RunShutdownTasks() {
+ nsTArray<nsCOMPtr<nsITargetShutdownTask>> shutdownTasks;
+
+ {
+ MutexAutoLock lock(mMutex);
+ shutdownTasks = std::move(mShutdownTasks);
+ mShutdownTasks.Clear();
+ mShutdownTasksRun = true;
+ }
+
+ for (auto& task : shutdownTasks) {
+ task->TargetShutdown();
+ }
+ mWorkerHybridEventTarget->ForgetWorkerPrivate(this);
+}
+
+void WorkerPrivate::CancelAllTimeouts() {
+ auto data = mWorkerThreadAccessible.Access();
+
+ LOG(TimeoutsLog(), ("Worker %p CancelAllTimeouts.\n", this));
+
+ if (data->mTimerRunning) {
+ NS_ASSERTION(data->mTimer && data->mTimerRunnable, "Huh?!");
+ NS_ASSERTION(!data->mTimeouts.IsEmpty(), "Huh?!");
+
+ if (NS_FAILED(data->mTimer->Cancel())) {
+ NS_WARNING("Failed to cancel timer!");
+ }
+
+ for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
+ data->mTimeouts[index]->mCanceled = true;
+ }
+
+ // If mRunningExpiredTimeouts, then the fact that they are all canceled now
+ // means that the currently executing RunExpiredTimeouts will deal with
+ // them. Otherwise, we need to clean them up ourselves.
+ if (!data->mRunningExpiredTimeouts) {
+ data->mTimeouts.Clear();
+ ModifyBusyCountFromWorker(false);
+ }
+
+ // Set mTimerRunning false even if mRunningExpiredTimeouts is true, so that
+ // if we get reentered under this same RunExpiredTimeouts call we don't
+ // assert above that !mTimeouts().IsEmpty(), because that's clearly false
+ // now.
+ data->mTimerRunning = false;
+ }
+#ifdef DEBUG
+ else if (!data->mRunningExpiredTimeouts) {
+ NS_ASSERTION(data->mTimeouts.IsEmpty(), "Huh?!");
+ }
+#endif
+
+ data->mTimer = nullptr;
+ data->mTimerRunnable = nullptr;
+}
+
+already_AddRefed<nsISerialEventTarget> WorkerPrivate::CreateNewSyncLoop(
+ WorkerStatus aFailStatus) {
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(
+ aFailStatus >= Canceling,
+ "Sync loops can be created when the worker is in Running/Closing state!");
+
+ ThreadEventQueue* queue = nullptr;
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mStatus >= aFailStatus) {
+ return nullptr;
+ }
+ queue = static_cast<ThreadEventQueue*>(mThread->EventQueue());
+ }
+
+ nsCOMPtr<nsISerialEventTarget> nestedEventTarget = queue->PushEventQueue();
+ MOZ_ASSERT(nestedEventTarget);
+
+ RefPtr<EventTarget> workerEventTarget =
+ new EventTarget(this, nestedEventTarget);
+
+ {
+ // Modifications must be protected by mMutex in DEBUG builds, see comment
+ // about mSyncLoopStack in WorkerPrivate.h.
+#ifdef DEBUG
+ MutexAutoLock lock(mMutex);
+#endif
+
+ mSyncLoopStack.AppendElement(new SyncLoopInfo(workerEventTarget));
+ }
+
+ return workerEventTarget.forget();
+}
+
+nsresult WorkerPrivate::RunCurrentSyncLoop() {
+ AssertIsOnWorkerThread();
+ RefPtr<WorkerThread> thread;
+ JSContext* cx = GetJSContext();
+ MOZ_ASSERT(cx);
+ // mThread is set before we enter, and is never changed during
+ // RunCurrentSyncLoop.
+ {
+ MutexAutoLock lock(mMutex);
+ // Copy to local so we don't trigger mutex analysis lower down
+ // mThread is set before we enter, and is never changed during
+ // RunCurrentSyncLoop copy to local so we don't trigger mutex analysis
+ thread = mThread;
+ }
+
+ AutoPushEventLoopGlobal eventLoopGlobal(this, cx);
+
+ // This should not change between now and the time we finish running this sync
+ // loop.
+ uint32_t currentLoopIndex = mSyncLoopStack.Length() - 1;
+
+ SyncLoopInfo* loopInfo = mSyncLoopStack[currentLoopIndex].get();
+
+ AutoYieldJSThreadExecution yield;
+
+ MOZ_ASSERT(loopInfo);
+ MOZ_ASSERT(!loopInfo->mHasRun);
+ MOZ_ASSERT(!loopInfo->mCompleted);
+
+#ifdef DEBUG
+ loopInfo->mHasRun = true;
+#endif
+
+ {
+ while (!loopInfo->mCompleted) {
+ bool normalRunnablesPending = false;
+
+ // Don't block with the periodic GC timer running.
+ if (!NS_HasPendingEvents(thread)) {
+ SetGCTimerMode(IdleTimer);
+ }
+
+ // Wait for something to do.
+ {
+ MutexAutoLock lock(mMutex);
+
+ for (;;) {
+ while (mControlQueue.IsEmpty() && !normalRunnablesPending &&
+ !(normalRunnablesPending = NS_HasPendingEvents(thread))) {
+ WaitForWorkerEvents();
+ }
+
+ auto result = ProcessAllControlRunnablesLocked();
+ if (result != ProcessAllControlRunnablesResult::Nothing) {
+ // The state of the world may have changed. Recheck it if we need to
+ // continue.
+ normalRunnablesPending =
+ result == ProcessAllControlRunnablesResult::MayContinue &&
+ NS_HasPendingEvents(thread);
+
+ // NB: If we processed a NotifyRunnable, we might have run
+ // non-control runnables, one of which may have shut down the
+ // sync loop.
+ if (loopInfo->mCompleted) {
+ break;
+ }
+ }
+
+ // If we *didn't* run any control runnables, this should be unchanged.
+ MOZ_ASSERT(!loopInfo->mCompleted);
+
+ if (normalRunnablesPending) {
+ break;
+ }
+ }
+ }
+
+ if (normalRunnablesPending) {
+ // Make sure the periodic timer is running before we continue.
+ SetGCTimerMode(PeriodicTimer);
+
+ MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(thread, false));
+
+ // Now *might* be a good time to GC. Let the JS engine make the
+ // decision.
+ if (GetCurrentEventLoopGlobal()) {
+ // If GetCurrentEventLoopGlobal() is non-null, our JSContext is in a
+ // Realm, so it's safe to try to GC.
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(cx));
+ JS_MaybeGC(cx);
+ }
+ }
+ }
+ }
+
+ // Make sure that the stack didn't change underneath us.
+ MOZ_ASSERT(mSyncLoopStack[currentLoopIndex].get() == loopInfo);
+
+ return DestroySyncLoop(currentLoopIndex);
+}
+
+nsresult WorkerPrivate::DestroySyncLoop(uint32_t aLoopIndex) {
+ MOZ_ASSERT(!mSyncLoopStack.IsEmpty());
+ MOZ_ASSERT(mSyncLoopStack.Length() - 1 == aLoopIndex);
+
+ AutoYieldJSThreadExecution yield;
+
+ // We're about to delete the loop, stash its event target and result.
+ const auto& loopInfo = mSyncLoopStack[aLoopIndex];
+
+ nsresult result = loopInfo->mResult;
+
+ {
+ RefPtr<nsIEventTarget> nestedEventTarget(
+ loopInfo->mEventTarget->GetNestedEventTarget());
+ MOZ_ASSERT(nestedEventTarget);
+
+ loopInfo->mEventTarget->Shutdown();
+
+ {
+ MutexAutoLock lock(mMutex);
+ static_cast<ThreadEventQueue*>(mThread->EventQueue())
+ ->PopEventQueue(nestedEventTarget);
+ }
+ }
+
+ // Are we making a 1 -> 0 transition here?
+ if (mSyncLoopStack.Length() == 1) {
+ if ((mPostSyncLoopOperations & ePendingEventQueueClearing)) {
+ ClearMainEventQueue(WorkerRan);
+ }
+
+ if ((mPostSyncLoopOperations & eDispatchCancelingRunnable)) {
+ DispatchCancelingRunnable();
+ }
+
+ mPostSyncLoopOperations = 0;
+ }
+
+ {
+ // Modifications must be protected by mMutex in DEBUG builds, see comment
+ // about mSyncLoopStack in WorkerPrivate.h.
+#ifdef DEBUG
+ MutexAutoLock lock(mMutex);
+#endif
+
+ // This will delete |loopInfo|!
+ mSyncLoopStack.RemoveElementAt(aLoopIndex);
+ }
+
+ return result;
+}
+
+void WorkerPrivate::DispatchCancelingRunnable() {
+ // Here we use a normal runnable to know when the current JS chunk of code
+ // is finished. We cannot use a WorkerRunnable because they are not
+ // accepted any more by the worker, and we do not want to use a
+ // WorkerControlRunnable because they are immediately executed.
+ RefPtr<CancelingRunnable> r = new CancelingRunnable();
+ {
+ MutexAutoLock lock(mMutex);
+ mThread->nsThread::Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+ }
+
+ // At the same time, we want to be sure that we interrupt infinite loops.
+ // The following runnable starts a timer that cancel the worker, from the
+ // parent thread, after CANCELING_TIMEOUT millseconds.
+ RefPtr<CancelingWithTimeoutOnParentRunnable> rr =
+ new CancelingWithTimeoutOnParentRunnable(this);
+ rr->Dispatch();
+}
+
+void WorkerPrivate::ReportUseCounters() {
+ AssertIsOnWorkerThread();
+
+ if (mReportedUseCounters) {
+ return;
+ }
+ mReportedUseCounters = true;
+
+ if (Telemetry::HistogramUseCounterWorkerCount <= 0 || IsChromeWorker()) {
+ return;
+ }
+
+ const size_t kind = Kind();
+ switch (kind) {
+ case WorkerKindDedicated:
+ Telemetry::Accumulate(Telemetry::DEDICATED_WORKER_DESTROYED, 1);
+ break;
+ case WorkerKindShared:
+ Telemetry::Accumulate(Telemetry::SHARED_WORKER_DESTROYED, 1);
+ break;
+ case WorkerKindService:
+ Telemetry::Accumulate(Telemetry::SERVICE_WORKER_DESTROYED, 1);
+ break;
+ default:
+ MOZ_ASSERT(false, "Unknown worker kind");
+ return;
+ }
+
+ Maybe<nsCString> workerPathForLogging;
+ const bool dumpCounters = StaticPrefs::dom_use_counters_dump_worker();
+ if (dumpCounters) {
+ nsAutoCString path(Domain());
+ path.AppendLiteral("(");
+ NS_ConvertUTF16toUTF8 script(ScriptURL());
+ path.Append(script);
+ path.AppendPrintf(", 0x%p)", this);
+ workerPathForLogging.emplace(std::move(path));
+ }
+
+ static_assert(
+ static_cast<size_t>(UseCounterWorker::Count) * 3 ==
+ static_cast<size_t>(Telemetry::HistogramUseCounterWorkerCount),
+ "There should be three histograms (dedicated and shared and "
+ "servie) for each worker use counter");
+ const size_t count = static_cast<size_t>(UseCounterWorker::Count);
+ const size_t factor =
+ static_cast<size_t>(Telemetry::HistogramUseCounterWorkerCount) / count;
+ MOZ_ASSERT(factor > kind);
+
+ for (size_t c = 0; c < count; ++c) {
+ // Histograms for worker use counters use the same order as the worker kinds
+ // , so we can use the worker kind to index to corresponding histogram.
+ auto id = static_cast<Telemetry::HistogramID>(
+ Telemetry::HistogramFirstUseCounterWorker + c * factor + kind);
+ MOZ_ASSERT(id <= Telemetry::HistogramLastUseCounterWorker);
+
+ if (!GetUseCounter(static_cast<UseCounterWorker>(c))) {
+ continue;
+ }
+ if (dumpCounters) {
+ printf_stderr("USE_COUNTER_WORKER: %s - %s\n",
+ Telemetry::GetHistogramName(id),
+ workerPathForLogging->get());
+ }
+ Telemetry::Accumulate(id, 1);
+ }
+}
+
+void WorkerPrivate::StopSyncLoop(nsIEventTarget* aSyncLoopTarget,
+ nsresult aResult) {
+ AssertIsOnWorkerThread();
+ AssertValidSyncLoop(aSyncLoopTarget);
+
+ MOZ_ASSERT(!mSyncLoopStack.IsEmpty());
+
+ for (uint32_t index = mSyncLoopStack.Length(); index > 0; index--) {
+ const auto& loopInfo = mSyncLoopStack[index - 1];
+ MOZ_ASSERT(loopInfo);
+ MOZ_ASSERT(loopInfo->mEventTarget);
+
+ if (loopInfo->mEventTarget == aSyncLoopTarget) {
+ // Can't assert |loop->mHasRun| here because dispatch failures can cause
+ // us to bail out early.
+ MOZ_ASSERT(!loopInfo->mCompleted);
+
+ loopInfo->mResult = aResult;
+ loopInfo->mCompleted = true;
+
+ loopInfo->mEventTarget->Disable();
+
+ return;
+ }
+
+ MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
+ }
+
+ MOZ_CRASH("Unknown sync loop!");
+}
+
+#ifdef DEBUG
+void WorkerPrivate::AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget) {
+ MOZ_ASSERT(aSyncLoopTarget);
+
+ EventTarget* workerTarget;
+ nsresult rv = aSyncLoopTarget->QueryInterface(
+ kDEBUGWorkerEventTargetIID, reinterpret_cast<void**>(&workerTarget));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(workerTarget);
+
+ bool valid = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ for (uint32_t index = 0; index < mSyncLoopStack.Length(); index++) {
+ const auto& loopInfo = mSyncLoopStack[index];
+ MOZ_ASSERT(loopInfo);
+ MOZ_ASSERT(loopInfo->mEventTarget);
+
+ if (loopInfo->mEventTarget == aSyncLoopTarget) {
+ valid = true;
+ break;
+ }
+
+ MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
+ }
+ }
+
+ MOZ_ASSERT(valid);
+}
+#endif
+
+void WorkerPrivate::PostMessageToParent(
+ JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Sequence<JSObject*>& aTransferable, ErrorResult& aRv) {
+ AssertIsOnWorkerThread();
+ MOZ_DIAGNOSTIC_ASSERT(IsDedicatedWorker());
+
+ JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
+
+ aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
+ &transferable);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ RefPtr<MessageEventRunnable> runnable = new MessageEventRunnable(
+ this, WorkerRunnable::ParentThreadUnchangedBusyCount);
+
+ UniquePtr<AbstractTimelineMarker> start;
+ UniquePtr<AbstractTimelineMarker> end;
+ bool isTimelineRecording = !TimelineConsumers::IsEmpty();
+
+ if (isTimelineRecording) {
+ start = MakeUnique<WorkerTimelineMarker>(
+ NS_IsMainThread()
+ ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+ MarkerTracingType::START);
+ }
+
+ JS::CloneDataPolicy clonePolicy;
+
+ // Parent and dedicated workers are always part of the same cluster.
+ clonePolicy.allowIntraClusterClonableSharedObjects();
+
+ if (IsSharedMemoryAllowed()) {
+ clonePolicy.allowSharedMemoryObjects();
+ }
+
+ runnable->Write(aCx, aMessage, transferable, clonePolicy, aRv);
+
+ if (isTimelineRecording) {
+ end = MakeUnique<WorkerTimelineMarker>(
+ NS_IsMainThread()
+ ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+ MarkerTracingType::END);
+ TimelineConsumers::AddMarkerForAllObservedDocShells(start);
+ TimelineConsumers::AddMarkerForAllObservedDocShells(end);
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (!runnable->Dispatch()) {
+ aRv = NS_ERROR_FAILURE;
+ }
+}
+
+void WorkerPrivate::EnterDebuggerEventLoop() {
+ auto data = mWorkerThreadAccessible.Access();
+
+ JSContext* cx = GetJSContext();
+ MOZ_ASSERT(cx);
+
+ AutoPushEventLoopGlobal eventLoopGlobal(this, cx);
+ AutoYieldJSThreadExecution yield;
+
+ CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
+
+ uint32_t currentEventLoopLevel = ++data->mDebuggerEventLoopLevel;
+
+ while (currentEventLoopLevel <= data->mDebuggerEventLoopLevel) {
+ bool debuggerRunnablesPending = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
+ }
+
+ // Don't block with the periodic GC timer running.
+ if (!debuggerRunnablesPending) {
+ SetGCTimerMode(IdleTimer);
+ }
+
+ // Wait for something to do
+ {
+ MutexAutoLock lock(mMutex);
+
+ std::deque<RefPtr<MicroTaskRunnable>>& debuggerMtQueue =
+ ccjscx->GetDebuggerMicroTaskQueue();
+ while (mControlQueue.IsEmpty() &&
+ !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
+ debuggerMtQueue.empty()) {
+ WaitForWorkerEvents();
+ }
+
+ ProcessAllControlRunnablesLocked();
+
+ // XXXkhuey should we abort JS on the stack here if we got Abort above?
+ }
+ ccjscx->PerformDebuggerMicroTaskCheckpoint();
+ if (debuggerRunnablesPending) {
+ // Start the periodic GC timer if it is not already running.
+ SetGCTimerMode(PeriodicTimer);
+
+ WorkerRunnable* runnable = nullptr;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ mDebuggerQueue.Pop(runnable);
+ }
+
+ MOZ_ASSERT(runnable);
+ static_cast<nsIRunnable*>(runnable)->Run();
+ runnable->Release();
+
+ ccjscx->PerformDebuggerMicroTaskCheckpoint();
+
+ // Now *might* be a good time to GC. Let the JS engine make the decision.
+ if (GetCurrentEventLoopGlobal()) {
+ // If GetCurrentEventLoopGlobal() is non-null, our JSContext is in a
+ // Realm, so it's safe to try to GC.
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(cx));
+ JS_MaybeGC(cx);
+ }
+ }
+ }
+}
+
+void WorkerPrivate::LeaveDebuggerEventLoop() {
+ auto data = mWorkerThreadAccessible.Access();
+
+ // TODO: Why lock the mutex if we're accessing data accessible to one thread
+ // only?
+ MutexAutoLock lock(mMutex);
+
+ if (data->mDebuggerEventLoopLevel > 0) {
+ --data->mDebuggerEventLoopLevel;
+ }
+}
+
+void WorkerPrivate::PostMessageToDebugger(const nsAString& aMessage) {
+ mDebugger->PostMessageToDebugger(aMessage);
+}
+
+void WorkerPrivate::SetDebuggerImmediate(dom::Function& aHandler,
+ ErrorResult& aRv) {
+ AssertIsOnWorkerThread();
+
+ RefPtr<DebuggerImmediateRunnable> runnable =
+ new DebuggerImmediateRunnable(this, aHandler);
+ if (!runnable->Dispatch()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+void WorkerPrivate::ReportErrorToDebugger(const nsAString& aFilename,
+ uint32_t aLineno,
+ const nsAString& aMessage) {
+ mDebugger->ReportErrorToDebugger(aFilename, aLineno, aMessage);
+}
+
+bool WorkerPrivate::NotifyInternal(WorkerStatus aStatus) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ // Yield execution while notifying out-of-module WorkerRefs and cancelling
+ // runnables.
+ AutoYieldJSThreadExecution yield;
+
+ NS_ASSERTION(aStatus > Running && aStatus < Dead, "Bad status!");
+
+ RefPtr<EventTarget> eventTarget;
+
+ // Save the old status and set the new status.
+ WorkerStatus previousStatus;
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mStatus >= aStatus) {
+ return true;
+ }
+
+ MOZ_ASSERT_IF(aStatus == Killing, mStatus == Canceling);
+
+ if (aStatus >= Canceling) {
+ MutexAutoUnlock unlock(mMutex);
+ if (data->mScope) {
+ if (aStatus == Canceling) {
+ data->mScope->NoteTerminating();
+ } else {
+ data->mScope->NoteShuttingDown();
+ }
+ }
+ }
+
+ previousStatus = mStatus;
+ mStatus = aStatus;
+
+ // Mark parent status as closing immediately to avoid new events being
+ // dispatched after we clear the queue below.
+ if (aStatus == Closing) {
+ Close();
+ }
+ }
+
+ MOZ_ASSERT(previousStatus != Pending);
+
+ if (aStatus >= Closing) {
+ CancelAllTimeouts();
+ }
+
+ // Let all our holders know the new status.
+ if (aStatus > Closing) {
+ NotifyWorkerRefs(aStatus);
+ }
+
+ // If this is the first time our status has changed then we need to clear the
+ // main event queue.
+ if (previousStatus == Running) {
+ // NB: If we're in a sync loop, we can't clear the queue immediately,
+ // because this is the wrong queue. So we have to defer it until later.
+ if (!mSyncLoopStack.IsEmpty()) {
+ mPostSyncLoopOperations |= ePendingEventQueueClearing;
+ } else {
+ ClearMainEventQueue(WorkerRan);
+ }
+ }
+
+ // If the worker script never ran, or failed to compile, we don't need to do
+ // anything else.
+ WorkerGlobalScope* global = GlobalScope();
+ if (!global) {
+ return true;
+ }
+
+ if (WebTaskScheduler* scheduler = global->GetExistingScheduler()) {
+ scheduler->Disconnect();
+ }
+
+ // Don't abort the script now, but we dispatch a runnable to do it when the
+ // current JS frame is executed.
+ if (aStatus == Closing) {
+ if (!mSyncLoopStack.IsEmpty()) {
+ mPostSyncLoopOperations |= eDispatchCancelingRunnable;
+ } else {
+ DispatchCancelingRunnable();
+ }
+ return true;
+ }
+
+ MOZ_ASSERT(aStatus == Canceling || aStatus == Killing);
+
+ // Always abort the script.
+ return false;
+}
+
+void WorkerPrivate::ReportError(JSContext* aCx,
+ JS::ConstUTF8CharsZ aToStringResult,
+ JSErrorReport* aReport) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ if (!MayContinueRunning() || data->mErrorHandlerRecursionCount == 2) {
+ return;
+ }
+
+ NS_ASSERTION(data->mErrorHandlerRecursionCount == 0 ||
+ data->mErrorHandlerRecursionCount == 1,
+ "Bad recursion logic!");
+
+ UniquePtr<WorkerErrorReport> report = MakeUnique<WorkerErrorReport>();
+ if (aReport) {
+ report->AssignErrorReport(aReport);
+ }
+
+ JS::ExceptionStack exnStack(aCx);
+ if (JS_IsExceptionPending(aCx)) {
+ if (!JS::StealPendingExceptionStack(aCx, &exnStack)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+
+ JS::Rooted<JSObject*> stack(aCx), stackGlobal(aCx);
+ xpc::FindExceptionStackForConsoleReport(
+ nullptr, exnStack.exception(), exnStack.stack(), &stack, &stackGlobal);
+
+ if (stack) {
+ JSAutoRealm ar(aCx, stackGlobal);
+ report->SerializeWorkerStack(aCx, this, stack);
+ }
+ } else {
+ // ReportError is also used for reporting warnings,
+ // so there won't be a pending exception.
+ MOZ_ASSERT(aReport && aReport->isWarning());
+ }
+
+ if (report->mMessage.IsEmpty() && aToStringResult) {
+ nsDependentCString toStringResult(aToStringResult.c_str());
+ if (!AppendUTF8toUTF16(toStringResult, report->mMessage,
+ mozilla::fallible)) {
+ // Try again, with only a 1 KB string. Do this infallibly this time.
+ // If the user doesn't have 1 KB to spare we're done anyways.
+ size_t index = std::min<size_t>(1024, toStringResult.Length());
+
+ // Drop the last code point that may be cropped.
+ index = RewindToPriorUTF8Codepoint(toStringResult.BeginReading(), index);
+
+ nsDependentCString truncatedToStringResult(aToStringResult.c_str(),
+ index);
+ AppendUTF8toUTF16(truncatedToStringResult, report->mMessage);
+ }
+ }
+
+ data->mErrorHandlerRecursionCount++;
+
+ // Don't want to run the scope's error handler if this is a recursive error or
+ // if we ran out of memory.
+ bool fireAtScope = data->mErrorHandlerRecursionCount == 1 &&
+ report->mErrorNumber != JSMSG_OUT_OF_MEMORY &&
+ JS::CurrentGlobalOrNull(aCx);
+
+ WorkerErrorReport::ReportError(aCx, this, fireAtScope, nullptr,
+ std::move(report), 0, exnStack.exception());
+
+ data->mErrorHandlerRecursionCount--;
+}
+
+// static
+void WorkerPrivate::ReportErrorToConsole(const char* aMessage) {
+ nsTArray<nsString> emptyParams;
+ WorkerPrivate::ReportErrorToConsole(aMessage, emptyParams);
+}
+
+// static
+void WorkerPrivate::ReportErrorToConsole(const char* aMessage,
+ const nsTArray<nsString>& aParams) {
+ WorkerPrivate* wp = nullptr;
+ if (!NS_IsMainThread()) {
+ wp = GetCurrentThreadWorkerPrivate();
+ }
+
+ ReportErrorToConsoleRunnable::Report(wp, aMessage, aParams);
+}
+
+int32_t WorkerPrivate::SetTimeout(JSContext* aCx, TimeoutHandler* aHandler,
+ int32_t aTimeout, bool aIsInterval,
+ Timeout::Reason aReason, ErrorResult& aRv) {
+ auto data = mWorkerThreadAccessible.Access();
+ MOZ_ASSERT(aHandler);
+
+ // Reasons that doesn't support cancellation will get -1 as their ids.
+ int32_t timerId = -1;
+ if (aReason == Timeout::Reason::eTimeoutOrInterval) {
+ timerId = data->mNextTimeoutId;
+ data->mNextTimeoutId += 1;
+ }
+
+ WorkerStatus currentStatus;
+ {
+ MutexAutoLock lock(mMutex);
+ currentStatus = mStatus;
+ }
+
+ // If the worker is trying to call setTimeout/setInterval and the parent
+ // thread has initiated the close process then just silently fail.
+ if (currentStatus >= Closing) {
+ return timerId;
+ }
+
+ auto newInfo = MakeUnique<TimeoutInfo>();
+ newInfo->mReason = aReason;
+ newInfo->mOnChromeWorker = mIsChromeWorker;
+ newInfo->mIsInterval = aIsInterval;
+ newInfo->mId = timerId;
+ if (newInfo->mReason == Timeout::Reason::eTimeoutOrInterval ||
+ newInfo->mReason == Timeout::Reason::eIdleCallbackTimeout) {
+ newInfo->AccumulateNestingLevel(data->mCurrentTimerNestingLevel);
+ }
+
+ if (MOZ_UNLIKELY(timerId == INT32_MAX)) {
+ NS_WARNING("Timeout ids overflowed!");
+ if (aReason == Timeout::Reason::eTimeoutOrInterval) {
+ data->mNextTimeoutId = 1;
+ }
+ }
+
+ newInfo->mHandler = aHandler;
+
+ // See if any of the optional arguments were passed.
+ aTimeout = std::max(0, aTimeout);
+ newInfo->mInterval = TimeDuration::FromMilliseconds(aTimeout);
+ newInfo->CalculateTargetTime();
+
+ const auto& insertedInfo = data->mTimeouts.InsertElementSorted(
+ std::move(newInfo), GetUniquePtrComparator(data->mTimeouts));
+
+ LOG(TimeoutsLog(), ("Worker %p has new timeout: delay=%d interval=%s\n", this,
+ aTimeout, aIsInterval ? "yes" : "no"));
+
+ // If the timeout we just made is set to fire next then we need to update the
+ // timer, unless we're currently running timeouts.
+ if (insertedInfo == data->mTimeouts.Elements() &&
+ !data->mRunningExpiredTimeouts) {
+ if (!data->mTimer) {
+ data->mTimer =
+ NS_NewTimer(GlobalScope()->EventTargetFor(TaskCategory::Timer));
+ if (!data->mTimer) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return 0;
+ }
+
+ data->mTimerRunnable = new TimerRunnable(this);
+ }
+
+ if (!data->mTimerRunning) {
+ if (!ModifyBusyCountFromWorker(true)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return 0;
+ }
+ data->mTimerRunning = true;
+ }
+
+ if (!RescheduleTimeoutTimer(aCx)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return 0;
+ }
+ }
+
+ return timerId;
+}
+
+void WorkerPrivate::ClearTimeout(int32_t aId, Timeout::Reason aReason) {
+ MOZ_ASSERT(aReason == Timeout::Reason::eTimeoutOrInterval,
+ "This timeout reason doesn't support cancellation.");
+
+ auto data = mWorkerThreadAccessible.Access();
+
+ if (!data->mTimeouts.IsEmpty()) {
+ NS_ASSERTION(data->mTimerRunning, "Huh?!");
+
+ for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
+ const auto& info = data->mTimeouts[index];
+ if (info->mId == aId && info->mReason == aReason) {
+ info->mCanceled = true;
+ break;
+ }
+ }
+ }
+}
+
+bool WorkerPrivate::RunExpiredTimeouts(JSContext* aCx) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ // We may be called recursively (e.g. close() inside a timeout) or we could
+ // have been canceled while this event was pending, bail out if there is
+ // nothing to do.
+ if (data->mRunningExpiredTimeouts || !data->mTimerRunning) {
+ return true;
+ }
+
+ NS_ASSERTION(data->mTimer && data->mTimerRunnable, "Must have a timer!");
+ NS_ASSERTION(!data->mTimeouts.IsEmpty(), "Should have some work to do!");
+
+ bool retval = true;
+
+ auto comparator = GetUniquePtrComparator(data->mTimeouts);
+ JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+
+ // We want to make sure to run *something*, even if the timer fired a little
+ // early. Fudge the value of now to at least include the first timeout.
+ const TimeStamp actual_now = TimeStamp::Now();
+ const TimeStamp now = std::max(actual_now, data->mTimeouts[0]->mTargetTime);
+
+ if (now != actual_now) {
+ LOG(TimeoutsLog(), ("Worker %p fudged timeout by %f ms.\n", this,
+ (now - actual_now).ToMilliseconds()));
+#ifdef DEBUG
+ double microseconds = (now - actual_now).ToMicroseconds();
+ uint32_t allowedEarlyFiringMicroseconds;
+ data->mTimer->GetAllowedEarlyFiringMicroseconds(
+ &allowedEarlyFiringMicroseconds);
+ MOZ_ASSERT(microseconds < allowedEarlyFiringMicroseconds);
+#endif
+ }
+
+ AutoTArray<TimeoutInfo*, 10> expiredTimeouts;
+ for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
+ TimeoutInfo* info = data->mTimeouts[index].get();
+ if (info->mTargetTime > now) {
+ break;
+ }
+ expiredTimeouts.AppendElement(info);
+ }
+
+ // Guard against recursion.
+ data->mRunningExpiredTimeouts = true;
+
+ MOZ_DIAGNOSTIC_ASSERT(data->mCurrentTimerNestingLevel == 0);
+
+ // Run expired timeouts.
+ for (uint32_t index = 0; index < expiredTimeouts.Length(); index++) {
+ TimeoutInfo*& info = expiredTimeouts[index];
+ AutoRestore<uint32_t> nestingLevel(data->mCurrentTimerNestingLevel);
+
+ if (info->mCanceled) {
+ continue;
+ }
+
+ // Set current timer nesting level to current running timer handler's
+ // nesting level
+ data->mCurrentTimerNestingLevel = info->mNestingLevel;
+
+ LOG(TimeoutsLog(),
+ ("Worker %p executing timeout with original delay %f ms.\n", this,
+ info->mInterval.ToMilliseconds()));
+
+ // Always check JS_IsExceptionPending if something fails, and if
+ // JS_IsExceptionPending returns false (i.e. uncatchable exception) then
+ // break out of the loop.
+
+ RefPtr<TimeoutHandler> handler(info->mHandler);
+
+ const char* reason;
+ switch (info->mReason) {
+ case Timeout::Reason::eTimeoutOrInterval:
+ if (info->mIsInterval) {
+ reason = "setInterval handler";
+ } else {
+ reason = "setTimeout handler";
+ }
+ break;
+ case Timeout::Reason::eDelayedWebTaskTimeout:
+ reason = "delayedWebTask handler";
+ break;
+ default:
+ MOZ_ASSERT(info->mReason == Timeout::Reason::eAbortSignalTimeout);
+ reason = "AbortSignal Timeout";
+ }
+ if (info->mReason == Timeout::Reason::eTimeoutOrInterval ||
+ info->mReason == Timeout::Reason::eDelayedWebTaskTimeout) {
+ RefPtr<WorkerGlobalScope> scope(this->GlobalScope());
+ CallbackDebuggerNotificationGuard guard(
+ scope, info->mIsInterval
+ ? DebuggerNotificationType::SetIntervalCallback
+ : DebuggerNotificationType::SetTimeoutCallback);
+
+ if (!handler->Call(reason)) {
+ retval = false;
+ break;
+ }
+ } else {
+ MOZ_ASSERT(info->mReason == Timeout::Reason::eAbortSignalTimeout);
+ MOZ_ALWAYS_TRUE(handler->Call(reason));
+ }
+
+ NS_ASSERTION(data->mRunningExpiredTimeouts, "Someone changed this!");
+ }
+
+ // No longer possible to be called recursively.
+ data->mRunningExpiredTimeouts = false;
+
+ // Now remove canceled and expired timeouts from the main list.
+ // NB: The timeouts present in expiredTimeouts must have the same order
+ // with respect to each other in mTimeouts. That is, mTimeouts is just
+ // expiredTimeouts with extra elements inserted. There may be unexpired
+ // timeouts that have been inserted between the expired timeouts if the
+ // timeout event handler called setTimeout/setInterval.
+ for (uint32_t index = 0, expiredTimeoutIndex = 0,
+ expiredTimeoutLength = expiredTimeouts.Length();
+ index < data->mTimeouts.Length();) {
+ const auto& info = data->mTimeouts[index];
+ if ((expiredTimeoutIndex < expiredTimeoutLength &&
+ info == expiredTimeouts[expiredTimeoutIndex] &&
+ ++expiredTimeoutIndex) ||
+ info->mCanceled) {
+ if (info->mIsInterval && !info->mCanceled) {
+ // Reschedule intervals.
+ // Reschedule a timeout, if needed, increase the nesting level.
+ info->AccumulateNestingLevel(info->mNestingLevel);
+ info->CalculateTargetTime();
+ // Don't resort the list here, we'll do that at the end.
+ ++index;
+ } else {
+ data->mTimeouts.RemoveElement(info);
+ }
+ } else {
+ // If info did not match the current entry in expiredTimeouts, it
+ // shouldn't be there at all.
+ NS_ASSERTION(!expiredTimeouts.Contains(info),
+ "Our timeouts are out of order!");
+ ++index;
+ }
+ }
+
+ data->mTimeouts.Sort(comparator);
+
+ // Either signal the parent that we're no longer using timeouts or reschedule
+ // the timer.
+ if (data->mTimeouts.IsEmpty()) {
+ if (!ModifyBusyCountFromWorker(false)) {
+ retval = false;
+ }
+ data->mTimerRunning = false;
+ } else if (retval && !RescheduleTimeoutTimer(aCx)) {
+ retval = false;
+ }
+
+ return retval;
+}
+
+bool WorkerPrivate::RescheduleTimeoutTimer(JSContext* aCx) {
+ auto data = mWorkerThreadAccessible.Access();
+ MOZ_ASSERT(!data->mRunningExpiredTimeouts);
+ NS_ASSERTION(!data->mTimeouts.IsEmpty(), "Should have some timeouts!");
+ NS_ASSERTION(data->mTimer && data->mTimerRunnable, "Should have a timer!");
+
+ // NB: This is important! The timer may have already fired, e.g. if a timeout
+ // callback itself calls setTimeout for a short duration and then takes longer
+ // than that to finish executing. If that has happened, it's very important
+ // that we don't execute the event that is now pending in our event queue, or
+ // our code in RunExpiredTimeouts to "fudge" the timeout value will unleash an
+ // early timeout when we execute the event we're about to queue.
+ data->mTimer->Cancel();
+
+ double delta =
+ (data->mTimeouts[0]->mTargetTime - TimeStamp::Now()).ToMilliseconds();
+ uint32_t delay = delta > 0 ? static_cast<uint32_t>(std::ceil(
+ std::min(delta, double(UINT32_MAX))))
+ : 0;
+
+ LOG(TimeoutsLog(),
+ ("Worker %p scheduled timer for %d ms, %zu pending timeouts\n", this,
+ delay, data->mTimeouts.Length()));
+
+ nsresult rv = data->mTimer->InitWithCallback(data->mTimerRunnable, delay,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ JS_ReportErrorASCII(aCx, "Failed to start timer!");
+ return false;
+ }
+
+ return true;
+}
+
+void WorkerPrivate::StartCancelingTimer() {
+ AssertIsOnParentThread();
+
+ auto errorCleanup = MakeScopeExit([&] { mCancelingTimer = nullptr; });
+
+ MOZ_ASSERT(!mCancelingTimer);
+
+ if (WorkerPrivate* parent = GetParent()) {
+ mCancelingTimer = NS_NewTimer(parent->ControlEventTarget());
+ } else {
+ mCancelingTimer = NS_NewTimer();
+ }
+
+ if (NS_WARN_IF(!mCancelingTimer)) {
+ return;
+ }
+
+ // This is not needed if we are already in an advanced shutdown state.
+ {
+ MutexAutoLock lock(mMutex);
+ if (ParentStatus() >= Canceling) {
+ return;
+ }
+ }
+
+ uint32_t cancelingTimeoutMillis =
+ StaticPrefs::dom_worker_canceling_timeoutMilliseconds();
+
+ RefPtr<CancelingTimerCallback> callback = new CancelingTimerCallback(this);
+ nsresult rv = mCancelingTimer->InitWithCallback(
+ callback, cancelingTimeoutMillis, nsITimer::TYPE_ONE_SHOT);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ errorCleanup.release();
+}
+
+void WorkerPrivate::UpdateContextOptionsInternal(
+ JSContext* aCx, const JS::ContextOptions& aContextOptions) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ JS::ContextOptionsRef(aCx) = aContextOptions;
+
+ for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
+ data->mChildWorkers[index]->UpdateContextOptions(aContextOptions);
+ }
+}
+
+void WorkerPrivate::UpdateLanguagesInternal(
+ const nsTArray<nsString>& aLanguages) {
+ WorkerGlobalScope* globalScope = GlobalScope();
+ RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
+ if (nav) {
+ nav->SetLanguages(aLanguages);
+ }
+
+ auto data = mWorkerThreadAccessible.Access();
+ for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
+ data->mChildWorkers[index]->UpdateLanguages(aLanguages);
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(globalScope, nullptr, nullptr);
+
+ event->InitEvent(u"languagechange"_ns, false, false);
+ event->SetTrusted(true);
+
+ globalScope->DispatchEvent(*event);
+}
+
+void WorkerPrivate::UpdateJSWorkerMemoryParameterInternal(
+ JSContext* aCx, JSGCParamKey aKey, Maybe<uint32_t> aValue) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ if (aValue) {
+ JS_SetGCParameter(aCx, aKey, *aValue);
+ } else {
+ JS_ResetGCParameter(aCx, aKey);
+ }
+
+ for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
+ data->mChildWorkers[index]->UpdateJSWorkerMemoryParameter(aKey, aValue);
+ }
+}
+
+#ifdef JS_GC_ZEAL
+void WorkerPrivate::UpdateGCZealInternal(JSContext* aCx, uint8_t aGCZeal,
+ uint32_t aFrequency) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ JS_SetGCZeal(aCx, aGCZeal, aFrequency);
+
+ for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
+ data->mChildWorkers[index]->UpdateGCZeal(aGCZeal, aFrequency);
+ }
+}
+#endif
+
+void WorkerPrivate::SetLowMemoryStateInternal(JSContext* aCx, bool aState) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ JS::SetLowMemoryState(aCx, aState);
+
+ for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
+ data->mChildWorkers[index]->SetLowMemoryState(aState);
+ }
+}
+
+void WorkerPrivate::SetCCCollectedAnything(bool collectedAnything) {
+ mWorkerThreadAccessible.Access()->mCCCollectedAnything = collectedAnything;
+}
+
+bool WorkerPrivate::isLastCCCollectedAnything() {
+ return mWorkerThreadAccessible.Access()->mCCCollectedAnything;
+}
+
+void WorkerPrivate::GarbageCollectInternal(JSContext* aCx, bool aShrinking,
+ bool aCollectChildren) {
+ // Perform GC followed by CC (the CC is triggered by
+ // WorkerJSRuntime::CustomGCCallback at the end of the collection).
+
+ auto data = mWorkerThreadAccessible.Access();
+
+ if (!GlobalScope()) {
+ // We haven't compiled anything yet. Just bail out.
+ return;
+ }
+
+ if (aShrinking || aCollectChildren) {
+ JS::PrepareForFullGC(aCx);
+
+ if (aShrinking && mSyncLoopStack.IsEmpty()) {
+ JS::NonIncrementalGC(aCx, JS::GCOptions::Shrink,
+ JS::GCReason::DOM_WORKER);
+
+ // Check whether the CC collected anything and if so GC again. This is
+ // necessary to collect all garbage.
+ if (data->mCCCollectedAnything) {
+ JS::NonIncrementalGC(aCx, JS::GCOptions::Normal,
+ JS::GCReason::DOM_WORKER);
+ }
+
+ if (!aCollectChildren) {
+ LOG(WorkerLog(), ("Worker %p collected idle garbage\n", this));
+ }
+ } else {
+ JS::NonIncrementalGC(aCx, JS::GCOptions::Normal,
+ JS::GCReason::DOM_WORKER);
+ LOG(WorkerLog(), ("Worker %p collected garbage\n", this));
+ }
+ } else {
+ JS_MaybeGC(aCx);
+ LOG(WorkerLog(), ("Worker %p collected periodic garbage\n", this));
+ }
+
+ if (aCollectChildren) {
+ for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
+ data->mChildWorkers[index]->GarbageCollect(aShrinking);
+ }
+ }
+}
+
+void WorkerPrivate::CycleCollectInternal(bool aCollectChildren) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ nsCycleCollector_collect(CCReason::WORKER, nullptr);
+
+ if (aCollectChildren) {
+ for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
+ data->mChildWorkers[index]->CycleCollect();
+ }
+ }
+}
+
+void WorkerPrivate::MemoryPressureInternal() {
+ auto data = mWorkerThreadAccessible.Access();
+
+ if (data->mScope) {
+ RefPtr<Console> console = data->mScope->GetConsoleIfExists();
+ if (console) {
+ console->ClearStorage();
+ }
+
+ RefPtr<Performance> performance = data->mScope->GetPerformanceIfExists();
+ if (performance) {
+ performance->MemoryPressure();
+ }
+
+ data->mScope->RemoveReportRecords();
+ }
+
+ if (data->mDebuggerScope) {
+ RefPtr<Console> console = data->mDebuggerScope->GetConsoleIfExists();
+ if (console) {
+ console->ClearStorage();
+ }
+ }
+
+ for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
+ data->mChildWorkers[index]->MemoryPressure();
+ }
+}
+
+void WorkerPrivate::SetThread(WorkerThread* aThread) {
+ if (aThread) {
+#ifdef DEBUG
+ {
+ bool isOnCurrentThread;
+ MOZ_ASSERT(NS_SUCCEEDED(aThread->IsOnCurrentThread(&isOnCurrentThread)));
+ MOZ_ASSERT(!isOnCurrentThread);
+ }
+#endif
+
+ MOZ_ASSERT(!mPRThread);
+ mPRThread = PRThreadFromThread(aThread);
+ MOZ_ASSERT(mPRThread);
+
+ mWorkerThreadAccessible.Transfer(mPRThread);
+ } else {
+ MOZ_ASSERT(mPRThread);
+ }
+}
+
+void WorkerPrivate::SetWorkerPrivateInWorkerThread(
+ WorkerThread* const aThread) {
+ MutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(!mThread);
+ MOZ_ASSERT(mStatus == Pending);
+
+ mThread = aThread;
+ mThread->SetWorker(WorkerThreadFriendKey{}, this);
+
+ if (!mPreStartRunnables.IsEmpty()) {
+ for (uint32_t index = 0; index < mPreStartRunnables.Length(); index++) {
+ MOZ_ALWAYS_SUCCEEDS(mThread->DispatchAnyThread(
+ WorkerThreadFriendKey{}, mPreStartRunnables[index].forget()));
+ }
+ mPreStartRunnables.Clear();
+ }
+}
+
+void WorkerPrivate::ResetWorkerPrivateInWorkerThread() {
+ RefPtr<WorkerThread> doomedThread;
+
+ // Release the mutex before doomedThread.
+ MutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(mThread);
+
+ mThread->SetWorker(WorkerThreadFriendKey{}, nullptr);
+ mThread.swap(doomedThread);
+}
+
+void WorkerPrivate::BeginCTypesCall() {
+ AssertIsOnWorkerThread();
+ auto data = mWorkerThreadAccessible.Access();
+
+ // Don't try to GC while we're blocked in a ctypes call.
+ SetGCTimerMode(NoTimer);
+
+ data->mYieldJSThreadExecution.EmplaceBack();
+}
+
+void WorkerPrivate::EndCTypesCall() {
+ AssertIsOnWorkerThread();
+ auto data = mWorkerThreadAccessible.Access();
+
+ data->mYieldJSThreadExecution.RemoveLastElement();
+
+ // Make sure the periodic timer is running before we start running JS again.
+ SetGCTimerMode(PeriodicTimer);
+}
+
+void WorkerPrivate::BeginCTypesCallback() {
+ AssertIsOnWorkerThread();
+
+ // Make sure the periodic timer is running before we start running JS again.
+ SetGCTimerMode(PeriodicTimer);
+
+ // Re-requesting execution is not needed since the JSRuntime code calling
+ // this will do an AutoEntryScript.
+}
+
+void WorkerPrivate::EndCTypesCallback() {
+ AssertIsOnWorkerThread();
+
+ // Don't try to GC while we're blocked in a ctypes call.
+ SetGCTimerMode(NoTimer);
+}
+
+bool WorkerPrivate::ConnectMessagePort(JSContext* aCx,
+ UniqueMessagePortId& aIdentifier) {
+ AssertIsOnWorkerThread();
+
+ WorkerGlobalScope* globalScope = GlobalScope();
+
+ JS::Rooted<JSObject*> jsGlobal(aCx, globalScope->GetWrapper());
+ MOZ_ASSERT(jsGlobal);
+
+ // This UniqueMessagePortId is used to create a new port, still connected
+ // with the other one, but in the worker thread.
+ ErrorResult rv;
+ RefPtr<MessagePort> port = MessagePort::Create(globalScope, aIdentifier, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ GlobalObject globalObject(aCx, jsGlobal);
+ if (globalObject.Failed()) {
+ return false;
+ }
+
+ RootedDictionary<MessageEventInit> init(aCx);
+ init.mData = JS_GetEmptyStringValue(aCx);
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mSource.SetValue().SetAsMessagePort() = port;
+ if (!init.mPorts.AppendElement(port.forget(), fallible)) {
+ return false;
+ }
+
+ RefPtr<MessageEvent> event =
+ MessageEvent::Constructor(globalObject, u"connect"_ns, init);
+
+ event->SetTrusted(true);
+
+ globalScope->DispatchEvent(*event);
+
+ return true;
+}
+
+WorkerGlobalScope* WorkerPrivate::GetOrCreateGlobalScope(JSContext* aCx) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ if (data->mScope) {
+ return data->mScope;
+ }
+
+ bool rfp = mLoadInfo.mShouldResistFingerprinting;
+
+ if (IsSharedWorker()) {
+ data->mScope = new SharedWorkerGlobalScope(this, CreateClientSource(),
+ WorkerName(), rfp);
+ } else if (IsServiceWorker()) {
+ data->mScope = new ServiceWorkerGlobalScope(
+ this, CreateClientSource(), GetServiceWorkerRegistrationDescriptor(),
+ rfp);
+ } else {
+ data->mScope = new DedicatedWorkerGlobalScope(this, CreateClientSource(),
+ WorkerName(), rfp);
+ }
+
+ JS::Rooted<JSObject*> global(aCx);
+ NS_ENSURE_TRUE(data->mScope->WrapGlobalObject(aCx, &global), nullptr);
+
+ JSAutoRealm ar(aCx, global);
+
+ if (!RegisterBindings(aCx, global)) {
+ data->mScope = nullptr;
+ return nullptr;
+ }
+
+ JS_FireOnNewGlobalObject(aCx, global);
+
+ return data->mScope;
+}
+
+WorkerDebuggerGlobalScope* WorkerPrivate::CreateDebuggerGlobalScope(
+ JSContext* aCx) {
+ auto data = mWorkerThreadAccessible.Access();
+ MOZ_ASSERT(!data->mDebuggerScope);
+
+ // The debugger global gets a dummy client, not the "real" client used by the
+ // debugee worker.
+ auto clientSource = ClientManager::CreateSource(
+ GetClientType(), HybridEventTarget(), NullPrincipalInfo());
+
+ bool rfp = false; // The debugger for a worker can exempt RFP; it is not
+ // client-exposed
+ data->mDebuggerScope =
+ new WorkerDebuggerGlobalScope(this, std::move(clientSource), rfp);
+
+ JS::Rooted<JSObject*> global(aCx);
+ NS_ENSURE_TRUE(data->mDebuggerScope->WrapGlobalObject(aCx, &global), nullptr);
+
+ JSAutoRealm ar(aCx, global);
+
+ if (!RegisterDebuggerBindings(aCx, global)) {
+ data->mDebuggerScope = nullptr;
+ return nullptr;
+ }
+
+ JS_FireOnNewGlobalObject(aCx, global);
+
+ return data->mDebuggerScope;
+}
+
+bool WorkerPrivate::IsOnWorkerThread() const {
+ // We can't use mThread because it must be protected by mMutex and sometimes
+ // this method is called when mMutex is already locked. This method should
+ // always work.
+ MOZ_ASSERT(mPRThread,
+ "AssertIsOnWorkerThread() called before a thread was assigned!");
+
+ return mPRThread == PR_GetCurrentThread();
+}
+
+#ifdef DEBUG
+void WorkerPrivate::AssertIsOnWorkerThread() const {
+ MOZ_ASSERT(IsOnWorkerThread());
+}
+#endif // DEBUG
+
+void WorkerPrivate::DumpCrashInformation(nsACString& aString) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ aString.Append("IsChromeWorker(");
+ if (IsChromeWorker()) {
+ aString.Append(NS_ConvertUTF16toUTF8(ScriptURL()));
+ } else {
+ aString.Append("false");
+ }
+ aString.Append(")");
+ for (const auto* workerRef : data->mWorkerRefs.NonObservingRange()) {
+ if (workerRef->IsPreventingShutdown()) {
+ aString.Append("|");
+ aString.Append(workerRef->Name());
+ }
+ }
+}
+
+PerformanceStorage* WorkerPrivate::GetPerformanceStorage() {
+ MOZ_ASSERT(mPerformanceStorage);
+ return mPerformanceStorage;
+}
+
+void WorkerPrivate::SetRemoteWorkerController(RemoteWorkerChild* aController) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aController);
+ MOZ_ASSERT(!mRemoteWorkerController);
+
+ mRemoteWorkerController = aController;
+}
+
+RemoteWorkerChild* WorkerPrivate::GetRemoteWorkerController() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mRemoteWorkerController);
+ return mRemoteWorkerController;
+}
+
+RefPtr<GenericPromise> WorkerPrivate::SetServiceWorkerSkipWaitingFlag() {
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(IsServiceWorker());
+
+ RefPtr<RemoteWorkerChild> rwc = mRemoteWorkerController;
+
+ if (!rwc) {
+ return GenericPromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ }
+
+ RefPtr<GenericPromise> promise =
+ rwc->MaybeSendSetServiceWorkerSkipWaitingFlag();
+
+ return promise;
+}
+
+const nsAString& WorkerPrivate::Id() {
+ AssertIsOnMainThread();
+
+ if (mId.IsEmpty()) {
+ mId = ComputeWorkerPrivateId();
+ }
+
+ MOZ_ASSERT(!mId.IsEmpty());
+
+ return mId;
+}
+
+bool WorkerPrivate::IsSharedMemoryAllowed() const {
+ if (StaticPrefs::
+ dom_postMessage_sharedArrayBuffer_bypassCOOP_COEP_insecure_enabled()) {
+ return true;
+ }
+
+ if (mIsPrivilegedAddonGlobal) {
+ return true;
+ }
+
+ return CrossOriginIsolated();
+}
+
+bool WorkerPrivate::CrossOriginIsolated() const {
+ if (!StaticPrefs::
+ dom_postMessage_sharedArrayBuffer_withCOOP_COEP_AtStartup()) {
+ return false;
+ }
+
+ return mAgentClusterOpenerPolicy ==
+ nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
+}
+
+nsILoadInfo::CrossOriginEmbedderPolicy WorkerPrivate::GetEmbedderPolicy()
+ const {
+ if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ return nsILoadInfo::EMBEDDER_POLICY_NULL;
+ }
+
+ return mEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL);
+}
+
+Result<Ok, nsresult> WorkerPrivate::SetEmbedderPolicy(
+ nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mEmbedderPolicy.isNothing());
+
+ if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ return Ok();
+ }
+
+ // https://html.spec.whatwg.org/multipage/browsers.html#check-a-global-object's-embedder-policy
+ // If ownerPolicy's value is not compatible with cross-origin isolation or
+ // policy's value is compatible with cross-origin isolation, then return true.
+ EnsureOwnerEmbedderPolicy();
+ nsILoadInfo::CrossOriginEmbedderPolicy ownerPolicy =
+ mOwnerEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL);
+ if (nsContentSecurityManager::IsCompatibleWithCrossOriginIsolation(
+ ownerPolicy) &&
+ !nsContentSecurityManager::IsCompatibleWithCrossOriginIsolation(
+ aPolicy)) {
+ return Err(NS_ERROR_BLOCKED_BY_POLICY);
+ }
+
+ mEmbedderPolicy.emplace(aPolicy);
+
+ return Ok();
+}
+
+void WorkerPrivate::InheritOwnerEmbedderPolicyOrNull(nsIRequest* aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aRequest);
+
+ EnsureOwnerEmbedderPolicy();
+
+ if (mOwnerEmbedderPolicy.isSome()) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ MOZ_ASSERT(channel);
+
+ nsCOMPtr<nsIURI> scriptURI;
+ MOZ_ALWAYS_SUCCEEDS(channel->GetURI(getter_AddRefs(scriptURI)));
+
+ bool isLocalScriptURI = false;
+ MOZ_ALWAYS_SUCCEEDS(NS_URIChainHasFlags(
+ scriptURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
+ &isLocalScriptURI));
+
+ MOZ_RELEASE_ASSERT(isLocalScriptURI);
+ }
+
+ mEmbedderPolicy.emplace(
+ mOwnerEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL));
+}
+
+bool WorkerPrivate::MatchEmbedderPolicy(
+ nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ return true;
+ }
+
+ return mEmbedderPolicy.value() == aPolicy;
+}
+
+nsILoadInfo::CrossOriginEmbedderPolicy WorkerPrivate::GetOwnerEmbedderPolicy()
+ const {
+ if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ return nsILoadInfo::EMBEDDER_POLICY_NULL;
+ }
+
+ return mOwnerEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL);
+}
+
+void WorkerPrivate::EnsureOwnerEmbedderPolicy() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerEmbedderPolicy.isNothing());
+
+ if (GetParent()) {
+ mOwnerEmbedderPolicy.emplace(GetParent()->GetEmbedderPolicy());
+ } else if (GetWindow() && GetWindow()->GetWindowContext()) {
+ mOwnerEmbedderPolicy.emplace(
+ GetWindow()->GetWindowContext()->GetEmbedderPolicy());
+ }
+}
+
+nsIPrincipal* WorkerPrivate::GetEffectiveStoragePrincipal() const {
+ AssertIsOnWorkerThread();
+
+ if (mLoadInfo.mUseRegularPrincipal) {
+ return mLoadInfo.mPrincipal;
+ }
+
+ return mLoadInfo.mPartitionedPrincipal;
+}
+
+const mozilla::ipc::PrincipalInfo&
+WorkerPrivate::GetEffectiveStoragePrincipalInfo() const {
+ AssertIsOnWorkerThread();
+
+ if (mLoadInfo.mUseRegularPrincipal) {
+ return *mLoadInfo.mPrincipalInfo;
+ }
+
+ return *mLoadInfo.mPartitionedPrincipalInfo;
+}
+
+NS_IMPL_ADDREF(WorkerPrivate::EventTarget)
+NS_IMPL_RELEASE(WorkerPrivate::EventTarget)
+
+NS_INTERFACE_MAP_BEGIN(WorkerPrivate::EventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+#ifdef DEBUG
+ // kDEBUGWorkerEventTargetIID is special in that it does not AddRef its
+ // result.
+ if (aIID.Equals(kDEBUGWorkerEventTargetIID)) {
+ *aInstancePtr = this;
+ return NS_OK;
+ } else
+#endif
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+WorkerPrivate::EventTarget::DispatchFromScript(nsIRunnable* aRunnable,
+ uint32_t aFlags) {
+ nsCOMPtr<nsIRunnable> event(aRunnable);
+ return Dispatch(event.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+WorkerPrivate::EventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aFlags) {
+ // May be called on any thread!
+ nsCOMPtr<nsIRunnable> event(aRunnable);
+
+ // Workers only support asynchronous dispatch for now.
+ if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<WorkerRunnable> workerRunnable;
+
+ MutexAutoLock lock(mMutex);
+
+ if (mDisabled) {
+ NS_WARNING(
+ "A runnable was posted to a worker that is already shutting "
+ "down!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mWorkerPrivate);
+ MOZ_ASSERT(mNestedEventTarget);
+
+ if (event) {
+ workerRunnable = mWorkerPrivate->MaybeWrapAsWorkerRunnable(event.forget());
+ }
+
+ nsresult rv =
+ mWorkerPrivate->Dispatch(workerRunnable.forget(), mNestedEventTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerPrivate::EventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>,
+ uint32_t)
+
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WorkerPrivate::EventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WorkerPrivate::EventTarget::UnregisterShutdownTask(
+ nsITargetShutdownTask* aTask) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WorkerPrivate::EventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) {
+ // May be called on any thread!
+
+ MOZ_ASSERT(aIsOnCurrentThread);
+
+ MutexAutoLock lock(mMutex);
+
+ if (mShutdown) {
+ NS_WARNING(
+ "A worker's event target was used after the worker has shutdown!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mNestedEventTarget);
+
+ *aIsOnCurrentThread = mNestedEventTarget->IsOnCurrentThread();
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+WorkerPrivate::EventTarget::IsOnCurrentThreadInfallible() {
+ // May be called on any thread!
+
+ MutexAutoLock lock(mMutex);
+
+ if (mShutdown) {
+ NS_WARNING(
+ "A worker's event target was used after the worker has shutdown!");
+ return false;
+ }
+
+ MOZ_ASSERT(mNestedEventTarget);
+
+ return mNestedEventTarget->IsOnCurrentThread();
+}
+
+WorkerPrivate::AutoPushEventLoopGlobal::AutoPushEventLoopGlobal(
+ WorkerPrivate* aWorkerPrivate, JSContext* aCx)
+ : mWorkerPrivate(aWorkerPrivate) {
+ auto data = mWorkerPrivate->mWorkerThreadAccessible.Access();
+ mOldEventLoopGlobal = std::move(data->mCurrentEventLoopGlobal);
+ if (JSObject* global = JS::CurrentGlobalOrNull(aCx)) {
+ data->mCurrentEventLoopGlobal = xpc::NativeGlobal(global);
+ }
+}
+
+WorkerPrivate::AutoPushEventLoopGlobal::~AutoPushEventLoopGlobal() {
+ auto data = mWorkerPrivate->mWorkerThreadAccessible.Access();
+ data->mCurrentEventLoopGlobal = std::move(mOldEventLoopGlobal);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h
new file mode 100644
index 0000000000..84b4f6d89e
--- /dev/null
+++ b/dom/workers/WorkerPrivate.h
@@ -0,0 +1,1625 @@
+/* -*- 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_workerprivate_h__
+#define mozilla_dom_workers_workerprivate_h__
+
+#include <bitset>
+#include "MainThreadUtils.h"
+#include "ScriptLoader.h"
+#include "js/ContextOptions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/OriginTrials.h"
+#include "mozilla/PerformanceCounter.h"
+#include "mozilla/RelativeTimeline.h"
+#include "mozilla/Result.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/ThreadBound.h"
+#include "mozilla/ThreadSafeWeakPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/UseCounter.h"
+#include "mozilla/dom/ClientSource.h"
+#include "mozilla/dom/FlippedOnce.h"
+#include "mozilla/dom/Timeout.h"
+#include "mozilla/dom/quota/CheckedUnsafePtr.h"
+#include "mozilla/dom/Worker.h"
+#include "mozilla/dom/WorkerBinding.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerLoadInfo.h"
+#include "mozilla/dom/WorkerStatus.h"
+#include "mozilla/dom/workerinternals/JSSettings.h"
+#include "mozilla/dom/workerinternals/Queue.h"
+#include "mozilla/dom/JSExecutionManager.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/StaticPrefs_extensions.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIEventTarget.h"
+#include "nsILoadInfo.h"
+#include "nsTObserverArray.h"
+
+class nsIThreadInternal;
+
+namespace JS {
+struct RuntimeStats;
+}
+
+namespace mozilla {
+class ThrottledEventQueue;
+namespace dom {
+
+class RemoteWorkerChild;
+
+// If you change this, the corresponding list in nsIWorkerDebugger.idl needs
+// to be updated too. And histograms enum for worker use counters uses the same
+// order of worker kind. Please also update dom/base/usecounters.py.
+enum WorkerKind { WorkerKindDedicated, WorkerKindShared, WorkerKindService };
+
+class ClientInfo;
+class ClientSource;
+class Function;
+class JSExecutionManager;
+class MessagePort;
+class UniqueMessagePortId;
+class PerformanceStorage;
+class TimeoutHandler;
+class WorkerControlRunnable;
+class WorkerCSPEventListener;
+class WorkerDebugger;
+class WorkerDebuggerGlobalScope;
+class WorkerErrorReport;
+class WorkerEventTarget;
+class WorkerGlobalScope;
+class WorkerRef;
+class WorkerRunnable;
+class WorkerDebuggeeRunnable;
+class WorkerThread;
+
+// SharedMutex is a small wrapper around an (internal) reference-counted Mutex
+// object. It exists to avoid changing a lot of code to use Mutex* instead of
+// Mutex&.
+class MOZ_CAPABILITY("mutex") SharedMutex {
+ using Mutex = mozilla::Mutex;
+
+ class MOZ_CAPABILITY("mutex") RefCountedMutex final : public Mutex {
+ public:
+ explicit RefCountedMutex(const char* aName) : Mutex(aName) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefCountedMutex)
+
+ private:
+ ~RefCountedMutex() = default;
+ };
+
+ const RefPtr<RefCountedMutex> mMutex;
+
+ public:
+ explicit SharedMutex(const char* aName)
+ : mMutex(new RefCountedMutex(aName)) {}
+
+ SharedMutex(const SharedMutex& aOther) = default;
+
+ operator Mutex&() MOZ_RETURN_CAPABILITY(this) { return *mMutex; }
+
+ operator const Mutex&() const MOZ_RETURN_CAPABILITY(this) { return *mMutex; }
+
+ // We need these to make thread-safety analysis work
+ void Lock() MOZ_CAPABILITY_ACQUIRE() { mMutex->Lock(); }
+ void Unlock() MOZ_CAPABILITY_RELEASE() { mMutex->Unlock(); }
+
+ // We can assert we own 'this', but we can't assert we hold mMutex
+ void AssertCurrentThreadOwns() const
+ MOZ_ASSERT_CAPABILITY(this) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ mMutex->AssertCurrentThreadOwns();
+ }
+};
+
+nsString ComputeWorkerPrivateId();
+
+class WorkerPrivate final
+ : public RelativeTimeline,
+ public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
+ public:
+ // Callback invoked on the parent thread when the worker's cancellation is
+ // about to be requested. This covers both calls to
+ // WorkerPrivate::Cancel() by the owner as well as self-initiated cancellation
+ // due to top-level script evaluation failing or close() being invoked on the
+ // global scope for Dedicated and Shared workers, but not Service Workers as
+ // they do not expose a close() method.
+ //
+ // ### Parent-Initiated Cancellation
+ //
+ // When WorkerPrivate::Cancel is invoked on the parent thread (by the binding
+ // exposed Worker::Terminate), this callback is invoked synchronously inside
+ // that call.
+ //
+ // ### Worker Self-Cancellation
+ //
+ // When a worker initiates self-cancellation, the worker's notification to the
+ // parent thread is a non-blocking, async mechanism triggered by
+ // `WorkerPrivate::DispatchCancelingRunnable`.
+ //
+ // Self-cancellation races a normally scheduled runnable against a timer that
+ // is scheduled against the parent. The 2 paths initiated by
+ // DispatchCancelingRunnable are:
+ //
+ // 1. A CancelingRunnable is dispatched at the worker's normal event target to
+ // wait for the event loop to be clear of runnables. When the
+ // CancelingRunnable runs it will dispatch a CancelingOnParentRunnable to
+ // its parent which is a normal, non-control WorkerDebuggeeRunnable to
+ // ensure that any postMessages to the parent or similar events get a
+ // chance to be processed prior to cancellation. The timer scheduled in
+ // the next bullet will not be canceled unless
+ //
+ // 2. A CancelingWithTimeoutOnParentRunnable control runnable is dispatched
+ // to the parent to schedule a timer which will (also) fire on the parent
+ // thread. This handles the case where the worker does not yield
+ // control-flow, and so the normal runnable scheduled above does not get to
+ // run in a timely fashion. Because this is a control runnable, if the
+ // parent is a worker then the runnable will be processed with urgency.
+ // However, if the worker is top-level, then the control-like throttled
+ // WorkerPrivate::mMainThreadEventTarget will end up getting used which is
+ // nsIRunnablePriority::PRIORITY_MEDIUMHIGH and distinct from the
+ // mMainThreadDebuggeeEventTarget which most runnables (like postMessage)
+ // use.
+ //
+ // The timer will explicitly use the control event target if the parent is
+ // a worker and the implicit event target (via `NS_NewTimer()`) otherwise.
+ // The callback is CancelingTimerCallback which just calls
+ // WorkerPrivate::Cancel.
+ using CancellationCallback = std::function<void(bool aEverRan)>;
+
+ // Callback invoked on the parent just prior to dropping the worker thread's
+ // strong reference that keeps the WorkerPrivate alive while the worker thread
+ // is running. This does not provide a guarantee that the underlying thread
+ // has fully shutdown, just that the worker logic has fully shutdown.
+ //
+ // ### Details
+ //
+ // The last thing the worker thread's WorkerThreadPrimaryRunnable does before
+ // initiating the shutdown of the underlying thread is call ScheduleDeletion.
+ // ScheduleDeletion dispatches a runnable to the parent to notify it that the
+ // worker has completed its work and will never touch the WorkerPrivate again
+ // and that the strong self-reference can be dropped.
+ //
+ // For parents that are themselves workers, this will be done by
+ // WorkerFinishedRunnable which is a WorkerControlRunnable, ensuring that this
+ // is processed in a timely fashion. For main-thread parents,
+ // TopLevelWorkerFinishedRunnable will be used and sent via
+ // mMainThreadEventTargetForMessaging which is a weird ThrottledEventQueue
+ // which does not provide any ordering guarantees relative to
+ // mMainThreadDebuggeeEventTarget, so if you want those, you need to enhance
+ // things.
+ using TerminationCallback = std::function<void(void)>;
+
+ struct LocationInfo {
+ nsCString mHref;
+ nsCString mProtocol;
+ nsCString mHost;
+ nsCString mHostname;
+ nsCString mPort;
+ nsCString mPathname;
+ nsCString mSearch;
+ nsCString mHash;
+ nsString mOrigin;
+ };
+
+ NS_INLINE_DECL_REFCOUNTING(WorkerPrivate)
+
+ static already_AddRefed<WorkerPrivate> Constructor(
+ JSContext* aCx, const nsAString& aScriptURL, bool aIsChromeWorker,
+ WorkerKind aWorkerKind, RequestCredentials aRequestCredentials,
+ const WorkerType aWorkerType, const nsAString& aWorkerName,
+ const nsACString& aServiceWorkerScope, WorkerLoadInfo* aLoadInfo,
+ ErrorResult& aRv, nsString aId = u""_ns,
+ CancellationCallback&& aCancellationCallback = {},
+ TerminationCallback&& aTerminationCallback = {});
+
+ enum LoadGroupBehavior { InheritLoadGroup, OverrideLoadGroup };
+
+ static nsresult GetLoadInfo(
+ JSContext* aCx, nsPIDOMWindowInner* aWindow, WorkerPrivate* aParent,
+ const nsAString& aScriptURL, const enum WorkerType& aWorkerType,
+ const RequestCredentials& aCredentials, bool aIsChromeWorker,
+ LoadGroupBehavior aLoadGroupBehavior, WorkerKind aWorkerKind,
+ WorkerLoadInfo* aLoadInfo);
+
+ void Traverse(nsCycleCollectionTraversalCallback& aCb);
+
+ void ClearSelfAndParentEventTargetRef() {
+ AssertIsOnParentThread();
+ MOZ_ASSERT(mSelfRef);
+
+ if (mTerminationCallback) {
+ mTerminationCallback();
+ mTerminationCallback = nullptr;
+ }
+
+ mParentEventTargetRef = nullptr;
+ mSelfRef = nullptr;
+ }
+
+ // May be called on any thread...
+ bool Start();
+
+ // Called on the parent thread.
+ bool Notify(WorkerStatus aStatus);
+
+ bool Cancel() { return Notify(Canceling); }
+
+ bool Close() MOZ_REQUIRES(mMutex);
+
+ // The passed principal must be the Worker principal in case of a
+ // ServiceWorker and the loading principal for any other type.
+ static void OverrideLoadInfoLoadGroup(WorkerLoadInfo& aLoadInfo,
+ nsIPrincipal* aPrincipal);
+
+ bool IsDebuggerRegistered() MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ AssertIsOnMainThread();
+
+ // No need to lock here since this is only ever modified by the same thread.
+ return mDebuggerRegistered; // would give a thread-safety warning
+ }
+
+ bool ExtensionAPIAllowed() {
+ return (
+ StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup() &&
+ mExtensionAPIAllowed);
+ }
+
+ void SetIsDebuggerRegistered(bool aDebuggerRegistered) {
+ AssertIsOnMainThread();
+
+ MutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(mDebuggerRegistered != aDebuggerRegistered);
+ mDebuggerRegistered = aDebuggerRegistered;
+
+ mCondVar.Notify();
+ }
+
+ void WaitForIsDebuggerRegistered(bool aDebuggerRegistered) {
+ AssertIsOnParentThread();
+
+ // Yield so that the main thread won't be blocked.
+ AutoYieldJSThreadExecution yield;
+
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ MutexAutoLock lock(mMutex);
+
+ while (mDebuggerRegistered != aDebuggerRegistered) {
+ mCondVar.Wait();
+ }
+ }
+
+ nsresult SetIsDebuggerReady(bool aReady);
+
+ WorkerDebugger* Debugger() const {
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(mDebugger);
+ return mDebugger;
+ }
+
+ const OriginTrials& Trials() const { return mLoadInfo.mTrials; }
+
+ void SetDebugger(WorkerDebugger* aDebugger) {
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(mDebugger != aDebugger);
+ mDebugger = aDebugger;
+ }
+
+ JS::UniqueChars AdoptDefaultLocale() {
+ MOZ_ASSERT(mDefaultLocale,
+ "the default locale must have been successfully set for anyone "
+ "to be trying to adopt it");
+ return std::move(mDefaultLocale);
+ }
+
+ /**
+ * Invoked by WorkerThreadPrimaryRunnable::Run if it already called
+ * SetWorkerPrivateInWorkerThread but has to bail out on initialization before
+ * calling DoRunLoop because PBackground failed to initialize or something
+ * like that. Note that there's currently no point earlier than this that
+ * failure can be reported.
+ *
+ * When this happens, the worker will need to be deleted, plus the call to
+ * SetWorkerPrivateInWorkerThread will have scheduled all the
+ * mPreStartRunnables which need to be cleaned up after, as well as any
+ * scheduled control runnables. We're somewhat punting on debugger runnables
+ * for now, which may leak, but the intent is to moot this whole scenario via
+ * shutdown blockers, so we don't want the extra complexity right now.
+ */
+ void RunLoopNeverRan();
+
+ MOZ_CAN_RUN_SCRIPT
+ void DoRunLoop(JSContext* aCx);
+
+ void UnrootGlobalScopes();
+
+ bool InterruptCallback(JSContext* aCx);
+
+ bool IsOnCurrentThread();
+
+ void CloseInternal();
+
+ bool FreezeInternal();
+
+ bool ThawInternal();
+
+ void PropagateStorageAccessPermissionGrantedInternal();
+
+ void TraverseTimeouts(nsCycleCollectionTraversalCallback& aCallback);
+
+ void UnlinkTimeouts();
+
+ bool ModifyBusyCountFromWorker(bool aIncrease);
+
+ bool AddChildWorker(WorkerPrivate& aChildWorker);
+
+ void RemoveChildWorker(WorkerPrivate& aChildWorker);
+
+ void PostMessageToParent(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Sequence<JSObject*>& aTransferable,
+ ErrorResult& aRv);
+
+ void PostMessageToParentMessagePort(JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const Sequence<JSObject*>& aTransferable,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void EnterDebuggerEventLoop();
+
+ void LeaveDebuggerEventLoop();
+
+ void PostMessageToDebugger(const nsAString& aMessage);
+
+ void SetDebuggerImmediate(Function& aHandler, ErrorResult& aRv);
+
+ void ReportErrorToDebugger(const nsAString& aFilename, uint32_t aLineno,
+ const nsAString& aMessage);
+
+ bool NotifyInternal(WorkerStatus aStatus);
+
+ void ReportError(JSContext* aCx, JS::ConstUTF8CharsZ aToStringResult,
+ JSErrorReport* aReport);
+
+ static void ReportErrorToConsole(const char* aMessage);
+
+ static void ReportErrorToConsole(const char* aMessage,
+ const nsTArray<nsString>& aParams);
+
+ int32_t SetTimeout(JSContext* aCx, TimeoutHandler* aHandler, int32_t aTimeout,
+ bool aIsInterval, Timeout::Reason aReason,
+ ErrorResult& aRv);
+
+ void ClearTimeout(int32_t aId, Timeout::Reason aReason);
+
+ MOZ_CAN_RUN_SCRIPT bool RunExpiredTimeouts(JSContext* aCx);
+
+ bool RescheduleTimeoutTimer(JSContext* aCx);
+
+ void UpdateContextOptionsInternal(JSContext* aCx,
+ const JS::ContextOptions& aContextOptions);
+
+ void UpdateLanguagesInternal(const nsTArray<nsString>& aLanguages);
+
+ void UpdateJSWorkerMemoryParameterInternal(JSContext* aCx, JSGCParamKey key,
+ Maybe<uint32_t> aValue);
+
+ enum WorkerRanOrNot { WorkerNeverRan = 0, WorkerRan };
+
+ void ScheduleDeletion(WorkerRanOrNot aRanOrNot);
+
+ bool CollectRuntimeStats(JS::RuntimeStats* aRtStats, bool aAnonymize);
+
+#ifdef JS_GC_ZEAL
+ void UpdateGCZealInternal(JSContext* aCx, uint8_t aGCZeal,
+ uint32_t aFrequency);
+#endif
+
+ void SetLowMemoryStateInternal(JSContext* aCx, bool aState);
+
+ void GarbageCollectInternal(JSContext* aCx, bool aShrinking,
+ bool aCollectChildren);
+
+ void CycleCollectInternal(bool aCollectChildren);
+
+ void OfflineStatusChangeEventInternal(bool aIsOffline);
+
+ void MemoryPressureInternal();
+
+ typedef MozPromise<uint64_t, nsresult, true> JSMemoryUsagePromise;
+ RefPtr<JSMemoryUsagePromise> GetJSMemoryUsage();
+
+ void SetFetchHandlerWasAdded() {
+ MOZ_ASSERT(IsServiceWorker());
+ AssertIsOnWorkerThread();
+ mFetchHandlerWasAdded = true;
+ }
+
+ bool FetchHandlerWasAdded() const {
+ MOZ_ASSERT(IsServiceWorker());
+ AssertIsOnWorkerThread();
+ return mFetchHandlerWasAdded;
+ }
+
+ JSContext* GetJSContext() const MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ // mJSContext is only modified on the worker thread, so workerthread code
+ // can safely read it without a lock
+ AssertIsOnWorkerThread();
+ return mJSContext;
+ }
+
+ WorkerGlobalScope* GlobalScope() const {
+ auto data = mWorkerThreadAccessible.Access();
+ return data->mScope;
+ }
+
+ WorkerDebuggerGlobalScope* DebuggerGlobalScope() const {
+ auto data = mWorkerThreadAccessible.Access();
+ return data->mDebuggerScope;
+ }
+
+ // Get the global associated with the current nested event loop. Will return
+ // null if we're not in a nested event loop or that nested event loop does not
+ // have an associated global.
+ nsIGlobalObject* GetCurrentEventLoopGlobal() const {
+ auto data = mWorkerThreadAccessible.Access();
+ return data->mCurrentEventLoopGlobal;
+ }
+
+ nsICSPEventListener* CSPEventListener() const;
+
+ void SetThread(WorkerThread* aThread);
+
+ void SetWorkerPrivateInWorkerThread(WorkerThread* aThread);
+
+ void ResetWorkerPrivateInWorkerThread();
+
+ bool IsOnWorkerThread() const;
+
+ void AssertIsOnWorkerThread() const
+#ifdef DEBUG
+ ;
+#else
+ {
+ }
+#endif
+
+ // This may block!
+ void BeginCTypesCall();
+
+ // This may block!
+ void EndCTypesCall();
+
+ void BeginCTypesCallback();
+
+ void EndCTypesCallback();
+
+ bool ConnectMessagePort(JSContext* aCx, UniqueMessagePortId& aIdentifier);
+
+ WorkerGlobalScope* GetOrCreateGlobalScope(JSContext* aCx);
+
+ WorkerDebuggerGlobalScope* CreateDebuggerGlobalScope(JSContext* aCx);
+
+ bool RegisterBindings(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
+
+ bool RegisterDebuggerBindings(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
+
+ bool OnLine() const {
+ auto data = mWorkerThreadAccessible.Access();
+ return data->mOnLine;
+ }
+
+ void StopSyncLoop(nsIEventTarget* aSyncLoopTarget, nsresult aResult);
+
+ bool AllPendingRunnablesShouldBeCanceled() const {
+ return mCancelAllPendingRunnables;
+ }
+
+ void ShutdownModuleLoader();
+
+ void ClearMainEventQueue(WorkerRanOrNot aRanOrNot);
+
+ void ClearDebuggerEventQueue();
+
+ void OnProcessNextEvent();
+
+ void AfterProcessNextEvent();
+
+ void AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget)
+#ifdef DEBUG
+ ;
+#else
+ {
+ }
+#endif
+
+ void SetWorkerScriptExecutedSuccessfully() {
+ AssertIsOnWorkerThread();
+ // Should only be called once!
+ MOZ_ASSERT(!mWorkerScriptExecutedSuccessfully);
+ mWorkerScriptExecutedSuccessfully = true;
+ }
+
+ // Only valid after CompileScriptRunnable has finished running!
+ bool WorkerScriptExecutedSuccessfully() const {
+ AssertIsOnWorkerThread();
+ return mWorkerScriptExecutedSuccessfully;
+ }
+
+ // Get the event target to use when dispatching to the main thread
+ // from this Worker thread. This may be the main thread itself or
+ // a ThrottledEventQueue to the main thread.
+ nsISerialEventTarget* MainThreadEventTargetForMessaging();
+
+ nsresult DispatchToMainThreadForMessaging(
+ nsIRunnable* aRunnable, uint32_t aFlags = NS_DISPATCH_NORMAL);
+
+ nsresult DispatchToMainThreadForMessaging(
+ already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aFlags = NS_DISPATCH_NORMAL);
+
+ nsISerialEventTarget* MainThreadEventTarget();
+
+ nsresult DispatchToMainThread(nsIRunnable* aRunnable,
+ uint32_t aFlags = NS_DISPATCH_NORMAL);
+
+ nsresult DispatchToMainThread(already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aFlags = NS_DISPATCH_NORMAL);
+
+ nsresult DispatchDebuggeeToMainThread(
+ already_AddRefed<WorkerDebuggeeRunnable> aRunnable,
+ uint32_t aFlags = NS_DISPATCH_NORMAL);
+
+ // Get an event target that will dispatch runnables as control runnables on
+ // the worker thread. Implement nsICancelableRunnable if you wish to take
+ // action on cancelation.
+ nsISerialEventTarget* ControlEventTarget();
+
+ // Get an event target that will attempt to dispatch a normal WorkerRunnable,
+ // but if that fails will then fall back to a control runnable.
+ nsISerialEventTarget* HybridEventTarget();
+
+ void DumpCrashInformation(nsACString& aString);
+
+ ClientType GetClientType() const;
+
+ bool EnsureCSPEventListener();
+
+ void EnsurePerformanceStorage();
+
+ bool GetExecutionGranted() const;
+ void SetExecutionGranted(bool aGranted);
+
+ void ScheduleTimeSliceExpiration(uint32_t aDelay);
+ void CancelTimeSliceExpiration();
+
+ JSExecutionManager* GetExecutionManager() const;
+ void SetExecutionManager(JSExecutionManager* aManager);
+
+ void ExecutionReady();
+
+ PerformanceStorage* GetPerformanceStorage();
+
+ PerformanceCounter& MutablePerformanceCounterRef() const {
+ return *mPerformanceCounter;
+ }
+
+ const PerformanceCounter& PerformanceCounterRef() const {
+ return MutablePerformanceCounterRef();
+ }
+
+ bool IsAcceptingEvents() {
+ AssertIsOnParentThread();
+
+ MutexAutoLock lock(mMutex);
+ return mParentStatus < Canceling;
+ }
+
+ WorkerStatus ParentStatusProtected() {
+ AssertIsOnParentThread();
+ MutexAutoLock lock(mMutex);
+ return mParentStatus;
+ }
+
+ WorkerStatus ParentStatus() const MOZ_REQUIRES(mMutex) {
+ mMutex.AssertCurrentThreadOwns();
+ return mParentStatus;
+ }
+
+ Worker* ParentEventTargetRef() const {
+ MOZ_DIAGNOSTIC_ASSERT(mParentEventTargetRef);
+ return mParentEventTargetRef;
+ }
+
+ void SetParentEventTargetRef(Worker* aParentEventTargetRef) {
+ MOZ_DIAGNOSTIC_ASSERT(aParentEventTargetRef);
+ MOZ_DIAGNOSTIC_ASSERT(!mParentEventTargetRef);
+ mParentEventTargetRef = aParentEventTargetRef;
+ }
+
+ bool ModifyBusyCount(bool aIncrease);
+
+ // This method is used by RuntimeService to know what is going wrong the
+ // shutting down.
+ uint32_t BusyCount() { return mBusyCount; }
+
+ // Check whether this worker is a secure context. For use from the parent
+ // thread only; the canonical "is secure context" boolean is stored on the
+ // compartment of the worker global. The only reason we don't
+ // AssertIsOnParentThread() here is so we can assert that this value matches
+ // the one on the compartment, which has to be done from the worker thread.
+ bool IsSecureContext() const { return mIsSecureContext; }
+
+ // Check whether we're running in automation.
+ bool IsInAutomation() const { return mIsInAutomation; }
+
+ bool IsPrivilegedAddonGlobal() const { return mIsPrivilegedAddonGlobal; }
+
+ TimeStamp CreationTimeStamp() const { return mCreationTimeStamp; }
+
+ DOMHighResTimeStamp CreationTime() const { return mCreationTimeHighRes; }
+
+ DOMHighResTimeStamp TimeStampToDOMHighRes(const TimeStamp& aTimeStamp) const {
+ MOZ_ASSERT(!aTimeStamp.IsNull());
+ TimeDuration duration = aTimeStamp - mCreationTimeStamp;
+ return duration.ToMilliseconds();
+ }
+
+ LocationInfo& GetLocationInfo() { return mLocationInfo; }
+
+ void CopyJSSettings(workerinternals::JSSettings& aSettings) {
+ mozilla::MutexAutoLock lock(mMutex);
+ aSettings = mJSSettings;
+ }
+
+ void CopyJSRealmOptions(JS::RealmOptions& aOptions) {
+ mozilla::MutexAutoLock lock(mMutex);
+ aOptions = IsChromeWorker() ? mJSSettings.chromeRealmOptions
+ : mJSSettings.contentRealmOptions;
+ }
+
+ // The ability to be a chrome worker is orthogonal to the type of
+ // worker [Dedicated|Shared|Service].
+ bool IsChromeWorker() const { return mIsChromeWorker; }
+
+ // TODO: Invariants require that the parent worker out-live any child
+ // worker, so WorkerPrivate* should be safe in the moment of calling.
+ // We would like to have stronger type-system annotated/enforced handling.
+ WorkerPrivate* GetParent() const { return mParent; }
+
+ bool IsFrozen() const {
+ AssertIsOnParentThread();
+ return mParentFrozen;
+ }
+
+ bool IsParentWindowPaused() const {
+ AssertIsOnParentThread();
+ return mParentWindowPaused;
+ }
+
+ // When we debug a worker, we want to disconnect the window and the worker
+ // communication. This happens calling this method.
+ // Note: this method doesn't suspend the worker! Use Freeze/Thaw instead.
+ void ParentWindowPaused();
+
+ void ParentWindowResumed();
+
+ const nsString& ScriptURL() const { return mScriptURL; }
+
+ const nsString& WorkerName() const { return mWorkerName; }
+ RequestCredentials WorkerCredentials() const { return mCredentialsMode; }
+ enum WorkerType WorkerType() const { return mWorkerType; }
+
+ WorkerKind Kind() const { return mWorkerKind; }
+
+ bool IsDedicatedWorker() const { return mWorkerKind == WorkerKindDedicated; }
+
+ bool IsSharedWorker() const { return mWorkerKind == WorkerKindShared; }
+
+ bool IsServiceWorker() const { return mWorkerKind == WorkerKindService; }
+
+ nsContentPolicyType ContentPolicyType() const {
+ return ContentPolicyType(mWorkerKind);
+ }
+
+ static nsContentPolicyType ContentPolicyType(WorkerKind aWorkerKind) {
+ switch (aWorkerKind) {
+ case WorkerKindDedicated:
+ return nsIContentPolicy::TYPE_INTERNAL_WORKER;
+ case WorkerKindShared:
+ return nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER;
+ case WorkerKindService:
+ return nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid worker type");
+ return nsIContentPolicy::TYPE_INVALID;
+ }
+ }
+
+ nsIScriptContext* GetScriptContext() const {
+ AssertIsOnMainThread();
+ return mLoadInfo.mScriptContext;
+ }
+
+ const nsCString& Domain() const { return mLoadInfo.mDomain; }
+
+ bool IsFromWindow() const { return mLoadInfo.mFromWindow; }
+
+ nsLoadFlags GetLoadFlags() const { return mLoadInfo.mLoadFlags; }
+
+ uint64_t WindowID() const { return mLoadInfo.mWindowID; }
+
+ uint64_t AssociatedBrowsingContextID() const {
+ return mLoadInfo.mAssociatedBrowsingContextID;
+ }
+
+ uint64_t ServiceWorkerID() const { return GetServiceWorkerDescriptor().Id(); }
+
+ const nsCString& ServiceWorkerScope() const {
+ return GetServiceWorkerDescriptor().Scope();
+ }
+
+ // This value should never change after the script load completes. Before
+ // then, it may only be called on the main thread.
+ nsIURI* GetBaseURI() const { return mLoadInfo.mBaseURI; }
+
+ void SetBaseURI(nsIURI* aBaseURI);
+
+ nsIURI* GetResolvedScriptURI() const { return mLoadInfo.mResolvedScriptURI; }
+
+ const nsString& ServiceWorkerCacheName() const {
+ MOZ_DIAGNOSTIC_ASSERT(IsServiceWorker());
+ AssertIsOnMainThread();
+ return mLoadInfo.mServiceWorkerCacheName;
+ }
+
+ const ServiceWorkerDescriptor& GetServiceWorkerDescriptor() const {
+ MOZ_DIAGNOSTIC_ASSERT(IsServiceWorker());
+ MOZ_DIAGNOSTIC_ASSERT(mLoadInfo.mServiceWorkerDescriptor.isSome());
+ return mLoadInfo.mServiceWorkerDescriptor.ref();
+ }
+
+ const ServiceWorkerRegistrationDescriptor&
+ GetServiceWorkerRegistrationDescriptor() const {
+ MOZ_DIAGNOSTIC_ASSERT(IsServiceWorker());
+ MOZ_DIAGNOSTIC_ASSERT(
+ mLoadInfo.mServiceWorkerRegistrationDescriptor.isSome());
+ return mLoadInfo.mServiceWorkerRegistrationDescriptor.ref();
+ }
+
+ void UpdateServiceWorkerState(ServiceWorkerState aState) {
+ MOZ_DIAGNOSTIC_ASSERT(IsServiceWorker());
+ MOZ_DIAGNOSTIC_ASSERT(mLoadInfo.mServiceWorkerDescriptor.isSome());
+ return mLoadInfo.mServiceWorkerDescriptor.ref().SetState(aState);
+ }
+
+ const Maybe<ServiceWorkerDescriptor>& GetParentController() const {
+ return mLoadInfo.mParentController;
+ }
+
+ const ChannelInfo& GetChannelInfo() const { return mLoadInfo.mChannelInfo; }
+
+ void SetChannelInfo(const ChannelInfo& aChannelInfo) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mLoadInfo.mChannelInfo.IsInitialized());
+ MOZ_ASSERT(aChannelInfo.IsInitialized());
+ mLoadInfo.mChannelInfo = aChannelInfo;
+ }
+
+ void InitChannelInfo(nsIChannel* aChannel) {
+ mLoadInfo.mChannelInfo.InitFromChannel(aChannel);
+ }
+
+ void InitChannelInfo(const ChannelInfo& aChannelInfo) {
+ mLoadInfo.mChannelInfo = aChannelInfo;
+ }
+
+ nsIPrincipal* GetPrincipal() const { return mLoadInfo.mPrincipal; }
+
+ nsIPrincipal* GetLoadingPrincipal() const {
+ return mLoadInfo.mLoadingPrincipal;
+ }
+
+ nsIPrincipal* GetPartitionedPrincipal() const {
+ return mLoadInfo.mPartitionedPrincipal;
+ }
+
+ nsIPrincipal* GetEffectiveStoragePrincipal() const;
+
+ nsILoadGroup* GetLoadGroup() const {
+ AssertIsOnMainThread();
+ return mLoadInfo.mLoadGroup;
+ }
+
+ bool UsesSystemPrincipal() const {
+ return GetPrincipal()->IsSystemPrincipal();
+ }
+ bool UsesAddonOrExpandedAddonPrincipal() const {
+ return GetPrincipal()->GetIsAddonOrExpandedAddonPrincipal();
+ }
+
+ const mozilla::ipc::PrincipalInfo& GetPrincipalInfo() const {
+ return *mLoadInfo.mPrincipalInfo;
+ }
+
+ const mozilla::ipc::PrincipalInfo& GetPartitionedPrincipalInfo() const {
+ return *mLoadInfo.mPartitionedPrincipalInfo;
+ }
+
+ const mozilla::ipc::PrincipalInfo& GetEffectiveStoragePrincipalInfo() const;
+
+ already_AddRefed<nsIChannel> ForgetWorkerChannel() {
+ AssertIsOnMainThread();
+ return mLoadInfo.mChannel.forget();
+ }
+
+ nsPIDOMWindowInner* GetWindow() const {
+ AssertIsOnMainThread();
+ return mLoadInfo.mWindow;
+ }
+
+ nsPIDOMWindowInner* GetAncestorWindow() const;
+
+ void EvictFromBFCache();
+
+ nsIContentSecurityPolicy* GetCsp() const {
+ AssertIsOnMainThread();
+ return mLoadInfo.mCSP;
+ }
+
+ void SetCsp(nsIContentSecurityPolicy* aCSP);
+
+ nsresult SetCSPFromHeaderValues(const nsACString& aCSPHeaderValue,
+ const nsACString& aCSPReportOnlyHeaderValue);
+
+ void StoreCSPOnClient();
+
+ const mozilla::ipc::CSPInfo& GetCSPInfo() const {
+ return *mLoadInfo.mCSPInfo;
+ }
+
+ void UpdateReferrerInfoFromHeader(
+ const nsACString& aReferrerPolicyHeaderValue);
+
+ nsIReferrerInfo* GetReferrerInfo() const { return mLoadInfo.mReferrerInfo; }
+
+ ReferrerPolicy GetReferrerPolicy() const {
+ return mLoadInfo.mReferrerInfo->ReferrerPolicy();
+ }
+
+ void SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ mLoadInfo.mReferrerInfo = aReferrerInfo;
+ }
+
+ bool IsEvalAllowed() const { return mLoadInfo.mEvalAllowed; }
+
+ void SetEvalAllowed(bool aAllowed) { mLoadInfo.mEvalAllowed = aAllowed; }
+
+ bool GetReportEvalCSPViolations() const {
+ return mLoadInfo.mReportEvalCSPViolations;
+ }
+
+ void SetReportEvalCSPViolations(bool aReport) {
+ mLoadInfo.mReportEvalCSPViolations = aReport;
+ }
+
+ bool IsWasmEvalAllowed() const { return mLoadInfo.mWasmEvalAllowed; }
+
+ void SetWasmEvalAllowed(bool aAllowed) {
+ mLoadInfo.mWasmEvalAllowed = aAllowed;
+ }
+
+ bool GetReportWasmEvalCSPViolations() const {
+ return mLoadInfo.mReportWasmEvalCSPViolations;
+ }
+
+ void SetReportWasmEvalCSPViolations(bool aReport) {
+ mLoadInfo.mReportWasmEvalCSPViolations = aReport;
+ }
+
+ bool XHRParamsAllowed() const { return mLoadInfo.mXHRParamsAllowed; }
+
+ void SetXHRParamsAllowed(bool aAllowed) {
+ mLoadInfo.mXHRParamsAllowed = aAllowed;
+ }
+
+ mozilla::StorageAccess StorageAccess() const {
+ AssertIsOnWorkerThread();
+ if (mLoadInfo.mHasStorageAccessPermissionGranted) {
+ return mozilla::StorageAccess::eAllow;
+ }
+
+ return mLoadInfo.mStorageAccess;
+ }
+
+ bool UseRegularPrincipal() const {
+ AssertIsOnWorkerThread();
+ return mLoadInfo.mUseRegularPrincipal;
+ }
+
+ bool HasStorageAccessPermissionGranted() const {
+ AssertIsOnWorkerThread();
+ return mLoadInfo.mHasStorageAccessPermissionGranted;
+ }
+
+ nsICookieJarSettings* CookieJarSettings() const {
+ // Any thread.
+ MOZ_ASSERT(mLoadInfo.mCookieJarSettings);
+ return mLoadInfo.mCookieJarSettings;
+ }
+
+ const net::CookieJarSettingsArgs& CookieJarSettingsArgs() const {
+ MOZ_ASSERT(mLoadInfo.mCookieJarSettings);
+ return mLoadInfo.mCookieJarSettingsArgs;
+ }
+
+ const OriginAttributes& GetOriginAttributes() const {
+ return mLoadInfo.mOriginAttributes;
+ }
+
+ // Determine if the SW testing per-window flag is set by devtools
+ bool ServiceWorkersTestingInWindow() const {
+ return mLoadInfo.mServiceWorkersTestingInWindow;
+ }
+
+ // Determine if the worker was created under a third-party context.
+ bool IsThirdPartyContextToTopWindow() const {
+ return mLoadInfo.mIsThirdPartyContextToTopWindow;
+ }
+
+ bool IsWatchedByDevTools() const { return mLoadInfo.mWatchedByDevTools; }
+
+ RemoteWorkerChild* GetRemoteWorkerController();
+
+ void SetRemoteWorkerController(RemoteWorkerChild* aController);
+
+ RefPtr<GenericPromise> SetServiceWorkerSkipWaitingFlag();
+
+ // We can assume that an nsPIDOMWindow will be available for Freeze, Thaw
+ // as these are only used for globals going in and out of the bfcache.
+ bool Freeze(const nsPIDOMWindowInner* aWindow);
+
+ bool Thaw(const nsPIDOMWindowInner* aWindow);
+
+ void PropagateStorageAccessPermissionGranted();
+
+ void EnableDebugger();
+
+ void DisableDebugger();
+
+ already_AddRefed<WorkerRunnable> MaybeWrapAsWorkerRunnable(
+ already_AddRefed<nsIRunnable> aRunnable);
+
+ bool ProxyReleaseMainThreadObjects();
+
+ void SetLowMemoryState(bool aState);
+
+ void GarbageCollect(bool aShrinking);
+
+ void CycleCollect();
+
+ nsresult SetPrincipalsAndCSPOnMainThread(nsIPrincipal* aPrincipal,
+ nsIPrincipal* aPartitionedPrincipal,
+ nsILoadGroup* aLoadGroup,
+ nsIContentSecurityPolicy* aCsp);
+
+ nsresult SetPrincipalsAndCSPFromChannel(nsIChannel* aChannel);
+
+ bool FinalChannelPrincipalIsValid(nsIChannel* aChannel);
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ bool PrincipalURIMatchesScriptURL();
+#endif
+
+ void UpdateOverridenLoadGroup(nsILoadGroup* aBaseLoadGroup);
+
+ void WorkerScriptLoaded();
+
+ Document* GetDocument() const;
+
+ void MemoryPressure();
+
+ void UpdateContextOptions(const JS::ContextOptions& aContextOptions);
+
+ void UpdateLanguages(const nsTArray<nsString>& aLanguages);
+
+ void UpdateJSWorkerMemoryParameter(JSGCParamKey key, Maybe<uint32_t> value);
+
+#ifdef JS_GC_ZEAL
+ void UpdateGCZeal(uint8_t aGCZeal, uint32_t aFrequency);
+#endif
+
+ void OfflineStatusChangeEvent(bool aIsOffline);
+
+ nsresult Dispatch(already_AddRefed<WorkerRunnable> aRunnable,
+ nsIEventTarget* aSyncLoopTarget = nullptr);
+
+ nsresult DispatchControlRunnable(
+ already_AddRefed<WorkerControlRunnable> aWorkerControlRunnable);
+
+ nsresult DispatchDebuggerRunnable(
+ already_AddRefed<WorkerRunnable> aDebuggerRunnable);
+
+#ifdef DEBUG
+ void AssertIsOnParentThread() const;
+
+ void AssertInnerWindowIsCorrect() const;
+#else
+ void AssertIsOnParentThread() const {}
+
+ void AssertInnerWindowIsCorrect() const {}
+#endif
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ bool PrincipalIsValid() const;
+#endif
+
+ void StartCancelingTimer();
+
+ const nsAString& Id();
+
+ const nsID& AgentClusterId() const { return mAgentClusterId; }
+
+ bool IsSharedMemoryAllowed() const;
+
+ // https://whatpr.org/html/4734/structured-data.html#cross-origin-isolated
+ bool CrossOriginIsolated() const;
+
+ void SetUseCounter(UseCounterWorker aUseCounter) {
+ MOZ_ASSERT(!mReportedUseCounters);
+ MOZ_ASSERT(aUseCounter > UseCounterWorker::Unknown);
+ AssertIsOnWorkerThread();
+ mUseCounters[static_cast<size_t>(aUseCounter)] = true;
+ }
+
+ /**
+ * COEP Methods
+ *
+ * If browser.tabs.remote.useCrossOriginEmbedderPolicy=false, these methods
+ * will, depending on the return type, return a value that will avoid
+ * assertion failures or a value that won't block loads.
+ */
+ nsILoadInfo::CrossOriginEmbedderPolicy GetEmbedderPolicy() const;
+
+ // Fails if a policy has already been set or if `aPolicy` violates the owner's
+ // policy, if an owner exists.
+ mozilla::Result<Ok, nsresult> SetEmbedderPolicy(
+ nsILoadInfo::CrossOriginEmbedderPolicy aPolicy);
+
+ // `aRequest` is the request loading the worker and must be QI-able to
+ // `nsIChannel*`. It's used to verify that the worker can indeed inherit its
+ // owner's COEP (when an owner exists).
+ //
+ // TODO: remove `aRequest`; currently, it's required because instances may not
+ // always know its final, resolved script URL or have access internally to
+ // `aRequest`.
+ void InheritOwnerEmbedderPolicyOrNull(nsIRequest* aRequest);
+
+ // Requires a policy to already have been set.
+ bool MatchEmbedderPolicy(
+ nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) const;
+
+ nsILoadInfo::CrossOriginEmbedderPolicy GetOwnerEmbedderPolicy() const;
+
+ void SetCCCollectedAnything(bool collectedAnything);
+ bool isLastCCCollectedAnything();
+
+ uint32_t GetCurrentTimerNestingLevel() const {
+ auto data = mWorkerThreadAccessible.Access();
+ return data->mCurrentTimerNestingLevel;
+ }
+
+ void IncreaseTopLevelWorkerFinishedRunnableCount() {
+ ++mTopLevelWorkerFinishedRunnableCount;
+ }
+ void DecreaseTopLevelWorkerFinishedRunnableCount() {
+ --mTopLevelWorkerFinishedRunnableCount;
+ }
+ void IncreaseWorkerFinishedRunnableCount() { ++mWorkerFinishedRunnableCount; }
+ void DecreaseWorkerFinishedRunnableCount() { --mWorkerFinishedRunnableCount; }
+
+ void RunShutdownTasks();
+
+ private:
+ WorkerPrivate(
+ WorkerPrivate* aParent, const nsAString& aScriptURL, bool aIsChromeWorker,
+ WorkerKind aWorkerKind, RequestCredentials aRequestCredentials,
+ enum WorkerType aWorkerType, const nsAString& aWorkerName,
+ const nsACString& aServiceWorkerScope, WorkerLoadInfo& aLoadInfo,
+ nsString&& aId, const nsID& aAgentClusterId,
+ const nsILoadInfo::CrossOriginOpenerPolicy aAgentClusterOpenerPolicy,
+ CancellationCallback&& aCancellationCallback,
+ TerminationCallback&& aTerminationCallback);
+
+ ~WorkerPrivate();
+
+ struct AgentClusterIdAndCoop {
+ nsID mId;
+ nsILoadInfo::CrossOriginOpenerPolicy mCoop;
+ };
+
+ static AgentClusterIdAndCoop ComputeAgentClusterIdAndCoop(
+ WorkerPrivate* aParent, WorkerKind aWorkerKind,
+ WorkerLoadInfo* aLoadInfo);
+
+ bool MayContinueRunning() {
+ AssertIsOnWorkerThread();
+
+ WorkerStatus status;
+ {
+ MutexAutoLock lock(mMutex);
+ status = mStatus;
+ }
+
+ if (status < Canceling) {
+ return true;
+ }
+
+ return false;
+ }
+
+ void CancelAllTimeouts();
+
+ enum class ProcessAllControlRunnablesResult {
+ // We did not process anything.
+ Nothing,
+ // We did process something, states may have changed, but we can keep
+ // executing script.
+ MayContinue,
+ // We did process something, and should not continue executing script.
+ Abort
+ };
+
+ ProcessAllControlRunnablesResult ProcessAllControlRunnables() {
+ MutexAutoLock lock(mMutex);
+ return ProcessAllControlRunnablesLocked();
+ }
+
+ ProcessAllControlRunnablesResult ProcessAllControlRunnablesLocked()
+ MOZ_REQUIRES(mMutex);
+
+ void EnableMemoryReporter();
+
+ void DisableMemoryReporter();
+
+ void WaitForWorkerEvents() MOZ_REQUIRES(mMutex);
+
+ // If the worker shutdown status is equal or greater then aFailStatus, this
+ // operation will fail and nullptr will be returned. See WorkerStatus.h for
+ // more information about the correct value to use.
+ already_AddRefed<nsISerialEventTarget> CreateNewSyncLoop(
+ WorkerStatus aFailStatus);
+
+ nsresult RunCurrentSyncLoop();
+
+ nsresult DestroySyncLoop(uint32_t aLoopIndex);
+
+ void InitializeGCTimers();
+
+ enum GCTimerMode { PeriodicTimer = 0, IdleTimer, NoTimer };
+
+ void SetGCTimerMode(GCTimerMode aMode);
+
+ public:
+ void CancelGCTimers() { SetGCTimerMode(NoTimer); }
+
+ private:
+ void ShutdownGCTimers();
+
+ friend class WorkerRef;
+
+ bool AddWorkerRef(WorkerRef* aWorkerRefer, WorkerStatus aFailStatus);
+
+ void RemoveWorkerRef(WorkerRef* aWorkerRef);
+
+ void NotifyWorkerRefs(WorkerStatus aStatus);
+
+ bool HasActiveWorkerRefs() {
+ auto data = mWorkerThreadAccessible.Access();
+ return !(data->mChildWorkers.IsEmpty() && data->mTimeouts.IsEmpty() &&
+ data->mWorkerRefs.IsEmpty());
+ }
+
+ friend class WorkerEventTarget;
+
+ nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask);
+
+ nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask);
+
+ // Internal logic to dispatch a runnable. This is separate from Dispatch()
+ // to allow runnables to be atomically dispatched in bulk.
+ nsresult DispatchLockHeld(already_AddRefed<WorkerRunnable> aRunnable,
+ nsIEventTarget* aSyncLoopTarget,
+ const MutexAutoLock& aProofOfLock)
+ MOZ_REQUIRES(mMutex);
+
+ // This method dispatches a simple runnable that starts the shutdown procedure
+ // after a self.close(). This method is called after a ClearMainEventQueue()
+ // to be sure that the canceling runnable is the only one in the queue. We
+ // need this async operation to be sure that all the current JS code is
+ // executed.
+ void DispatchCancelingRunnable();
+
+ bool GetUseCounter(UseCounterWorker aUseCounter) {
+ MOZ_ASSERT(aUseCounter > UseCounterWorker::Unknown);
+ AssertIsOnWorkerThread();
+ return mUseCounters[static_cast<size_t>(aUseCounter)];
+ }
+
+ void ReportUseCounters();
+
+ UniquePtr<ClientSource> CreateClientSource();
+
+ // This method is called when corresponding script loader processes the COEP
+ // header for the worker.
+ // This method should be called only once in the main thread.
+ // After this method is called the COEP value owner(window/parent worker) is
+ // cached in mOwnerEmbedderPolicy such that it can be accessed in other
+ // threads, i.e. WorkerThread.
+ void EnsureOwnerEmbedderPolicy();
+
+ class EventTarget;
+ friend class EventTarget;
+ friend class AutoSyncLoopHolder;
+
+ struct TimeoutInfo;
+
+ class MemoryReporter;
+ friend class MemoryReporter;
+
+ friend class mozilla::dom::WorkerThread;
+
+ SharedMutex mMutex;
+ mozilla::CondVar mCondVar MOZ_GUARDED_BY(mMutex);
+
+ // We cannot make this CheckedUnsafePtr<WorkerPrivate> as this would violate
+ // our static assert
+ MOZ_NON_OWNING_REF WorkerPrivate* const mParent;
+
+ const nsString mScriptURL;
+
+ // This is the worker name for shared workers and dedicated workers.
+ const nsString mWorkerName;
+ const RequestCredentials mCredentialsMode;
+ enum WorkerType mWorkerType;
+
+ const WorkerKind mWorkerKind;
+
+ // The worker is owned by its thread, which is represented here. This is set
+ // in Constructor() and emptied by WorkerFinishedRunnable, and conditionally
+ // traversed by the cycle collector if the busy count is zero.
+ //
+ // There are 4 ways a worker can be terminated:
+ // 1. GC/CC - When the worker is in idle state (busycount == 0), it allows to
+ // traverse the 'hidden' mParentEventTargetRef pointer. This is the exposed
+ // Worker webidl object. Doing this, CC will be able to detect a cycle and
+ // Unlink is called. In Unlink, Worker calls Cancel().
+ // 2. Worker::Cancel() is called - the shutdown procedure starts immediately.
+ // 3. WorkerScope::Close() is called - Similar to point 2.
+ // 4. xpcom-shutdown notification - We call Kill().
+ RefPtr<Worker> mParentEventTargetRef;
+ RefPtr<WorkerPrivate> mSelfRef;
+
+ CancellationCallback mCancellationCallback;
+
+ // The termination callback is passed into the constructor on the parent
+ // thread and invoked by `ClearSelfAndParentEventTargetRef` just before it
+ // drops its self-ref.
+ TerminationCallback mTerminationCallback;
+
+ // The lifetime of these objects within LoadInfo is managed explicitly;
+ // they do not need to be cycle collected.
+ WorkerLoadInfo mLoadInfo;
+ LocationInfo mLocationInfo;
+
+ // Protected by mMutex.
+ workerinternals::JSSettings mJSSettings MOZ_GUARDED_BY(mMutex);
+
+ WorkerDebugger* mDebugger;
+
+ workerinternals::Queue<WorkerControlRunnable*, 4> mControlQueue;
+ workerinternals::Queue<WorkerRunnable*, 4> mDebuggerQueue;
+
+ // Touched on multiple threads, protected with mMutex. Only modified on the
+ // worker thread
+ JSContext* mJSContext MOZ_GUARDED_BY(mMutex);
+ // mThread is only modified on the Worker thread, before calling DoRunLoop
+ RefPtr<WorkerThread> mThread MOZ_GUARDED_BY(mMutex);
+ // mPRThread is only modified on another thread in ScheduleWorker(), and is
+ // constant for the duration of DoRunLoop. Static mutex analysis doesn't help
+ // here
+ PRThread* mPRThread;
+
+ // Accessed from main thread
+ RefPtr<ThrottledEventQueue> mMainThreadEventTargetForMessaging;
+ RefPtr<ThrottledEventQueue> mMainThreadEventTarget;
+
+ // Accessed from worker thread and destructing thread
+ RefPtr<WorkerEventTarget> mWorkerControlEventTarget;
+ RefPtr<WorkerEventTarget> mWorkerHybridEventTarget;
+
+ // A pauseable queue for WorkerDebuggeeRunnables directed at the main thread.
+ // See WorkerDebuggeeRunnable for details.
+ RefPtr<ThrottledEventQueue> mMainThreadDebuggeeEventTarget;
+
+ struct SyncLoopInfo {
+ explicit SyncLoopInfo(EventTarget* aEventTarget);
+
+ RefPtr<EventTarget> mEventTarget;
+ nsresult mResult;
+ bool mCompleted;
+#ifdef DEBUG
+ bool mHasRun;
+#endif
+ };
+
+ // This is only modified on the worker thread, but in DEBUG builds
+ // AssertValidSyncLoop function iterates it on other threads. Therefore
+ // modifications are done with mMutex held *only* in DEBUG builds.
+ nsTArray<UniquePtr<SyncLoopInfo>> mSyncLoopStack;
+
+ nsCOMPtr<nsITimer> mCancelingTimer;
+
+ // fired on the main thread if the worker script fails to load
+ nsCOMPtr<nsIRunnable> mLoadFailedRunnable;
+
+ RefPtr<PerformanceStorage> mPerformanceStorage;
+
+ RefPtr<WorkerCSPEventListener> mCSPEventListener;
+
+ // Protected by mMutex.
+ nsTArray<RefPtr<WorkerRunnable>> mPreStartRunnables MOZ_GUARDED_BY(mMutex);
+
+ // Only touched on the parent thread. Used for both SharedWorker and
+ // ServiceWorker RemoteWorkers.
+ RefPtr<RemoteWorkerChild> mRemoteWorkerController;
+
+ JS::UniqueChars mDefaultLocale; // nulled during worker JSContext init
+ TimeStamp mKillTime;
+ WorkerStatus mParentStatus MOZ_GUARDED_BY(mMutex);
+ WorkerStatus mStatus MOZ_GUARDED_BY(mMutex);
+
+ // This is touched on parent thread only, but it can be read on a different
+ // thread before crashing because hanging.
+ Atomic<uint64_t> mBusyCount;
+
+ TimeStamp mCreationTimeStamp;
+ DOMHighResTimeStamp mCreationTimeHighRes;
+
+ // Flags for use counters used directly by this worker.
+ static_assert(sizeof(UseCounterWorker) <= sizeof(size_t),
+ "UseCounterWorker is too big");
+ static_assert(UseCounterWorker::Count >= static_cast<UseCounterWorker>(0),
+ "Should be non-negative value and safe to cast to unsigned");
+ std::bitset<static_cast<size_t>(UseCounterWorker::Count)> mUseCounters;
+ bool mReportedUseCounters;
+
+ // This is created while creating the WorkerPrivate, so it's safe to be
+ // touched on any thread.
+ const nsID mAgentClusterId;
+
+ // Things touched on worker thread only.
+ struct WorkerThreadAccessible {
+ explicit WorkerThreadAccessible(WorkerPrivate* aParent);
+
+ RefPtr<WorkerGlobalScope> mScope;
+ RefPtr<WorkerDebuggerGlobalScope> mDebuggerScope;
+ // We cannot make this CheckedUnsafePtr<WorkerPrivate> as this would violate
+ // our static assert
+ nsTArray<WorkerPrivate*> mChildWorkers;
+ nsTObserverArray<WorkerRef*> mWorkerRefs;
+ nsTArray<UniquePtr<TimeoutInfo>> mTimeouts;
+
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsITimerCallback> mTimerRunnable;
+
+ nsCOMPtr<nsITimer> mPeriodicGCTimer;
+ nsCOMPtr<nsITimer> mIdleGCTimer;
+
+ RefPtr<MemoryReporter> mMemoryReporter;
+
+ // While running a nested event loop, whether a sync loop or a debugger
+ // event loop we want to keep track of which global is running it, if any,
+ // so runnables that run off that event loop can get at that information. In
+ // practice this only matters for various worker debugger runnables running
+ // against sandboxes, because all other runnables know which globals they
+ // belong to already. We could also address this by threading the relevant
+ // global through the chains of runnables involved, but we'd need to thread
+ // it through some runnables that run on the main thread, and that would
+ // require some care to make sure things get released on the correct thread,
+ // which we'd rather avoid. This member is only accessed on the worker
+ // thread.
+ nsCOMPtr<nsIGlobalObject> mCurrentEventLoopGlobal;
+
+ // Timer that triggers an interrupt on expiration of the current time slice
+ nsCOMPtr<nsITimer> mTSTimer;
+
+ // Execution manager used to regulate execution for this worker.
+ RefPtr<JSExecutionManager> mExecutionManager;
+
+ // Used to relinguish clearance for CTypes Callbacks.
+ nsTArray<AutoYieldJSThreadExecution> mYieldJSThreadExecution;
+
+ uint32_t mNumWorkerRefsPreventingShutdownStart;
+ uint32_t mDebuggerEventLoopLevel;
+
+ uint32_t mErrorHandlerRecursionCount;
+ int32_t mNextTimeoutId;
+
+ // Tracks the current setTimeout/setInterval nesting level.
+ // When there isn't a TimeoutHandler on the stack, this will be 0.
+ // Whenever setTimeout/setInterval are called, a new TimeoutInfo will be
+ // created with a nesting level one more than the current nesting level,
+ // saturating at the kClampTimeoutNestingLevel.
+ //
+ // When RunExpiredTimeouts is run, it sets this value to the
+ // TimeoutInfo::mNestingLevel for the duration of
+ // the WorkerScriptTimeoutHandler::Call which will explicitly trigger a
+ // microtask checkpoint so that any immediately-resolved promises will
+ // still see the nesting level.
+ uint32_t mCurrentTimerNestingLevel;
+
+ bool mFrozen;
+ bool mTimerRunning;
+ bool mRunningExpiredTimeouts;
+ bool mPeriodicGCTimerRunning;
+ bool mIdleGCTimerRunning;
+ bool mOnLine;
+ bool mJSThreadExecutionGranted;
+ bool mCCCollectedAnything;
+ FlippedOnce<false> mDeletionScheduled;
+ };
+ ThreadBound<WorkerThreadAccessible> mWorkerThreadAccessible;
+
+ class MOZ_RAII AutoPushEventLoopGlobal {
+ public:
+ AutoPushEventLoopGlobal(WorkerPrivate* aWorkerPrivate, JSContext* aCx);
+ ~AutoPushEventLoopGlobal();
+
+ private:
+ // We cannot make this CheckedUnsafePtr<WorkerPrivate> as this would violate
+ // our static assert
+ MOZ_NON_OWNING_REF WorkerPrivate* mWorkerPrivate;
+ nsCOMPtr<nsIGlobalObject> mOldEventLoopGlobal;
+ };
+ friend class AutoPushEventLoopGlobal;
+
+ uint32_t mPostSyncLoopOperations;
+
+ // List of operations to do at the end of the last sync event loop.
+ enum {
+ ePendingEventQueueClearing = 0x01,
+ eDispatchCancelingRunnable = 0x02,
+ };
+
+ bool mParentWindowPaused;
+
+ bool mCancelAllPendingRunnables;
+ bool mWorkerScriptExecutedSuccessfully;
+ bool mFetchHandlerWasAdded;
+ bool mMainThreadObjectsForgotten;
+ bool mIsChromeWorker;
+ bool mParentFrozen;
+
+ // mIsSecureContext is set once in our constructor; after that it can be read
+ // from various threads.
+ //
+ // It's a bit unfortunate that we have to have an out-of-band boolean for
+ // this, but we need access to this state from the parent thread, and we can't
+ // use our global object's secure state there.
+ const bool mIsSecureContext;
+
+ bool mDebuggerRegistered MOZ_GUARDED_BY(mMutex);
+
+ // During registration, this worker may be marked as not being ready to
+ // execute debuggee runnables or content.
+ //
+ // Protected by mMutex.
+ bool mDebuggerReady;
+ nsTArray<RefPtr<WorkerRunnable>> mDelayedDebuggeeRunnables;
+
+ // Whether this worker should have access to the WebExtension API bindings
+ // (currently only the Extension Background ServiceWorker declared in the
+ // extension manifest is allowed to access any WebExtension API bindings).
+ // This default to false, and it is eventually set to true by
+ // RemoteWorkerChild::ExecWorkerOnMainThread if the needed conditions
+ // are met.
+ bool mExtensionAPIAllowed;
+
+ // mIsInAutomation is true when we're running in test automation.
+ // We expose some extra testing functions in that case.
+ bool mIsInAutomation;
+
+ const RefPtr<PerformanceCounter> mPerformanceCounter =
+ MakeRefPtr<PerformanceCounter>(nsPrintfCString(
+ "Worker:%s", NS_ConvertUTF16toUTF8(mWorkerName).get()));
+
+ nsString mId;
+
+ // This is used to check if it's allowed to share the memory across the agent
+ // cluster.
+ const nsILoadInfo::CrossOriginOpenerPolicy mAgentClusterOpenerPolicy;
+
+ // Member variable of this class rather than the worker global scope because
+ // it's received on the main thread, but the global scope is thread-bound
+ // to the worker thread, so storing the value in the global scope would
+ // involve sacrificing the thread-bound-ness or using a WorkerRunnable, and
+ // there isn't a strong reason to store it on the global scope other than
+ // better consistency with the COEP spec.
+ Maybe<nsILoadInfo::CrossOriginEmbedderPolicy> mEmbedderPolicy;
+ Maybe<nsILoadInfo::CrossOriginEmbedderPolicy> mOwnerEmbedderPolicy;
+
+ /* Privileged add-on flag extracted from the AddonPolicy on the nsIPrincipal
+ * on the main thread when constructing a top-level worker. The flag is
+ * propagated to nested workers. The flag is only allowed to take effect in
+ * extension processes and is forbidden in content scripts in content
+ * processes. The flag may be read on either the parent/owner thread as well
+ * as on the worker thread itself. When bug 1443925 is fixed allowing
+ * nsIPrincipal to be used OMT, it may be possible to remove this flag. */
+ bool mIsPrivilegedAddonGlobal;
+
+ Atomic<uint32_t> mTopLevelWorkerFinishedRunnableCount;
+ Atomic<uint32_t> mWorkerFinishedRunnableCount;
+
+ nsTArray<nsCOMPtr<nsITargetShutdownTask>> mShutdownTasks
+ MOZ_GUARDED_BY(mMutex);
+ bool mShutdownTasksRun MOZ_GUARDED_BY(mMutex) = false;
+};
+
+class AutoSyncLoopHolder {
+ CheckedUnsafePtr<WorkerPrivate> mWorkerPrivate;
+ nsCOMPtr<nsISerialEventTarget> mTarget;
+ uint32_t mIndex;
+
+ public:
+ // See CreateNewSyncLoop() for more information about the correct value to use
+ // for aFailStatus.
+ AutoSyncLoopHolder(WorkerPrivate* aWorkerPrivate, WorkerStatus aFailStatus)
+ : mWorkerPrivate(aWorkerPrivate),
+ mTarget(aWorkerPrivate->CreateNewSyncLoop(aFailStatus)),
+ mIndex(aWorkerPrivate->mSyncLoopStack.Length() - 1) {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ ~AutoSyncLoopHolder() {
+ if (mWorkerPrivate && mTarget) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ mWorkerPrivate->StopSyncLoop(mTarget, NS_ERROR_FAILURE);
+ mWorkerPrivate->DestroySyncLoop(mIndex);
+ }
+ }
+
+ nsresult Run() {
+ CheckedUnsafePtr<WorkerPrivate> workerPrivate = mWorkerPrivate;
+ mWorkerPrivate = nullptr;
+
+ workerPrivate->AssertIsOnWorkerThread();
+
+ return workerPrivate->RunCurrentSyncLoop();
+ }
+
+ nsISerialEventTarget* GetSerialEventTarget() const {
+ // This can be null if CreateNewSyncLoop() fails.
+ return mTarget;
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_workers_workerprivate_h__ */
diff --git a/dom/workers/WorkerRef.cpp b/dom/workers/WorkerRef.cpp
new file mode 100644
index 0000000000..9fcf329495
--- /dev/null
+++ b/dom/workers/WorkerRef.cpp
@@ -0,0 +1,245 @@
+/* -*- 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/WorkerRef.h"
+
+#include "nsDebug.h"
+#include "WorkerRunnable.h"
+#include "WorkerPrivate.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+// This runnable is used to release the StrongWorkerRef on the worker thread
+// when a ThreadSafeWorkerRef is released.
+class ReleaseRefControlRunnable final : public WorkerControlRunnable {
+ public:
+ ReleaseRefControlRunnable(WorkerPrivate* aWorkerPrivate,
+ already_AddRefed<StrongWorkerRef> aRef)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mRef(std::move(aRef)) {
+ MOZ_ASSERT(mRef);
+ }
+
+ bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { return true; }
+
+ void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {}
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ mRef = nullptr;
+ return true;
+ }
+
+ private:
+ RefPtr<StrongWorkerRef> mRef;
+};
+
+} // namespace
+
+// ----------------------------------------------------------------------------
+// WorkerRef
+
+WorkerRef::WorkerRef(WorkerPrivate* aWorkerPrivate, const char* aName,
+ bool aIsPreventingShutdown)
+ : mWorkerPrivate(aWorkerPrivate),
+ mName(aName),
+ mIsPreventingShutdown(aIsPreventingShutdown),
+ mHolding(false) {
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aName);
+
+ aWorkerPrivate->AssertIsOnWorkerThread();
+}
+
+WorkerRef::~WorkerRef() {
+ NS_ASSERT_OWNINGTHREAD(WorkerRef);
+ ReleaseWorker();
+}
+
+void WorkerRef::ReleaseWorker() {
+ if (mHolding) {
+ MOZ_ASSERT(mWorkerPrivate);
+
+ mWorkerPrivate->RemoveWorkerRef(this);
+ mWorkerPrivate = nullptr;
+
+ mHolding = false;
+ }
+}
+
+bool WorkerRef::HoldWorker(WorkerStatus aStatus) {
+ MOZ_ASSERT(mWorkerPrivate);
+ MOZ_ASSERT(!mHolding);
+
+ if (NS_WARN_IF(!mWorkerPrivate->AddWorkerRef(this, aStatus))) {
+ return false;
+ }
+
+ mHolding = true;
+ return true;
+}
+
+void WorkerRef::Notify() {
+ NS_ASSERT_OWNINGTHREAD(WorkerRef);
+
+ if (!mCallback) {
+ return;
+ }
+
+ MoveOnlyFunction<void()> callback = std::move(mCallback);
+ MOZ_ASSERT(!mCallback);
+
+ callback();
+}
+
+// ----------------------------------------------------------------------------
+// WeakWorkerRef
+
+/* static */
+already_AddRefed<WeakWorkerRef> WeakWorkerRef::Create(
+ WorkerPrivate* aWorkerPrivate, MoveOnlyFunction<void()>&& aCallback) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<WeakWorkerRef> ref = new WeakWorkerRef(aWorkerPrivate);
+ if (!ref->HoldWorker(Canceling)) {
+ return nullptr;
+ }
+
+ ref->mCallback = std::move(aCallback);
+
+ return ref.forget();
+}
+
+WeakWorkerRef::WeakWorkerRef(WorkerPrivate* aWorkerPrivate)
+ : WorkerRef(aWorkerPrivate, "WeakWorkerRef", false) {}
+
+WeakWorkerRef::~WeakWorkerRef() = default;
+
+void WeakWorkerRef::Notify() {
+ MOZ_ASSERT(mHolding);
+ MOZ_ASSERT(mWorkerPrivate);
+
+ // Notify could drop the last reference to this object. We must keep it alive
+ // in order to call ReleaseWorker() immediately after.
+ RefPtr<WeakWorkerRef> kungFuGrip = this;
+
+ WorkerRef::Notify();
+ ReleaseWorker();
+}
+
+WorkerPrivate* WeakWorkerRef::GetPrivate() const {
+ NS_ASSERT_OWNINGTHREAD(WeakWorkerRef);
+ return mWorkerPrivate;
+}
+
+WorkerPrivate* WeakWorkerRef::GetUnsafePrivate() const {
+ return mWorkerPrivate;
+}
+
+// ----------------------------------------------------------------------------
+// StrongWorkerRef
+
+/* static */
+already_AddRefed<StrongWorkerRef> StrongWorkerRef::Create(
+ WorkerPrivate* const aWorkerPrivate, const char* const aName,
+ MoveOnlyFunction<void()>&& aCallback) {
+ if (RefPtr<StrongWorkerRef> ref =
+ CreateImpl(aWorkerPrivate, aName, Canceling)) {
+ ref->mCallback = std::move(aCallback);
+ return ref.forget();
+ }
+ return nullptr;
+}
+
+/* static */
+already_AddRefed<StrongWorkerRef> StrongWorkerRef::CreateForcibly(
+ WorkerPrivate* const aWorkerPrivate, const char* const aName) {
+ return CreateImpl(aWorkerPrivate, aName, Killing);
+}
+
+/* static */
+already_AddRefed<StrongWorkerRef> StrongWorkerRef::CreateImpl(
+ WorkerPrivate* const aWorkerPrivate, const char* const aName,
+ WorkerStatus const aFailStatus) {
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aName);
+
+ RefPtr<StrongWorkerRef> ref = new StrongWorkerRef(aWorkerPrivate, aName);
+ if (!ref->HoldWorker(aFailStatus)) {
+ return nullptr;
+ }
+
+ return ref.forget();
+}
+
+StrongWorkerRef::StrongWorkerRef(WorkerPrivate* aWorkerPrivate,
+ const char* aName)
+ : WorkerRef(aWorkerPrivate, aName, true) {}
+
+StrongWorkerRef::~StrongWorkerRef() = default;
+
+WorkerPrivate* StrongWorkerRef::Private() const {
+ NS_ASSERT_OWNINGTHREAD(StrongWorkerRef);
+ return mWorkerPrivate;
+}
+
+// ----------------------------------------------------------------------------
+// ThreadSafeWorkerRef
+
+ThreadSafeWorkerRef::ThreadSafeWorkerRef(StrongWorkerRef* aRef) : mRef(aRef) {
+ MOZ_ASSERT(aRef);
+ aRef->Private()->AssertIsOnWorkerThread();
+}
+
+ThreadSafeWorkerRef::~ThreadSafeWorkerRef() {
+ // Let's release the StrongWorkerRef on the correct thread.
+ if (!mRef->mWorkerPrivate->IsOnWorkerThread()) {
+ WorkerPrivate* workerPrivate = mRef->mWorkerPrivate;
+ RefPtr<ReleaseRefControlRunnable> r =
+ new ReleaseRefControlRunnable(workerPrivate, mRef.forget());
+ r->Dispatch();
+ return;
+ }
+}
+
+WorkerPrivate* ThreadSafeWorkerRef::Private() const {
+ return mRef->mWorkerPrivate;
+}
+
+// ----------------------------------------------------------------------------
+// IPCWorkerRef
+
+/* static */
+already_AddRefed<IPCWorkerRef> IPCWorkerRef::Create(
+ WorkerPrivate* aWorkerPrivate, const char* aName,
+ MoveOnlyFunction<void()>&& aCallback) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<IPCWorkerRef> ref = new IPCWorkerRef(aWorkerPrivate, aName);
+ if (!ref->HoldWorker(Canceling)) {
+ return nullptr;
+ }
+
+ ref->mCallback = std::move(aCallback);
+
+ return ref.forget();
+}
+
+IPCWorkerRef::IPCWorkerRef(WorkerPrivate* aWorkerPrivate, const char* aName)
+ : WorkerRef(aWorkerPrivate, aName, false) {}
+
+IPCWorkerRef::~IPCWorkerRef() = default;
+
+WorkerPrivate* IPCWorkerRef::Private() const {
+ NS_ASSERT_OWNINGTHREAD(IPCWorkerRef);
+ return mWorkerPrivate;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/workers/WorkerRef.h b/dom/workers/WorkerRef.h
new file mode 100644
index 0000000000..decee3994e
--- /dev/null
+++ b/dom/workers/WorkerRef.h
@@ -0,0 +1,237 @@
+/* -*- 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_WorkerRef_h
+#define mozilla_dom_workers_WorkerRef_h
+
+#include "mozilla/dom/WorkerStatus.h"
+#include "mozilla/MoveOnlyFunction.h"
+#include "mozilla/RefPtr.h"
+#include "nsISupports.h"
+
+namespace mozilla::dom {
+
+/*
+ * If you want to play with a DOM Worker, you must know that it can go away
+ * at any time if nothing prevents its shutting down. This documentation helps
+ * to understand how to play with DOM Workers correctly.
+ *
+ * There are several reasons why a DOM Worker could go away. Here is the
+ * complete list:
+ *
+ * a. GC/CC - If the DOM Worker thread is idle and the Worker object is garbage
+ * collected, it goes away.
+ * b. The worker script can call self.close()
+ * c. The Worker object calls worker.terminate()
+ * d. Firefox is shutting down.
+ *
+ * When a DOM Worker goes away, it does several steps. See more in
+ * WorkerStatus.h. The DOM Worker thread will basically stop scheduling
+ * WorkerRunnables, and eventually WorkerControlRunnables. But if there is
+ * something preventing the shutting down, it will always possible to dispatch
+ * WorkerControlRunnables. Of course, at some point, the worker _must_ be
+ * released, otherwise firefox will leak it and the browser shutdown will hang.
+ *
+ * WeakWorkerRef is a refcounted, NON thread-safe object.
+ *
+ * From this object, you can obtain a WorkerPrivate, calling
+ * WeakWorkerRef::GetPrivate(). It returns nullptr if the worker is shutting
+ * down or if it is already gone away.
+ *
+ * If you want to know when a DOM Worker starts the shutting down procedure,
+ * pass a callback to the mozilla::dom::WeakWorkerRef::Create() method.
+ * Your function will be called. Note that _after_ the callback,
+ * WeakWorkerRef::GetPrivate() will return nullptr.
+ *
+ * How to keep a DOM Worker alive?
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * If you need to keep the worker alive, you must use StrongWorkerRef.
+ * You can have this refcounted, NON thread-safe object, calling
+ * mozilla::dom::StrongWorkerRef::Create(WorkerPrivate* aWorkerPrivate);
+ *
+ * If you have a StrongWorkerRef:
+ * a. the DOM Worker is kept alive.
+ * b. you can have access to the WorkerPrivate, calling: Private().
+ * c. WorkerControlRunnable can be dispatched.
+ *
+ * Note that the DOM Worker shutdown can start at any time, but having a
+ * StrongWorkerRef prevents the full shutdown. Also with StrongWorkerRef, you
+ * can pass a callback when calling mozilla::dom::StrongWorkerRef::Create().
+ *
+ * When the DOM Worker shutdown starts, WorkerRunnable cannot be dispatched
+ * anymore. At this point, you should dispatch WorkerControlRunnable just to
+ * release resources.
+ *
+ * How to have a thread-safe DOM Worker reference?
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * Sometimes you need to play with threads and you need a thread-safe worker
+ * reference. ThreadSafeWorkerRef is what you want.
+ *
+ * Just because this object can be sent to different threads, we don't allow the
+ * setting of a callback. It would be confusing.
+ *
+ * ThreadSafeWorkerRef can be destroyed in any thread. Internally it keeps a
+ * reference to its StrongWorkerRef creator and this ref will be dropped on the
+ * correct thread when the ThreadSafeWorkerRef is deleted.
+ *
+ * IPC WorkerRef
+ * ~~~~~~~~~~~~~
+ *
+ * IPDL protocols require a correct shutdown sequence. Because of this, they
+ * need a special configuration:
+ * 1. they need to be informed when the Worker starts the shutting down
+ * 2. they don't want to prevent the shutdown
+ * 3. but at the same time, they need to block the shutdown until the WorkerRef
+ * is not longer alive.
+ *
+ * Point 1 is a standard feature of WorkerRef; point 2 is similar to
+ * WeakWorkerRef; point 3 is similar to StrongWorkerRef.
+ *
+ * You can create a special IPC WorkerRef using this static method:
+ * mozilla::dom::IPCWorkerRef::Create(WorkerPrivate* aWorkerPrivate,
+ * const char* * aName);
+ */
+
+class WorkerPrivate;
+class StrongWorkerRef;
+class ThreadSafeWorkerRef;
+
+class WorkerRef {
+ friend class WorkerPrivate;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(WorkerRef)
+
+ protected:
+ WorkerRef(WorkerPrivate* aWorkerPrivate, const char* aName,
+ bool aIsPreventingShutdown);
+ virtual ~WorkerRef();
+
+ virtual void Notify();
+
+ bool HoldWorker(WorkerStatus aStatus);
+ void ReleaseWorker();
+
+ bool IsPreventingShutdown() const { return mIsPreventingShutdown; }
+
+ const char* Name() const { return mName; }
+
+ WorkerPrivate* mWorkerPrivate;
+
+ MoveOnlyFunction<void()> mCallback;
+ const char* const mName;
+ const bool mIsPreventingShutdown;
+
+ // True if this WorkerRef has been added to a WorkerPrivate.
+ bool mHolding;
+};
+
+class WeakWorkerRef final : public WorkerRef {
+ public:
+ static already_AddRefed<WeakWorkerRef> Create(
+ WorkerPrivate* aWorkerPrivate,
+ MoveOnlyFunction<void()>&& aCallback = nullptr);
+
+ WorkerPrivate* GetPrivate() const;
+
+ // This can be called on any thread. It's racy and, in general, the wrong
+ // choice.
+ WorkerPrivate* GetUnsafePrivate() const;
+
+ private:
+ explicit WeakWorkerRef(WorkerPrivate* aWorkerPrivate);
+ ~WeakWorkerRef();
+
+ void Notify() override;
+};
+
+class StrongWorkerRef final : public WorkerRef {
+ public:
+ static already_AddRefed<StrongWorkerRef> Create(
+ WorkerPrivate* aWorkerPrivate, const char* aName,
+ MoveOnlyFunction<void()>&& aCallback = nullptr);
+
+ // This function creates a StrongWorkerRef even when in the Canceling state of
+ // the worker's lifecycle. It's intended to be used by system code, e.g. code
+ // that needs to perform IPC.
+ //
+ // This method should only be used in cases where the StrongWorkerRef will be
+ // used for an extremely bounded duration that cannot be impacted by content.
+ // For example, IPCStreams use this type of ref in order to immediately
+ // migrate to an actor on another thread. Whether the IPCStream ever actually
+ // is streamed does not matter; the ref will be dropped once the new actor is
+ // created. For this reason, this method does not take a callback. It's
+ // expected and required that callers will drop the reference when they are
+ // done.
+ static already_AddRefed<StrongWorkerRef> CreateForcibly(
+ WorkerPrivate* aWorkerPrivate, const char* aName);
+
+ WorkerPrivate* Private() const;
+
+ private:
+ friend class WeakWorkerRef;
+ friend class ThreadSafeWorkerRef;
+
+ static already_AddRefed<StrongWorkerRef> CreateImpl(
+ WorkerPrivate* aWorkerPrivate, const char* aName,
+ WorkerStatus aFailStatus);
+
+ StrongWorkerRef(WorkerPrivate* aWorkerPrivate, const char* aName);
+ ~StrongWorkerRef();
+};
+
+class ThreadSafeWorkerRef final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadSafeWorkerRef)
+
+ explicit ThreadSafeWorkerRef(StrongWorkerRef* aRef);
+
+ WorkerPrivate* Private() const;
+
+ private:
+ friend class StrongWorkerRef;
+
+ ~ThreadSafeWorkerRef();
+
+ RefPtr<StrongWorkerRef> mRef;
+};
+
+class IPCWorkerRef final : public WorkerRef {
+ public:
+ static already_AddRefed<IPCWorkerRef> Create(
+ WorkerPrivate* aWorkerPrivate, const char* aName,
+ MoveOnlyFunction<void()>&& aCallback = nullptr);
+
+ WorkerPrivate* Private() const;
+
+ private:
+ IPCWorkerRef(WorkerPrivate* aWorkerPrivate, const char* aName);
+ ~IPCWorkerRef();
+};
+
+// Template class to keep an Actor pointer, as a raw pointer, in a ref-counted
+// way when passed to lambdas.
+template <class ActorPtr>
+class IPCWorkerRefHelper final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(IPCWorkerRefHelper);
+
+ explicit IPCWorkerRefHelper(ActorPtr* aActor) : mActor(aActor) {}
+
+ ActorPtr* Actor() const { return mActor; }
+
+ private:
+ ~IPCWorkerRefHelper() = default;
+
+ // Raw pointer
+ ActorPtr* mActor;
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_workers_WorkerRef_h */
diff --git a/dom/workers/WorkerRunnable.cpp b/dom/workers/WorkerRunnable.cpp
new file mode 100644
index 0000000000..ec461d3cfb
--- /dev/null
+++ b/dom/workers/WorkerRunnable.cpp
@@ -0,0 +1,728 @@
+/* -*- 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 "WorkerRunnable.h"
+
+#include "WorkerScope.h"
+#include "js/RootingAPI.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryHistogramEnums.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/Worker.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "nsDebug.h"
+#include "nsGlobalWindowInner.h"
+#include "nsID.h"
+#include "nsIEventTarget.h"
+#include "nsIGlobalObject.h"
+#include "nsIRunnable.h"
+#include "nsThreadUtils.h"
+#include "nsWrapperCacheInlines.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+const nsIID kWorkerRunnableIID = {
+ 0x320cc0b5,
+ 0xef12,
+ 0x4084,
+ {0x88, 0x6e, 0xca, 0x6a, 0x81, 0xe4, 0x1d, 0x68}};
+
+} // namespace
+
+#ifdef DEBUG
+WorkerRunnable::WorkerRunnable(WorkerPrivate* aWorkerPrivate,
+ TargetAndBusyBehavior aBehavior)
+ : mWorkerPrivate(aWorkerPrivate),
+ mBehavior(aBehavior),
+ mCanceled(0),
+ mCallingCancelWithinRun(false) {
+ MOZ_ASSERT(aWorkerPrivate);
+}
+#endif
+
+bool WorkerRunnable::IsDebuggerRunnable() const { return false; }
+
+nsIGlobalObject* WorkerRunnable::DefaultGlobalObject() const {
+ if (IsDebuggerRunnable()) {
+ return mWorkerPrivate->DebuggerGlobalScope();
+ } else {
+ return mWorkerPrivate->GlobalScope();
+ }
+}
+
+bool WorkerRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate) {
+#ifdef DEBUG
+ MOZ_ASSERT(aWorkerPrivate);
+
+ switch (mBehavior) {
+ case ParentThreadUnchangedBusyCount:
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ break;
+
+ case WorkerThreadModifyBusyCount:
+ case WorkerThreadUnchangedBusyCount:
+ aWorkerPrivate->AssertIsOnParentThread();
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown behavior!");
+ }
+#endif
+
+ if (mBehavior == WorkerThreadModifyBusyCount) {
+ return aWorkerPrivate->ModifyBusyCount(true);
+ }
+
+ return true;
+}
+
+bool WorkerRunnable::Dispatch() {
+ bool ok = PreDispatch(mWorkerPrivate);
+ if (ok) {
+ ok = DispatchInternal();
+ }
+ PostDispatch(mWorkerPrivate, ok);
+ return ok;
+}
+
+bool WorkerRunnable::DispatchInternal() {
+ RefPtr<WorkerRunnable> runnable(this);
+
+ if (mBehavior == WorkerThreadModifyBusyCount ||
+ mBehavior == WorkerThreadUnchangedBusyCount) {
+ if (IsDebuggerRunnable()) {
+ return NS_SUCCEEDED(
+ mWorkerPrivate->DispatchDebuggerRunnable(runnable.forget()));
+ } else {
+ return NS_SUCCEEDED(mWorkerPrivate->Dispatch(runnable.forget()));
+ }
+ }
+
+ MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount);
+
+ if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) {
+ return NS_SUCCEEDED(parent->Dispatch(runnable.forget()));
+ }
+
+ if (IsDebuggeeRunnable()) {
+ RefPtr<WorkerDebuggeeRunnable> debuggeeRunnable =
+ runnable.forget().downcast<WorkerDebuggeeRunnable>();
+ return NS_SUCCEEDED(mWorkerPrivate->DispatchDebuggeeToMainThread(
+ debuggeeRunnable.forget(), NS_DISPATCH_NORMAL));
+ }
+
+ return NS_SUCCEEDED(mWorkerPrivate->DispatchToMainThread(runnable.forget()));
+}
+
+void WorkerRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) {
+ MOZ_ASSERT(aWorkerPrivate);
+
+#ifdef DEBUG
+ switch (mBehavior) {
+ case ParentThreadUnchangedBusyCount:
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ break;
+
+ case WorkerThreadModifyBusyCount:
+ aWorkerPrivate->AssertIsOnParentThread();
+ break;
+
+ case WorkerThreadUnchangedBusyCount:
+ aWorkerPrivate->AssertIsOnParentThread();
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown behavior!");
+ }
+#endif
+
+ if (!aDispatchResult) {
+ if (mBehavior == WorkerThreadModifyBusyCount) {
+ aWorkerPrivate->ModifyBusyCount(false);
+ }
+ }
+}
+
+bool WorkerRunnable::PreRun(WorkerPrivate* aWorkerPrivate) { return true; }
+
+void WorkerRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult) {
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aWorkerPrivate);
+
+#ifdef DEBUG
+ switch (mBehavior) {
+ case ParentThreadUnchangedBusyCount:
+ aWorkerPrivate->AssertIsOnParentThread();
+ break;
+
+ case WorkerThreadModifyBusyCount:
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ break;
+
+ case WorkerThreadUnchangedBusyCount:
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown behavior!");
+ }
+#endif
+
+ if (mBehavior == WorkerThreadModifyBusyCount) {
+ aWorkerPrivate->ModifyBusyCountFromWorker(false);
+ }
+}
+
+// static
+WorkerRunnable* WorkerRunnable::FromRunnable(nsIRunnable* aRunnable) {
+ MOZ_ASSERT(aRunnable);
+
+ WorkerRunnable* runnable;
+ nsresult rv = aRunnable->QueryInterface(kWorkerRunnableIID,
+ reinterpret_cast<void**>(&runnable));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(runnable);
+ return runnable;
+}
+
+NS_IMPL_ADDREF(WorkerRunnable)
+NS_IMPL_RELEASE(WorkerRunnable)
+
+NS_INTERFACE_MAP_BEGIN(WorkerRunnable)
+ NS_INTERFACE_MAP_ENTRY(nsIRunnable)
+ NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRunnable)
+ // kWorkerRunnableIID is special in that it does not AddRef its result.
+ if (aIID.Equals(kWorkerRunnableIID)) {
+ *aInstancePtr = this;
+ return NS_OK;
+ } else
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+WorkerRunnable::Run() {
+ bool targetIsWorkerThread = mBehavior == WorkerThreadModifyBusyCount ||
+ mBehavior == WorkerThreadUnchangedBusyCount;
+
+#ifdef DEBUG
+ MOZ_ASSERT_IF(mCallingCancelWithinRun, targetIsWorkerThread);
+ if (targetIsWorkerThread) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ } else {
+ MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount);
+ mWorkerPrivate->AssertIsOnParentThread();
+ }
+#endif
+
+ if (IsCanceled() && !mCallingCancelWithinRun) {
+ return NS_OK;
+ }
+
+ if (targetIsWorkerThread &&
+ mWorkerPrivate->AllPendingRunnablesShouldBeCanceled() && !IsCanceled() &&
+ !mCallingCancelWithinRun) {
+ // Prevent recursion.
+ mCallingCancelWithinRun = true;
+
+ Cancel();
+
+ MOZ_ASSERT(mCallingCancelWithinRun);
+ mCallingCancelWithinRun = false;
+
+ MOZ_ASSERT(IsCanceled(), "Subclass Cancel() didn't set IsCanceled()!");
+
+ if (mBehavior == WorkerThreadModifyBusyCount) {
+ mWorkerPrivate->ModifyBusyCountFromWorker(false);
+ }
+
+ return NS_OK;
+ }
+
+ bool result = PreRun(mWorkerPrivate);
+ if (!result) {
+ MOZ_ASSERT(targetIsWorkerThread,
+ "The only PreRun implementation that can fail is "
+ "ScriptExecutorRunnable");
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(!JS_IsExceptionPending(mWorkerPrivate->GetJSContext()));
+ // We can't enter a useful realm on the JSContext here; just pass it
+ // in as-is.
+ PostRun(mWorkerPrivate->GetJSContext(), mWorkerPrivate, false);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Track down the appropriate global, if any, to use for the AutoEntryScript.
+ nsCOMPtr<nsIGlobalObject> globalObject;
+ bool isMainThread = !targetIsWorkerThread && !mWorkerPrivate->GetParent();
+ MOZ_ASSERT(isMainThread == NS_IsMainThread());
+ RefPtr<WorkerPrivate> kungFuDeathGrip;
+ if (targetIsWorkerThread) {
+ globalObject = mWorkerPrivate->GetCurrentEventLoopGlobal();
+ if (!globalObject) {
+ globalObject = DefaultGlobalObject();
+ // Our worker thread may not be in a good state here if there is no
+ // JSContext avaliable. The way this manifests itself is that
+ // globalObject ends up null (though it's not clear to me how we can be
+ // running runnables at all when DefaultGlobalObject() is returning
+ // false!) and then when we try to init the AutoJSAPI either
+ // CycleCollectedJSContext::Get() returns null or it has a null JSContext.
+ // In any case, we used to have a check for
+ // GetCurrentWorkerThreadJSContext() being non-null here and that seems to
+ // avoid the problem, so let's keep doing that check even if we don't need
+ // the JSContext here at all.
+ if (NS_WARN_IF(!globalObject && !GetCurrentWorkerThreadJSContext())) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // We may still not have a globalObject here: in the case of
+ // CompileScriptRunnable, we don't actually create the global object until
+ // we have the script data, which happens in a syncloop under
+ // CompileScriptRunnable::WorkerRun, so we can't assert that it got created
+ // in the PreRun call above.
+ } else {
+ kungFuDeathGrip = mWorkerPrivate;
+ if (isMainThread) {
+ globalObject = nsGlobalWindowInner::Cast(mWorkerPrivate->GetWindow());
+ } else {
+ globalObject = mWorkerPrivate->GetParent()->GlobalScope();
+ }
+ }
+
+ // We might run script as part of WorkerRun, so we need an AutoEntryScript.
+ // This is part of the HTML spec for workers at:
+ // http://www.whatwg.org/specs/web-apps/current-work/#run-a-worker
+ // If we don't have a globalObject we have to use an AutoJSAPI instead, but
+ // this is OK as we won't be running script in these circumstances.
+ Maybe<mozilla::dom::AutoJSAPI> maybeJSAPI;
+ Maybe<mozilla::dom::AutoEntryScript> aes;
+ JSContext* cx;
+ AutoJSAPI* jsapi;
+ if (globalObject) {
+ aes.emplace(globalObject, "Worker runnable", isMainThread);
+ jsapi = aes.ptr();
+ cx = aes->cx();
+ } else {
+ maybeJSAPI.emplace();
+ maybeJSAPI->Init();
+ jsapi = maybeJSAPI.ptr();
+ cx = jsapi->cx();
+ }
+
+ // Note that we can't assert anything about
+ // mWorkerPrivate->ParentEventTargetRef()->GetWrapper()
+ // existing, since it may in fact have been GCed (and we may be one of the
+ // runnables cleaning up the worker as a result).
+
+ // If we are on the parent thread and that thread is not the main thread,
+ // then we must be a dedicated worker (because there are no
+ // Shared/ServiceWorkers whose parent is itself a worker) and then we
+ // definitely have a globalObject. If it _is_ the main thread, globalObject
+ // can be null for workers started from JSMs or other non-window contexts,
+ // sadly.
+ MOZ_ASSERT_IF(!targetIsWorkerThread && !isMainThread,
+ mWorkerPrivate->IsDedicatedWorker() && globalObject);
+
+ // If we're on the parent thread we might be in a null realm in the
+ // situation described above when globalObject is null. Make sure to enter
+ // the realm of the worker's reflector if there is one. There might
+ // not be one if we're just starting to compile the script for this worker.
+ Maybe<JSAutoRealm> ar;
+ if (!targetIsWorkerThread && mWorkerPrivate->IsDedicatedWorker() &&
+ mWorkerPrivate->ParentEventTargetRef()->GetWrapper()) {
+ JSObject* wrapper = mWorkerPrivate->ParentEventTargetRef()->GetWrapper();
+
+ // If we're on the parent thread and have a reflector and a globalObject,
+ // then the realms of cx, globalObject, and the worker's reflector
+ // should all match.
+ MOZ_ASSERT_IF(globalObject,
+ js::GetNonCCWObjectRealm(wrapper) == js::GetContextRealm(cx));
+ MOZ_ASSERT_IF(globalObject,
+ js::GetNonCCWObjectRealm(wrapper) ==
+ js::GetNonCCWObjectRealm(
+ globalObject->GetGlobalJSObjectPreserveColor()));
+
+ // If we're on the parent thread and have a reflector, then our
+ // JSContext had better be either in the null realm (and hence
+ // have no globalObject) or in the realm of our reflector.
+ MOZ_ASSERT(!js::GetContextRealm(cx) ||
+ js::GetNonCCWObjectRealm(wrapper) == js::GetContextRealm(cx),
+ "Must either be in the null compartment or in our reflector "
+ "compartment");
+
+ ar.emplace(cx, wrapper);
+ }
+
+ MOZ_ASSERT(!jsapi->HasException());
+ result = WorkerRun(cx, mWorkerPrivate);
+ jsapi->ReportException();
+
+ // We can't even assert that this didn't create our global, since in the case
+ // of CompileScriptRunnable it _does_.
+
+ // It would be nice to avoid passing a JSContext to PostRun, but in the case
+ // of ScriptExecutorRunnable we need to know the current compartment on the
+ // JSContext (the one we set up based on the global returned from PreRun) so
+ // that we can sanely do exception reporting. In particular, we want to make
+ // sure that we do our JS_SetPendingException while still in that compartment,
+ // because otherwise we might end up trying to create a cross-compartment
+ // wrapper when we try to move the JS exception from our runnable's
+ // ErrorResult to the JSContext, and that's not desirable in this case.
+ //
+ // We _could_ skip passing a JSContext here and then in
+ // ScriptExecutorRunnable::PostRun end up grabbing it from the WorkerPrivate
+ // and looking at its current compartment. But that seems like slightly weird
+ // action-at-a-distance...
+ //
+ // In any case, we do NOT try to change the compartment on the JSContext at
+ // this point; in the one case in which we could do that
+ // (CompileScriptRunnable) it actually doesn't matter which compartment we're
+ // in for PostRun.
+ PostRun(cx, mWorkerPrivate, result);
+ MOZ_ASSERT(!jsapi->HasException());
+
+ return result ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult WorkerRunnable::Cancel() {
+ uint32_t canceledCount = ++mCanceled;
+
+ MOZ_ASSERT(canceledCount, "Cancel() overflow!");
+
+ // The docs say that Cancel() should not be called more than once and that we
+ // should throw NS_ERROR_UNEXPECTED if it is.
+ return (canceledCount == 1) ? NS_OK : NS_ERROR_UNEXPECTED;
+}
+
+void WorkerDebuggerRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) {}
+
+WorkerSyncRunnable::WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+ nsIEventTarget* aSyncLoopTarget)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mSyncLoopTarget(aSyncLoopTarget) {
+#ifdef DEBUG
+ if (mSyncLoopTarget) {
+ mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
+ }
+#endif
+}
+
+WorkerSyncRunnable::WorkerSyncRunnable(
+ WorkerPrivate* aWorkerPrivate, nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mSyncLoopTarget(std::move(aSyncLoopTarget)) {
+#ifdef DEBUG
+ if (mSyncLoopTarget) {
+ mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
+ }
+#endif
+}
+
+WorkerSyncRunnable::~WorkerSyncRunnable() = default;
+
+bool WorkerSyncRunnable::DispatchInternal() {
+ if (mSyncLoopTarget) {
+ RefPtr<WorkerSyncRunnable> runnable(this);
+ return NS_SUCCEEDED(
+ mSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL));
+ }
+
+ return WorkerRunnable::DispatchInternal();
+}
+
+void MainThreadWorkerSyncRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) {}
+
+MainThreadStopSyncLoopRunnable::MainThreadStopSyncLoopRunnable(
+ WorkerPrivate* aWorkerPrivate, nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget,
+ nsresult aResult)
+ : WorkerSyncRunnable(aWorkerPrivate, std::move(aSyncLoopTarget)),
+ mResult(aResult) {
+ AssertIsOnMainThread();
+#ifdef DEBUG
+ mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
+#endif
+}
+
+nsresult MainThreadStopSyncLoopRunnable::Cancel() {
+ // We need to check first if cancel is called twice
+ nsresult rv = WorkerSyncRunnable::Cancel();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = Run();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Run() failed");
+
+ return rv;
+}
+
+bool MainThreadStopSyncLoopRunnable::WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(mSyncLoopTarget);
+
+ nsCOMPtr<nsIEventTarget> syncLoopTarget;
+ mSyncLoopTarget.swap(syncLoopTarget);
+
+ aWorkerPrivate->StopSyncLoop(syncLoopTarget, mResult);
+ return true;
+}
+
+bool MainThreadStopSyncLoopRunnable::DispatchInternal() {
+ MOZ_ASSERT(mSyncLoopTarget);
+
+ RefPtr<MainThreadStopSyncLoopRunnable> runnable(this);
+ return NS_SUCCEEDED(
+ mSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL));
+}
+
+void MainThreadStopSyncLoopRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) {}
+
+#ifdef DEBUG
+WorkerControlRunnable::WorkerControlRunnable(WorkerPrivate* aWorkerPrivate,
+ TargetAndBusyBehavior aBehavior)
+ : WorkerRunnable(aWorkerPrivate, aBehavior) {
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aBehavior == ParentThreadUnchangedBusyCount ||
+ aBehavior == WorkerThreadUnchangedBusyCount,
+ "WorkerControlRunnables should not modify the busy count");
+}
+#endif
+
+nsresult WorkerControlRunnable::Cancel() {
+ // We need to check first if cancel is called twice
+ nsresult rv = WorkerRunnable::Cancel();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (NS_FAILED(Run())) {
+ NS_WARNING("WorkerControlRunnable::Run() failed.");
+ }
+
+ return NS_OK;
+}
+
+bool WorkerControlRunnable::DispatchInternal() {
+ RefPtr<WorkerControlRunnable> runnable(this);
+
+ if (mBehavior == WorkerThreadUnchangedBusyCount) {
+ return NS_SUCCEEDED(
+ mWorkerPrivate->DispatchControlRunnable(runnable.forget()));
+ }
+
+ if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) {
+ return NS_SUCCEEDED(parent->DispatchControlRunnable(runnable.forget()));
+ }
+
+ return NS_SUCCEEDED(mWorkerPrivate->DispatchToMainThread(runnable.forget()));
+}
+
+WorkerMainThreadRunnable::WorkerMainThreadRunnable(
+ WorkerPrivate* aWorkerPrivate, const nsACString& aTelemetryKey)
+ : mozilla::Runnable("dom::WorkerMainThreadRunnable"),
+ mWorkerPrivate(aWorkerPrivate),
+ mTelemetryKey(aTelemetryKey) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+}
+
+WorkerMainThreadRunnable::~WorkerMainThreadRunnable() = default;
+
+void WorkerMainThreadRunnable::Dispatch(WorkerStatus aFailStatus,
+ mozilla::ErrorResult& aRv) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ TimeStamp startTime = TimeStamp::NowLoRes();
+
+ AutoSyncLoopHolder syncLoop(mWorkerPrivate, aFailStatus);
+
+ mSyncLoopTarget = syncLoop.GetSerialEventTarget();
+ if (!mSyncLoopTarget) {
+ // SyncLoop creation can fail if the worker is shutting down.
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ DebugOnly<nsresult> rv = mWorkerPrivate->DispatchToMainThread(this);
+ MOZ_ASSERT(
+ NS_SUCCEEDED(rv),
+ "Should only fail after xpcom-shutdown-threads and we're gone by then");
+
+ bool success = NS_SUCCEEDED(syncLoop.Run());
+
+ Telemetry::Accumulate(
+ Telemetry::SYNC_WORKER_OPERATION, mTelemetryKey,
+ static_cast<uint32_t>(
+ (TimeStamp::NowLoRes() - startTime).ToMilliseconds()));
+
+ Unused << startTime; // Shut the compiler up.
+
+ if (!success) {
+ aRv.ThrowUncatchableException();
+ }
+}
+
+NS_IMETHODIMP
+WorkerMainThreadRunnable::Run() {
+ AssertIsOnMainThread();
+
+ // This shouldn't be necessary once we're better about making sure no workers
+ // are created during shutdown in earlier phases.
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads)) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ bool runResult = MainThreadRun();
+
+ RefPtr<MainThreadStopSyncLoopRunnable> response =
+ new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
+ std::move(mSyncLoopTarget),
+ runResult ? NS_OK : NS_ERROR_FAILURE);
+
+ MOZ_ALWAYS_TRUE(response->Dispatch());
+
+ return NS_OK;
+}
+
+bool WorkerSameThreadRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate) {
+ // We don't call WorkerRunnable::PreDispatch, because we're using
+ // WorkerThreadModifyBusyCount for mBehavior, and WorkerRunnable will assert
+ // that PreDispatch is on the parent thread in that case.
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ return true;
+}
+
+void WorkerSameThreadRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) {
+ // We don't call WorkerRunnable::PostDispatch, because we're using
+ // WorkerThreadModifyBusyCount for mBehavior, and WorkerRunnable will assert
+ // that PostDispatch is on the parent thread in that case.
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ if (aDispatchResult) {
+ DebugOnly<bool> willIncrement =
+ aWorkerPrivate->ModifyBusyCountFromWorker(true);
+ // Should never fail since if this thread is still running, so should the
+ // parent and it should be able to process a control runnable.
+ MOZ_ASSERT(willIncrement);
+ }
+}
+
+WorkerProxyToMainThreadRunnable::WorkerProxyToMainThreadRunnable()
+ : mozilla::Runnable("dom::WorkerProxyToMainThreadRunnable") {}
+
+WorkerProxyToMainThreadRunnable::~WorkerProxyToMainThreadRunnable() = default;
+
+bool WorkerProxyToMainThreadRunnable::Dispatch(WorkerPrivate* aWorkerPrivate) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
+ aWorkerPrivate, "WorkerProxyToMainThreadRunnable");
+ if (NS_WARN_IF(!workerRef)) {
+ RunBackOnWorkerThreadForCleanup(aWorkerPrivate);
+ return false;
+ }
+
+ MOZ_ASSERT(!mWorkerRef);
+ mWorkerRef = new ThreadSafeWorkerRef(workerRef);
+
+ if (ForMessaging()
+ ? NS_WARN_IF(NS_FAILED(
+ aWorkerPrivate->DispatchToMainThreadForMessaging(this)))
+ : NS_WARN_IF(NS_FAILED(aWorkerPrivate->DispatchToMainThread(this)))) {
+ ReleaseWorker();
+ RunBackOnWorkerThreadForCleanup(aWorkerPrivate);
+ return false;
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+WorkerProxyToMainThreadRunnable::Run() {
+ AssertIsOnMainThread();
+ RunOnMainThread(mWorkerRef->Private());
+ PostDispatchOnMainThread();
+ return NS_OK;
+}
+
+void WorkerProxyToMainThreadRunnable::PostDispatchOnMainThread() {
+ class ReleaseRunnable final : public MainThreadWorkerControlRunnable {
+ RefPtr<WorkerProxyToMainThreadRunnable> mRunnable;
+
+ public:
+ ReleaseRunnable(WorkerPrivate* aWorkerPrivate,
+ WorkerProxyToMainThreadRunnable* aRunnable)
+ : MainThreadWorkerControlRunnable(aWorkerPrivate),
+ mRunnable(aRunnable) {
+ MOZ_ASSERT(aRunnable);
+ }
+
+ // We must call RunBackOnWorkerThreadForCleanup() also if the runnable is
+ // canceled.
+ nsresult Cancel() override {
+ WorkerRun(nullptr, mWorkerPrivate);
+ return MainThreadWorkerControlRunnable::Cancel();
+ }
+
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mRunnable) {
+ mRunnable->RunBackOnWorkerThreadForCleanup(aWorkerPrivate);
+
+ // Let's release the worker thread.
+ mRunnable->ReleaseWorker();
+ mRunnable = nullptr;
+ }
+
+ return true;
+ }
+
+ private:
+ ~ReleaseRunnable() = default;
+ };
+
+ RefPtr<WorkerControlRunnable> runnable =
+ new ReleaseRunnable(mWorkerRef->Private(), this);
+ Unused << NS_WARN_IF(!runnable->Dispatch());
+}
+
+void WorkerProxyToMainThreadRunnable::ReleaseWorker() { mWorkerRef = nullptr; }
+
+bool WorkerDebuggeeRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate) {
+ if (mBehavior == ParentThreadUnchangedBusyCount) {
+ RefPtr<StrongWorkerRef> strongRef = StrongWorkerRef::Create(
+ aWorkerPrivate, "WorkerDebuggeeRunnable::mSender");
+ if (!strongRef) {
+ return false;
+ }
+
+ mSender = new ThreadSafeWorkerRef(strongRef);
+ }
+
+ return WorkerRunnable::PreDispatch(aWorkerPrivate);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/workers/WorkerRunnable.h b/dom/workers/WorkerRunnable.h
new file mode 100644
index 0000000000..34a90e3c39
--- /dev/null
+++ b/dom/workers/WorkerRunnable.h
@@ -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/. */
+
+#ifndef mozilla_dom_workers_workerrunnable_h__
+#define mozilla_dom_workers_workerrunnable_h__
+
+#include <cstdint>
+#include <utility>
+#include "MainThreadUtils.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerStatus.h"
+#include "nsCOMPtr.h"
+#include "nsICancelableRunnable.h"
+#include "nsIRunnable.h"
+#include "nsISupports.h"
+#include "nsStringFwd.h"
+#include "nsThreadUtils.h"
+#include "nscore.h"
+
+struct JSContext;
+class nsIEventTarget;
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class WorkerPrivate;
+
+// Use this runnable to communicate from the worker to its parent or vice-versa.
+// The busy count must be taken into consideration and declared at construction
+// time.
+class WorkerRunnable : public nsIRunnable, public nsICancelableRunnable {
+ public:
+ enum TargetAndBusyBehavior {
+ // Target the main thread for top-level workers, otherwise target the
+ // WorkerThread of the worker's parent. No change to the busy count.
+ ParentThreadUnchangedBusyCount,
+
+ // Target the thread where the worker event loop runs. The busy count will
+ // be incremented before dispatching and decremented (asynchronously) after
+ // running.
+ WorkerThreadModifyBusyCount,
+
+ // Target the thread where the worker event loop runs. The busy count will
+ // not be modified in any way. Besides worker-internal runnables this is
+ // almost always the wrong choice.
+ WorkerThreadUnchangedBusyCount
+ };
+
+ protected:
+ // The WorkerPrivate that this runnable is associated with.
+ WorkerPrivate* mWorkerPrivate;
+
+ // See above.
+ TargetAndBusyBehavior mBehavior;
+
+ // It's unclear whether or not Cancel() is supposed to work when called on any
+ // thread. To be safe we're using an atomic but it's likely overkill.
+ Atomic<uint32_t> mCanceled;
+
+ private:
+ // Whether or not Cancel() is currently being called from inside the Run()
+ // method. Avoids infinite recursion when a subclass calls Run() from inside
+ // Cancel(). Only checked and modified on the target thread.
+ bool mCallingCancelWithinRun;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // If you override Cancel() then you'll need to either call the base class
+ // Cancel() method or override IsCanceled() so that the Run() method bails out
+ // appropriately.
+ // Cancel() should not be called more than once and we throw
+ // NS_ERROR_UNEXPECTED if it is. If you override it, ensure to call the base
+ // class method first and bail out on failure to avoid unexpected side
+ // effects.
+ nsresult Cancel() override;
+
+ // The return value is true if and only if both PreDispatch and
+ // DispatchInternal return true.
+ bool Dispatch();
+
+ // See above note about Cancel().
+ // TODO: Check if we can remove the possibility to override IsCanceled.
+ virtual bool IsCanceled() const { return mCanceled != 0; }
+
+ // True if this runnable is handled by running JavaScript in some global that
+ // could possibly be a debuggee, and thus needs to be deferred when the target
+ // is paused in the debugger, until the JavaScript invocation in progress has
+ // run to completion. Examples are MessageEventRunnable and
+ // ReportErrorRunnable. These runnables are segregated into separate
+ // ThrottledEventQueues, which the debugger pauses.
+ //
+ // Note that debugger runnables do not fall in this category, since we don't
+ // support debugging the debugger server at the moment.
+ virtual bool IsDebuggeeRunnable() const { return false; }
+
+ static WorkerRunnable* FromRunnable(nsIRunnable* aRunnable);
+
+ protected:
+ WorkerRunnable(WorkerPrivate* aWorkerPrivate,
+ TargetAndBusyBehavior aBehavior = WorkerThreadModifyBusyCount)
+#ifdef DEBUG
+ ;
+#else
+ : mWorkerPrivate(aWorkerPrivate),
+ mBehavior(aBehavior),
+ mCanceled(0),
+ mCallingCancelWithinRun(false) {
+ }
+#endif
+
+ // This class is reference counted.
+ virtual ~WorkerRunnable() = default;
+
+ // Returns true if this runnable should be dispatched to the debugger queue,
+ // and false otherwise.
+ virtual bool IsDebuggerRunnable() const;
+
+ nsIGlobalObject* DefaultGlobalObject() const;
+
+ // By default asserts that Dispatch() is being called on the right thread
+ // (ParentThread if |mTarget| is WorkerThread, or WorkerThread otherwise).
+ // Also increments the busy count of |mWorkerPrivate| if targeting the
+ // WorkerThread.
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate);
+
+ // By default asserts that Dispatch() is being called on the right thread
+ // (ParentThread if |mTarget| is WorkerThread, or WorkerThread otherwise).
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult);
+
+ // May be implemented by subclasses if desired if they need to do some sort of
+ // setup before we try to set up our JSContext and compartment for real.
+ // Typically the only thing that should go in here is creation of the worker's
+ // global.
+ //
+ // If false is returned, WorkerRun will not be called at all. PostRun will
+ // still be called, with false passed for aRunResult.
+ virtual bool PreRun(WorkerPrivate* aWorkerPrivate);
+
+ // Must be implemented by subclasses. Called on the target thread. The return
+ // value will be passed to PostRun(). The JSContext passed in here comes from
+ // an AutoJSAPI (or AutoEntryScript) that we set up on the stack. If
+ // mBehavior is ParentThreadUnchangedBusyCount, it is in the compartment of
+ // mWorkerPrivate's reflector (i.e. the worker object in the parent thread),
+ // unless that reflector is null, in which case it's in the compartment of the
+ // parent global (which is the compartment reflector would have been in), or
+ // in the null compartment if there is no parent global. For other mBehavior
+ // values, we're running on the worker thread and aCx is in whatever
+ // compartment GetCurrentWorkerThreadJSContext() was in when
+ // nsIRunnable::Run() got called. This is actually important for cases when a
+ // runnable spins a syncloop and wants everything that happens during the
+ // syncloop to happen in the compartment that runnable set up (which may, for
+ // example, be a debugger sandbox compartment!). If aCx wasn't in a
+ // compartment to start with, aCx will be in either the debugger global's
+ // compartment or the worker's global's compartment depending on whether
+ // IsDebuggerRunnable() is true.
+ //
+ // Immediately after WorkerRun returns, the caller will assert that either it
+ // returns false or there is no exception pending on aCx. Then it will report
+ // any pending exceptions on aCx.
+ virtual bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) = 0;
+
+ // By default asserts that Run() (and WorkerRun()) were called on the correct
+ // thread. Also sends an asynchronous message to the ParentThread if the
+ // busy count was previously modified in PreDispatch().
+ //
+ // The aCx passed here is the same one as was passed to WorkerRun and is
+ // still in the same compartment. PostRun implementations must NOT leave an
+ // exception on the JSContext and must not run script, because the incoming
+ // JSContext may be in the null compartment.
+ virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult);
+
+ virtual bool DispatchInternal();
+
+ // Calling Run() directly is not supported. Just call Dispatch() and
+ // WorkerRun() will be called on the correct thread automatically.
+ NS_DECL_NSIRUNNABLE
+};
+
+// This runnable is used to send a message to a worker debugger.
+class WorkerDebuggerRunnable : public WorkerRunnable {
+ protected:
+ explicit WorkerDebuggerRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
+
+ virtual ~WorkerDebuggerRunnable() = default;
+
+ private:
+ virtual bool IsDebuggerRunnable() const override { return true; }
+
+ bool PreDispatch(WorkerPrivate* aWorkerPrivate) final {
+ AssertIsOnMainThread();
+
+ return true;
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override;
+};
+
+// This runnable is used to send a message directly to a worker's sync loop.
+class WorkerSyncRunnable : public WorkerRunnable {
+ protected:
+ nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
+
+ // Passing null for aSyncLoopTarget is allowed and will result in the behavior
+ // of a normal WorkerRunnable.
+ WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+ nsIEventTarget* aSyncLoopTarget);
+
+ WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+ nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget);
+
+ virtual ~WorkerSyncRunnable();
+
+ virtual bool DispatchInternal() override;
+};
+
+// This runnable is identical to WorkerSyncRunnable except it is meant to be
+// created on and dispatched from the main thread only. Its WorkerRun/PostRun
+// will run on the worker thread.
+class MainThreadWorkerSyncRunnable : public WorkerSyncRunnable {
+ protected:
+ // Passing null for aSyncLoopTarget is allowed and will result in the behavior
+ // of a normal WorkerRunnable.
+ MainThreadWorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+ nsIEventTarget* aSyncLoopTarget)
+ : WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget) {
+ AssertIsOnMainThread();
+ }
+
+ MainThreadWorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+ nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget)
+ : WorkerSyncRunnable(aWorkerPrivate, std::move(aSyncLoopTarget)) {
+ AssertIsOnMainThread();
+ }
+
+ virtual ~MainThreadWorkerSyncRunnable() = default;
+
+ private:
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
+ AssertIsOnMainThread();
+ return true;
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override;
+};
+
+// This runnable is processed as soon as it is received by the worker,
+// potentially running before previously queued runnables and perhaps even with
+// other JS code executing on the stack. These runnables must not alter the
+// state of the JS runtime and should only twiddle state values. The busy count
+// is never modified.
+class WorkerControlRunnable : public WorkerRunnable {
+ friend class WorkerPrivate;
+
+ protected:
+ WorkerControlRunnable(WorkerPrivate* aWorkerPrivate,
+ TargetAndBusyBehavior aBehavior)
+#ifdef DEBUG
+ ;
+#else
+ : WorkerRunnable(aWorkerPrivate, aBehavior) {
+ }
+#endif
+
+ virtual ~WorkerControlRunnable() = default;
+
+ nsresult Cancel() override;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerControlRunnable, WorkerRunnable)
+
+ private:
+ virtual bool DispatchInternal() override;
+
+ // Should only be called by WorkerPrivate::DoRunLoop.
+ using WorkerRunnable::Cancel;
+};
+
+// A convenience class for WorkerRunnables that are originated on the main
+// thread.
+class MainThreadWorkerRunnable : public WorkerRunnable {
+ protected:
+ explicit MainThreadWorkerRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {
+ AssertIsOnMainThread();
+ }
+
+ virtual ~MainThreadWorkerRunnable() = default;
+
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
+ AssertIsOnMainThread();
+ return true;
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {
+ AssertIsOnMainThread();
+ }
+};
+
+// A convenience class for WorkerControlRunnables that originate on the main
+// thread.
+class MainThreadWorkerControlRunnable : public WorkerControlRunnable {
+ protected:
+ explicit MainThreadWorkerControlRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
+
+ virtual ~MainThreadWorkerControlRunnable() = default;
+
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
+ AssertIsOnMainThread();
+ return true;
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {
+ AssertIsOnMainThread();
+ }
+};
+
+// A WorkerRunnable that should be dispatched from the worker to itself for
+// async tasks. This will increment the busy count PostDispatch() (only if
+// dispatch was successful) and decrement it in PostRun().
+//
+// Async tasks will almost always want to use this since
+// a WorkerSameThreadRunnable keeps the Worker from being GCed.
+class WorkerSameThreadRunnable : public WorkerRunnable {
+ protected:
+ explicit WorkerSameThreadRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount) {}
+
+ virtual ~WorkerSameThreadRunnable() = default;
+
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override;
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override;
+
+ // We just delegate PostRun to WorkerRunnable, since it does exactly
+ // what we want.
+};
+
+// Base class for the runnable objects, which makes a synchronous call to
+// dispatch the tasks from the worker thread to the main thread.
+//
+// Note that the derived class must override MainThreadRun.
+class WorkerMainThreadRunnable : public Runnable {
+ protected:
+ WorkerPrivate* mWorkerPrivate;
+ nsCOMPtr<nsISerialEventTarget> mSyncLoopTarget;
+ const nsCString mTelemetryKey;
+
+ explicit WorkerMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsACString& aTelemetryKey);
+ ~WorkerMainThreadRunnable();
+
+ virtual bool MainThreadRun() = 0;
+
+ public:
+ // Dispatch the runnable to the main thread. If dispatch to main thread
+ // fails, or if the worker is in a state equal or greater of aFailStatus, an
+ // error will be reported on aRv. Normally you want to use 'Canceling' for
+ // aFailStatus, except if you want an infallible runnable. In this case, use
+ // 'Killing'.
+ // In that case the error MUST be propagated out to script.
+ void Dispatch(WorkerStatus aFailStatus, ErrorResult& aRv);
+
+ private:
+ NS_IMETHOD Run() override;
+};
+
+// This runnable is an helper class for dispatching something from a worker
+// thread to the main-thread and back to the worker-thread. During this
+// operation, this class will keep the worker alive.
+// The purpose of RunBackOnWorkerThreadForCleanup() must be used, as the name
+// says, only to release resources, no JS has to be executed, no timers, or
+// other things. The reason of such limitations is that, in order to execute
+// this method in any condition (also when the worker is shutting down), a
+// Control Runnable is used, and, this could generate a reordering of existing
+// runnables.
+class WorkerProxyToMainThreadRunnable : public Runnable {
+ protected:
+ WorkerProxyToMainThreadRunnable();
+
+ virtual ~WorkerProxyToMainThreadRunnable();
+
+ // First this method is called on the main-thread.
+ virtual void RunOnMainThread(WorkerPrivate* aWorkerPrivate) = 0;
+
+ // After this second method is called on the worker-thread.
+ virtual void RunBackOnWorkerThreadForCleanup(
+ WorkerPrivate* aWorkerPrivate) = 0;
+
+ public:
+ bool Dispatch(WorkerPrivate* aWorkerPrivate);
+
+ virtual bool ForMessaging() const { return false; }
+
+ private:
+ NS_IMETHOD Run() override;
+
+ void PostDispatchOnMainThread();
+
+ void ReleaseWorker();
+
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+};
+
+// This runnable is used to stop a sync loop and it's meant to be used on the
+// main-thread only. As sync loops keep the busy count incremented as long as
+// they run this runnable does not modify the busy count
+// in any way.
+class MainThreadStopSyncLoopRunnable : public WorkerSyncRunnable {
+ nsresult mResult;
+
+ public:
+ // Passing null for aSyncLoopTarget is not allowed.
+ MainThreadStopSyncLoopRunnable(WorkerPrivate* aWorkerPrivate,
+ nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget,
+ nsresult aResult);
+
+ // By default StopSyncLoopRunnables cannot be canceled since they could leave
+ // a sync loop spinning forever.
+ nsresult Cancel() override;
+
+ protected:
+ virtual ~MainThreadStopSyncLoopRunnable() = default;
+
+ private:
+ bool PreDispatch(WorkerPrivate* aWorkerPrivate) final {
+ AssertIsOnMainThread();
+ return true;
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override;
+
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override;
+
+ bool DispatchInternal() final;
+};
+
+// Runnables handled by content JavaScript (MessageEventRunnable, JavaScript
+// error reports, and so on) must not be delivered while that content is in the
+// midst of being debugged; the debuggee must be allowed to complete its current
+// JavaScript invocation and return to its own event loop. Only then is it
+// prepared for messages sent from the worker.
+//
+// Runnables that need to be deferred in this way should inherit from this
+// class. They will be routed to mMainThreadDebuggeeEventTarget, which is paused
+// while the window is suspended, as it is whenever the debugger spins its
+// nested event loop. When the debugger leaves its nested event loop, it resumes
+// the window, so that mMainThreadDebuggeeEventTarget will resume delivering
+// runnables from the worker when control returns to the main event loop.
+//
+// When a page enters the bfcache, it freezes all its workers. Since a frozen
+// worker processes only control runnables, it doesn't take any special
+// consideration to prevent WorkerDebuggeeRunnables sent from child to parent
+// workers from running; they'll never run anyway. But WorkerDebuggeeRunnables
+// from a top-level frozen worker to its parent window must not be delivered
+// either, even as the main thread event loop continues to spin. Thus, freezing
+// a top-level worker also pauses mMainThreadDebuggeeEventTarget.
+class WorkerDebuggeeRunnable : public WorkerRunnable {
+ protected:
+ WorkerDebuggeeRunnable(
+ WorkerPrivate* aWorkerPrivate,
+ TargetAndBusyBehavior aBehavior = ParentThreadUnchangedBusyCount)
+ : WorkerRunnable(aWorkerPrivate, aBehavior) {}
+
+ bool PreDispatch(WorkerPrivate* aWorkerPrivate) override;
+
+ private:
+ // This override is deliberately private: it doesn't make sense to call it if
+ // we know statically that we are a WorkerDebuggeeRunnable.
+ bool IsDebuggeeRunnable() const override { return true; }
+
+ // Runnables sent upwards, to the content window or parent worker, must keep
+ // their sender alive until they are delivered: they check back with the
+ // sender in case it has been terminated after having dispatched the runnable
+ // (in which case it should not be acted upon); and runnables sent to content
+ // wait until delivery to determine the target window, since
+ // WorkerPrivate::GetWindow may only be used on the main thread.
+ //
+ // Runnables sent downwards, from content to a worker or from a worker to a
+ // child, keep the sender alive because they are WorkerThreadModifyBusyCount
+ // runnables, and so leave this null.
+ RefPtr<ThreadSafeWorkerRef> mSender;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_workerrunnable_h__
diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp
new file mode 100644
index 0000000000..8a592e4065
--- /dev/null
+++ b/dom/workers/WorkerScope.cpp
@@ -0,0 +1,1414 @@
+/* -*- 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/WorkerScope.h"
+
+#include <stdio.h>
+#include <new>
+#include <utility>
+#include "Crypto.h"
+#include "GeckoProfiler.h"
+#include "MainThreadUtils.h"
+#include "ScriptLoader.h"
+#include "js/CompilationAndEvaluation.h"
+#include "js/CompileOptions.h"
+#include "js/RealmOptions.h"
+#include "js/RootingAPI.h"
+#include "js/SourceText.h"
+#include "js/Value.h"
+#include "js/Wrapper.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/BaseProfilerMarkersPrerequisites.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Result.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/TaskCategory.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/CSPEvalChecker.h"
+#include "mozilla/dom/CallbackDebuggerNotification.h"
+#include "mozilla/dom/ClientSource.h"
+#include "mozilla/dom/Clients.h"
+#include "mozilla/dom/Console.h"
+#include "mozilla/dom/DOMMozPromiseRequestHolder.h"
+#include "mozilla/dom/DebuggerNotification.h"
+#include "mozilla/dom/DebuggerNotificationBinding.h"
+#include "mozilla/dom/DebuggerNotificationManager.h"
+#include "mozilla/dom/DedicatedWorkerGlobalScopeBinding.h"
+#include "mozilla/dom/DOMString.h"
+#include "mozilla/dom/Fetch.h"
+#include "mozilla/dom/FontFaceSet.h"
+#include "mozilla/dom/IDBFactory.h"
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/ImageBitmapSource.h"
+#include "mozilla/dom/MessagePortBinding.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseWorkerProxy.h"
+#include "mozilla/dom/WebTaskSchedulerWorker.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/SerializedStackHolder.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
+#include "mozilla/dom/ServiceWorkerManager.h"
+#include "mozilla/dom/ServiceWorkerRegistration.h"
+#include "mozilla/dom/ServiceWorkerRegistrationDescriptor.h"
+#include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/dom/SharedWorkerGlobalScopeBinding.h"
+#include "mozilla/dom/SimpleGlobalObject.h"
+#include "mozilla/dom/TimeoutHandler.h"
+#include "mozilla/dom/TestUtils.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
+#include "mozilla/dom/WorkerGlobalScopeBinding.h"
+#include "mozilla/dom/WorkerLocation.h"
+#include "mozilla/dom/WorkerNavigator.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/WorkerDocumentListener.h"
+#include "mozilla/dom/VsyncWorkerChild.h"
+#include "mozilla/dom/cache/CacheStorage.h"
+#include "mozilla/dom/cache/Types.h"
+#include "mozilla/extensions/ExtensionBrowser.h"
+#include "mozilla/fallible.h"
+#include "mozilla/gfx/Rect.h"
+#include "nsAtom.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsGkAtoms.h"
+#include "nsIEventTarget.h"
+#include "nsIGlobalObject.h"
+#include "nsIScriptError.h"
+#include "nsISerialEventTarget.h"
+#include "nsIWeakReference.h"
+#include "nsJSUtils.h"
+#include "nsLiteralString.h"
+#include "nsQueryObject.h"
+#include "nsReadableUtils.h"
+#include "nsRFPService.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsTLiteralString.h"
+#include "nsThreadUtils.h"
+#include "nsWeakReference.h"
+#include "nsWrapperCacheInlines.h"
+#include "nscore.h"
+#include "xpcpublic.h"
+
+#ifdef ANDROID
+# include <android/log.h>
+#endif
+
+#ifdef XP_WIN
+# undef PostMessage
+#endif
+
+using mozilla::dom::cache::CacheStorage;
+using mozilla::dom::workerinternals::NamedWorkerGlobalScopeMixin;
+using mozilla::ipc::BackgroundChild;
+using mozilla::ipc::PBackgroundChild;
+using mozilla::ipc::PrincipalInfo;
+
+namespace mozilla::dom {
+
+class WorkerScriptTimeoutHandler final : public ScriptTimeoutHandler {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WorkerScriptTimeoutHandler,
+ ScriptTimeoutHandler)
+
+ WorkerScriptTimeoutHandler(JSContext* aCx, nsIGlobalObject* aGlobal,
+ const nsAString& aExpression)
+ : ScriptTimeoutHandler(aCx, aGlobal, aExpression) {}
+
+ MOZ_CAN_RUN_SCRIPT virtual bool Call(const char* aExecutionReason) override;
+
+ private:
+ virtual ~WorkerScriptTimeoutHandler() = default;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(WorkerScriptTimeoutHandler,
+ ScriptTimeoutHandler)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerScriptTimeoutHandler)
+NS_INTERFACE_MAP_END_INHERITING(ScriptTimeoutHandler)
+
+NS_IMPL_ADDREF_INHERITED(WorkerScriptTimeoutHandler, ScriptTimeoutHandler)
+NS_IMPL_RELEASE_INHERITED(WorkerScriptTimeoutHandler, ScriptTimeoutHandler)
+
+bool WorkerScriptTimeoutHandler::Call(const char* aExecutionReason) {
+ nsAutoMicroTask mt;
+ AutoEntryScript aes(mGlobal, aExecutionReason, false);
+
+ JSContext* cx = aes.cx();
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(mFileName.get(), mLineNo).setNoScriptRval(true);
+ options.setIntroductionType("domTimer");
+
+ JS::Rooted<JS::Value> unused(cx);
+ JS::SourceText<char16_t> srcBuf;
+ if (!srcBuf.init(cx, mExpr.BeginReading(), mExpr.Length(),
+ JS::SourceOwnership::Borrowed) ||
+ !JS::Evaluate(cx, options, srcBuf, &unused)) {
+ if (!JS_IsExceptionPending(cx)) {
+ return false;
+ }
+ }
+
+ return true;
+};
+
+namespace workerinternals {
+void NamedWorkerGlobalScopeMixin::GetName(DOMString& aName) const {
+ aName.AsAString() = mName;
+}
+} // namespace workerinternals
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(WorkerGlobalScopeBase)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WorkerGlobalScopeBase,
+ DOMEventTargetHelper)
+ tmp->AssertIsOnWorkerThread();
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mModuleLoader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSerialEventTarget)
+ tmp->TraverseObjectsInGlobal(cb);
+ // If we already exited WorkerThreadPrimaryRunnable, we will find it
+ // nullptr and there is nothing left to do here on the WorkerPrivate,
+ // in particular the timeouts have already been canceled and unlinked.
+ if (tmp->mWorkerPrivate) {
+ tmp->mWorkerPrivate->TraverseTimeouts(cb);
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WorkerGlobalScopeBase,
+ DOMEventTargetHelper)
+ tmp->AssertIsOnWorkerThread();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mModuleLoader)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSerialEventTarget)
+ tmp->UnlinkObjectsInGlobal();
+ // If we already exited WorkerThreadPrimaryRunnable, we will find it
+ // nullptr and there is nothing left to do here on the WorkerPrivate,
+ // in particular the timeouts have already been canceled and unlinked.
+ if (tmp->mWorkerPrivate) {
+ tmp->mWorkerPrivate->UnlinkTimeouts();
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WorkerGlobalScopeBase,
+ DOMEventTargetHelper)
+ tmp->AssertIsOnWorkerThread();
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_ADDREF_INHERITED(WorkerGlobalScopeBase, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(WorkerGlobalScopeBase, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerGlobalScopeBase)
+ NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+WorkerGlobalScopeBase::WorkerGlobalScopeBase(
+ WorkerPrivate* aWorkerPrivate, UniquePtr<ClientSource> aClientSource,
+ bool aShouldResistFingerprinting)
+ : mWorkerPrivate(aWorkerPrivate),
+ mClientSource(std::move(aClientSource)),
+ mSerialEventTarget(aWorkerPrivate->HybridEventTarget()),
+ mShouldResistFingerprinting(aShouldResistFingerprinting) {
+ MOZ_ASSERT(mWorkerPrivate);
+#ifdef DEBUG
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ mWorkerThreadUsedOnlyForAssert = PR_GetCurrentThread();
+#endif
+ MOZ_ASSERT(mClientSource);
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ mSerialEventTarget,
+ "There should be an event target when a worker global is created.");
+
+ // In workers, each DETH must have an owner. Because the global scope doesn't
+ // have one, let's set it as owner of itself.
+ BindToOwner(static_cast<nsIGlobalObject*>(this));
+}
+
+WorkerGlobalScopeBase::~WorkerGlobalScopeBase() = default;
+
+JSObject* WorkerGlobalScopeBase::GetGlobalJSObject() {
+ AssertIsOnWorkerThread();
+ return GetWrapper();
+}
+
+JSObject* WorkerGlobalScopeBase::GetGlobalJSObjectPreserveColor() const {
+ AssertIsOnWorkerThread();
+ return GetWrapperPreserveColor();
+}
+
+bool WorkerGlobalScopeBase::IsSharedMemoryAllowed() const {
+ AssertIsOnWorkerThread();
+ return mWorkerPrivate->IsSharedMemoryAllowed();
+}
+
+bool WorkerGlobalScopeBase::ShouldResistFingerprinting(
+ RFPTarget aTarget) const {
+ AssertIsOnWorkerThread();
+ return mShouldResistFingerprinting && nsRFPService::IsRFPEnabledFor(aTarget);
+}
+
+OriginTrials WorkerGlobalScopeBase::Trials() const {
+ AssertIsOnWorkerThread();
+ return mWorkerPrivate->Trials();
+}
+
+StorageAccess WorkerGlobalScopeBase::GetStorageAccess() {
+ AssertIsOnWorkerThread();
+ return mWorkerPrivate->StorageAccess();
+}
+
+Maybe<ClientInfo> WorkerGlobalScopeBase::GetClientInfo() const {
+ return Some(mClientSource->Info());
+}
+
+Maybe<ServiceWorkerDescriptor> WorkerGlobalScopeBase::GetController() const {
+ return mClientSource->GetController();
+}
+
+mozilla::Result<mozilla::ipc::PrincipalInfo, nsresult>
+WorkerGlobalScopeBase::GetStorageKey() {
+ AssertIsOnWorkerThread();
+
+ const mozilla::ipc::PrincipalInfo& principalInfo =
+ mWorkerPrivate->GetEffectiveStoragePrincipalInfo();
+
+ // Block expanded and null principals, let content and system through.
+ if (principalInfo.type() !=
+ mozilla::ipc::PrincipalInfo::TContentPrincipalInfo &&
+ principalInfo.type() !=
+ mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo) {
+ return Err(NS_ERROR_DOM_SECURITY_ERR);
+ }
+
+ return principalInfo;
+}
+
+void WorkerGlobalScopeBase::Control(
+ const ServiceWorkerDescriptor& aServiceWorker) {
+ AssertIsOnWorkerThread();
+ MOZ_DIAGNOSTIC_ASSERT(!mWorkerPrivate->IsChromeWorker());
+ MOZ_DIAGNOSTIC_ASSERT(mWorkerPrivate->Kind() != WorkerKindService);
+
+ if (IsBlobURI(mWorkerPrivate->GetBaseURI())) {
+ // Blob URL workers can only become controlled by inheriting from
+ // their parent. Make sure to note this properly.
+ mClientSource->InheritController(aServiceWorker);
+ } else {
+ // Otherwise this is a normal interception and we simply record the
+ // controller locally.
+ mClientSource->SetController(aServiceWorker);
+ }
+}
+
+nsresult WorkerGlobalScopeBase::Dispatch(
+ TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable) {
+ return EventTargetFor(aCategory)->Dispatch(std::move(aRunnable),
+ NS_DISPATCH_NORMAL);
+}
+
+nsISerialEventTarget* WorkerGlobalScopeBase::EventTargetFor(
+ TaskCategory) const {
+ AssertIsOnWorkerThread();
+ return mSerialEventTarget;
+}
+
+// See also AutoJSAPI::ReportException
+void WorkerGlobalScopeBase::ReportError(JSContext* aCx,
+ JS::Handle<JS::Value> aError,
+ CallerType, ErrorResult& aRv) {
+ JS::ErrorReportBuilder jsReport(aCx);
+ JS::ExceptionStack exnStack(aCx, aError, nullptr);
+ if (!jsReport.init(aCx, exnStack, JS::ErrorReportBuilder::NoSideEffects)) {
+ return aRv.NoteJSContextException(aCx);
+ }
+
+ // Before invoking ReportError, put the exception back on the context,
+ // because it may want to put it in its error events and has no other way
+ // to get hold of it. After we invoke ReportError, clear the exception on
+ // cx(), just in case ReportError didn't.
+ JS::SetPendingExceptionStack(aCx, exnStack);
+ mWorkerPrivate->ReportError(aCx, jsReport.toStringResult(),
+ jsReport.report());
+ JS_ClearPendingException(aCx);
+}
+
+void WorkerGlobalScopeBase::Atob(const nsAString& aAtob, nsAString& aOut,
+ ErrorResult& aRv) const {
+ AssertIsOnWorkerThread();
+ aRv = nsContentUtils::Atob(aAtob, aOut);
+}
+
+void WorkerGlobalScopeBase::Btoa(const nsAString& aBtoa, nsAString& aOut,
+ ErrorResult& aRv) const {
+ AssertIsOnWorkerThread();
+ aRv = nsContentUtils::Btoa(aBtoa, aOut);
+}
+
+already_AddRefed<Console> WorkerGlobalScopeBase::GetConsole(ErrorResult& aRv) {
+ AssertIsOnWorkerThread();
+
+ if (!mConsole) {
+ mConsole = Console::Create(mWorkerPrivate->GetJSContext(), nullptr, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ RefPtr<Console> console = mConsole;
+ return console.forget();
+}
+
+uint64_t WorkerGlobalScopeBase::WindowID() const {
+ return mWorkerPrivate->WindowID();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(WorkerGlobalScope)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WorkerGlobalScope,
+ WorkerGlobalScopeBase)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrypto)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebTaskScheduler)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocation)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheStorage)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDebuggerNotificationManager)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WorkerGlobalScope,
+ WorkerGlobalScopeBase)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance)
+ if (tmp->mWebTaskScheduler) {
+ tmp->mWebTaskScheduler->Disconnect();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWebTaskScheduler)
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheStorage)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDebuggerNotificationManager)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(WorkerGlobalScope,
+ WorkerGlobalScopeBase)
+
+WorkerGlobalScope::~WorkerGlobalScope() = default;
+
+void WorkerGlobalScope::NoteTerminating() {
+ if (IsDying()) {
+ return;
+ }
+
+ StartDying();
+}
+
+void WorkerGlobalScope::NoteShuttingDown() {
+ MOZ_ASSERT(IsDying());
+
+ if (mNavigator) {
+ mNavigator->Invalidate();
+ mNavigator = nullptr;
+ }
+
+ if (mPerformance) {
+ RefPtr<PerformanceWorker> pw =
+ static_cast<PerformanceWorker*>(mPerformance.get());
+ MOZ_ASSERT(pw);
+ pw->NoteShuttingDown();
+ }
+}
+
+Crypto* WorkerGlobalScope::GetCrypto(ErrorResult& aError) {
+ AssertIsOnWorkerThread();
+
+ if (!mCrypto) {
+ mCrypto = new Crypto(this);
+ }
+
+ return mCrypto;
+}
+
+already_AddRefed<CacheStorage> WorkerGlobalScope::GetCaches(ErrorResult& aRv) {
+ if (!mCacheStorage) {
+ mCacheStorage = CacheStorage::CreateOnWorker(cache::DEFAULT_NAMESPACE, this,
+ mWorkerPrivate, aRv);
+ }
+
+ RefPtr<CacheStorage> ref = mCacheStorage;
+ return ref.forget();
+}
+
+bool WorkerGlobalScope::IsSecureContext() const {
+ bool globalSecure = JS::GetIsSecureContext(
+ js::GetNonCCWObjectRealm(GetWrapperPreserveColor()));
+ MOZ_ASSERT(globalSecure == mWorkerPrivate->IsSecureContext());
+ return globalSecure;
+}
+
+already_AddRefed<WorkerLocation> WorkerGlobalScope::Location() {
+ AssertIsOnWorkerThread();
+
+ if (!mLocation) {
+ mLocation = WorkerLocation::Create(mWorkerPrivate->GetLocationInfo());
+ MOZ_ASSERT(mLocation);
+ }
+
+ RefPtr<WorkerLocation> location = mLocation;
+ return location.forget();
+}
+
+already_AddRefed<WorkerNavigator> WorkerGlobalScope::Navigator() {
+ AssertIsOnWorkerThread();
+
+ if (!mNavigator) {
+ mNavigator = WorkerNavigator::Create(mWorkerPrivate->OnLine());
+ MOZ_ASSERT(mNavigator);
+ }
+
+ RefPtr<WorkerNavigator> navigator = mNavigator;
+ return navigator.forget();
+}
+
+already_AddRefed<WorkerNavigator> WorkerGlobalScope::GetExistingNavigator()
+ const {
+ AssertIsOnWorkerThread();
+
+ RefPtr<WorkerNavigator> navigator = mNavigator;
+ return navigator.forget();
+}
+
+FontFaceSet* WorkerGlobalScope::GetFonts(ErrorResult& aRv) {
+ AssertIsOnWorkerThread();
+
+ if (!mFontFaceSet) {
+ mFontFaceSet = FontFaceSet::CreateForWorker(this, mWorkerPrivate);
+ if (MOZ_UNLIKELY(!mFontFaceSet)) {
+ aRv.ThrowInvalidStateError("Couldn't acquire worker reference");
+ return nullptr;
+ }
+ }
+
+ return mFontFaceSet;
+}
+
+OnErrorEventHandlerNonNull* WorkerGlobalScope::GetOnerror() {
+ AssertIsOnWorkerThread();
+
+ EventListenerManager* elm = GetExistingListenerManager();
+ return elm ? elm->GetOnErrorEventHandler() : nullptr;
+}
+
+void WorkerGlobalScope::SetOnerror(OnErrorEventHandlerNonNull* aHandler) {
+ AssertIsOnWorkerThread();
+
+ EventListenerManager* elm = GetOrCreateListenerManager();
+ if (elm) {
+ elm->SetEventHandler(aHandler);
+ }
+}
+
+void WorkerGlobalScope::ImportScripts(JSContext* aCx,
+ const Sequence<nsString>& aScriptURLs,
+ ErrorResult& aRv) {
+ AssertIsOnWorkerThread();
+
+ UniquePtr<SerializedStackHolder> stack;
+ if (mWorkerPrivate->IsWatchedByDevTools()) {
+ stack = GetCurrentStackForNetMonitor(aCx);
+ }
+
+ {
+ AUTO_PROFILER_MARKER_TEXT(
+ "ImportScripts", JS, MarkerStack::Capture(),
+ profiler_thread_is_being_profiled_for_markers()
+ ? StringJoin(","_ns, aScriptURLs,
+ [](nsACString& dest, const auto& scriptUrl) {
+ AppendUTF16toUTF8(
+ Substring(
+ scriptUrl, 0,
+ std::min(size_t(128), scriptUrl.Length())),
+ dest);
+ })
+ : nsAutoCString{});
+ workerinternals::Load(mWorkerPrivate, std::move(stack), aScriptURLs,
+ WorkerScript, aRv);
+ }
+}
+
+int32_t WorkerGlobalScope::SetTimeout(JSContext* aCx, Function& aHandler,
+ const int32_t aTimeout,
+ const Sequence<JS::Value>& aArguments,
+ ErrorResult& aRv) {
+ return SetTimeoutOrInterval(aCx, aHandler, aTimeout, aArguments, false, aRv);
+}
+
+int32_t WorkerGlobalScope::SetTimeout(JSContext* aCx, const nsAString& aHandler,
+ const int32_t aTimeout,
+ const Sequence<JS::Value>& /* unused */,
+ ErrorResult& aRv) {
+ return SetTimeoutOrInterval(aCx, aHandler, aTimeout, false, aRv);
+}
+
+void WorkerGlobalScope::ClearTimeout(int32_t aHandle) {
+ AssertIsOnWorkerThread();
+
+ DebuggerNotificationDispatch(this, DebuggerNotificationType::ClearTimeout);
+
+ mWorkerPrivate->ClearTimeout(aHandle, Timeout::Reason::eTimeoutOrInterval);
+}
+
+int32_t WorkerGlobalScope::SetInterval(JSContext* aCx, Function& aHandler,
+ const int32_t aTimeout,
+ const Sequence<JS::Value>& aArguments,
+ ErrorResult& aRv) {
+ return SetTimeoutOrInterval(aCx, aHandler, aTimeout, aArguments, true, aRv);
+}
+
+int32_t WorkerGlobalScope::SetInterval(JSContext* aCx,
+ const nsAString& aHandler,
+ const int32_t aTimeout,
+ const Sequence<JS::Value>& /* unused */,
+ ErrorResult& aRv) {
+ return SetTimeoutOrInterval(aCx, aHandler, aTimeout, true, aRv);
+}
+
+void WorkerGlobalScope::ClearInterval(int32_t aHandle) {
+ AssertIsOnWorkerThread();
+
+ DebuggerNotificationDispatch(this, DebuggerNotificationType::ClearInterval);
+
+ mWorkerPrivate->ClearTimeout(aHandle, Timeout::Reason::eTimeoutOrInterval);
+}
+
+int32_t WorkerGlobalScope::SetTimeoutOrInterval(
+ JSContext* aCx, Function& aHandler, const int32_t aTimeout,
+ const Sequence<JS::Value>& aArguments, bool aIsInterval, ErrorResult& aRv) {
+ AssertIsOnWorkerThread();
+
+ DebuggerNotificationDispatch(
+ this, aIsInterval ? DebuggerNotificationType::SetInterval
+ : DebuggerNotificationType::SetTimeout);
+
+ nsTArray<JS::Heap<JS::Value>> args;
+ if (!args.AppendElements(aArguments, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return 0;
+ }
+
+ RefPtr<TimeoutHandler> handler =
+ new CallbackTimeoutHandler(aCx, this, &aHandler, std::move(args));
+
+ return mWorkerPrivate->SetTimeout(aCx, handler, aTimeout, aIsInterval,
+ Timeout::Reason::eTimeoutOrInterval, aRv);
+}
+
+int32_t WorkerGlobalScope::SetTimeoutOrInterval(JSContext* aCx,
+ const nsAString& aHandler,
+ const int32_t aTimeout,
+ bool aIsInterval,
+ ErrorResult& aRv) {
+ AssertIsOnWorkerThread();
+
+ DebuggerNotificationDispatch(
+ this, aIsInterval ? DebuggerNotificationType::SetInterval
+ : DebuggerNotificationType::SetTimeout);
+
+ bool allowEval = false;
+ aRv =
+ CSPEvalChecker::CheckForWorker(aCx, mWorkerPrivate, aHandler, &allowEval);
+ if (NS_WARN_IF(aRv.Failed()) || !allowEval) {
+ return 0;
+ }
+
+ RefPtr<TimeoutHandler> handler =
+ new WorkerScriptTimeoutHandler(aCx, this, aHandler);
+
+ return mWorkerPrivate->SetTimeout(aCx, handler, aTimeout, aIsInterval,
+ Timeout::Reason::eTimeoutOrInterval, aRv);
+}
+
+void WorkerGlobalScope::GetOrigin(nsAString& aOrigin) const {
+ AssertIsOnWorkerThread();
+ nsContentUtils::GetUTFOrigin(mWorkerPrivate->GetPrincipal(), aOrigin);
+}
+
+bool WorkerGlobalScope::CrossOriginIsolated() const {
+ return mWorkerPrivate->CrossOriginIsolated();
+}
+
+void WorkerGlobalScope::Dump(const Optional<nsAString>& aString) const {
+ AssertIsOnWorkerThread();
+
+ if (!aString.WasPassed()) {
+ return;
+ }
+
+ if (!nsJSUtils::DumpEnabled()) {
+ return;
+ }
+
+ NS_ConvertUTF16toUTF8 str(aString.Value());
+
+ MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug,
+ ("[Worker.Dump] %s", str.get()));
+#ifdef ANDROID
+ __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", str.get());
+#endif
+ fputs(str.get(), stdout);
+ fflush(stdout);
+}
+
+Performance* WorkerGlobalScope::GetPerformance() {
+ AssertIsOnWorkerThread();
+
+ if (!mPerformance) {
+ mPerformance = Performance::CreateForWorker(mWorkerPrivate);
+ }
+
+ return mPerformance;
+}
+
+bool WorkerGlobalScope::IsInAutomation(JSContext* aCx, JSObject* /* unused */) {
+ return GetWorkerPrivateFromContext(aCx)->IsInAutomation();
+}
+
+void WorkerGlobalScope::GetJSTestingFunctions(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aFunctions, ErrorResult& aRv) {
+ JSObject* obj = js::GetTestingFunctions(aCx);
+ if (!obj) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ aFunctions.set(obj);
+}
+
+already_AddRefed<Promise> WorkerGlobalScope::Fetch(
+ const RequestOrUSVString& aInput, const RequestInit& aInit,
+ CallerType aCallerType, ErrorResult& aRv) {
+ return FetchRequest(this, aInput, aInit, aCallerType, aRv);
+}
+
+already_AddRefed<IDBFactory> WorkerGlobalScope::GetIndexedDB(
+ JSContext* aCx, ErrorResult& aErrorResult) {
+ AssertIsOnWorkerThread();
+
+ RefPtr<IDBFactory> indexedDB = mIndexedDB;
+
+ if (!indexedDB) {
+ StorageAccess access = mWorkerPrivate->StorageAccess();
+
+ if (access == StorageAccess::eDeny) {
+ NS_WARNING("IndexedDB is not allowed in this worker!");
+ aErrorResult = NS_ERROR_DOM_SECURITY_ERR;
+ return nullptr;
+ }
+
+ if (ShouldPartitionStorage(access) &&
+ !StoragePartitioningEnabled(access,
+ mWorkerPrivate->CookieJarSettings())) {
+ NS_WARNING("IndexedDB is not allowed in this worker!");
+ aErrorResult = NS_ERROR_DOM_SECURITY_ERR;
+ return nullptr;
+ }
+
+ const PrincipalInfo& principalInfo =
+ mWorkerPrivate->GetEffectiveStoragePrincipalInfo();
+
+ auto res = IDBFactory::CreateForWorker(this, principalInfo,
+ mWorkerPrivate->WindowID());
+ if (NS_WARN_IF(res.isErr())) {
+ aErrorResult = res.unwrapErr();
+ return nullptr;
+ }
+
+ indexedDB = res.unwrap();
+ mIndexedDB = indexedDB;
+ }
+
+ return indexedDB.forget();
+}
+
+WebTaskScheduler* WorkerGlobalScope::Scheduler() {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (!mWebTaskScheduler) {
+ mWebTaskScheduler = WebTaskScheduler::CreateForWorker(mWorkerPrivate);
+ }
+
+ MOZ_ASSERT(mWebTaskScheduler);
+ return mWebTaskScheduler;
+}
+
+WebTaskScheduler* WorkerGlobalScope::GetExistingScheduler() const {
+ return mWebTaskScheduler;
+}
+
+already_AddRefed<Promise> WorkerGlobalScope::CreateImageBitmap(
+ const ImageBitmapSource& aImage, const ImageBitmapOptions& aOptions,
+ ErrorResult& aRv) {
+ return ImageBitmap::Create(this, aImage, Nothing(), aOptions, aRv);
+}
+
+already_AddRefed<Promise> WorkerGlobalScope::CreateImageBitmap(
+ const ImageBitmapSource& aImage, int32_t aSx, int32_t aSy, int32_t aSw,
+ int32_t aSh, const ImageBitmapOptions& aOptions, ErrorResult& aRv) {
+ return ImageBitmap::Create(
+ this, aImage, Some(gfx::IntRect(aSx, aSy, aSw, aSh)), aOptions, aRv);
+}
+
+// https://html.spec.whatwg.org/#structured-cloning
+void WorkerGlobalScope::StructuredClone(
+ JSContext* aCx, JS::Handle<JS::Value> aValue,
+ const StructuredSerializeOptions& aOptions,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aError) {
+ nsContentUtils::StructuredClone(aCx, this, aValue, aOptions, aRetval, aError);
+}
+
+mozilla::dom::DebuggerNotificationManager*
+WorkerGlobalScope::GetOrCreateDebuggerNotificationManager() {
+ if (!mDebuggerNotificationManager) {
+ mDebuggerNotificationManager = new DebuggerNotificationManager(this);
+ }
+
+ return mDebuggerNotificationManager;
+}
+
+mozilla::dom::DebuggerNotificationManager*
+WorkerGlobalScope::GetExistingDebuggerNotificationManager() {
+ return mDebuggerNotificationManager;
+}
+
+Maybe<EventCallbackDebuggerNotificationType>
+WorkerGlobalScope::GetDebuggerNotificationType() const {
+ return Some(EventCallbackDebuggerNotificationType::Global);
+}
+
+RefPtr<ServiceWorkerRegistration>
+WorkerGlobalScope::GetServiceWorkerRegistration(
+ const ServiceWorkerRegistrationDescriptor& aDescriptor) const {
+ AssertIsOnWorkerThread();
+ RefPtr<ServiceWorkerRegistration> ref;
+ ForEachGlobalTeardownObserver(
+ [&](GlobalTeardownObserver* aObserver, bool* aDoneOut) {
+ RefPtr<ServiceWorkerRegistration> swr = do_QueryObject(aObserver);
+ if (!swr || !swr->MatchesDescriptor(aDescriptor)) {
+ return;
+ }
+
+ ref = std::move(swr);
+ *aDoneOut = true;
+ });
+ return ref;
+}
+
+RefPtr<ServiceWorkerRegistration>
+WorkerGlobalScope::GetOrCreateServiceWorkerRegistration(
+ const ServiceWorkerRegistrationDescriptor& aDescriptor) {
+ AssertIsOnWorkerThread();
+ RefPtr<ServiceWorkerRegistration> ref =
+ GetServiceWorkerRegistration(aDescriptor);
+ if (!ref) {
+ ref = ServiceWorkerRegistration::CreateForWorker(mWorkerPrivate, this,
+ aDescriptor);
+ }
+ return ref;
+}
+
+mozilla::dom::StorageManager* WorkerGlobalScope::GetStorageManager() {
+ return RefPtr(Navigator())->Storage();
+}
+
+void WorkerGlobalScope::StorageAccessPermissionGranted() {
+ // Reset the IndexedDB factory.
+ mIndexedDB = nullptr;
+
+ // Reset DOM Cache
+ mCacheStorage = nullptr;
+}
+
+bool WorkerGlobalScope::WindowInteractionAllowed() const {
+ AssertIsOnWorkerThread();
+ return mWindowInteractionsAllowed > 0;
+}
+
+void WorkerGlobalScope::AllowWindowInteraction() {
+ AssertIsOnWorkerThread();
+ mWindowInteractionsAllowed++;
+}
+
+void WorkerGlobalScope::ConsumeWindowInteraction() {
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(mWindowInteractionsAllowed);
+ mWindowInteractionsAllowed--;
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(DedicatedWorkerGlobalScope,
+ WorkerGlobalScope, mFrameRequestManager)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(DedicatedWorkerGlobalScope,
+ WorkerGlobalScope)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(DedicatedWorkerGlobalScope,
+ WorkerGlobalScope)
+
+DedicatedWorkerGlobalScope::DedicatedWorkerGlobalScope(
+ WorkerPrivate* aWorkerPrivate, UniquePtr<ClientSource> aClientSource,
+ const nsString& aName, bool aShouldResistFingerprinting)
+ : WorkerGlobalScope(std::move(aWorkerPrivate), std::move(aClientSource),
+ aShouldResistFingerprinting),
+ NamedWorkerGlobalScopeMixin(aName) {}
+
+bool DedicatedWorkerGlobalScope::WrapGlobalObject(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aReflector) {
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(!mWorkerPrivate->IsSharedWorker());
+
+ JS::RealmOptions options;
+ mWorkerPrivate->CopyJSRealmOptions(options);
+
+ const bool usesSystemPrincipal = mWorkerPrivate->UsesSystemPrincipal();
+
+ // Note that xpc::ShouldDiscardSystemSource() reads a prefs that is cached
+ // on the main thread. This is benignly racey.
+ const bool discardSource =
+ usesSystemPrincipal && xpc::ShouldDiscardSystemSource();
+
+ JS::RealmBehaviors& behaviors = options.behaviors();
+ behaviors.setDiscardSource(discardSource);
+
+ xpc::SetPrefableRealmOptions(options);
+
+ return DedicatedWorkerGlobalScope_Binding::Wrap(
+ aCx, this, this, options,
+ nsJSPrincipals::get(mWorkerPrivate->GetPrincipal()), true, aReflector);
+}
+
+void DedicatedWorkerGlobalScope::PostMessage(
+ JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Sequence<JSObject*>& aTransferable, ErrorResult& aRv) {
+ AssertIsOnWorkerThread();
+ mWorkerPrivate->PostMessageToParent(aCx, aMessage, aTransferable, aRv);
+}
+
+void DedicatedWorkerGlobalScope::PostMessage(
+ JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const StructuredSerializeOptions& aOptions, ErrorResult& aRv) {
+ AssertIsOnWorkerThread();
+ mWorkerPrivate->PostMessageToParent(aCx, aMessage, aOptions.mTransfer, aRv);
+}
+
+void DedicatedWorkerGlobalScope::Close() {
+ AssertIsOnWorkerThread();
+ mWorkerPrivate->CloseInternal();
+}
+
+int32_t DedicatedWorkerGlobalScope::RequestAnimationFrame(
+ FrameRequestCallback& aCallback, ErrorResult& aError) {
+ AssertIsOnWorkerThread();
+
+ DebuggerNotificationDispatch(this,
+ DebuggerNotificationType::RequestAnimationFrame);
+
+ // Ensure the worker is associated with a window.
+ if (mWorkerPrivate->WindowID() == UINT64_MAX) {
+ aError.ThrowNotSupportedError("Worker has no associated owner Window");
+ return 0;
+ }
+
+ if (!mVsyncChild) {
+ PBackgroundChild* bgChild = BackgroundChild::GetOrCreateForCurrentThread();
+ mVsyncChild = MakeRefPtr<VsyncWorkerChild>();
+
+ if (!bgChild || !mVsyncChild->Initialize(mWorkerPrivate) ||
+ !bgChild->SendPVsyncConstructor(mVsyncChild)) {
+ mVsyncChild->Destroy();
+ mVsyncChild = nullptr;
+ aError.ThrowNotSupportedError(
+ "Worker failed to register for vsync to drive event loop");
+ return 0;
+ }
+ }
+
+ if (!mDocListener) {
+ mDocListener = WorkerDocumentListener::Create(mWorkerPrivate);
+ if (!mDocListener) {
+ aError.ThrowNotSupportedError(
+ "Worker failed to register for document visibility events");
+ return 0;
+ }
+ }
+
+ int32_t handle = 0;
+ aError = mFrameRequestManager.Schedule(aCallback, &handle);
+ if (!aError.Failed() && mDocumentVisible) {
+ mVsyncChild->TryObserve();
+ }
+ return handle;
+}
+
+void DedicatedWorkerGlobalScope::CancelAnimationFrame(int32_t aHandle,
+ ErrorResult& aError) {
+ AssertIsOnWorkerThread();
+
+ DebuggerNotificationDispatch(this,
+ DebuggerNotificationType::CancelAnimationFrame);
+
+ // Ensure the worker is associated with a window.
+ if (mWorkerPrivate->WindowID() == UINT64_MAX) {
+ aError.ThrowNotSupportedError("Worker has no associated owner Window");
+ return;
+ }
+
+ mFrameRequestManager.Cancel(aHandle);
+ if (mVsyncChild && mFrameRequestManager.IsEmpty()) {
+ mVsyncChild->TryUnobserve();
+ }
+}
+
+void DedicatedWorkerGlobalScope::OnDocumentVisible(bool aVisible) {
+ AssertIsOnWorkerThread();
+
+ mDocumentVisible = aVisible;
+
+ // We only change state immediately when we become visible. If we become
+ // hidden, then we wait for the next vsync tick to apply that.
+ if (aVisible && !mFrameRequestManager.IsEmpty()) {
+ mVsyncChild->TryObserve();
+ }
+}
+
+void DedicatedWorkerGlobalScope::OnVsync(const VsyncEvent& aVsync) {
+ AssertIsOnWorkerThread();
+
+ if (mFrameRequestManager.IsEmpty() || !mDocumentVisible) {
+ // If we ever receive a vsync event, and there are still no callbacks to
+ // process, or we remain hidden, we should disable observing them. By
+ // waiting an extra tick, we ensure we minimize extra IPC for content that
+ // does not call requestFrameAnimation directly during the callback, or
+ // that is rapidly toggling between hidden and visible.
+ mVsyncChild->TryUnobserve();
+ return;
+ }
+
+ nsTArray<FrameRequest> callbacks;
+ mFrameRequestManager.Take(callbacks);
+
+ RefPtr<DedicatedWorkerGlobalScope> scope(this);
+ CallbackDebuggerNotificationGuard guard(
+ scope, DebuggerNotificationType::RequestAnimationFrameCallback);
+
+ // This is similar to what we do in nsRefreshDriver::RunFrameRequestCallbacks
+ // and Performance::TimeStampToDOMHighResForRendering in order to have the
+ // same behaviour for requestAnimationFrame on both the main and worker
+ // threads.
+ DOMHighResTimeStamp timeStamp = 0;
+ if (!aVsync.mTime.IsNull()) {
+ timeStamp = mWorkerPrivate->TimeStampToDOMHighRes(aVsync.mTime);
+ // 0 is an inappropriate mixin for this this area; however CSS Animations
+ // needs to have it's Time Reduction Logic refactored, so it's currently
+ // only clamping for RFP mode. RFP mode gives a much lower time precision,
+ // so we accept the security leak here for now.
+ timeStamp = nsRFPService::ReduceTimePrecisionAsMSecsRFPOnly(
+ timeStamp, 0, this->GetRTPCallerType());
+ }
+
+ for (auto& callback : callbacks) {
+ if (mFrameRequestManager.IsCanceled(callback.mHandle)) {
+ continue;
+ }
+
+ // MOZ_KnownLive is OK, because the stack array `callbacks` keeps the
+ // callback alive and the mCallback strong reference can't be mutated by
+ // the call.
+ LogFrameRequestCallback::Run run(callback.mCallback);
+ MOZ_KnownLive(callback.mCallback)->Call(timeStamp);
+ }
+}
+
+SharedWorkerGlobalScope::SharedWorkerGlobalScope(
+ WorkerPrivate* aWorkerPrivate, UniquePtr<ClientSource> aClientSource,
+ const nsString& aName, bool aShouldResistFingerprinting)
+ : WorkerGlobalScope(std::move(aWorkerPrivate), std::move(aClientSource),
+ aShouldResistFingerprinting),
+ NamedWorkerGlobalScopeMixin(aName) {}
+
+bool SharedWorkerGlobalScope::WrapGlobalObject(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aReflector) {
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(mWorkerPrivate->IsSharedWorker());
+
+ JS::RealmOptions options;
+ mWorkerPrivate->CopyJSRealmOptions(options);
+
+ return SharedWorkerGlobalScope_Binding::Wrap(
+ aCx, this, this, options,
+ nsJSPrincipals::get(mWorkerPrivate->GetPrincipal()), true, aReflector);
+}
+
+void SharedWorkerGlobalScope::Close() {
+ AssertIsOnWorkerThread();
+ mWorkerPrivate->CloseInternal();
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope,
+ mClients, mExtensionBrowser, mRegistration)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerGlobalScope)
+NS_INTERFACE_MAP_END_INHERITING(WorkerGlobalScope)
+
+NS_IMPL_ADDREF_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope)
+NS_IMPL_RELEASE_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope)
+
+ServiceWorkerGlobalScope::ServiceWorkerGlobalScope(
+ WorkerPrivate* aWorkerPrivate, UniquePtr<ClientSource> aClientSource,
+ const ServiceWorkerRegistrationDescriptor& aRegistrationDescriptor,
+ bool aShouldResistFingerprinting)
+ : WorkerGlobalScope(std::move(aWorkerPrivate), std::move(aClientSource),
+ aShouldResistFingerprinting),
+ mScope(NS_ConvertUTF8toUTF16(aRegistrationDescriptor.Scope()))
+
+ // Eagerly create the registration because we will need to receive
+ // updates about the state of the registration. We can't wait until
+ // first access to start receiving these.
+ ,
+ mRegistration(
+ GetOrCreateServiceWorkerRegistration(aRegistrationDescriptor)) {}
+
+ServiceWorkerGlobalScope::~ServiceWorkerGlobalScope() = default;
+
+bool ServiceWorkerGlobalScope::WrapGlobalObject(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aReflector) {
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(mWorkerPrivate->IsServiceWorker());
+
+ JS::RealmOptions options;
+ mWorkerPrivate->CopyJSRealmOptions(options);
+
+ return ServiceWorkerGlobalScope_Binding::Wrap(
+ aCx, this, this, options,
+ nsJSPrincipals::get(mWorkerPrivate->GetPrincipal()), true, aReflector);
+}
+
+already_AddRefed<Clients> ServiceWorkerGlobalScope::GetClients() {
+ if (!mClients) {
+ mClients = new Clients(this);
+ }
+
+ RefPtr<Clients> ref = mClients;
+ return ref.forget();
+}
+
+ServiceWorkerRegistration* ServiceWorkerGlobalScope::Registration() {
+ return mRegistration;
+}
+
+EventHandlerNonNull* ServiceWorkerGlobalScope::GetOnfetch() {
+ AssertIsOnWorkerThread();
+
+ return GetEventHandler(nsGkAtoms::onfetch);
+}
+
+namespace {
+
+class ReportFetchListenerWarningRunnable final : public Runnable {
+ const nsCString mScope;
+ nsString mSourceSpec;
+ uint32_t mLine;
+ uint32_t mColumn;
+
+ public:
+ explicit ReportFetchListenerWarningRunnable(const nsString& aScope)
+ : mozilla::Runnable("ReportFetchListenerWarningRunnable"),
+ mScope(NS_ConvertUTF16toUTF8(aScope)) {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ JSContext* cx = workerPrivate->GetJSContext();
+ MOZ_ASSERT(cx);
+
+ nsJSUtils::GetCallingLocation(cx, mSourceSpec, &mLine, &mColumn);
+ }
+
+ NS_IMETHOD
+ Run() override {
+ AssertIsOnMainThread();
+
+ ServiceWorkerManager::LocalizeAndReportToAllClients(
+ mScope, "ServiceWorkerNoFetchHandler", nsTArray<nsString>{},
+ nsIScriptError::warningFlag, mSourceSpec, u""_ns, mLine, mColumn);
+
+ return NS_OK;
+ }
+};
+
+} // anonymous namespace
+
+void ServiceWorkerGlobalScope::NoteFetchHandlerWasAdded() const {
+ if (mWorkerPrivate->WorkerScriptExecutedSuccessfully()) {
+ RefPtr<Runnable> r = new ReportFetchListenerWarningRunnable(mScope);
+ mWorkerPrivate->DispatchToMainThreadForMessaging(r.forget());
+ }
+ mWorkerPrivate->SetFetchHandlerWasAdded();
+}
+
+void ServiceWorkerGlobalScope::SetOnfetch(
+ mozilla::dom::EventHandlerNonNull* aCallback) {
+ AssertIsOnWorkerThread();
+
+ if (aCallback) {
+ NoteFetchHandlerWasAdded();
+ }
+ SetEventHandler(nsGkAtoms::onfetch, aCallback);
+}
+
+void ServiceWorkerGlobalScope::EventListenerAdded(nsAtom* aType) {
+ AssertIsOnWorkerThread();
+
+ if (aType == nsGkAtoms::onfetch) {
+ NoteFetchHandlerWasAdded();
+ }
+}
+
+already_AddRefed<Promise> ServiceWorkerGlobalScope::SkipWaiting(
+ ErrorResult& aRv) {
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(mWorkerPrivate->IsServiceWorker());
+
+ RefPtr<Promise> promise = Promise::Create(this, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ using MozPromiseType =
+ decltype(mWorkerPrivate->SetServiceWorkerSkipWaitingFlag())::element_type;
+ auto holder = MakeRefPtr<DOMMozPromiseRequestHolder<MozPromiseType>>(this);
+
+ mWorkerPrivate->SetServiceWorkerSkipWaitingFlag()
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [holder, promise](const MozPromiseType::ResolveOrRejectValue&) {
+ holder->Complete();
+ promise->MaybeResolveWithUndefined();
+ })
+ ->Track(*holder);
+
+ return promise.forget();
+}
+
+SafeRefPtr<extensions::ExtensionBrowser>
+ServiceWorkerGlobalScope::AcquireExtensionBrowser() {
+ if (!mExtensionBrowser) {
+ mExtensionBrowser = MakeSafeRefPtr<extensions::ExtensionBrowser>(this);
+ }
+
+ return mExtensionBrowser.clonePtr();
+}
+
+bool WorkerDebuggerGlobalScope::WrapGlobalObject(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aReflector) {
+ AssertIsOnWorkerThread();
+
+ JS::RealmOptions options;
+ mWorkerPrivate->CopyJSRealmOptions(options);
+
+ return WorkerDebuggerGlobalScope_Binding::Wrap(
+ aCx, this, this, options,
+ nsJSPrincipals::get(mWorkerPrivate->GetPrincipal()), true, aReflector);
+}
+
+void WorkerDebuggerGlobalScope::GetGlobal(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aGlobal,
+ ErrorResult& aRv) {
+ WorkerGlobalScope* scope = mWorkerPrivate->GetOrCreateGlobalScope(aCx);
+ if (!scope) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aGlobal.set(scope->GetWrapper());
+}
+
+void WorkerDebuggerGlobalScope::CreateSandbox(
+ JSContext* aCx, const nsAString& aName, JS::Handle<JSObject*> aPrototype,
+ JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv) {
+ AssertIsOnWorkerThread();
+
+ aResult.set(nullptr);
+
+ JS::Rooted<JS::Value> protoVal(aCx);
+ protoVal.setObjectOrNull(aPrototype);
+ JS::Rooted<JSObject*> sandbox(
+ aCx,
+ SimpleGlobalObject::Create(
+ SimpleGlobalObject::GlobalType::WorkerDebuggerSandbox, protoVal));
+
+ if (!sandbox) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ if (!JS_WrapObject(aCx, &sandbox)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ aResult.set(sandbox);
+}
+
+void WorkerDebuggerGlobalScope::LoadSubScript(
+ JSContext* aCx, const nsAString& aURL,
+ const Optional<JS::Handle<JSObject*>>& aSandbox, ErrorResult& aRv) {
+ AssertIsOnWorkerThread();
+
+ Maybe<JSAutoRealm> ar;
+ if (aSandbox.WasPassed()) {
+ // We only care about worker debugger sandbox objects here, so
+ // CheckedUnwrapStatic is fine.
+ JS::Rooted<JSObject*> sandbox(aCx,
+ js::CheckedUnwrapStatic(aSandbox.Value()));
+ if (!sandbox || !IsWorkerDebuggerSandbox(sandbox)) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ ar.emplace(aCx, sandbox);
+ }
+
+ nsTArray<nsString> urls;
+ urls.AppendElement(aURL);
+ workerinternals::Load(mWorkerPrivate, nullptr, urls, DebuggerScript, aRv);
+}
+
+void WorkerDebuggerGlobalScope::EnterEventLoop() {
+ // 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)->EnterDebuggerEventLoop();
+}
+
+void WorkerDebuggerGlobalScope::LeaveEventLoop() {
+ mWorkerPrivate->LeaveDebuggerEventLoop();
+}
+
+void WorkerDebuggerGlobalScope::PostMessage(const nsAString& aMessage) {
+ mWorkerPrivate->PostMessageToDebugger(aMessage);
+}
+
+void WorkerDebuggerGlobalScope::SetImmediate(Function& aHandler,
+ ErrorResult& aRv) {
+ mWorkerPrivate->SetDebuggerImmediate(aHandler, aRv);
+}
+
+void WorkerDebuggerGlobalScope::ReportError(JSContext* aCx,
+ const nsAString& aMessage) {
+ JS::AutoFilename chars;
+ uint32_t lineno = 0;
+ JS::DescribeScriptedCaller(aCx, &chars, &lineno);
+ nsString filename(NS_ConvertUTF8toUTF16(chars.get()));
+ mWorkerPrivate->ReportErrorToDebugger(filename, lineno, aMessage);
+}
+
+void WorkerDebuggerGlobalScope::RetrieveConsoleEvents(
+ JSContext* aCx, nsTArray<JS::Value>& aEvents, ErrorResult& aRv) {
+ WorkerGlobalScope* scope = mWorkerPrivate->GetOrCreateGlobalScope(aCx);
+ if (!scope) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<Console> console = scope->GetConsole(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ console->RetrieveConsoleEvents(aCx, aEvents, aRv);
+}
+
+void WorkerDebuggerGlobalScope::ClearConsoleEvents(JSContext* aCx,
+ ErrorResult& aRv) {
+ WorkerGlobalScope* scope = mWorkerPrivate->GetOrCreateGlobalScope(aCx);
+ if (!scope) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<Console> console = scope->GetConsoleIfExists();
+ if (console) {
+ console->ClearStorage();
+ }
+}
+
+void WorkerDebuggerGlobalScope::SetConsoleEventHandler(JSContext* aCx,
+ AnyCallback* aHandler,
+ ErrorResult& aRv) {
+ WorkerGlobalScope* scope = mWorkerPrivate->GetOrCreateGlobalScope(aCx);
+ if (!scope) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<Console> console = scope->GetConsole(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ console->SetConsoleEventHandler(aHandler);
+}
+
+void WorkerDebuggerGlobalScope::Dump(JSContext* aCx,
+ const Optional<nsAString>& aString) const {
+ WorkerGlobalScope* scope = mWorkerPrivate->GetOrCreateGlobalScope(aCx);
+ if (scope) {
+ scope->Dump(aString);
+ }
+}
+
+bool IsWorkerGlobal(JSObject* object) {
+ return IS_INSTANCE_OF(WorkerGlobalScope, object);
+}
+
+bool IsWorkerDebuggerGlobal(JSObject* object) {
+ return IS_INSTANCE_OF(WorkerDebuggerGlobalScope, object);
+}
+
+bool IsWorkerDebuggerSandbox(JSObject* object) {
+ return SimpleGlobalObject::SimpleGlobalType(object) ==
+ SimpleGlobalObject::GlobalType::WorkerDebuggerSandbox;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h
new file mode 100644
index 0000000000..5a3414572a
--- /dev/null
+++ b/dom/workers/WorkerScope.h
@@ -0,0 +1,562 @@
+/* -*- 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_workerscope_h__
+#define mozilla_dom_workerscope_h__
+
+#include "js/TypeDecls.h"
+#include "js/loader/ModuleLoaderBase.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/AnimationFrameProvider.h"
+#include "mozilla/dom/ImageBitmapBinding.h"
+#include "mozilla/dom/ImageBitmapSource.h"
+#include "mozilla/dom/PerformanceWorker.h"
+#include "mozilla/dom/SafeRefPtr.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIGlobalObject.h"
+#include "nsISupports.h"
+#include "nsWeakReference.h"
+
+#ifdef XP_WIN
+# undef PostMessage
+#endif
+
+class nsAtom;
+class nsISerialEventTarget;
+
+namespace mozilla {
+class ErrorResult;
+struct VsyncEvent;
+
+namespace extensions {
+
+class ExtensionBrowser;
+
+} // namespace extensions
+
+namespace dom {
+
+class AnyCallback;
+enum class CallerType : uint32_t;
+class ClientInfo;
+class ClientSource;
+class Clients;
+class Console;
+class Crypto;
+class DOMString;
+class DebuggerNotificationManager;
+enum class EventCallbackDebuggerNotificationType : uint8_t;
+class EventHandlerNonNull;
+class FontFaceSet;
+class Function;
+class IDBFactory;
+class OnErrorEventHandlerNonNull;
+template <typename T>
+class Optional;
+class Performance;
+class Promise;
+class RequestOrUSVString;
+template <typename T>
+class Sequence;
+class ServiceWorkerDescriptor;
+class ServiceWorkerRegistration;
+class ServiceWorkerRegistrationDescriptor;
+struct StructuredSerializeOptions;
+class WorkerDocumentListener;
+class WorkerLocation;
+class WorkerNavigator;
+class WorkerPrivate;
+class VsyncWorkerChild;
+class WebTaskScheduler;
+class WebTaskSchedulerWorker;
+struct RequestInit;
+
+namespace cache {
+
+class CacheStorage;
+
+} // namespace cache
+
+class WorkerGlobalScopeBase : public DOMEventTargetHelper,
+ public nsSupportsWeakReference,
+ public nsIGlobalObject {
+ friend class WorkerPrivate;
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(WorkerGlobalScopeBase,
+ DOMEventTargetHelper)
+
+ WorkerGlobalScopeBase(WorkerPrivate* aWorkerPrivate,
+ UniquePtr<ClientSource> aClientSource,
+ bool aShouldResistFingerprinting);
+
+ virtual bool WrapGlobalObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aReflector) = 0;
+
+ // EventTarget implementation
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) final {
+ MOZ_CRASH("WrapObject not supported; use WrapGlobalObject.");
+ }
+
+ // nsIGlobalObject implementation
+ JSObject* GetGlobalJSObject() final;
+
+ JSObject* GetGlobalJSObjectPreserveColor() const final;
+
+ bool IsSharedMemoryAllowed() const final;
+
+ bool ShouldResistFingerprinting(RFPTarget aTarget) const final;
+
+ OriginTrials Trials() const final;
+
+ StorageAccess GetStorageAccess() final;
+
+ Maybe<ClientInfo> GetClientInfo() const final;
+
+ Maybe<ServiceWorkerDescriptor> GetController() const final;
+
+ mozilla::Result<mozilla::ipc::PrincipalInfo, nsresult> GetStorageKey() final;
+
+ virtual void Control(const ServiceWorkerDescriptor& aServiceWorker);
+
+ // DispatcherTrait implementation
+ nsresult Dispatch(TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable) final;
+
+ nsISerialEventTarget* EventTargetFor(TaskCategory) const final;
+
+ AbstractThread* AbstractMainThreadFor(TaskCategory) final {
+ MOZ_CRASH("AbstractMainThreadFor not supported for workers.");
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void ReportError(JSContext* aCx, JS::Handle<JS::Value> aError,
+ CallerType aCallerType, ErrorResult& aRv);
+
+ // atob, btoa, and dump are declared (separately) by both WorkerGlobalScope
+ // and WorkerDebuggerGlobalScope WebIDL interfaces
+ void Atob(const nsAString& aAtob, nsAString& aOut, ErrorResult& aRv) const;
+
+ void Btoa(const nsAString& aBtoa, nsAString& aOut, ErrorResult& aRv) const;
+
+ already_AddRefed<Console> GetConsole(ErrorResult& aRv);
+
+ Console* GetConsoleIfExists() const { return mConsole; }
+
+ void InitModuleLoader(JS::loader::ModuleLoaderBase* aModuleLoader) {
+ if (!mModuleLoader) {
+ mModuleLoader = aModuleLoader;
+ }
+ }
+
+ // The nullptr here is not used, but is required to make the override method
+ // have the same signature as other GetModuleLoader methods on globals.
+ JS::loader::ModuleLoaderBase* GetModuleLoader(
+ JSContext* aCx = nullptr) override {
+ return mModuleLoader;
+ };
+
+ uint64_t WindowID() const;
+
+ // Usually global scope dies earlier than the WorkerPrivate, but if we see
+ // it leak at least we can tell it to not carry away a dead pointer.
+ void NoteWorkerTerminated() { mWorkerPrivate = nullptr; }
+
+ ClientSource& MutableClientSourceRef() const { return *mClientSource; }
+
+ // WorkerPrivate wants to be able to forbid script when its state machine
+ // demands it.
+ void WorkerPrivateSaysForbidScript() { StartForbiddingScript(); }
+ void WorkerPrivateSaysAllowScript() { StopForbiddingScript(); }
+
+ protected:
+ ~WorkerGlobalScopeBase();
+
+ CheckedUnsafePtr<WorkerPrivate> mWorkerPrivate;
+
+ void AssertIsOnWorkerThread() const {
+ MOZ_ASSERT(mWorkerThreadUsedOnlyForAssert == PR_GetCurrentThread());
+ }
+
+ private:
+ RefPtr<Console> mConsole;
+ RefPtr<JS::loader::ModuleLoaderBase> mModuleLoader;
+ const UniquePtr<ClientSource> mClientSource;
+ nsCOMPtr<nsISerialEventTarget> mSerialEventTarget;
+ bool mShouldResistFingerprinting;
+#ifdef DEBUG
+ PRThread* mWorkerThreadUsedOnlyForAssert;
+#endif
+};
+
+namespace workerinternals {
+
+class NamedWorkerGlobalScopeMixin {
+ public:
+ explicit NamedWorkerGlobalScopeMixin(const nsAString& aName) : mName(aName) {}
+
+ void GetName(DOMString& aName) const;
+
+ protected:
+ ~NamedWorkerGlobalScopeMixin() = default;
+
+ private:
+ const nsString mName;
+};
+
+} // namespace workerinternals
+
+class WorkerGlobalScope : public WorkerGlobalScopeBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WorkerGlobalScope,
+ WorkerGlobalScopeBase)
+
+ using WorkerGlobalScopeBase::WorkerGlobalScopeBase;
+
+ void NoteTerminating();
+
+ void NoteShuttingDown();
+
+ // nsIGlobalObject implementation
+ RefPtr<ServiceWorkerRegistration> GetServiceWorkerRegistration(
+ const ServiceWorkerRegistrationDescriptor& aDescriptor) const final;
+
+ RefPtr<ServiceWorkerRegistration> GetOrCreateServiceWorkerRegistration(
+ const ServiceWorkerRegistrationDescriptor& aDescriptor) final;
+
+ DebuggerNotificationManager* GetOrCreateDebuggerNotificationManager() final;
+
+ DebuggerNotificationManager* GetExistingDebuggerNotificationManager() final;
+
+ Maybe<EventCallbackDebuggerNotificationType> GetDebuggerNotificationType()
+ const final;
+
+ mozilla::dom::StorageManager* GetStorageManager() final;
+
+ // WorkerGlobalScope WebIDL implementation
+ WorkerGlobalScope* Self() { return this; }
+
+ already_AddRefed<WorkerLocation> Location();
+
+ already_AddRefed<WorkerNavigator> Navigator();
+
+ already_AddRefed<WorkerNavigator> GetExistingNavigator() const;
+
+ FontFaceSet* GetFonts(ErrorResult&);
+ FontFaceSet* GetFonts() final { return GetFonts(IgnoreErrors()); }
+
+ void ImportScripts(JSContext* aCx, const Sequence<nsString>& aScriptURLs,
+ ErrorResult& aRv);
+
+ OnErrorEventHandlerNonNull* GetOnerror();
+
+ void SetOnerror(OnErrorEventHandlerNonNull* aHandler);
+
+ IMPL_EVENT_HANDLER(languagechange)
+ IMPL_EVENT_HANDLER(offline)
+ IMPL_EVENT_HANDLER(online)
+ IMPL_EVENT_HANDLER(rejectionhandled)
+ IMPL_EVENT_HANDLER(unhandledrejection)
+
+ void Dump(const Optional<nsAString>& aString) const;
+
+ Performance* GetPerformance();
+
+ Performance* GetPerformanceIfExists() const { return mPerformance; }
+
+ static bool IsInAutomation(JSContext* aCx, JSObject*);
+
+ void GetJSTestingFunctions(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aFunctions,
+ ErrorResult& aRv);
+
+ // GlobalCrypto WebIDL implementation
+ Crypto* GetCrypto(ErrorResult& aError);
+
+ // WindowOrWorkerGlobalScope WebIDL implementation
+ void GetOrigin(nsAString& aOrigin) const;
+
+ bool CrossOriginIsolated() const final;
+
+ MOZ_CAN_RUN_SCRIPT
+ int32_t SetTimeout(JSContext* aCx, Function& aHandler, int32_t aTimeout,
+ const Sequence<JS::Value>& aArguments, ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT
+ int32_t SetTimeout(JSContext* aCx, const nsAString& aHandler,
+ int32_t aTimeout, const Sequence<JS::Value>&,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT
+ void ClearTimeout(int32_t aHandle);
+
+ MOZ_CAN_RUN_SCRIPT
+ int32_t SetInterval(JSContext* aCx, Function& aHandler, int32_t aTimeout,
+ const Sequence<JS::Value>& aArguments, ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT
+ int32_t SetInterval(JSContext* aCx, const nsAString& aHandler,
+ int32_t aTimeout, const Sequence<JS::Value>&,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT
+ void ClearInterval(int32_t aHandle);
+
+ already_AddRefed<Promise> CreateImageBitmap(
+ const ImageBitmapSource& aImage, const ImageBitmapOptions& aOptions,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> CreateImageBitmap(
+ const ImageBitmapSource& aImage, int32_t aSx, int32_t aSy, int32_t aSw,
+ int32_t aSh, const ImageBitmapOptions& aOptions, ErrorResult& aRv);
+
+ void StructuredClone(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ const StructuredSerializeOptions& aOptions,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aError);
+
+ already_AddRefed<Promise> Fetch(const RequestOrUSVString& aInput,
+ const RequestInit& aInit,
+ CallerType aCallerType, ErrorResult& aRv);
+
+ bool IsSecureContext() const;
+
+ already_AddRefed<IDBFactory> GetIndexedDB(JSContext* aCx,
+ ErrorResult& aErrorResult);
+
+ already_AddRefed<cache::CacheStorage> GetCaches(ErrorResult& aRv);
+
+ WebTaskScheduler* Scheduler();
+ WebTaskScheduler* GetExistingScheduler() const;
+
+ bool WindowInteractionAllowed() const;
+
+ void AllowWindowInteraction();
+
+ void ConsumeWindowInteraction();
+
+ void StorageAccessPermissionGranted();
+
+ virtual void OnDocumentVisible(bool aVisible) {}
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual void OnVsync(const VsyncEvent& aVsync) {}
+
+ protected:
+ ~WorkerGlobalScope();
+
+ private:
+ MOZ_CAN_RUN_SCRIPT
+ int32_t SetTimeoutOrInterval(JSContext* aCx, Function& aHandler,
+ int32_t aTimeout,
+ const Sequence<JS::Value>& aArguments,
+ bool aIsInterval, ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT
+ int32_t SetTimeoutOrInterval(JSContext* aCx, const nsAString& aHandler,
+ int32_t aTimeout, bool aIsInterval,
+ ErrorResult& aRv);
+
+ RefPtr<Crypto> mCrypto;
+ RefPtr<WorkerLocation> mLocation;
+ RefPtr<WorkerNavigator> mNavigator;
+ RefPtr<FontFaceSet> mFontFaceSet;
+ RefPtr<Performance> mPerformance;
+ RefPtr<IDBFactory> mIndexedDB;
+ RefPtr<cache::CacheStorage> mCacheStorage;
+ RefPtr<DebuggerNotificationManager> mDebuggerNotificationManager;
+ RefPtr<WebTaskSchedulerWorker> mWebTaskScheduler;
+ uint32_t mWindowInteractionsAllowed = 0;
+};
+
+class DedicatedWorkerGlobalScope final
+ : public WorkerGlobalScope,
+ public workerinternals::NamedWorkerGlobalScopeMixin {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+ DedicatedWorkerGlobalScope, WorkerGlobalScope)
+
+ DedicatedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate,
+ UniquePtr<ClientSource> aClientSource,
+ const nsString& aName,
+ bool aShouldResistFingerprinting);
+
+ bool WrapGlobalObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aReflector) override;
+
+ void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Sequence<JSObject*>& aTransferable, ErrorResult& aRv);
+
+ void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const StructuredSerializeOptions& aOptions,
+ ErrorResult& aRv);
+
+ void Close();
+
+ MOZ_CAN_RUN_SCRIPT
+ int32_t RequestAnimationFrame(FrameRequestCallback& aCallback,
+ ErrorResult& aError);
+
+ MOZ_CAN_RUN_SCRIPT
+ void CancelAnimationFrame(int32_t aHandle, ErrorResult& aError);
+
+ void OnDocumentVisible(bool aVisible) override;
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void OnVsync(const VsyncEvent& aVsync) override;
+
+ IMPL_EVENT_HANDLER(message)
+ IMPL_EVENT_HANDLER(messageerror)
+
+ private:
+ ~DedicatedWorkerGlobalScope() = default;
+
+ FrameRequestManager mFrameRequestManager;
+ RefPtr<VsyncWorkerChild> mVsyncChild;
+ RefPtr<WorkerDocumentListener> mDocListener;
+ bool mDocumentVisible = false;
+};
+
+class SharedWorkerGlobalScope final
+ : public WorkerGlobalScope,
+ public workerinternals::NamedWorkerGlobalScopeMixin {
+ public:
+ SharedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate,
+ UniquePtr<ClientSource> aClientSource,
+ const nsString& aName,
+ bool aShouldResistFingerprinting);
+
+ bool WrapGlobalObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aReflector) override;
+
+ void Close();
+
+ IMPL_EVENT_HANDLER(connect)
+
+ private:
+ ~SharedWorkerGlobalScope() = default;
+};
+
+class ServiceWorkerGlobalScope final : public WorkerGlobalScope {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerGlobalScope,
+ WorkerGlobalScope)
+
+ ServiceWorkerGlobalScope(
+ WorkerPrivate* aWorkerPrivate, UniquePtr<ClientSource> aClientSource,
+ const ServiceWorkerRegistrationDescriptor& aRegistrationDescriptor,
+ bool aShouldResistFingerprinting);
+
+ bool WrapGlobalObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aReflector) override;
+
+ already_AddRefed<Clients> GetClients();
+
+ ServiceWorkerRegistration* Registration();
+
+ already_AddRefed<Promise> SkipWaiting(ErrorResult& aRv);
+
+ SafeRefPtr<extensions::ExtensionBrowser> AcquireExtensionBrowser();
+
+ IMPL_EVENT_HANDLER(install)
+ IMPL_EVENT_HANDLER(activate)
+
+ EventHandlerNonNull* GetOnfetch();
+
+ void SetOnfetch(EventHandlerNonNull* aCallback);
+
+ void EventListenerAdded(nsAtom* aType) override;
+
+ IMPL_EVENT_HANDLER(message)
+ IMPL_EVENT_HANDLER(messageerror)
+
+ IMPL_EVENT_HANDLER(notificationclick)
+ IMPL_EVENT_HANDLER(notificationclose)
+
+ IMPL_EVENT_HANDLER(push)
+ IMPL_EVENT_HANDLER(pushsubscriptionchange)
+
+ private:
+ ~ServiceWorkerGlobalScope();
+
+ void NoteFetchHandlerWasAdded() const;
+
+ RefPtr<Clients> mClients;
+ const nsString mScope;
+ RefPtr<ServiceWorkerRegistration> mRegistration;
+ SafeRefPtr<extensions::ExtensionBrowser> mExtensionBrowser;
+};
+
+class WorkerDebuggerGlobalScope final : public WorkerGlobalScopeBase {
+ public:
+ using WorkerGlobalScopeBase::WorkerGlobalScopeBase;
+
+ bool WrapGlobalObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aReflector) override;
+
+ void Control(const ServiceWorkerDescriptor& aServiceWorker) override {
+ MOZ_CRASH("Can't control debugger workers.");
+ }
+
+ void GetGlobal(JSContext* aCx, JS::MutableHandle<JSObject*> aGlobal,
+ ErrorResult& aRv);
+
+ void CreateSandbox(JSContext* aCx, const nsAString& aName,
+ JS::Handle<JSObject*> aPrototype,
+ JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv);
+
+ void LoadSubScript(JSContext* aCx, const nsAString& aUrl,
+ const Optional<JS::Handle<JSObject*>>& aSandbox,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void EnterEventLoop();
+
+ void LeaveEventLoop();
+
+ void PostMessage(const nsAString& aMessage);
+
+ void SetImmediate(Function& aHandler, ErrorResult& aRv);
+
+ void ReportError(JSContext* aCx, const nsAString& aMessage);
+
+ void RetrieveConsoleEvents(JSContext* aCx, nsTArray<JS::Value>& aEvents,
+ ErrorResult& aRv);
+
+ void ClearConsoleEvents(JSContext* aCx, ErrorResult& aRv);
+
+ void SetConsoleEventHandler(JSContext* aCx, AnyCallback* aHandler,
+ ErrorResult& aRv);
+
+ void Dump(JSContext* aCx, const Optional<nsAString>& aString) const;
+
+ IMPL_EVENT_HANDLER(message)
+ IMPL_EVENT_HANDLER(messageerror)
+
+ private:
+ ~WorkerDebuggerGlobalScope() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+inline nsISupports* ToSupports(mozilla::dom::WorkerGlobalScope* aScope) {
+ return static_cast<mozilla::dom::EventTarget*>(aScope);
+}
+
+#endif /* mozilla_dom_workerscope_h__ */
diff --git a/dom/workers/WorkerStatus.h b/dom/workers/WorkerStatus.h
new file mode 100644
index 0000000000..696abc5d2c
--- /dev/null
+++ b/dom/workers/WorkerStatus.h
@@ -0,0 +1,58 @@
+/* -*- 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_WorkerStatus_h
+#define mozilla_dom_workers_WorkerStatus_h
+
+namespace mozilla::dom {
+
+/**
+ * Use this chart to help figure out behavior during each of the closing
+ * statuses. Details below.
+ *
+ * +========================================================+
+ * | Closing Statuses |
+ * +=============+=============+=================+==========+
+ * | status | clear queue | abort execution | notified |
+ * +=============+=============+=================+==========+
+ * | Closing | yes | no | no |
+ * +-------------+-------------+-----------------+----------+
+ * | Canceling | yes | yes | yes |
+ * +-------------+-------------+-----------------+----------+
+ * | Killing | yes | yes | yes |
+ * +-------------+-------------+-----------------+----------+
+ */
+
+enum WorkerStatus {
+ // Not yet scheduled.
+ Pending = 0,
+
+ // This status means that the worker is active.
+ Running,
+
+ // Inner script called close() on the worker global scope. Setting this
+ // status causes the worker to clear its queue of events but does not abort
+ // the currently running script. WorkerRef objects are not going to be
+ // notified because the behavior of APIs/Components should not change during
+ // this status yet.
+ Closing,
+
+ // Either the user navigated away from the owning page or the owning page fell
+ // out of bfcache. Setting this status causes the worker to abort immediately.
+ // Since the page has gone away the worker may not post any messages.
+ Canceling,
+
+ // The application is shutting down. Setting this status causes the worker to
+ // abort immediately.
+ Killing,
+
+ // The worker is effectively dead.
+ Dead
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_workers_WorkerStatus_h */
diff --git a/dom/workers/WorkerTestUtils.cpp b/dom/workers/WorkerTestUtils.cpp
new file mode 100644
index 0000000000..81fa41b03c
--- /dev/null
+++ b/dom/workers/WorkerTestUtils.cpp
@@ -0,0 +1,21 @@
+/* -*- 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/ErrorResult.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerTestUtils.h"
+
+namespace mozilla::dom {
+
+uint32_t WorkerTestUtils::CurrentTimerNestingLevel(const GlobalObject& aGlobal,
+ ErrorResult& aErr) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ return worker->GetCurrentTimerNestingLevel();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/workers/WorkerTestUtils.h b/dom/workers/WorkerTestUtils.h
new file mode 100644
index 0000000000..6668fdf1f7
--- /dev/null
+++ b/dom/workers/WorkerTestUtils.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_WorkerTestUtils__
+#define mozilla_dom_WorkerTestUtils__
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+/**
+ * dom/webidl/WorkerTestUtils.webidl defines APIs to expose worker's internal
+ * status for glass-box testing. The APIs are only exposed to Workers with prefs
+ * dom.workers.testing.enabled.
+ *
+ * WorkerTestUtils is the implementation of dom/webidl/WorkerTestUtils.webidl
+ */
+class WorkerTestUtils final {
+ public:
+ /**
+ * Expose the worker's current timer nesting level.
+ *
+ * The worker's current timer nesting level means the executing timer
+ * handler's timer nesting level. When there is no executing timer handler, 0
+ * should be returned by this API. The maximum timer nesting level is 5.
+ *
+ * https://html.spec.whatwg.org/#timer-initialisation-steps
+ */
+ static uint32_t CurrentTimerNestingLevel(const GlobalObject&,
+ ErrorResult& aErr);
+};
+
+} // namespace dom
+} // namespace mozilla
+#endif
diff --git a/dom/workers/WorkerThread.cpp b/dom/workers/WorkerThread.cpp
new file mode 100644
index 0000000000..b6f8674343
--- /dev/null
+++ b/dom/workers/WorkerThread.cpp
@@ -0,0 +1,378 @@
+/* -*- 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 "WorkerThread.h"
+
+#include <utility>
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/PerformanceCounter.h"
+#include "mozilla/ThreadEventQueue.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsICancelableRunnable.h"
+#include "nsIEventTarget.h"
+#include "nsIRunnable.h"
+#include "nsIThreadInternal.h"
+#include "nsString.h"
+#include "prthread.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+namespace {
+
+// The C stack size. We use the same stack size on all platforms for
+// consistency.
+//
+// Note: Our typical equation of 256 machine words works out to 2MB on 64-bit
+// platforms. Since that works out to the size of a VM huge page, that can
+// sometimes lead to an OS allocating an entire huge page for the stack at once.
+// To avoid this, we subtract the size of 2 pages, to be safe.
+const uint32_t kWorkerStackSize = 256 * sizeof(size_t) * 1024 - 8192;
+
+} // namespace
+
+WorkerThreadFriendKey::WorkerThreadFriendKey() {
+ MOZ_COUNT_CTOR(WorkerThreadFriendKey);
+}
+
+WorkerThreadFriendKey::~WorkerThreadFriendKey() {
+ MOZ_COUNT_DTOR(WorkerThreadFriendKey);
+}
+
+class WorkerThread::Observer final : public nsIThreadObserver {
+ WorkerPrivate* mWorkerPrivate;
+
+ public:
+ explicit Observer(WorkerPrivate* aWorkerPrivate)
+ : mWorkerPrivate(aWorkerPrivate) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ private:
+ ~Observer() { mWorkerPrivate->AssertIsOnWorkerThread(); }
+
+ NS_DECL_NSITHREADOBSERVER
+};
+
+WorkerThread::WorkerThread(ConstructorKey)
+ : nsThread(
+ MakeNotNull<ThreadEventQueue*>(MakeUnique<mozilla::EventQueue>()),
+ nsThread::NOT_MAIN_THREAD, {.stackSize = kWorkerStackSize}),
+ mLock("WorkerThread::mLock"),
+ mWorkerPrivateCondVar(mLock, "WorkerThread::mWorkerPrivateCondVar"),
+ mWorkerPrivate(nullptr),
+ mOtherThreadsDispatchingViaEventTarget(0)
+#ifdef DEBUG
+ ,
+ mAcceptingNonWorkerRunnables(true)
+#endif
+{
+}
+
+WorkerThread::~WorkerThread() {
+ MOZ_ASSERT(!mWorkerPrivate);
+ MOZ_ASSERT(!mOtherThreadsDispatchingViaEventTarget);
+ MOZ_ASSERT(mAcceptingNonWorkerRunnables);
+}
+
+// static
+SafeRefPtr<WorkerThread> WorkerThread::Create(
+ const WorkerThreadFriendKey& /* aKey */) {
+ SafeRefPtr<WorkerThread> thread =
+ MakeSafeRefPtr<WorkerThread>(ConstructorKey());
+ if (NS_FAILED(thread->Init("DOM Worker"_ns))) {
+ NS_WARNING("Failed to create new thread!");
+ return nullptr;
+ }
+
+ return thread;
+}
+
+void WorkerThread::SetWorker(const WorkerThreadFriendKey& /* aKey */,
+ WorkerPrivate* aWorkerPrivate) {
+ MOZ_ASSERT(PR_GetCurrentThread() == mThread);
+
+ if (aWorkerPrivate) {
+ {
+ MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(!mWorkerPrivate);
+ MOZ_ASSERT(mAcceptingNonWorkerRunnables);
+
+ mWorkerPrivate = aWorkerPrivate;
+#ifdef DEBUG
+ mAcceptingNonWorkerRunnables = false;
+#endif
+ }
+
+ mObserver = new Observer(aWorkerPrivate);
+ MOZ_ALWAYS_SUCCEEDS(AddObserver(mObserver));
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(RemoveObserver(mObserver));
+ mObserver = nullptr;
+
+ {
+ MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(mWorkerPrivate);
+ MOZ_ASSERT(!mAcceptingNonWorkerRunnables);
+ // mOtherThreadsDispatchingViaEventTarget can still be non-zero here
+ // because WorkerThread::Dispatch isn't atomic so a thread initiating
+ // dispatch can have dispatched a runnable at this thread allowing us to
+ // begin shutdown before that thread gets a chance to decrement
+ // mOtherThreadsDispatchingViaEventTarget back to 0. So we need to wait
+ // for that.
+ while (mOtherThreadsDispatchingViaEventTarget) {
+ mWorkerPrivateCondVar.Wait();
+ }
+
+#ifdef DEBUG
+ mAcceptingNonWorkerRunnables = true;
+#endif
+ mWorkerPrivate = nullptr;
+ }
+ }
+}
+
+void WorkerThread::IncrementDispatchCounter() {
+ MutexAutoLock lock(mLock);
+ if (mWorkerPrivate) {
+ mWorkerPrivate->MutablePerformanceCounterRef().IncrementDispatchCounter(
+ DispatchCategory::Worker);
+ }
+}
+
+nsresult WorkerThread::DispatchPrimaryRunnable(
+ const WorkerThreadFriendKey& /* aKey */,
+ already_AddRefed<nsIRunnable> aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+
+#ifdef DEBUG
+ MOZ_ASSERT(PR_GetCurrentThread() != mThread);
+ MOZ_ASSERT(runnable);
+ {
+ MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(!mWorkerPrivate);
+ MOZ_ASSERT(mAcceptingNonWorkerRunnables);
+ }
+#endif
+
+ nsresult rv = nsThread::Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult WorkerThread::DispatchAnyThread(
+ const WorkerThreadFriendKey& /* aKey */,
+ already_AddRefed<WorkerRunnable> aWorkerRunnable) {
+ // May be called on any thread!
+
+#ifdef DEBUG
+ {
+ const bool onWorkerThread = PR_GetCurrentThread() == mThread;
+ {
+ MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(mWorkerPrivate);
+ MOZ_ASSERT(!mAcceptingNonWorkerRunnables);
+
+ if (onWorkerThread) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+ }
+ }
+#endif
+
+ // Increment the PerformanceCounter dispatch count
+ // to keep track of how many runnables are executed.
+ IncrementDispatchCounter();
+ nsCOMPtr<nsIRunnable> runnable(aWorkerRunnable);
+
+ nsresult rv = nsThread::Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // We don't need to notify the worker's condition variable here because we're
+ // being called from worker-controlled code and it will make sure to wake up
+ // the worker thread if needed.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerThread::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) {
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+ return Dispatch(runnable.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+WorkerThread::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aFlags) {
+ // May be called on any thread!
+ nsCOMPtr<nsIRunnable> runnable(aRunnable); // in case we exit early
+
+ // Workers only support asynchronous dispatch.
+ if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ const bool onWorkerThread = PR_GetCurrentThread() == mThread;
+
+#ifdef DEBUG
+ if (runnable && !onWorkerThread) {
+ nsCOMPtr<nsIDiscardableRunnable> discardable = do_QueryInterface(runnable);
+
+ {
+ MutexAutoLock lock(mLock);
+
+ // Only enforce discardable runnables after we've started the worker loop.
+ if (!mAcceptingNonWorkerRunnables) {
+ MOZ_ASSERT(
+ discardable,
+ "Only nsIDiscardableRunnable may be dispatched to a worker!");
+ }
+ }
+ }
+#endif
+
+ WorkerPrivate* workerPrivate = nullptr;
+ if (onWorkerThread) {
+ // No need to lock here because it is only modified on this thread.
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ workerPrivate = mWorkerPrivate;
+ } else {
+ MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(mOtherThreadsDispatchingViaEventTarget < UINT32_MAX);
+
+ if (mWorkerPrivate) {
+ workerPrivate = mWorkerPrivate;
+
+ // Incrementing this counter will make the worker thread sleep if it
+ // somehow tries to unset mWorkerPrivate while we're using it.
+ mOtherThreadsDispatchingViaEventTarget++;
+ }
+ }
+
+ // Increment the PerformanceCounter dispatch count
+ // to keep track of how many runnables are executed.
+ IncrementDispatchCounter();
+ nsresult rv;
+ if (runnable && onWorkerThread) {
+ RefPtr<WorkerRunnable> workerRunnable =
+ workerPrivate->MaybeWrapAsWorkerRunnable(runnable.forget());
+ rv = nsThread::Dispatch(workerRunnable.forget(), NS_DISPATCH_NORMAL);
+ } else {
+ rv = nsThread::Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ }
+
+ if (!onWorkerThread && workerPrivate) {
+ // We need to wake the worker thread if we're not already on the right
+ // thread and the dispatch succeeded.
+ if (NS_SUCCEEDED(rv)) {
+ MutexAutoLock workerLock(workerPrivate->mMutex);
+
+ workerPrivate->mCondVar.Notify();
+ }
+
+ // Now unset our waiting flag.
+ {
+ MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(mOtherThreadsDispatchingViaEventTarget);
+
+ if (!--mOtherThreadsDispatchingViaEventTarget) {
+ mWorkerPrivateCondVar.Notify();
+ }
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerThread::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t WorkerThread::RecursionDepth(
+ const WorkerThreadFriendKey& /* aKey */) const {
+ MOZ_ASSERT(PR_GetCurrentThread() == mThread);
+
+ return mNestedEventLoopDepth;
+}
+
+PerformanceCounter* WorkerThread::GetPerformanceCounter(nsIRunnable*) const {
+ return mWorkerPrivate ? &mWorkerPrivate->MutablePerformanceCounterRef()
+ : nullptr;
+}
+
+NS_IMPL_ISUPPORTS(WorkerThread::Observer, nsIThreadObserver)
+
+NS_IMETHODIMP
+WorkerThread::Observer::OnDispatchedEvent() {
+ MOZ_CRASH("OnDispatchedEvent() should never be called!");
+}
+
+NS_IMETHODIMP
+WorkerThread::Observer::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
+ bool aMayWait) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ // If the PBackground child is not created yet, then we must permit
+ // blocking event processing to support
+ // BackgroundChild::GetOrCreateCreateForCurrentThread(). If this occurs
+ // then we are spinning on the event queue at the start of
+ // PrimaryWorkerRunnable::Run() and don't want to process the event in
+ // mWorkerPrivate yet.
+ if (aMayWait) {
+ MOZ_ASSERT(CycleCollectedJSContext::Get()->RecursionDepth() == 2);
+ MOZ_ASSERT(!BackgroundChild::GetForCurrentThread());
+ return NS_OK;
+ }
+
+ mWorkerPrivate->OnProcessNextEvent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerThread::Observer::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
+ bool /* aEventWasProcessed */) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ mWorkerPrivate->AfterProcessNextEvent();
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/WorkerThread.h b/dom/workers/WorkerThread.h
new file mode 100644
index 0000000000..a0bbf2369a
--- /dev/null
+++ b/dom/workers/WorkerThread.h
@@ -0,0 +1,112 @@
+/* -*- 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_WorkerThread_h__
+#define mozilla_dom_workers_WorkerThread_h__
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/PerformanceCounter.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/SafeRefPtr.h"
+#include "nsISupports.h"
+#include "nsThread.h"
+#include "nscore.h"
+
+class nsIRunnable;
+
+namespace mozilla {
+class Runnable;
+
+namespace dom {
+
+class WorkerRunnable;
+class WorkerPrivate;
+template <class>
+class WorkerPrivateParent;
+
+namespace workerinternals {
+class RuntimeService;
+}
+
+// This class lets us restrict the public methods that can be called on
+// WorkerThread to RuntimeService and WorkerPrivate without letting them gain
+// full access to private methods (as would happen if they were simply friends).
+class WorkerThreadFriendKey {
+ friend class workerinternals::RuntimeService;
+ friend class WorkerPrivate;
+ friend class WorkerPrivateParent<WorkerPrivate>;
+
+ WorkerThreadFriendKey();
+ ~WorkerThreadFriendKey();
+};
+
+class WorkerThread final : public nsThread {
+ class Observer;
+
+ Mutex mLock MOZ_UNANNOTATED;
+ CondVar mWorkerPrivateCondVar;
+
+ // Protected by nsThread::mLock.
+ WorkerPrivate* mWorkerPrivate;
+
+ // Only touched on the target thread.
+ RefPtr<Observer> mObserver;
+
+ // Protected by nsThread::mLock and waited on with mWorkerPrivateCondVar.
+ uint32_t mOtherThreadsDispatchingViaEventTarget;
+
+#ifdef DEBUG
+ // Protected by nsThread::mLock.
+ bool mAcceptingNonWorkerRunnables;
+#endif
+
+ // Using this struct we restrict access to the constructor while still being
+ // able to use MakeSafeRefPtr.
+ struct ConstructorKey {};
+
+ public:
+ explicit WorkerThread(ConstructorKey);
+
+ static SafeRefPtr<WorkerThread> Create(const WorkerThreadFriendKey& aKey);
+
+ void SetWorker(const WorkerThreadFriendKey& aKey,
+ WorkerPrivate* aWorkerPrivate);
+
+ nsresult DispatchPrimaryRunnable(const WorkerThreadFriendKey& aKey,
+ already_AddRefed<nsIRunnable> aRunnable);
+
+ nsresult DispatchAnyThread(const WorkerThreadFriendKey& aKey,
+ already_AddRefed<WorkerRunnable> aWorkerRunnable);
+
+ uint32_t RecursionDepth(const WorkerThreadFriendKey& aKey) const;
+
+ PerformanceCounter* GetPerformanceCounter(nsIRunnable* aEvent) const override;
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerThread, nsThread)
+
+ private:
+ ~WorkerThread();
+
+ // This should only be called by consumers that have an
+ // nsIEventTarget/nsIThread pointer.
+ NS_IMETHOD
+ Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) override;
+
+ NS_IMETHOD
+ DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) override;
+
+ NS_IMETHOD
+ DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) override;
+
+ void IncrementDispatchCounter();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_WorkerThread_h__
diff --git a/dom/workers/loader/CacheLoadHandler.cpp b/dom/workers/loader/CacheLoadHandler.cpp
new file mode 100644
index 0000000000..3e43ce224c
--- /dev/null
+++ b/dom/workers/loader/CacheLoadHandler.cpp
@@ -0,0 +1,651 @@
+/* -*- 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 "CacheLoadHandler.h"
+#include "ScriptResponseHeaderProcessor.h" // ScriptResponseHeaderProcessor
+#include "WorkerLoadContext.h" // WorkerLoadContext
+
+#include "nsIPrincipal.h"
+
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIXPConnect.h"
+
+#include "jsapi.h"
+#include "nsNetUtil.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/dom/CacheBinding.h"
+#include "mozilla/dom/cache/CacheTypes.h"
+#include "mozilla/dom/Response.h"
+#include "mozilla/dom/ServiceWorkerBinding.h" // ServiceWorkerState
+#include "mozilla/Result.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/WorkerScope.h"
+
+#include "mozilla/dom/workerinternals/ScriptLoader.h" // WorkerScriptLoader
+
+namespace mozilla {
+namespace dom {
+
+namespace workerinternals::loader {
+
+NS_IMPL_ISUPPORTS0(CacheCreator)
+
+NS_IMPL_ISUPPORTS(CacheLoadHandler, nsIStreamLoaderObserver)
+
+NS_IMPL_ISUPPORTS0(CachePromiseHandler)
+
+CachePromiseHandler::CachePromiseHandler(
+ WorkerScriptLoader* aLoader, ThreadSafeRequestHandle* aRequestHandle)
+ : mLoader(aLoader), mRequestHandle(aRequestHandle) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mLoader);
+}
+
+void CachePromiseHandler::ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ AssertIsOnMainThread();
+ if (mRequestHandle->IsEmpty()) {
+ return;
+ }
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ // May already have been canceled by CacheLoadHandler::Fail from
+ // CancelMainThread.
+ MOZ_ASSERT(loadContext->mCacheStatus == WorkerLoadContext::WritingToCache ||
+ loadContext->mCacheStatus == WorkerLoadContext::Cancel);
+ MOZ_ASSERT_IF(loadContext->mCacheStatus == WorkerLoadContext::Cancel,
+ !loadContext->mCachePromise);
+
+ if (loadContext->mCachePromise) {
+ loadContext->mCacheStatus = WorkerLoadContext::Cached;
+ loadContext->mCachePromise = nullptr;
+ mRequestHandle->MaybeExecuteFinishedScripts();
+ }
+}
+
+void CachePromiseHandler::RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ AssertIsOnMainThread();
+ if (mRequestHandle->IsEmpty()) {
+ return;
+ }
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ // May already have been canceled by CacheLoadHandler::Fail from
+ // CancelMainThread.
+ MOZ_ASSERT(loadContext->mCacheStatus == WorkerLoadContext::WritingToCache ||
+ loadContext->mCacheStatus == WorkerLoadContext::Cancel);
+ loadContext->mCacheStatus = WorkerLoadContext::Cancel;
+
+ loadContext->mCachePromise = nullptr;
+
+ // This will delete the cache object and will call LoadingFinished() with an
+ // error for each ongoing operation.
+ auto* cacheCreator = mRequestHandle->GetCacheCreator();
+ if (cacheCreator) {
+ cacheCreator->DeleteCache(NS_ERROR_FAILURE);
+ }
+}
+
+CacheCreator::CacheCreator(WorkerPrivate* aWorkerPrivate)
+ : mCacheName(aWorkerPrivate->ServiceWorkerCacheName()),
+ mOriginAttributes(aWorkerPrivate->GetOriginAttributes()) {
+ MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
+}
+
+nsresult CacheCreator::CreateCacheStorage(nsIPrincipal* aPrincipal) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mCacheStorage);
+ MOZ_ASSERT(aPrincipal);
+
+ nsIXPConnect* xpc = nsContentUtils::XPConnect();
+ MOZ_ASSERT(xpc, "This should never be null!");
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> sandbox(cx);
+ nsresult rv = xpc->CreateSandbox(cx, aPrincipal, sandbox.address());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // The JSContext is not in a realm, so CreateSandbox returned an unwrapped
+ // global.
+ MOZ_ASSERT(JS_IsGlobalObject(sandbox));
+
+ mSandboxGlobalObject = xpc::NativeGlobal(sandbox);
+ if (NS_WARN_IF(!mSandboxGlobalObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we're in private browsing mode, don't even try to create the
+ // CacheStorage. Instead, just fail immediately to terminate the
+ // ServiceWorker load.
+ if (NS_WARN_IF(mOriginAttributes.mPrivateBrowsingId > 0)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // Create a CacheStorage bypassing its trusted origin checks. The
+ // ServiceWorker has already performed its own checks before getting
+ // to this point.
+ ErrorResult error;
+ mCacheStorage = CacheStorage::CreateOnMainThread(
+ mozilla::dom::cache::CHROME_ONLY_NAMESPACE, mSandboxGlobalObject,
+ aPrincipal, true /* force trusted origin */, error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheCreator::Load(nsIPrincipal* aPrincipal) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mLoaders.IsEmpty());
+
+ nsresult rv = CreateCacheStorage(aPrincipal);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ ErrorResult error;
+ MOZ_ASSERT(!mCacheName.IsEmpty());
+ RefPtr<Promise> promise = mCacheStorage->Open(mCacheName, error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ promise->AppendNativeHandler(this);
+ return NS_OK;
+}
+
+void CacheCreator::FailLoaders(nsresult aRv) {
+ AssertIsOnMainThread();
+
+ // Fail() can call LoadingFinished() which may call ExecuteFinishedScripts()
+ // which sets mCacheCreator to null, so hold a ref.
+ RefPtr<CacheCreator> kungfuDeathGrip = this;
+
+ for (uint32_t i = 0, len = mLoaders.Length(); i < len; ++i) {
+ mLoaders[i]->Fail(aRv);
+ }
+
+ mLoaders.Clear();
+}
+
+void CacheCreator::RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ AssertIsOnMainThread();
+ FailLoaders(NS_ERROR_FAILURE);
+}
+
+void CacheCreator::ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ AssertIsOnMainThread();
+ if (!aValue.isObject()) {
+ FailLoaders(NS_ERROR_FAILURE);
+ return;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+ Cache* cache = nullptr;
+ nsresult rv = UNWRAP_OBJECT(Cache, &obj, cache);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailLoaders(NS_ERROR_FAILURE);
+ return;
+ }
+
+ mCache = cache;
+ MOZ_DIAGNOSTIC_ASSERT(mCache);
+
+ // If the worker is canceled, CancelMainThread() will have cleared the
+ // loaders via DeleteCache().
+ for (uint32_t i = 0, len = mLoaders.Length(); i < len; ++i) {
+ mLoaders[i]->Load(cache);
+ }
+}
+
+void CacheCreator::DeleteCache(nsresult aReason) {
+ AssertIsOnMainThread();
+
+ // This is called when the load is canceled which can occur before
+ // mCacheStorage is initialized.
+ if (mCacheStorage) {
+ // It's safe to do this while Cache::Match() and Cache::Put() calls are
+ // running.
+ RefPtr<Promise> promise = mCacheStorage->Delete(mCacheName, IgnoreErrors());
+
+ // We don't care to know the result of the promise object.
+ }
+
+ // Always call this here to ensure the loaders array is cleared.
+ FailLoaders(NS_ERROR_FAILURE);
+}
+
+CacheLoadHandler::CacheLoadHandler(ThreadSafeWorkerRef* aWorkerRef,
+ ThreadSafeRequestHandle* aRequestHandle,
+ bool aIsWorkerScript,
+ bool aOnlyExistingCachedResourcesAllowed,
+ WorkerScriptLoader* aLoader)
+ : mRequestHandle(aRequestHandle),
+ mLoader(aLoader),
+ mWorkerRef(aWorkerRef),
+ mIsWorkerScript(aIsWorkerScript),
+ mFailed(false),
+ mOnlyExistingCachedResourcesAllowed(aOnlyExistingCachedResourcesAllowed) {
+ MOZ_ASSERT(aWorkerRef);
+ MOZ_ASSERT(aWorkerRef->Private()->IsServiceWorker());
+ mMainThreadEventTarget = aWorkerRef->Private()->MainThreadEventTarget();
+ MOZ_ASSERT(mMainThreadEventTarget);
+ mBaseURI = mLoader->GetBaseURI();
+ AssertIsOnMainThread();
+
+ // Worker scripts are always decoded as UTF-8 per spec.
+ mDecoder = MakeUnique<ScriptDecoder>(UTF_8_ENCODING,
+ ScriptDecoder::BOMHandling::Remove);
+}
+
+void CacheLoadHandler::Fail(nsresult aRv) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(NS_FAILED(aRv));
+
+ if (mFailed) {
+ return;
+ }
+
+ mFailed = true;
+
+ if (mPump) {
+ MOZ_ASSERT_IF(!mRequestHandle->IsEmpty(),
+ mRequestHandle->GetContext()->mCacheStatus ==
+ WorkerLoadContext::ReadingFromCache);
+ mPump->Cancel(aRv);
+ mPump = nullptr;
+ }
+ if (mRequestHandle->IsEmpty()) {
+ return;
+ }
+
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ loadContext->mCacheStatus = WorkerLoadContext::Cancel;
+
+ if (loadContext->mCachePromise) {
+ loadContext->mCachePromise->MaybeReject(aRv);
+ }
+
+ loadContext->mCachePromise = nullptr;
+
+ mRequestHandle->LoadingFinished(aRv);
+}
+
+void CacheLoadHandler::Load(Cache* aCache) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aCache);
+ MOZ_ASSERT(!mRequestHandle->IsEmpty());
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), loadContext->mRequest->mURL,
+ nullptr, mBaseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ nsAutoCString spec;
+ rv = uri->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ MOZ_ASSERT(loadContext->mFullURL.IsEmpty());
+ CopyUTF8toUTF16(spec, loadContext->mFullURL);
+
+ mozilla::dom::RequestOrUSVString request;
+ request.SetAsUSVString().ShareOrDependUpon(loadContext->mFullURL);
+
+ mozilla::dom::CacheQueryOptions params;
+
+ // This JSContext will not end up executing JS code because here there are
+ // no ReadableStreams involved.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ ErrorResult error;
+ RefPtr<Promise> promise = aCache->Match(jsapi.cx(), request, params, error);
+ if (NS_WARN_IF(error.Failed())) {
+ Fail(error.StealNSResult());
+ return;
+ }
+
+ promise->AppendNativeHandler(this);
+}
+
+void CacheLoadHandler::RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mRequestHandle->IsEmpty());
+
+ MOZ_ASSERT(mRequestHandle->GetContext()->mCacheStatus ==
+ WorkerLoadContext::Uncached);
+ Fail(NS_ERROR_FAILURE);
+}
+
+void CacheLoadHandler::ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mRequestHandle->IsEmpty());
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ // If we have already called 'Fail', we should not proceed. If we cancelled,
+ // we should similarily not proceed.
+ if (mFailed) {
+ return;
+ }
+
+ MOZ_ASSERT(loadContext->mCacheStatus == WorkerLoadContext::Uncached);
+
+ nsresult rv;
+
+ // The ServiceWorkerScriptCache will store data for any scripts it
+ // it knows about. This is always at least the top level script.
+ // Depending on if a previous version of the service worker has
+ // been installed or not it may also know about importScripts(). We
+ // must handle loading and offlining new importScripts() here, however.
+ if (aValue.isUndefined()) {
+ // If this is the main script or we're not loading a new service worker
+ // then this is an error. This can happen for internal reasons, like
+ // storage was probably wiped without removing the service worker
+ // registration. It can also happen for exposed reasons like the
+ // service worker script calling importScripts() after install.
+ if (NS_WARN_IF(mIsWorkerScript || mOnlyExistingCachedResourcesAllowed)) {
+ Fail(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ loadContext->mCacheStatus = WorkerLoadContext::ToBeCached;
+ rv = mLoader->LoadScript(mRequestHandle);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ }
+ return;
+ }
+
+ MOZ_ASSERT(aValue.isObject());
+
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+ mozilla::dom::Response* response = nullptr;
+ rv = UNWRAP_OBJECT(Response, &obj, response);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ InternalHeaders* headers = response->GetInternalHeaders();
+
+ headers->Get("content-security-policy"_ns, mCSPHeaderValue, IgnoreErrors());
+ headers->Get("content-security-policy-report-only"_ns,
+ mCSPReportOnlyHeaderValue, IgnoreErrors());
+ headers->Get("referrer-policy"_ns, mReferrerPolicyHeaderValue,
+ IgnoreErrors());
+
+ nsAutoCString coepHeader;
+ headers->Get("cross-origin-embedder-policy"_ns, coepHeader, IgnoreErrors());
+
+ nsILoadInfo::CrossOriginEmbedderPolicy coep =
+ NS_GetCrossOriginEmbedderPolicyFromHeader(
+ coepHeader, mWorkerRef->Private()->Trials().IsEnabled(
+ OriginTrial::CoepCredentialless));
+
+ rv = ScriptResponseHeaderProcessor::ProcessCrossOriginEmbedderPolicyHeader(
+ mWorkerRef->Private(), coep, loadContext->IsTopLevel());
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ response->GetBody(getter_AddRefs(inputStream));
+ mChannelInfo = response->GetChannelInfo();
+ const UniquePtr<PrincipalInfo>& pInfo = response->GetPrincipalInfo();
+ if (pInfo) {
+ mPrincipalInfo = mozilla::MakeUnique<PrincipalInfo>(*pInfo);
+ }
+
+ if (!inputStream) {
+ loadContext->mCacheStatus = WorkerLoadContext::Cached;
+
+ if (mRequestHandle->IsCancelled()) {
+ auto* cacheCreator = mRequestHandle->GetCacheCreator();
+ if (cacheCreator) {
+ cacheCreator->DeleteCache(mRequestHandle->GetCancelResult());
+ }
+ return;
+ }
+
+ nsresult rv = DataReceivedFromCache(
+ (uint8_t*)"", 0, mChannelInfo, std::move(mPrincipalInfo),
+ mCSPHeaderValue, mCSPReportOnlyHeaderValue, mReferrerPolicyHeaderValue);
+
+ mRequestHandle->OnStreamComplete(rv);
+ return;
+ }
+
+ MOZ_ASSERT(!mPump);
+ rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inputStream.forget(),
+ 0, /* default segsize */
+ 0, /* default segcount */
+ false, /* default closeWhenDone */
+ mMainThreadEventTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ rv = mPump->AsyncRead(loader);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mPump = nullptr;
+ Fail(rv);
+ return;
+ }
+
+ nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(mPump);
+ if (rr) {
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ RefPtr<TaskQueue> queue =
+ TaskQueue::Create(sts.forget(), "CacheLoadHandler STS Delivery Queue");
+ rv = rr->RetargetDeliveryTo(queue);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the nsIInputStreamPump to a IO thread.");
+ }
+ }
+
+ loadContext->mCacheStatus = WorkerLoadContext::ReadingFromCache;
+}
+
+NS_IMETHODIMP
+CacheLoadHandler::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* aContext, nsresult aStatus,
+ uint32_t aStringLen,
+ const uint8_t* aString) {
+ AssertIsOnMainThread();
+ if (mRequestHandle->IsEmpty()) {
+ return NS_OK;
+ }
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ mPump = nullptr;
+
+ if (NS_FAILED(aStatus)) {
+ MOZ_ASSERT(loadContext->mCacheStatus ==
+ WorkerLoadContext::ReadingFromCache ||
+ loadContext->mCacheStatus == WorkerLoadContext::Cancel);
+ Fail(aStatus);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(loadContext->mCacheStatus == WorkerLoadContext::ReadingFromCache);
+ loadContext->mCacheStatus = WorkerLoadContext::Cached;
+
+ MOZ_ASSERT(mPrincipalInfo);
+
+ nsresult rv = DataReceivedFromCache(
+ aString, aStringLen, mChannelInfo, std::move(mPrincipalInfo),
+ mCSPHeaderValue, mCSPReportOnlyHeaderValue, mReferrerPolicyHeaderValue);
+ return mRequestHandle->OnStreamComplete(rv);
+}
+
+nsresult CacheLoadHandler::DataReceivedFromCache(
+ const uint8_t* aString, uint32_t aStringLen,
+ const mozilla::dom::ChannelInfo& aChannelInfo,
+ UniquePtr<PrincipalInfo> aPrincipalInfo, const nsACString& aCSPHeaderValue,
+ const nsACString& aCSPReportOnlyHeaderValue,
+ const nsACString& aReferrerPolicyHeaderValue) {
+ AssertIsOnMainThread();
+ if (mRequestHandle->IsEmpty()) {
+ return NS_OK;
+ }
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ MOZ_ASSERT(loadContext->mCacheStatus == WorkerLoadContext::Cached);
+ MOZ_ASSERT(loadContext->mRequest);
+
+ auto responsePrincipalOrErr = PrincipalInfoToPrincipal(*aPrincipalInfo);
+ MOZ_DIAGNOSTIC_ASSERT(responsePrincipalOrErr.isOk());
+
+ nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal();
+ if (!principal) {
+ WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent();
+ MOZ_ASSERT(parentWorker, "Must have a parent!");
+ principal = parentWorker->GetPrincipal();
+ }
+
+ nsCOMPtr<nsIPrincipal> responsePrincipal = responsePrincipalOrErr.unwrap();
+
+ loadContext->mMutedErrorFlag.emplace(!principal->Subsumes(responsePrincipal));
+
+ // May be null.
+ Document* parentDoc = mWorkerRef->Private()->GetDocument();
+
+ // Use the regular ScriptDecoder Decoder for this grunt work! Should be just
+ // fine because we're running on the main thread.
+ nsresult rv;
+
+ // Set the Source type to "text" for decoding.
+ loadContext->mRequest->SetTextSource();
+
+ rv = mDecoder->DecodeRawData(loadContext->mRequest, aString, aStringLen,
+ /* aEndOfStream = */ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!loadContext->mRequest->ScriptTextLength()) {
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
+ parentDoc, nsContentUtils::eDOM_PROPERTIES,
+ "EmptyWorkerSourceWarning");
+ }
+
+ nsCOMPtr<nsIURI> finalURI;
+ rv = NS_NewURI(getter_AddRefs(finalURI), loadContext->mFullURL);
+ if (!loadContext->mRequest->mBaseURL) {
+ loadContext->mRequest->mBaseURL = finalURI;
+ }
+ if (loadContext->IsTopLevel()) {
+ if (NS_SUCCEEDED(rv)) {
+ mWorkerRef->Private()->SetBaseURI(finalURI);
+ }
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal();
+ MOZ_DIAGNOSTIC_ASSERT(principal);
+
+ bool equal = false;
+ MOZ_ALWAYS_SUCCEEDS(responsePrincipal->Equals(principal, &equal));
+ MOZ_DIAGNOSTIC_ASSERT(equal);
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ if (parentDoc) {
+ csp = parentDoc->GetCsp();
+ }
+ MOZ_DIAGNOSTIC_ASSERT(!csp);
+#endif
+
+ mWorkerRef->Private()->InitChannelInfo(aChannelInfo);
+
+ nsILoadGroup* loadGroup = mWorkerRef->Private()->GetLoadGroup();
+ MOZ_DIAGNOSTIC_ASSERT(loadGroup);
+
+ // Override the principal on the WorkerPrivate. This is only necessary
+ // in order to get a principal with exactly the correct URL. The fetch
+ // referrer logic depends on the WorkerPrivate principal having a URL
+ // that matches the worker script URL. If bug 1340694 is ever fixed
+ // this can be removed.
+ // XXX: force the partitionedPrincipal to be equal to the response one.
+ // This is OK for now because we don't want to expose partitionedPrincipal
+ // functionality in ServiceWorkers yet.
+ rv = mWorkerRef->Private()->SetPrincipalsAndCSPOnMainThread(
+ responsePrincipal, responsePrincipal, loadGroup, nullptr);
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = mWorkerRef->Private()->SetCSPFromHeaderValues(
+ aCSPHeaderValue, aCSPReportOnlyHeaderValue);
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ mWorkerRef->Private()->UpdateReferrerInfoFromHeader(
+ aReferrerPolicyHeaderValue);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ DataReceived();
+ }
+
+ return rv;
+}
+
+void CacheLoadHandler::DataReceived() {
+ MOZ_ASSERT(!mRequestHandle->IsEmpty());
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ if (loadContext->IsTopLevel()) {
+ WorkerPrivate* parent = mWorkerRef->Private()->GetParent();
+
+ if (parent) {
+ // XHR Params Allowed
+ mWorkerRef->Private()->SetXHRParamsAllowed(parent->XHRParamsAllowed());
+
+ // Set Eval and ContentSecurityPolicy
+ mWorkerRef->Private()->SetCsp(parent->GetCsp());
+ mWorkerRef->Private()->SetEvalAllowed(parent->IsEvalAllowed());
+ mWorkerRef->Private()->SetWasmEvalAllowed(parent->IsWasmEvalAllowed());
+ }
+ }
+}
+
+} // namespace workerinternals::loader
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/loader/CacheLoadHandler.h b/dom/workers/loader/CacheLoadHandler.h
new file mode 100644
index 0000000000..b1e164b79e
--- /dev/null
+++ b/dom/workers/loader/CacheLoadHandler.h
@@ -0,0 +1,221 @@
+/* -*- 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_CacheLoadHandler_h__
+#define mozilla_dom_workers_CacheLoadHandler_h__
+
+#include "nsIContentPolicy.h"
+#include "nsIInputStreamPump.h"
+#include "nsIStreamLoader.h"
+#include "nsStringFwd.h"
+#include "nsStreamUtils.h"
+
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/dom/CacheBinding.h"
+#include "mozilla/dom/ChannelInfo.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/ScriptLoadHandler.h"
+#include "mozilla/dom/cache/Cache.h"
+#include "mozilla/dom/cache/CacheStorage.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerRef.h"
+
+#include "mozilla/dom/workerinternals/ScriptLoader.h"
+
+using mozilla::dom::cache::Cache;
+using mozilla::dom::cache::CacheStorage;
+using mozilla::ipc::PrincipalInfo;
+
+namespace mozilla::dom {
+
+class WorkerLoadContext;
+
+namespace workerinternals::loader {
+
+/*
+ * [DOMDOC] CacheLoadHandler for Workers
+ *
+ * A LoadHandler is a ScriptLoader helper class that reacts to an
+ * nsIStreamLoader's events for loading JS scripts. It is primarily responsible
+ * for decoding the stream into UTF8 or UTF16. Additionally, it takes care of
+ * any work that needs to follow the completion of a stream. Every LoadHandler
+ * also manages additional tasks for the type of load that it is doing.
+ *
+ * CacheLoadHandler is a specialized LoadHandler used by ServiceWorkers to
+ * implement the installation model used by ServiceWorkers to support running
+ * offline. When a ServiceWorker is installed, its main script is evaluated and
+ * all script resources that are loaded are saved. The spec does not specify the
+ * storage mechanism for this, but we chose to reuse the Cache API[1] mechanism
+ * that we expose to content to also store the script and its dependencies. We
+ * store the script resources in a special chrome namespace CacheStorage that is
+ * not visible to content. Each distinct ServiceWorker installation gets its own
+ * Cache keyed by a randomly-generated UUID.
+ *
+ * In terms of specification, this class implements step 4 of
+ * https://w3c.github.io/ServiceWorker/#importscripts
+ *
+ * Relationship to NetworkLoadHandler
+ *
+ * During ServiceWorker installation, the CacheLoadHandler falls back on the
+ * NetworkLoadHandler by calling `mLoader->LoadScript(...)`. If a script has not
+ * been seen before, then we will fall back on loading from the network.
+ * However, if the ServiceWorker is already installed, an error will be
+ * generated and the ServiceWorker will fail to load, per spec.
+ *
+ * CacheLoadHandler does not persist some pieces of information, such as the
+ * sourceMapUrl. Also, the DOM Cache API storage does not yet support alternate
+ * data streams for JS Bytecode or WASM caching; this is tracked by Bug 1336199.
+ *
+ * [1]: https://developer.mozilla.org/en-US/docs/Web/API/caches
+ *
+ */
+
+class CacheLoadHandler final : public PromiseNativeHandler,
+ public nsIStreamLoaderObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+ CacheLoadHandler(ThreadSafeWorkerRef* aWorkerRef,
+ ThreadSafeRequestHandle* aRequestHandle,
+ bool aIsWorkerScript,
+ bool aOnlyExistingCachedResourcesAllowed,
+ WorkerScriptLoader* aLoader);
+
+ void Fail(nsresult aRv);
+
+ void Load(Cache* aCache);
+
+ virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ private:
+ ~CacheLoadHandler() { AssertIsOnMainThread(); }
+
+ nsresult DataReceivedFromCache(const uint8_t* aString, uint32_t aStringLen,
+ const mozilla::dom::ChannelInfo& aChannelInfo,
+ UniquePtr<PrincipalInfo> aPrincipalInfo,
+ const nsACString& aCSPHeaderValue,
+ const nsACString& aCSPReportOnlyHeaderValue,
+ const nsACString& aReferrerPolicyHeaderValue);
+ void DataReceived();
+
+ RefPtr<ThreadSafeRequestHandle> mRequestHandle;
+ const RefPtr<WorkerScriptLoader> mLoader;
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+ const bool mIsWorkerScript;
+ bool mFailed;
+ bool mOnlyExistingCachedResourcesAllowed;
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ nsCOMPtr<nsIURI> mBaseURI;
+ mozilla::dom::ChannelInfo mChannelInfo;
+ UniquePtr<PrincipalInfo> mPrincipalInfo;
+ UniquePtr<ScriptDecoder> mDecoder;
+ nsCString mCSPHeaderValue;
+ nsCString mCSPReportOnlyHeaderValue;
+ nsCString mReferrerPolicyHeaderValue;
+ nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
+};
+
+/*
+ * CacheCreator
+ *
+ * The CacheCreator is responsible for maintaining a CacheStorage for the
+ * purposes of caching ServiceWorkers (see comment on CacheLoadHandler). In
+ * addition, it tracks all CacheLoadHandlers and is used for cleanup once
+ * loading has finished.
+ *
+ */
+
+class CacheCreator final : public PromiseNativeHandler {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit CacheCreator(WorkerPrivate* aWorkerPrivate);
+
+ void AddLoader(MovingNotNull<RefPtr<CacheLoadHandler>> aLoader) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mCacheStorage);
+ mLoaders.AppendElement(std::move(aLoader));
+ }
+
+ virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ // Try to load from cache with aPrincipal used for cache access.
+ nsresult Load(nsIPrincipal* aPrincipal);
+
+ Cache* Cache_() const {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mCache);
+ return mCache;
+ }
+
+ nsIGlobalObject* Global() const {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mSandboxGlobalObject);
+ return mSandboxGlobalObject;
+ }
+
+ void DeleteCache(nsresult aReason);
+
+ private:
+ ~CacheCreator() = default;
+
+ nsresult CreateCacheStorage(nsIPrincipal* aPrincipal);
+
+ void FailLoaders(nsresult aRv);
+
+ RefPtr<Cache> mCache;
+ RefPtr<CacheStorage> mCacheStorage;
+ nsCOMPtr<nsIGlobalObject> mSandboxGlobalObject;
+ nsTArray<NotNull<RefPtr<CacheLoadHandler>>> mLoaders;
+
+ nsString mCacheName;
+ OriginAttributes mOriginAttributes;
+};
+
+/*
+ * CachePromiseHandler
+ *
+ * This promise handler is used to track if a ServiceWorker has been written to
+ * Cache. It is responsible for tracking the state of the ServiceWorker being
+ * cached. It also handles cancelling caching of a ServiceWorker if loading is
+ * interrupted. It is initialized by the NetworkLoadHandler as part of the first
+ * load of a ServiceWorker.
+ *
+ */
+class CachePromiseHandler final : public PromiseNativeHandler {
+ public:
+ NS_DECL_ISUPPORTS
+
+ CachePromiseHandler(WorkerScriptLoader* aLoader,
+ ThreadSafeRequestHandle* aRequestHandle);
+
+ virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ private:
+ ~CachePromiseHandler() { AssertIsOnMainThread(); }
+
+ RefPtr<WorkerScriptLoader> mLoader;
+ RefPtr<ThreadSafeRequestHandle> mRequestHandle;
+};
+
+} // namespace workerinternals::loader
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_workers_CacheLoadHandler_h__ */
diff --git a/dom/workers/loader/NetworkLoadHandler.cpp b/dom/workers/loader/NetworkLoadHandler.cpp
new file mode 100644
index 0000000000..4d7af91055
--- /dev/null
+++ b/dom/workers/loader/NetworkLoadHandler.cpp
@@ -0,0 +1,393 @@
+/* -*- 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 "NetworkLoadHandler.h"
+#include "CacheLoadHandler.h" // CachePromiseHandler
+
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptError.h"
+#include "nsNetUtil.h"
+
+#include "mozilla/Encoding.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/InternalResponse.h"
+#include "mozilla/dom/ServiceWorkerBinding.h"
+#include "mozilla/dom/ServiceWorkerManager.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/dom/Response.h"
+#include "mozilla/dom/WorkerScope.h"
+
+#include "mozilla/dom/workerinternals/ScriptLoader.h" // WorkerScriptLoader
+
+using mozilla::ipc::PrincipalInfo;
+
+namespace mozilla {
+namespace dom {
+
+namespace workerinternals::loader {
+
+NS_IMPL_ISUPPORTS(NetworkLoadHandler, nsIStreamLoaderObserver,
+ nsIRequestObserver)
+
+NetworkLoadHandler::NetworkLoadHandler(WorkerScriptLoader* aLoader,
+ ThreadSafeRequestHandle* aRequestHandle)
+ : mLoader(aLoader),
+ mWorkerRef(aLoader->mWorkerRef),
+ mRequestHandle(aRequestHandle) {
+ MOZ_ASSERT(mLoader);
+
+ // Worker scripts are always decoded as UTF-8 per spec.
+ mDecoder = MakeUnique<ScriptDecoder>(UTF_8_ENCODING,
+ ScriptDecoder::BOMHandling::Remove);
+}
+
+NS_IMETHODIMP
+NetworkLoadHandler::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* aContext, nsresult aStatus,
+ uint32_t aStringLen,
+ const uint8_t* aString) {
+ // If we have cancelled, or we have no mRequest, it means that the loader has
+ // shut down and we can exit early. If the cancel result is still NS_OK
+ if (mRequestHandle->IsEmpty()) {
+ return NS_OK;
+ }
+ nsresult rv = DataReceivedFromNetwork(aLoader, aStatus, aStringLen, aString);
+ return mRequestHandle->OnStreamComplete(rv);
+}
+
+nsresult NetworkLoadHandler::DataReceivedFromNetwork(nsIStreamLoader* aLoader,
+ nsresult aStatus,
+ uint32_t aStringLen,
+ const uint8_t* aString) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mRequestHandle->IsEmpty());
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ if (!loadContext->mChannel) {
+ return NS_BINDING_ABORTED;
+ }
+
+ loadContext->mChannel = nullptr;
+
+ if (NS_FAILED(aStatus)) {
+ return aStatus;
+ }
+
+ if (mRequestHandle->IsCancelled()) {
+ return mRequestHandle->GetCancelResult();
+ }
+
+ NS_ASSERTION(aString, "This should never be null!");
+
+ nsCOMPtr<nsIRequest> request;
+ nsresult rv = aLoader->GetRequest(getter_AddRefs(request));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ MOZ_ASSERT(channel);
+
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ NS_ASSERTION(ssm, "Should never be null!");
+
+ nsCOMPtr<nsIPrincipal> channelPrincipal;
+ rv =
+ ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal();
+ if (!principal) {
+ WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent();
+ MOZ_ASSERT(parentWorker, "Must have a parent!");
+ principal = parentWorker->GetPrincipal();
+ }
+
+#ifdef DEBUG
+ if (loadContext->IsTopLevel()) {
+ nsCOMPtr<nsIPrincipal> loadingPrincipal =
+ mWorkerRef->Private()->GetLoadingPrincipal();
+ // if we are not in a ServiceWorker, and the principal is not null, then
+ // the loading principal must subsume the worker principal if it is not a
+ // nullPrincipal (sandbox).
+ MOZ_ASSERT(!loadingPrincipal || loadingPrincipal->GetIsNullPrincipal() ||
+ principal->GetIsNullPrincipal() ||
+ loadingPrincipal->Subsumes(principal));
+ }
+#endif
+
+ // We don't mute the main worker script becase we've already done
+ // same-origin checks on them so we should be able to see their errors.
+ // Note that for data: url, where we allow it through the same-origin check
+ // but then give it a different origin.
+ loadContext->mMutedErrorFlag.emplace(!loadContext->IsTopLevel() &&
+ !principal->Subsumes(channelPrincipal));
+
+ // Make sure we're not seeing the result of a 404 or something by checking
+ // the 'requestSucceeded' attribute on the http channel.
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
+ nsAutoCString tCspHeaderValue, tCspROHeaderValue, tRPHeaderCValue;
+
+ if (httpChannel) {
+ bool requestSucceeded;
+ rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!requestSucceeded) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
+ tCspHeaderValue);
+
+ Unused << httpChannel->GetResponseHeader(
+ "content-security-policy-report-only"_ns, tCspROHeaderValue);
+
+ Unused << httpChannel->GetResponseHeader("referrer-policy"_ns,
+ tRPHeaderCValue);
+
+ nsAutoCString sourceMapURL;
+ if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
+ loadContext->mRequest->mSourceMapURL =
+ Some(NS_ConvertUTF8toUTF16(sourceMapURL));
+ }
+ }
+
+ // May be null.
+ Document* parentDoc = mWorkerRef->Private()->GetDocument();
+
+ // Set the Source type to "text" for decoding.
+ loadContext->mRequest->SetTextSource();
+
+ // Use the regular ScriptDecoder Decoder for this grunt work! Should be just
+ // fine because we're running on the main thread.
+ rv = mDecoder->DecodeRawData(loadContext->mRequest, aString, aStringLen,
+ /* aEndOfStream = */ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!loadContext->mRequest->ScriptTextLength()) {
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
+ parentDoc, nsContentUtils::eDOM_PROPERTIES,
+ "EmptyWorkerSourceWarning");
+ }
+
+ // For modules, we need to store the base URI on the module request object,
+ // rather than on the worker private (as we do for classic scripts). This is
+ // because module loading is shared across multiple components, with
+ // ScriptLoadRequests being the common structure among them. This specific
+ // use of the base url is used when resolving the module specifier for child
+ // modules.
+ nsCOMPtr<nsIURI> uri;
+ rv = channel->GetOriginalURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ channel->GetURI(getter_AddRefs(loadContext->mRequest->mBaseURL));
+
+ // Figure out what we actually loaded.
+ nsCOMPtr<nsIURI> finalURI;
+ rv = NS_GetFinalChannelURI(channel, getter_AddRefs(finalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (principal->IsSameOrigin(finalURI)) {
+ nsCString filename;
+ rv = finalURI->GetSpec(filename);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!filename.IsEmpty()) {
+ // This will help callers figure out what their script url resolved to
+ // in case of errors, and is used for debugging.
+ // The full URL shouldn't be exposed to the debugger if cross origin.
+ // See Bug 1634872.
+ loadContext->mRequest->mURL = filename;
+ }
+ }
+
+ // Update the principal of the worker and its base URI if we just loaded the
+ // worker's primary script.
+ bool isDynamic = loadContext->mRequest->IsModuleRequest() &&
+ loadContext->mRequest->AsModuleRequest()->IsDynamicImport();
+ if (loadContext->IsTopLevel() && !isDynamic) {
+ // Take care of the base URI first.
+ mWorkerRef->Private()->SetBaseURI(finalURI);
+
+ // Store the channel info if needed.
+ mWorkerRef->Private()->InitChannelInfo(channel);
+
+ // Our final channel principal should match the loading principal
+ // in terms of the origin. This used to be an assert, but it seems
+ // there are some rare cases where this check can fail in practice.
+ // Perhaps some browser script setting nsIChannel.owner, etc.
+ NS_ENSURE_TRUE(mWorkerRef->Private()->FinalChannelPrincipalIsValid(channel),
+ NS_ERROR_FAILURE);
+
+ // However, we must still override the principal since the nsIPrincipal
+ // URL may be different due to same-origin redirects. Unfortunately this
+ // URL must exactly match the final worker script URL in order to
+ // properly set the referrer header on fetch/xhr requests. If bug 1340694
+ // is ever fixed this can be removed.
+ rv = mWorkerRef->Private()->SetPrincipalsAndCSPFromChannel(channel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp = mWorkerRef->Private()->GetCsp();
+ // We did inherit CSP in bug 1223647. If we do not already have a CSP, we
+ // should get it from the HTTP headers on the worker script.
+ if (!csp) {
+ rv = mWorkerRef->Private()->SetCSPFromHeaderValues(tCspHeaderValue,
+ tCspROHeaderValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ csp->EnsureEventTarget(mWorkerRef->Private()->MainThreadEventTarget());
+ }
+
+ mWorkerRef->Private()->UpdateReferrerInfoFromHeader(tRPHeaderCValue);
+
+ WorkerPrivate* parent = mWorkerRef->Private()->GetParent();
+ if (parent) {
+ // XHR Params Allowed
+ mWorkerRef->Private()->SetXHRParamsAllowed(parent->XHRParamsAllowed());
+ }
+
+ nsCOMPtr<nsILoadInfo> chanLoadInfo = channel->LoadInfo();
+ if (chanLoadInfo) {
+ mLoader->SetController(chanLoadInfo->GetController());
+ }
+
+ // If we are loading a blob URL we must inherit the controller
+ // from the parent. This is a bit odd as the blob URL may have
+ // been created in a different context with a different controller.
+ // For now, though, this is what the spec says. See:
+ //
+ // https://github.com/w3c/ServiceWorker/issues/1261
+ //
+ if (IsBlobURI(mWorkerRef->Private()->GetBaseURI())) {
+ MOZ_DIAGNOSTIC_ASSERT(mLoader->GetController().isNothing());
+ mLoader->SetController(mWorkerRef->Private()->GetParentController());
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkLoadHandler::OnStartRequest(nsIRequest* aRequest) {
+ nsresult rv = PrepareForRequest(aRequest);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->Cancel(rv);
+ }
+
+ return rv;
+}
+
+nsresult NetworkLoadHandler::PrepareForRequest(nsIRequest* aRequest) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mRequestHandle->IsEmpty());
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ // If one load info cancels or hits an error, it can race with the start
+ // callback coming from another load info.
+ if (mRequestHandle->IsCancelled()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+
+ // Checking the MIME type is only required for ServiceWorkers'
+ // importScripts, per step 10 of
+ // https://w3c.github.io/ServiceWorker/#importscripts
+ //
+ // "Extract a MIME type from the response’s header list. If this MIME type
+ // (ignoring parameters) is not a JavaScript MIME type, return a network
+ // error."
+ if (mWorkerRef->Private()->IsServiceWorker()) {
+ nsAutoCString mimeType;
+ channel->GetContentType(mimeType);
+
+ if (!nsContentUtils::IsJavascriptMIMEType(
+ NS_ConvertUTF8toUTF16(mimeType))) {
+ const nsCString& scope = mWorkerRef->Private()
+ ->GetServiceWorkerRegistrationDescriptor()
+ .Scope();
+
+ ServiceWorkerManager::LocalizeAndReportToAllClients(
+ scope, "ServiceWorkerRegisterMimeTypeError2",
+ nsTArray<nsString>{
+ NS_ConvertUTF8toUTF16(scope), NS_ConvertUTF8toUTF16(mimeType),
+ NS_ConvertUTF8toUTF16(loadContext->mRequest->mURL)});
+
+ return NS_ERROR_DOM_NETWORK_ERR;
+ }
+ }
+
+ // We synthesize the result code, but its never exposed to content.
+ SafeRefPtr<mozilla::dom::InternalResponse> ir =
+ MakeSafeRefPtr<mozilla::dom::InternalResponse>(200, "OK"_ns);
+ ir->SetBody(loadContext->mCacheReadStream,
+ InternalResponse::UNKNOWN_BODY_SIZE);
+
+ // Drop our reference to the stream now that we've passed it along, so it
+ // doesn't hang around once the cache is done with it and keep data alive.
+ loadContext->mCacheReadStream = nullptr;
+
+ // Set the channel info of the channel on the response so that it's
+ // saved in the cache.
+ ir->InitChannelInfo(channel);
+
+ // Save the principal of the channel since its URI encodes the script URI
+ // rather than the ServiceWorkerRegistrationInfo URI.
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ NS_ASSERTION(ssm, "Should never be null!");
+
+ nsCOMPtr<nsIPrincipal> channelPrincipal;
+ MOZ_TRY(ssm->GetChannelResultPrincipal(channel,
+ getter_AddRefs(channelPrincipal)));
+
+ UniquePtr<PrincipalInfo> principalInfo(new PrincipalInfo());
+ MOZ_TRY(PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get()));
+
+ ir->SetPrincipalInfo(std::move(principalInfo));
+ ir->Headers()->FillResponseHeaders(channel);
+
+ RefPtr<mozilla::dom::Response> response = new mozilla::dom::Response(
+ mRequestHandle->GetCacheCreator()->Global(), std::move(ir), nullptr);
+
+ mozilla::dom::RequestOrUSVString request;
+
+ MOZ_ASSERT(!loadContext->mFullURL.IsEmpty());
+ request.SetAsUSVString().ShareOrDependUpon(loadContext->mFullURL);
+
+ // This JSContext will not end up executing JS code because here there are
+ // no ReadableStreams involved.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ ErrorResult error;
+ RefPtr<Promise> cachePromise =
+ mRequestHandle->GetCacheCreator()->Cache_()->Put(jsapi.cx(), request,
+ *response, error);
+ error.WouldReportJSException();
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ RefPtr<CachePromiseHandler> promiseHandler =
+ new CachePromiseHandler(mLoader, mRequestHandle);
+ cachePromise->AppendNativeHandler(promiseHandler);
+
+ loadContext->mCachePromise.swap(cachePromise);
+ loadContext->mCacheStatus = WorkerLoadContext::WritingToCache;
+
+ return NS_OK;
+}
+
+} // namespace workerinternals::loader
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/loader/NetworkLoadHandler.h b/dom/workers/loader/NetworkLoadHandler.h
new file mode 100644
index 0000000000..b32c9d8d8e
--- /dev/null
+++ b/dom/workers/loader/NetworkLoadHandler.h
@@ -0,0 +1,79 @@
+/* -*- 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_NetworkLoadHandler_h__
+#define mozilla_dom_workers_NetworkLoadHandler_h__
+
+#include "nsIStreamLoader.h"
+#include "mozilla/dom/WorkerLoadContext.h"
+#include "mozilla/dom/ScriptLoadHandler.h"
+#include "mozilla/dom/WorkerRef.h"
+
+namespace mozilla::dom::workerinternals::loader {
+
+class WorkerScriptLoader;
+
+/*
+ * [DOMDOC] NetworkLoadHandler for Workers
+ *
+ * A LoadHandler is a ScriptLoader helper class that reacts to an
+ * nsIStreamLoader's events for
+ * loading JS scripts. It is primarily responsible for decoding the stream into
+ * UTF8 or UTF16. Additionally, it takes care of any work that needs to follow
+ * the completion of a stream. Every LoadHandler also manages additional tasks
+ * for the type of load that it is doing.
+ *
+ * As part of worker loading we have an number of tasks that we need to take
+ * care of after a successfully completed stream, including setting a final URI
+ * on the WorkerPrivate if we have loaded a main script, or handling CSP issues.
+ * These are handled in DataReceivedFromNetwork, and implement roughly the same
+ * set of tasks as you will find in the CacheLoadhandler, which has a companion
+ * method DataReceivedFromcache.
+ *
+ * In the worker context, the LoadHandler is run on the main thread, and all
+ * work in this file ultimately is done by the main thread, including decoding.
+ *
+ */
+
+class NetworkLoadHandler final : public nsIStreamLoaderObserver,
+ public nsIRequestObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ NetworkLoadHandler(WorkerScriptLoader* aLoader,
+ ThreadSafeRequestHandle* aRequestHandle);
+
+ NS_IMETHOD
+ OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
+ nsresult aStatus, uint32_t aStringLen,
+ const uint8_t* aString) override;
+
+ nsresult DataReceivedFromNetwork(nsIStreamLoader* aLoader, nsresult aStatus,
+ uint32_t aStringLen, const uint8_t* aString);
+
+ NS_IMETHOD
+ OnStartRequest(nsIRequest* aRequest) override;
+
+ nsresult PrepareForRequest(nsIRequest* aRequest);
+
+ NS_IMETHOD
+ OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) override {
+ // Nothing to do here!
+ return NS_OK;
+ }
+
+ private:
+ ~NetworkLoadHandler() = default;
+
+ RefPtr<WorkerScriptLoader> mLoader;
+ UniquePtr<ScriptDecoder> mDecoder;
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+ RefPtr<ThreadSafeRequestHandle> mRequestHandle;
+};
+
+} // namespace mozilla::dom::workerinternals::loader
+
+#endif /* mozilla_dom_workers_NetworkLoadHandler_h__ */
diff --git a/dom/workers/loader/ScriptResponseHeaderProcessor.cpp b/dom/workers/loader/ScriptResponseHeaderProcessor.cpp
new file mode 100644
index 0000000000..30b9d42613
--- /dev/null
+++ b/dom/workers/loader/ScriptResponseHeaderProcessor.cpp
@@ -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/. */
+
+#include "ScriptResponseHeaderProcessor.h"
+#include "mozilla/dom/WorkerScope.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace workerinternals {
+
+namespace loader {
+
+NS_IMPL_ISUPPORTS(ScriptResponseHeaderProcessor, nsIRequestObserver);
+
+nsresult ScriptResponseHeaderProcessor::ProcessCrossOriginEmbedderPolicyHeader(
+ WorkerPrivate* aWorkerPrivate,
+ nsILoadInfo::CrossOriginEmbedderPolicy aPolicy, bool aIsMainScript) {
+ MOZ_ASSERT(aWorkerPrivate);
+
+ if (aIsMainScript) {
+ MOZ_TRY(aWorkerPrivate->SetEmbedderPolicy(aPolicy));
+ } else {
+ // NOTE: Spec doesn't mention non-main scripts must match COEP header with
+ // the main script, but it must pass CORP checking.
+ // see: wpt window-simple-success.https.html, the worker import script
+ // test-incrementer.js without coep header.
+ Unused << NS_WARN_IF(!aWorkerPrivate->MatchEmbedderPolicy(aPolicy));
+ }
+
+ return NS_OK;
+}
+
+// Enforce strict MIME type checks for worker-imported scripts
+// https://github.com/whatwg/html/pull/4001
+nsresult ScriptResponseHeaderProcessor::EnsureJavaScriptMimeType(
+ nsIRequest* aRequest) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ MOZ_ASSERT(channel);
+ nsAutoCString mimeType;
+ channel->GetContentType(mimeType);
+ if (!nsContentUtils::IsJavascriptMIMEType(NS_ConvertUTF8toUTF16(mimeType))) {
+ return NS_ERROR_DOM_NETWORK_ERR;
+ }
+ return NS_OK;
+}
+
+nsresult ScriptResponseHeaderProcessor::ProcessCrossOriginEmbedderPolicyHeader(
+ nsIRequest* aRequest) {
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aRequest);
+
+ // NOTE: the spec doesn't say what to do with non-HTTP workers.
+ // See: https://github.com/whatwg/html/issues/4916
+ if (!httpChannel) {
+ if (mIsMainScript) {
+ mWorkerPrivate->InheritOwnerEmbedderPolicyOrNull(aRequest);
+ }
+
+ return NS_OK;
+ }
+
+ nsILoadInfo::CrossOriginEmbedderPolicy coep;
+ MOZ_TRY(httpChannel->GetResponseEmbedderPolicy(
+ mWorkerPrivate->Trials().IsEnabled(OriginTrial::CoepCredentialless),
+ &coep));
+
+ return ProcessCrossOriginEmbedderPolicyHeader(mWorkerPrivate, coep,
+ mIsMainScript);
+}
+
+} // namespace loader
+} // namespace workerinternals
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/loader/ScriptResponseHeaderProcessor.h b/dom/workers/loader/ScriptResponseHeaderProcessor.h
new file mode 100644
index 0000000000..43a9bfde42
--- /dev/null
+++ b/dom/workers/loader/ScriptResponseHeaderProcessor.h
@@ -0,0 +1,94 @@
+/* -*- 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_ScriptResponseHeaderProcessor_h__
+#define mozilla_dom_workers_ScriptResponseHeaderProcessor_h__
+
+#include "mozilla/dom/WorkerCommon.h"
+
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIStreamLoader.h"
+#include "nsStreamUtils.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_dom.h"
+
+namespace mozilla::dom {
+
+class WorkerPrivate;
+
+namespace workerinternals::loader {
+
+/* ScriptResponseHeaderProcessor
+ *
+ * This class handles Policy headers. It can be used as a RequestObserver in a
+ * Tee, as it is for NetworkLoadHandler in WorkerScriptLoader, or the static
+ * method can be called directly, as it is in CacheLoadHandler.
+ *
+ */
+
+class ScriptResponseHeaderProcessor final : public nsIRequestObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ ScriptResponseHeaderProcessor(WorkerPrivate* aWorkerPrivate,
+ bool aIsMainScript, bool aIsImportScript)
+ : mWorkerPrivate(aWorkerPrivate),
+ mIsMainScript(aIsMainScript),
+ mIsImportScript(aIsImportScript) {
+ AssertIsOnMainThread();
+ }
+
+ NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override {
+ nsresult rv = NS_OK;
+ if (mIsImportScript &&
+ StaticPrefs::dom_workers_importScripts_enforceStrictMimeType()) {
+ rv = EnsureJavaScriptMimeType(aRequest);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->Cancel(rv);
+ return NS_OK;
+ }
+ }
+
+ if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ return NS_OK;
+ }
+
+ rv = ProcessCrossOriginEmbedderPolicyHeader(aRequest);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->Cancel(rv);
+ }
+
+ return rv;
+ }
+
+ NS_IMETHOD OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) override {
+ return NS_OK;
+ }
+
+ static nsresult ProcessCrossOriginEmbedderPolicyHeader(
+ WorkerPrivate* aWorkerPrivate,
+ nsILoadInfo::CrossOriginEmbedderPolicy aPolicy, bool aIsMainScript);
+
+ private:
+ ~ScriptResponseHeaderProcessor() = default;
+
+ nsresult EnsureJavaScriptMimeType(nsIRequest* aRequest);
+
+ nsresult ProcessCrossOriginEmbedderPolicyHeader(nsIRequest* aRequest);
+
+ WorkerPrivate* const mWorkerPrivate;
+ const bool mIsMainScript;
+ const bool mIsImportScript;
+};
+
+} // namespace workerinternals::loader
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_workers_ScriptResponseHeaderProcessor_h__ */
diff --git a/dom/workers/loader/WorkerLoadContext.cpp b/dom/workers/loader/WorkerLoadContext.cpp
new file mode 100644
index 0000000000..a788d5173c
--- /dev/null
+++ b/dom/workers/loader/WorkerLoadContext.cpp
@@ -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/. */
+
+#include "WorkerLoadContext.h"
+#include "mozilla/dom/workerinternals/ScriptLoader.h"
+#include "CacheLoadHandler.h" // CacheCreator
+
+namespace mozilla {
+namespace dom {
+
+WorkerLoadContext::WorkerLoadContext(
+ Kind aKind, const Maybe<ClientInfo>& aClientInfo,
+ workerinternals::loader::WorkerScriptLoader* aScriptLoader,
+ bool aOnlyExistingCachedResourcesAllowed)
+ : JS::loader::LoadContextBase(JS::loader::ContextKind::Worker),
+ mKind(aKind),
+ mClientInfo(aClientInfo),
+ mScriptLoader(aScriptLoader),
+ mOnlyExistingCachedResourcesAllowed(
+ aOnlyExistingCachedResourcesAllowed){};
+
+ThreadSafeRequestHandle::ThreadSafeRequestHandle(
+ JS::loader::ScriptLoadRequest* aRequest, nsISerialEventTarget* aSyncTarget)
+ : mRequest(aRequest), mOwningEventTarget(aSyncTarget) {}
+
+already_AddRefed<JS::loader::ScriptLoadRequest>
+ThreadSafeRequestHandle::ReleaseRequest() {
+ RefPtr<JS::loader::ScriptLoadRequest> request;
+ mRequest.swap(request);
+ mRunnable = nullptr;
+ return request.forget();
+}
+
+nsresult ThreadSafeRequestHandle::OnStreamComplete(nsresult aStatus) {
+ return mRunnable->OnStreamComplete(this, aStatus);
+}
+
+void ThreadSafeRequestHandle::LoadingFinished(nsresult aRv) {
+ mRunnable->LoadingFinished(this, aRv);
+}
+
+void ThreadSafeRequestHandle::MaybeExecuteFinishedScripts() {
+ mRunnable->MaybeExecuteFinishedScripts(this);
+}
+
+bool ThreadSafeRequestHandle::IsCancelled() { return mRunnable->IsCancelled(); }
+
+nsresult ThreadSafeRequestHandle::GetCancelResult() {
+ return mRunnable->GetCancelResult();
+}
+
+workerinternals::loader::CacheCreator*
+ThreadSafeRequestHandle::GetCacheCreator() {
+ AssertIsOnMainThread();
+ return mRunnable->GetCacheCreator();
+}
+
+ThreadSafeRequestHandle::~ThreadSafeRequestHandle() {
+ // Normally we only touch mStrongRef on the owning thread. This is safe,
+ // however, because when we do use mStrongRef on the owning thread we are
+ // always holding a strong ref to the ThreadsafeHandle via the owning
+ // runnable. So we cannot run the ThreadsafeHandle destructor simultaneously.
+ if (!mRequest || mOwningEventTarget->IsOnCurrentThread()) {
+ return;
+ }
+
+ // Dispatch in NS_ProxyRelease is guaranteed to succeed here because we block
+ // shutdown until all Contexts have been destroyed. Therefore it is ok to have
+ // MOZ_ALWAYS_SUCCEED here.
+ MOZ_ALWAYS_SUCCEEDS(NS_ProxyRelease("ThreadSafeRequestHandle::mRequest",
+ mOwningEventTarget, mRequest.forget()));
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/loader/WorkerLoadContext.h b/dom/workers/loader/WorkerLoadContext.h
new file mode 100644
index 0000000000..97362f2871
--- /dev/null
+++ b/dom/workers/loader/WorkerLoadContext.h
@@ -0,0 +1,219 @@
+/* -*- 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_WorkerLoadContext_h__
+#define mozilla_dom_workers_WorkerLoadContext_h__
+
+#include "nsIChannel.h"
+#include "nsIInputStream.h"
+#include "nsIRequest.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/Promise.h"
+#include "js/loader/ScriptKind.h"
+#include "js/loader/ScriptLoadRequest.h"
+#include "js/loader/LoadContextBase.h"
+
+class nsIReferrerInfo;
+class nsIURI;
+
+namespace mozilla::dom {
+
+class ClientInfo;
+class WorkerPrivate;
+
+namespace workerinternals::loader {
+class CacheCreator;
+class ScriptLoaderRunnable;
+class WorkerScriptLoader;
+} // namespace workerinternals::loader
+
+/*
+ * WorkerLoadContext (for all workers)
+ *
+ * LoadContexts augment the loading of a ScriptLoadRequest. They
+ * describe how a ScriptLoadRequests loading and evaluation needs to be
+ * augmented, based on the information provided by the loading context. The
+ * WorkerLoadContext has the following generic fields applied to all worker
+ * ScriptLoadRequests (and primarily used for error handling):
+ *
+ * * mMutedErrorFlag
+ * Set when we finish loading a script, and used to determine whether a
+ * given error is thrown or muted.
+ * * mLoadResult
+ * In order to report errors correctly in the worker thread, we need to
+ * move them from the main thread to the worker. This field records the
+ * load error, for throwing when we return to the worker thread.
+ * * mKind
+ * See documentation of WorkerLoadContext::Kind.
+ * * mClientInfo
+ * A snapshot of a global living in the system (see documentation for
+ * ClientInfo). In worker loading, this field is important for CSP
+ * information and knowing what to intercept for Service Worker
+ * interception.
+ * * mChannel
+ * The channel used by this request for it's load. Used for cancellation,
+ * in order to cancel the stream.
+ *
+ * The rest of the fields on this class focus on enabling the ServiceWorker
+ * usecase, in particular -- using the Cache API to store the worker so that
+ * in the case of (for example) a page refresh, the service worker itself is
+ * persisted so that it can do other work. For more details see the
+ * CacheLoadHandler.h file.
+ *
+ */
+
+class WorkerLoadContext : public JS::loader::LoadContextBase {
+ public:
+ /* Worker Load Context Kinds
+ *
+ * A script that is loaded and run as a worker can be one of several species.
+ * Each may have slightly different behavior, but they fall into roughly two
+ * categories: the Main Worker Script (the script that triggers the first
+ * load) and scripts that are attached to this main worker script.
+ *
+ * In the specification, the Main Worker Script is referred to as the "top
+ * level script" and is defined here:
+ * https://html.spec.whatwg.org/multipage/webappapis.html#fetching-scripts-is-top-level
+ */
+
+ enum Kind {
+ // Indicates that the is-top-level bit is true. This may be a Classic script
+ // or a Module script.
+ MainScript,
+ // We are importing a script from the worker via ImportScript. This may only
+ // be a Classic script.
+ ImportScript,
+ // We are importing a script from the worker via a Static Import. This may
+ // only
+ // be a Module script.
+ StaticImport,
+ DynamicImport,
+ // We have an attached debugger, and these should be treated specially and
+ // not like a main script (regardless of their type). This is not part of
+ // the specification.
+ DebuggerScript
+ };
+
+ WorkerLoadContext(Kind aKind, const Maybe<ClientInfo>& aClientInfo,
+ workerinternals::loader::WorkerScriptLoader* aScriptLoader,
+ bool aOnlyExistingCachedResourcesAllowed);
+
+ // Used to detect if the `is top-level` bit is set on a given module.
+ bool IsTopLevel() {
+ return mRequest->IsTopLevel() && (mKind == Kind::MainScript);
+ };
+
+ static Kind GetKind(bool isMainScript, bool isDebuggerScript) {
+ if (isDebuggerScript) {
+ return Kind::DebuggerScript;
+ }
+ if (isMainScript) {
+ return Kind::MainScript;
+ }
+ return Kind::ImportScript;
+ };
+
+ /* These fields are used by all workers */
+ Maybe<bool> mMutedErrorFlag;
+ nsresult mLoadResult = NS_ERROR_NOT_INITIALIZED;
+ bool mLoadingFinished = false;
+ bool mIsTopLevel = true;
+ Kind mKind;
+ Maybe<ClientInfo> mClientInfo;
+ nsCOMPtr<nsIChannel> mChannel;
+ RefPtr<workerinternals::loader::WorkerScriptLoader> mScriptLoader;
+
+ /* These fields are only used by service workers */
+ /* TODO: Split out a ServiceWorkerLoadContext */
+ // This full URL string is populated only if this object is used in a
+ // ServiceWorker.
+ nsString mFullURL;
+
+ // This promise is set only when the script is for a ServiceWorker but
+ // it's not in the cache yet. The promise is resolved when the full body is
+ // stored into the cache. mCachePromise will be set to nullptr after
+ // resolution.
+ RefPtr<Promise> mCachePromise;
+
+ // The reader stream the cache entry should be filled from, for those cases
+ // when we're going to have an mCachePromise.
+ nsCOMPtr<nsIInputStream> mCacheReadStream;
+
+ enum CacheStatus {
+ // By default a normal script is just loaded from the network. But for
+ // ServiceWorkers, we have to check if the cache contains the script and
+ // load it from the cache.
+ Uncached,
+
+ WritingToCache,
+
+ ReadingFromCache,
+
+ // This script has been loaded from the ServiceWorker cache.
+ Cached,
+
+ // This script must be stored in the ServiceWorker cache.
+ ToBeCached,
+
+ // Something went wrong or the worker went away.
+ Cancel
+ };
+
+ CacheStatus mCacheStatus = Uncached;
+
+ // If the requested script is not currently in the cache, should we initiate
+ // a request to fetch and cache it? Only ServiceWorkers that are being
+ // installed are allowed to go to the network (and then cache the result).
+ bool mOnlyExistingCachedResourcesAllowed = false;
+
+ bool IsAwaitingPromise() const { return bool(mCachePromise); }
+};
+
+class ThreadSafeRequestHandle final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadSafeRequestHandle)
+
+ ThreadSafeRequestHandle(JS::loader::ScriptLoadRequest* aRequest,
+ nsISerialEventTarget* aSyncTarget);
+
+ JS::loader::ScriptLoadRequest* GetRequest() const { return mRequest; }
+
+ WorkerLoadContext* GetContext() { return mRequest->GetWorkerLoadContext(); }
+
+ bool IsEmpty() { return !mRequest; }
+
+ // Runnable controls
+ nsresult OnStreamComplete(nsresult aStatus);
+
+ void LoadingFinished(nsresult aRv);
+
+ void MaybeExecuteFinishedScripts();
+
+ bool IsCancelled();
+
+ bool Finished() {
+ return GetContext()->mLoadingFinished && !GetContext()->IsAwaitingPromise();
+ }
+
+ nsresult GetCancelResult();
+
+ already_AddRefed<JS::loader::ScriptLoadRequest> ReleaseRequest();
+
+ workerinternals::loader::CacheCreator* GetCacheCreator();
+
+ RefPtr<workerinternals::loader::ScriptLoaderRunnable> mRunnable;
+
+ bool mExecutionScheduled = false;
+
+ private:
+ ~ThreadSafeRequestHandle();
+
+ RefPtr<JS::loader::ScriptLoadRequest> mRequest;
+ nsCOMPtr<nsISerialEventTarget> mOwningEventTarget;
+};
+
+} // namespace mozilla::dom
+#endif /* mozilla_dom_workers_WorkerLoadContext_h__ */
diff --git a/dom/workers/loader/WorkerModuleLoader.cpp b/dom/workers/loader/WorkerModuleLoader.cpp
new file mode 100644
index 0000000000..2a2f892edb
--- /dev/null
+++ b/dom/workers/loader/WorkerModuleLoader.cpp
@@ -0,0 +1,215 @@
+/* -*- 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 "js/experimental/JSStencil.h" // JS::Stencil, JS::CompileModuleScriptToStencil, JS::InstantiateModuleStencil
+#include "js/loader/ModuleLoadRequest.h"
+#include "mozilla/dom/WorkerLoadContext.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/workerinternals/ScriptLoader.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "WorkerModuleLoader.h"
+
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom::workerinternals::loader {
+
+//////////////////////////////////////////////////////////////
+// WorkerModuleLoader
+//////////////////////////////////////////////////////////////
+
+NS_IMPL_ADDREF_INHERITED(WorkerModuleLoader, JS::loader::ModuleLoaderBase)
+NS_IMPL_RELEASE_INHERITED(WorkerModuleLoader, JS::loader::ModuleLoaderBase)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(WorkerModuleLoader,
+ JS::loader::ModuleLoaderBase)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerModuleLoader)
+NS_INTERFACE_MAP_END_INHERITING(JS::loader::ModuleLoaderBase)
+
+WorkerModuleLoader::WorkerModuleLoader(WorkerScriptLoader* aScriptLoader,
+ nsIGlobalObject* aGlobalObject,
+ nsISerialEventTarget* aEventTarget)
+ : ModuleLoaderBase(aScriptLoader, aGlobalObject, aEventTarget) {}
+
+nsIURI* WorkerModuleLoader::GetBaseURI() const {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ return workerPrivate->GetBaseURI();
+}
+
+already_AddRefed<ModuleLoadRequest> WorkerModuleLoader::CreateStaticImport(
+ nsIURI* aURI, ModuleLoadRequest* aParent) {
+ // We are intentionally deviating from the specification here and using the
+ // worker's CSP rather than the document CSP. The spec otherwise requires our
+ // service worker integration to be changed, and additionally the decision
+ // here did not make sense as we are treating static imports as different from
+ // other kinds of subresources.
+ // See Discussion in https://github.com/w3c/webappsec-csp/issues/336
+ Maybe<ClientInfo> clientInfo = GetGlobalObject()->GetClientInfo();
+
+ RefPtr<WorkerLoadContext> loadContext = new WorkerLoadContext(
+ WorkerLoadContext::Kind::StaticImport, clientInfo,
+ aParent->GetWorkerLoadContext()->mScriptLoader,
+ aParent->GetWorkerLoadContext()->mOnlyExistingCachedResourcesAllowed);
+ RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest(
+ aURI, aParent->mFetchOptions, SRIMetadata(), aParent->mURI, loadContext,
+ false, /* is top level */
+ false, /* is dynamic import */
+ this, aParent->mVisitedSet, aParent->GetRootModule());
+
+ request->mURL = request->mURI->GetSpecOrDefault();
+ return request.forget();
+}
+
+bool WorkerModuleLoader::CreateDynamicImportLoader() {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ workerPrivate->AssertIsOnWorkerThread();
+
+ IgnoredErrorResult rv;
+ RefPtr<WorkerScriptLoader> loader = new loader::WorkerScriptLoader(
+ workerPrivate, nullptr, nullptr,
+ GetCurrentScriptLoader()->GetWorkerScriptType(), rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return false;
+ }
+
+ SetScriptLoader(loader);
+ SetEventTarget(GetCurrentSerialEventTarget());
+ return true;
+}
+
+already_AddRefed<ModuleLoadRequest> WorkerModuleLoader::CreateDynamicImport(
+ JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript,
+ JS::Handle<JS::Value> aReferencingPrivate, JS::Handle<JSString*> aSpecifier,
+ JS::Handle<JSObject*> aPromise) {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+
+ if (!CreateDynamicImportLoader()) {
+ return nullptr;
+ }
+
+ // Not supported for Service Workers.
+ // https://github.com/w3c/ServiceWorker/issues/1585 covers existing discussion
+ // about potentially supporting use of import().
+ if (workerPrivate->IsServiceWorker()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(aSpecifier);
+ MOZ_ASSERT(aPromise);
+
+ RefPtr<ScriptFetchOptions> options;
+ nsIURI* baseURL = nullptr;
+ if (aMaybeActiveScript) {
+ options = aMaybeActiveScript->GetFetchOptions();
+ baseURL = aMaybeActiveScript->BaseURL();
+ } else {
+ ReferrerPolicy referrerPolicy = workerPrivate->GetReferrerPolicy();
+ options =
+ new ScriptFetchOptions(CORSMode::CORS_NONE, referrerPolicy, nullptr);
+ baseURL = GetBaseURI();
+ }
+
+ Maybe<ClientInfo> clientInfo = GetGlobalObject()->GetClientInfo();
+
+ RefPtr<WorkerLoadContext> context = new WorkerLoadContext(
+ WorkerLoadContext::Kind::DynamicImport, clientInfo,
+ GetCurrentScriptLoader(),
+ // When dynamic import is supported in ServiceWorkers,
+ // the current plan in onlyExistingCachedResourcesAllowed
+ // is that only existing cached resources will be
+ // allowed. (`import()` will not be used for caching
+ // side effects, but instead a specific method will be
+ // used during installation.)
+ true);
+
+ RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest(
+ aURI, options, SRIMetadata(), baseURL, context, true,
+ /* is top level */ true, /* is dynamic import */
+ this, ModuleLoadRequest::NewVisitedSetForTopLevelImport(aURI), nullptr);
+
+ request->mDynamicReferencingPrivate = aReferencingPrivate;
+ request->mDynamicSpecifier = aSpecifier;
+ request->mDynamicPromise = aPromise;
+
+ HoldJSObjects(request.get());
+
+ return request.forget();
+}
+
+bool WorkerModuleLoader::CanStartLoad(ModuleLoadRequest* aRequest,
+ nsresult* aRvOut) {
+ return true;
+}
+
+nsresult WorkerModuleLoader::StartFetch(ModuleLoadRequest* aRequest) {
+ if (!GetScriptLoaderFor(aRequest)->DispatchLoadScript(aRequest)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult WorkerModuleLoader::CompileFetchedModule(
+ JSContext* aCx, JS::Handle<JSObject*> aGlobal, JS::CompileOptions& aOptions,
+ ModuleLoadRequest* aRequest, JS::MutableHandle<JSObject*> aModuleScript) {
+ RefPtr<JS::Stencil> stencil;
+ MOZ_ASSERT(aRequest->IsTextSource());
+ MaybeSourceText maybeSource;
+ nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto compile = [&](auto& source) {
+ return JS::CompileModuleScriptToStencil(aCx, aOptions, source);
+ };
+ stencil = maybeSource.mapNonEmpty(compile);
+
+ if (!stencil) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::InstantiateOptions instantiateOptions(aOptions);
+ aModuleScript.set(
+ JS::InstantiateModuleStencil(aCx, instantiateOptions, stencil));
+ if (!aModuleScript) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+WorkerScriptLoader* WorkerModuleLoader::GetCurrentScriptLoader() {
+ return static_cast<WorkerScriptLoader*>(mLoader.get());
+}
+
+WorkerScriptLoader* WorkerModuleLoader::GetScriptLoaderFor(
+ ModuleLoadRequest* aRequest) {
+ return aRequest->GetWorkerLoadContext()->mScriptLoader;
+}
+
+void WorkerModuleLoader::OnModuleLoadComplete(ModuleLoadRequest* aRequest) {
+ if (aRequest->IsTopLevel()) {
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) {
+ return;
+ }
+ RefPtr<WorkerScriptLoader> requestScriptLoader =
+ GetScriptLoaderFor(aRequest);
+ if (aRequest->IsDynamicImport()) {
+ aRequest->ProcessDynamicImport();
+ requestScriptLoader->TryShutdown();
+ } else {
+ requestScriptLoader->MaybeMoveToLoadedList(aRequest);
+ requestScriptLoader->ProcessPendingRequests(jsapi.cx());
+ }
+ }
+}
+
+bool WorkerModuleLoader::IsModuleEvaluationAborted(
+ ModuleLoadRequest* aRequest) {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ return !workerPrivate || !workerPrivate->GlobalScope() ||
+ workerPrivate->GlobalScope()->IsDying();
+}
+
+} // namespace mozilla::dom::workerinternals::loader
diff --git a/dom/workers/loader/WorkerModuleLoader.h b/dom/workers/loader/WorkerModuleLoader.h
new file mode 100644
index 0000000000..2fcd3125a4
--- /dev/null
+++ b/dom/workers/loader/WorkerModuleLoader.h
@@ -0,0 +1,87 @@
+/* -*- 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_loader_WorkerModuleLoader_h
+#define mozilla_loader_WorkerModuleLoader_h
+
+#include "js/loader/ModuleLoaderBase.h"
+#include "mozilla/dom/SerializedStackHolder.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla::dom::workerinternals::loader {
+class WorkerScriptLoader;
+
+// alias common classes
+using ScriptFetchOptions = JS::loader::ScriptFetchOptions;
+using ScriptKind = JS::loader::ScriptKind;
+using ScriptLoadRequest = JS::loader::ScriptLoadRequest;
+using ScriptLoadRequestList = JS::loader::ScriptLoadRequestList;
+using ModuleLoadRequest = JS::loader::ModuleLoadRequest;
+
+// WorkerModuleLoader
+//
+// The WorkerModuleLoader provides the methods that implement specification
+// step 5 from "To fetch a worklet/module worker script graph", specifically for
+// workers. In addition, this implements worker specific initialization for
+// Static imports and Dynamic imports.
+//
+// The steps are outlined in "To fetch the descendants of and link a module
+// script" and are common for all Modules. Thus we delegate to ModuleLoaderBase
+// for those steps.
+class WorkerModuleLoader : public JS::loader::ModuleLoaderBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WorkerModuleLoader,
+ JS::loader::ModuleLoaderBase)
+
+ WorkerModuleLoader(WorkerScriptLoader* aScriptLoader,
+ nsIGlobalObject* aGlobalObject,
+ nsISerialEventTarget* aEventTarget);
+
+ private:
+ ~WorkerModuleLoader() = default;
+
+ bool CreateDynamicImportLoader();
+ void SetScriptLoader(JS::loader::ScriptLoaderInterface* aLoader) {
+ mLoader = aLoader;
+ }
+ void SetEventTarget(nsISerialEventTarget* aEventTarget) {
+ mEventTarget = aEventTarget;
+ }
+
+ WorkerScriptLoader* GetCurrentScriptLoader();
+
+ WorkerScriptLoader* GetScriptLoaderFor(ModuleLoadRequest* aRequest);
+
+ nsIURI* GetBaseURI() const override;
+
+ already_AddRefed<ModuleLoadRequest> CreateStaticImport(
+ nsIURI* aURI, ModuleLoadRequest* aParent) override;
+
+ already_AddRefed<ModuleLoadRequest> CreateDynamicImport(
+ JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript,
+ JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSString*> aSpecifier,
+ JS::Handle<JSObject*> aPromise) override;
+
+ bool CanStartLoad(ModuleLoadRequest* aRequest, nsresult* aRvOut) override;
+
+ // StartFetch is special for worker modules, as we need to move back to the
+ // main thread to start a new load.
+ nsresult StartFetch(ModuleLoadRequest* aRequest) override;
+
+ nsresult CompileFetchedModule(
+ JSContext* aCx, JS::Handle<JSObject*> aGlobal,
+ JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest,
+ JS::MutableHandle<JSObject*> aModuleScript) override;
+
+ void OnModuleLoadComplete(ModuleLoadRequest* aRequest) override;
+
+ bool IsModuleEvaluationAborted(ModuleLoadRequest* aRequest) override;
+};
+
+} // namespace mozilla::dom::workerinternals::loader
+#endif // mozilla_loader_WorkerModuleLoader_h
diff --git a/dom/workers/loader/moz.build b/dom/workers/loader/moz.build
new file mode 100644
index 0000000000..d39a6acb24
--- /dev/null
+++ b/dom/workers/loader/moz.build
@@ -0,0 +1,34 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Workers")
+
+# Public stuff.
+EXPORTS.mozilla.dom += [
+ "WorkerLoadContext.h",
+]
+
+# Private stuff.
+EXPORTS.mozilla.dom.workerinternals += [
+ "CacheLoadHandler.h",
+ "NetworkLoadHandler.h",
+ "ScriptResponseHeaderProcessor.h",
+ "WorkerModuleLoader.h",
+]
+
+UNIFIED_SOURCES += [
+ "CacheLoadHandler.cpp",
+ "NetworkLoadHandler.cpp",
+ "ScriptResponseHeaderProcessor.cpp",
+ "WorkerLoadContext.cpp",
+ "WorkerModuleLoader.cpp",
+]
+
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/workers/moz.build b/dom/workers/moz.build
new file mode 100644
index 0000000000..ed6950bd70
--- /dev/null
+++ b/dom/workers/moz.build
@@ -0,0 +1,110 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Workers")
+
+DIRS += ["remoteworkers", "sharedworkers", "loader"]
+
+# Public stuff.
+EXPORTS.mozilla.dom += [
+ "ChromeWorker.h",
+ "JSExecutionManager.h",
+ "Worker.h",
+ "WorkerChannelInfo.h",
+ "WorkerCommon.h",
+ "WorkerDebugger.h",
+ "WorkerDebuggerManager.h",
+ "WorkerDocumentListener.h",
+ "WorkerError.h",
+ "WorkerIPCUtils.h",
+ "WorkerLoadInfo.h",
+ "WorkerLocation.h",
+ "WorkerNavigator.h",
+ "WorkerPrivate.h",
+ "WorkerRef.h",
+ "WorkerRunnable.h",
+ "WorkerScope.h",
+ "WorkerStatus.h",
+ "WorkerTestUtils.h",
+]
+
+# Private stuff.
+EXPORTS.mozilla.dom.workerinternals += [
+ "JSSettings.h",
+ "Queue.h",
+ "RuntimeService.h",
+ "ScriptLoader.h",
+]
+
+XPIDL_MODULE = "dom_workers"
+
+XPIDL_SOURCES += [
+ "nsIWorkerChannelInfo.idl",
+ "nsIWorkerDebugger.idl",
+ "nsIWorkerDebuggerManager.idl",
+]
+
+UNIFIED_SOURCES += [
+ "ChromeWorker.cpp",
+ "ChromeWorkerScope.cpp",
+ "JSExecutionManager.cpp",
+ "MessageEventRunnable.cpp",
+ "RegisterBindings.cpp",
+ "RuntimeService.cpp",
+ "ScriptLoader.cpp",
+ "Worker.cpp",
+ "WorkerChannelInfo.cpp",
+ "WorkerCSPEventListener.cpp",
+ "WorkerDebugger.cpp",
+ "WorkerDebuggerManager.cpp",
+ "WorkerDocumentListener.cpp",
+ "WorkerError.cpp",
+ "WorkerEventTarget.cpp",
+ "WorkerLoadInfo.cpp",
+ "WorkerLocation.cpp",
+ "WorkerNavigator.cpp",
+ "WorkerPrivate.cpp",
+ "WorkerRef.cpp",
+ "WorkerRunnable.cpp",
+ "WorkerScope.cpp",
+ "WorkerTestUtils.cpp",
+ "WorkerThread.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/caps",
+ "/dom/base",
+ "/dom/bindings",
+ "/dom/system",
+ "/dom/workers/remoteworkers",
+ "/js/xpconnect/loader",
+ "/netwerk/base",
+ "/xpcom/build",
+ "/xpcom/threads",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ LOCAL_INCLUDES += [
+ "/xpcom/base",
+ ]
+
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+MOCHITEST_MANIFESTS += [
+ "test/mochitest.ini",
+]
+
+MOCHITEST_CHROME_MANIFESTS += [
+ "test/chrome.ini",
+]
+
+XPCSHELL_TESTS_MANIFESTS += ["test/xpcshell/xpcshell.ini"]
+
+BROWSER_CHROME_MANIFESTS += ["test/browser.ini"]
diff --git a/dom/workers/nsIWorkerChannelInfo.idl b/dom/workers/nsIWorkerChannelInfo.idl
new file mode 100644
index 0000000000..5cf3305749
--- /dev/null
+++ b/dom/workers/nsIWorkerChannelInfo.idl
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsISupports.idl"
+
+webidl BrowsingContext;
+
+[scriptable, builtinclass, uuid(bf9a175a-03bc-4d7b-ba2f-76347cf40d7b)]
+interface nsIWorkerChannelLoadInfo : nsISupports
+{
+ [infallible] attribute unsigned long long workerAssociatedBrowsingContextID;
+ [infallible] readonly attribute BrowsingContext workerAssociatedBrowsingContext;
+};
+
+[scriptable, builtinclass, uuid(df1fffe4-dac6-487e-979a-629ac8c64831)]
+interface nsIWorkerChannelInfo : nsISupports
+{
+ attribute nsIWorkerChannelLoadInfo loadInfo;
+ [must_use] readonly attribute uint64_t channelId;
+};
diff --git a/dom/workers/nsIWorkerDebugger.idl b/dom/workers/nsIWorkerDebugger.idl
new file mode 100644
index 0000000000..35b58e78e5
--- /dev/null
+++ b/dom/workers/nsIWorkerDebugger.idl
@@ -0,0 +1,69 @@
+#include "nsISupports.idl"
+
+interface mozIDOMWindow;
+interface nsIPrincipal;
+
+[scriptable, uuid(9cf3b48e-361d-486a-8917-55cf8d00bb41)]
+interface nsIWorkerDebuggerListener : nsISupports
+{
+ void onClose();
+
+ void onError(in AString filename, in unsigned long lineno,
+ in AString message);
+
+ void onMessage(in AString message);
+};
+
+[scriptable, builtinclass, uuid(22f93aa3-8a05-46be-87e0-fa93bf8a8eff)]
+interface nsIWorkerDebugger : nsISupports
+{
+ const unsigned long TYPE_DEDICATED = 0;
+ const unsigned long TYPE_SHARED = 1;
+ const unsigned long TYPE_SERVICE = 2;
+
+ readonly attribute bool isClosed;
+
+ readonly attribute bool isChrome;
+
+ readonly attribute bool isInitialized;
+
+ readonly attribute nsIWorkerDebugger parent;
+
+ readonly attribute unsigned long type;
+
+ readonly attribute AString url;
+
+ // If this is a dedicated worker, the window this worker or (in the case of
+ // nested workers) its top-level ancestral worker is associated with.
+ readonly attribute mozIDOMWindow window;
+
+ readonly attribute Array<uint64_t> windowIDs;
+
+ readonly attribute nsIPrincipal principal;
+
+ readonly attribute unsigned long serviceWorkerID;
+
+ readonly attribute AString id;
+
+ void initialize(in AString url);
+
+ [binaryname(PostMessageMoz)]
+ void postMessage(in AString message);
+
+ void addListener(in nsIWorkerDebuggerListener listener);
+
+ void removeListener(in nsIWorkerDebuggerListener listener);
+
+ // Indicate whether the debugger has finished initializing. By default the
+ // debugger will be considered initialized when the onRegister hooks in all
+ // nsIWorkerDebuggerManagerListener have been called.
+ //
+ // setDebuggerReady(false) can be called during an onRegister hook to mark
+ // the debugger as not being ready yet. This will prevent all content from
+ // running in the worker, including the worker's main script and any messages
+ // posted to it. Other runnables will still execute in the worker as normal.
+ //
+ // When the debugger is ready, setDebuggerReady(true) should then be called
+ // to allow the worker to begin executing content.
+ void setDebuggerReady(in boolean ready);
+};
diff --git a/dom/workers/nsIWorkerDebuggerManager.idl b/dom/workers/nsIWorkerDebuggerManager.idl
new file mode 100644
index 0000000000..f7a0fb3091
--- /dev/null
+++ b/dom/workers/nsIWorkerDebuggerManager.idl
@@ -0,0 +1,22 @@
+#include "nsISupports.idl"
+
+interface nsISimpleEnumerator;
+interface nsIWorkerDebugger;
+
+[scriptable, uuid(d2aa74ee-6b98-4d5d-8173-4e23422daf1e)]
+interface nsIWorkerDebuggerManagerListener : nsISupports
+{
+ void onRegister(in nsIWorkerDebugger debugger);
+
+ void onUnregister(in nsIWorkerDebugger debugger);
+};
+
+[scriptable, builtinclass, uuid(056d7918-dc86-452a-b4e6-86da3405f015)]
+interface nsIWorkerDebuggerManager : nsISupports
+{
+ nsISimpleEnumerator getWorkerDebuggerEnumerator();
+
+ void addListener(in nsIWorkerDebuggerManagerListener listener);
+
+ void removeListener(in nsIWorkerDebuggerManagerListener listener);
+};
diff --git a/dom/workers/remoteworkers/PRemoteWorker.ipdl b/dom/workers/remoteworkers/PRemoteWorker.ipdl
new file mode 100644
index 0000000000..4da11c4840
--- /dev/null
+++ b/dom/workers/remoteworkers/PRemoteWorker.ipdl
@@ -0,0 +1,90 @@
+/* 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 protocol PBackground;
+include protocol PFetchEventOpProxy;
+
+include DOMTypes;
+include ServiceWorkerOpArgs;
+include RemoteWorkerTypes;
+
+namespace mozilla {
+namespace dom {
+
+struct RemoteWorkerSuspendOp
+{};
+
+struct RemoteWorkerResumeOp
+{};
+
+struct RemoteWorkerFreezeOp
+{};
+
+struct RemoteWorkerThawOp
+{};
+
+struct RemoteWorkerTerminateOp
+{};
+
+struct RemoteWorkerPortIdentifierOp
+{
+ MessagePortIdentifier portIdentifier;
+};
+
+struct RemoteWorkerAddWindowIDOp
+{
+ uint64_t windowID;
+};
+
+struct RemoteWorkerRemoveWindowIDOp
+{
+ uint64_t windowID;
+};
+
+union RemoteWorkerOp {
+ RemoteWorkerSuspendOp;
+ RemoteWorkerResumeOp;
+ RemoteWorkerFreezeOp;
+ RemoteWorkerThawOp;
+ RemoteWorkerTerminateOp;
+ RemoteWorkerPortIdentifierOp;
+ RemoteWorkerAddWindowIDOp;
+ RemoteWorkerRemoveWindowIDOp;
+};
+
+// This protocol is used to make a remote worker controllable from the parent
+// process. The parent process will receive operations from the
+// PRemoteWorkerController protocol.
+protocol PRemoteWorker
+{
+ manager PBackground;
+
+ manages PFetchEventOpProxy;
+
+parent:
+ async Created(bool aStatus);
+
+ async Error(ErrorValue aValue);
+
+ async NotifyLock(bool aCreated);
+
+ async NotifyWebTransport(bool aCreated);
+
+ async Close();
+
+ async SetServiceWorkerSkipWaitingFlag() returns (bool aOk);
+
+child:
+ async PFetchEventOpProxy(ParentToChildServiceWorkerFetchEventOpArgs aArgs);
+
+ async __delete__();
+
+ async ExecOp(RemoteWorkerOp op);
+
+ async ExecServiceWorkerOp(ServiceWorkerOpArgs aArgs)
+ returns (ServiceWorkerOpResult aResult);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/PRemoteWorkerController.ipdl b/dom/workers/remoteworkers/PRemoteWorkerController.ipdl
new file mode 100644
index 0000000000..5c3818a27d
--- /dev/null
+++ b/dom/workers/remoteworkers/PRemoteWorkerController.ipdl
@@ -0,0 +1,49 @@
+/* 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 protocol PBackground;
+include protocol PFetchEventOp;
+
+include RemoteWorkerTypes;
+include ServiceWorkerOpArgs;
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * Proxy protocol used by ServiceWorkerManager whose canonical state exists on
+ * the main thread to control/receive feedback from RemoteWorkers which are
+ * canonically controlled from the PBackground thread. Exclusively for use from
+ * the parent process main thread to the parent process PBackground thread.
+ */
+[ManualDealloc]
+protocol PRemoteWorkerController {
+ manager PBackground;
+
+ manages PFetchEventOp;
+
+ child:
+ async CreationFailed();
+
+ async CreationSucceeded();
+
+ async ErrorReceived(ErrorValue aError);
+
+ async Terminated();
+
+ async SetServiceWorkerSkipWaitingFlag() returns (bool aOk);
+
+ parent:
+ async PFetchEventOp(ParentToParentServiceWorkerFetchEventOpArgs aArgs);
+
+ async __delete__();
+
+ async Shutdown() returns (bool aOk);
+
+ async ExecServiceWorkerOp(ServiceWorkerOpArgs aArgs)
+ returns (ServiceWorkerOpResult aResult);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/PRemoteWorkerService.ipdl b/dom/workers/remoteworkers/PRemoteWorkerService.ipdl
new file mode 100644
index 0000000000..6287bb56a1
--- /dev/null
+++ b/dom/workers/remoteworkers/PRemoteWorkerService.ipdl
@@ -0,0 +1,25 @@
+/* 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 protocol PBackground;
+
+include ProtocolTypes;
+include RemoteWorkerTypes;
+
+namespace mozilla {
+namespace dom {
+
+// Simple protocol to register any active RemoteWorkerService running on any
+// process. Initialization/registration is delayed for preallocated processes
+// until the process takes on its final remoteType.
+protocol PRemoteWorkerService
+{
+ manager PBackground;
+
+parent:
+ async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerChild.cpp b/dom/workers/remoteworkers/RemoteWorkerChild.cpp
new file mode 100644
index 0000000000..163fd00fec
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerChild.cpp
@@ -0,0 +1,1011 @@
+/* -*- 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 "RemoteWorkerChild.h"
+
+#include <utility>
+
+#include "MainThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIPrincipal.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+
+#include "RemoteWorkerService.h"
+#include "mozilla/ArrayAlgorithm.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/Services.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/FetchEventOpProxyChild.h"
+#include "mozilla/dom/IndexedDatabaseManager.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::IsRemoteTypeAllowed
+#include "mozilla/dom/RemoteWorkerTypes.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/dom/ServiceWorkerInterceptController.h"
+#include "mozilla/dom/ServiceWorkerOp.h"
+#include "mozilla/dom/ServiceWorkerRegistrationDescriptor.h"
+#include "mozilla/dom/ServiceWorkerShutdownState.h"
+#include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/dom/workerinternals/ScriptLoader.h"
+#include "mozilla/dom/WorkerError.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/PermissionManager.h"
+
+mozilla::LazyLogModule gRemoteWorkerChildLog("RemoteWorkerChild");
+
+#ifdef LOG
+# undef LOG
+#endif
+#define LOG(fmt) MOZ_LOG(gRemoteWorkerChildLog, mozilla::LogLevel::Verbose, fmt)
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+using workerinternals::ChannelFromScriptURLMainThread;
+
+namespace {
+
+class SharedWorkerInterfaceRequestor final : public nsIInterfaceRequestor {
+ public:
+ NS_DECL_ISUPPORTS
+
+ SharedWorkerInterfaceRequestor() {
+ // This check must match the code nsDocShell::Create.
+ if (XRE_IsParentProcess()) {
+ mSWController = new ServiceWorkerInterceptController();
+ }
+ }
+
+ NS_IMETHOD
+ GetInterface(const nsIID& aIID, void** aSink) override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mSWController &&
+ aIID.Equals(NS_GET_IID(nsINetworkInterceptController))) {
+ // If asked for the network intercept controller, ask the outer requestor,
+ // which could be the docshell.
+ RefPtr<ServiceWorkerInterceptController> swController = mSWController;
+ swController.forget(aSink);
+ return NS_OK;
+ }
+
+ return NS_NOINTERFACE;
+ }
+
+ private:
+ ~SharedWorkerInterfaceRequestor() = default;
+
+ RefPtr<ServiceWorkerInterceptController> mSWController;
+};
+
+NS_IMPL_ADDREF(SharedWorkerInterfaceRequestor)
+NS_IMPL_RELEASE(SharedWorkerInterfaceRequestor)
+NS_IMPL_QUERY_INTERFACE(SharedWorkerInterfaceRequestor, nsIInterfaceRequestor)
+
+// Normal runnable because AddPortIdentifier() is going to exec JS code.
+class MessagePortIdentifierRunnable final : public WorkerRunnable {
+ public:
+ MessagePortIdentifierRunnable(WorkerPrivate* aWorkerPrivate,
+ RemoteWorkerChild* aActor,
+ const MessagePortIdentifier& aPortIdentifier)
+ : WorkerRunnable(aWorkerPrivate),
+ mActor(aActor),
+ mPortIdentifier(aPortIdentifier) {}
+
+ private:
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ mActor->AddPortIdentifier(aCx, aWorkerPrivate, mPortIdentifier);
+ return true;
+ }
+
+ RefPtr<RemoteWorkerChild> mActor;
+ UniqueMessagePortId mPortIdentifier;
+};
+
+} // anonymous namespace
+
+RemoteWorkerChild::RemoteWorkerChild(const RemoteWorkerData& aData)
+ : mState(VariantType<Pending>(), "RemoteWorkerChild::mState"),
+ mServiceKeepAlive(RemoteWorkerService::MaybeGetKeepAlive()),
+ mIsServiceWorker(aData.serviceWorkerData().type() ==
+ OptionalServiceWorkerData::TServiceWorkerData) {
+ MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread());
+}
+
+RemoteWorkerChild::~RemoteWorkerChild() {
+#ifdef DEBUG
+ auto lock = mState.Lock();
+ MOZ_ASSERT(lock->is<Killed>());
+#endif
+}
+
+void RemoteWorkerChild::ActorDestroy(ActorDestroyReason) {
+ auto launcherData = mLauncherData.Access();
+
+ Unused << NS_WARN_IF(!launcherData->mTerminationPromise.IsEmpty());
+ launcherData->mTerminationPromise.RejectIfExists(NS_ERROR_DOM_ABORT_ERR,
+ __func__);
+
+ auto lock = mState.Lock();
+
+ // If the worker hasn't shutdown or begun shutdown, we need to ensure it gets
+ // canceled.
+ if (NS_WARN_IF(!lock->is<Killed>() && !lock->is<Canceled>())) {
+ // In terms of strong references to this RemoteWorkerChild, at this moment:
+ // - IPC is holding a strong reference that will be dropped in the near
+ // future after this method returns.
+ // - If the worker has been started by ExecWorkerOnMainThread, then
+ // WorkerPrivate::mRemoteWorkerController is a strong reference to us.
+ // If the worker has not been started, ExecWorker's runnable lambda will
+ // have a strong reference that will cover the call to
+ // ExecWorkerOnMainThread.
+ // - The WorkerPrivate cancellation and termination callbacks will also
+ // hold strong references, but those callbacks will not outlive the
+ // WorkerPrivate and are not exposed to callers like
+ // mRemoteWorkerController is.
+ //
+ // Note that this call to RequestWorkerCancellation can still race worker
+ // cancellation, in which case the strong reference obtained by
+ // NewRunnableMethod can end up being the last strong reference.
+ // (RequestWorkerCancellation handles the case that the Worker is already
+ // canceled if this happens.)
+ RefPtr<nsIRunnable> runnable =
+ NewRunnableMethod("RequestWorkerCancellation", this,
+ &RemoteWorkerChild::RequestWorkerCancellation);
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget()));
+ }
+}
+
+void RemoteWorkerChild::ExecWorker(const RemoteWorkerData& aData) {
+#ifdef DEBUG
+ MOZ_ASSERT(GetActorEventTarget()->IsOnCurrentThread());
+ auto launcherData = mLauncherData.Access();
+ MOZ_ASSERT(CanSend());
+#endif
+
+ RefPtr<RemoteWorkerChild> self = this;
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [self = std::move(self), data = aData]() mutable {
+ nsresult rv = self->ExecWorkerOnMainThread(std::move(data));
+
+ // Creation failure will already have been reported via the method
+ // above internally using ScopeExit.
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+}
+
+nsresult RemoteWorkerChild::ExecWorkerOnMainThread(RemoteWorkerData&& aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Ensure that the IndexedDatabaseManager is initialized so that if any
+ // workers do any IndexedDB calls that all of IDB's prefs/etc. are
+ // initialized.
+ Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate());
+
+ auto scopeExit =
+ MakeScopeExit([&] { ExceptionalErrorTransitionDuringExecWorker(); });
+
+ // Verify the the RemoteWorker should be really allowed to run in this
+ // process, and fail if it shouldn't (This shouldn't normally happen,
+ // unless the RemoteWorkerData has been tempered in the process it was
+ // sent from).
+ if (!RemoteWorkerManager::IsRemoteTypeAllowed(aData)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ auto principalOrErr = PrincipalInfoToPrincipal(aData.principalInfo());
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ return principalOrErr.unwrapErr();
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+
+ auto loadingPrincipalOrErr =
+ PrincipalInfoToPrincipal(aData.loadingPrincipalInfo());
+ if (NS_WARN_IF(loadingPrincipalOrErr.isErr())) {
+ return loadingPrincipalOrErr.unwrapErr();
+ }
+
+ auto partitionedPrincipalOrErr =
+ PrincipalInfoToPrincipal(aData.partitionedPrincipalInfo());
+ if (NS_WARN_IF(partitionedPrincipalOrErr.isErr())) {
+ return partitionedPrincipalOrErr.unwrapErr();
+ }
+
+ WorkerLoadInfo info;
+ info.mBaseURI = DeserializeURI(aData.baseScriptURL());
+ info.mResolvedScriptURI = DeserializeURI(aData.resolvedScriptURL());
+
+ info.mPrincipalInfo = MakeUnique<PrincipalInfo>(aData.principalInfo());
+ info.mPartitionedPrincipalInfo =
+ MakeUnique<PrincipalInfo>(aData.partitionedPrincipalInfo());
+
+ info.mReferrerInfo = aData.referrerInfo();
+ info.mDomain = aData.domain();
+ info.mTrials = aData.originTrials();
+ info.mPrincipal = principal;
+ info.mPartitionedPrincipal = partitionedPrincipalOrErr.unwrap();
+ info.mLoadingPrincipal = loadingPrincipalOrErr.unwrap();
+ info.mStorageAccess = aData.storageAccess();
+ info.mUseRegularPrincipal = aData.useRegularPrincipal();
+ info.mHasStorageAccessPermissionGranted =
+ aData.hasStorageAccessPermissionGranted();
+ info.mIsThirdPartyContextToTopWindow = aData.isThirdPartyContextToTopWindow();
+ info.mOriginAttributes =
+ BasePrincipal::Cast(principal)->OriginAttributesRef();
+ info.mShouldResistFingerprinting = aData.shouldResistFingerprinting();
+ net::CookieJarSettings::Deserialize(aData.cookieJarSettings(),
+ getter_AddRefs(info.mCookieJarSettings));
+ info.mCookieJarSettingsArgs = aData.cookieJarSettings();
+
+ // Default CSP permissions for now. These will be overrided if necessary
+ // based on the script CSP headers during load in ScriptLoader.
+ info.mEvalAllowed = true;
+ info.mReportEvalCSPViolations = false;
+ info.mWasmEvalAllowed = true;
+ info.mReportWasmEvalCSPViolations = false;
+ info.mSecureContext = aData.isSecureContext()
+ ? WorkerLoadInfo::eSecureContext
+ : WorkerLoadInfo::eInsecureContext;
+
+ WorkerPrivate::OverrideLoadInfoLoadGroup(info, info.mLoadingPrincipal);
+
+ RefPtr<SharedWorkerInterfaceRequestor> requestor =
+ new SharedWorkerInterfaceRequestor();
+ info.mInterfaceRequestor->SetOuterRequestor(requestor);
+
+ Maybe<ClientInfo> clientInfo;
+ if (aData.clientInfo().isSome()) {
+ clientInfo.emplace(ClientInfo(aData.clientInfo().ref()));
+ }
+
+ nsresult rv = NS_OK;
+
+ if (clientInfo.isSome()) {
+ Maybe<mozilla::ipc::CSPInfo> cspInfo = clientInfo.ref().GetCspInfo();
+ if (cspInfo.isSome()) {
+ info.mCSP = CSPInfoToCSP(cspInfo.ref(), nullptr);
+ info.mCSPInfo = MakeUnique<CSPInfo>();
+ rv = CSPToCSPInfo(info.mCSP, info.mCSPInfo.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ rv = info.SetPrincipalsAndCSPOnMainThread(
+ info.mPrincipal, info.mPartitionedPrincipal, info.mLoadGroup, info.mCSP);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsString workerPrivateId;
+
+ if (mIsServiceWorker) {
+ ServiceWorkerData& data = aData.serviceWorkerData().get_ServiceWorkerData();
+
+ MOZ_ASSERT(!data.id().IsEmpty());
+ workerPrivateId = std::move(data.id());
+
+ info.mServiceWorkerCacheName = data.cacheName();
+ info.mServiceWorkerDescriptor.emplace(data.descriptor());
+ info.mServiceWorkerRegistrationDescriptor.emplace(
+ data.registrationDescriptor());
+ info.mLoadFlags = static_cast<nsLoadFlags>(data.loadFlags());
+ } else {
+ // Top level workers' main script use the document charset for the script
+ // uri encoding.
+ rv = ChannelFromScriptURLMainThread(
+ info.mLoadingPrincipal, nullptr /* parent document */, info.mLoadGroup,
+ info.mResolvedScriptURI, aData.type(), aData.credentials(), clientInfo,
+ nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER, info.mCookieJarSettings,
+ info.mReferrerInfo, getter_AddRefs(info.mChannel));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ info.mAgentClusterId = aData.agentClusterId();
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ ErrorResult error;
+ RefPtr<RemoteWorkerChild> self = this;
+ RefPtr<WorkerPrivate> workerPrivate = WorkerPrivate::Constructor(
+ jsapi.cx(), aData.originalScriptURL(), false,
+ mIsServiceWorker ? WorkerKindService : WorkerKindShared,
+ aData.credentials(), aData.type(), aData.name(), VoidCString(), &info,
+ error, std::move(workerPrivateId),
+ [self](bool aEverRan) {
+ self->OnWorkerCancellationTransitionStateFromPendingOrRunningToCanceled();
+ },
+ // This will be invoked here on the main thread when the worker is already
+ // fully shutdown. This replaces a prior approach where we would
+ // begin to transition when the worker thread would reach the Canceling
+ // state. This lambda ensures that we not only wait for the Killing state
+ // to be reached but that the global shutdown has already occurred.
+ [self]() { self->TransitionStateFromCanceledToKilled(); });
+
+ if (NS_WARN_IF(error.Failed())) {
+ MOZ_ASSERT(!workerPrivate);
+
+ rv = error.StealNSResult();
+ return rv;
+ }
+
+ workerPrivate->SetRemoteWorkerController(this);
+
+ // This wants to run as a normal task sequentially after the top level script
+ // evaluation, so the hybrid target is the correct choice between hybrid and
+ // `ControlEventTarget`.
+ nsCOMPtr<nsISerialEventTarget> workerTarget =
+ workerPrivate->HybridEventTarget();
+
+ nsCOMPtr<nsIRunnable> runnable = NewCancelableRunnableMethod(
+ "InitialzeOnWorker", this, &RemoteWorkerChild::InitializeOnWorker);
+
+ {
+ MOZ_ASSERT(workerPrivate);
+ auto lock = mState.Lock();
+ // We MUST be pending here, so direct access is ok.
+ lock->as<Pending>().mWorkerPrivate = std::move(workerPrivate);
+ }
+
+ if (mIsServiceWorker) {
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [workerTarget,
+ initializeWorkerRunnable = std::move(runnable)]() mutable {
+ Unused << NS_WARN_IF(NS_FAILED(
+ workerTarget->Dispatch(initializeWorkerRunnable.forget())));
+ });
+
+ RefPtr<PermissionManager> permissionManager =
+ PermissionManager::GetInstance();
+ if (!permissionManager) {
+ return NS_ERROR_FAILURE;
+ }
+ permissionManager->WhenPermissionsAvailable(principal, r);
+ } else {
+ if (NS_WARN_IF(NS_FAILED(workerTarget->Dispatch(runnable.forget())))) {
+ rv = NS_ERROR_FAILURE;
+ return rv;
+ }
+ }
+
+ scopeExit.release();
+
+ return NS_OK;
+}
+
+void RemoteWorkerChild::RequestWorkerCancellation() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("RequestWorkerCancellation[this=%p]", this));
+
+ // We want to ensure that we've requested the worker be canceled. So if the
+ // worker is running, cancel it. We can't do this with the lock held,
+ // however, because our lambdas will want to manipulate the state.
+ RefPtr<WorkerPrivate> cancelWith;
+ {
+ auto lock = mState.Lock();
+ if (lock->is<Pending>()) {
+ cancelWith = lock->as<Pending>().mWorkerPrivate;
+ } else if (lock->is<Running>()) {
+ cancelWith = lock->as<Running>().mWorkerPrivate;
+ }
+ }
+
+ if (cancelWith) {
+ cancelWith->Cancel();
+ }
+}
+
+// This method will be invoked on the worker after the top-level
+// CompileScriptRunnable task has succeeded and as long as the worker has not
+// been closed/canceled. There are edge-cases related to cancellation, but we
+// have our caller ensure that we are only called as long as the worker's state
+// is Running.
+//
+// (https://bugzilla.mozilla.org/show_bug.cgi?id=1800659 will eliminate
+// cancellation, and the documentation around that bug / in design documents
+// helps provide more context about this.)
+void RemoteWorkerChild::InitializeOnWorker() {
+ nsCOMPtr<nsIRunnable> r =
+ NewRunnableMethod("TransitionStateToRunning", this,
+ &RemoteWorkerChild::TransitionStateToRunning);
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+}
+
+RefPtr<GenericNonExclusivePromise> RemoteWorkerChild::GetTerminationPromise() {
+ auto launcherData = mLauncherData.Access();
+ return launcherData->mTerminationPromise.Ensure(__func__);
+}
+
+void RemoteWorkerChild::CreationSucceededOnAnyThread() {
+ CreationSucceededOrFailedOnAnyThread(true);
+}
+
+void RemoteWorkerChild::CreationFailedOnAnyThread() {
+ CreationSucceededOrFailedOnAnyThread(false);
+}
+
+void RemoteWorkerChild::CreationSucceededOrFailedOnAnyThread(
+ bool aDidCreationSucceed) {
+#ifdef DEBUG
+ {
+ auto lock = mState.Lock();
+ MOZ_ASSERT_IF(aDidCreationSucceed, lock->is<Running>());
+ MOZ_ASSERT_IF(!aDidCreationSucceed, lock->is<Killed>());
+ }
+#endif
+
+ RefPtr<RemoteWorkerChild> self = this;
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__,
+ [self = std::move(self), didCreationSucceed = aDidCreationSucceed] {
+ auto launcherData = self->mLauncherData.Access();
+
+ if (!self->CanSend() || launcherData->mDidSendCreated) {
+ return;
+ }
+
+ Unused << self->SendCreated(didCreationSucceed);
+ launcherData->mDidSendCreated = true;
+ });
+
+ GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void RemoteWorkerChild::CloseWorkerOnMainThread() {
+ AssertIsOnMainThread();
+
+ LOG(("CloseWorkerOnMainThread[this=%p]", this));
+
+ // We can't hold the state lock while calling WorkerPrivate::Cancel because
+ // the lambda callback will want to touch the state, so save off the
+ // WorkerPrivate so we can cancel it (if we need to cancel it).
+ RefPtr<WorkerPrivate> cancelWith;
+ {
+ auto lock = mState.Lock();
+
+ if (lock->is<Pending>()) {
+ cancelWith = lock->as<Pending>().mWorkerPrivate;
+ // There should be no way for this code to run before we
+ // ExecWorkerOnMainThread runs, which means that either it should have
+ // set a WorkerPrivate on Pending, or its error handling should already
+ // have transitioned us to Canceled and Killing in that order. (It's
+ // also possible that it assigned a WorkerPrivate and subsequently we
+ // transitioned to Running, which would put us in the next branch.)
+ MOZ_DIAGNOSTIC_ASSERT(cancelWith);
+ } else if (lock->is<Running>()) {
+ cancelWith = lock->as<Running>().mWorkerPrivate;
+ }
+ }
+
+ // It's very okay for us to not have a WorkerPrivate here if we've already
+ // canceled the worker or if errors happened.
+ if (cancelWith) {
+ cancelWith->Cancel();
+ }
+}
+
+/**
+ * Error reporting method
+ */
+void RemoteWorkerChild::ErrorPropagation(const ErrorValue& aValue) {
+ MOZ_ASSERT(GetActorEventTarget()->IsOnCurrentThread());
+
+ if (!CanSend()) {
+ return;
+ }
+
+ Unused << SendError(aValue);
+}
+
+void RemoteWorkerChild::ErrorPropagationDispatch(nsresult aError) {
+ MOZ_ASSERT(NS_FAILED(aError));
+
+ RefPtr<RemoteWorkerChild> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "RemoteWorkerChild::ErrorPropagationDispatch",
+ [self = std::move(self), aError]() { self->ErrorPropagation(aError); });
+
+ GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void RemoteWorkerChild::ErrorPropagationOnMainThread(
+ const WorkerErrorReport* aReport, bool aIsErrorEvent) {
+ AssertIsOnMainThread();
+
+ ErrorValue value;
+ if (aIsErrorEvent) {
+ ErrorData data(
+ 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);
+ }));
+ value = data;
+ } else {
+ value = void_t();
+ }
+
+ RefPtr<RemoteWorkerChild> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "RemoteWorkerChild::ErrorPropagationOnMainThread",
+ [self = std::move(self), value]() { self->ErrorPropagation(value); });
+
+ GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void RemoteWorkerChild::NotifyLock(bool aCreated) {
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction(__func__, [self = RefPtr(this), aCreated] {
+ if (!self->CanSend()) {
+ return;
+ }
+
+ Unused << self->SendNotifyLock(aCreated);
+ });
+
+ GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void RemoteWorkerChild::NotifyWebTransport(bool aCreated) {
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction(__func__, [self = RefPtr(this), aCreated] {
+ if (!self->CanSend()) {
+ return;
+ }
+
+ Unused << self->SendNotifyWebTransport(aCreated);
+ });
+
+ GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void RemoteWorkerChild::FlushReportsOnMainThread(
+ nsIConsoleReportCollector* aReporter) {
+ AssertIsOnMainThread();
+
+ bool reportErrorToBrowserConsole = true;
+
+ // Flush the reports.
+ for (uint32_t i = 0, len = mWindowIDs.Length(); i < len; ++i) {
+ aReporter->FlushReportsToConsole(
+ mWindowIDs[i], nsIConsoleReportCollector::ReportAction::Save);
+ reportErrorToBrowserConsole = false;
+ }
+
+ // Finally report to browser console if there is no any window.
+ if (reportErrorToBrowserConsole) {
+ aReporter->FlushReportsToConsole(0);
+ return;
+ }
+
+ aReporter->ClearConsoleReports();
+}
+
+/**
+ * Worker state transition methods
+ */
+RemoteWorkerChild::WorkerPrivateAccessibleState::
+ ~WorkerPrivateAccessibleState() {
+ // We should now only be performing state transitions on the main thread, so
+ // we should assert we're only releasing on the main thread.
+ MOZ_ASSERT(!mWorkerPrivate || NS_IsMainThread());
+ // mWorkerPrivate can be safely released on the main thread.
+ if (!mWorkerPrivate || NS_IsMainThread()) {
+ return;
+ }
+
+ // But as a backstop, do proxy the release to the main thread.
+ NS_ReleaseOnMainThread(
+ "RemoteWorkerChild::WorkerPrivateAccessibleState::mWorkerPrivate",
+ mWorkerPrivate.forget());
+}
+
+void RemoteWorkerChild::
+ OnWorkerCancellationTransitionStateFromPendingOrRunningToCanceled() {
+ auto lock = mState.Lock();
+
+ LOG(("TransitionStateFromPendingOrRunningToCanceled[this=%p]", this));
+
+ if (lock->is<Pending>()) {
+ TransitionStateFromPendingToCanceled(lock.ref());
+ } else if (lock->is<Running>()) {
+ *lock = VariantType<Canceled>();
+ } else {
+ MOZ_ASSERT(false, "State should have been Pending or Running");
+ }
+}
+
+void RemoteWorkerChild::TransitionStateFromPendingToCanceled(State& aState) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aState.is<Pending>());
+ LOG(("TransitionStateFromPendingToCanceled[this=%p]", this));
+
+ CancelAllPendingOps(aState);
+
+ aState = VariantType<Canceled>();
+}
+
+void RemoteWorkerChild::TransitionStateFromCanceledToKilled() {
+ AssertIsOnMainThread();
+
+ LOG(("TransitionStateFromCanceledToKilled[this=%p]", this));
+
+ auto lock = mState.Lock();
+ MOZ_ASSERT(lock->is<Canceled>());
+
+ *lock = VariantType<Killed>();
+
+ RefPtr<RemoteWorkerChild> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(__func__, [self]() {
+ auto launcherData = self->mLauncherData.Access();
+
+ // (We maintain the historical ordering of resolving this promise prior to
+ // calling SendClose, however the previous code used 2 separate dispatches
+ // to this thread for the resolve and SendClose, and there inherently
+ // would be a race between the runnables resulting from the resolved
+ // promise and the promise containing the call to SendClose. Now it's
+ // entirely clear that our call to SendClose will effectively run before
+ // any of the resolved promises are able to do anything.)
+ launcherData->mTerminationPromise.ResolveIfExists(true, __func__);
+
+ if (self->CanSend()) {
+ Unused << self->SendClose();
+ }
+ });
+
+ GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void RemoteWorkerChild::TransitionStateToRunning() {
+ AssertIsOnMainThread();
+
+ LOG(("TransitionStateToRunning[this=%p]", this));
+
+ nsTArray<RefPtr<Op>> pendingOps;
+
+ {
+ auto lock = mState.Lock();
+
+ // Because this is an async notification sent from the worker to the main
+ // thread, it's very possible that we've already decided on the main thread
+ // to transition to the Canceled state, in which case there is nothing for
+ // us to do here.
+ if (!lock->is<Pending>()) {
+ LOG(("State is already not pending in TransitionStateToRunning[this=%p]!",
+ this));
+ return;
+ }
+
+ RefPtr<WorkerPrivate> workerPrivate =
+ std::move(lock->as<Pending>().mWorkerPrivate);
+ pendingOps = std::move(lock->as<Pending>().mPendingOps);
+
+ // Move the worker private into place to avoid gratuitous ref churn; prior
+ // comments here suggest the Variant can't accept a move.
+ *lock = VariantType<Running>();
+ lock->as<Running>().mWorkerPrivate = std::move(workerPrivate);
+ }
+
+ CreationSucceededOnAnyThread();
+
+ RefPtr<RemoteWorkerChild> self = this;
+ for (auto& op : pendingOps) {
+ op->StartOnMainThread(self);
+ }
+}
+
+void RemoteWorkerChild::ExceptionalErrorTransitionDuringExecWorker() {
+ AssertIsOnMainThread();
+
+ LOG(("ExceptionalErrorTransitionDuringExecWorker[this=%p]", this));
+
+ // This method is called synchronously by ExecWorkerOnMainThread in the event
+ // of any error. Because we only transition to Running on the main thread
+ // as the result of a notification from the worker, we know our state will be
+ // Pending, but mWorkerPrivate may or may not be null, as we may not have
+ // gotten to spawning the worker.
+ //
+ // In the event the worker exists, we need to Cancel() it. We must do this
+ // without the lock held because our call to Cancel() will invoke the
+ // cancellation callback we created which will call TransitionStateToCanceled,
+ // and we can't be holding the lock when that happens.
+
+ RefPtr<WorkerPrivate> cancelWith;
+
+ {
+ auto lock = mState.Lock();
+
+ MOZ_ASSERT(lock->is<Pending>());
+ if (lock->is<Pending>()) {
+ cancelWith = lock->as<Pending>().mWorkerPrivate;
+ if (!cancelWith) {
+ // The worker wasn't actually created, so we should synthetically
+ // transition to canceled and onward. Since we have the lock,
+ // perform the transition now for clarity, but we'll handle the rest of
+ // this case after dropping the lock.
+ TransitionStateFromPendingToCanceled(lock.ref());
+ }
+ }
+ }
+
+ if (cancelWith) {
+ cancelWith->Cancel();
+ } else {
+ TransitionStateFromCanceledToKilled();
+ CreationFailedOnAnyThread();
+ }
+}
+
+/**
+ * Operation execution classes/methods
+ */
+class RemoteWorkerChild::SharedWorkerOp : public RemoteWorkerChild::Op {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWorkerOp, override)
+
+ explicit SharedWorkerOp(RemoteWorkerOp&& aOp) : mOp(std::move(aOp)) {}
+
+ bool MaybeStart(RemoteWorkerChild* aOwner,
+ RemoteWorkerChild::State& aState) override {
+ MOZ_ASSERT(!mStarted);
+ MOZ_ASSERT(aOwner);
+ // Thread: We are on the Worker Launcher thread.
+
+ // Return false, indicating we should queue this op if our current state is
+ // pending and this isn't a termination op (which should skip the line).
+ if (aState.is<Pending>() && !IsTerminationOp()) {
+ return false;
+ }
+
+ // If the worker is already shutting down (which should be unexpected
+ // because we should be told new operations after a termination op), just
+ // return true to indicate the op should be discarded.
+ if (aState.is<Canceled>() || aState.is<Killed>()) {
+#ifdef DEBUG
+ mStarted = true;
+#endif
+
+ return true;
+ }
+
+ MOZ_ASSERT(aState.is<Running>() || IsTerminationOp());
+
+ RefPtr<SharedWorkerOp> self = this;
+ RefPtr<RemoteWorkerChild> owner = aOwner;
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [self = std::move(self), owner = std::move(owner)]() mutable {
+ {
+ auto lock = owner->mState.Lock();
+
+ if (NS_WARN_IF(lock->is<Canceled>() || lock->is<Killed>())) {
+ self->Cancel();
+ return;
+ }
+ }
+
+ self->StartOnMainThread(owner);
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+
+#ifdef DEBUG
+ mStarted = true;
+#endif
+
+ return true;
+ }
+
+ void StartOnMainThread(RefPtr<RemoteWorkerChild>& aOwner) final {
+ using Running = RemoteWorkerChild::Running;
+
+ AssertIsOnMainThread();
+
+ if (IsTerminationOp()) {
+ aOwner->CloseWorkerOnMainThread();
+ return;
+ }
+
+ auto lock = aOwner->mState.Lock();
+ MOZ_ASSERT(lock->is<Running>());
+ if (!lock->is<Running>()) {
+ aOwner->ErrorPropagationDispatch(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ RefPtr<WorkerPrivate> workerPrivate = lock->as<Running>().mWorkerPrivate;
+
+ MOZ_ASSERT(workerPrivate);
+
+ if (mOp.type() == RemoteWorkerOp::TRemoteWorkerSuspendOp) {
+ workerPrivate->ParentWindowPaused();
+ } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerResumeOp) {
+ workerPrivate->ParentWindowResumed();
+ } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerFreezeOp) {
+ workerPrivate->Freeze(nullptr);
+ } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerThawOp) {
+ workerPrivate->Thaw(nullptr);
+ } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerPortIdentifierOp) {
+ RefPtr<MessagePortIdentifierRunnable> r =
+ new MessagePortIdentifierRunnable(
+ workerPrivate, aOwner,
+ mOp.get_RemoteWorkerPortIdentifierOp().portIdentifier());
+
+ if (NS_WARN_IF(!r->Dispatch())) {
+ aOwner->ErrorPropagationDispatch(NS_ERROR_FAILURE);
+ }
+ } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerAddWindowIDOp) {
+ aOwner->mWindowIDs.AppendElement(
+ mOp.get_RemoteWorkerAddWindowIDOp().windowID());
+ } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerRemoveWindowIDOp) {
+ aOwner->mWindowIDs.RemoveElement(
+ mOp.get_RemoteWorkerRemoveWindowIDOp().windowID());
+ } else {
+ MOZ_CRASH("Unknown RemoteWorkerOp type!");
+ }
+ }
+
+ void Cancel() override {
+#ifdef DEBUG
+ mStarted = true;
+#endif
+ }
+
+ private:
+ ~SharedWorkerOp() { MOZ_ASSERT(mStarted); }
+
+ bool IsTerminationOp() const {
+ return mOp.type() == RemoteWorkerOp::TRemoteWorkerTerminateOp;
+ }
+
+ RemoteWorkerOp mOp;
+
+#ifdef DEBUG
+ bool mStarted = false;
+#endif
+};
+
+void RemoteWorkerChild::AddPortIdentifier(
+ JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ UniqueMessagePortId& aPortIdentifier) {
+ if (NS_WARN_IF(!aWorkerPrivate->ConnectMessagePort(aCx, aPortIdentifier))) {
+ ErrorPropagationDispatch(NS_ERROR_FAILURE);
+ }
+}
+
+void RemoteWorkerChild::CancelAllPendingOps(State& aState) {
+ MOZ_ASSERT(aState.is<Pending>());
+
+ auto pendingOps = std::move(aState.as<Pending>().mPendingOps);
+
+ for (auto& op : pendingOps) {
+ op->Cancel();
+ }
+}
+
+void RemoteWorkerChild::MaybeStartOp(RefPtr<Op>&& aOp) {
+ MOZ_ASSERT(aOp);
+
+ auto lock = mState.Lock();
+
+ if (!aOp->MaybeStart(this, lock.ref())) {
+ // Maybestart returns false only if we are <Pending>.
+ lock->as<Pending>().mPendingOps.AppendElement(std::move(aOp));
+ }
+}
+
+IPCResult RemoteWorkerChild::RecvExecOp(RemoteWorkerOp&& aOp) {
+ MOZ_ASSERT(!mIsServiceWorker);
+
+ MaybeStartOp(new SharedWorkerOp(std::move(aOp)));
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerChild::RecvExecServiceWorkerOp(
+ ServiceWorkerOpArgs&& aArgs, ExecServiceWorkerOpResolver&& aResolve) {
+ MOZ_ASSERT(mIsServiceWorker);
+ MOZ_ASSERT(
+ aArgs.type() !=
+ ServiceWorkerOpArgs::TParentToChildServiceWorkerFetchEventOpArgs,
+ "FetchEvent operations should be sent via PFetchEventOp(Proxy) actors!");
+
+ MaybeReportServiceWorkerShutdownProgress(aArgs);
+
+ MaybeStartOp(ServiceWorkerOp::Create(std::move(aArgs), std::move(aResolve)));
+
+ return IPC_OK();
+}
+
+RefPtr<GenericPromise>
+RemoteWorkerChild::MaybeSendSetServiceWorkerSkipWaitingFlag() {
+ RefPtr<GenericPromise::Private> promise =
+ new GenericPromise::Private(__func__);
+
+ RefPtr<RemoteWorkerChild> self = this;
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(__func__, [self = std::move(
+ self),
+ promise] {
+ if (!self->CanSend()) {
+ promise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ return;
+ }
+
+ self->SendSetServiceWorkerSkipWaitingFlag()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](
+ const SetServiceWorkerSkipWaitingFlagPromise::ResolveOrRejectValue&
+ aResult) {
+ if (NS_WARN_IF(aResult.IsReject())) {
+ promise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ return;
+ }
+
+ promise->Resolve(aResult.ResolveValue(), __func__);
+ });
+ });
+
+ GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+
+ return promise;
+}
+
+/**
+ * PFetchEventOpProxy methods
+ */
+already_AddRefed<PFetchEventOpProxyChild>
+RemoteWorkerChild::AllocPFetchEventOpProxyChild(
+ const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) {
+ return RefPtr{new FetchEventOpProxyChild()}.forget();
+}
+
+IPCResult RemoteWorkerChild::RecvPFetchEventOpProxyConstructor(
+ PFetchEventOpProxyChild* aActor,
+ const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) {
+ MOZ_ASSERT(aActor);
+
+ (static_cast<FetchEventOpProxyChild*>(aActor))->Initialize(aArgs);
+
+ return IPC_OK();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerChild.h b/dom/workers/remoteworkers/RemoteWorkerChild.h
new file mode 100644
index 0000000000..78fc43246e
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerChild.h
@@ -0,0 +1,237 @@
+/* -*- 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_RemoteWorkerChild_h
+#define mozilla_dom_RemoteWorkerChild_h
+
+#include "nsCOMPtr.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+#include "mozilla/DataMutex.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ThreadBound.h"
+#include "mozilla/dom/PRemoteWorkerChild.h"
+#include "mozilla/dom/ServiceWorkerOpArgs.h"
+
+class nsISerialEventTarget;
+class nsIConsoleReportCollector;
+
+namespace mozilla::dom {
+
+class ErrorValue;
+class FetchEventOpProxyChild;
+class RemoteWorkerData;
+class RemoteWorkerServiceKeepAlive;
+class ServiceWorkerOp;
+class UniqueMessagePortId;
+class WeakWorkerRef;
+class WorkerErrorReport;
+class WorkerPrivate;
+
+/**
+ * Background-managed "Worker Launcher"-thread-resident created via the
+ * RemoteWorkerManager to actually spawn the worker. Currently, the worker will
+ * be spawned from the main thread due to nsIPrincipal not being able to be
+ * created on background threads and other ownership invariants, most of which
+ * can be relaxed in the future.
+ */
+class RemoteWorkerChild final : public PRemoteWorkerChild {
+ friend class FetchEventOpProxyChild;
+ friend class PRemoteWorkerChild;
+ friend class ServiceWorkerOp;
+
+ ~RemoteWorkerChild();
+
+ public:
+ // Note that all IPC-using methods must only be invoked on the
+ // RemoteWorkerService thread which the inherited
+ // IProtocol::GetActorEventTarget() will return for us.
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteWorkerChild, final)
+
+ explicit RemoteWorkerChild(const RemoteWorkerData& aData);
+
+ void ExecWorker(const RemoteWorkerData& aData);
+
+ void ErrorPropagationOnMainThread(const WorkerErrorReport* aReport,
+ bool aIsErrorEvent);
+
+ void NotifyLock(bool aCreated);
+
+ void NotifyWebTransport(bool aCreated);
+
+ void FlushReportsOnMainThread(nsIConsoleReportCollector* aReporter);
+
+ void AddPortIdentifier(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ UniqueMessagePortId& aPortIdentifier);
+
+ RefPtr<GenericNonExclusivePromise> GetTerminationPromise();
+
+ RefPtr<GenericPromise> MaybeSendSetServiceWorkerSkipWaitingFlag();
+
+ const nsTArray<uint64_t>& WindowIDs() const { return mWindowIDs; }
+
+ private:
+ class InitializeWorkerRunnable;
+
+ class Op;
+ class SharedWorkerOp;
+
+ struct WorkerPrivateAccessibleState {
+ ~WorkerPrivateAccessibleState();
+ RefPtr<WorkerPrivate> mWorkerPrivate;
+ };
+
+ // Initial state, mWorkerPrivate is initially null but will be initialized on
+ // the main thread by ExecWorkerOnMainThread when the WorkerPrivate is
+ // created. The state will transition to Running or Canceled, also from the
+ // main thread.
+ struct Pending : WorkerPrivateAccessibleState {
+ nsTArray<RefPtr<Op>> mPendingOps;
+ };
+
+ // Running, with the state transition happening on the main thread as a result
+ // of the worker successfully processing our initialization runnable,
+ // indicating that top-level script execution successfully completed. Because
+ // all of our state transitions happen on the main thread and are posed in
+ // terms of the main thread's perspective of the worker's state, it's very
+ // possible for us to skip directly from Pending to Canceled because we decide
+ // to cancel/terminate the worker prior to it finishing script loading or
+ // reporting back to us.
+ struct Running : WorkerPrivateAccessibleState {};
+
+ // Cancel() has been called on the WorkerPrivate on the main thread by a
+ // TerminationOp, top-level script evaluation has failed and canceled the
+ // worker, or in the case of a SharedWorker, close() has been called on
+ // the global scope by content code and the worker has advanced to the
+ // Canceling state. (Dedicated Workers can also self close, but they will
+ // never be RemoteWorkers. Although a SharedWorker can own DedicatedWorkers.)
+ // Browser shutdown will result in a TerminationOp thanks to use of a shutdown
+ // blocker in the parent, so the RuntimeService shouldn't get involved, but we
+ // would also handle that case acceptably too.
+ //
+ // Because worker self-closing is still handled by dispatching a runnable to
+ // the main thread to effectively call WorkerPrivate::Cancel(), there isn't
+ // a race between a worker deciding to self-close and our termination ops.
+ //
+ // In this state, we have dropped the reference to the WorkerPrivate and will
+ // no longer be dispatching runnables to the worker. We wait in this state
+ // until the termination lambda is invoked letting us know that the worker has
+ // entirely shutdown and we can advanced to the Killed state.
+ struct Canceled {};
+
+ // The worker termination lambda has been invoked and we know the Worker is
+ // entirely shutdown. (Inherently it is possible for us to advance to this
+ // state while the nsThread for the worker is still in the process of
+ // shutting down, but no more worker code will run on it.)
+ //
+ // This name is chosen to match the Worker's own state model.
+ struct Killed {};
+
+ using State = Variant<Pending, Running, Canceled, Killed>;
+
+ // The state of the WorkerPrivate as perceived by the owner on the main
+ // thread. All state transitions now happen on the main thread, but the
+ // Worker Launcher thread will consult the state and will directly append ops
+ // to the Pending queue
+ DataMutex<State> mState;
+
+ const RefPtr<RemoteWorkerServiceKeepAlive> mServiceKeepAlive;
+
+ class Op {
+ public:
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ virtual ~Op() = default;
+
+ virtual bool MaybeStart(RemoteWorkerChild* aOwner, State& aState) = 0;
+
+ virtual void StartOnMainThread(RefPtr<RemoteWorkerChild>& aOwner) = 0;
+
+ virtual void Cancel() = 0;
+ };
+
+ void ActorDestroy(ActorDestroyReason) override;
+
+ mozilla::ipc::IPCResult RecvExecOp(RemoteWorkerOp&& aOp);
+
+ mozilla::ipc::IPCResult RecvExecServiceWorkerOp(
+ ServiceWorkerOpArgs&& aArgs, ExecServiceWorkerOpResolver&& aResolve);
+
+ already_AddRefed<PFetchEventOpProxyChild> AllocPFetchEventOpProxyChild(
+ const ParentToChildServiceWorkerFetchEventOpArgs& aArgs);
+
+ mozilla::ipc::IPCResult RecvPFetchEventOpProxyConstructor(
+ PFetchEventOpProxyChild* aActor,
+ const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) override;
+
+ nsresult ExecWorkerOnMainThread(RemoteWorkerData&& aData);
+
+ void ExceptionalErrorTransitionDuringExecWorker();
+
+ void RequestWorkerCancellation();
+
+ void InitializeOnWorker();
+
+ void CreationSucceededOnAnyThread();
+
+ void CreationFailedOnAnyThread();
+
+ void CreationSucceededOrFailedOnAnyThread(bool aDidCreationSucceed);
+
+ // Cancels the worker if it has been started and ensures that we transition
+ // to the Terminated state once the worker has been terminated or we have
+ // ensured that it will never start.
+ void CloseWorkerOnMainThread();
+
+ void ErrorPropagation(const ErrorValue& aValue);
+
+ void ErrorPropagationDispatch(nsresult aError);
+
+ // When the WorkerPrivate Cancellation lambda is invoked, it's possible that
+ // we have not yet advanced to running from pending, so we could be in either
+ // state. This method is expected to be called by the Workers' cancellation
+ // lambda and will obtain the lock and call the
+ // TransitionStateFromPendingToCanceled if appropriate. Otherwise it will
+ // directly move from the running state to the canceled state which does not
+ // require additional cleanup.
+ void OnWorkerCancellationTransitionStateFromPendingOrRunningToCanceled();
+ // A helper used by the above method by the worker cancellation lambda if the
+ // the worker hasn't started running, or in exceptional cases where we bail
+ // out of the ExecWorker method early. The caller must be holding the lock
+ // (in order to pass in the state).
+ void TransitionStateFromPendingToCanceled(State& aState);
+ void TransitionStateFromCanceledToKilled();
+
+ void TransitionStateToRunning();
+
+ void TransitionStateToTerminated();
+
+ void TransitionStateToTerminated(State& aState);
+
+ void CancelAllPendingOps(State& aState);
+
+ void MaybeStartOp(RefPtr<Op>&& aOp);
+
+ const bool mIsServiceWorker;
+
+ // Touched on main-thread only.
+ nsTArray<uint64_t> mWindowIDs;
+
+ struct LauncherBoundData {
+ MozPromiseHolder<GenericNonExclusivePromise> mTerminationPromise;
+ // Flag to ensure we report creation at most once. This could be cleaned up
+ // further.
+ bool mDidSendCreated = false;
+ };
+
+ ThreadBound<LauncherBoundData> mLauncherData;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_RemoteWorkerChild_h
diff --git a/dom/workers/remoteworkers/RemoteWorkerController.cpp b/dom/workers/remoteworkers/RemoteWorkerController.cpp
new file mode 100644
index 0000000000..b0d56aa33d
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerController.cpp
@@ -0,0 +1,573 @@
+/* -*- 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 "RemoteWorkerController.h"
+
+#include <utility>
+
+#include "nsDebug.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RemoteLazyInputStreamStorage.h"
+#include "mozilla/dom/FetchEventOpParent.h"
+#include "mozilla/dom/FetchEventOpProxyParent.h"
+#include "mozilla/dom/MessagePortParent.h"
+#include "mozilla/dom/RemoteWorkerTypes.h"
+#include "mozilla/dom/ServiceWorkerCloneData.h"
+#include "mozilla/dom/ServiceWorkerShutdownState.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "RemoteWorkerControllerParent.h"
+#include "RemoteWorkerManager.h"
+#include "RemoteWorkerParent.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+/* static */
+already_AddRefed<RemoteWorkerController> RemoteWorkerController::Create(
+ const RemoteWorkerData& aData, RemoteWorkerObserver* aObserver,
+ base::ProcessId aProcessId) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aObserver);
+
+ RefPtr<RemoteWorkerController> controller =
+ new RemoteWorkerController(aData, aObserver);
+
+ RefPtr<RemoteWorkerManager> manager = RemoteWorkerManager::GetOrCreate();
+ MOZ_ASSERT(manager);
+
+ // XXX: We do not check for failure here, should we?
+ manager->Launch(controller, aData, aProcessId);
+
+ return controller.forget();
+}
+
+RemoteWorkerController::RemoteWorkerController(const RemoteWorkerData& aData,
+ RemoteWorkerObserver* aObserver)
+ : mObserver(aObserver),
+ mState(ePending),
+ mIsServiceWorker(aData.serviceWorkerData().type() ==
+ OptionalServiceWorkerData::TServiceWorkerData) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+}
+
+RemoteWorkerController::~RemoteWorkerController() {
+ AssertIsOnBackgroundThread();
+ MOZ_DIAGNOSTIC_ASSERT(mPendingOps.IsEmpty());
+}
+
+void RemoteWorkerController::SetWorkerActor(RemoteWorkerParent* aActor) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActor);
+ MOZ_ASSERT(aActor);
+
+ mActor = aActor;
+}
+
+void RemoteWorkerController::NoteDeadWorkerActor() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mActor);
+
+ // The actor has been destroyed without a proper close() notification. Let's
+ // inform the observer.
+ if (mState == eReady) {
+ mObserver->Terminated();
+ }
+
+ mActor = nullptr;
+
+ Shutdown();
+}
+
+void RemoteWorkerController::CreationFailed() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(mState == ePending || mState == eTerminated);
+
+ if (mState == eTerminated) {
+ MOZ_ASSERT(!mActor);
+ MOZ_ASSERT(mPendingOps.IsEmpty());
+ // Nothing to do.
+ return;
+ }
+
+ NoteDeadWorker();
+
+ mObserver->CreationFailed();
+}
+
+void RemoteWorkerController::CreationSucceeded() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mState == ePending || mState == eTerminated);
+
+ if (mState == eTerminated) {
+ MOZ_ASSERT(!mActor);
+ MOZ_ASSERT(mPendingOps.IsEmpty());
+ // Nothing to do.
+ return;
+ }
+
+ MOZ_ASSERT(mActor);
+ mState = eReady;
+
+ mObserver->CreationSucceeded();
+
+ auto pendingOps = std::move(mPendingOps);
+
+ for (auto& op : pendingOps) {
+ DebugOnly<bool> started = op->MaybeStart(this);
+ MOZ_ASSERT(started);
+ }
+}
+
+void RemoteWorkerController::ErrorPropagation(const ErrorValue& aValue) {
+ AssertIsOnBackgroundThread();
+
+ mObserver->ErrorReceived(aValue);
+}
+
+void RemoteWorkerController::NotifyLock(bool aCreated) {
+ AssertIsOnBackgroundThread();
+
+ mObserver->LockNotified(aCreated);
+}
+
+void RemoteWorkerController::NotifyWebTransport(bool aCreated) {
+ AssertIsOnBackgroundThread();
+
+ mObserver->WebTransportNotified(aCreated);
+}
+
+void RemoteWorkerController::WorkerTerminated() {
+ AssertIsOnBackgroundThread();
+
+ NoteDeadWorker();
+
+ mObserver->Terminated();
+}
+
+void RemoteWorkerController::CancelAllPendingOps() {
+ AssertIsOnBackgroundThread();
+
+ auto pendingOps = std::move(mPendingOps);
+
+ for (auto& op : pendingOps) {
+ op->Cancel();
+ }
+}
+
+void RemoteWorkerController::Shutdown() {
+ AssertIsOnBackgroundThread();
+ Unused << NS_WARN_IF(mIsServiceWorker && !mPendingOps.IsEmpty());
+
+ if (mState == eTerminated) {
+ MOZ_ASSERT(mPendingOps.IsEmpty());
+ return;
+ }
+
+ mState = eTerminated;
+
+ CancelAllPendingOps();
+
+ if (!mActor) {
+ return;
+ }
+
+ mActor->SetController(nullptr);
+
+ /**
+ * The "non-remote-side" of the Service Worker will have ensured that the
+ * remote worker is terminated before calling `Shutdown().`
+ */
+ if (mIsServiceWorker) {
+ mActor->MaybeSendDelete();
+ } else {
+ Unused << mActor->SendExecOp(RemoteWorkerTerminateOp());
+ }
+
+ mActor = nullptr;
+}
+
+void RemoteWorkerController::NoteDeadWorker() {
+ AssertIsOnBackgroundThread();
+
+ CancelAllPendingOps();
+
+ /**
+ * The "non-remote-side" of the Service Worker will initiate `Shutdown()`
+ * once it's notified that all dispatched operations have either completed
+ * or canceled. That is, it'll explicitly call `Shutdown()` later.
+ */
+ if (!mIsServiceWorker) {
+ Shutdown();
+ }
+}
+
+template <typename... Args>
+void RemoteWorkerController::MaybeStartSharedWorkerOp(Args&&... aArgs) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mIsServiceWorker);
+
+ UniquePtr<PendingSharedWorkerOp> op =
+ MakeUnique<PendingSharedWorkerOp>(std::forward<Args>(aArgs)...);
+
+ if (!op->MaybeStart(this)) {
+ mPendingOps.AppendElement(std::move(op));
+ }
+}
+
+void RemoteWorkerController::AddWindowID(uint64_t aWindowID) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aWindowID);
+
+ MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eAddWindowID, aWindowID);
+}
+
+void RemoteWorkerController::RemoveWindowID(uint64_t aWindowID) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aWindowID);
+
+ MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eRemoveWindowID, aWindowID);
+}
+
+void RemoteWorkerController::AddPortIdentifier(
+ const MessagePortIdentifier& aPortIdentifier) {
+ AssertIsOnBackgroundThread();
+
+ MaybeStartSharedWorkerOp(aPortIdentifier);
+}
+
+void RemoteWorkerController::Terminate() {
+ AssertIsOnBackgroundThread();
+
+ MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eTerminate);
+}
+
+void RemoteWorkerController::Suspend() {
+ AssertIsOnBackgroundThread();
+
+ MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eSuspend);
+}
+
+void RemoteWorkerController::Resume() {
+ AssertIsOnBackgroundThread();
+
+ MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eResume);
+}
+
+void RemoteWorkerController::Freeze() {
+ AssertIsOnBackgroundThread();
+
+ MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eFreeze);
+}
+
+void RemoteWorkerController::Thaw() {
+ AssertIsOnBackgroundThread();
+
+ MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eThaw);
+}
+
+RefPtr<ServiceWorkerOpPromise> RemoteWorkerController::ExecServiceWorkerOp(
+ ServiceWorkerOpArgs&& aArgs) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mIsServiceWorker);
+
+ RefPtr<ServiceWorkerOpPromise::Private> promise =
+ new ServiceWorkerOpPromise::Private(__func__);
+
+ UniquePtr<PendingServiceWorkerOp> op =
+ MakeUnique<PendingServiceWorkerOp>(std::move(aArgs), promise);
+
+ if (!op->MaybeStart(this)) {
+ mPendingOps.AppendElement(std::move(op));
+ }
+
+ return promise;
+}
+
+RefPtr<ServiceWorkerFetchEventOpPromise>
+RemoteWorkerController::ExecServiceWorkerFetchEventOp(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs,
+ RefPtr<FetchEventOpParent> aReal) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mIsServiceWorker);
+
+ RefPtr<ServiceWorkerFetchEventOpPromise::Private> promise =
+ new ServiceWorkerFetchEventOpPromise::Private(__func__);
+
+ UniquePtr<PendingSWFetchEventOp> op =
+ MakeUnique<PendingSWFetchEventOp>(aArgs, promise, std::move(aReal));
+
+ if (!op->MaybeStart(this)) {
+ mPendingOps.AppendElement(std::move(op));
+ }
+
+ return promise;
+}
+
+RefPtr<GenericPromise> RemoteWorkerController::SetServiceWorkerSkipWaitingFlag()
+ const {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mObserver);
+
+ RefPtr<GenericPromise::Private> promise =
+ new GenericPromise::Private(__func__);
+
+ static_cast<RemoteWorkerControllerParent*>(mObserver.get())
+ ->MaybeSendSetServiceWorkerSkipWaitingFlag(
+ [promise](bool aOk) { promise->Resolve(aOk, __func__); });
+
+ return promise;
+}
+
+bool RemoteWorkerController::IsTerminated() const {
+ return mState == eTerminated;
+}
+
+RemoteWorkerController::PendingSharedWorkerOp::PendingSharedWorkerOp(
+ Type aType, uint64_t aWindowID)
+ : mType(aType), mWindowID(aWindowID) {
+ AssertIsOnBackgroundThread();
+}
+
+RemoteWorkerController::PendingSharedWorkerOp::PendingSharedWorkerOp(
+ const MessagePortIdentifier& aPortIdentifier)
+ : mType(ePortIdentifier), mPortIdentifier(aPortIdentifier) {
+ AssertIsOnBackgroundThread();
+}
+
+RemoteWorkerController::PendingSharedWorkerOp::~PendingSharedWorkerOp() {
+ AssertIsOnBackgroundThread();
+ MOZ_DIAGNOSTIC_ASSERT(mCompleted);
+}
+
+bool RemoteWorkerController::PendingSharedWorkerOp::MaybeStart(
+ RemoteWorkerController* const aOwner) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mCompleted);
+ MOZ_ASSERT(aOwner);
+
+ if (aOwner->mState == RemoteWorkerController::eTerminated) {
+ Cancel();
+ return true;
+ }
+
+ if (aOwner->mState == RemoteWorkerController::ePending &&
+ mType != eTerminate) {
+ return false;
+ }
+
+ switch (mType) {
+ case eTerminate:
+ aOwner->Shutdown();
+ break;
+ case eSuspend:
+ Unused << aOwner->mActor->SendExecOp(RemoteWorkerSuspendOp());
+ break;
+ case eResume:
+ Unused << aOwner->mActor->SendExecOp(RemoteWorkerResumeOp());
+ break;
+ case eFreeze:
+ Unused << aOwner->mActor->SendExecOp(RemoteWorkerFreezeOp());
+ break;
+ case eThaw:
+ Unused << aOwner->mActor->SendExecOp(RemoteWorkerThawOp());
+ break;
+ case ePortIdentifier:
+ Unused << aOwner->mActor->SendExecOp(
+ RemoteWorkerPortIdentifierOp(mPortIdentifier));
+ break;
+ case eAddWindowID:
+ Unused << aOwner->mActor->SendExecOp(
+ RemoteWorkerAddWindowIDOp(mWindowID));
+ break;
+ case eRemoveWindowID:
+ Unused << aOwner->mActor->SendExecOp(
+ RemoteWorkerRemoveWindowIDOp(mWindowID));
+ break;
+ default:
+ MOZ_CRASH("Unknown op.");
+ }
+
+ mCompleted = true;
+
+ return true;
+}
+
+void RemoteWorkerController::PendingSharedWorkerOp::Cancel() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mCompleted);
+
+ // We don't want to leak the port if the operation has not been processed.
+ if (mType == ePortIdentifier) {
+ MessagePortParent::ForceClose(mPortIdentifier.uuid(),
+ mPortIdentifier.destinationUuid(),
+ mPortIdentifier.sequenceId());
+ }
+
+ mCompleted = true;
+}
+
+RemoteWorkerController::PendingServiceWorkerOp::PendingServiceWorkerOp(
+ ServiceWorkerOpArgs&& aArgs,
+ RefPtr<ServiceWorkerOpPromise::Private> aPromise)
+ : mArgs(std::move(aArgs)), mPromise(std::move(aPromise)) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mPromise);
+}
+
+RemoteWorkerController::PendingServiceWorkerOp::~PendingServiceWorkerOp() {
+ AssertIsOnBackgroundThread();
+ MOZ_DIAGNOSTIC_ASSERT(!mPromise);
+}
+
+bool RemoteWorkerController::PendingServiceWorkerOp::MaybeStart(
+ RemoteWorkerController* const aOwner) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mPromise);
+ MOZ_ASSERT(aOwner);
+
+ if (NS_WARN_IF(aOwner->mState == RemoteWorkerController::eTerminated)) {
+ mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ mPromise = nullptr;
+ return true;
+ }
+
+ // The target content process must still be starting up.
+ if (!aOwner->mActor) {
+ // We can avoid starting the worker at all if we know it should be
+ // terminated.
+ MOZ_ASSERT(aOwner->mState == RemoteWorkerController::ePending);
+ if (mArgs.type() ==
+ ServiceWorkerOpArgs::TServiceWorkerTerminateWorkerOpArgs) {
+ aOwner->CancelAllPendingOps();
+ Cancel();
+
+ aOwner->mState = RemoteWorkerController::eTerminated;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Allow termination operations to pass through while pending because the
+ * remote Service Worker can be terminated while still starting up.
+ */
+ if (aOwner->mState == RemoteWorkerController::ePending &&
+ mArgs.type() !=
+ ServiceWorkerOpArgs::TServiceWorkerTerminateWorkerOpArgs) {
+ return false;
+ }
+
+ MaybeReportServiceWorkerShutdownProgress(mArgs);
+
+ aOwner->mActor->SendExecServiceWorkerOp(mArgs)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise = std::move(mPromise)](
+ PRemoteWorkerParent::ExecServiceWorkerOpPromise::
+ ResolveOrRejectValue&& aResult) {
+ if (NS_WARN_IF(aResult.IsReject())) {
+ promise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ return;
+ }
+
+ promise->Resolve(std::move(aResult.ResolveValue()), __func__);
+ });
+
+ return true;
+}
+
+void RemoteWorkerController::PendingServiceWorkerOp::Cancel() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mPromise);
+
+ mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ mPromise = nullptr;
+}
+
+RemoteWorkerController::PendingSWFetchEventOp::PendingSWFetchEventOp(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs,
+ RefPtr<ServiceWorkerFetchEventOpPromise::Private> aPromise,
+ RefPtr<FetchEventOpParent>&& aReal)
+ : mArgs(aArgs), mPromise(std::move(aPromise)), mReal(aReal) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mPromise);
+
+ // If there is a TParentToParentStream in the request body, we need to
+ // save it to our stream.
+ IPCInternalRequest& req = mArgs.common().internalRequest();
+ if (req.body().isSome() &&
+ req.body().ref().type() == BodyStreamVariant::TParentToParentStream) {
+ nsCOMPtr<nsIInputStream> stream;
+ auto streamLength = req.bodySize();
+ const auto& uuid = req.body().ref().get_ParentToParentStream().uuid();
+
+ auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr);
+ MOZ_DIAGNOSTIC_ASSERT(storage);
+ storage->GetStream(uuid, 0, streamLength, getter_AddRefs(mBodyStream));
+ storage->ForgetStream(uuid);
+
+ MOZ_DIAGNOSTIC_ASSERT(mBodyStream);
+
+ req.body() = Nothing();
+ }
+}
+
+RemoteWorkerController::PendingSWFetchEventOp::~PendingSWFetchEventOp() {
+ AssertIsOnBackgroundThread();
+ MOZ_DIAGNOSTIC_ASSERT(!mPromise);
+}
+
+bool RemoteWorkerController::PendingSWFetchEventOp::MaybeStart(
+ RemoteWorkerController* const aOwner) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mPromise);
+ MOZ_ASSERT(aOwner);
+
+ if (NS_WARN_IF(aOwner->mState == RemoteWorkerController::eTerminated)) {
+ mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ mPromise = nullptr;
+ // Because the worker has transitioned to terminated, this operation is moot
+ // and so we should return true because there's no need to queue it.
+ return true;
+ }
+
+ // The target content process must still be starting up.
+ if (!aOwner->mActor) {
+ MOZ_ASSERT(aOwner->mState == RemoteWorkerController::ePending);
+ return false;
+ }
+
+ // At this point we are handing off responsibility for the promise to the
+ // actor.
+ FetchEventOpProxyParent::Create(aOwner->mActor.get(), std::move(mPromise),
+ mArgs, std::move(mReal),
+ std::move(mBodyStream));
+
+ return true;
+}
+
+void RemoteWorkerController::PendingSWFetchEventOp::Cancel() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mPromise);
+
+ if (mPromise) {
+ mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ mPromise = nullptr;
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerController.h b/dom/workers/remoteworkers/RemoteWorkerController.h
new file mode 100644
index 0000000000..af53634abd
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerController.h
@@ -0,0 +1,323 @@
+/* -*- 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_RemoteWorkerController_h
+#define mozilla_dom_RemoteWorkerController_h
+
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/ServiceWorkerOpArgs.h"
+#include "mozilla/dom/ServiceWorkerOpPromise.h"
+
+namespace mozilla::dom {
+
+/* Here's a graph about this remote workers are spawned.
+ *
+ * _________________________________ | ________________________________
+ * | | | | |
+ * | Parent process | IPC | Creation of Process X |
+ * | PBackground thread | | | |
+ * | | | | [RemoteWorkerService::Init()] |
+ * | | | | | |
+ * | | | | | (1) |
+ * | [RemoteWorkerManager:: (2) | | | V |
+ * | RegisterActor()]<-------- [new RemoteWorkerServiceChild] |
+ * | | | | |
+ * | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | |________________________________|
+ * | | |
+ * | new SharedWorker/ServiceWorker | |
+ * | | ^ | IPC
+ * | (3) | (4)| |
+ * | V | | |
+ * | [RemoteWorkerController:: | |
+ * | | Create(data)] | |
+ * | | (5) | |
+ * | V | |
+ * | [RemoteWorkerManager::Launch()] | |
+ * | | | IPC _____________________________
+ * | | (6) | | | |
+ * | | | | Selected content process |
+ * | V | (7) | |
+ * | [SendPRemoteWorkerConstructor()]--------->[new RemoteWorkerChild()] |
+ * | | | | | | |
+ * | | (8) | | | | |
+ * | V | | | V |
+ * | [RemoteWorkerController-> | | | RemoteWorkerChild->Exec() |
+ * | | SetControllerActor()] | | |_____________________________|
+ * | (9) | | IPC
+ * | V | |
+ * | [RemoteWorkerObserver-> | |
+ * | CreationCompleted()] | |
+ * |_________________________________| |
+ * |
+ *
+ * 1. When a new process starts, it creates a RemoteWorkerService singleton.
+ * This service creates a new thread (Worker Launcher) and from there, it
+ * starts a PBackground RemoteWorkerServiceChild actor.
+ * 2. On the parent process, PBackground thread, RemoteWorkerServiceParent
+ * actors are registered into the RemoteWorkerManager service.
+ *
+ * 3. At some point, a SharedWorker or a ServiceWorker must be executed.
+ * RemoteWorkerController::Create() is used to start the launching. This
+ * method must be called on the parent process, on the PBackground thread.
+ * 4. RemoteWorkerController object is immediately returned to the caller. Any
+ * operation done with this controller object will be stored in a queue,
+ * until the launching is correctly executed.
+ * 5. RemoteWorkerManager has the list of active RemoteWorkerServiceParent
+ * actors. From them, it picks one.
+ * In case we don't have any content process to select, a new one is
+ * spawned. If this happens, the operation is suspended until a new
+ * RemoteWorkerServiceParent is registered.
+ * 6. RemoteWorkerServiceParent is used to create a RemoteWorkerParent.
+ * 7. RemoteWorkerChild is created on a selected process and it executes the
+ * WorkerPrivate.
+ * 8. The RemoteWorkerParent actor is passed to the RemoteWorkerController.
+ * 9. RemoteWorkerController now is ready to continue and it called
+ * RemoteWorkerObserver to inform that the operation is completed.
+ * In case there were pending operations, they are now executed.
+ */
+
+class ErrorValue;
+class FetchEventOpParent;
+class RemoteWorkerControllerParent;
+class RemoteWorkerData;
+class RemoteWorkerManager;
+class RemoteWorkerParent;
+
+class RemoteWorkerObserver {
+ public:
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ virtual void CreationFailed() = 0;
+
+ virtual void CreationSucceeded() = 0;
+
+ virtual void ErrorReceived(const ErrorValue& aValue) = 0;
+
+ virtual void LockNotified(bool aCreated) = 0;
+
+ virtual void WebTransportNotified(bool aCreated) = 0;
+
+ virtual void Terminated() = 0;
+};
+
+/**
+ * PBackground instance created by static RemoteWorkerController::Create that
+ * builds on RemoteWorkerManager. Interface to control the remote worker as well
+ * as receive events via the RemoteWorkerObserver interface that the owner
+ * (SharedWorkerManager in this case) must implement to hear about errors,
+ * termination, and whether the initial spawning succeeded/failed.
+ *
+ * Its methods may be called immediately after creation even though the worker
+ * is created asynchronously; an internal operation queue makes this work.
+ * Communicates with the remote worker via owned RemoteWorkerParent over
+ * PRemoteWorker protocol.
+ */
+class RemoteWorkerController final {
+ friend class RemoteWorkerControllerParent;
+ friend class RemoteWorkerManager;
+ friend class RemoteWorkerParent;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerController)
+
+ static already_AddRefed<RemoteWorkerController> Create(
+ const RemoteWorkerData& aData, RemoteWorkerObserver* aObserver,
+ base::ProcessId = 0);
+
+ void AddWindowID(uint64_t aWindowID);
+
+ void RemoveWindowID(uint64_t aWindowID);
+
+ void AddPortIdentifier(const MessagePortIdentifier& aPortIdentifier);
+
+ void Terminate();
+
+ void Suspend();
+
+ void Resume();
+
+ void Freeze();
+
+ void Thaw();
+
+ RefPtr<ServiceWorkerOpPromise> ExecServiceWorkerOp(
+ ServiceWorkerOpArgs&& aArgs);
+
+ RefPtr<ServiceWorkerFetchEventOpPromise> ExecServiceWorkerFetchEventOp(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs,
+ RefPtr<FetchEventOpParent> aReal);
+
+ RefPtr<GenericPromise> SetServiceWorkerSkipWaitingFlag() const;
+
+ bool IsTerminated() const;
+
+ void NotifyWebTransport(bool aCreated);
+
+ private:
+ RemoteWorkerController(const RemoteWorkerData& aData,
+ RemoteWorkerObserver* aObserver);
+
+ ~RemoteWorkerController();
+
+ void SetWorkerActor(RemoteWorkerParent* aActor);
+
+ void NoteDeadWorkerActor();
+
+ void ErrorPropagation(const ErrorValue& aValue);
+
+ void NotifyLock(bool aCreated);
+
+ void WorkerTerminated();
+
+ void Shutdown();
+
+ void CreationFailed();
+
+ void CreationSucceeded();
+
+ void CancelAllPendingOps();
+
+ template <typename... Args>
+ void MaybeStartSharedWorkerOp(Args&&... aArgs);
+
+ void NoteDeadWorker();
+
+ RefPtr<RemoteWorkerObserver> mObserver;
+ RefPtr<RemoteWorkerParent> mActor;
+
+ enum {
+ ePending,
+ eReady,
+ eTerminated,
+ } mState;
+
+ const bool mIsServiceWorker;
+
+ /**
+ * `PendingOp` is responsible for encapsulating logic for starting and
+ * canceling pending remote worker operations, as this logic may vary
+ * depending on the type of the remote worker and the type of the operation.
+ */
+ class PendingOp {
+ public:
+ PendingOp() = default;
+
+ PendingOp(const PendingOp&) = delete;
+
+ PendingOp& operator=(const PendingOp&) = delete;
+
+ virtual ~PendingOp() = default;
+
+ /**
+ * Returns `true` if execution has started or the operation is moot and
+ * doesn't need to be queued, `false` if execution hasn't started and the
+ * operation should be queued. In general, operations should only return
+ * false when a remote worker is first starting up. Operations may also
+ * somewhat non-intuitively return true without doing anything if the worker
+ * has already been told to shutdown.
+ *
+ * Starting execution may depend the state of `aOwner.`
+ */
+ virtual bool MaybeStart(RemoteWorkerController* const aOwner) = 0;
+
+ /**
+ * Invoked if the operation will never have MaybeStart() called again
+ * because the RemoteWorkerController has terminated (or will never start).
+ * This should be used by PendingOps to clean up any resources they own and
+ * may also be called internally by their MaybeStart() methods if they
+ * determine the worker has been terminated. This should be idempotent.
+ */
+ virtual void Cancel() = 0;
+ };
+
+ class PendingSharedWorkerOp final : public PendingOp {
+ public:
+ enum Type {
+ eTerminate,
+ eSuspend,
+ eResume,
+ eFreeze,
+ eThaw,
+ ePortIdentifier,
+ eAddWindowID,
+ eRemoveWindowID,
+ };
+
+ explicit PendingSharedWorkerOp(Type aType, uint64_t aWindowID = 0);
+
+ explicit PendingSharedWorkerOp(
+ const MessagePortIdentifier& aPortIdentifier);
+
+ ~PendingSharedWorkerOp();
+
+ bool MaybeStart(RemoteWorkerController* const aOwner) override;
+
+ void Cancel() override;
+
+ private:
+ const Type mType;
+ const MessagePortIdentifier mPortIdentifier;
+ const uint64_t mWindowID = 0;
+ bool mCompleted = false;
+ };
+
+ class PendingServiceWorkerOp final : public PendingOp {
+ public:
+ PendingServiceWorkerOp(ServiceWorkerOpArgs&& aArgs,
+ RefPtr<ServiceWorkerOpPromise::Private> aPromise);
+
+ ~PendingServiceWorkerOp();
+
+ bool MaybeStart(RemoteWorkerController* const aOwner) override;
+
+ void Cancel() override;
+
+ private:
+ ServiceWorkerOpArgs mArgs;
+ RefPtr<ServiceWorkerOpPromise::Private> mPromise;
+ };
+
+ /**
+ * Custom pending op type to deal with the complexities of FetchEvents having
+ * their own actor.
+ *
+ * FetchEvent Ops have their own actor type because their lifecycle is more
+ * complex than IPDL's async return value mechanism allows. Additionally,
+ * its IPC struct potentially has to serialize RemoteLazyStreams which
+ * requires us to hold an nsIInputStream when at rest and serialize it when
+ * eventually sending.
+ */
+ class PendingSWFetchEventOp final : public PendingOp {
+ public:
+ PendingSWFetchEventOp(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs,
+ RefPtr<ServiceWorkerFetchEventOpPromise::Private> aPromise,
+ RefPtr<FetchEventOpParent>&& aReal);
+
+ ~PendingSWFetchEventOp();
+
+ bool MaybeStart(RemoteWorkerController* const aOwner) override;
+
+ void Cancel() override;
+
+ private:
+ ParentToParentServiceWorkerFetchEventOpArgs mArgs;
+ RefPtr<ServiceWorkerFetchEventOpPromise::Private> mPromise;
+ RefPtr<FetchEventOpParent> mReal;
+ nsCOMPtr<nsIInputStream> mBodyStream;
+ };
+
+ nsTArray<UniquePtr<PendingOp>> mPendingOps;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_RemoteWorkerController_h
diff --git a/dom/workers/remoteworkers/RemoteWorkerControllerChild.cpp b/dom/workers/remoteworkers/RemoteWorkerControllerChild.cpp
new file mode 100644
index 0000000000..1b7f159fa5
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerControllerChild.cpp
@@ -0,0 +1,149 @@
+/* -*- 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 "RemoteWorkerControllerChild.h"
+
+#include <utility>
+
+#include "MainThreadUtils.h"
+#include "nsError.h"
+#include "nsThreadUtils.h"
+
+#include "ServiceWorkerPrivate.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/PFetchEventOpChild.h"
+
+namespace mozilla {
+
+using ipc::IPCResult;
+
+namespace dom {
+
+RemoteWorkerControllerChild::RemoteWorkerControllerChild(
+ RefPtr<RemoteWorkerObserver> aObserver)
+ : mObserver(std::move(aObserver)) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mObserver);
+}
+
+PFetchEventOpChild* RemoteWorkerControllerChild::AllocPFetchEventOpChild(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs) {
+ MOZ_CRASH("PFetchEventOpChild actors must be manually constructed!");
+ return nullptr;
+}
+
+bool RemoteWorkerControllerChild::DeallocPFetchEventOpChild(
+ PFetchEventOpChild* aActor) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aActor);
+
+ delete aActor;
+ return true;
+}
+
+void RemoteWorkerControllerChild::ActorDestroy(ActorDestroyReason aReason) {
+ AssertIsOnMainThread();
+
+ mIPCActive = false;
+
+ if (NS_WARN_IF(mObserver)) {
+ mObserver->ErrorReceived(NS_ERROR_DOM_ABORT_ERR);
+ }
+}
+
+IPCResult RemoteWorkerControllerChild::RecvCreationFailed() {
+ AssertIsOnMainThread();
+
+ if (mObserver) {
+ mObserver->CreationFailed();
+ }
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerControllerChild::RecvCreationSucceeded() {
+ AssertIsOnMainThread();
+
+ if (mObserver) {
+ mObserver->CreationSucceeded();
+ }
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerControllerChild::RecvErrorReceived(
+ const ErrorValue& aError) {
+ AssertIsOnMainThread();
+
+ if (mObserver) {
+ mObserver->ErrorReceived(aError);
+ }
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerControllerChild::RecvTerminated() {
+ AssertIsOnMainThread();
+
+ if (mObserver) {
+ mObserver->Terminated();
+ }
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerControllerChild::RecvSetServiceWorkerSkipWaitingFlag(
+ SetServiceWorkerSkipWaitingFlagResolver&& aResolve) {
+ AssertIsOnMainThread();
+
+ if (mObserver) {
+ static_cast<ServiceWorkerPrivate*>(mObserver.get())
+ ->SetSkipWaitingFlag()
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [resolve = std::move(aResolve)](
+ const GenericPromise::ResolveOrRejectValue& aResult) {
+ resolve(aResult.IsResolve() ? aResult.ResolveValue() : false);
+ });
+
+ return IPC_OK();
+ }
+
+ aResolve(false);
+
+ return IPC_OK();
+}
+
+void RemoteWorkerControllerChild::RevokeObserver(
+ RemoteWorkerObserver* aObserver) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aObserver);
+ MOZ_ASSERT(aObserver == mObserver);
+
+ mObserver = nullptr;
+}
+
+void RemoteWorkerControllerChild::MaybeSendDelete() {
+ AssertIsOnMainThread();
+
+ if (!mIPCActive) {
+ return;
+ }
+
+ RefPtr<RemoteWorkerControllerChild> self = this;
+
+ SendShutdown()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = std::move(self)](const ShutdownPromise::ResolveOrRejectValue&) {
+ if (self->mIPCActive) {
+ Unused << self->Send__delete__(self);
+ }
+ });
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerControllerChild.h b/dom/workers/remoteworkers/RemoteWorkerControllerChild.h
new file mode 100644
index 0000000000..049d67ad56
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerControllerChild.h
@@ -0,0 +1,64 @@
+/* -*- 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_remoteworkercontrollerchild_h__
+#define mozilla_dom_remoteworkercontrollerchild_h__
+
+#include "nsISupportsImpl.h"
+
+#include "RemoteWorkerController.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/PRemoteWorkerControllerChild.h"
+
+namespace mozilla::dom {
+
+/**
+ * Parent-process main-thread proxy used by ServiceWorkerManager to control
+ * RemoteWorkerController instances on the parent-process PBackground thread.
+ */
+class RemoteWorkerControllerChild final : public PRemoteWorkerControllerChild {
+ friend class PRemoteWorkerControllerChild;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerControllerChild)
+
+ explicit RemoteWorkerControllerChild(RefPtr<RemoteWorkerObserver> aObserver);
+
+ void Initialize();
+
+ void RevokeObserver(RemoteWorkerObserver* aObserver);
+
+ void MaybeSendDelete();
+
+ private:
+ ~RemoteWorkerControllerChild() = default;
+
+ PFetchEventOpChild* AllocPFetchEventOpChild(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs);
+
+ bool DeallocPFetchEventOpChild(PFetchEventOpChild* aActor);
+
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ mozilla::ipc::IPCResult RecvCreationFailed();
+
+ mozilla::ipc::IPCResult RecvCreationSucceeded();
+
+ mozilla::ipc::IPCResult RecvErrorReceived(const ErrorValue& aError);
+
+ mozilla::ipc::IPCResult RecvTerminated();
+
+ mozilla::ipc::IPCResult RecvSetServiceWorkerSkipWaitingFlag(
+ SetServiceWorkerSkipWaitingFlagResolver&& aResolve);
+
+ RefPtr<RemoteWorkerObserver> mObserver;
+
+ bool mIPCActive = true;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_remoteworkercontrollerchild_h__
diff --git a/dom/workers/remoteworkers/RemoteWorkerControllerParent.cpp b/dom/workers/remoteworkers/RemoteWorkerControllerParent.cpp
new file mode 100644
index 0000000000..7ac901f655
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerControllerParent.cpp
@@ -0,0 +1,215 @@
+/* -*- 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 "RemoteWorkerControllerParent.h"
+
+#include <utility>
+
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/FetchEventOpParent.h"
+#include "mozilla/dom/RemoteWorkerParent.h"
+#include "mozilla/dom/ServiceWorkerOpPromise.h"
+#include "mozilla/ipc/BackgroundParent.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+RemoteWorkerControllerParent::RemoteWorkerControllerParent(
+ const RemoteWorkerData& aRemoteWorkerData)
+ : mRemoteWorkerController(RemoteWorkerController::Create(
+ aRemoteWorkerData, this, 0 /* random process ID */)) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mRemoteWorkerController);
+}
+
+RefPtr<RemoteWorkerParent> RemoteWorkerControllerParent::GetRemoteWorkerParent()
+ const {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mRemoteWorkerController);
+
+ return mRemoteWorkerController->mActor;
+}
+
+void RemoteWorkerControllerParent::MaybeSendSetServiceWorkerSkipWaitingFlag(
+ std::function<void(bool)>&& aCallback) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aCallback);
+
+ if (!mIPCActive) {
+ aCallback(false);
+ return;
+ }
+
+ SendSetServiceWorkerSkipWaitingFlag()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [callback = std::move(aCallback)](
+ const SetServiceWorkerSkipWaitingFlagPromise::ResolveOrRejectValue&
+ aResult) {
+ callback(aResult.IsResolve() ? aResult.ResolveValue() : false);
+ });
+}
+
+RemoteWorkerControllerParent::~RemoteWorkerControllerParent() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mIPCActive);
+ MOZ_ASSERT(!mRemoteWorkerController);
+}
+
+PFetchEventOpParent* RemoteWorkerControllerParent::AllocPFetchEventOpParent(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs) {
+ AssertIsOnBackgroundThread();
+
+ RefPtr<FetchEventOpParent> actor = new FetchEventOpParent();
+ return actor.forget().take();
+}
+
+IPCResult RemoteWorkerControllerParent::RecvPFetchEventOpConstructor(
+ PFetchEventOpParent* aActor,
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ RefPtr<FetchEventOpParent> realFetchOp =
+ static_cast<FetchEventOpParent*>(aActor);
+ mRemoteWorkerController->ExecServiceWorkerFetchEventOp(aArgs, realFetchOp)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [fetchOp = std::move(realFetchOp)](
+ ServiceWorkerFetchEventOpPromise::ResolveOrRejectValue&&
+ aResult) {
+ if (NS_WARN_IF(aResult.IsReject())) {
+ MOZ_ASSERT(NS_FAILED(aResult.RejectValue()));
+ Unused << fetchOp->Send__delete__(fetchOp, aResult.RejectValue());
+ return;
+ }
+
+ Unused << fetchOp->Send__delete__(fetchOp, aResult.ResolveValue());
+ });
+
+ return IPC_OK();
+}
+
+bool RemoteWorkerControllerParent::DeallocPFetchEventOpParent(
+ PFetchEventOpParent* aActor) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ RefPtr<FetchEventOpParent> actor =
+ dont_AddRef(static_cast<FetchEventOpParent*>(aActor));
+ return true;
+}
+
+IPCResult RemoteWorkerControllerParent::RecvExecServiceWorkerOp(
+ ServiceWorkerOpArgs&& aArgs, ExecServiceWorkerOpResolver&& aResolve) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mIPCActive);
+ MOZ_ASSERT(mRemoteWorkerController);
+
+ mRemoteWorkerController->ExecServiceWorkerOp(std::move(aArgs))
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [resolve = std::move(aResolve)](
+ ServiceWorkerOpPromise::ResolveOrRejectValue&& aResult) {
+ if (NS_WARN_IF(aResult.IsReject())) {
+ MOZ_ASSERT(NS_FAILED(aResult.RejectValue()));
+ resolve(aResult.RejectValue());
+ return;
+ }
+
+ resolve(aResult.ResolveValue());
+ });
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerControllerParent::RecvShutdown(
+ ShutdownResolver&& aResolve) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mIPCActive);
+ MOZ_ASSERT(mRemoteWorkerController);
+
+ mIPCActive = false;
+
+ mRemoteWorkerController->Shutdown();
+ mRemoteWorkerController = nullptr;
+
+ aResolve(true);
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerControllerParent::Recv__delete__() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mIPCActive);
+ MOZ_ASSERT(!mRemoteWorkerController);
+
+ return IPC_OK();
+}
+
+void RemoteWorkerControllerParent::ActorDestroy(ActorDestroyReason aReason) {
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(mIPCActive)) {
+ mIPCActive = false;
+ }
+
+ if (NS_WARN_IF(mRemoteWorkerController)) {
+ mRemoteWorkerController->Shutdown();
+ mRemoteWorkerController = nullptr;
+ }
+}
+
+void RemoteWorkerControllerParent::CreationFailed() {
+ AssertIsOnBackgroundThread();
+
+ if (!mIPCActive) {
+ return;
+ }
+
+ Unused << SendCreationFailed();
+}
+
+void RemoteWorkerControllerParent::CreationSucceeded() {
+ AssertIsOnBackgroundThread();
+
+ if (!mIPCActive) {
+ return;
+ }
+
+ Unused << SendCreationSucceeded();
+}
+
+void RemoteWorkerControllerParent::ErrorReceived(const ErrorValue& aValue) {
+ AssertIsOnBackgroundThread();
+
+ if (!mIPCActive) {
+ return;
+ }
+
+ Unused << SendErrorReceived(aValue);
+}
+
+void RemoteWorkerControllerParent::Terminated() {
+ AssertIsOnBackgroundThread();
+
+ if (!mIPCActive) {
+ return;
+ }
+
+ Unused << SendTerminated();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerControllerParent.h b/dom/workers/remoteworkers/RemoteWorkerControllerParent.h
new file mode 100644
index 0000000000..618fbf9506
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerControllerParent.h
@@ -0,0 +1,86 @@
+/* -*- 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_remoteworkercontrollerparent_h__
+#define mozilla_dom_remoteworkercontrollerparent_h__
+
+#include <functional>
+
+#include "nsISupportsImpl.h"
+
+#include "RemoteWorkerController.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/PRemoteWorkerControllerParent.h"
+
+namespace mozilla::dom {
+
+/**
+ * PBackground-resident proxy used by ServiceWorkerManager because canonical
+ * ServiceWorkerManager state exists on the parent process main thread but the
+ * RemoteWorkerController API is used from the parent process PBackground
+ * thread.
+ */
+class RemoteWorkerControllerParent final : public PRemoteWorkerControllerParent,
+ public RemoteWorkerObserver {
+ friend class PRemoteWorkerControllerParent;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerControllerParent, override)
+
+ explicit RemoteWorkerControllerParent(
+ const RemoteWorkerData& aRemoteWorkerData);
+
+ // Returns the corresponding RemoteWorkerParent (if any).
+ RefPtr<RemoteWorkerParent> GetRemoteWorkerParent() const;
+
+ void MaybeSendSetServiceWorkerSkipWaitingFlag(
+ std::function<void(bool)>&& aCallback);
+
+ private:
+ ~RemoteWorkerControllerParent();
+
+ PFetchEventOpParent* AllocPFetchEventOpParent(
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs);
+
+ mozilla::ipc::IPCResult RecvPFetchEventOpConstructor(
+ PFetchEventOpParent* aActor,
+ const ParentToParentServiceWorkerFetchEventOpArgs& aArgs) override;
+
+ bool DeallocPFetchEventOpParent(PFetchEventOpParent* aActor);
+
+ mozilla::ipc::IPCResult RecvExecServiceWorkerOp(
+ ServiceWorkerOpArgs&& aArgs, ExecServiceWorkerOpResolver&& aResolve);
+
+ mozilla::ipc::IPCResult RecvShutdown(ShutdownResolver&& aResolve);
+
+ mozilla::ipc::IPCResult Recv__delete__() override;
+
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ void CreationFailed() override;
+
+ void CreationSucceeded() override;
+
+ void ErrorReceived(const ErrorValue& aValue) override;
+
+ void LockNotified(bool aCreated) final {
+ // no-op for service workers
+ }
+
+ void WebTransportNotified(bool aCreated) final {
+ // no-op for service workers
+ }
+
+ void Terminated() override;
+
+ RefPtr<RemoteWorkerController> mRemoteWorkerController;
+
+ bool mIPCActive = true;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_remoteworkercontrollerparent_h__
diff --git a/dom/workers/remoteworkers/RemoteWorkerManager.cpp b/dom/workers/remoteworkers/RemoteWorkerManager.cpp
new file mode 100644
index 0000000000..9d9189b38f
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerManager.cpp
@@ -0,0 +1,735 @@
+/* -*- 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 "RemoteWorkerManager.h"
+
+#include <utility>
+
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/dom/ContentChild.h" // ContentChild::GetSingleton
+#include "mozilla/dom/RemoteWorkerController.h"
+#include "mozilla/dom/RemoteWorkerParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundParent.h"
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+# include "mozilla/dom/DOMException.h"
+# include "mozilla/CycleCollectedJSContext.h"
+# include "mozilla/Sprintf.h" // SprintfLiteral
+# include "nsIXPConnect.h" // nsIXPConnectWrappedJS
+#endif
+#include "mozilla/StaticPrefs_extensions.h"
+#include "nsCOMPtr.h"
+#include "nsIE10SUtils.h"
+#include "nsImportModule.h"
+#include "nsIXULRuntime.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "RemoteWorkerServiceParent.h"
+
+mozilla::LazyLogModule gRemoteWorkerManagerLog("RemoteWorkerManager");
+
+#ifdef LOG
+# undef LOG
+#endif
+#define LOG(fmt) \
+ MOZ_LOG(gRemoteWorkerManagerLog, mozilla::LogLevel::Verbose, fmt)
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+namespace {
+
+// Raw pointer because this object is kept alive by RemoteWorkerServiceParent
+// actors.
+RemoteWorkerManager* sRemoteWorkerManager;
+
+bool IsServiceWorker(const RemoteWorkerData& aData) {
+ return aData.serviceWorkerData().type() ==
+ OptionalServiceWorkerData::TServiceWorkerData;
+}
+
+void TransmitPermissionsAndBlobURLsForPrincipalInfo(
+ ContentParent* aContentParent, const PrincipalInfo& aPrincipalInfo) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aContentParent);
+
+ auto principalOrErr = PrincipalInfoToPrincipal(aPrincipalInfo);
+
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+
+ aContentParent->TransmitBlobURLsForPrincipal(principal);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ aContentParent->TransmitPermissionsForPrincipal(principal));
+}
+
+} // namespace
+
+// static
+bool RemoteWorkerManager::MatchRemoteType(const nsACString& processRemoteType,
+ const nsACString& workerRemoteType) {
+ LOG(("MatchRemoteType [processRemoteType=%s, workerRemoteType=%s]",
+ PromiseFlatCString(processRemoteType).get(),
+ PromiseFlatCString(workerRemoteType).get()));
+
+ // Respecting COOP and COEP requires processing headers in the parent
+ // process in order to choose an appropriate content process, but the
+ // workers' ScriptLoader processes headers in content processes. An
+ // intermediary step that provides security guarantees is to simply never
+ // allow SharedWorkers and ServiceWorkers to exist in a COOP+COEP process.
+ // The ultimate goal is to allow these worker types to be put in such
+ // processes based on their script response headers.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1595206
+ //
+ // RemoteWorkerManager::GetRemoteType should not select this remoteType
+ // and so workerRemoteType is not expected to be set to a coop+coep
+ // remoteType and here we can just assert that it is not happening.
+ MOZ_ASSERT(!IsWebCoopCoepRemoteType(workerRemoteType));
+
+ return processRemoteType.Equals(workerRemoteType);
+}
+
+// static
+Result<nsCString, nsresult> RemoteWorkerManager::GetRemoteType(
+ const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerKind aWorkerKind) {
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT_IF(aWorkerKind == WorkerKind::WorkerKindService,
+ aPrincipal->GetIsContentPrincipal());
+
+ nsCOMPtr<nsIE10SUtils> e10sUtils = do_ImportESModule(
+ "resource://gre/modules/E10SUtils.sys.mjs", "E10SUtils", fallible);
+ if (NS_WARN_IF(!e10sUtils)) {
+ LOG(("GetRemoteType Abort: could not import E10SUtils"));
+ return Err(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ nsCString preferredRemoteType = DEFAULT_REMOTE_TYPE;
+ if (aWorkerKind == WorkerKind::WorkerKindShared) {
+ if (auto* contentChild = ContentChild::GetSingleton()) {
+ // For a shared worker set the preferred remote type to the content
+ // child process remote type.
+ preferredRemoteType = contentChild->GetRemoteType();
+ } else if (aPrincipal->IsSystemPrincipal()) {
+ preferredRemoteType = NOT_REMOTE_TYPE;
+ }
+ }
+
+ nsIE10SUtils::RemoteWorkerType workerType;
+
+ switch (aWorkerKind) {
+ case WorkerKind::WorkerKindService:
+ workerType = nsIE10SUtils::REMOTE_WORKER_TYPE_SERVICE;
+ break;
+ case WorkerKind::WorkerKindShared:
+ workerType = nsIE10SUtils::REMOTE_WORKER_TYPE_SHARED;
+ break;
+ default:
+ // This method isn't expected to be called for worker types that
+ // aren't remote workers (currently Service and Shared workers).
+ LOG(("GetRemoteType Error on unexpected worker type"));
+ MOZ_DIAGNOSTIC_ASSERT(false, "Unexpected worker type");
+ return Err(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ // Here we do not have access to the window and so we can't use its
+ // useRemoteTabs and useRemoteSubframes flags (for the service
+ // worker there may not even be a window associated to the worker
+ // yet), and so we have to use the prefs instead.
+ bool isMultiprocess = BrowserTabsRemoteAutostart();
+ bool isFission = FissionAutostart();
+
+ nsCString remoteType = NOT_REMOTE_TYPE;
+
+ nsresult rv = e10sUtils->GetRemoteTypeForWorkerPrincipal(
+ aPrincipal, workerType, isMultiprocess, isFission, preferredRemoteType,
+ remoteType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(
+ ("GetRemoteType Abort: E10SUtils.getRemoteTypeForWorkerPrincipal "
+ "exception"));
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ nsCString principalTypeOrScheme;
+ if (aPrincipal->IsSystemPrincipal()) {
+ principalTypeOrScheme = "system"_ns;
+ } else if (aPrincipal->GetIsExpandedPrincipal()) {
+ principalTypeOrScheme = "expanded"_ns;
+ } else if (aPrincipal->GetIsNullPrincipal()) {
+ principalTypeOrScheme = "null"_ns;
+ } else {
+ nsCOMPtr<nsIURI> uri = aPrincipal->GetURI();
+ nsresult rv2 = uri->GetScheme(principalTypeOrScheme);
+ if (NS_FAILED(rv2)) {
+ principalTypeOrScheme = "content"_ns;
+ }
+ }
+
+ nsCString processRemoteType = "parent"_ns;
+ if (auto* contentChild = ContentChild::GetSingleton()) {
+ // RemoteTypePrefix make sure that we are not going to include
+ // the full origin that may be part of the current remote type.
+ processRemoteType = RemoteTypePrefix(contentChild->GetRemoteType());
+ }
+
+ // Convert the error code into an error name.
+ nsAutoCString errorName;
+ GetErrorName(rv, errorName);
+
+ // Try to retrieve the line number from the exception.
+ nsAutoCString errorFilename("(unknown)"_ns);
+ uint32_t jsmErrorLineNumber = 0;
+
+ if (auto* context = CycleCollectedJSContext::Get()) {
+ if (RefPtr<Exception> exn = context->GetPendingException()) {
+ nsAutoString filename(u"(unknown)"_ns);
+
+ if (rv == NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS) {
+ // When the failure is a Javascript Error, the line number retrieved
+ // from the Exception instance isn't going to be the E10SUtils.sys.mjs
+ // line that originated the failure, and so we fallback to retrieve it
+ // from the nsIScriptError.
+ nsCOMPtr<nsIScriptError> scriptError =
+ do_QueryInterface(exn->GetData());
+ if (scriptError) {
+ scriptError->GetLineNumber(&jsmErrorLineNumber);
+ scriptError->GetSourceName(filename);
+ }
+ } else {
+ nsCOMPtr<nsIXPConnectWrappedJS> wrapped =
+ do_QueryInterface(e10sUtils);
+ dom::AutoJSAPI jsapi;
+ if (jsapi.Init(wrapped->GetJSObjectGlobal())) {
+ auto* cx = jsapi.cx();
+ jsmErrorLineNumber = exn->LineNumber(cx);
+ exn->GetFilename(cx, filename);
+ }
+ }
+
+ errorFilename = NS_ConvertUTF16toUTF8(filename);
+ }
+ }
+
+ char buf[1024];
+ SprintfLiteral(
+ buf,
+ "workerType=%s, principal=%s, preferredRemoteType=%s, "
+ "processRemoteType=%s, errorName=%s, errorLocation=%s:%d",
+ aWorkerKind == WorkerKind::WorkerKindService ? "service" : "shared",
+ principalTypeOrScheme.get(),
+ PromiseFlatCString(RemoteTypePrefix(preferredRemoteType)).get(),
+ processRemoteType.get(), errorName.get(), errorFilename.get(),
+ jsmErrorLineNumber);
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "E10SUtils.getRemoteTypeForWorkerPrincipal did throw: %s", buf);
+#endif
+ return Err(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ if (MOZ_LOG_TEST(gRemoteWorkerManagerLog, LogLevel::Verbose)) {
+ nsCString principalOrigin;
+ aPrincipal->GetOrigin(principalOrigin);
+
+ LOG(
+ ("GetRemoteType workerType=%s, principal=%s, "
+ "preferredRemoteType=%s, selectedRemoteType=%s",
+ aWorkerKind == WorkerKind::WorkerKindService ? "service" : "shared",
+ principalOrigin.get(), preferredRemoteType.get(), remoteType.get()));
+ }
+
+ return remoteType;
+}
+
+// static
+bool RemoteWorkerManager::HasExtensionPrincipal(const RemoteWorkerData& aData) {
+ auto principalInfo = aData.principalInfo();
+ return principalInfo.type() == PrincipalInfo::TContentPrincipalInfo &&
+ // This helper method is also called from the background thread and so
+ // we can't check if the principal does have an addonPolicy object
+ // associated and we have to resort to check the url scheme instead.
+ StringBeginsWith(principalInfo.get_ContentPrincipalInfo().spec(),
+ "moz-extension://"_ns);
+}
+
+// static
+bool RemoteWorkerManager::IsRemoteTypeAllowed(const RemoteWorkerData& aData) {
+ AssertIsOnMainThread();
+
+ // If Gecko is running in single process mode, there is no child process
+ // to select and we have to just consider it valid (if it should haven't
+ // been launched it should have been already prevented before reaching
+ // a RemoteWorkerChild instance).
+ if (!BrowserTabsRemoteAutostart()) {
+ return true;
+ }
+
+ const auto& principalInfo = aData.principalInfo();
+
+ auto* contentChild = ContentChild::GetSingleton();
+ if (!contentChild) {
+ // If e10s isn't disabled, only workers related to the system principal
+ // should be allowed to run in the parent process, and extension principals
+ // if extensions.webextensions.remote is false.
+ return principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo ||
+ (!StaticPrefs::extensions_webextensions_remote() &&
+ aData.remoteType().Equals(NOT_REMOTE_TYPE) &&
+ HasExtensionPrincipal(aData));
+ }
+
+ auto principalOrErr = PrincipalInfoToPrincipal(principalInfo);
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ return false;
+ }
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+
+ // Recompute the remoteType based on the principal, to double-check that it
+ // has not been tempered to select a different child process than the one
+ // expected.
+ bool isServiceWorker = aData.serviceWorkerData().type() ==
+ OptionalServiceWorkerData::TServiceWorkerData;
+ auto remoteType = GetRemoteType(
+ principal, isServiceWorker ? WorkerKindService : WorkerKindShared);
+ if (NS_WARN_IF(remoteType.isErr())) {
+ LOG(("IsRemoteTypeAllowed: Error to retrieve remote type"));
+ return false;
+ }
+
+ return MatchRemoteType(remoteType.unwrap(), contentChild->GetRemoteType());
+}
+
+/* static */
+already_AddRefed<RemoteWorkerManager> RemoteWorkerManager::GetOrCreate() {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ if (!sRemoteWorkerManager) {
+ sRemoteWorkerManager = new RemoteWorkerManager();
+ }
+
+ RefPtr<RemoteWorkerManager> rwm = sRemoteWorkerManager;
+ return rwm.forget();
+}
+
+RemoteWorkerManager::RemoteWorkerManager() : mParentActor(nullptr) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!sRemoteWorkerManager);
+}
+
+RemoteWorkerManager::~RemoteWorkerManager() {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(sRemoteWorkerManager == this);
+ sRemoteWorkerManager = nullptr;
+}
+
+void RemoteWorkerManager::RegisterActor(RemoteWorkerServiceParent* aActor) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ if (!BackgroundParent::IsOtherProcessActor(aActor->Manager())) {
+ MOZ_ASSERT(!mParentActor);
+ mParentActor = aActor;
+ MOZ_ASSERT(mPendings.IsEmpty());
+ return;
+ }
+
+ MOZ_ASSERT(!mChildActors.Contains(aActor));
+ mChildActors.AppendElement(aActor);
+
+ if (!mPendings.IsEmpty()) {
+ const auto& processRemoteType = aActor->GetRemoteType();
+ nsTArray<Pending> unlaunched;
+
+ // Flush pending launching.
+ for (Pending& p : mPendings) {
+ if (p.mController->IsTerminated()) {
+ continue;
+ }
+
+ const auto& workerRemoteType = p.mData.remoteType();
+
+ if (MatchRemoteType(processRemoteType, workerRemoteType)) {
+ LOG(("RegisterActor - Launch Pending, workerRemoteType=%s",
+ workerRemoteType.get()));
+ LaunchInternal(p.mController, aActor, p.mData);
+ } else {
+ unlaunched.AppendElement(std::move(p));
+ continue;
+ }
+ }
+
+ std::swap(mPendings, unlaunched);
+
+ // AddRef is called when the first Pending object is added to mPendings, so
+ // the balancing Release is called when the last Pending object is removed.
+ // RemoteWorkerServiceParents will hold strong references to
+ // RemoteWorkerManager.
+ if (mPendings.IsEmpty()) {
+ Release();
+ }
+
+ LOG(("RegisterActor - mPendings length: %zu", mPendings.Length()));
+ }
+}
+
+void RemoteWorkerManager::UnregisterActor(RemoteWorkerServiceParent* aActor) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ if (aActor == mParentActor) {
+ mParentActor = nullptr;
+ } else {
+ MOZ_ASSERT(mChildActors.Contains(aActor));
+ mChildActors.RemoveElement(aActor);
+ }
+}
+
+void RemoteWorkerManager::Launch(RemoteWorkerController* aController,
+ const RemoteWorkerData& aData,
+ base::ProcessId aProcessId) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ RemoteWorkerServiceParent* targetActor = SelectTargetActor(aData, aProcessId);
+
+ // If there is not an available actor, let's store the data, and let's spawn a
+ // new process.
+ if (!targetActor) {
+ // If this is the first time we have a pending launching, we must keep alive
+ // the manager.
+ if (mPendings.IsEmpty()) {
+ AddRef();
+ }
+
+ Pending* pending = mPendings.AppendElement();
+ pending->mController = aController;
+ pending->mData = aData;
+
+ // Launching is async, so we cannot check for failures right here.
+ LaunchNewContentProcess(aData);
+ return;
+ }
+
+ /**
+ * If a target actor for the remote worker has been selected, the actor has
+ * already been registered with the corresponding `ContentParent` and we
+ * should not increment the `mRemoteWorkerActorData`'s `mCount` again (see
+ * `SelectTargetActorForServiceWorker()` /
+ * `SelectTargetActorForSharedWorker()`).
+ */
+ LaunchInternal(aController, targetActor, aData, true);
+}
+
+void RemoteWorkerManager::LaunchInternal(
+ RemoteWorkerController* aController,
+ RemoteWorkerServiceParent* aTargetActor, const RemoteWorkerData& aData,
+ bool aRemoteWorkerAlreadyRegistered) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aController);
+ MOZ_ASSERT(aTargetActor);
+ MOZ_ASSERT(aTargetActor == mParentActor ||
+ mChildActors.Contains(aTargetActor));
+
+ // We need to send permissions to content processes, but not if we're spawning
+ // the worker here in the parent process.
+ if (aTargetActor != mParentActor) {
+ RefPtr<ThreadsafeContentParentHandle> contentHandle =
+ BackgroundParent::GetContentParentHandle(aTargetActor->Manager());
+
+ // This won't cause any race conditions because the content process
+ // should wait for the permissions to be received before executing the
+ // Service Worker.
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [contentHandle = std::move(contentHandle),
+ principalInfo = aData.principalInfo()] {
+ AssertIsOnMainThread();
+ if (RefPtr<ContentParent> contentParent =
+ contentHandle->GetContentParent()) {
+ TransmitPermissionsAndBlobURLsForPrincipalInfo(contentParent,
+ principalInfo);
+ }
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+ }
+
+ RefPtr<RemoteWorkerParent> workerActor = MakeAndAddRef<RemoteWorkerParent>();
+ if (!aTargetActor->Manager()->SendPRemoteWorkerConstructor(workerActor,
+ aData)) {
+ AsyncCreationFailed(aController);
+ return;
+ }
+
+ workerActor->Initialize(aRemoteWorkerAlreadyRegistered);
+
+ // This makes the link better the 2 actors.
+ aController->SetWorkerActor(workerActor);
+ workerActor->SetController(aController);
+}
+
+void RemoteWorkerManager::AsyncCreationFailed(
+ RemoteWorkerController* aController) {
+ RefPtr<RemoteWorkerController> controller = aController;
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction("RemoteWorkerManager::AsyncCreationFailed",
+ [controller]() { controller->CreationFailed(); });
+
+ NS_DispatchToCurrentThread(r.forget());
+}
+
+template <typename Callback>
+void RemoteWorkerManager::ForEachActor(
+ Callback&& aCallback, const nsACString& aRemoteType,
+ Maybe<base::ProcessId> aProcessId) const {
+ AssertIsOnBackgroundThread();
+
+ const auto length = mChildActors.Length();
+
+ auto end = static_cast<uint32_t>(rand()) % length;
+ if (aProcessId) {
+ // Start from the actor with the given processId instead of starting from
+ // a random index.
+ for (auto j = length - 1; j > 0; j--) {
+ if (mChildActors[j]->OtherPid() == *aProcessId) {
+ end = j;
+ break;
+ }
+ }
+ }
+
+ uint32_t i = end;
+
+ do {
+ MOZ_ASSERT(i < mChildActors.Length());
+ RemoteWorkerServiceParent* actor = mChildActors[i];
+
+ if (MatchRemoteType(actor->GetRemoteType(), aRemoteType)) {
+ ThreadsafeContentParentHandle* contentHandle =
+ BackgroundParent::GetContentParentHandle(actor->Manager());
+
+ if (!aCallback(actor, contentHandle)) {
+ break;
+ }
+ }
+
+ i = (i + 1) % length;
+ } while (i != end);
+}
+
+/**
+ * When selecting a target actor for a given remote worker, we have to consider
+ * that:
+ *
+ * - Service Workers can spawn even when their registering page/script isn't
+ * active (e.g. push notifications), so we don't attempt to spawn the worker
+ * in its registering script's process. We search linearly and choose the
+ * search's starting position randomly.
+ *
+ * - When Fission is enabled, Shared Workers may have to be spawned into
+ * different child process from the one where it has been registered from, and
+ * that child process may be going to be marked as dead and shutdown.
+ *
+ * Spawning the workers in a random process makes the process selection criteria
+ * a little tricky, as a candidate process may imminently shutdown due to a
+ * remove worker actor unregistering
+ * (see `ContentParent::UnregisterRemoveWorkerActor`).
+ *
+ * In `ContentParent::MaybeAsyncSendShutDownMessage` we only dispatch a runnable
+ * to call `ContentParent::ShutDownProcess` if there are no registered remote
+ * worker actors, and we ensure that the check for the number of registered
+ * actors and the dispatching of the runnable are atomic. That happens on the
+ * main thread, so here on the background thread, while
+ * `ContentParent::mRemoteWorkerActorData` is locked, if `mCount` > 0, we can
+ * register a remote worker actor "early" and guarantee that the corresponding
+ * content process will not shutdown.
+ */
+RemoteWorkerServiceParent* RemoteWorkerManager::SelectTargetActorInternal(
+ const RemoteWorkerData& aData, base::ProcessId aProcessId) const {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mChildActors.IsEmpty());
+
+ RemoteWorkerServiceParent* actor = nullptr;
+
+ const auto& workerRemoteType = aData.remoteType();
+
+ ForEachActor(
+ [&](RemoteWorkerServiceParent* aActor,
+ ThreadsafeContentParentHandle* aContentHandle) {
+ // Make sure to choose an actor related to a child process that is not
+ // going to shutdown while we are still in the process of launching the
+ // remote worker.
+ //
+ // ForEachActor will start from the child actor coming from the child
+ // process with a pid equal to aProcessId if any, otherwise it would
+ // start from a random actor in the mChildActors array, this guarantees
+ // that we will choose that actor if it does also match the remote type.
+ if (aContentHandle->MaybeRegisterRemoteWorkerActor(
+ [&](uint32_t count, bool shutdownStarted) -> bool {
+ return (count || !shutdownStarted) &&
+ (aActor->OtherPid() == aProcessId || !actor);
+ })) {
+ actor = aActor;
+ return false;
+ }
+ MOZ_ASSERT(!actor);
+ return true;
+ },
+ workerRemoteType, IsServiceWorker(aData) ? Nothing() : Some(aProcessId));
+
+ return actor;
+}
+
+RemoteWorkerServiceParent* RemoteWorkerManager::SelectTargetActor(
+ const RemoteWorkerData& aData, base::ProcessId aProcessId) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ // System principal workers should run on the parent process.
+ if (aData.principalInfo().type() == PrincipalInfo::TSystemPrincipalInfo) {
+ MOZ_ASSERT(mParentActor);
+ return mParentActor;
+ }
+
+ // Extension principal workers are allowed to run on the parent process
+ // when "extension.webextensions.remote" pref is false.
+ if (aProcessId == base::GetCurrentProcId() &&
+ aData.remoteType().Equals(NOT_REMOTE_TYPE) &&
+ !StaticPrefs::extensions_webextensions_remote() &&
+ HasExtensionPrincipal(aData)) {
+ MOZ_ASSERT(mParentActor);
+ return mParentActor;
+ }
+
+ // If e10s is off, use the parent process.
+ if (!BrowserTabsRemoteAutostart()) {
+ MOZ_ASSERT(mParentActor);
+ return mParentActor;
+ }
+
+ // We shouldn't have to worry about content-principal parent-process workers.
+ MOZ_ASSERT(aProcessId != base::GetCurrentProcId());
+
+ if (mChildActors.IsEmpty()) {
+ return nullptr;
+ }
+
+ return SelectTargetActorInternal(aData, aProcessId);
+}
+
+void RemoteWorkerManager::LaunchNewContentProcess(
+ const RemoteWorkerData& aData) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ nsCOMPtr<nsISerialEventTarget> bgEventTarget = GetCurrentSerialEventTarget();
+
+ using LaunchPromiseType = ContentParent::LaunchPromise;
+ using CallbackParamType = LaunchPromiseType::ResolveOrRejectValue;
+
+ // A new content process must be requested on the main thread. On success,
+ // the success callback will also run on the main thread. On failure, however,
+ // the failure callback must be run on the background thread - it uses
+ // RemoteWorkerManager, and RemoteWorkerManager isn't threadsafe, so the
+ // promise callback will just dispatch the "real" failure callback to the
+ // background thread.
+ auto processLaunchCallback = [principalInfo = aData.principalInfo(),
+ bgEventTarget = std::move(bgEventTarget),
+ self = RefPtr<RemoteWorkerManager>(this)](
+ const CallbackParamType& aValue,
+ const nsCString& remoteType) mutable {
+ if (aValue.IsResolve()) {
+ LOG(("LaunchNewContentProcess: successfully got child process"));
+
+ // The failure callback won't run, and we're on the main thread, so
+ // we need to properly release the thread-unsafe RemoteWorkerManager.
+ NS_ProxyRelease(__func__, bgEventTarget, self.forget());
+ } else {
+ // The "real" failure callback.
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [self = std::move(self), remoteType] {
+ nsTArray<Pending> uncancelled;
+ auto pendings = std::move(self->mPendings);
+
+ for (const auto& pending : pendings) {
+ const auto& workerRemoteType = pending.mData.remoteType();
+ if (self->MatchRemoteType(remoteType, workerRemoteType)) {
+ LOG(
+ ("LaunchNewContentProcess: Cancel pending with "
+ "workerRemoteType=%s",
+ workerRemoteType.get()));
+ pending.mController->CreationFailed();
+ } else {
+ uncancelled.AppendElement(pending);
+ }
+ }
+
+ std::swap(self->mPendings, uncancelled);
+ });
+
+ bgEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+ }
+ };
+
+ LOG(("LaunchNewContentProcess: remoteType=%s", aData.remoteType().get()));
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [callback = std::move(processLaunchCallback),
+ workerRemoteType = aData.remoteType()]() mutable {
+ auto remoteType =
+ workerRemoteType.IsEmpty() ? DEFAULT_REMOTE_TYPE : workerRemoteType;
+
+ RefPtr<LaunchPromiseType> onFinished;
+ if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ // Request a process making sure to specify aPreferUsed=true. For a
+ // given remoteType there's a pool size limit. If we pass aPreferUsed
+ // here, then if there's any process in the pool already, we will use
+ // that. If we pass false (which is the default if omitted), then
+ // this call will spawn a new process if the pool isn't at its limit
+ // yet.
+ //
+ // (Our intent is never to grow the pool size here. Our logic gets
+ // here because our current logic on PBackground is only aware of
+ // RemoteWorkerServiceParent actors that have registered themselves,
+ // which is fundamentally unaware of processes that will match in the
+ // future when they register. So we absolutely are fine with and want
+ // any existing processes.)
+ onFinished = ContentParent::GetNewOrUsedBrowserProcessAsync(
+ /* aRemoteType = */ remoteType,
+ /* aGroup */ nullptr,
+ hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND,
+ /* aPreferUsed */ true);
+ } else {
+ // We can find this event still in flight after having been asked to
+ // shutdown. Let's fake a failure to ensure our callback is called
+ // such that we clean up everything properly.
+ onFinished = LaunchPromiseType::CreateAndReject(
+ NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ }
+ onFinished->Then(GetCurrentSerialEventTarget(), __func__,
+ [callback = std::move(callback),
+ remoteType](const CallbackParamType& aValue) mutable {
+ callback(aValue, remoteType);
+ });
+ });
+
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerManager.h b/dom/workers/remoteworkers/RemoteWorkerManager.h
new file mode 100644
index 0000000000..5ff11ee6e6
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerManager.h
@@ -0,0 +1,118 @@
+/* -*- 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_RemoteWorkerManager_h
+#define mozilla_dom_RemoteWorkerManager_h
+
+#include "base/process.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/RemoteWorkerTypes.h"
+#include "mozilla/dom/WorkerPrivate.h" // WorkerKind enum
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+
+class RemoteWorkerController;
+class RemoteWorkerServiceParent;
+
+/**
+ * PBackground instance that keeps tracks of RemoteWorkerServiceParent actors
+ * (1 per process, including the main process) and pending
+ * RemoteWorkerController requests to spawn remote workers if the spawn request
+ * can't be immediately fulfilled. Decides which RemoteWorkerServerParent to use
+ * internally via SelectTargetActor in order to select a BackgroundParent
+ * manager on which to create a RemoteWorkerParent.
+ */
+class RemoteWorkerManager final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerManager)
+
+ static already_AddRefed<RemoteWorkerManager> GetOrCreate();
+
+ void RegisterActor(RemoteWorkerServiceParent* aActor);
+
+ void UnregisterActor(RemoteWorkerServiceParent* aActor);
+
+ void Launch(RemoteWorkerController* aController,
+ const RemoteWorkerData& aData, base::ProcessId aProcessId);
+
+ static bool MatchRemoteType(const nsACString& processRemoteType,
+ const nsACString& workerRemoteType);
+
+ /**
+ * Get the child process RemoteType where a RemoteWorker should be
+ * launched.
+ */
+ static Result<nsCString, nsresult> GetRemoteType(
+ const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerKind aWorkerKind);
+
+ /**
+ * Verify if a remote worker should be allowed to run in the current
+ * child process remoteType.
+ */
+ static bool IsRemoteTypeAllowed(const RemoteWorkerData& aData);
+
+ static bool HasExtensionPrincipal(const RemoteWorkerData& aData);
+
+ private:
+ RemoteWorkerManager();
+ ~RemoteWorkerManager();
+
+ RemoteWorkerServiceParent* SelectTargetActor(const RemoteWorkerData& aData,
+ base::ProcessId aProcessId);
+
+ RemoteWorkerServiceParent* SelectTargetActorInternal(
+ const RemoteWorkerData& aData, base::ProcessId aProcessId) const;
+
+ void LaunchInternal(RemoteWorkerController* aController,
+ RemoteWorkerServiceParent* aTargetActor,
+ const RemoteWorkerData& aData,
+ bool aRemoteWorkerAlreadyRegistered = false);
+
+ void LaunchNewContentProcess(const RemoteWorkerData& aData);
+
+ void AsyncCreationFailed(RemoteWorkerController* aController);
+
+ // Iterate through all RemoteWorkerServiceParent actors with the given
+ // remoteType, starting from the actor related to a child process with pid
+ // aProcessId if needed and available or from a random index otherwise (as if
+ // iterating through a circular array).
+ //
+ // aCallback should be a invokable object with a function signature of
+ // bool (RemoteWorkerServiceParent*, RefPtr<ContentParent>&&)
+ //
+ // aCallback is called with the actor and corresponding ContentParent, should
+ // return false to abort iteration before all actors have been traversed (e.g.
+ // if the desired actor is found), and must not mutate mChildActors (which
+ // shouldn't be an issue because this function is const). aCallback also
+ // doesn't need to worry about proxy-releasing the ContentParent if it isn't
+ // moved out of the parameter.
+ template <typename Callback>
+ void ForEachActor(Callback&& aCallback, const nsACString& aRemoteType,
+ Maybe<base::ProcessId> aProcessId = Nothing()) const;
+
+ // The list of existing RemoteWorkerServiceParent actors for child processes.
+ // Raw pointers because RemoteWorkerServiceParent actors unregister themselves
+ // when destroyed.
+ // XXX For Fission, where we could have a lot of child actors, should we maybe
+ // instead keep either a hash table (PID->actor) or perhaps store the actors
+ // in order, sorted by PID, to avoid linear lookup times?
+ nsTArray<RemoteWorkerServiceParent*> mChildActors;
+ RemoteWorkerServiceParent* mParentActor;
+
+ struct Pending {
+ RefPtr<RemoteWorkerController> mController;
+ RemoteWorkerData mData;
+ };
+
+ nsTArray<Pending> mPendings;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_RemoteWorkerManager_h
diff --git a/dom/workers/remoteworkers/RemoteWorkerParent.cpp b/dom/workers/remoteworkers/RemoteWorkerParent.cpp
new file mode 100644
index 0000000000..ee6c8b436e
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerParent.cpp
@@ -0,0 +1,201 @@
+/* -*- 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 "RemoteWorkerParent.h"
+#include "RemoteWorkerController.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/PFetchEventOpProxyParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/Unused.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+namespace {
+
+class UnregisterActorRunnable final : public Runnable {
+ public:
+ explicit UnregisterActorRunnable(
+ already_AddRefed<ThreadsafeContentParentHandle> aParent)
+ : Runnable("UnregisterActorRunnable"), mContentHandle(aParent) {
+ AssertIsOnBackgroundThread();
+ }
+
+ NS_IMETHOD
+ Run() override {
+ AssertIsOnMainThread();
+ if (RefPtr<ContentParent> contentParent =
+ mContentHandle->GetContentParent()) {
+ contentParent->UnregisterRemoveWorkerActor();
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<ThreadsafeContentParentHandle> mContentHandle;
+};
+
+} // namespace
+
+RemoteWorkerParent::RemoteWorkerParent() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+}
+
+RemoteWorkerParent::~RemoteWorkerParent() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+}
+
+void RemoteWorkerParent::Initialize(bool aAlreadyRegistered) {
+ RefPtr<ThreadsafeContentParentHandle> parent =
+ BackgroundParent::GetContentParentHandle(Manager());
+
+ // Parent is null if the child actor runs on the parent process.
+ if (parent) {
+ if (!aAlreadyRegistered) {
+ parent->RegisterRemoteWorkerActor();
+ }
+
+ NS_ReleaseOnMainThread("RemoteWorkerParent::Initialize ContentParent",
+ parent.forget());
+ }
+}
+
+already_AddRefed<PFetchEventOpProxyParent>
+RemoteWorkerParent::AllocPFetchEventOpProxyParent(
+ const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) {
+ MOZ_CRASH("PFetchEventOpProxyParent actors must be manually constructed!");
+ return nullptr;
+}
+
+void RemoteWorkerParent::ActorDestroy(IProtocol::ActorDestroyReason) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<ThreadsafeContentParentHandle> parent =
+ BackgroundParent::GetContentParentHandle(Manager());
+
+ // Parent is null if the child actor runs on the parent process.
+ if (parent) {
+ RefPtr<UnregisterActorRunnable> r =
+ new UnregisterActorRunnable(parent.forget());
+
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
+ }
+
+ if (mController) {
+ mController->NoteDeadWorkerActor();
+ mController = nullptr;
+ }
+}
+
+IPCResult RemoteWorkerParent::RecvCreated(const bool& aStatus) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!mController) {
+ return IPC_OK();
+ }
+
+ if (aStatus) {
+ mController->CreationSucceeded();
+ } else {
+ mController->CreationFailed();
+ }
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerParent::RecvError(const ErrorValue& aValue) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (mController) {
+ mController->ErrorPropagation(aValue);
+ }
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerParent::RecvNotifyLock(const bool& aCreated) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (mController) {
+ mController->NotifyLock(aCreated);
+ }
+
+ return IPC_OK();
+}
+
+IPCResult RemoteWorkerParent::RecvNotifyWebTransport(const bool& aCreated) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (mController) {
+ mController->NotifyWebTransport(aCreated);
+ }
+
+ return IPC_OK();
+}
+
+void RemoteWorkerParent::MaybeSendDelete() {
+ if (mDeleteSent) {
+ return;
+ }
+
+ // For some reason, if the following two lines are swapped, ASan says there's
+ // a UAF...
+ mDeleteSent = true;
+ Unused << Send__delete__(this);
+}
+
+IPCResult RemoteWorkerParent::RecvClose() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (mController) {
+ mController->WorkerTerminated();
+ }
+
+ MaybeSendDelete();
+
+ return IPC_OK();
+}
+
+void RemoteWorkerParent::SetController(RemoteWorkerController* aController) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ mController = aController;
+}
+
+IPCResult RemoteWorkerParent::RecvSetServiceWorkerSkipWaitingFlag(
+ SetServiceWorkerSkipWaitingFlagResolver&& aResolve) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (mController) {
+ mController->SetServiceWorkerSkipWaitingFlag()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [resolve = aResolve](bool /* unused */) { resolve(true); },
+ [resolve = aResolve](nsresult /* unused */) { resolve(false); });
+ } else {
+ aResolve(false);
+ }
+
+ return IPC_OK();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerParent.h b/dom/workers/remoteworkers/RemoteWorkerParent.h
new file mode 100644
index 0000000000..811206eb91
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerParent.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_RemoteWorkerParent_h
+#define mozilla_dom_RemoteWorkerParent_h
+
+#include "mozilla/dom/PRemoteWorkerParent.h"
+
+namespace mozilla::dom {
+
+class RemoteWorkerController;
+
+/**
+ * PBackground-managed parent actor that is mutually associated with a single
+ * RemoteWorkerController. Relays error/close events to the controller and in
+ * turns is told life-cycle events.
+ */
+class RemoteWorkerParent final : public PRemoteWorkerParent {
+ friend class PRemoteWorkerParent;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerParent, override);
+
+ RemoteWorkerParent();
+
+ void Initialize(bool aAlreadyRegistered = false);
+
+ void SetController(RemoteWorkerController* aController);
+
+ void MaybeSendDelete();
+
+ private:
+ ~RemoteWorkerParent();
+
+ already_AddRefed<PFetchEventOpProxyParent> AllocPFetchEventOpProxyParent(
+ const ParentToChildServiceWorkerFetchEventOpArgs& aArgs);
+
+ void ActorDestroy(mozilla::ipc::IProtocol::ActorDestroyReason) override;
+
+ mozilla::ipc::IPCResult RecvError(const ErrorValue& aValue);
+
+ mozilla::ipc::IPCResult RecvNotifyLock(const bool& aCreated);
+
+ mozilla::ipc::IPCResult RecvNotifyWebTransport(const bool& aCreated);
+
+ mozilla::ipc::IPCResult RecvClose();
+
+ mozilla::ipc::IPCResult RecvCreated(const bool& aStatus);
+
+ mozilla::ipc::IPCResult RecvSetServiceWorkerSkipWaitingFlag(
+ SetServiceWorkerSkipWaitingFlagResolver&& aResolve);
+
+ bool mDeleteSent = false;
+ RefPtr<RemoteWorkerController> mController;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_RemoteWorkerParent_h
diff --git a/dom/workers/remoteworkers/RemoteWorkerService.cpp b/dom/workers/remoteworkers/RemoteWorkerService.cpp
new file mode 100644
index 0000000000..b62c551c22
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerService.cpp
@@ -0,0 +1,346 @@
+/* -*- 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 "RemoteWorkerService.h"
+
+#include "mozilla/dom/PRemoteWorkerParent.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/PBackgroundParent.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIObserverService.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMPrivate.h"
+#include "RemoteWorkerController.h"
+#include "RemoteWorkerServiceChild.h"
+#include "RemoteWorkerServiceParent.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+namespace {
+
+StaticMutex sRemoteWorkerServiceMutex;
+StaticRefPtr<RemoteWorkerService> sRemoteWorkerService;
+
+} // namespace
+
+/**
+ * Block shutdown until the RemoteWorkers have shutdown so that we do not try
+ * and shutdown the RemoteWorkerService "Worker Launcher" thread until they have
+ * cleanly shutdown.
+ *
+ * Note that this shutdown blocker is not used to initiate shutdown of any of
+ * the workers directly; their shutdown is initiated from PBackground in the
+ * parent process. The shutdown blocker just exists to avoid races around
+ * shutting down the worker launcher thread after all of the workers have
+ * shutdown and torn down their actors.
+ *
+ * Currently, it should be the case that the ContentParent should want to keep
+ * the content processes alive until the RemoteWorkers have all reported their
+ * shutdown over IPC (on the "Worker Launcher" thread). So for an orderly
+ * content process shutdown that is waiting for there to no longer be a reason
+ * to keep the content process alive, this blocker should only hang around for
+ * a brief period of time, helping smooth out lifecycle edge cases.
+ *
+ * In the event the content process is trying to shutdown while the
+ * RemoteWorkers think they should still be alive, it's possible that this
+ * blocker could expose the relevant logic error in the parent process if no
+ * attempt is made to shutdown the RemoteWorker.
+ *
+ * ## Major Implementation Note: This is not actually an nsIAsyncShutdownClient
+ *
+ * Until https://bugzilla.mozilla.org/show_bug.cgi?id=1760855 provides us with a
+ * non-JS implementation of nsIAsyncShutdownService, this implementation
+ * actually uses event loop spinning. The patch on
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1775784 that changed us to use
+ * this hack can be reverted when the time is right.
+ *
+ * Event loop spinning is handled by `RemoteWorkerService::Observe` and it calls
+ * our exposed `ShouldBlockShutdown()` to know when to stop spinning.
+ */
+class RemoteWorkerServiceShutdownBlocker final {
+ ~RemoteWorkerServiceShutdownBlocker() = default;
+
+ public:
+ explicit RemoteWorkerServiceShutdownBlocker(RemoteWorkerService* aService)
+ : mService(aService), mBlockShutdown(true) {}
+
+ void RemoteWorkersAllGoneAllowShutdown() {
+ mService->FinishShutdown();
+ mService = nullptr;
+
+ mBlockShutdown = false;
+ }
+
+ bool ShouldBlockShutdown() { return mBlockShutdown; }
+
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerServiceShutdownBlocker);
+
+ RefPtr<RemoteWorkerService> mService;
+ bool mBlockShutdown;
+};
+
+RemoteWorkerServiceKeepAlive::RemoteWorkerServiceKeepAlive(
+ RemoteWorkerServiceShutdownBlocker* aBlocker)
+ : mBlocker(aBlocker) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+RemoteWorkerServiceKeepAlive::~RemoteWorkerServiceKeepAlive() {
+ // Dispatch a runnable to the main thread to tell the Shutdown Blocker to
+ // remove itself and notify the RemoteWorkerService it can finish its
+ // shutdown. We dispatch this to the main thread even if we are already on
+ // the main thread.
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction(__func__, [blocker = std::move(mBlocker)] {
+ blocker->RemoteWorkersAllGoneAllowShutdown();
+ });
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+}
+
+/* static */
+void RemoteWorkerService::Initialize() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
+ MOZ_ASSERT(!sRemoteWorkerService);
+
+ RefPtr<RemoteWorkerService> service = new RemoteWorkerService();
+
+ // ## Content Process Initialization Case
+ //
+ // We are being told to initialize now that we know what our remote type is.
+ // Now is a fine time to call InitializeOnMainThread.
+ if (!XRE_IsParentProcess()) {
+ nsresult rv = service->InitializeOnMainThread();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ sRemoteWorkerService = service;
+ return;
+ }
+ // ## Parent Process Initialization Case
+ //
+ // Otherwise we are in the parent process and were invoked by
+ // nsLayoutStatics::Initialize. We wait until profile-after-change to kick
+ // off the Worker Launcher thread and have it connect to PBackground. This is
+ // an appropriate time for remote worker APIs to come online, especially
+ // because the PRemoteWorkerService mechanism needs processes to eagerly
+ // register themselves with PBackground since the design explicitly intends to
+ // avoid blocking on the main threads. (Disclaimer: Currently, things block
+ // on the main thread.)
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return;
+ }
+
+ nsresult rv = obs->AddObserver(service, "profile-after-change", false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ sRemoteWorkerService = service;
+}
+
+/* static */
+nsIThread* RemoteWorkerService::Thread() {
+ StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
+ MOZ_ASSERT(sRemoteWorkerService);
+ MOZ_ASSERT(sRemoteWorkerService->mThread);
+ return sRemoteWorkerService->mThread;
+}
+
+/* static */
+already_AddRefed<RemoteWorkerServiceKeepAlive>
+RemoteWorkerService::MaybeGetKeepAlive() {
+ StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
+ // In normal operation no one should be calling this without a service
+ // existing, so assert, but we'll also handle this being null as it is a
+ // plausible shutdown race.
+ MOZ_ASSERT(sRemoteWorkerService);
+ if (!sRemoteWorkerService) {
+ return nullptr;
+ }
+
+ // Note that this value can be null, but this all handles that.
+ auto lockedKeepAlive = sRemoteWorkerService->mKeepAlive.Lock();
+ RefPtr<RemoteWorkerServiceKeepAlive> extraRef = *lockedKeepAlive;
+ return extraRef.forget();
+}
+
+nsresult RemoteWorkerService::InitializeOnMainThread() {
+ // I would like to call this thread "DOM Remote Worker Launcher", but the max
+ // length is 16 chars.
+ nsresult rv = NS_NewNamedThread("Worker Launcher", getter_AddRefs(mThread));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mShutdownBlocker = new RemoteWorkerServiceShutdownBlocker(this);
+
+ {
+ RefPtr<RemoteWorkerServiceKeepAlive> keepAlive =
+ new RemoteWorkerServiceKeepAlive(mShutdownBlocker);
+
+ auto lockedKeepAlive = mKeepAlive.Lock();
+ *lockedKeepAlive = std::move(keepAlive);
+ }
+
+ RefPtr<RemoteWorkerService> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "InitializeThread", [self]() { self->InitializeOnTargetThread(); });
+
+ rv = mThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+RemoteWorkerService::RemoteWorkerService()
+ : mKeepAlive(nullptr, "RemoteWorkerService::mKeepAlive") {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+RemoteWorkerService::~RemoteWorkerService() = default;
+
+void RemoteWorkerService::InitializeOnTargetThread() {
+ MOZ_ASSERT(mThread);
+ MOZ_ASSERT(mThread->IsOnCurrentThread());
+
+ PBackgroundChild* backgroundActor =
+ BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!backgroundActor)) {
+ return;
+ }
+
+ RefPtr<RemoteWorkerServiceChild> serviceActor =
+ MakeAndAddRef<RemoteWorkerServiceChild>();
+ if (NS_WARN_IF(!backgroundActor->SendPRemoteWorkerServiceConstructor(
+ serviceActor))) {
+ return;
+ }
+
+ // Now we are ready!
+ mActor = serviceActor;
+}
+
+void RemoteWorkerService::CloseActorOnTargetThread() {
+ MOZ_ASSERT(mThread);
+ MOZ_ASSERT(mThread->IsOnCurrentThread());
+
+ // If mActor is nullptr it means that initialization failed.
+ if (mActor) {
+ // Here we need to shutdown the IPC protocol.
+ mActor->Send__delete__(mActor);
+ mActor = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+RemoteWorkerService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ MOZ_ASSERT(mThread);
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ // Note that nsObserverList::NotifyObservers will hold a strong reference to
+ // our instance throughout the entire duration of this call, so it is not
+ // necessary for us to hold a kungFuDeathGrip here.
+
+ // Drop our keep-alive. This could immediately result in our blocker saying
+ // it's okay for us to shutdown. SpinEventLoopUntil checks the predicate
+ // before spinning, so in the ideal case we will not spin the loop at all.
+ BeginShutdown();
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "RemoteWorkerService::Observe"_ns,
+ [&]() { return !mShutdownBlocker->ShouldBlockShutdown(); }));
+
+ mShutdownBlocker = nullptr;
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!strcmp(aTopic, "profile-after-change"));
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "profile-after-change");
+ }
+
+ return InitializeOnMainThread();
+}
+
+void RemoteWorkerService::BeginShutdown() {
+ // Drop our keepalive reference which may allow near-immediate removal of the
+ // blocker.
+ auto lockedKeepAlive = mKeepAlive.Lock();
+ *lockedKeepAlive = nullptr;
+}
+
+void RemoteWorkerService::FinishShutdown() {
+ // Clear the singleton before spinning the event loop when shutting down the
+ // thread so that MaybeGetKeepAlive() can assert if there are any late calls
+ // and to better reflect the actual state.
+ //
+ // Our caller, the RemoteWorkerServiceShutdownBlocker, will continue to hold a
+ // strong reference to us until we return from this call, so there are no
+ // lifecycle implications to dropping this reference.
+ {
+ StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
+ sRemoteWorkerService = nullptr;
+ }
+
+ RefPtr<RemoteWorkerService> self = this;
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction("RemoteWorkerService::CloseActorOnTargetThread",
+ [self]() { self->CloseActorOnTargetThread(); });
+
+ mThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+
+ // We've posted a shutdown message; now shutdown the thread. This will spin
+ // a nested event loop waiting for the thread to process all pending events
+ // (including the just dispatched CloseActorOnTargetThread which will close
+ // the actor), ensuring to block main thread shutdown long enough to avoid
+ // races.
+ mThread->Shutdown();
+ mThread = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(RemoteWorkerService, nsIObserver)
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerService.h b/dom/workers/remoteworkers/RemoteWorkerService.h
new file mode 100644
index 0000000000..9e05e3958b
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerService.h
@@ -0,0 +1,123 @@
+/* -*- 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_RemoteWorkerService_h
+#define mozilla_dom_RemoteWorkerService_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/DataMutex.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsISupportsImpl.h"
+
+class nsIThread;
+
+namespace mozilla::dom {
+
+class RemoteWorkerService;
+class RemoteWorkerServiceChild;
+class RemoteWorkerServiceShutdownBlocker;
+
+/**
+ * Refcounted lifecycle helper; when its refcount goes to zero its destructor
+ * will call RemoteWorkerService::Shutdown() which will remove the shutdown
+ * blocker and shutdown the "Worker Launcher" thread.
+ *
+ * The RemoteWorkerService itself will hold a reference to this singleton which
+ * it will use to hand out additional refcounts to RemoteWorkerChild instances.
+ * When the shutdown blocker is notified that it's time to shutdown, the
+ * RemoteWorkerService's reference will be dropped.
+ */
+class RemoteWorkerServiceKeepAlive {
+ public:
+ explicit RemoteWorkerServiceKeepAlive(
+ RemoteWorkerServiceShutdownBlocker* aBlocker);
+
+ private:
+ ~RemoteWorkerServiceKeepAlive();
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteWorkerServiceKeepAlive);
+
+ RefPtr<RemoteWorkerServiceShutdownBlocker> mBlocker;
+};
+
+/**
+ * Every process has a RemoteWorkerService which does the actual spawning of
+ * RemoteWorkerChild instances. The RemoteWorkerService creates a "Worker
+ * Launcher" thread at initialization on which it creates a
+ * RemoteWorkerServiceChild to service spawn requests. The thread is exposed as
+ * RemoteWorkerService::Thread(). A new/distinct thread is used because we
+ * (eventually) don't want to deal with main-thread contention, content
+ * processes have no equivalent of a PBackground thread, and actors are bound to
+ * specific threads.
+ *
+ * (Disclaimer: currently most RemoteWorkerOps need to happen on the main thread
+ * because the main-thread ends up as the owner of the worker and all
+ * manipulation of the worker must happen from the owning thread.)
+ */
+class RemoteWorkerService final : public nsIObserver {
+ friend class RemoteWorkerServiceShutdownBlocker;
+ friend class RemoteWorkerServiceKeepAlive;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ // To be called when a process is initialized on main-thread.
+ static void Initialize();
+
+ static nsIThread* Thread();
+
+ // Called by RemoteWorkerChild instances on the "Worker Launcher" thread at
+ // their creation to assist in tracking when it's safe to shutdown the
+ // RemoteWorkerService and "Worker Launcher" thread. This method will return
+ // a null pointer if the RemoteWorkerService has already begun shutdown.
+ //
+ // This is somewhat awkwardly a static method because the RemoteWorkerChild
+ // instances are not managed by RemoteWorkerServiceChild, but instead are
+ // managed by PBackground(Child). So we either need to find the
+ // RemoteWorkerService via the hidden singleton or by having the
+ // RemoteWorkerChild use PBackgroundChild::ManagedPRemoteWorkerServiceChild()
+ // to locate the instance. We are choosing to use the singleton because we
+ // already need to acquire a mutex in the call regardless and the upcoming
+ // refactorings may want to start using new toplevel protocols and this will
+ // avoid requiring a change when that happens.
+ static already_AddRefed<RemoteWorkerServiceKeepAlive> MaybeGetKeepAlive();
+
+ private:
+ RemoteWorkerService();
+ ~RemoteWorkerService();
+
+ nsresult InitializeOnMainThread();
+
+ void InitializeOnTargetThread();
+
+ void CloseActorOnTargetThread();
+
+ // Called by RemoteWorkerServiceShutdownBlocker when it's time to drop the
+ // RemoteWorkerServiceKeepAlive reference.
+ void BeginShutdown();
+
+ // Called by RemoteWorkerServiceShutdownBlocker when the blocker has been
+ // removed and it's safe to shutdown the "Worker Launcher" thread.
+ void FinishShutdown();
+
+ nsCOMPtr<nsIThread> mThread;
+ RefPtr<RemoteWorkerServiceChild> mActor;
+ // The keep-alive is set and cleared on the main thread but we will hand out
+ // additional references to it from the "Worker Launcher" thread, so it's
+ // appropriate to use a mutex. (Alternately we could have used a ThreadBound
+ // and set and cleared on the "Worker Launcher" thread, but that would
+ // involve more moving parts and could have complicated edge cases.)
+ DataMutex<RefPtr<RemoteWorkerServiceKeepAlive>> mKeepAlive;
+ // In order to poll the blocker to know when we can stop spinning the event
+ // loop at shutdown, we retain a reference to the blocker.
+ RefPtr<RemoteWorkerServiceShutdownBlocker> mShutdownBlocker;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_RemoteWorkerService_h
diff --git a/dom/workers/remoteworkers/RemoteWorkerServiceChild.cpp b/dom/workers/remoteworkers/RemoteWorkerServiceChild.cpp
new file mode 100644
index 0000000000..100908064e
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerServiceChild.cpp
@@ -0,0 +1,16 @@
+/* -*- 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 "RemoteWorkerServiceChild.h"
+#include "RemoteWorkerController.h"
+
+namespace mozilla::dom {
+
+RemoteWorkerServiceChild::RemoteWorkerServiceChild() = default;
+
+RemoteWorkerServiceChild::~RemoteWorkerServiceChild() = default;
+
+} // namespace mozilla::dom
diff --git a/dom/workers/remoteworkers/RemoteWorkerServiceChild.h b/dom/workers/remoteworkers/RemoteWorkerServiceChild.h
new file mode 100644
index 0000000000..41ae879b29
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerServiceChild.h
@@ -0,0 +1,34 @@
+/* -*- 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_RemoteWorkerServiceChild_h
+#define mozilla_dom_RemoteWorkerServiceChild_h
+
+#include "mozilla/dom/PRemoteWorkerServiceChild.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom {
+
+class RemoteWorkerController;
+class RemoteWorkerData;
+
+/**
+ * "Worker Launcher"-thread child actor created by the RemoteWorkerService to
+ * register itself with the PBackground RemoteWorkerManager in the parent.
+ */
+class RemoteWorkerServiceChild final : public PRemoteWorkerServiceChild {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerServiceChild, final)
+
+ RemoteWorkerServiceChild();
+
+ private:
+ ~RemoteWorkerServiceChild();
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_RemoteWorkerServiceChild_h
diff --git a/dom/workers/remoteworkers/RemoteWorkerServiceParent.cpp b/dom/workers/remoteworkers/RemoteWorkerServiceParent.cpp
new file mode 100644
index 0000000000..c7fd5ea6c8
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerServiceParent.cpp
@@ -0,0 +1,34 @@
+/* -*- 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 "RemoteWorkerServiceParent.h"
+#include "RemoteWorkerManager.h"
+#include "mozilla/ipc/BackgroundParent.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+RemoteWorkerServiceParent::RemoteWorkerServiceParent()
+ : mManager(RemoteWorkerManager::GetOrCreate()) {}
+
+RemoteWorkerServiceParent::~RemoteWorkerServiceParent() = default;
+
+void RemoteWorkerServiceParent::Initialize(const nsACString& aRemoteType) {
+ AssertIsOnBackgroundThread();
+ mRemoteType = aRemoteType;
+ mManager->RegisterActor(this);
+}
+
+void RemoteWorkerServiceParent::ActorDestroy(IProtocol::ActorDestroyReason) {
+ AssertIsOnBackgroundThread();
+ mManager->UnregisterActor(this);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/RemoteWorkerServiceParent.h b/dom/workers/remoteworkers/RemoteWorkerServiceParent.h
new file mode 100644
index 0000000000..31ed29a78c
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerServiceParent.h
@@ -0,0 +1,41 @@
+/* -*- 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_RemoteWorkerServiceParent_h
+#define mozilla_dom_RemoteWorkerServiceParent_h
+
+#include "mozilla/dom/PRemoteWorkerServiceParent.h"
+#include "mozilla/dom/RemoteType.h"
+
+namespace mozilla::dom {
+
+class RemoteWorkerManager;
+
+/**
+ * PBackground parent actor that registers with the PBackground
+ * RemoteWorkerManager and used to relay spawn requests.
+ */
+class RemoteWorkerServiceParent final : public PRemoteWorkerServiceParent {
+ public:
+ RemoteWorkerServiceParent();
+ NS_INLINE_DECL_REFCOUNTING(RemoteWorkerServiceParent, override);
+
+ void ActorDestroy(mozilla::ipc::IProtocol::ActorDestroyReason) override;
+
+ void Initialize(const nsACString& aRemoteType);
+
+ nsCString GetRemoteType() const { return mRemoteType; }
+
+ private:
+ ~RemoteWorkerServiceParent();
+
+ RefPtr<RemoteWorkerManager> mManager;
+ nsCString mRemoteType = NOT_REMOTE_TYPE;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_RemoteWorkerServiceParent_h
diff --git a/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh b/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh
new file mode 100644
index 0000000000..67a23e9aae
--- /dev/null
+++ b/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh
@@ -0,0 +1,123 @@
+/* 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 ClientIPCTypes;
+include IPCServiceWorkerDescriptor;
+include IPCServiceWorkerRegistrationDescriptor;
+include PBackgroundSharedTypes;
+include URIParams;
+include DOMTypes;
+include NeckoChannelParams;
+include ProtocolTypes;
+
+include "mozilla/dom/ClientIPCUtils.h";
+include "mozilla/dom/ReferrerInfoUtils.h";
+include "mozilla/dom/WorkerIPCUtils.h";
+
+using struct mozilla::void_t from "mozilla/ipc/IPCCore.h";
+using mozilla::dom::RequestCredentials from "mozilla/dom/RequestBinding.h";
+using mozilla::StorageAccess from "mozilla/StorageAccess.h";
+using mozilla::OriginTrials from "mozilla/OriginTrialsIPCUtils.h";
+using mozilla::dom::WorkerType from "mozilla/dom/WorkerBinding.h";
+
+namespace mozilla {
+namespace dom {
+
+struct ServiceWorkerData {
+ IPCServiceWorkerDescriptor descriptor;
+ IPCServiceWorkerRegistrationDescriptor registrationDescriptor;
+ nsString cacheName;
+ uint32_t loadFlags;
+ nsString id;
+};
+
+union OptionalServiceWorkerData {
+ void_t;
+ ServiceWorkerData;
+};
+
+struct RemoteWorkerData
+{
+ // This should only be used for devtools.
+ nsString originalScriptURL;
+
+ // It is important to pass these as URIParams instead of strings for blob
+ // URLs: they carry an additional bit of state with them (mIsRevoked) that
+ // gives us a chance to use them, even after they've been revoked. Because
+ // we're asynchronously calling into the parent process before potentially
+ // loading the worker, it is important to keep this state. Note that this
+ // isn't a panacea: once the URL has been revoked, it'll give the worker 5
+ // seconds to actually load it; so it's possible to still fail to load the
+ // blob URL if it takes too long to do the round trip.
+ URIParams baseScriptURL;
+ URIParams resolvedScriptURL;
+
+ nsString name;
+ WorkerType type;
+ RequestCredentials credentials;
+
+ PrincipalInfo loadingPrincipalInfo;
+ PrincipalInfo principalInfo;
+ PrincipalInfo partitionedPrincipalInfo;
+
+ bool useRegularPrincipal;
+ bool hasStorageAccessPermissionGranted;
+
+ CookieJarSettingsArgs cookieJarSettings;
+
+ nsCString domain;
+
+ bool isSecureContext;
+
+ IPCClientInfo? clientInfo;
+
+ nullable nsIReferrerInfo referrerInfo;
+
+ StorageAccess storageAccess;
+
+ bool isThirdPartyContextToTopWindow;
+
+ bool shouldResistFingerprinting;
+
+ OriginTrials originTrials;
+
+ OptionalServiceWorkerData serviceWorkerData;
+
+ nsID agentClusterId;
+
+ // Child process remote type where the worker should only run on.
+ nsCString remoteType;
+};
+
+// ErrorData/ErrorDataNote correspond to WorkerErrorReport/WorkerErrorNote
+// which in turn correspond to JSErrorReport/JSErrorNotes which allows JS to
+// report complicated errors such as redeclarations that involve multiple
+// distinct lines. For more generic error-propagation IPC structures, see bug
+// 1357463 on making ErrorResult usable over IPC.
+
+struct ErrorDataNote {
+ uint32_t lineNumber;
+ uint32_t columnNumber;
+ nsString message;
+ nsString filename;
+};
+
+struct ErrorData {
+ bool isWarning;
+ uint32_t lineNumber;
+ uint32_t columnNumber;
+ nsString message;
+ nsString filename;
+ nsString line;
+ ErrorDataNote[] notes;
+};
+
+union ErrorValue {
+ nsresult;
+ ErrorData;
+ void_t;
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/remoteworkers/moz.build b/dom/workers/remoteworkers/moz.build
new file mode 100644
index 0000000000..9983b7dd10
--- /dev/null
+++ b/dom/workers/remoteworkers/moz.build
@@ -0,0 +1,45 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ "RemoteWorkerChild.h",
+ "RemoteWorkerController.h",
+ "RemoteWorkerControllerChild.h",
+ "RemoteWorkerControllerParent.h",
+ "RemoteWorkerManager.h",
+ "RemoteWorkerParent.h",
+ "RemoteWorkerService.h",
+ "RemoteWorkerServiceChild.h",
+ "RemoteWorkerServiceParent.h",
+]
+
+UNIFIED_SOURCES += [
+ "RemoteWorkerChild.cpp",
+ "RemoteWorkerController.cpp",
+ "RemoteWorkerControllerChild.cpp",
+ "RemoteWorkerControllerParent.cpp",
+ "RemoteWorkerManager.cpp",
+ "RemoteWorkerParent.cpp",
+ "RemoteWorkerService.cpp",
+ "RemoteWorkerServiceChild.cpp",
+ "RemoteWorkerServiceParent.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/dom/serviceworkers",
+ "/xpcom/build",
+]
+
+IPDL_SOURCES += [
+ "PRemoteWorker.ipdl",
+ "PRemoteWorkerController.ipdl",
+ "PRemoteWorkerService.ipdl",
+ "RemoteWorkerTypes.ipdlh",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/workers/sharedworkers/PSharedWorker.ipdl b/dom/workers/sharedworkers/PSharedWorker.ipdl
new file mode 100644
index 0000000000..2406a731da
--- /dev/null
+++ b/dom/workers/sharedworkers/PSharedWorker.ipdl
@@ -0,0 +1,40 @@
+/* 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 protocol PBackground;
+
+include RemoteWorkerTypes;
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * Protocol for SharedWorker bindings to communicate with per-worker
+ * SharedWorkerManager instances in the parent via SharedWorkerChild /
+ * SharedWorkerParent and SharedWorkerService getting/creating the
+ * SharedWorkerManager if it doesn't already exist. Main-thread to PBackground.
+ */
+[ManualDealloc]
+protocol PSharedWorker
+{
+ manager PBackground;
+
+parent:
+ async Close();
+ async Suspend();
+ async Resume();
+ async Freeze();
+ async Thaw();
+
+child:
+ async Error(ErrorValue value);
+ async NotifyLock(bool aCreated);
+ async NotifyWebTransport(bool aCreated);
+ async Terminate();
+
+ async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/sharedworkers/SharedWorker.cpp b/dom/workers/sharedworkers/SharedWorker.cpp
new file mode 100644
index 0000000000..b5aa7d9e2e
--- /dev/null
+++ b/dom/workers/sharedworkers/SharedWorker.cpp
@@ -0,0 +1,426 @@
+/* -*- 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 "SharedWorker.h"
+
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/MessageChannel.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/PMessagePort.h"
+#include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::GetRemoteType
+#include "mozilla/dom/RemoteWorkerTypes.h"
+#include "mozilla/dom/SharedWorkerBinding.h"
+#include "mozilla/dom/SharedWorkerChild.h"
+#include "mozilla/dom/WorkerBinding.h"
+#include "mozilla/dom/WorkerLoadInfo.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/StorageAccess.h"
+#include "nsGlobalWindowInner.h"
+#include "nsPIDOMWindow.h"
+
+#ifdef XP_WIN
+# undef PostMessage
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+SharedWorker::SharedWorker(nsPIDOMWindowInner* aWindow,
+ SharedWorkerChild* aActor, MessagePort* aMessagePort)
+ : DOMEventTargetHelper(aWindow),
+ mWindow(aWindow),
+ mActor(aActor),
+ mMessagePort(aMessagePort),
+ mFrozen(false) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aMessagePort);
+}
+
+SharedWorker::~SharedWorker() {
+ AssertIsOnMainThread();
+ Close();
+}
+
+// static
+already_AddRefed<SharedWorker> SharedWorker::Constructor(
+ const GlobalObject& aGlobal, const nsAString& aScriptURL,
+ const StringOrWorkerOptions& aOptions, ErrorResult& aRv) {
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(window);
+
+ // Our current idiom is that storage-related APIs specialize for the system
+ // principal themselves, which is consistent with StorageAllowedForwindow not
+ // specializing for the system principal. Without this specialization we
+ // would end up with ePrivateBrowsing for system principaled private browsing
+ // windows which is explicitly not what we want. System Principal code always
+ // should have access to storage. It may make sense to enhance
+ // StorageAllowedForWindow in the future to handle this after comprehensive
+ // auditing.
+ nsCOMPtr<nsIPrincipal> principal = aGlobal.GetSubjectPrincipal();
+ StorageAccess storageAllowed;
+ if (principal && principal->IsSystemPrincipal()) {
+ storageAllowed = StorageAccess::eAllow;
+ } else {
+ storageAllowed = StorageAllowedForWindow(window);
+ }
+
+ if (storageAllowed == StorageAccess::eDeny) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ if (ShouldPartitionStorage(storageAllowed) &&
+ !StoragePartitioningEnabled(
+ storageAllowed, window->GetExtantDoc()->CookieJarSettings())) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ // Assert that the principal private browsing state matches the
+ // StorageAccess value.
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ if (storageAllowed == StorageAccess::ePrivateBrowsing) {
+ uint32_t privateBrowsingId = 0;
+ if (principal) {
+ MOZ_ALWAYS_SUCCEEDS(principal->GetPrivateBrowsingId(&privateBrowsingId));
+ }
+ MOZ_DIAGNOSTIC_ASSERT(privateBrowsingId != 0);
+ }
+#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
+
+ nsAutoString name;
+ WorkerType workerType = WorkerType::Classic;
+ RequestCredentials credentials = RequestCredentials::Omit;
+ if (aOptions.IsString()) {
+ name = aOptions.GetAsString();
+ } else {
+ MOZ_ASSERT(aOptions.IsWorkerOptions());
+ name = aOptions.GetAsWorkerOptions().mName;
+ workerType = aOptions.GetAsWorkerOptions().mType;
+ credentials = aOptions.GetAsWorkerOptions().mCredentials;
+ }
+
+ JSContext* cx = aGlobal.Context();
+
+ WorkerLoadInfo loadInfo;
+ aRv = WorkerPrivate::GetLoadInfo(
+ cx, window, nullptr, aScriptURL, workerType, credentials, false,
+ WorkerPrivate::OverrideLoadGroup, WorkerKindShared, &loadInfo);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ PrincipalInfo principalInfo;
+ aRv = PrincipalToPrincipalInfo(loadInfo.mPrincipal, &principalInfo);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ PrincipalInfo loadingPrincipalInfo;
+ aRv = PrincipalToPrincipalInfo(loadInfo.mLoadingPrincipal,
+ &loadingPrincipalInfo);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // Here, the PartitionedPrincipal is always equal to the SharedWorker's
+ // principal because the channel is not opened yet, and, because of this, it's
+ // not classified. We need to force the correct originAttributes.
+ //
+ // The sharedWorker's principal could be a null principal, e.g. loading a
+ // data url. In this case, we don't need to force the OAs for the partitioned
+ // principal because creating storage from a null principal will fail anyway.
+ // We should only do this for content principals.
+ //
+ // You can find more details in StoragePrincipalHelper.h
+ if (ShouldPartitionStorage(storageAllowed) &&
+ BasePrincipal::Cast(loadInfo.mPrincipal)->IsContentPrincipal()) {
+ nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(window);
+ if (!sop) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsIPrincipal* windowPrincipal = sop->GetPrincipal();
+ if (!windowPrincipal) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ nsIPrincipal* windowPartitionedPrincipal = sop->PartitionedPrincipal();
+ if (!windowPartitionedPrincipal) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ if (!windowPrincipal->Equals(windowPartitionedPrincipal)) {
+ loadInfo.mPartitionedPrincipal =
+ BasePrincipal::Cast(loadInfo.mPrincipal)
+ ->CloneForcingOriginAttributes(
+ BasePrincipal::Cast(windowPartitionedPrincipal)
+ ->OriginAttributesRef());
+ }
+ }
+
+ PrincipalInfo partitionedPrincipalInfo;
+ if (loadInfo.mPrincipal->Equals(loadInfo.mPartitionedPrincipal)) {
+ partitionedPrincipalInfo = principalInfo;
+ } else {
+ aRv = PrincipalToPrincipalInfo(loadInfo.mPartitionedPrincipal,
+ &partitionedPrincipalInfo);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ // We don't actually care about this MessageChannel, but we use it to 'steal'
+ // its 2 connected ports.
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(window);
+ RefPtr<MessageChannel> channel = MessageChannel::Constructor(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ UniqueMessagePortId portIdentifier;
+ channel->Port1()->CloneAndDisentangle(portIdentifier);
+
+ URIParams resolvedScriptURL;
+ SerializeURI(loadInfo.mResolvedScriptURI, resolvedScriptURL);
+
+ URIParams baseURL;
+ SerializeURI(loadInfo.mBaseURI, baseURL);
+
+ // Register this component to PBackground.
+ PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
+
+ bool isSecureContext = JS::GetIsSecureContext(js::GetContextRealm(cx));
+
+ Maybe<IPCClientInfo> ipcClientInfo;
+ Maybe<ClientInfo> clientInfo = window->GetClientInfo();
+ if (clientInfo.isSome()) {
+ ipcClientInfo.emplace(clientInfo.value().ToIPC());
+ }
+
+ nsID agentClusterId = nsID::GenerateUUID();
+
+ net::CookieJarSettingsArgs cjsData;
+ MOZ_ASSERT(loadInfo.mCookieJarSettings);
+ net::CookieJarSettings::Cast(loadInfo.mCookieJarSettings)->Serialize(cjsData);
+
+ auto remoteType = RemoteWorkerManager::GetRemoteType(
+ loadInfo.mPrincipal, WorkerKind::WorkerKindShared);
+ if (NS_WARN_IF(remoteType.isErr())) {
+ aRv.Throw(remoteType.unwrapErr());
+ return nullptr;
+ }
+
+ RemoteWorkerData remoteWorkerData(
+ nsString(aScriptURL), baseURL, resolvedScriptURL, name, workerType,
+ credentials, loadingPrincipalInfo, principalInfo,
+ partitionedPrincipalInfo, loadInfo.mUseRegularPrincipal,
+ loadInfo.mHasStorageAccessPermissionGranted, cjsData, loadInfo.mDomain,
+ isSecureContext, ipcClientInfo, loadInfo.mReferrerInfo, storageAllowed,
+ AntiTrackingUtils::IsThirdPartyWindow(window, nullptr),
+ loadInfo.mShouldResistFingerprinting,
+ OriginTrials::FromWindow(nsGlobalWindowInner::Cast(window)),
+ void_t() /* OptionalServiceWorkerData */, agentClusterId,
+ remoteType.unwrap());
+
+ PSharedWorkerChild* pActor = actorChild->SendPSharedWorkerConstructor(
+ remoteWorkerData, loadInfo.mWindowID, portIdentifier.release());
+
+ RefPtr<SharedWorkerChild> actor = static_cast<SharedWorkerChild*>(pActor);
+ MOZ_ASSERT(actor);
+
+ RefPtr<SharedWorker> sharedWorker =
+ new SharedWorker(window, actor, channel->Port2());
+
+ // Let's inform the window about this SharedWorker.
+ nsGlobalWindowInner::Cast(window)->StoreSharedWorker(sharedWorker);
+ actor->SetParent(sharedWorker);
+
+ if (nsGlobalWindowInner::Cast(window)->IsSuspended()) {
+ sharedWorker->Suspend();
+ }
+
+ return sharedWorker.forget();
+}
+
+MessagePort* SharedWorker::Port() {
+ AssertIsOnMainThread();
+ return mMessagePort;
+}
+
+void SharedWorker::Freeze() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!IsFrozen());
+
+ if (mFrozen) {
+ return;
+ }
+
+ mFrozen = true;
+
+ if (mActor) {
+ mActor->SendFreeze();
+ }
+}
+
+void SharedWorker::Thaw() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(IsFrozen());
+
+ if (!mFrozen) {
+ return;
+ }
+
+ mFrozen = false;
+
+ if (mActor) {
+ mActor->SendThaw();
+ }
+
+ if (!mFrozenEvents.IsEmpty()) {
+ nsTArray<RefPtr<Event>> events = std::move(mFrozenEvents);
+
+ for (uint32_t index = 0; index < events.Length(); index++) {
+ RefPtr<Event>& event = events[index];
+ MOZ_ASSERT(event);
+
+ RefPtr<EventTarget> target = event->GetTarget();
+ ErrorResult rv;
+ target->DispatchEvent(*event, rv);
+ if (rv.Failed()) {
+ NS_WARNING("Failed to dispatch event!");
+ }
+ }
+ }
+}
+
+void SharedWorker::QueueEvent(Event* aEvent) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aEvent);
+ MOZ_ASSERT(IsFrozen());
+
+ mFrozenEvents.AppendElement(aEvent);
+}
+
+void SharedWorker::Close() {
+ AssertIsOnMainThread();
+
+ if (mWindow) {
+ nsGlobalWindowInner::Cast(mWindow)->ForgetSharedWorker(this);
+ mWindow = nullptr;
+ }
+
+ if (mActor) {
+ mActor->SendClose();
+ mActor->SetParent(nullptr);
+ mActor = nullptr;
+ }
+
+ if (mMessagePort) {
+ mMessagePort->Close();
+ }
+}
+
+void SharedWorker::Suspend() {
+ if (mActor) {
+ mActor->SendSuspend();
+ }
+}
+
+void SharedWorker::Resume() {
+ if (mActor) {
+ mActor->SendResume();
+ }
+}
+
+void SharedWorker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Sequence<JSObject*>& aTransferable,
+ ErrorResult& aRv) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mMessagePort);
+
+ mMessagePort->PostMessage(aCx, aMessage, aTransferable, aRv);
+}
+
+NS_IMPL_ADDREF_INHERITED(SharedWorker, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(SharedWorker, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SharedWorker)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(SharedWorker)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SharedWorker,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessagePort)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrozenEvents)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SharedWorker,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessagePort)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrozenEvents)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+JSObject* SharedWorker::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ AssertIsOnMainThread();
+
+ return SharedWorker_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void SharedWorker::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
+ AssertIsOnMainThread();
+
+ if (IsFrozen()) {
+ RefPtr<Event> event = aVisitor.mDOMEvent;
+ if (!event) {
+ event = EventDispatcher::CreateEvent(aVisitor.mEvent->mOriginalTarget,
+ aVisitor.mPresContext,
+ aVisitor.mEvent, u""_ns);
+ }
+
+ QueueEvent(event);
+
+ aVisitor.mCanHandle = false;
+ aVisitor.SetParentTarget(nullptr, false);
+ return;
+ }
+
+ DOMEventTargetHelper::GetEventTargetParent(aVisitor);
+}
+
+void SharedWorker::ErrorPropagation(nsresult aError) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mActor);
+ MOZ_ASSERT(NS_FAILED(aError));
+
+ RefPtr<AsyncEventDispatcher> errorEvent =
+ new AsyncEventDispatcher(this, u"error"_ns, CanBubble::eNo);
+ errorEvent->PostDOMEvent();
+
+ Close();
+}
diff --git a/dom/workers/sharedworkers/SharedWorker.h b/dom/workers/sharedworkers/SharedWorker.h
new file mode 100644
index 0000000000..8318021236
--- /dev/null
+++ b/dom/workers/sharedworkers/SharedWorker.h
@@ -0,0 +1,94 @@
+/* -*- 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_sharedworker_h__
+#define mozilla_dom_workers_sharedworker_h__
+
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/DOMEventTargetHelper.h"
+
+#ifdef XP_WIN
+# undef PostMessage
+#endif
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+class EventChainPreVisitor;
+
+namespace dom {
+class MessagePort;
+class StringOrWorkerOptions;
+class Event;
+
+class SharedWorkerChild;
+
+/**
+ * DOM binding. Holds a SharedWorkerChild. Must exist on the main thread because
+ * we only allow top-level windows to create SharedWorkers.
+ */
+class SharedWorker final : public DOMEventTargetHelper {
+ using ErrorResult = mozilla::ErrorResult;
+ using GlobalObject = mozilla::dom::GlobalObject;
+
+ RefPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<SharedWorkerChild> mActor;
+ RefPtr<MessagePort> mMessagePort;
+ nsTArray<RefPtr<Event>> mFrozenEvents;
+ bool mFrozen;
+
+ public:
+ static already_AddRefed<SharedWorker> Constructor(
+ const GlobalObject& aGlobal, const nsAString& aScriptURL,
+ const StringOrWorkerOptions& aOptions, ErrorResult& aRv);
+
+ MessagePort* Port();
+
+ bool IsFrozen() const { return mFrozen; }
+
+ void QueueEvent(Event* aEvent);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SharedWorker, DOMEventTargetHelper)
+
+ IMPL_EVENT_HANDLER(error)
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
+
+ void ErrorPropagation(nsresult aError);
+
+ // Methods called from the window.
+
+ void Close();
+
+ void Suspend();
+
+ void Resume();
+
+ void Freeze();
+
+ void Thaw();
+
+ private:
+ SharedWorker(nsPIDOMWindowInner* aWindow, SharedWorkerChild* aActor,
+ MessagePort* aMessagePort);
+
+ // This class is reference-counted and will be destroyed from Release().
+ ~SharedWorker();
+
+ // Only called by MessagePort.
+ void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Sequence<JSObject*>& aTransferable, ErrorResult& aRv);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_sharedworker_h__
diff --git a/dom/workers/sharedworkers/SharedWorkerChild.cpp b/dom/workers/sharedworkers/SharedWorkerChild.cpp
new file mode 100644
index 0000000000..b11fffdcf4
--- /dev/null
+++ b/dom/workers/sharedworkers/SharedWorkerChild.cpp
@@ -0,0 +1,155 @@
+/* -*- 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 "SharedWorkerChild.h"
+#include "mozilla/dom/ErrorEvent.h"
+#include "mozilla/dom/ErrorEventBinding.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/SharedWorker.h"
+#include "mozilla/dom/WebTransport.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/dom/WorkerError.h"
+#include "mozilla/dom/locks/LockManagerChild.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+SharedWorkerChild::SharedWorkerChild() : mParent(nullptr), mActive(true) {}
+
+SharedWorkerChild::~SharedWorkerChild() = default;
+
+void SharedWorkerChild::ActorDestroy(ActorDestroyReason aWhy) {
+ mActive = false;
+}
+
+void SharedWorkerChild::SendClose() {
+ if (mActive) {
+ // This is the last message.
+ mActive = false;
+ PSharedWorkerChild::SendClose();
+ }
+}
+
+void SharedWorkerChild::SendSuspend() {
+ if (mActive) {
+ PSharedWorkerChild::SendSuspend();
+ }
+}
+
+void SharedWorkerChild::SendResume() {
+ if (mActive) {
+ PSharedWorkerChild::SendResume();
+ }
+}
+
+void SharedWorkerChild::SendFreeze() {
+ if (mActive) {
+ PSharedWorkerChild::SendFreeze();
+ }
+}
+
+void SharedWorkerChild::SendThaw() {
+ if (mActive) {
+ PSharedWorkerChild::SendThaw();
+ }
+}
+
+IPCResult SharedWorkerChild::RecvError(const ErrorValue& aValue) {
+ if (!mParent) {
+ return IPC_OK();
+ }
+
+ if (aValue.type() == ErrorValue::Tnsresult) {
+ mParent->ErrorPropagation(aValue.get_nsresult());
+ return IPC_OK();
+ }
+
+ nsPIDOMWindowInner* window = mParent->GetOwner();
+ uint64_t innerWindowId = window ? window->WindowID() : 0;
+
+ if (aValue.type() == ErrorValue::TErrorData &&
+ aValue.get_ErrorData().isWarning()) {
+ // Don't fire any events for warnings. Just log to console.
+ WorkerErrorReport::LogErrorToConsole(aValue.get_ErrorData(), innerWindowId);
+ return IPC_OK();
+ }
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ RefPtr<Event> event;
+ if (aValue.type() == ErrorValue::TErrorData) {
+ const ErrorData& errorData = aValue.get_ErrorData();
+ RootedDictionary<ErrorEventInit> errorInit(jsapi.cx());
+ errorInit.mBubbles = false;
+ errorInit.mCancelable = true;
+ errorInit.mMessage = errorData.message();
+ errorInit.mFilename = errorData.filename();
+ errorInit.mLineno = errorData.lineNumber();
+ errorInit.mColno = errorData.columnNumber();
+
+ event = ErrorEvent::Constructor(mParent, u"error"_ns, errorInit);
+ } else {
+ event = Event::Constructor(mParent, u"error"_ns, EventInit());
+ }
+ event->SetTrusted(true);
+
+ ErrorResult res;
+ bool defaultActionEnabled =
+ mParent->DispatchEvent(*event, CallerType::System, res);
+ if (res.Failed()) {
+ ThrowAndReport(window, res.StealNSResult());
+ return IPC_OK();
+ }
+
+ if (aValue.type() != ErrorValue::TErrorData) {
+ MOZ_ASSERT(aValue.type() == ErrorValue::Tvoid_t);
+ return IPC_OK();
+ }
+
+ if (defaultActionEnabled) {
+ WorkerErrorReport::LogErrorToConsole(aValue.get_ErrorData(), innerWindowId);
+ }
+
+ return IPC_OK();
+}
+
+IPCResult SharedWorkerChild::RecvNotifyLock(bool aCreated) {
+ if (!mParent) {
+ return IPC_OK();
+ }
+
+ locks::LockManagerChild::NotifyBFCacheOnMainThread(mParent->GetOwner(),
+ aCreated);
+
+ return IPC_OK();
+}
+
+IPCResult SharedWorkerChild::RecvNotifyWebTransport(bool aCreated) {
+ if (!mParent) {
+ return IPC_OK();
+ }
+
+ WebTransport::NotifyBFCacheOnMainThread(mParent->GetOwner(), aCreated);
+
+ return IPC_OK();
+}
+
+IPCResult SharedWorkerChild::RecvTerminate() {
+ if (mParent) {
+ mParent->Close();
+ }
+
+ return IPC_OK();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/sharedworkers/SharedWorkerChild.h b/dom/workers/sharedworkers/SharedWorkerChild.h
new file mode 100644
index 0000000000..c899589320
--- /dev/null
+++ b/dom/workers/sharedworkers/SharedWorkerChild.h
@@ -0,0 +1,61 @@
+/* -*- 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_dom_SharedWorkerChild_h
+#define mozilla_dom_dom_SharedWorkerChild_h
+
+#include "mozilla/dom/PSharedWorkerChild.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom {
+
+class SharedWorker;
+
+/**
+ * Held by SharedWorker bindings to remotely control sharedworker lifecycle and
+ * receive error and termination reports.
+ */
+class SharedWorkerChild final : public mozilla::dom::PSharedWorkerChild {
+ friend class PSharedWorkerChild;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(SharedWorkerChild)
+
+ SharedWorkerChild();
+
+ void SetParent(SharedWorker* aSharedWorker) { mParent = aSharedWorker; }
+
+ void SendClose();
+
+ void SendSuspend();
+
+ void SendResume();
+
+ void SendFreeze();
+
+ void SendThaw();
+
+ private:
+ ~SharedWorkerChild();
+
+ mozilla::ipc::IPCResult RecvError(const ErrorValue& aValue);
+
+ mozilla::ipc::IPCResult RecvNotifyLock(bool aCreated);
+
+ mozilla::ipc::IPCResult RecvNotifyWebTransport(bool aCreated);
+
+ mozilla::ipc::IPCResult RecvTerminate();
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ // Raw pointer because mParent is set to null when released.
+ SharedWorker* MOZ_NON_OWNING_REF mParent;
+ bool mActive;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_dom_SharedWorkerChild_h
diff --git a/dom/workers/sharedworkers/SharedWorkerManager.cpp b/dom/workers/sharedworkers/SharedWorkerManager.cpp
new file mode 100644
index 0000000000..37dbc700d9
--- /dev/null
+++ b/dom/workers/sharedworkers/SharedWorkerManager.cpp
@@ -0,0 +1,348 @@
+/* -*- 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 "SharedWorkerManager.h"
+#include "SharedWorkerParent.h"
+#include "SharedWorkerService.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/PSharedWorker.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/dom/RemoteWorkerController.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsIPrincipal.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla::dom {
+
+// static
+already_AddRefed<SharedWorkerManagerHolder> SharedWorkerManager::Create(
+ SharedWorkerService* aService, nsIEventTarget* aPBackgroundEventTarget,
+ const RemoteWorkerData& aData, nsIPrincipal* aLoadingPrincipal,
+ const OriginAttributes& aEffectiveStoragePrincipalAttrs) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<SharedWorkerManager> manager =
+ new SharedWorkerManager(aPBackgroundEventTarget, aData, aLoadingPrincipal,
+ aEffectiveStoragePrincipalAttrs);
+
+ RefPtr<SharedWorkerManagerHolder> holder =
+ new SharedWorkerManagerHolder(manager, aService);
+ return holder.forget();
+}
+
+SharedWorkerManager::SharedWorkerManager(
+ nsIEventTarget* aPBackgroundEventTarget, const RemoteWorkerData& aData,
+ nsIPrincipal* aLoadingPrincipal,
+ const OriginAttributes& aEffectiveStoragePrincipalAttrs)
+ : mPBackgroundEventTarget(aPBackgroundEventTarget),
+ mLoadingPrincipal(aLoadingPrincipal),
+ mDomain(aData.domain()),
+ mEffectiveStoragePrincipalAttrs(aEffectiveStoragePrincipalAttrs),
+ mResolvedScriptURL(DeserializeURI(aData.resolvedScriptURL())),
+ mName(aData.name()),
+ mIsSecureContext(aData.isSecureContext()),
+ mSuspended(false),
+ mFrozen(false) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aLoadingPrincipal);
+}
+
+SharedWorkerManager::~SharedWorkerManager() {
+ NS_ReleaseOnMainThread("SharedWorkerManager::mLoadingPrincipal",
+ mLoadingPrincipal.forget());
+ NS_ProxyRelease("SharedWorkerManager::mRemoteWorkerController",
+ mPBackgroundEventTarget, mRemoteWorkerController.forget());
+}
+
+bool SharedWorkerManager::MaybeCreateRemoteWorker(
+ const RemoteWorkerData& aData, uint64_t aWindowID,
+ UniqueMessagePortId& aPortIdentifier, base::ProcessId aProcessId) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ // Creating remote workers may result in creating new processes, but during
+ // parent shutdown that would add just noise, so better bail out.
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return false;
+ }
+
+ if (!mRemoteWorkerController) {
+ mRemoteWorkerController =
+ RemoteWorkerController::Create(aData, this, aProcessId);
+ if (NS_WARN_IF(!mRemoteWorkerController)) {
+ return false;
+ }
+ }
+
+ if (aWindowID) {
+ mRemoteWorkerController->AddWindowID(aWindowID);
+ }
+
+ mRemoteWorkerController->AddPortIdentifier(aPortIdentifier.release());
+ return true;
+}
+
+already_AddRefed<SharedWorkerManagerHolder>
+SharedWorkerManager::MatchOnMainThread(
+ SharedWorkerService* aService, const nsACString& aDomain,
+ nsIURI* aScriptURL, const nsAString& aName, nsIPrincipal* aLoadingPrincipal,
+ const OriginAttributes& aEffectiveStoragePrincipalAttrs) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool urlEquals;
+ if (NS_FAILED(aScriptURL->Equals(mResolvedScriptURL, &urlEquals))) {
+ return nullptr;
+ }
+
+ bool match =
+ aDomain == mDomain && urlEquals && aName == mName &&
+ // We want to be sure that the window's principal subsumes the
+ // SharedWorker's loading principal and vice versa.
+ mLoadingPrincipal->Subsumes(aLoadingPrincipal) &&
+ aLoadingPrincipal->Subsumes(mLoadingPrincipal) &&
+ mEffectiveStoragePrincipalAttrs == aEffectiveStoragePrincipalAttrs;
+ if (!match) {
+ return nullptr;
+ }
+
+ RefPtr<SharedWorkerManagerHolder> holder =
+ new SharedWorkerManagerHolder(this, aService);
+ return holder.forget();
+}
+
+void SharedWorkerManager::AddActor(SharedWorkerParent* aParent) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParent);
+ MOZ_ASSERT(!mActors.Contains(aParent));
+
+ mActors.AppendElement(aParent);
+
+ if (mLockCount) {
+ Unused << aParent->SendNotifyLock(true);
+ }
+
+ if (mWebTransportCount) {
+ Unused << aParent->SendNotifyWebTransport(true);
+ }
+
+ // NB: We don't update our Suspended/Frozen state here, yet. The aParent is
+ // responsible for doing so from SharedWorkerParent::ManagerCreated.
+ // XXX But we could avoid iterating all of our actors because if aParent is
+ // not frozen and we are, we would just need to thaw ourselves.
+}
+
+void SharedWorkerManager::RemoveActor(SharedWorkerParent* aParent) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParent);
+ MOZ_ASSERT(mActors.Contains(aParent));
+
+ uint64_t windowID = aParent->WindowID();
+ if (windowID) {
+ mRemoteWorkerController->RemoveWindowID(windowID);
+ }
+
+ mActors.RemoveElement(aParent);
+
+ if (!mActors.IsEmpty()) {
+ // Our remaining actors could be all suspended or frozen.
+ UpdateSuspend();
+ UpdateFrozen();
+ return;
+ }
+}
+
+void SharedWorkerManager::Terminate() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mActors.IsEmpty());
+ MOZ_ASSERT(mHolders.IsEmpty());
+
+ // mRemoteWorkerController creation can fail. If the creation fails
+ // mRemoteWorkerController is nullptr and we should stop termination here.
+ if (!mRemoteWorkerController) {
+ return;
+ }
+
+ mRemoteWorkerController->Terminate();
+ mRemoteWorkerController = nullptr;
+}
+
+void SharedWorkerManager::UpdateSuspend() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mRemoteWorkerController);
+
+ uint32_t suspended = 0;
+
+ for (SharedWorkerParent* actor : mActors) {
+ if (actor->IsSuspended()) {
+ ++suspended;
+ }
+ }
+
+ // Call Suspend only when all of our actors' windows are suspended and call
+ // Resume only when one of them resumes.
+ if ((mSuspended && suspended == mActors.Length()) ||
+ (!mSuspended && suspended != mActors.Length())) {
+ return;
+ }
+
+ if (!mSuspended) {
+ mSuspended = true;
+ mRemoteWorkerController->Suspend();
+ } else {
+ mSuspended = false;
+ mRemoteWorkerController->Resume();
+ }
+}
+
+void SharedWorkerManager::UpdateFrozen() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mRemoteWorkerController);
+
+ uint32_t frozen = 0;
+
+ for (SharedWorkerParent* actor : mActors) {
+ if (actor->IsFrozen()) {
+ ++frozen;
+ }
+ }
+
+ // Similar to UpdateSuspend, above, we only want to be frozen when all of our
+ // actors are frozen.
+ if ((mFrozen && frozen == mActors.Length()) ||
+ (!mFrozen && frozen != mActors.Length())) {
+ return;
+ }
+
+ if (!mFrozen) {
+ mFrozen = true;
+ mRemoteWorkerController->Freeze();
+ } else {
+ mFrozen = false;
+ mRemoteWorkerController->Thaw();
+ }
+}
+
+bool SharedWorkerManager::IsSecureContext() const { return mIsSecureContext; }
+
+void SharedWorkerManager::CreationFailed() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ for (SharedWorkerParent* actor : mActors) {
+ Unused << actor->SendError(NS_ERROR_FAILURE);
+ }
+}
+
+void SharedWorkerManager::CreationSucceeded() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ // Nothing to do here.
+}
+
+void SharedWorkerManager::ErrorReceived(const ErrorValue& aValue) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ for (SharedWorkerParent* actor : mActors) {
+ Unused << actor->SendError(aValue);
+ }
+}
+
+void SharedWorkerManager::LockNotified(bool aCreated) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT_IF(!aCreated, mLockCount > 0);
+
+ mLockCount += aCreated ? 1 : -1;
+
+ // Notify only when we either:
+ // 1. Got a new lock when nothing were there
+ // 2. Lost all locks
+ if ((aCreated && mLockCount == 1) || !mLockCount) {
+ for (SharedWorkerParent* actor : mActors) {
+ Unused << actor->SendNotifyLock(aCreated);
+ }
+ }
+};
+
+void SharedWorkerManager::WebTransportNotified(bool aCreated) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT_IF(!aCreated, mWebTransportCount > 0);
+
+ mWebTransportCount += aCreated ? 1 : -1;
+
+ // Notify only when we either:
+ // 1. Got a first WebTransport
+ // 2. The last WebTransport goes away
+ if ((aCreated && mWebTransportCount == 1) || mWebTransportCount == 0) {
+ for (SharedWorkerParent* actor : mActors) {
+ Unused << actor->SendNotifyWebTransport(aCreated);
+ }
+ }
+};
+
+void SharedWorkerManager::Terminated() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ for (SharedWorkerParent* actor : mActors) {
+ Unused << actor->SendTerminate();
+ }
+}
+
+void SharedWorkerManager::RegisterHolder(SharedWorkerManagerHolder* aHolder) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aHolder);
+ MOZ_ASSERT(!mHolders.Contains(aHolder));
+
+ mHolders.AppendElement(aHolder);
+}
+
+void SharedWorkerManager::UnregisterHolder(SharedWorkerManagerHolder* aHolder) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aHolder);
+ MOZ_ASSERT(mHolders.Contains(aHolder));
+
+ mHolders.RemoveElement(aHolder);
+
+ if (!mHolders.IsEmpty()) {
+ return;
+ }
+
+ // Time to go.
+
+ aHolder->Service()->RemoveWorkerManagerOnMainThread(this);
+
+ RefPtr<SharedWorkerManager> self = this;
+ mPBackgroundEventTarget->Dispatch(
+ NS_NewRunnableFunction(
+ "SharedWorkerService::RemoveWorkerManagerOnMainThread",
+ [self]() { self->Terminate(); }),
+ NS_DISPATCH_NORMAL);
+}
+
+SharedWorkerManagerHolder::SharedWorkerManagerHolder(
+ SharedWorkerManager* aManager, SharedWorkerService* aService)
+ : mManager(aManager), mService(aService) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aManager);
+ MOZ_ASSERT(aService);
+
+ aManager->RegisterHolder(this);
+}
+
+SharedWorkerManagerHolder::~SharedWorkerManagerHolder() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mManager->UnregisterHolder(this);
+}
+
+SharedWorkerManagerWrapper::SharedWorkerManagerWrapper(
+ already_AddRefed<SharedWorkerManagerHolder> aHolder)
+ : mHolder(aHolder) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+SharedWorkerManagerWrapper::~SharedWorkerManagerWrapper() {
+ NS_ReleaseOnMainThread("SharedWorkerManagerWrapper::mHolder",
+ mHolder.forget());
+}
+
+} // namespace mozilla::dom
diff --git a/dom/workers/sharedworkers/SharedWorkerManager.h b/dom/workers/sharedworkers/SharedWorkerManager.h
new file mode 100644
index 0000000000..fceabca4d4
--- /dev/null
+++ b/dom/workers/sharedworkers/SharedWorkerManager.h
@@ -0,0 +1,164 @@
+/* -*- 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_SharedWorkerManager_h
+#define mozilla_dom_SharedWorkerManager_h
+
+#include "SharedWorkerParent.h"
+#include "mozilla/dom/RemoteWorkerController.h"
+#include "mozilla/dom/quota/CheckedUnsafePtr.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+class nsIPrincipal;
+
+namespace mozilla::dom {
+
+class UniqueMessagePortId;
+class RemoteWorkerData;
+class SharedWorkerManager;
+class SharedWorkerService;
+
+// Main-thread only object that keeps a manager and the service alive.
+// When the last SharedWorkerManagerHolder is released, the corresponding
+// manager unregisters itself from the service and terminates the worker.
+class SharedWorkerManagerHolder final
+ : public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(SharedWorkerManagerHolder);
+
+ SharedWorkerManagerHolder(SharedWorkerManager* aManager,
+ SharedWorkerService* aService);
+
+ SharedWorkerManager* Manager() const { return mManager; }
+
+ SharedWorkerService* Service() const { return mService; }
+
+ private:
+ ~SharedWorkerManagerHolder();
+
+ const RefPtr<SharedWorkerManager> mManager;
+ const RefPtr<SharedWorkerService> mService;
+};
+
+// Thread-safe wrapper for SharedWorkerManagerHolder.
+class SharedWorkerManagerWrapper final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWorkerManagerWrapper);
+
+ explicit SharedWorkerManagerWrapper(
+ already_AddRefed<SharedWorkerManagerHolder> aHolder);
+
+ SharedWorkerManager* Manager() const { return mHolder->Manager(); }
+
+ private:
+ ~SharedWorkerManagerWrapper();
+
+ RefPtr<SharedWorkerManagerHolder> mHolder;
+};
+
+/**
+ * PBackground instance that corresponds to a single logical Shared Worker that
+ * exists somewhere in the process tree. Referenced/owned by multiple
+ * SharedWorkerParent instances on the PBackground thread. Holds/owns a single
+ * RemoteWorkerController to interact with the actual shared worker thread,
+ * wherever it is located. Creates the RemoteWorkerController via
+ * RemoteWorkerController::Create which uses RemoteWorkerManager::Launch under
+ * the hood.
+ */
+class SharedWorkerManager final : public RemoteWorkerObserver {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWorkerManager, override);
+
+ // Called on main-thread thread methods
+
+ static already_AddRefed<SharedWorkerManagerHolder> Create(
+ SharedWorkerService* aService, nsIEventTarget* aPBackgroundEventTarget,
+ const RemoteWorkerData& aData, nsIPrincipal* aLoadingPrincipal,
+ const OriginAttributes& aEffectiveStoragePrincipalAttrs);
+
+ // Returns a holder if this manager matches. The holder blocks the shutdown of
+ // the manager.
+ already_AddRefed<SharedWorkerManagerHolder> MatchOnMainThread(
+ SharedWorkerService* aService, const nsACString& aDomain,
+ nsIURI* aScriptURL, const nsAString& aName,
+ nsIPrincipal* aLoadingPrincipal,
+ const OriginAttributes& aEffectiveStoragePrincipalAttrs);
+
+ // RemoteWorkerObserver
+
+ void CreationFailed() override;
+
+ void CreationSucceeded() override;
+
+ void ErrorReceived(const ErrorValue& aValue) override;
+
+ void LockNotified(bool aCreated) final;
+
+ void WebTransportNotified(bool aCreated) final;
+
+ void Terminated() override;
+
+ // Called on PBackground thread methods
+
+ bool MaybeCreateRemoteWorker(const RemoteWorkerData& aData,
+ uint64_t aWindowID,
+ UniqueMessagePortId& aPortIdentifier,
+ base::ProcessId aProcessId);
+
+ void AddActor(SharedWorkerParent* aParent);
+
+ void RemoveActor(SharedWorkerParent* aParent);
+
+ void UpdateSuspend();
+
+ void UpdateFrozen();
+
+ bool IsSecureContext() const;
+
+ void Terminate();
+
+ // Called on main-thread only.
+
+ void RegisterHolder(SharedWorkerManagerHolder* aHolder);
+
+ void UnregisterHolder(SharedWorkerManagerHolder* aHolder);
+
+ private:
+ SharedWorkerManager(nsIEventTarget* aPBackgroundEventTarget,
+ const RemoteWorkerData& aData,
+ nsIPrincipal* aLoadingPrincipal,
+ const OriginAttributes& aEffectiveStoragePrincipalAttrs);
+
+ ~SharedWorkerManager();
+
+ nsCOMPtr<nsIEventTarget> mPBackgroundEventTarget;
+
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ const nsCString mDomain;
+ const OriginAttributes mEffectiveStoragePrincipalAttrs;
+ const nsCOMPtr<nsIURI> mResolvedScriptURL;
+ const nsString mName;
+ const bool mIsSecureContext;
+ bool mSuspended;
+ bool mFrozen;
+ uint32_t mLockCount = 0;
+ uint32_t mWebTransportCount = 0;
+
+ // Raw pointers because SharedWorkerParent unregisters itself in
+ // ActorDestroy().
+ nsTArray<CheckedUnsafePtr<SharedWorkerParent>> mActors;
+
+ RefPtr<RemoteWorkerController> mRemoteWorkerController;
+
+ // Main-thread only. Raw Pointers because holders keep the manager alive and
+ // they unregister themselves in their DTOR.
+ nsTArray<CheckedUnsafePtr<SharedWorkerManagerHolder>> mHolders;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_SharedWorkerManager_h
diff --git a/dom/workers/sharedworkers/SharedWorkerParent.cpp b/dom/workers/sharedworkers/SharedWorkerParent.cpp
new file mode 100644
index 0000000000..38e56f5100
--- /dev/null
+++ b/dom/workers/sharedworkers/SharedWorkerParent.cpp
@@ -0,0 +1,165 @@
+/* -*- 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 "SharedWorkerParent.h"
+#include "SharedWorkerManager.h"
+#include "SharedWorkerService.h"
+#include "mozilla/dom/RemoteWorkerTypes.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+SharedWorkerParent::SharedWorkerParent()
+ : mBackgroundEventTarget(GetCurrentSerialEventTarget()),
+ mStatus(eInit),
+ mSuspended(false),
+ mFrozen(false) {
+ AssertIsOnBackgroundThread();
+}
+
+SharedWorkerParent::~SharedWorkerParent() = default;
+
+void SharedWorkerParent::ActorDestroy(IProtocol::ActorDestroyReason aReason) {
+ AssertIsOnBackgroundThread();
+
+ if (mWorkerManagerWrapper) {
+ mWorkerManagerWrapper->Manager()->RemoveActor(this);
+ mWorkerManagerWrapper = nullptr;
+ }
+}
+
+void SharedWorkerParent::Initialize(
+ const RemoteWorkerData& aData, uint64_t aWindowID,
+ const MessagePortIdentifier& aPortIdentifier) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mStatus == eInit);
+
+ mWindowID = aWindowID;
+
+ mStatus = ePending;
+
+ RefPtr<SharedWorkerService> service = SharedWorkerService::GetOrCreate();
+ MOZ_ASSERT(service);
+ service->GetOrCreateWorkerManager(this, aData, aWindowID, aPortIdentifier);
+}
+
+IPCResult SharedWorkerParent::RecvClose() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mStatus == ePending || mStatus == eActive);
+
+ mStatus = eClosed;
+
+ if (mWorkerManagerWrapper) {
+ mWorkerManagerWrapper->Manager()->RemoveActor(this);
+ mWorkerManagerWrapper = nullptr;
+ }
+
+ Unused << Send__delete__(this);
+ return IPC_OK();
+}
+
+IPCResult SharedWorkerParent::RecvSuspend() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mSuspended);
+ MOZ_ASSERT(mStatus == ePending || mStatus == eActive);
+
+ mSuspended = true;
+
+ if (mStatus == eActive) {
+ MOZ_ASSERT(mWorkerManagerWrapper);
+ mWorkerManagerWrapper->Manager()->UpdateSuspend();
+ }
+
+ return IPC_OK();
+}
+
+IPCResult SharedWorkerParent::RecvResume() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mSuspended);
+ MOZ_ASSERT(mStatus == ePending || mStatus == eActive);
+
+ mSuspended = false;
+
+ if (mStatus == eActive) {
+ MOZ_ASSERT(mWorkerManagerWrapper);
+ mWorkerManagerWrapper->Manager()->UpdateSuspend();
+ }
+
+ return IPC_OK();
+}
+
+IPCResult SharedWorkerParent::RecvFreeze() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mFrozen);
+ MOZ_ASSERT(mStatus == ePending || mStatus == eActive);
+
+ mFrozen = true;
+
+ if (mStatus == eActive) {
+ MOZ_ASSERT(mWorkerManagerWrapper);
+ mWorkerManagerWrapper->Manager()->UpdateFrozen();
+ }
+
+ return IPC_OK();
+}
+
+IPCResult SharedWorkerParent::RecvThaw() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mFrozen);
+ MOZ_ASSERT(mStatus == ePending || mStatus == eActive);
+
+ mFrozen = false;
+
+ if (mStatus == eActive) {
+ MOZ_ASSERT(mWorkerManagerWrapper);
+ mWorkerManagerWrapper->Manager()->UpdateFrozen();
+ }
+
+ return IPC_OK();
+}
+
+void SharedWorkerParent::ManagerCreated(
+ already_AddRefed<SharedWorkerManagerWrapper> aWorkerManagerWrapper) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mWorkerManagerWrapper);
+ MOZ_ASSERT(mStatus == ePending || mStatus == eClosed);
+
+ RefPtr<SharedWorkerManagerWrapper> wrapper = aWorkerManagerWrapper;
+
+ // Already gone.
+ if (mStatus == eClosed) {
+ wrapper->Manager()->RemoveActor(this);
+ return;
+ }
+
+ mStatus = eActive;
+ mWorkerManagerWrapper = wrapper;
+
+ mWorkerManagerWrapper->Manager()->UpdateFrozen();
+ mWorkerManagerWrapper->Manager()->UpdateSuspend();
+}
+
+void SharedWorkerParent::ErrorPropagation(nsresult aError) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(NS_FAILED(aError));
+ MOZ_ASSERT(mStatus == ePending || mStatus == eClosed);
+
+ // Already gone.
+ if (mStatus == eClosed) {
+ return;
+ }
+
+ Unused << SendError(aError);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/sharedworkers/SharedWorkerParent.h b/dom/workers/sharedworkers/SharedWorkerParent.h
new file mode 100644
index 0000000000..c91e66cc2e
--- /dev/null
+++ b/dom/workers/sharedworkers/SharedWorkerParent.h
@@ -0,0 +1,81 @@
+/* -*- 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_dom_SharedWorkerParent_h
+#define mozilla_dom_dom_SharedWorkerParent_h
+
+#include "mozilla/dom/PSharedWorkerParent.h"
+#include "mozilla/dom/quota/CheckedUnsafePtr.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom {
+
+class MessagePortIdentifier;
+class RemoteWorkerData;
+class SharedWorkerManagerWrapper;
+
+/**
+ * PBackground actor that relays life-cycle events (freeze/thaw, suspend/resume,
+ * close) to the PBackground SharedWorkerManager and relays error/termination
+ * back to the child.
+ */
+class SharedWorkerParent final
+ : public mozilla::dom::PSharedWorkerParent,
+ public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWorkerParent)
+
+ SharedWorkerParent();
+
+ void Initialize(const RemoteWorkerData& aData, uint64_t aWindowID,
+ const MessagePortIdentifier& aPortIdentifier);
+
+ void ManagerCreated(
+ already_AddRefed<SharedWorkerManagerWrapper> aWorkerManagerWrapper);
+
+ void ErrorPropagation(nsresult aError);
+
+ mozilla::ipc::IPCResult RecvClose();
+
+ mozilla::ipc::IPCResult RecvSuspend();
+
+ mozilla::ipc::IPCResult RecvResume();
+
+ mozilla::ipc::IPCResult RecvFreeze();
+
+ mozilla::ipc::IPCResult RecvThaw();
+
+ bool IsSuspended() const { return mSuspended; }
+
+ bool IsFrozen() const { return mFrozen; }
+
+ uint64_t WindowID() const { return mWindowID; }
+
+ private:
+ ~SharedWorkerParent();
+
+ void ActorDestroy(IProtocol::ActorDestroyReason aReason) override;
+
+ nsCOMPtr<nsIEventTarget> mBackgroundEventTarget;
+ RefPtr<SharedWorkerManagerWrapper> mWorkerManagerWrapper;
+
+ enum {
+ eInit,
+ ePending,
+ eActive,
+ eClosed,
+ } mStatus;
+
+ uint64_t mWindowID;
+
+ bool mSuspended;
+ bool mFrozen;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_dom_SharedWorkerParent_h
diff --git a/dom/workers/sharedworkers/SharedWorkerService.cpp b/dom/workers/sharedworkers/SharedWorkerService.cpp
new file mode 100644
index 0000000000..b543e8cb5e
--- /dev/null
+++ b/dom/workers/sharedworkers/SharedWorkerService.cpp
@@ -0,0 +1,263 @@
+/* -*- 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 "SharedWorkerService.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/RemoteWorkerTypes.h"
+#include "mozilla/dom/SharedWorkerManager.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticMutex.h"
+#include "nsIPrincipal.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+namespace {
+
+StaticMutex sSharedWorkerMutex;
+
+StaticRefPtr<SharedWorkerService> sSharedWorkerService;
+
+class GetOrCreateWorkerManagerRunnable final : public Runnable {
+ public:
+ GetOrCreateWorkerManagerRunnable(SharedWorkerService* aService,
+ SharedWorkerParent* aActor,
+ const RemoteWorkerData& aData,
+ uint64_t aWindowID,
+ const MessagePortIdentifier& aPortIdentifier)
+ : Runnable("GetOrCreateWorkerManagerRunnable"),
+ mBackgroundEventTarget(GetCurrentSerialEventTarget()),
+ mService(aService),
+ mActor(aActor),
+ mData(aData),
+ mWindowID(aWindowID),
+ mPortIdentifier(aPortIdentifier) {}
+
+ NS_IMETHOD
+ Run() {
+ mService->GetOrCreateWorkerManagerOnMainThread(
+ mBackgroundEventTarget, mActor, mData, mWindowID, mPortIdentifier);
+
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIEventTarget> mBackgroundEventTarget;
+ RefPtr<SharedWorkerService> mService;
+ RefPtr<SharedWorkerParent> mActor;
+ RemoteWorkerData mData;
+ uint64_t mWindowID;
+ UniqueMessagePortId mPortIdentifier;
+};
+
+class WorkerManagerCreatedRunnable final : public Runnable {
+ public:
+ WorkerManagerCreatedRunnable(
+ already_AddRefed<SharedWorkerManagerWrapper> aManagerWrapper,
+ SharedWorkerParent* aActor, const RemoteWorkerData& aData,
+ uint64_t aWindowID, UniqueMessagePortId& aPortIdentifier)
+ : Runnable("WorkerManagerCreatedRunnable"),
+ mManagerWrapper(aManagerWrapper),
+ mActor(aActor),
+ mData(aData),
+ mWindowID(aWindowID),
+ mPortIdentifier(std::move(aPortIdentifier)) {}
+
+ NS_IMETHOD
+ Run() {
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(
+ !mActor->CanSend() ||
+ !mManagerWrapper->Manager()->MaybeCreateRemoteWorker(
+ mData, mWindowID, mPortIdentifier, mActor->OtherPid()))) {
+ // If we cannot send, the error won't arrive, but we may log something.
+ mActor->ErrorPropagation(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ mManagerWrapper->Manager()->AddActor(mActor);
+ mActor->ManagerCreated(mManagerWrapper.forget());
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<SharedWorkerManagerWrapper> mManagerWrapper;
+ RefPtr<SharedWorkerParent> mActor;
+ RemoteWorkerData mData;
+ uint64_t mWindowID;
+ UniqueMessagePortId mPortIdentifier;
+};
+
+class ErrorPropagationRunnable final : public Runnable {
+ public:
+ ErrorPropagationRunnable(SharedWorkerParent* aActor, nsresult aError)
+ : Runnable("ErrorPropagationRunnable"), mActor(aActor), mError(aError) {}
+
+ NS_IMETHOD
+ Run() {
+ AssertIsOnBackgroundThread();
+ mActor->ErrorPropagation(mError);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<SharedWorkerParent> mActor;
+ nsresult mError;
+};
+
+} // namespace
+
+/* static */
+already_AddRefed<SharedWorkerService> SharedWorkerService::GetOrCreate() {
+ AssertIsOnBackgroundThread();
+
+ StaticMutexAutoLock lock(sSharedWorkerMutex);
+
+ if (!sSharedWorkerService) {
+ sSharedWorkerService = new SharedWorkerService();
+ // ClearOnShutdown can only be called on main thread
+ nsresult rv = SchedulerGroup::Dispatch(
+ TaskCategory::Other,
+ NS_NewRunnableFunction("RegisterSharedWorkerServiceClearOnShutdown",
+ []() {
+ StaticMutexAutoLock lock(sSharedWorkerMutex);
+ MOZ_ASSERT(sSharedWorkerService);
+ ClearOnShutdown(&sSharedWorkerService);
+ }));
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+
+ RefPtr<SharedWorkerService> instance = sSharedWorkerService;
+ return instance.forget();
+}
+
+/* static */
+SharedWorkerService* SharedWorkerService::Get() {
+ StaticMutexAutoLock lock(sSharedWorkerMutex);
+
+ MOZ_ASSERT(sSharedWorkerService);
+ return sSharedWorkerService;
+}
+
+void SharedWorkerService::GetOrCreateWorkerManager(
+ SharedWorkerParent* aActor, const RemoteWorkerData& aData,
+ uint64_t aWindowID, const MessagePortIdentifier& aPortIdentifier) {
+ AssertIsOnBackgroundThread();
+
+ // The real check happens on main-thread.
+ RefPtr<GetOrCreateWorkerManagerRunnable> r =
+ new GetOrCreateWorkerManagerRunnable(this, aActor, aData, aWindowID,
+ aPortIdentifier);
+
+ nsresult rv = SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+}
+
+void SharedWorkerService::GetOrCreateWorkerManagerOnMainThread(
+ nsIEventTarget* aBackgroundEventTarget, SharedWorkerParent* aActor,
+ const RemoteWorkerData& aData, uint64_t aWindowID,
+ UniqueMessagePortId& aPortIdentifier) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aBackgroundEventTarget);
+ MOZ_ASSERT(aActor);
+
+ auto partitionedPrincipalOrErr =
+ PrincipalInfoToPrincipal(aData.partitionedPrincipalInfo());
+ if (NS_WARN_IF(partitionedPrincipalOrErr.isErr())) {
+ ErrorPropagationOnMainThread(aBackgroundEventTarget, aActor,
+ partitionedPrincipalOrErr.unwrapErr());
+ return;
+ }
+
+ auto loadingPrincipalOrErr =
+ PrincipalInfoToPrincipal(aData.loadingPrincipalInfo());
+ if (NS_WARN_IF(loadingPrincipalOrErr.isErr())) {
+ ErrorPropagationOnMainThread(aBackgroundEventTarget, aActor,
+ loadingPrincipalOrErr.unwrapErr());
+ return;
+ }
+
+ RefPtr<SharedWorkerManagerHolder> managerHolder;
+
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = loadingPrincipalOrErr.unwrap();
+ nsCOMPtr<nsIPrincipal> partitionedPrincipal =
+ partitionedPrincipalOrErr.unwrap();
+
+ nsCOMPtr<nsIPrincipal> effectiveStoragePrincipal = partitionedPrincipal;
+ if (aData.useRegularPrincipal()) {
+ effectiveStoragePrincipal = loadingPrincipal;
+ }
+
+ // Let's see if there is already a SharedWorker to share.
+ nsCOMPtr<nsIURI> resolvedScriptURL =
+ DeserializeURI(aData.resolvedScriptURL());
+ for (SharedWorkerManager* workerManager : mWorkerManagers) {
+ managerHolder = workerManager->MatchOnMainThread(
+ this, aData.domain(), resolvedScriptURL, aData.name(), loadingPrincipal,
+ BasePrincipal::Cast(effectiveStoragePrincipal)->OriginAttributesRef());
+ if (managerHolder) {
+ break;
+ }
+ }
+
+ // Let's create a new one.
+ if (!managerHolder) {
+ managerHolder = SharedWorkerManager::Create(
+ this, aBackgroundEventTarget, aData, loadingPrincipal,
+ BasePrincipal::Cast(effectiveStoragePrincipal)->OriginAttributesRef());
+
+ mWorkerManagers.AppendElement(managerHolder->Manager());
+ } else {
+ // We are attaching the actor to an existing one.
+ if (managerHolder->Manager()->IsSecureContext() !=
+ aData.isSecureContext()) {
+ ErrorPropagationOnMainThread(aBackgroundEventTarget, aActor,
+ NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+ }
+
+ RefPtr<SharedWorkerManagerWrapper> wrapper =
+ new SharedWorkerManagerWrapper(managerHolder.forget());
+
+ RefPtr<WorkerManagerCreatedRunnable> r = new WorkerManagerCreatedRunnable(
+ wrapper.forget(), aActor, aData, aWindowID, aPortIdentifier);
+ aBackgroundEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void SharedWorkerService::ErrorPropagationOnMainThread(
+ nsIEventTarget* aBackgroundEventTarget, SharedWorkerParent* aActor,
+ nsresult aError) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aBackgroundEventTarget);
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(NS_FAILED(aError));
+
+ RefPtr<ErrorPropagationRunnable> r =
+ new ErrorPropagationRunnable(aActor, aError);
+ aBackgroundEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+void SharedWorkerService::RemoveWorkerManagerOnMainThread(
+ SharedWorkerManager* aManager) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aManager);
+ MOZ_ASSERT(mWorkerManagers.Contains(aManager));
+
+ mWorkerManagers.RemoveElement(aManager);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/sharedworkers/SharedWorkerService.h b/dom/workers/sharedworkers/SharedWorkerService.h
new file mode 100644
index 0000000000..c4671163bd
--- /dev/null
+++ b/dom/workers/sharedworkers/SharedWorkerService.h
@@ -0,0 +1,74 @@
+/* -*- 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_SharedWorkerService_h
+#define mozilla_dom_SharedWorkerService_h
+
+#include "mozilla/dom/quota/CheckedUnsafePtr.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+class nsIEventTarget;
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+}
+
+namespace dom {
+
+class MessagePortIdentifier;
+class RemoteWorkerData;
+class SharedWorkerManager;
+class SharedWorkerParent;
+class UniqueMessagePortId;
+
+/**
+ * PBackground service that creates and tracks the per-worker
+ * SharedWorkerManager instances, allowing rendezvous between SharedWorkerParent
+ * instances and the SharedWorkerManagers they want to talk to (1:1).
+ */
+class SharedWorkerService final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWorkerService);
+
+ // This can be called on PBackground thread only.
+ static already_AddRefed<SharedWorkerService> GetOrCreate();
+
+ // The service, if already created, is available on any thread using this
+ // method.
+ static SharedWorkerService* Get();
+
+ // PBackground method only.
+ void GetOrCreateWorkerManager(SharedWorkerParent* aActor,
+ const RemoteWorkerData& aData,
+ uint64_t aWindowID,
+ const MessagePortIdentifier& aPortIdentifier);
+
+ void GetOrCreateWorkerManagerOnMainThread(
+ nsIEventTarget* aBackgroundEventTarget, SharedWorkerParent* aActor,
+ const RemoteWorkerData& aData, uint64_t aWindowID,
+ UniqueMessagePortId& aPortIdentifier);
+
+ void RemoveWorkerManagerOnMainThread(SharedWorkerManager* aManager);
+
+ private:
+ SharedWorkerService() = default;
+ ~SharedWorkerService() = default;
+
+ void ErrorPropagationOnMainThread(nsIEventTarget* aBackgroundEventTarget,
+ SharedWorkerParent* aActor,
+ nsresult aError);
+
+ // Touched on main-thread only.
+ nsTArray<RefPtr<SharedWorkerManager>> mWorkerManagers;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_SharedWorkerService_h
diff --git a/dom/workers/sharedworkers/moz.build b/dom/workers/sharedworkers/moz.build
new file mode 100644
index 0000000000..2b83bc9525
--- /dev/null
+++ b/dom/workers/sharedworkers/moz.build
@@ -0,0 +1,28 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ "SharedWorker.h",
+ "SharedWorkerChild.h",
+ "SharedWorkerManager.h",
+ "SharedWorkerParent.h",
+]
+
+UNIFIED_SOURCES += [
+ "SharedWorker.cpp",
+ "SharedWorkerChild.cpp",
+ "SharedWorkerManager.cpp",
+ "SharedWorkerParent.cpp",
+ "SharedWorkerService.cpp",
+]
+
+IPDL_SOURCES += [
+ "PSharedWorker.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/workers/test/404_server.sjs b/dom/workers/test/404_server.sjs
new file mode 100644
index 0000000000..f83281efa8
--- /dev/null
+++ b/dom/workers/test/404_server.sjs
@@ -0,0 +1,9 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 404, "Not found");
+
+ // Any valid JS.
+ if (request.queryString == "js") {
+ response.setHeader("Content-Type", "text/javascript", false);
+ response.write("4 + 4");
+ }
+}
diff --git a/dom/workers/test/WorkerDebugger.console_childWorker.js b/dom/workers/test/WorkerDebugger.console_childWorker.js
new file mode 100644
index 0000000000..8cee6809e5
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.console_childWorker.js
@@ -0,0 +1,3 @@
+"use strict";
+
+self.onmessage = function () {};
diff --git a/dom/workers/test/WorkerDebugger.console_debugger.js b/dom/workers/test/WorkerDebugger.console_debugger.js
new file mode 100644
index 0000000000..a8b2493200
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.console_debugger.js
@@ -0,0 +1,49 @@
+"use strict";
+
+function ok(a, msg) {
+ postMessage(JSON.stringify({ type: "status", what: !!a, msg }));
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+function finish() {
+ postMessage(JSON.stringify({ type: "finish" }));
+}
+
+function magic() {
+ console.log("Hello from the debugger script!");
+
+ var foo = retrieveConsoleEvents();
+ ok(Array.isArray(foo), "We received an array.");
+ ok(foo.length >= 2, "At least 2 messages.");
+
+ is(
+ foo[0].arguments[0],
+ "Can you see this console message?",
+ "First message ok."
+ );
+ is(
+ foo[1].arguments[0],
+ "Can you see this second console message?",
+ "Second message ok."
+ );
+
+ setConsoleEventHandler(function (consoleData) {
+ is(consoleData.arguments[0], "Random message.", "Random message ok!");
+
+ // The consoleEventHandler can be null.
+ setConsoleEventHandler(null);
+
+ finish();
+ });
+}
+
+this.onmessage = function (event) {
+ switch (event.data) {
+ case "do magic":
+ magic();
+ break;
+ }
+};
diff --git a/dom/workers/test/WorkerDebugger.console_worker.js b/dom/workers/test/WorkerDebugger.console_worker.js
new file mode 100644
index 0000000000..a4d6af2e0f
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.console_worker.js
@@ -0,0 +1,10 @@
+"use strict";
+
+console.log("Can you see this console message?");
+console.warn("Can you see this second console message?");
+
+var worker = new Worker("WorkerDebugger.console_childWorker.js");
+
+setInterval(function () {
+ console.log("Random message.");
+}, 200);
diff --git a/dom/workers/test/WorkerDebugger.initialize_childWorker.js b/dom/workers/test/WorkerDebugger.initialize_childWorker.js
new file mode 100644
index 0000000000..a85764bd9c
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.initialize_childWorker.js
@@ -0,0 +1,6 @@
+"use strict";
+
+self.onmessage = function () {};
+
+debugger;
+postMessage("worker");
diff --git a/dom/workers/test/WorkerDebugger.initialize_debugger.js b/dom/workers/test/WorkerDebugger.initialize_debugger.js
new file mode 100644
index 0000000000..f52e95b159
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.initialize_debugger.js
@@ -0,0 +1,6 @@
+"use strict";
+
+var dbg = new Debugger(global);
+dbg.onDebuggerStatement = function (frame) {
+ frame.eval("postMessage('debugger');");
+};
diff --git a/dom/workers/test/WorkerDebugger.initialize_debugger_es_worker.js b/dom/workers/test/WorkerDebugger.initialize_debugger_es_worker.js
new file mode 100644
index 0000000000..d357fca2d3
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.initialize_debugger_es_worker.js
@@ -0,0 +1,10 @@
+"use strict";
+
+// The following check is one possible way to identify
+// if this script is loaded as a ES Module or the classic way.
+const isLoadedAsEsModule = this != globalThis;
+
+// We expect the debugger script to always be loaded as classic
+if (!isLoadedAsEsModule) {
+ postMessage("debugger script ran");
+}
diff --git a/dom/workers/test/WorkerDebugger.initialize_es_worker.js b/dom/workers/test/WorkerDebugger.initialize_es_worker.js
new file mode 100644
index 0000000000..1b9ddda769
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.initialize_es_worker.js
@@ -0,0 +1,10 @@
+"use strict";
+
+// The following check is one possible way to identify
+// if this script is loaded as a ES Module or the classic way.
+const isLoadedAsEsModule = this != globalThis;
+
+// Here we expect the worker to be loaded a ES Module
+if (isLoadedAsEsModule) {
+ postMessage("worker");
+}
diff --git a/dom/workers/test/WorkerDebugger.initialize_worker.js b/dom/workers/test/WorkerDebugger.initialize_worker.js
new file mode 100644
index 0000000000..5a24efd3ac
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.initialize_worker.js
@@ -0,0 +1,9 @@
+"use strict";
+
+var worker = new Worker("WorkerDebugger.initialize_childWorker.js");
+worker.onmessage = function (event) {
+ postMessage("child:" + event.data);
+};
+
+debugger;
+postMessage("worker");
diff --git a/dom/workers/test/WorkerDebugger.postMessage_childWorker.js b/dom/workers/test/WorkerDebugger.postMessage_childWorker.js
new file mode 100644
index 0000000000..8cee6809e5
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.postMessage_childWorker.js
@@ -0,0 +1,3 @@
+"use strict";
+
+self.onmessage = function () {};
diff --git a/dom/workers/test/WorkerDebugger.postMessage_debugger.js b/dom/workers/test/WorkerDebugger.postMessage_debugger.js
new file mode 100644
index 0000000000..14236a4430
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.postMessage_debugger.js
@@ -0,0 +1,9 @@
+"use strict";
+
+this.onmessage = function (event) {
+ switch (event.data) {
+ case "ping":
+ postMessage("pong");
+ break;
+ }
+};
diff --git a/dom/workers/test/WorkerDebugger.postMessage_worker.js b/dom/workers/test/WorkerDebugger.postMessage_worker.js
new file mode 100644
index 0000000000..8ddf6cf865
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.postMessage_worker.js
@@ -0,0 +1,3 @@
+"use strict";
+
+var worker = new Worker("WorkerDebugger.postMessage_childWorker.js");
diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_debugger.js b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_debugger.js
new file mode 100644
index 0000000000..908c9f3161
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_debugger.js
@@ -0,0 +1,9 @@
+"use strict";
+
+const SANDBOX_URL = "WorkerDebuggerGlobalScope.createSandbox_sandbox.js";
+
+var prototype = {
+ self: this,
+};
+var sandbox = createSandbox(SANDBOX_URL, prototype);
+loadSubScript(SANDBOX_URL, sandbox);
diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_sandbox.js b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_sandbox.js
new file mode 100644
index 0000000000..d2de6de924
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_sandbox.js
@@ -0,0 +1,9 @@
+"use strict";
+
+self.addEventListener("message", function (event) {
+ switch (event.data) {
+ case "ping":
+ self.postMessage("pong");
+ break;
+ }
+});
diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_worker.js b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_worker.js
new file mode 100644
index 0000000000..8cee6809e5
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_worker.js
@@ -0,0 +1,3 @@
+"use strict";
+
+self.onmessage = function () {};
diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js
new file mode 100644
index 0000000000..e0b838759e
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js
@@ -0,0 +1,14 @@
+"use strict";
+
+function f() {
+ debugger;
+}
+
+self.onmessage = function (event) {
+ switch (event.data) {
+ case "ping":
+ debugger;
+ postMessage("pong");
+ break;
+ }
+};
diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js
new file mode 100644
index 0000000000..d2a1a21e85
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js
@@ -0,0 +1,29 @@
+"use strict";
+
+var frames = [];
+
+var dbg = new Debugger(global);
+dbg.onDebuggerStatement = function (frame) {
+ frames.push(frame);
+ postMessage("paused");
+ enterEventLoop();
+ frames.pop();
+ postMessage("resumed");
+};
+
+this.onmessage = function (event) {
+ switch (event.data) {
+ case "eval":
+ frames[frames.length - 1].eval("f()");
+ postMessage("evalled");
+ break;
+
+ case "ping":
+ postMessage("pong");
+ break;
+
+ case "resume":
+ leaveEventLoop();
+ break;
+ }
+};
diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js
new file mode 100644
index 0000000000..8108a20289
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js
@@ -0,0 +1,27 @@
+"use strict";
+
+function f() {
+ debugger;
+}
+
+var worker = new Worker(
+ "WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js"
+);
+
+worker.onmessage = function (event) {
+ postMessage("child:" + event.data);
+};
+
+self.onmessage = function (event) {
+ var message = event.data;
+ if (message.includes(":")) {
+ worker.postMessage(message.split(":")[1]);
+ return;
+ }
+ switch (message) {
+ case "ping":
+ debugger;
+ postMessage("pong");
+ break;
+ }
+};
diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.reportError_childWorker.js b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_childWorker.js
new file mode 100644
index 0000000000..0fb4113a24
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_childWorker.js
@@ -0,0 +1,5 @@
+"use strict";
+
+self.onerror = function () {
+ postMessage("error");
+};
diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.reportError_debugger.js b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_debugger.js
new file mode 100644
index 0000000000..9d50dc67cd
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_debugger.js
@@ -0,0 +1,12 @@
+"use strict";
+
+this.onmessage = function (event) {
+ switch (event.data) {
+ case "report":
+ reportError("reported");
+ break;
+ case "throw":
+ throw new Error("thrown");
+ break;
+ }
+};
diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.reportError_worker.js b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_worker.js
new file mode 100644
index 0000000000..67ccfc2ca0
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_worker.js
@@ -0,0 +1,11 @@
+"use strict";
+
+var worker = new Worker("WorkerDebuggerGlobalScope.reportError_childWorker.js");
+
+worker.onmessage = function (event) {
+ postMessage("child:" + event.data);
+};
+
+self.onerror = function () {
+ postMessage("error");
+};
diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_debugger.js b/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_debugger.js
new file mode 100644
index 0000000000..b2a01d380c
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_debugger.js
@@ -0,0 +1,12 @@
+"use strict";
+
+this.onmessage = function (event) {
+ switch (event.data) {
+ case "ping":
+ setImmediate(function () {
+ postMessage("pong1");
+ });
+ postMessage("pong2");
+ break;
+ }
+};
diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_worker.js b/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_worker.js
new file mode 100644
index 0000000000..8cee6809e5
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_worker.js
@@ -0,0 +1,3 @@
+"use strict";
+
+self.onmessage = function () {};
diff --git a/dom/workers/test/WorkerDebuggerManager_childWorker.js b/dom/workers/test/WorkerDebuggerManager_childWorker.js
new file mode 100644
index 0000000000..8cee6809e5
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerManager_childWorker.js
@@ -0,0 +1,3 @@
+"use strict";
+
+self.onmessage = function () {};
diff --git a/dom/workers/test/WorkerDebuggerManager_worker.js b/dom/workers/test/WorkerDebuggerManager_worker.js
new file mode 100644
index 0000000000..0737d17ebc
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerManager_worker.js
@@ -0,0 +1,3 @@
+"use strict";
+
+var worker = new Worker("WorkerDebuggerManager_childWorker.js");
diff --git a/dom/workers/test/WorkerDebugger_childWorker.js b/dom/workers/test/WorkerDebugger_childWorker.js
new file mode 100644
index 0000000000..8cee6809e5
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_childWorker.js
@@ -0,0 +1,3 @@
+"use strict";
+
+self.onmessage = function () {};
diff --git a/dom/workers/test/WorkerDebugger_frozen_window1.html b/dom/workers/test/WorkerDebugger_frozen_window1.html
new file mode 100644
index 0000000000..05d65bbb54
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_frozen_window1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <script>
+ var worker = new Worker("WorkerDebugger_frozen_worker1.js");
+ worker.onmessage = function () {
+ opener.postMessage("ready", "*");
+ };
+ </script>
+ </head>
+ <body>
+ This is page 1.
+ </body>
+<html>
diff --git a/dom/workers/test/WorkerDebugger_frozen_window2.html b/dom/workers/test/WorkerDebugger_frozen_window2.html
new file mode 100644
index 0000000000..3a2445ba54
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_frozen_window2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <script>
+ var worker = new Worker("WorkerDebugger_frozen_worker2.js");
+ worker.onmessage = function () {
+ opener.postMessage("ready", "*");
+ };
+ </script>
+ </head>
+ <body>
+ This is page 2.
+ </body>
+<html>
diff --git a/dom/workers/test/WorkerDebugger_frozen_worker1.js b/dom/workers/test/WorkerDebugger_frozen_worker1.js
new file mode 100644
index 0000000000..371d2c064b
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_frozen_worker1.js
@@ -0,0 +1,5 @@
+"use strict";
+
+onmessage = function () {};
+
+postMessage("ready");
diff --git a/dom/workers/test/WorkerDebugger_frozen_worker2.js b/dom/workers/test/WorkerDebugger_frozen_worker2.js
new file mode 100644
index 0000000000..371d2c064b
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_frozen_worker2.js
@@ -0,0 +1,5 @@
+"use strict";
+
+onmessage = function () {};
+
+postMessage("ready");
diff --git a/dom/workers/test/WorkerDebugger_promise_debugger.js b/dom/workers/test/WorkerDebugger_promise_debugger.js
new file mode 100644
index 0000000000..dd4d1bf2c9
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_promise_debugger.js
@@ -0,0 +1,34 @@
+"use strict";
+
+var self = this;
+
+self.onmessage = function (event) {
+ if (event.data !== "resolve") {
+ return;
+ }
+ // This then-handler should be executed inside the top-level event loop,
+ // within the context of the debugger's global.
+ Promise.resolve().then(function () {
+ var dbg = new Debugger(global);
+ dbg.onDebuggerStatement = function () {
+ self.onmessage = function (e) {
+ if (e.data !== "resume") {
+ return;
+ }
+ // This then-handler should be executed inside the nested event loop,
+ // within the context of the debugger's global.
+ Promise.resolve().then(function () {
+ postMessage("resumed");
+ leaveEventLoop();
+ });
+ };
+ // Test bug 1392540 where DOM Promises from debugger principal
+ // where frozen while hitting a worker breakpoint.
+ Promise.resolve().then(() => {
+ postMessage("paused");
+ });
+ enterEventLoop();
+ };
+ postMessage("resolved");
+ });
+};
diff --git a/dom/workers/test/WorkerDebugger_promise_worker.js b/dom/workers/test/WorkerDebugger_promise_worker.js
new file mode 100644
index 0000000000..4b1042e80b
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_promise_worker.js
@@ -0,0 +1,25 @@
+"use strict";
+
+self.onmessage = function (event) {
+ if (event.data !== "resolve") {
+ return;
+ }
+ // This then-handler should be executed inside the top-level event loop,
+ // within the context of the worker's global.
+ Promise.resolve().then(function () {
+ self.onmessage = function (e) {
+ if (e.data !== "pause") {
+ return;
+ }
+ // This then-handler should be executed inside the top-level event loop,
+ // within the context of the worker's global. Because the debugger
+ // statement here below enters a nested event loop, the then-handler
+ // should not be executed until the debugger statement returns.
+ Promise.resolve().then(function () {
+ postMessage("resumed");
+ });
+ debugger;
+ };
+ postMessage("resolved");
+ });
+};
diff --git a/dom/workers/test/WorkerDebugger_sharedWorker.js b/dom/workers/test/WorkerDebugger_sharedWorker.js
new file mode 100644
index 0000000000..18dfa87783
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_sharedWorker.js
@@ -0,0 +1,17 @@
+"use strict";
+
+self.onconnect = function (event) {
+ event.ports[0].onmessage = function (e) {
+ switch (e.data) {
+ case "close":
+ close();
+ break;
+
+ case "close_loop":
+ close();
+ // Let's loop forever.
+ while (1) {}
+ break;
+ }
+ };
+};
diff --git a/dom/workers/test/WorkerDebugger_suspended_debugger.js b/dom/workers/test/WorkerDebugger_suspended_debugger.js
new file mode 100644
index 0000000000..2ed4e16c44
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_suspended_debugger.js
@@ -0,0 +1,6 @@
+"use strict";
+
+var dbg = new Debugger(global);
+dbg.onDebuggerStatement = function (frame) {
+ postMessage("debugger");
+};
diff --git a/dom/workers/test/WorkerDebugger_suspended_worker.js b/dom/workers/test/WorkerDebugger_suspended_worker.js
new file mode 100644
index 0000000000..c096be7e46
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_suspended_worker.js
@@ -0,0 +1,6 @@
+"use strict";
+
+self.onmessage = function () {
+ postMessage("worker");
+ debugger;
+};
diff --git a/dom/workers/test/WorkerDebugger_worker.js b/dom/workers/test/WorkerDebugger_worker.js
new file mode 100644
index 0000000000..f301ac1e85
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_worker.js
@@ -0,0 +1,8 @@
+"use strict";
+
+var worker = new Worker("WorkerDebugger_childWorker.js");
+self.onmessage = function (event) {
+ postMessage("child:" + event.data);
+};
+debugger;
+postMessage("worker");
diff --git a/dom/workers/test/WorkerTest.jsm b/dom/workers/test/WorkerTest.jsm
new file mode 100644
index 0000000000..702dc9e46a
--- /dev/null
+++ b/dom/workers/test/WorkerTest.jsm
@@ -0,0 +1,15 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+var EXPORTED_SYMBOLS = ["WorkerTest"];
+
+var WorkerTest = {
+ go(message, messageCallback, errorCallback) {
+ let worker = new ChromeWorker("WorkerTest_worker.js");
+ worker.onmessage = messageCallback;
+ worker.onerror = errorCallback;
+ worker.postMessage(message);
+ return worker;
+ },
+};
diff --git a/dom/workers/test/WorkerTest_badworker.js b/dom/workers/test/WorkerTest_badworker.js
new file mode 100644
index 0000000000..5fbd8a5e63
--- /dev/null
+++ b/dom/workers/test/WorkerTest_badworker.js
@@ -0,0 +1 @@
+// doesn't matter what is in here if the URL is bad
diff --git a/dom/workers/test/WorkerTest_subworker.js b/dom/workers/test/WorkerTest_subworker.js
new file mode 100644
index 0000000000..0022fc5046
--- /dev/null
+++ b/dom/workers/test/WorkerTest_subworker.js
@@ -0,0 +1,37 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+onmessage = function (event) {
+ let chromeURL = event.data.replace(
+ "test_chromeWorkerJSM.xhtml",
+ "WorkerTest_badworker.js"
+ );
+
+ let mochitestURL = event.data
+ .replace("test_chromeWorkerJSM.xhtml", "WorkerTest_badworker.js")
+ .replace(
+ "chrome://mochitests/content/chrome",
+ "http://mochi.test:8888/tests"
+ );
+
+ // We should be able to XHR to anything we want, including a chrome URL.
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", mochitestURL, false);
+ xhr.send();
+
+ if (!xhr.responseText) {
+ throw "Can't load script file via XHR!";
+ }
+
+ // We shouldn't be able to make a ChromeWorker to a non-chrome URL.
+ try {
+ new ChromeWorker(mochitestURL);
+ } catch (e) {
+ if (e.name === "SecurityError") {
+ postMessage("Done");
+ return;
+ }
+ }
+ throw "creating a chrome worker with a bad URL should throw a SecurityError";
+};
diff --git a/dom/workers/test/WorkerTest_worker.js b/dom/workers/test/WorkerTest_worker.js
new file mode 100644
index 0000000000..53a380be50
--- /dev/null
+++ b/dom/workers/test/WorkerTest_worker.js
@@ -0,0 +1,11 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+onmessage = function (event) {
+ let worker = new ChromeWorker("WorkerTest_subworker.js");
+ worker.onmessage = function (e) {
+ postMessage(e.data);
+ };
+ worker.postMessage(event.data);
+};
diff --git a/dom/workers/test/atob_worker.js b/dom/workers/test/atob_worker.js
new file mode 100644
index 0000000000..f55ec77e81
--- /dev/null
+++ b/dom/workers/test/atob_worker.js
@@ -0,0 +1,55 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+var data = [
+ -1,
+ 0,
+ 1,
+ 1.5,
+ /* null ,*/ undefined,
+ true,
+ false,
+ "foo",
+ "123456789012345",
+ "1234567890123456",
+ "12345678901234567",
+];
+
+var str = "";
+for (var i = 0; i < 30; i++) {
+ data.push(str);
+ str += i % 2 ? "b" : "a";
+}
+
+onmessage = function (event) {
+ data.forEach(function (string) {
+ var encoded = btoa(string);
+ postMessage({ type: "btoa", value: encoded });
+ postMessage({ type: "atob", value: atob(encoded) });
+ });
+
+ var threw;
+ try {
+ atob();
+ } catch (e) {
+ threw = true;
+ }
+
+ if (!threw) {
+ throw "atob didn't throw when called without an argument!";
+ }
+ threw = false;
+
+ try {
+ btoa();
+ } catch (e) {
+ threw = true;
+ }
+
+ if (!threw) {
+ throw "btoa didn't throw when called without an argument!";
+ }
+
+ postMessage({ type: "done" });
+};
diff --git a/dom/workers/test/blank.html b/dom/workers/test/blank.html
new file mode 100644
index 0000000000..fcbbdb17e9
--- /dev/null
+++ b/dom/workers/test/blank.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Blank</title>
+ </head>
+ <body onload="notifyOnload();">
+ <script type="application/javascript">
+ function notifyOnload() {
+ opener.postMessage({event: 'load'}, '*');
+ }
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/browser.ini b/dom/workers/test/browser.ini
new file mode 100644
index 0000000000..2436cdcafb
--- /dev/null
+++ b/dom/workers/test/browser.ini
@@ -0,0 +1,36 @@
+[DEFAULT]
+support-files =
+ bug1047663_tab.html
+ bug1047663_worker.sjs
+ head.js
+ !/dom/base/test/file_empty.html
+
+[browser_bug1047663.js]
+[browser_bug1104623.js]
+run-if = buildapp == 'browser'
+[browser_consoleSharedWorkers.js]
+support-files = sharedWorker_console.js empty.html
+[browser_fileURL.js]
+support-files = empty.html empty_worker.js
+[browser_worker_use_counters.js]
+support-files =
+ file_use_counter_worker.html
+ file_use_counter_worker.js
+ file_use_counter_shared_worker.js
+ file_use_counter_shared_worker_microtask.js
+ file_use_counter_service_worker.js
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_privilegedmozilla_remoteworker.js]
+support-files =
+ file_service_worker.js
+ file_service_worker_container.html
+[browser_serviceworker_fetch_new_process.js]
+support-files =
+ file_service_worker_fetch_synthetic.js
+ server_fetch_synthetic.sjs
+[browser_WorkerDebugger.initialize.js]
+support-files =
+ WorkerDebugger.initialize_debugger_es_worker.js
+ WorkerDebugger.initialize_es_worker.js
+skip-if = !nightly_build # to be enabled once ES module in workers is enabled (bug 1812591)
diff --git a/dom/workers/test/browser_WorkerDebugger.initialize.js b/dom/workers/test/browser_WorkerDebugger.initialize.js
new file mode 100644
index 0000000000..edc20af4e0
--- /dev/null
+++ b/dom/workers/test/browser_WorkerDebugger.initialize.js
@@ -0,0 +1,54 @@
+/* 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/. */
+
+const wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"].getService(
+ Ci.nsIWorkerDebuggerManager
+);
+
+const BASE_URL = "chrome://mochitests/content/browser/dom/workers/test/";
+const WORKER_URL = BASE_URL + "WorkerDebugger.initialize_es_worker.js";
+const DEBUGGER_URL =
+ BASE_URL + "WorkerDebugger.initialize_debugger_es_worker.js";
+
+add_task(async function test() {
+ const onDbg = waitForRegister(WORKER_URL);
+ const worker = new Worker(WORKER_URL, { type: "module" });
+
+ info("Wait for worker message");
+ await new Promise(resolve => (worker.onmessage = resolve));
+
+ const dbg = await onDbg;
+
+ info("Calling WorkerDebugger::Initialize");
+ const onDebuggerScriptEvaluated = new Promise(resolve => {
+ const listener = {
+ onMessage(msg) {
+ is(msg, "debugger script ran");
+ dbg.removeListener(listener);
+ resolve();
+ },
+ };
+ dbg.addListener(listener);
+ });
+ dbg.initialize(DEBUGGER_URL);
+ ok(true, "dbg.initialize didn't throw");
+
+ info("Waiting for debugger script to be evaluated and dispatching a message");
+ await onDebuggerScriptEvaluated;
+});
+
+function waitForRegister(url, dbgUrl) {
+ return new Promise(function (resolve) {
+ wdm.addListener({
+ onRegister(dbg) {
+ if (dbg.url !== url) {
+ return;
+ }
+ ok(true, "Debugger with url " + url + " should be registered.");
+ wdm.removeListener(this);
+ resolve(dbg);
+ },
+ });
+ });
+}
diff --git a/dom/workers/test/browser_bug1047663.js b/dom/workers/test/browser_bug1047663.js
new file mode 100644
index 0000000000..f55f1152f6
--- /dev/null
+++ b/dom/workers/test/browser_bug1047663.js
@@ -0,0 +1,56 @@
+/* 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/. */
+"use strict";
+
+const EXAMPLE_URL = "https://example.com/browser/dom/workers/test/";
+const TAB_URL = EXAMPLE_URL + "bug1047663_tab.html";
+const WORKER_URL = EXAMPLE_URL + "bug1047663_worker.sjs";
+
+function test() {
+ waitForExplicitFinish();
+
+ (async function () {
+ // Disable rcwn to make cache behavior deterministic.
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.http.rcwn.enabled", false]],
+ });
+
+ let tab = await addTab(TAB_URL);
+
+ // Create a worker. Post a message to it, and check the reply. Since the
+ // server side JavaScript file returns the first source for the first
+ // request, the reply should be "one". If the reply is correct, terminate
+ // the worker.
+ await createWorkerInTab(tab, WORKER_URL);
+ let message = await postMessageToWorkerInTab(tab, WORKER_URL, "ping");
+ is(message, "one");
+ await terminateWorkerInTab(tab, WORKER_URL);
+
+ // Create a second worker with the same URL. Post a message to it, and check
+ // the reply. The server side JavaScript file returns the second source for
+ // all subsequent requests, but since the cache is still enabled, the reply
+ // should still be "one". If the reply is correct, terminate the worker.
+ await createWorkerInTab(tab, WORKER_URL);
+ message = await postMessageToWorkerInTab(tab, WORKER_URL, "ping");
+ is(message, "one");
+ await terminateWorkerInTab(tab, WORKER_URL);
+
+ // Disable the cache in this tab. This should also disable the cache for all
+ // workers in this tab.
+ await disableCacheInTab(tab);
+
+ // Create a third worker with the same URL. Post a message to it, and check
+ // the reply. Since the server side JavaScript file returns the second
+ // source for all subsequent requests, and the cache is now disabled, the
+ // reply should now be "two". If the reply is correct, terminate the worker.
+ await createWorkerInTab(tab, WORKER_URL);
+ message = await postMessageToWorkerInTab(tab, WORKER_URL, "ping");
+ is(message, "two");
+ await terminateWorkerInTab(tab, WORKER_URL);
+
+ removeTab(tab);
+
+ finish();
+ })();
+}
diff --git a/dom/workers/test/browser_bug1104623.js b/dom/workers/test/browser_bug1104623.js
new file mode 100644
index 0000000000..7dc421b873
--- /dev/null
+++ b/dom/workers/test/browser_bug1104623.js
@@ -0,0 +1,60 @@
+/* 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/. */
+
+function whenBrowserLoaded(aBrowser, aCallback) {
+ aBrowser.addEventListener(
+ "load",
+ function onLoad(event) {
+ if (event.target == aBrowser.contentDocument) {
+ aBrowser.removeEventListener("load", onLoad, true);
+ executeSoon(aCallback);
+ }
+ },
+ true
+ );
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ let testURL =
+ "chrome://mochitests/content/chrome/dom/base/test/file_empty.html";
+
+ let tab = BrowserTestUtils.addTab(gBrowser, testURL);
+ gBrowser.selectedTab = tab;
+
+ whenBrowserLoaded(tab.linkedBrowser, function () {
+ let doc = tab.linkedBrowser.contentDocument;
+ let contentWin = tab.linkedBrowser.contentWindow;
+
+ let blob = new contentWin.Blob([
+ "onmessage = function() { postMessage(true); }",
+ ]);
+ ok(blob, "Blob has been created");
+
+ let blobURL = contentWin.URL.createObjectURL(blob);
+ ok(blobURL, "Blob URL has been created");
+
+ let worker = new contentWin.Worker(blobURL);
+ ok(worker, "Worker has been created");
+
+ worker.onerror = function (error) {
+ ok(false, "Worker.onerror:" + error.message);
+ worker.terminate();
+ contentWin.URL.revokeObjectURL(blob);
+ gBrowser.removeTab(tab);
+ executeSoon(finish);
+ };
+
+ worker.onmessage = function () {
+ ok(true, "Worker.onmessage");
+ worker.terminate();
+ contentWin.URL.revokeObjectURL(blob);
+ gBrowser.removeTab(tab);
+ executeSoon(finish);
+ };
+
+ worker.postMessage(true);
+ });
+}
diff --git a/dom/workers/test/browser_consoleSharedWorkers.js b/dom/workers/test/browser_consoleSharedWorkers.js
new file mode 100644
index 0000000000..a8f9b79760
--- /dev/null
+++ b/dom/workers/test/browser_consoleSharedWorkers.js
@@ -0,0 +1,70 @@
+/* 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/. */
+
+add_task(async function test() {
+ const testURL = getRootDirectory(gTestPath) + "empty.html";
+ let tab = BrowserTestUtils.addTab(gBrowser, testURL);
+ gBrowser.selectedTab = tab;
+
+ await BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab));
+
+ let promise = new Promise(resolve => {
+ const ConsoleAPIStorage = SpecialPowers.Cc[
+ "@mozilla.org/consoleAPI-storage;1"
+ ].getService(SpecialPowers.Ci.nsIConsoleAPIStorage);
+
+ function consoleListener() {
+ this.onConsoleLogEvent = this.onConsoleLogEvent.bind(this);
+ ConsoleAPIStorage.addLogEventListener(
+ this.onConsoleLogEvent,
+ SpecialPowers.wrap(document).nodePrincipal
+ );
+ Services.obs.addObserver(this, "console-api-profiler");
+ }
+
+ var order = 0;
+ consoleListener.prototype = {
+ onConsoleLogEvent(aSubject) {
+ var obj = aSubject.wrappedJSObject;
+ is(
+ obj.arguments[0],
+ "Hello world from a SharedWorker!",
+ "A message from a SharedWorker \\o/"
+ );
+ is(obj.ID, "sharedWorker_console.js", "The ID is SharedWorker");
+ is(obj.innerID, "SharedWorker", "The ID is SharedWorker");
+ is(order++, 1, "Then a log message.");
+
+ ConsoleAPIStorage.removeLogEventListener(this.onConsoleLogEvent);
+ resolve();
+ },
+
+ observe: (aSubject, aTopic) => {
+ ok(true, "Something has been received");
+
+ if (aTopic == "console-api-profiler") {
+ var obj = aSubject.wrappedJSObject;
+ is(
+ obj.arguments[0],
+ "Hello profiling from a SharedWorker!",
+ "A message from a SharedWorker \\o/"
+ );
+ is(order++, 0, "First a profiler message.");
+
+ Services.obs.removeObserver(cl, "console-api-profiler");
+ }
+ },
+ };
+
+ var cl = new consoleListener();
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ new content.SharedWorker("sharedWorker_console.js");
+ });
+
+ await promise;
+
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/dom/workers/test/browser_fileURL.js b/dom/workers/test/browser_fileURL.js
new file mode 100644
index 0000000000..9891bdcba9
--- /dev/null
+++ b/dom/workers/test/browser_fileURL.js
@@ -0,0 +1,73 @@
+/* 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/. */
+"use strict";
+
+const EMPTY_URL = "/browser/dom/workers/test/empty.html";
+const WORKER_URL = "/browser/dom/workers/test/empty_worker.js";
+
+add_task(async function () {
+ let tab = BrowserTestUtils.addTab(
+ gBrowser,
+ "http://mochi.test:8888" + EMPTY_URL
+ );
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(
+ browser,
+ ["http://example.org" + WORKER_URL],
+ function (spec) {
+ return new content.Promise((resolve, reject) => {
+ let w = new content.window.Worker(spec);
+ w.onerror = _ => {
+ resolve();
+ };
+ w.onmessage = _ => {
+ reject();
+ };
+ });
+ }
+ );
+ ok(
+ true,
+ "The worker is not loaded when the script is from different origin."
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function () {
+ let tab = BrowserTestUtils.addTab(
+ gBrowser,
+ "https://example.org" + EMPTY_URL
+ );
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(
+ browser,
+ ["http://example.org" + WORKER_URL],
+ function (spec) {
+ return new content.Promise((resolve, reject) => {
+ let w = new content.window.Worker(spec);
+ w.onerror = _ => {
+ resolve();
+ };
+ w.onmessage = _ => {
+ reject();
+ };
+ });
+ }
+ );
+ ok(
+ true,
+ "The worker is not loaded when the script is from different origin."
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/dom/workers/test/browser_privilegedmozilla_remoteworker.js b/dom/workers/test/browser_privilegedmozilla_remoteworker.js
new file mode 100644
index 0000000000..573cae5e61
--- /dev/null
+++ b/dom/workers/test/browser_privilegedmozilla_remoteworker.js
@@ -0,0 +1,116 @@
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", true],
+ ["browser.tabs.remote.separatedMozillaDomains", "example.org"],
+ ["dom.ipc.processCount.web", 1],
+ ["dom.ipc.processCount.privilegedmozilla", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ });
+});
+
+// This test attempts to verify proper placement of spawned remoteworkers
+// by spawning them and then verifying that they were spawned in the expected
+// process by way of nsIWorkerDebuggerManager enumeration.
+//
+// Unfortunately, there's no other way to introspect where a worker global was
+// spawned at this time. (devtools just ends up enumerating all workers in all
+// processes and we don't want to depend on devtools in this test).
+//
+// As a result, this test currently only tests situations where it's known that
+// a remote worker will be spawned in the same process that is initiating its
+// spawning.
+//
+// This should be enhanced in the future.
+add_task(async function test_serviceworker() {
+ const basePath = "browser/dom/workers/test";
+ const pagePath = `${basePath}/file_service_worker_container.html`;
+ const scriptPath = `${basePath}/file_service_worker.js`;
+
+ Services.ppmm.releaseCachedProcesses();
+
+ async function runWorkerInProcess() {
+ function getActiveWorkerURLs() {
+ const wdm = Cc[
+ "@mozilla.org/dom/workers/workerdebuggermanager;1"
+ ].getService(Ci.nsIWorkerDebuggerManager);
+
+ const workerDebuggerUrls = Array.from(
+ wdm.getWorkerDebuggerEnumerator()
+ ).map(wd => {
+ return wd.url;
+ });
+
+ return workerDebuggerUrls;
+ }
+
+ return new Promise(resolve => {
+ content.navigator.serviceWorker.ready.then(({ active }) => {
+ const { port1, port2 } = new content.MessageChannel();
+ active.postMessage("webpage->serviceworker", [port2]);
+ port1.onmessage = evt => {
+ resolve({
+ msg: evt.data,
+ workerUrls: getActiveWorkerURLs(),
+ });
+ };
+ });
+ }).then(async res => {
+ // Unregister the service worker used in this test.
+ const registration = await content.navigator.serviceWorker.ready;
+ await registration.unregister();
+ return res;
+ });
+ }
+
+ const testCaseList = [
+ // TODO: find a reasonable way to test the non-privileged scenario
+ // (because more than 1 process is usually available and the worker
+ // can be launched in a different one from the one where the tab
+ // is running).
+ /*{
+ remoteType: "web",
+ hostname: "example.com",
+ },*/
+ {
+ remoteType: "privilegedmozilla",
+ hostname: `example.org`,
+ },
+ ];
+
+ for (const testCase of testCaseList) {
+ const { remoteType, hostname } = testCase;
+
+ info(`Test remote serviceworkers launch selects a ${remoteType} process`);
+
+ const tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: `https://${hostname}/${pagePath}`,
+ });
+
+ is(
+ tab.linkedBrowser.remoteType,
+ remoteType,
+ `Got the expected remoteType for ${hostname} tab`
+ );
+
+ const results = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ runWorkerInProcess
+ );
+
+ Assert.deepEqual(
+ results,
+ {
+ msg: "serviceworker-reply",
+ workerUrls: [`https://${hostname}/${scriptPath}`],
+ },
+ `Got the expected results for ${hostname} tab`
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ }
+});
diff --git a/dom/workers/test/browser_serviceworker_fetch_new_process.js b/dom/workers/test/browser_serviceworker_fetch_new_process.js
new file mode 100644
index 0000000000..801c505387
--- /dev/null
+++ b/dom/workers/test/browser_serviceworker_fetch_new_process.js
@@ -0,0 +1,405 @@
+const DIRPATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ ""
+);
+
+/**
+ * We choose blob contents that will roundtrip cleanly through the `textContent`
+ * of our returned HTML page.
+ */
+const TEST_BLOB_CONTENTS = `I'm a disk-backed test blob! Hooray!`;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Set preferences so that opening a page with the origin "example.org"
+ // will result in a remoteType of "privilegedmozilla" for both the
+ // page and the ServiceWorker.
+ ["browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", true],
+ ["browser.tabs.remote.separatedMozillaDomains", "example.org"],
+ ["dom.ipc.processCount.privilegedmozilla", 1],
+ ["dom.ipc.processPrelaunch.enabled", false],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ // ServiceWorker worker instances should stay alive until explicitly
+ // caused to terminate by dropping these timeouts to 0 in
+ // `waitForWorkerAndProcessShutdown`.
+ ["dom.serviceWorkers.idle_timeout", 299999],
+ ["dom.serviceWorkers.idle_extended_timeout", 299999],
+ ],
+ });
+});
+
+function countRemoteType(remoteType) {
+ return ChromeUtils.getAllDOMProcesses().filter(
+ p => p.remoteType == remoteType
+ ).length;
+}
+
+/**
+ * Helper function to get a list of all current processes and their remote
+ * types. Note that when in used in a templated literal that it is
+ * synchronously invoked when the string is evaluated and captures system state
+ * at that instant.
+ */
+function debugRemotes() {
+ return ChromeUtils.getAllDOMProcesses()
+ .map(p => p.remoteType || "parent")
+ .join(",");
+}
+
+/**
+ * Wait for there to be zero processes of the given remoteType. This check is
+ * considered successful if there are already no processes of the given type
+ * at this very moment.
+ */
+async function waitForNoProcessesOfType(remoteType) {
+ info(`waiting for there to be no ${remoteType} procs`);
+ await TestUtils.waitForCondition(
+ () => countRemoteType(remoteType) == 0,
+ "wait for the worker's process to shutdown"
+ );
+}
+
+/**
+ * Given a ServiceWorkerRegistrationInfo with an active ServiceWorker that
+ * has no active ExtendableEvents but would otherwise continue running thanks
+ * to the idle keepalive:
+ * - Assert that there is a ServiceWorker instance in the given registration's
+ * active slot. (General invariant check.)
+ * - Assert that a single process with the given remoteType currently exists.
+ * (This doesn't mean the SW is alive in that process, though this test
+ * verifies that via other checks when appropriate.)
+ * - Induce the worker to shutdown by temporarily dropping the idle timeout to 0
+ * and causing the idle timer to be reset due to rapid debugger attach/detach.
+ * - Wait for the the single process with the given remoteType to go away.
+ * - Reset the idle timeouts back to their previous high values.
+ */
+async function waitForWorkerAndProcessShutdown(swRegInfo, remoteType) {
+ info(`terminating worker and waiting for ${remoteType} procs to shut down`);
+ ok(swRegInfo.activeWorker, "worker should be in the active slot");
+ is(
+ countRemoteType(remoteType),
+ 1,
+ `should have a single ${remoteType} process but have: ${debugRemotes()}`
+ );
+
+ // Let's not wait too long for the process to shutdown.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.idle_timeout", 0],
+ ["dom.serviceWorkers.idle_extended_timeout", 0],
+ ],
+ });
+
+ // We need to cause the worker to re-evaluate its idle timeout. The easiest
+ // way to do this I could think of is to attach and then detach the debugger
+ // from the active worker.
+ swRegInfo.activeWorker.attachDebugger();
+ await new Promise(resolve => Cu.dispatch(resolve));
+ swRegInfo.activeWorker.detachDebugger();
+
+ // Eventually the length will reach 0, meaning we're done!
+ await waitForNoProcessesOfType(remoteType);
+
+ is(
+ countRemoteType(remoteType),
+ 0,
+ `processes with remoteType=${remoteType} type should have shut down`
+ );
+
+ // Make sure we never kill workers on idle except when this is called.
+ await SpecialPowers.popPrefEnv();
+}
+
+async function do_test_sw(host, remoteType, swMode, fileBlob) {
+ info(
+ `### entering test: host=${host}, remoteType=${remoteType}, mode=${swMode}`
+ );
+
+ const prin = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI(`https://${host}`),
+ {}
+ );
+ const sw = `https://${host}/${DIRPATH}file_service_worker_fetch_synthetic.js`;
+ const scope = `https://${host}/${DIRPATH}server_fetch_synthetic.sjs`;
+
+ const swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+ Ci.nsIServiceWorkerManager
+ );
+ const swRegInfo = await swm.registerForTest(prin, scope, sw);
+ swRegInfo.QueryInterface(Ci.nsIServiceWorkerRegistrationInfo);
+
+ info(
+ `service worker registered: ${JSON.stringify({
+ principal: swRegInfo.principal.spec,
+ scope: swRegInfo.scope,
+ })}`
+ );
+
+ // Wait for the worker to install & shut down.
+ await TestUtils.waitForCondition(
+ () => swRegInfo.activeWorker,
+ "wait for the worker to become active"
+ );
+ await waitForWorkerAndProcessShutdown(swRegInfo, remoteType);
+
+ info(
+ `test navigation interception with mode=${swMode} starting from about:blank`
+ );
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "about:blank",
+ },
+ async browser => {
+ // NOTE: We intentionally trigger the navigation from content in order to
+ // make sure frontend doesn't eagerly process-switch for us.
+ SpecialPowers.spawn(
+ browser,
+ [scope, swMode, fileBlob],
+ // eslint-disable-next-line no-shadow
+ async (scope, swMode, fileBlob) => {
+ const pageUrl = `${scope}?mode=${swMode}`;
+ if (!fileBlob) {
+ content.location.href = pageUrl;
+ } else {
+ const doc = content.document;
+ const formElem = doc.createElement("form");
+ doc.body.appendChild(formElem);
+
+ formElem.action = pageUrl;
+ formElem.method = "POST";
+ formElem.enctype = "multipart/form-data";
+
+ const fileElem = doc.createElement("input");
+ formElem.appendChild(fileElem);
+
+ fileElem.type = "file";
+ fileElem.name = "foo";
+
+ fileElem.mozSetFileArray([fileBlob]);
+
+ formElem.submit();
+ }
+ }
+ );
+
+ await BrowserTestUtils.browserLoaded(browser);
+
+ is(
+ countRemoteType(remoteType),
+ 1,
+ `should have spawned a content process with remoteType=${remoteType}`
+ );
+
+ const { source, blobContents } = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return {
+ source: content.document.getElementById("source").textContent,
+ blobContents: content.document.getElementById("blob").textContent,
+ };
+ }
+ );
+
+ is(
+ source,
+ swMode === "synthetic" ? "ServiceWorker" : "ServerJS",
+ "The page contents should come from the right place."
+ );
+
+ is(
+ blobContents,
+ fileBlob ? TEST_BLOB_CONTENTS : "",
+ "The request blob contents should be the blob/empty as appropriate."
+ );
+
+ // Ensure the worker was loaded in this process.
+ const workerDebuggerURLs = await SpecialPowers.spawn(
+ browser,
+ [sw],
+ async url => {
+ if (!content.navigator.serviceWorker.controller) {
+ throw new Error("document not controlled!");
+ }
+ const wdm = Cc[
+ "@mozilla.org/dom/workers/workerdebuggermanager;1"
+ ].getService(Ci.nsIWorkerDebuggerManager);
+
+ return Array.from(wdm.getWorkerDebuggerEnumerator())
+ .map(wd => {
+ return wd.url;
+ })
+ .filter(swURL => swURL == url);
+ }
+ );
+ if (remoteType.startsWith("webServiceWorker=")) {
+ Assert.notDeepEqual(
+ workerDebuggerURLs,
+ [sw],
+ "Isolated workers should not be running in the content child process"
+ );
+ } else {
+ Assert.deepEqual(
+ workerDebuggerURLs,
+ [sw],
+ "The worker should be running in the correct child process"
+ );
+ }
+
+ // Unregister the ServiceWorker. The registration will continue to control
+ // `browser` and therefore continue to exist and its worker to continue
+ // running until the tab is closed.
+ await SpecialPowers.spawn(browser, [], async () => {
+ let registration = await content.navigator.serviceWorker.ready;
+ await registration.unregister();
+ });
+ }
+ );
+
+ // Now that the controlled tab is closed and the registration has been
+ // removed, the ServiceWorker will be made redundant which will forcibly
+ // terminate it, which will result in the shutdown of the given content
+ // process. Wait for that to happen both as a verification and so the next
+ // test has a sufficiently clean slate.
+ await waitForNoProcessesOfType(remoteType);
+}
+
+/**
+ * Create a File-backed blob. This will happen synchronously from the main
+ * thread, which isn't optimal, but the test blocks on this progress anyways.
+ * Bug 1669578 has been filed on improving this idiom and avoiding the sync
+ * writes.
+ */
+async function makeFileBlob(blobContents) {
+ const tmpFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ tmpFile.append("test-file-backed-blob.txt");
+ tmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+ var outStream = Cc[
+ "@mozilla.org/network/file-output-stream;1"
+ ].createInstance(Ci.nsIFileOutputStream);
+ outStream.init(
+ tmpFile,
+ 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666,
+ 0
+ );
+ outStream.write(blobContents, blobContents.length);
+ outStream.close();
+
+ const fileBlob = await File.createFromNsIFile(tmpFile);
+ return fileBlob;
+}
+
+function getSWTelemetrySums() {
+ let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(
+ Ci.nsITelemetry
+ );
+ let keyedhistograms = telemetry.getSnapshotForKeyedHistograms(
+ "main",
+ false
+ ).parent;
+ let keyedscalars = telemetry.getSnapshotForKeyedScalars("main", false).parent;
+ // We're not looking at the distribution of the histograms, just that they changed
+ return {
+ SERVICE_WORKER_RUNNING_All: keyedhistograms.SERVICE_WORKER_RUNNING
+ ? keyedhistograms.SERVICE_WORKER_RUNNING.All.sum
+ : 0,
+ SERVICE_WORKER_RUNNING_Fetch: keyedhistograms.SERVICE_WORKER_RUNNING
+ ? keyedhistograms.SERVICE_WORKER_RUNNING.Fetch.sum
+ : 0,
+ SERVICEWORKER_RUNNING_MAX_All: keyedscalars["serviceworker.running_max"]
+ ? keyedscalars["serviceworker.running_max"].All
+ : 0,
+ SERVICEWORKER_RUNNING_MAX_Fetch: keyedscalars["serviceworker.running_max"]
+ ? keyedscalars["serviceworker.running_max"].Fetch
+ : 0,
+ };
+}
+
+add_task(async function test() {
+ // Can't test telemetry without this since we may not be on the nightly channel
+ let oldCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+ registerCleanupFunction(() => {
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ });
+
+ let initialSums = getSWTelemetrySums();
+
+ // ## Isolated Privileged Process
+ // Trigger a straightforward intercepted navigation with no request body that
+ // returns a synthetic response.
+ await do_test_sw("example.org", "privilegedmozilla", "synthetic", null);
+
+ // Trigger an intercepted navigation with FormData containing an
+ // <input type="file"> which will result in the request body containing a
+ // RemoteLazyInputStream which will be consumed in the content process by the
+ // ServiceWorker while generating the synthetic response.
+ const fileBlob = await makeFileBlob(TEST_BLOB_CONTENTS);
+ await do_test_sw("example.org", "privilegedmozilla", "synthetic", fileBlob);
+
+ // Trigger an intercepted navigation with FormData containing an
+ // <input type="file"> which will result in the request body containing a
+ // RemoteLazyInputStream which will be relayed back to the parent process
+ // via direct invocation of fetch() on the event.request but without any
+ // cloning.
+ await do_test_sw("example.org", "privilegedmozilla", "fetch", fileBlob);
+
+ // Same as the above but cloning the request before fetching it.
+ await do_test_sw("example.org", "privilegedmozilla", "clone", fileBlob);
+
+ // ## Fission Isolation
+ if (Services.appinfo.fissionAutostart) {
+ // ## ServiceWorker isolation
+ const isolateUrl = "example.com";
+ const isolateRemoteType = `webServiceWorker=https://` + isolateUrl;
+ await do_test_sw(isolateUrl, isolateRemoteType, "synthetic", null);
+ await do_test_sw(isolateUrl, isolateRemoteType, "synthetic", fileBlob);
+ }
+ let telemetrySums = getSWTelemetrySums();
+ info(JSON.stringify(telemetrySums));
+ info(
+ "Initial Running All: " +
+ initialSums.SERVICE_WORKER_RUNNING_All +
+ ", Fetch: " +
+ initialSums.SERVICE_WORKER_RUNNING_Fetch
+ );
+ info(
+ "Initial Max Running All: " +
+ initialSums.SERVICEWORKER_RUNNING_MAX_All +
+ ", Fetch: " +
+ initialSums.SERVICEWORKER_RUNNING_MAX_Fetch
+ );
+ info(
+ "Running All: " +
+ telemetrySums.SERVICE_WORKER_RUNNING_All +
+ ", Fetch: " +
+ telemetrySums.SERVICE_WORKER_RUNNING_Fetch
+ );
+ info(
+ "Max Running All: " +
+ telemetrySums.SERVICEWORKER_RUNNING_MAX_All +
+ ", Fetch: " +
+ telemetrySums.SERVICEWORKER_RUNNING_MAX_Fetch
+ );
+ ok(
+ telemetrySums.SERVICE_WORKER_RUNNING_All >
+ initialSums.SERVICE_WORKER_RUNNING_All,
+ "ServiceWorker running count changed"
+ );
+ ok(
+ telemetrySums.SERVICE_WORKER_RUNNING_Fetch >
+ initialSums.SERVICE_WORKER_RUNNING_Fetch,
+ "ServiceWorker running count changed"
+ );
+ // We don't use ok()'s for MAX because MAX may have been set before we
+ // set canRecordExtended, and if so we won't record a new value unless
+ // the max increases again.
+});
diff --git a/dom/workers/test/browser_worker_use_counters.js b/dom/workers/test/browser_worker_use_counters.js
new file mode 100644
index 0000000000..6f115916d8
--- /dev/null
+++ b/dom/workers/test/browser_worker_use_counters.js
@@ -0,0 +1,176 @@
+/* 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/. */
+"use strict";
+
+const gHttpTestRoot = "https://example.com/browser/dom/workers/test/";
+
+function grabHistogramsFromContent(
+ use_counter_name,
+ worker_type,
+ counter_before = null
+) {
+ let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(
+ Ci.nsITelemetry
+ );
+ let gather = () => {
+ let snapshots;
+ if (Services.appinfo.browserTabsRemoteAutostart) {
+ snapshots = telemetry.getSnapshotForHistograms("main", false).content;
+ } else {
+ snapshots = telemetry.getSnapshotForHistograms("main", false).parent;
+ }
+ let checkedGet = probe => {
+ return snapshots[probe] ? snapshots[probe].sum : 0;
+ };
+ return [
+ checkedGet(`USE_COUNTER2_${use_counter_name}_${worker_type}_WORKER`),
+ checkedGet(`${worker_type}_WORKER_DESTROYED`),
+ ];
+ };
+ return BrowserTestUtils.waitForCondition(() => {
+ return counter_before != gather()[0];
+ }).then(gather, gather);
+}
+
+var check_use_counter_worker = async function (
+ use_counter_name,
+ worker_type,
+ content_task
+) {
+ info(`checking ${use_counter_name} use counters for ${worker_type} worker`);
+
+ let newTab = BrowserTestUtils.addTab(gBrowser, "about:blank");
+ gBrowser.selectedTab = newTab;
+ newTab.linkedBrowser.stop();
+
+ // Hold on to the current values of the telemetry histograms we're
+ // interested in.
+ let [histogram_before, destructions_before] = await grabHistogramsFromContent(
+ use_counter_name,
+ worker_type
+ );
+
+ BrowserTestUtils.loadURIString(
+ gBrowser.selectedBrowser,
+ gHttpTestRoot + "file_use_counter_worker.html"
+ );
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ await content_task(gBrowser.selectedBrowser);
+
+ // Tear down the page.
+ let tabClosed = BrowserTestUtils.waitForTabClosing(newTab);
+ gBrowser.removeTab(newTab);
+ await tabClosed;
+
+ // Grab histograms again and compare.
+ let [histogram_after, destructions_after] = await grabHistogramsFromContent(
+ use_counter_name,
+ worker_type,
+ histogram_before
+ );
+
+ is(
+ histogram_after,
+ histogram_before + 1,
+ `histogram ${use_counter_name} counts for ${worker_type} worker are correct`
+ );
+ // There might be other workers created by prior tests get destroyed during
+ // this tests.
+ ok(
+ destructions_after > destructions_before,
+ `${worker_type} worker counts are correct`
+ );
+};
+
+add_task(async function test_dedicated_worker() {
+ await check_use_counter_worker("CONSOLE_LOG", "DEDICATED", async browser => {
+ await ContentTask.spawn(browser, {}, function () {
+ return new Promise(resolve => {
+ let worker = new content.Worker("file_use_counter_worker.js");
+ worker.onmessage = function (e) {
+ if (e.data === "DONE") {
+ worker.terminate();
+ resolve();
+ }
+ };
+ });
+ });
+ });
+});
+
+add_task(async function test_shared_worker() {
+ await check_use_counter_worker("CONSOLE_LOG", "SHARED", async browser => {
+ await ContentTask.spawn(browser, {}, function () {
+ return new Promise(resolve => {
+ let worker = new content.SharedWorker(
+ "file_use_counter_shared_worker.js"
+ );
+ worker.port.onmessage = function (e) {
+ if (e.data === "DONE") {
+ resolve();
+ }
+ };
+ worker.port.postMessage("RUN");
+ });
+ });
+ });
+});
+
+add_task(async function test_shared_worker_microtask() {
+ await check_use_counter_worker("CONSOLE_LOG", "SHARED", async browser => {
+ await ContentTask.spawn(browser, {}, function () {
+ return new Promise(resolve => {
+ let worker = new content.SharedWorker(
+ "file_use_counter_shared_worker_microtask.js"
+ );
+ worker.port.onmessage = function (e) {
+ if (e.data === "DONE") {
+ resolve();
+ }
+ };
+ worker.port.postMessage("RUN");
+ });
+ });
+ });
+});
+
+add_task(async function test_service_worker() {
+ await check_use_counter_worker("CONSOLE_LOG", "SERVICE", async browser => {
+ await ContentTask.spawn(browser, {}, function () {
+ let waitForActivated = async function (registration) {
+ return new Promise(resolve => {
+ let worker =
+ registration.installing ||
+ registration.waiting ||
+ registration.active;
+ if (worker.state === "activated") {
+ resolve(worker);
+ return;
+ }
+
+ worker.addEventListener("statechange", function onStateChange() {
+ if (worker.state === "activated") {
+ worker.removeEventListener("statechange", onStateChange);
+ resolve(worker);
+ }
+ });
+ });
+ };
+
+ return new Promise(resolve => {
+ content.navigator.serviceWorker
+ .register("file_use_counter_service_worker.js")
+ .then(async registration => {
+ content.navigator.serviceWorker.onmessage = function (e) {
+ if (e.data === "DONE") {
+ registration.unregister().then(resolve);
+ }
+ };
+ let worker = await waitForActivated(registration);
+ worker.postMessage("RUN");
+ });
+ });
+ });
+ });
+});
diff --git a/dom/workers/test/bug1014466_data1.txt b/dom/workers/test/bug1014466_data1.txt
new file mode 100644
index 0000000000..a32a4347a4
--- /dev/null
+++ b/dom/workers/test/bug1014466_data1.txt
@@ -0,0 +1 @@
+1234567890
diff --git a/dom/workers/test/bug1014466_data2.txt b/dom/workers/test/bug1014466_data2.txt
new file mode 100644
index 0000000000..4d40154cef
--- /dev/null
+++ b/dom/workers/test/bug1014466_data2.txt
@@ -0,0 +1 @@
+ABCDEFGH
diff --git a/dom/workers/test/bug1014466_worker.js b/dom/workers/test/bug1014466_worker.js
new file mode 100644
index 0000000000..2161954d2b
--- /dev/null
+++ b/dom/workers/test/bug1014466_worker.js
@@ -0,0 +1,63 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function ok(a, msg) {
+ postMessage({ type: "status", status: !!a, msg });
+}
+
+onmessage = function (event) {
+ function getResponse(url) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url, false);
+ xhr.send();
+ return xhr.responseText;
+ }
+
+ const testFile1 = "bug1014466_data1.txt";
+ const testFile2 = "bug1014466_data2.txt";
+ const testData1 = getResponse(testFile1);
+ const testData2 = getResponse(testFile2);
+
+ var response_count = 0;
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState == xhr.DONE && xhr.status == 200) {
+ response_count++;
+ switch (response_count) {
+ case 1:
+ ok(xhr.responseText == testData1, "Check data 1");
+ test_data2();
+ break;
+ case 2:
+ ok(xhr.responseText == testData2, "Check data 2");
+ postMessage({ type: "finish" });
+ break;
+ default:
+ ok(false, "Unexpected response received");
+ postMessage({ type: "finish" });
+ break;
+ }
+ }
+ };
+ xhr.onerror = function (e) {
+ ok(false, "Got an error event: " + e);
+ postMessage({ type: "finish" });
+ };
+
+ function test_data1() {
+ xhr.open("GET", testFile1, true);
+ xhr.responseType = "text";
+ xhr.send();
+ }
+
+ function test_data2() {
+ xhr.abort();
+ xhr.open("GET", testFile2, true);
+ xhr.responseType = "text";
+ xhr.send();
+ }
+
+ test_data1();
+};
diff --git a/dom/workers/test/bug1020226_frame.html b/dom/workers/test/bug1020226_frame.html
new file mode 100644
index 0000000000..039dc9480a
--- /dev/null
+++ b/dom/workers/test/bug1020226_frame.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1020226
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1020226</title>
+</head>
+<body>
+
+<script type="application/javascript">
+ var worker = new Worker("bug1020226_worker.js");
+ worker.onmessage = function(e) {
+ window.parent.postMessage("loaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/bug1020226_worker.js b/dom/workers/test/bug1020226_worker.js
new file mode 100644
index 0000000000..c09abdd7de
--- /dev/null
+++ b/dom/workers/test/bug1020226_worker.js
@@ -0,0 +1,12 @@
+var p = new Promise(function (resolve, reject) {
+ // This causes a runnable to be queued.
+ reject(new Error());
+ postMessage("loaded");
+
+ // This prevents that runnable from running until the window calls terminate(),
+ // at which point the worker goes into the Canceling state and then an
+ // HoldWorker() is attempted, which fails, which used to result in
+ // multiple calls to the error reporter, one after the worker's context had
+ // been GCed.
+ while (true) {}
+});
diff --git a/dom/workers/test/bug1047663_tab.html b/dom/workers/test/bug1047663_tab.html
new file mode 100644
index 0000000000..62ab9be7d2
--- /dev/null
+++ b/dom/workers/test/bug1047663_tab.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/dom/workers/test/bug1047663_worker.sjs b/dom/workers/test/bug1047663_worker.sjs
new file mode 100644
index 0000000000..169ca05f89
--- /dev/null
+++ b/dom/workers/test/bug1047663_worker.sjs
@@ -0,0 +1,41 @@
+/* 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/. */
+"use strict";
+
+const WORKER_1 = `
+ "use strict";
+
+ self.onmessage = function () {
+ postMessage("one");
+ };
+`;
+
+const WORKER_2 = `
+ "use strict";
+
+ self.onmessage = function () {
+ postMessage("two");
+ };
+`;
+
+function handleRequest(request, response) {
+ let count = getState("count");
+ if (count === "") {
+ count = "1";
+ }
+
+ // This header is necessary for the cache to trigger.
+ response.setHeader("Cache-control", "max-age=3600");
+ response.setHeader("Content-Type", "text/javascript", false);
+
+ // If this is the first request, return the first source.
+ if (count === "1") {
+ response.write(WORKER_1);
+ setState("count", "2");
+ }
+ // For all subsequent requests, return the second source.
+ else {
+ response.write(WORKER_2);
+ }
+}
diff --git a/dom/workers/test/bug1060621_worker.js b/dom/workers/test/bug1060621_worker.js
new file mode 100644
index 0000000000..4137e3b06f
--- /dev/null
+++ b/dom/workers/test/bug1060621_worker.js
@@ -0,0 +1,2 @@
+navigator.foobar = 42;
+postMessage("done");
diff --git a/dom/workers/test/bug1062920_worker.js b/dom/workers/test/bug1062920_worker.js
new file mode 100644
index 0000000000..238bf949ec
--- /dev/null
+++ b/dom/workers/test/bug1062920_worker.js
@@ -0,0 +1,8 @@
+postMessage({
+ appCodeName: navigator.appCodeName,
+ appName: navigator.appName,
+ appVersion: navigator.appVersion,
+ platform: navigator.platform,
+ userAgent: navigator.userAgent,
+ product: navigator.product,
+});
diff --git a/dom/workers/test/bug1063538.sjs b/dom/workers/test/bug1063538.sjs
new file mode 100644
index 0000000000..7e4ba33998
--- /dev/null
+++ b/dom/workers/test/bug1063538.sjs
@@ -0,0 +1,11 @@
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+function handleRequest(request, response) {
+ response.processAsync();
+ response.write("Hello");
+ setTimeout(function () {
+ response.finish();
+ }, 100000); // wait 100 seconds.
+}
diff --git a/dom/workers/test/bug1063538_worker.js b/dom/workers/test/bug1063538_worker.js
new file mode 100644
index 0000000000..33e60f0a16
--- /dev/null
+++ b/dom/workers/test/bug1063538_worker.js
@@ -0,0 +1,25 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var gURL = "http://example.org/tests/dom/workers/test/bug1063538.sjs";
+var xhr = new XMLHttpRequest({ mozAnon: true, mozSystem: true });
+var progressFired = false;
+
+xhr.onloadend = function (e) {
+ postMessage({ type: "finish", progressFired });
+ self.close();
+};
+
+xhr.onprogress = function (e) {
+ if (e.loaded > 0) {
+ progressFired = true;
+ xhr.abort();
+ }
+};
+
+onmessage = function (e) {
+ xhr.open("GET", gURL, true);
+ xhr.send();
+};
diff --git a/dom/workers/test/bug1104064_worker.js b/dom/workers/test/bug1104064_worker.js
new file mode 100644
index 0000000000..02b802a826
--- /dev/null
+++ b/dom/workers/test/bug1104064_worker.js
@@ -0,0 +1,10 @@
+onmessage = function () {
+ var counter = 0;
+ var id = setInterval(function () {
+ ++counter;
+ if (counter == 2) {
+ clearInterval(id);
+ postMessage("done");
+ }
+ }, 0);
+};
diff --git a/dom/workers/test/bug1132395_sharedWorker.js b/dom/workers/test/bug1132395_sharedWorker.js
new file mode 100644
index 0000000000..851c7f7f6c
--- /dev/null
+++ b/dom/workers/test/bug1132395_sharedWorker.js
@@ -0,0 +1,12 @@
+dump("SW created\n");
+onconnect = function (evt) {
+ dump("SW onconnect\n");
+ evt.ports[0].onmessage = function (e) {
+ dump("SW onmessage\n");
+ var blob = new Blob(["123"], { type: "text/plain" });
+ dump("SW blob created\n");
+ var url = URL.createObjectURL(blob);
+ dump("SW url created: " + url + "\n");
+ evt.ports[0].postMessage("alive \\o/");
+ };
+};
diff --git a/dom/workers/test/bug1132924_worker.js b/dom/workers/test/bug1132924_worker.js
new file mode 100644
index 0000000000..db67ba323c
--- /dev/null
+++ b/dom/workers/test/bug1132924_worker.js
@@ -0,0 +1,10 @@
+onmessage = function () {
+ var a = new XMLHttpRequest();
+ a.open("GET", "empty.html", false);
+ a.onreadystatechange = function () {
+ if (a.readyState == 4) {
+ postMessage(a.response);
+ }
+ };
+ a.send(null);
+};
diff --git a/dom/workers/test/bug978260_worker.js b/dom/workers/test/bug978260_worker.js
new file mode 100644
index 0000000000..126b9c901a
--- /dev/null
+++ b/dom/workers/test/bug978260_worker.js
@@ -0,0 +1,7 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tell the main thread we're here.
+postMessage("loaded");
diff --git a/dom/workers/test/bug998474_worker.js b/dom/workers/test/bug998474_worker.js
new file mode 100644
index 0000000000..c9c85db83b
--- /dev/null
+++ b/dom/workers/test/bug998474_worker.js
@@ -0,0 +1,6 @@
+self.addEventListener("connect", function (e) {
+ var port = e.ports[0];
+ port.onmessage = function (msg) {
+ port.postMessage(eval(msg.data));
+ };
+});
diff --git a/dom/workers/test/chrome.ini b/dom/workers/test/chrome.ini
new file mode 100644
index 0000000000..1b8658063f
--- /dev/null
+++ b/dom/workers/test/chrome.ini
@@ -0,0 +1,96 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ WorkerDebugger.console_childWorker.js
+ WorkerDebugger.console_debugger.js
+ WorkerDebugger.console_worker.js
+ WorkerDebugger.initialize_childWorker.js
+ WorkerDebugger.initialize_debugger.js
+ WorkerDebugger.initialize_worker.js
+ WorkerDebugger.postMessage_childWorker.js
+ WorkerDebugger.postMessage_debugger.js
+ WorkerDebugger.postMessage_worker.js
+ WorkerDebuggerGlobalScope.createSandbox_debugger.js
+ WorkerDebuggerGlobalScope.createSandbox_sandbox.js
+ WorkerDebuggerGlobalScope.createSandbox_worker.js
+ WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js
+ WorkerDebuggerGlobalScope.enterEventLoop_debugger.js
+ WorkerDebuggerGlobalScope.enterEventLoop_worker.js
+ WorkerDebuggerGlobalScope.reportError_childWorker.js
+ WorkerDebuggerGlobalScope.reportError_debugger.js
+ WorkerDebuggerGlobalScope.reportError_worker.js
+ WorkerDebuggerGlobalScope.setImmediate_debugger.js
+ WorkerDebuggerGlobalScope.setImmediate_worker.js
+ WorkerDebuggerManager_childWorker.js
+ WorkerDebuggerManager_worker.js
+ WorkerDebugger_childWorker.js
+ WorkerDebugger_frozen_window1.html
+ WorkerDebugger_frozen_window2.html
+ WorkerDebugger_frozen_worker1.js
+ WorkerDebugger_frozen_worker2.js
+ WorkerDebugger_promise_debugger.js
+ WorkerDebugger_promise_worker.js
+ WorkerDebugger_sharedWorker.js
+ WorkerDebugger_suspended_debugger.js
+ WorkerDebugger_suspended_worker.js
+ WorkerDebugger_worker.js
+ WorkerTest.jsm
+ WorkerTest_subworker.js
+ WorkerTest_worker.js
+ bug1062920_worker.js
+ chromeWorker_subworker.js
+ chromeWorker_worker_submod.sys.mjs
+ chromeWorker_worker.js
+ chromeWorker_worker.sys.mjs
+ dom_worker_helper.js
+ empty.html
+ fileBlobSubWorker_worker.js
+ fileBlob_worker.js
+ filePosting_worker.js
+ fileReadSlice_worker.js
+ fileReaderSyncErrors_worker.js
+ fileReaderSync_worker.js
+ fileSlice_worker.js
+ fileSubWorker_worker.js
+ file_worker.js
+ sharedWorker_privateBrowsing.js
+ sourcemap_header.js
+ sourcemap_header_debugger.js
+
+[test_WorkerDebugger.initialize.xhtml]
+[test_WorkerDebugger.postMessage.xhtml]
+[test_WorkerDebugger.xhtml]
+skip-if =
+ true
+ (verify && !debug && os == 'linux') # Frequent intermittent on QR platforms Bug 1454935
+[test_WorkerDebuggerGlobalScope.createSandbox.xhtml]
+[test_WorkerDebuggerGlobalScope.enterEventLoop.xhtml]
+[test_WorkerDebuggerGlobalScope.reportError.xhtml]
+[test_WorkerDebuggerGlobalScope.setImmediate.xhtml]
+[test_WorkerDebuggerManager.xhtml]
+skip-if = true # Frequent intermittent on QR platforms Bug 1454935
+[test_WorkerDebugger_console.xhtml]
+[test_WorkerDebugger_frozen.xhtml]
+[test_WorkerDebugger_promise.xhtml]
+[test_WorkerDebugger_suspended.xhtml]
+[test_chromeWorker.xhtml]
+[test_chromeWorkerJSM.xhtml]
+[test_file.xhtml]
+skip-if =
+ os == "linux" && bits == 64 && debug # Bug 1765445
+ os == "mac" && os_version == "10.15" && !debug # Bug 1765445
+ win10_2004 && !debug # Bug 1765445
+[test_fileBlobPosting.xhtml]
+[test_fileBlobSubWorker.xhtml]
+[test_filePosting.xhtml]
+[test_fileReadSlice.xhtml]
+[test_fileReaderSync.xhtml]
+[test_fileReaderSyncErrors.xhtml]
+[test_fileSlice.xhtml]
+[test_fileSubWorker.xhtml]
+[test_bug1062920.xhtml]
+[test_sharedWorker_privateBrowsing.html]
+[test_shutdownCheck.xhtml]
+support-files = worker_shutdownCheck.js
+[test_sourcemap_header.html]
+[test_readableStream_when_closing.html]
diff --git a/dom/workers/test/chromeWorker_subworker.js b/dom/workers/test/chromeWorker_subworker.js
new file mode 100644
index 0000000000..6e89453150
--- /dev/null
+++ b/dom/workers/test/chromeWorker_subworker.js
@@ -0,0 +1,7 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+onmessage = function (event) {
+ postMessage("Done!");
+};
diff --git a/dom/workers/test/chromeWorker_worker.js b/dom/workers/test/chromeWorker_worker.js
new file mode 100644
index 0000000000..2d354b087b
--- /dev/null
+++ b/dom/workers/test/chromeWorker_worker.js
@@ -0,0 +1,20 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+if (!("ctypes" in self)) {
+ throw "No ctypes!";
+}
+
+// Go ahead and verify that the ctypes lazy getter actually works.
+if (ctypes.toString() != "[object ctypes]") {
+ throw "Bad ctypes object: " + ctypes.toString();
+}
+
+onmessage = function (event) {
+ let worker = new ChromeWorker("chromeWorker_subworker.js");
+ worker.onmessage = function (msg) {
+ postMessage(msg.data);
+ };
+ worker.postMessage(event.data);
+};
diff --git a/dom/workers/test/chromeWorker_worker.sys.mjs b/dom/workers/test/chromeWorker_worker.sys.mjs
new file mode 100644
index 0000000000..ee96d7829b
--- /dev/null
+++ b/dom/workers/test/chromeWorker_worker.sys.mjs
@@ -0,0 +1,16 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// our onmessage handler lives in the module.
+import _ from "./chromeWorker_worker_submod.sys.mjs";
+
+if (!("ctypes" in self)) {
+ throw "No ctypes!";
+}
+
+// Go ahead and verify that the ctypes lazy getter actually works.
+if (ctypes.toString() != "[object ctypes]") {
+ throw "Bad ctypes object: " + ctypes.toString();
+}
diff --git a/dom/workers/test/chromeWorker_worker_submod.sys.mjs b/dom/workers/test/chromeWorker_worker_submod.sys.mjs
new file mode 100644
index 0000000000..c2fc29175c
--- /dev/null
+++ b/dom/workers/test/chromeWorker_worker_submod.sys.mjs
@@ -0,0 +1,9 @@
+onmessage = function (event) {
+ let worker = new ChromeWorker("chromeWorker_subworker.js");
+ worker.onmessage = function (msg) {
+ postMessage(msg.data);
+ };
+ worker.postMessage(event.data);
+};
+
+export default "go away linter";
diff --git a/dom/workers/test/clearTimeoutsImplicit_worker.js b/dom/workers/test/clearTimeoutsImplicit_worker.js
new file mode 100644
index 0000000000..dfb8d0f6ce
--- /dev/null
+++ b/dom/workers/test/clearTimeoutsImplicit_worker.js
@@ -0,0 +1,11 @@
+var count = 0;
+function timerFunction() {
+ if (++count == 30) {
+ postMessage("ready");
+ while (true) {}
+ }
+}
+
+for (var i = 0; i < 10; i++) {
+ setInterval(timerFunction, 500);
+}
diff --git a/dom/workers/test/clearTimeouts_worker.js b/dom/workers/test/clearTimeouts_worker.js
new file mode 100644
index 0000000000..6e6198c6b5
--- /dev/null
+++ b/dom/workers/test/clearTimeouts_worker.js
@@ -0,0 +1,12 @@
+var count = 0;
+function timerFunction() {
+ if (++count == 30) {
+ close();
+ postMessage("ready");
+ while (true) {}
+ }
+}
+
+for (var i = 0; i < 10; i++) {
+ setInterval(timerFunction, 500);
+}
diff --git a/dom/workers/test/consoleReplaceable_worker.js b/dom/workers/test/consoleReplaceable_worker.js
new file mode 100644
index 0000000000..7e6be8cdd6
--- /dev/null
+++ b/dom/workers/test/consoleReplaceable_worker.js
@@ -0,0 +1,24 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+onmessage = function (event) {
+ postMessage({ event: "console exists", status: !!console, last: false });
+ var logCalled = false;
+ console.log = function () {
+ logCalled = true;
+ };
+ console.log("foo");
+ postMessage({
+ event: "console.log is replaceable",
+ status: logCalled,
+ last: false,
+ });
+ console = 42;
+ postMessage({
+ event: "console is replaceable",
+ status: console === 42,
+ last: true,
+ });
+};
diff --git a/dom/workers/test/console_worker.js b/dom/workers/test/console_worker.js
new file mode 100644
index 0000000000..798ede1671
--- /dev/null
+++ b/dom/workers/test/console_worker.js
@@ -0,0 +1,112 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+onmessage = function (event) {
+ // TEST: does console exist?
+ postMessage({ event: "console exists", status: !!console, last: false });
+
+ postMessage({
+ event: "console is the same object",
+ status: console === console,
+ last: false,
+ });
+
+ postMessage({ event: "trace without function", status: true, last: false });
+
+ for (var i = 0; i < 10; ++i) {
+ console.log(i, i, i);
+ }
+
+ function trace1() {
+ function trace2() {
+ function trace3() {
+ console.trace("trace " + i);
+ }
+ trace3();
+ }
+ trace2();
+ }
+ trace1();
+
+ foobar585956c = function (a) {
+ console.trace();
+ return a + "c";
+ };
+
+ function foobar585956b(a) {
+ return foobar585956c(a + "b");
+ }
+
+ function foobar585956a(omg) {
+ return foobar585956b(omg + "a");
+ }
+
+ function foobar646025(omg) {
+ console.log(omg, "o", "d");
+ }
+
+ function startTimer(timer) {
+ console.time(timer);
+ }
+
+ function stopTimer(timer) {
+ console.timeEnd(timer);
+ }
+
+ function timeStamp(label) {
+ console.timeStamp(label);
+ }
+
+ function testGroups() {
+ console.groupCollapsed("a", "group");
+ console.group("b", "group");
+ console.groupEnd();
+ }
+
+ foobar585956a("omg");
+ foobar646025("omg");
+ timeStamp();
+ timeStamp("foo");
+ testGroups();
+ startTimer("foo");
+ setTimeout(function () {
+ stopTimer("foo");
+ nextSteps(event);
+ }, 10);
+};
+
+function nextSteps(event) {
+ function namelessTimer() {
+ console.time();
+ console.timeEnd();
+ }
+
+ namelessTimer();
+
+ var str = "Test Message.";
+ console.log(str);
+ console.info(str);
+ console.warn(str);
+ console.error(str);
+ console.exception(str);
+ console.assert(true, str);
+ console.assert(false, str);
+ console.profile(str);
+ console.profileEnd(str);
+ console.timeStamp();
+ console.clear();
+ postMessage({ event: "4 messages", status: true, last: false });
+
+ // Recursive:
+ if (event.data == true) {
+ var worker = new Worker("console_worker.js");
+ worker.onmessage = function (msg) {
+ postMessage(msg.data);
+ };
+ worker.postMessage(false);
+ } else {
+ postMessage({ event: "bye bye", status: true, last: true });
+ }
+}
diff --git a/dom/workers/test/content_worker.js b/dom/workers/test/content_worker.js
new file mode 100644
index 0000000000..d79732b281
--- /dev/null
+++ b/dom/workers/test/content_worker.js
@@ -0,0 +1,12 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+var props = {
+ ctypes: 1,
+ OS: 1,
+};
+for (var prop in props) {
+ postMessage({ prop, value: self[prop] });
+}
+postMessage({ testfinished: 1 });
diff --git a/dom/workers/test/crashtests/1153636.html b/dom/workers/test/crashtests/1153636.html
new file mode 100644
index 0000000000..6ad0d550fd
--- /dev/null
+++ b/dom/workers/test/crashtests/1153636.html
@@ -0,0 +1,5 @@
+<script>
+
+new Worker("data:text/javascript;charset=UTF-8,self.addEventListener('',function(){},false);");
+
+</script>
diff --git a/dom/workers/test/crashtests/1158031.html b/dom/workers/test/crashtests/1158031.html
new file mode 100644
index 0000000000..6d896bc466
--- /dev/null
+++ b/dom/workers/test/crashtests/1158031.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script>
+
+function boom()
+{
+ var w = new Worker("data:text/javascript;charset=UTF-8,");
+ w.postMessage(new Blob([], {}));
+}
+
+</script>
+<body onload="boom();"></body>
diff --git a/dom/workers/test/crashtests/1228456.html b/dom/workers/test/crashtests/1228456.html
new file mode 100644
index 0000000000..6d1f0f0a72
--- /dev/null
+++ b/dom/workers/test/crashtests/1228456.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+
+function boom()
+{
+ var w;
+ for (var i = 0; i < 99; ++i) {
+ w = new SharedWorker("data:text/javascript;charset=UTF-8," + encodeURIComponent(i + ";"));
+ }
+ w.port.postMessage("");
+}
+
+</script>
+<body onload="boom();"></body>
diff --git a/dom/workers/test/crashtests/1348882.html b/dom/workers/test/crashtests/1348882.html
new file mode 100644
index 0000000000..e0288c4ccb
--- /dev/null
+++ b/dom/workers/test/crashtests/1348882.html
@@ -0,0 +1,18 @@
+<!DOCTYPE>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+function boom() {
+ let r = new Request("#a#a");
+ setTimeout(function(){
+ r.formData();
+ setTimeout(function(){
+ r.blob();
+ }, 0);
+ }, 0);
+}
+addEventListener("DOMContentLoaded", boom);
+</script>
+</head>
+</html>
diff --git a/dom/workers/test/crashtests/1819146.html b/dom/workers/test/crashtests/1819146.html
new file mode 100644
index 0000000000..611acb3ed2
--- /dev/null
+++ b/dom/workers/test/crashtests/1819146.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script id="worker1" type="javascript/worker">
+ self.onmessage = async function (e) {
+ const abort = new AbortController()
+ const signal = abort.signal
+ abort.abort()
+ close()
+ try { await fetch(undefined, { signal: signal }) } catch (e) {}
+ await navigator.locks.request("weblock_0", { signal: signal }, () => {})
+ await fetch(undefined, { headers: [] })
+ }
+ </script>
+ <script>
+ document.addEventListener('DOMContentLoaded', () => {
+ const blob = new Blob([document.querySelector('#worker1').textContent], { type: 'text/javascript' })
+ const worker = new Worker(window.URL.createObjectURL(blob))
+ worker.postMessage([], [])
+ })
+ </script>
+</head>
+</html>
diff --git a/dom/workers/test/crashtests/1821066.html b/dom/workers/test/crashtests/1821066.html
new file mode 100644
index 0000000000..d654b37768
--- /dev/null
+++ b/dom/workers/test/crashtests/1821066.html
@@ -0,0 +1,9 @@
+<!DOCTYPE>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+new Worker("data:text/javascript;charset=UTF-8,import { o } from '404.js'", {type: 'module'});
+</script>
+</head>
+</html>
diff --git a/dom/workers/test/crashtests/779707.html b/dom/workers/test/crashtests/779707.html
new file mode 100644
index 0000000000..97a8113dab
--- /dev/null
+++ b/dom/workers/test/crashtests/779707.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var x = new XMLHttpRequest();
+ x.open('GET', "data:text/plain,2", false);
+ x.send();
+
+ new Worker("data:text/javascript,3");
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/workers/test/crashtests/943516.html b/dom/workers/test/crashtests/943516.html
new file mode 100644
index 0000000000..5f4667850f
--- /dev/null
+++ b/dom/workers/test/crashtests/943516.html
@@ -0,0 +1,10 @@
+<!--
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<script>
+// Using a DOM bindings object as a weak map key should not crash when attempting to
+// call the preserve wrapper callback.
+new Worker("data:text/javascript;charset=UTF-8,(new WeakMap()).set(self, 0);")
+</script>
diff --git a/dom/workers/test/crashtests/crashtests.list b/dom/workers/test/crashtests/crashtests.list
new file mode 100644
index 0000000000..528f4c8a10
--- /dev/null
+++ b/dom/workers/test/crashtests/crashtests.list
@@ -0,0 +1,8 @@
+load 779707.html
+load 943516.html
+load 1153636.html
+load 1158031.html
+load 1228456.html
+load 1348882.html
+load 1821066.html
+load 1819146.html
diff --git a/dom/workers/test/csp_worker.js b/dom/workers/test/csp_worker.js
new file mode 100644
index 0000000000..e5adf3fe78
--- /dev/null
+++ b/dom/workers/test/csp_worker.js
@@ -0,0 +1,25 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+onmessage = function (event) {
+ if (event.data.do == "eval") {
+ var res;
+ try {
+ res = eval("40+2");
+ } catch (ex) {
+ res = ex + "";
+ }
+ postMessage(res);
+ } else if (event.data.do == "nest") {
+ var worker = new Worker(event.data.uri);
+ if (--event.data.level) {
+ worker.postMessage(event.data);
+ } else {
+ worker.postMessage({ do: "eval" });
+ }
+ worker.onmessage = e => {
+ postMessage(e.data);
+ };
+ }
+};
diff --git a/dom/workers/test/csp_worker.js^headers^ b/dom/workers/test/csp_worker.js^headers^
new file mode 100644
index 0000000000..7b835bf2a8
--- /dev/null
+++ b/dom/workers/test/csp_worker.js^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'self' blob: ; script-src 'unsafe-eval'
diff --git a/dom/workers/test/dom_worker_helper.js b/dom/workers/test/dom_worker_helper.js
new file mode 100644
index 0000000000..4768fc4840
--- /dev/null
+++ b/dom/workers/test/dom_worker_helper.js
@@ -0,0 +1,171 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"].getService(
+ Ci.nsIWorkerDebuggerManager
+);
+
+const BASE_URL = "chrome://mochitests/content/chrome/dom/workers/test/";
+
+var gRemainingTests = 0;
+
+function waitForWorkerFinish() {
+ if (gRemainingTests == 0) {
+ SimpleTest.waitForExplicitFinish();
+ }
+ ++gRemainingTests;
+}
+
+function finish() {
+ --gRemainingTests;
+ if (gRemainingTests == 0) {
+ SimpleTest.finish();
+ }
+}
+
+function assertThrows(fun, message) {
+ let throws = false;
+ try {
+ fun();
+ } catch (e) {
+ throws = true;
+ }
+ ok(throws, message);
+}
+
+function generateDebuggers() {
+ return wdm.getWorkerDebuggerEnumerator();
+}
+
+function findDebugger(url) {
+ for (let dbg of generateDebuggers()) {
+ if (dbg.url === url) {
+ return dbg;
+ }
+ }
+ return null;
+}
+
+function waitForRegister(url, dbgUrl) {
+ return new Promise(function (resolve) {
+ wdm.addListener({
+ onRegister(dbg) {
+ if (dbg.url !== url) {
+ return;
+ }
+ ok(true, "Debugger with url " + url + " should be registered.");
+ wdm.removeListener(this);
+ if (dbgUrl) {
+ info("Initializing worker debugger with url " + url + ".");
+ dbg.initialize(dbgUrl);
+ }
+ resolve(dbg);
+ },
+ });
+ });
+}
+
+function waitForUnregister(url) {
+ return new Promise(function (resolve) {
+ wdm.addListener({
+ onUnregister(dbg) {
+ if (dbg.url !== url) {
+ return;
+ }
+ ok(true, "Debugger with url " + url + " should be unregistered.");
+ wdm.removeListener(this);
+ resolve();
+ },
+ });
+ });
+}
+
+function waitForDebuggerClose(dbg) {
+ return new Promise(function (resolve) {
+ dbg.addListener({
+ onClose() {
+ ok(true, "Debugger should be closed.");
+ dbg.removeListener(this);
+ resolve();
+ },
+ });
+ });
+}
+
+function waitForDebuggerError(dbg) {
+ return new Promise(function (resolve) {
+ dbg.addListener({
+ onError(filename, lineno, message) {
+ dbg.removeListener(this);
+ resolve(new Error(message, filename, lineno));
+ },
+ });
+ });
+}
+
+function waitForDebuggerMessage(dbg, message) {
+ return new Promise(function (resolve) {
+ dbg.addListener({
+ onMessage(message1) {
+ if (message !== message1) {
+ return;
+ }
+ ok(true, "Should receive " + message + " message from debugger.");
+ dbg.removeListener(this);
+ resolve();
+ },
+ });
+ });
+}
+
+function waitForWindowMessage(window, message) {
+ return new Promise(function (resolve) {
+ let onmessage = function (event) {
+ if (event.data !== event.data) {
+ return;
+ }
+ window.removeEventListener("message", onmessage);
+ resolve();
+ };
+ window.addEventListener("message", onmessage);
+ });
+}
+
+function waitForWorkerMessage(worker, message) {
+ return new Promise(function (resolve) {
+ worker.addEventListener("message", function onmessage(event) {
+ if (event.data !== message) {
+ return;
+ }
+ ok(true, "Should receive " + message + " message from worker.");
+ worker.removeEventListener("message", onmessage);
+ resolve();
+ });
+ });
+}
+
+function waitForMultiple(promises) {
+ return new Promise(function (resolve) {
+ let values = [];
+ for (let i = 0; i < promises.length; ++i) {
+ let index = i;
+ promises[i].then(function (value) {
+ is(
+ index + 1,
+ values.length + 1,
+ "Promise " +
+ (values.length + 1) +
+ " out of " +
+ promises.length +
+ " should be resolved."
+ );
+ values.push(value);
+ if (values.length === promises.length) {
+ resolve(values);
+ }
+ });
+ }
+ });
+}
diff --git a/dom/workers/test/dynamicImport_nested.mjs b/dom/workers/test/dynamicImport_nested.mjs
new file mode 100644
index 0000000000..688078d803
--- /dev/null
+++ b/dom/workers/test/dynamicImport_nested.mjs
@@ -0,0 +1,8 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+await import("./dynamicImport_postMessage.mjs");
+
+export const message = "first";
diff --git a/dom/workers/test/dynamicImport_postMessage.mjs b/dom/workers/test/dynamicImport_postMessage.mjs
new file mode 100644
index 0000000000..ddb9ee0644
--- /dev/null
+++ b/dom/workers/test/dynamicImport_postMessage.mjs
@@ -0,0 +1,8 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+postMessage("second");
+
+export const message = "second";
diff --git a/dom/workers/test/dynamicImport_worker.js b/dom/workers/test/dynamicImport_worker.js
new file mode 100644
index 0000000000..44e5fc4e0a
--- /dev/null
+++ b/dom/workers/test/dynamicImport_worker.js
@@ -0,0 +1,15 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+onmessage = function (event) {
+ switch (event.data) {
+ case "start":
+ import("./dynamicImport_nested.mjs").then(m => postMessage(m.message));
+ break;
+ default:
+ throw new Error("Bad message: " + event.data);
+ break;
+ }
+};
diff --git a/dom/workers/test/empty.html b/dom/workers/test/empty.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/workers/test/empty.html
diff --git a/dom/workers/test/empty_worker.js b/dom/workers/test/empty_worker.js
new file mode 100644
index 0000000000..9dda1c9fd1
--- /dev/null
+++ b/dom/workers/test/empty_worker.js
@@ -0,0 +1 @@
+postMessage(42);
diff --git a/dom/workers/test/errorPropagation_iframe.html b/dom/workers/test/errorPropagation_iframe.html
new file mode 100644
index 0000000000..cba0ee8ca2
--- /dev/null
+++ b/dom/workers/test/errorPropagation_iframe.html
@@ -0,0 +1,55 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <meta charset="utf-8">
+ <body>
+ <script type="text/javascript">
+ var worker;
+
+ function start(workerCount, messageCallback) {
+ var seenWindowError;
+ window.onerror = function(message, filename, lineno) {
+ if (!seenWindowError) {
+ seenWindowError = true;
+ messageCallback({
+ type: "window",
+ data: { message, filename, lineno }
+ });
+ return true;
+ }
+ };
+
+ worker = new Worker("errorPropagation_worker.js");
+
+ worker.onmessage = function(event) {
+ messageCallback(event.data);
+ };
+
+ var seenWorkerError;
+ worker.onerror = function(event) {
+ if (!seenWorkerError) {
+ seenWorkerError = true;
+ messageCallback({
+ type: "worker",
+ data: {
+ message: event.message,
+ filename: event.filename,
+ lineno: event.lineno
+ }
+ });
+ event.preventDefault();
+ }
+ };
+
+ worker.postMessage(workerCount);
+ }
+
+ function stop() {
+ worker.terminate();
+ }
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/errorPropagation_worker.js b/dom/workers/test/errorPropagation_worker.js
new file mode 100644
index 0000000000..2c4de27460
--- /dev/null
+++ b/dom/workers/test/errorPropagation_worker.js
@@ -0,0 +1,50 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+var seenScopeError;
+onerror = function (message, filename, lineno) {
+ if (!seenScopeError) {
+ seenScopeError = true;
+ postMessage({
+ type: "scope",
+ data: { message, filename, lineno },
+ });
+ return true;
+ }
+};
+
+onmessage = function (event) {
+ var workerId = parseInt(event.data);
+
+ if (workerId > 1) {
+ var worker = new Worker("errorPropagation_worker.js");
+
+ worker.onmessage = function (msg) {
+ postMessage(msg.data);
+ };
+
+ var seenWorkerError;
+ worker.onerror = function (error) {
+ if (!seenWorkerError) {
+ seenWorkerError = true;
+ postMessage({
+ type: "worker",
+ data: {
+ message: error.message,
+ filename: error.filename,
+ lineno: error.lineno,
+ },
+ });
+ error.preventDefault();
+ }
+ };
+
+ worker.postMessage(workerId - 1);
+ return;
+ }
+
+ var interval = setInterval(function () {
+ throw new Error("expectedError");
+ }, 100);
+};
diff --git a/dom/workers/test/errorwarning_worker.js b/dom/workers/test/errorwarning_worker.js
new file mode 100644
index 0000000000..5c951fbede
--- /dev/null
+++ b/dom/workers/test/errorwarning_worker.js
@@ -0,0 +1,44 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function errorHandler() {
+ postMessage({ type: "error" });
+}
+
+onmessage = function (event) {
+ if (event.data.errors) {
+ try {
+ // This is an error:
+ postMessage({ type: "ignore", value: b.aaa });
+ } catch (e) {
+ errorHandler();
+ }
+ } else {
+ var a = {};
+ // This is a warning:
+ postMessage({ type: "ignore", value: a.foo });
+ }
+
+ if (event.data.loop != 0) {
+ var worker = new Worker("errorwarning_worker.js");
+ worker.onerror = errorHandler;
+ worker.postMessage({
+ loop: event.data.loop - 1,
+ errors: event.data.errors,
+ });
+
+ worker.onmessage = function (e) {
+ postMessage(e.data);
+ };
+ } else {
+ postMessage({ type: "finish" });
+ }
+};
+
+onerror = errorHandler;
+onerror = onerror;
+if (!onerror || onerror != onerror) {
+ throw "onerror wasn't set properly";
+}
diff --git a/dom/workers/test/eventDispatch_worker.js b/dom/workers/test/eventDispatch_worker.js
new file mode 100644
index 0000000000..57bbc99aa3
--- /dev/null
+++ b/dom/workers/test/eventDispatch_worker.js
@@ -0,0 +1,84 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+const fakeEventType = "foo";
+
+function testEventTarget(event) {
+ if (event.target !== self) {
+ throw new Error("Event has a bad target!");
+ }
+ if (event.currentTarget) {
+ throw new Error("Event has a bad currentTarget!");
+ }
+ postMessage(event.data);
+}
+
+addEventListener(
+ fakeEventType,
+ function (event) {
+ throw new Error("Trusted event listener received untrusted event!");
+ },
+ false,
+ false
+);
+
+addEventListener(
+ fakeEventType,
+ function (event) {
+ if (event.target !== self || event.currentTarget !== self) {
+ throw new Error("Fake event has bad target!");
+ }
+ if (event.isTrusted) {
+ throw new Error("Event should be untrusted!");
+ }
+ event.stopImmediatePropagation();
+ postMessage(event.data);
+ },
+ false,
+ true
+);
+
+addEventListener(
+ fakeEventType,
+ function (event) {
+ throw new Error(
+ "This shouldn't get called because of stopImmediatePropagation."
+ );
+ },
+ false,
+ true
+);
+
+var count = 0;
+onmessage = function (event) {
+ if (event.target !== self || event.currentTarget !== self) {
+ throw new Error("Event has bad target!");
+ }
+
+ if (!count++) {
+ var exception;
+ try {
+ self.dispatchEvent(event);
+ } catch (e) {
+ exception = e;
+ }
+
+ if (!exception) {
+ throw new Error("Recursive dispatch didn't fail!");
+ }
+
+ event = new MessageEvent(fakeEventType, {
+ bubbles: event.bubbles,
+ cancelable: event.cancelable,
+ data: event.data,
+ origin: "*",
+ source: null,
+ });
+ self.dispatchEvent(event);
+
+ return;
+ }
+
+ setTimeout(testEventTarget, 0, event);
+};
diff --git a/dom/workers/test/fibonacci_worker.js b/dom/workers/test/fibonacci_worker.js
new file mode 100644
index 0000000000..0efe5a18d9
--- /dev/null
+++ b/dom/workers/test/fibonacci_worker.js
@@ -0,0 +1,24 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+onmessage = function (event) {
+ var n = parseInt(event.data);
+
+ if (n < 2) {
+ postMessage(n);
+ return;
+ }
+
+ var results = [];
+ for (var i = 1; i <= 2; i++) {
+ var worker = new Worker("fibonacci_worker.js");
+ worker.onmessage = function (msg) {
+ results.push(parseInt(msg.data));
+ if (results.length == 2) {
+ postMessage(results[0] + results[1]);
+ }
+ };
+ worker.postMessage(n - i);
+ }
+};
diff --git a/dom/workers/test/fileBlobSubWorker_worker.js b/dom/workers/test/fileBlobSubWorker_worker.js
new file mode 100644
index 0000000000..170362b1a4
--- /dev/null
+++ b/dom/workers/test/fileBlobSubWorker_worker.js
@@ -0,0 +1,17 @@
+/**
+ * Expects a blob. Returns an object containing the size, type.
+ * Used to test posting of blob from worker to worker.
+ */
+onmessage = function (event) {
+ var worker = new Worker("fileBlob_worker.js");
+
+ worker.postMessage(event.data);
+
+ worker.onmessage = function (msg) {
+ postMessage(msg.data);
+ };
+
+ worker.onerror = function (error) {
+ postMessage(undefined);
+ };
+};
diff --git a/dom/workers/test/fileBlob_worker.js b/dom/workers/test/fileBlob_worker.js
new file mode 100644
index 0000000000..fbb50c7c10
--- /dev/null
+++ b/dom/workers/test/fileBlob_worker.js
@@ -0,0 +1,13 @@
+/**
+ * Expects a blob. Returns an object containing the size, type.
+ */
+onmessage = function (event) {
+ var file = event.data;
+
+ var rtnObj = new Object();
+
+ rtnObj.size = file.size;
+ rtnObj.type = file.type;
+
+ postMessage(rtnObj);
+};
diff --git a/dom/workers/test/filePosting_worker.js b/dom/workers/test/filePosting_worker.js
new file mode 100644
index 0000000000..052d400374
--- /dev/null
+++ b/dom/workers/test/filePosting_worker.js
@@ -0,0 +1,3 @@
+onmessage = function (event) {
+ postMessage(event.data);
+};
diff --git a/dom/workers/test/fileReadSlice_worker.js b/dom/workers/test/fileReadSlice_worker.js
new file mode 100644
index 0000000000..edbad88802
--- /dev/null
+++ b/dom/workers/test/fileReadSlice_worker.js
@@ -0,0 +1,16 @@
+/**
+ * Expects an object containing a blob, a start index and an end index
+ * for slicing. Returns the contents of the blob read as text.
+ */
+onmessage = function (event) {
+ var blob = event.data.blob;
+ var start = event.data.start;
+ var end = event.data.end;
+
+ var slicedBlob = blob.slice(start, end);
+
+ var fileReader = new FileReaderSync();
+ var text = fileReader.readAsText(slicedBlob);
+
+ postMessage(text);
+};
diff --git a/dom/workers/test/fileReaderSyncErrors_worker.js b/dom/workers/test/fileReaderSyncErrors_worker.js
new file mode 100644
index 0000000000..0ba91469c8
--- /dev/null
+++ b/dom/workers/test/fileReaderSyncErrors_worker.js
@@ -0,0 +1,82 @@
+/**
+ * Delegates "is" evaluation back to main thread.
+ */
+function is(actual, expected, message) {
+ var rtnObj = new Object();
+ rtnObj.actual = actual;
+ rtnObj.expected = expected;
+ rtnObj.message = message;
+ postMessage(rtnObj);
+}
+
+/**
+ * Tries to write to property.
+ */
+function writeProperty(file, property) {
+ var oldValue = file[property];
+ file[property] = -1;
+ is(file[property], oldValue, "Property " + property + " should be readonly.");
+}
+
+/**
+ * Passes junk arguments to FileReaderSync methods and expects an exception to
+ * be thrown.
+ */
+function fileReaderJunkArgument(blob) {
+ var fileReader = new FileReaderSync();
+
+ try {
+ fileReader.readAsBinaryString(blob);
+ is(
+ false,
+ true,
+ "Should have thrown an exception calling readAsBinaryString."
+ );
+ } catch (ex) {
+ is(true, true, "Should have thrown an exception.");
+ }
+
+ try {
+ fileReader.readAsDataURL(blob);
+ is(false, true, "Should have thrown an exception calling readAsDataURL.");
+ } catch (ex) {
+ is(true, true, "Should have thrown an exception.");
+ }
+
+ try {
+ fileReader.readAsArrayBuffer(blob);
+ is(
+ false,
+ true,
+ "Should have thrown an exception calling readAsArrayBuffer."
+ );
+ } catch (ex) {
+ is(true, true, "Should have thrown an exception.");
+ }
+
+ try {
+ fileReader.readAsText(blob);
+ is(false, true, "Should have thrown an exception calling readAsText.");
+ } catch (ex) {
+ is(true, true, "Should have thrown an exception.");
+ }
+}
+
+onmessage = function (event) {
+ var file = event.data;
+
+ // Test read only properties.
+ writeProperty(file, "size");
+ writeProperty(file, "type");
+ writeProperty(file, "name");
+
+ // Bad types.
+ fileReaderJunkArgument(undefined);
+ fileReaderJunkArgument(-1);
+ fileReaderJunkArgument(1);
+ fileReaderJunkArgument(new Object());
+ fileReaderJunkArgument("hello");
+
+ // Post undefined to indicate that testing has finished.
+ postMessage(undefined);
+};
diff --git a/dom/workers/test/fileReaderSync_worker.js b/dom/workers/test/fileReaderSync_worker.js
new file mode 100644
index 0000000000..d5512ef024
--- /dev/null
+++ b/dom/workers/test/fileReaderSync_worker.js
@@ -0,0 +1,25 @@
+var reader = new FileReaderSync();
+
+/**
+ * Expects an object containing a file and an encoding then uses a
+ * FileReaderSync to read the file. Returns an object containing the
+ * file read a binary string, text, url and ArrayBuffer.
+ */
+onmessage = function (event) {
+ var file = event.data.file;
+ var encoding = event.data.encoding;
+
+ var rtnObj = new Object();
+
+ if (encoding != undefined) {
+ rtnObj.text = reader.readAsText(file, encoding);
+ } else {
+ rtnObj.text = reader.readAsText(file);
+ }
+
+ rtnObj.bin = reader.readAsBinaryString(file);
+ rtnObj.url = reader.readAsDataURL(file);
+ rtnObj.arrayBuffer = reader.readAsArrayBuffer(file);
+
+ postMessage(rtnObj);
+};
diff --git a/dom/workers/test/fileSlice_worker.js b/dom/workers/test/fileSlice_worker.js
new file mode 100644
index 0000000000..94a283033a
--- /dev/null
+++ b/dom/workers/test/fileSlice_worker.js
@@ -0,0 +1,27 @@
+/**
+ * Expects an object containing a blob, a start offset, an end offset
+ * and an optional content type to slice the blob. Returns an object
+ * containing the size and type of the sliced blob.
+ */
+onmessage = function (event) {
+ var blob = event.data.blob;
+ var start = event.data.start;
+ var end = event.data.end;
+ var contentType = event.data.contentType;
+
+ var slicedBlob;
+ if (contentType == undefined && end == undefined) {
+ slicedBlob = blob.slice(start);
+ } else if (contentType == undefined) {
+ slicedBlob = blob.slice(start, end);
+ } else {
+ slicedBlob = blob.slice(start, end, contentType);
+ }
+
+ var rtnObj = new Object();
+
+ rtnObj.size = slicedBlob.size;
+ rtnObj.type = slicedBlob.type;
+
+ postMessage(rtnObj);
+};
diff --git a/dom/workers/test/fileSubWorker_worker.js b/dom/workers/test/fileSubWorker_worker.js
new file mode 100644
index 0000000000..8a5c002865
--- /dev/null
+++ b/dom/workers/test/fileSubWorker_worker.js
@@ -0,0 +1,17 @@
+/**
+ * Expects a file. Returns an object containing the size, type, name and path
+ * using another worker. Used to test posting of file from worker to worker.
+ */
+onmessage = function (event) {
+ var worker = new Worker("file_worker.js");
+
+ worker.postMessage(event.data);
+
+ worker.onmessage = function (msg) {
+ postMessage(msg.data);
+ };
+
+ worker.onerror = function (error) {
+ postMessage(undefined);
+ };
+};
diff --git a/dom/workers/test/file_bug1010784_worker.js b/dom/workers/test/file_bug1010784_worker.js
new file mode 100644
index 0000000000..3e88f5cc97
--- /dev/null
+++ b/dom/workers/test/file_bug1010784_worker.js
@@ -0,0 +1,9 @@
+onmessage = function (event) {
+ var xhr = new XMLHttpRequest();
+
+ xhr.open("GET", event.data, false);
+ xhr.send();
+ xhr.open("GET", event.data, false);
+ xhr.send();
+ postMessage("done");
+};
diff --git a/dom/workers/test/file_service_worker.js b/dom/workers/test/file_service_worker.js
new file mode 100644
index 0000000000..dd264e340e
--- /dev/null
+++ b/dom/workers/test/file_service_worker.js
@@ -0,0 +1,3 @@
+self.onmessage = evt => {
+ evt.ports[0].postMessage("serviceworker-reply");
+};
diff --git a/dom/workers/test/file_service_worker_container.html b/dom/workers/test/file_service_worker_container.html
new file mode 100644
index 0000000000..625e911adc
--- /dev/null
+++ b/dom/workers/test/file_service_worker_container.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script>
+ (async function () {
+ await navigator.serviceWorker.register("file_service_worker.js");
+ await navigator.serviceWorker.ready;
+ })();
+ </script>
+ </head>
+ <body>
+ Service Worker Container
+ </body>
+</html>
diff --git a/dom/workers/test/file_service_worker_fetch_synthetic.js b/dom/workers/test/file_service_worker_fetch_synthetic.js
new file mode 100644
index 0000000000..c58aeb294a
--- /dev/null
+++ b/dom/workers/test/file_service_worker_fetch_synthetic.js
@@ -0,0 +1,59 @@
+addEventListener("install", function (evt) {
+ evt.waitUntil(self.skipWaiting());
+});
+
+/**
+ * Given a multipart/form-data encoded string that we know to have only a single
+ * part, return the contents of the part. (MIME multipart encoding is too
+ * exciting to delve into.)
+ */
+function extractBlobFromMultipartFormData(text) {
+ const lines = text.split(/\r\n/g);
+ const firstBlank = lines.indexOf("");
+ const foo = lines.slice(firstBlank + 1, -2).join("\n");
+ return foo;
+}
+
+self.addEventListener("fetch", event => {
+ const url = new URL(event.request.url);
+ const mode = url.searchParams.get("mode");
+
+ if (mode === "synthetic") {
+ event.respondWith(
+ (async () => {
+ // This works even if there wasn't a body explicitly associated with the
+ // request. We just get a zero-length string in that case.
+ const requestBodyContents = await event.request.text();
+ const blobContents =
+ extractBlobFromMultipartFormData(requestBodyContents);
+
+ return new Response(
+ `<!DOCTYPE HTML><head><meta charset="utf-8"/></head><body>
+ <h1 id="url">${event.request.url}</h1>
+ <div id="source">ServiceWorker</div>
+ <div id="blob">${blobContents}</div>
+ </body>`,
+ { headers: { "Content-Type": "text/html" } }
+ );
+ })()
+ );
+ } else if (mode === "fetch") {
+ event.respondWith(fetch(event.request));
+ } else if (mode === "clone") {
+ // In order for the act of cloning to be interesting, we want the original
+ // request to remain alive so that any pipes end up having to buffer.
+ self.originalRequest = event.request;
+ event.respondWith(fetch(event.request.clone()));
+ } else {
+ event.respondWith(
+ new Response(
+ `<!DOCTYPE HTML><head><meta charset="utf-8"/></head><body>
+ <h1 id="error">Bad mode: ${mode}</h1>
+ <div id="source">ServiceWorker::Error</div>
+ <div id="blob">No, this is an error.</div>
+ </body>`,
+ { headers: { "Content-Type": "text/html" }, status: 400 }
+ )
+ );
+ }
+});
diff --git a/dom/workers/test/file_use_counter_service_worker.js b/dom/workers/test/file_use_counter_service_worker.js
new file mode 100644
index 0000000000..8ee0d2e04a
--- /dev/null
+++ b/dom/workers/test/file_use_counter_service_worker.js
@@ -0,0 +1,9 @@
+onmessage = async function (e) {
+ if (e.data === "RUN") {
+ console.log("worker runs");
+ await clients.claim();
+ clients.matchAll().then(res => {
+ res.forEach(client => client.postMessage("DONE"));
+ });
+ }
+};
diff --git a/dom/workers/test/file_use_counter_shared_worker.js b/dom/workers/test/file_use_counter_shared_worker.js
new file mode 100644
index 0000000000..7e4f95af3b
--- /dev/null
+++ b/dom/workers/test/file_use_counter_shared_worker.js
@@ -0,0 +1,10 @@
+onconnect = function (e) {
+ let port = e.ports[0];
+ port.onmessage = function (m) {
+ if (m.data === "RUN") {
+ console.log("worker runs");
+ port.postMessage("DONE");
+ close();
+ }
+ };
+};
diff --git a/dom/workers/test/file_use_counter_shared_worker_microtask.js b/dom/workers/test/file_use_counter_shared_worker_microtask.js
new file mode 100644
index 0000000000..b219da2225
--- /dev/null
+++ b/dom/workers/test/file_use_counter_shared_worker_microtask.js
@@ -0,0 +1,12 @@
+onconnect = function (e) {
+ let port = e.ports[0];
+ port.onmessage = function (m) {
+ if (m.data === "RUN") {
+ queueMicrotask(() => {
+ console.log("worker runs");
+ });
+ port.postMessage("DONE");
+ close();
+ }
+ };
+};
diff --git a/dom/workers/test/file_use_counter_worker.html b/dom/workers/test/file_use_counter_worker.html
new file mode 100644
index 0000000000..d034ad6f5f
--- /dev/null
+++ b/dom/workers/test/file_use_counter_worker.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1202706
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1202706</title>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1202706">Mozilla Bug 1202706</a>
+</body>
+</html>
diff --git a/dom/workers/test/file_use_counter_worker.js b/dom/workers/test/file_use_counter_worker.js
new file mode 100644
index 0000000000..12046940a8
--- /dev/null
+++ b/dom/workers/test/file_use_counter_worker.js
@@ -0,0 +1,2 @@
+console.log("worker runs");
+postMessage("DONE");
diff --git a/dom/workers/test/file_worker.js b/dom/workers/test/file_worker.js
new file mode 100644
index 0000000000..6ed8fe1816
--- /dev/null
+++ b/dom/workers/test/file_worker.js
@@ -0,0 +1,16 @@
+/**
+ * Expects a file. Returns an object containing the size, type, name and path.
+ */
+onmessage = function (event) {
+ var file = event.data;
+
+ var rtnObj = new Object();
+
+ rtnObj.size = file.size;
+ rtnObj.type = file.type;
+ rtnObj.name = file.name;
+ rtnObj.path = file.path;
+ rtnObj.lastModified = file.lastModified;
+
+ postMessage(rtnObj);
+};
diff --git a/dom/workers/test/foreign.js b/dom/workers/test/foreign.js
new file mode 100644
index 0000000000..33c982fa8f
--- /dev/null
+++ b/dom/workers/test/foreign.js
@@ -0,0 +1 @@
+response = "bad";
diff --git a/dom/workers/test/head.js b/dom/workers/test/head.js
new file mode 100644
index 0000000000..ce6cebbd06
--- /dev/null
+++ b/dom/workers/test/head.js
@@ -0,0 +1,75 @@
+/* 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/. */
+"use strict";
+
+/**
+ * Add a tab with given `url`. Returns a promise
+ * that will be resolved when the tab finished loading.
+ */
+function addTab(url) {
+ return BrowserTestUtils.openNewForegroundTab(gBrowser, TAB_URL);
+}
+
+/**
+ * Remove the given `tab`.
+ */
+function removeTab(tab) {
+ gBrowser.removeTab(tab);
+}
+
+/**
+ * Create a worker with the given `url` in the given `tab`.
+ */
+function createWorkerInTab(tab, url) {
+ info("Creating worker with url '" + url + "'\n");
+ return SpecialPowers.spawn(tab.linkedBrowser, [url], urlChild => {
+ if (!content._workers) {
+ content._workers = {};
+ }
+ content._workers[urlChild] = new content.Worker(urlChild);
+ });
+}
+
+/**
+ * Terminate the worker with the given `url` in the given `tab`.
+ */
+function terminateWorkerInTab(tab, url) {
+ info("Terminating worker with url '" + url + "'\n");
+ return SpecialPowers.spawn(tab.linkedBrowser, [url], urlChild => {
+ content._workers[urlChild].terminate();
+ delete content._workers[urlChild];
+ });
+}
+
+/**
+ * Post the given `message` to the worker with the given `url` in the given
+ * `tab`.
+ */
+function postMessageToWorkerInTab(tab, url, message) {
+ info("Posting message to worker with url '" + url + "'\n");
+ return SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [url, message],
+ (urlChild, messageChild) => {
+ let worker = content._workers[urlChild];
+ worker.postMessage(messageChild);
+ return new Promise(function (resolve) {
+ worker.onmessage = function (event) {
+ worker.onmessage = null;
+ resolve(event.data);
+ };
+ });
+ }
+ );
+}
+
+/**
+ * Disable the cache in the given `tab`.
+ */
+function disableCacheInTab(tab) {
+ return SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ content.docShell.defaultLoadFlags =
+ Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
+ });
+}
diff --git a/dom/workers/test/importForeignScripts_worker.js b/dom/workers/test/importForeignScripts_worker.js
new file mode 100644
index 0000000000..4e3d65483f
--- /dev/null
+++ b/dom/workers/test/importForeignScripts_worker.js
@@ -0,0 +1,55 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var target = self;
+var response;
+
+function runTests() {
+ response = "good";
+ try {
+ importScripts("http://example.org/tests/dom/workers/test/foreign.js");
+ } catch (e) {
+ dump("Got error " + e + " when calling importScripts");
+ }
+ if (response === "good") {
+ try {
+ importScripts("redirect_to_foreign.sjs");
+ } catch (e) {
+ dump("Got error " + e + " when calling importScripts");
+ }
+ }
+ target.postMessage(response);
+
+ // Now, test a nested worker.
+ if (location.search !== "?nested") {
+ var worker = new Worker("importForeignScripts_worker.js?nested");
+
+ worker.onmessage = function (e) {
+ target.postMessage(e.data);
+ target.postMessage("finish");
+ };
+
+ worker.onerror = function () {
+ target.postMessage("nested worker error");
+ };
+
+ worker.postMessage("start");
+ }
+}
+
+onmessage = function (e) {
+ if (e.data === "start") {
+ runTests();
+ }
+};
+
+onconnect = function (e) {
+ target = e.ports[0];
+ e.ports[0].onmessage = function (msg) {
+ if (msg.data === "start") {
+ runTests();
+ }
+ };
+};
diff --git a/dom/workers/test/importScripts_3rdParty_worker.js b/dom/workers/test/importScripts_3rdParty_worker.js
new file mode 100644
index 0000000000..326d48f77a
--- /dev/null
+++ b/dom/workers/test/importScripts_3rdParty_worker.js
@@ -0,0 +1,88 @@
+const workerURL =
+ "http://mochi.test:8888/tests/dom/workers/test/importScripts_3rdParty_worker.js";
+
+onmessage = function (a) {
+ if (a.data.nested) {
+ var worker = new Worker(workerURL);
+ worker.onmessage = function (event) {
+ postMessage(event.data);
+ };
+
+ worker.onerror = function (event) {
+ event.preventDefault();
+ postMessage({
+ error: event instanceof ErrorEvent && event.filename == workerURL,
+ });
+ };
+
+ a.data.nested = false;
+ worker.postMessage(a.data);
+ return;
+ }
+
+ // This first URL will use the same origin of this script.
+ var sameOriginURL = new URL(a.data.url);
+ var fileName1 = 42;
+
+ // This is cross-origin URL.
+ var crossOriginURL = new URL(a.data.url);
+ crossOriginURL.host = "example.com";
+ crossOriginURL.port = 80;
+ var fileName2 = 42;
+
+ if (a.data.test == "none") {
+ importScripts(crossOriginURL.href);
+ return;
+ }
+
+ try {
+ importScripts(sameOriginURL.href);
+ } catch (e) {
+ if (!(e instanceof SyntaxError)) {
+ postMessage({ result: false });
+ return;
+ }
+
+ fileName1 = e.fileName;
+ }
+
+ if (fileName1 != sameOriginURL.href || !fileName1) {
+ postMessage({ result: false });
+ return;
+ }
+
+ if (a.data.test == "try") {
+ var exception;
+ try {
+ importScripts(crossOriginURL.href);
+ } catch (e) {
+ fileName2 = e.filename;
+ exception = e;
+ }
+
+ postMessage({
+ result:
+ fileName2 == workerURL &&
+ exception.name == "NetworkError" &&
+ exception.code == DOMException.NETWORK_ERR,
+ });
+ return;
+ }
+
+ if (a.data.test == "eventListener") {
+ addEventListener("error", function (event) {
+ event.preventDefault();
+ postMessage({
+ result: event instanceof ErrorEvent && event.filename == workerURL,
+ });
+ });
+ }
+
+ if (a.data.test == "onerror") {
+ onerror = function (...args) {
+ postMessage({ result: args[1] == workerURL });
+ };
+ }
+
+ importScripts(crossOriginURL.href);
+};
diff --git a/dom/workers/test/importScripts_mixedcontent.html b/dom/workers/test/importScripts_mixedcontent.html
new file mode 100644
index 0000000000..2955fdc46e
--- /dev/null
+++ b/dom/workers/test/importScripts_mixedcontent.html
@@ -0,0 +1,46 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<script>
+ function ok(cond, msg) {
+ window.parent.postMessage({status: "ok", data: cond, msg}, "*");
+ }
+ function finish() {
+ window.parent.postMessage({status: "done"}, "*");
+ }
+
+ function testSharedWorker() {
+ var sw = new SharedWorker("importForeignScripts_worker.js");
+ sw.port.onmessage = function(e) {
+ if (e.data == "finish") {
+ finish();
+ return;
+ }
+ ok(e.data === "good", "mixed content for shared workers is correctly blocked");
+ };
+
+ sw.onerror = function() {
+ ok(false, "Error on shared worker ");
+ };
+
+ sw.port.postMessage("start");
+ }
+
+ var worker = new Worker("importForeignScripts_worker.js");
+
+ worker.onmessage = function(e) {
+ if (e.data == "finish") {
+ testSharedWorker();
+ return;
+ }
+ ok(e.data === "good", "mixed content is correctly blocked");
+ }
+
+ worker.onerror = function() {
+ ok(false, "Error on worker");
+ }
+
+ worker.postMessage("start");
+</script>
diff --git a/dom/workers/test/importScripts_worker.js b/dom/workers/test/importScripts_worker.js
new file mode 100644
index 0000000000..b206df1e75
--- /dev/null
+++ b/dom/workers/test/importScripts_worker.js
@@ -0,0 +1,63 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+// Try no args. This shouldn't do anything.
+importScripts();
+
+// This caused security exceptions in the past, make sure it doesn't!
+var constructor = {}.constructor;
+
+importScripts("importScripts_worker_imported1.js");
+
+// Try to call a function defined in the imported script.
+importedScriptFunction();
+
+function tryBadScripts() {
+ var badScripts = [
+ // Has a syntax error
+ "importScripts_worker_imported3.js",
+ // Throws an exception
+ "importScripts_worker_imported4.js",
+ // Shouldn't exist!
+ "http://example.com/non-existing/importScripts_worker_foo.js",
+ // Not a valid url
+ "http://notadomain::notafile aword",
+ ];
+
+ for (var i = 0; i < badScripts.length; i++) {
+ var caughtException = false;
+ var url = badScripts[i];
+ try {
+ importScripts(url);
+ } catch (e) {
+ caughtException = true;
+ }
+ if (!caughtException) {
+ throw "Bad script didn't throw exception: " + url;
+ }
+ }
+}
+
+const url = "data:text/javascript,const startResponse = 'started';";
+importScripts(url);
+
+onmessage = function (event) {
+ switch (event.data) {
+ case "start":
+ importScripts("importScripts_worker_imported2.js");
+ importedScriptFunction2();
+ tryBadScripts();
+ postMessage(startResponse);
+ break;
+ case "stop":
+ tryBadScripts();
+ postMessage("stopped");
+ break;
+ default:
+ throw new Error("Bad message: " + event.data);
+ break;
+ }
+};
+
+tryBadScripts();
diff --git a/dom/workers/test/importScripts_worker_imported1.js b/dom/workers/test/importScripts_worker_imported1.js
new file mode 100644
index 0000000000..2a1d28c44d
--- /dev/null
+++ b/dom/workers/test/importScripts_worker_imported1.js
@@ -0,0 +1,9 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+// This caused security exceptions in the past, make sure it doesn't!
+var myConstructor = {}.constructor;
+
+// Try to call a function defined in the imported script.
+function importedScriptFunction() {}
diff --git a/dom/workers/test/importScripts_worker_imported2.js b/dom/workers/test/importScripts_worker_imported2.js
new file mode 100644
index 0000000000..3f275e237b
--- /dev/null
+++ b/dom/workers/test/importScripts_worker_imported2.js
@@ -0,0 +1,9 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+// This caused security exceptions in the past, make sure it doesn't!
+var myConstructor2 = {}.constructor;
+
+// Try to call a function defined in the imported script.
+function importedScriptFunction2() {}
diff --git a/dom/workers/test/importScripts_worker_imported3.js b/dom/workers/test/importScripts_worker_imported3.js
new file mode 100644
index 0000000000..c54be3e5f7
--- /dev/null
+++ b/dom/workers/test/importScripts_worker_imported3.js
@@ -0,0 +1,6 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+// Deliberate syntax error, should generate a worker exception!
+for (var index = 0; index < 100) {}
diff --git a/dom/workers/test/importScripts_worker_imported4.js b/dom/workers/test/importScripts_worker_imported4.js
new file mode 100644
index 0000000000..82f8708c59
--- /dev/null
+++ b/dom/workers/test/importScripts_worker_imported4.js
@@ -0,0 +1,6 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+// Deliberate throw, should generate a worker exception!
+throw new Error("Bah!");
diff --git a/dom/workers/test/instanceof_worker.js b/dom/workers/test/instanceof_worker.js
new file mode 100644
index 0000000000..a7f3ab418d
--- /dev/null
+++ b/dom/workers/test/instanceof_worker.js
@@ -0,0 +1,16 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+onmessage = function (event) {
+ postMessage({
+ event: "XMLHttpRequest",
+ status: new XMLHttpRequest() instanceof XMLHttpRequest,
+ last: false,
+ });
+ postMessage({
+ event: "XMLHttpRequestUpload",
+ status: new XMLHttpRequest().upload instanceof XMLHttpRequestUpload,
+ last: true,
+ });
+};
diff --git a/dom/workers/test/invalid.js b/dom/workers/test/invalid.js
new file mode 100644
index 0000000000..8912b7ee06
--- /dev/null
+++ b/dom/workers/test/invalid.js
@@ -0,0 +1 @@
+invalid>
diff --git a/dom/workers/test/json_worker.js b/dom/workers/test/json_worker.js
new file mode 100644
index 0000000000..229ac954c9
--- /dev/null
+++ b/dom/workers/test/json_worker.js
@@ -0,0 +1,354 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+var cyclicalObject = {};
+cyclicalObject.foo = cyclicalObject;
+
+var cyclicalArray = [];
+cyclicalArray.push(cyclicalArray);
+
+function makeNested(obj, count) {
+ var innermostobj;
+ for (var i = 0; i < count; i++) {
+ obj.foo = { bar: 5 };
+ innermostobj = obj.foo;
+ obj = innermostobj;
+ }
+ return innermostobj;
+}
+
+var nestedObject = {};
+makeNested(nestedObject, 100);
+
+var cyclicalObject = {};
+var innermost = makeNested(cyclicalObject, 1000);
+innermost.baz = cyclicalObject;
+
+var objectWithSaneGetter = {};
+objectWithSaneGetter.__defineGetter__("foo", function () {
+ return 5;
+});
+
+// We don't walk prototype chains for cloning so this won't actually do much...
+function objectWithSaneGetter2() {}
+objectWithSaneGetter2.prototype = {
+ get foo() {
+ return 5;
+ },
+};
+
+const throwingGetterThrownString = "bad";
+
+var objectWithThrowingGetter = {};
+objectWithThrowingGetter.__defineGetter__("foo", function () {
+ throw throwingGetterThrownString;
+});
+
+var typedArrayWithValues = new Int8Array(5);
+for (let index in typedArrayWithValues) {
+ typedArrayWithValues[index] = index;
+}
+
+var typedArrayWithFunBuffer = new Int8Array(4);
+for (let index in typedArrayWithFunBuffer) {
+ typedArrayWithFunBuffer[index] = 255;
+}
+
+var typedArrayWithFunBuffer2 = new Int32Array(typedArrayWithFunBuffer.buffer);
+
+var xhr = new XMLHttpRequest();
+
+var messages = [
+ {
+ type: "object",
+ value: {},
+ jsonValue: "{}",
+ },
+ {
+ type: "object",
+ value: { foo: "bar" },
+ jsonValue: '{"foo":"bar"}',
+ },
+ {
+ type: "object",
+ value: { foo: "bar", foo2: { bee: "bop" } },
+ jsonValue: '{"foo":"bar","foo2":{"bee":"bop"}}',
+ },
+ {
+ type: "object",
+ value: { foo: "bar", foo2: { bee: "bop" }, foo3: "baz" },
+ jsonValue: '{"foo":"bar","foo2":{"bee":"bop"},"foo3":"baz"}',
+ },
+ {
+ type: "object",
+ value: { foo: "bar", foo2: [1, 2, 3] },
+ jsonValue: '{"foo":"bar","foo2":[1,2,3]}',
+ },
+ {
+ type: "object",
+ value: cyclicalObject,
+ },
+ {
+ type: "object",
+ value: [null, 2, false, cyclicalObject],
+ },
+ {
+ type: "object",
+ value: cyclicalArray,
+ },
+ {
+ type: "object",
+ value: { foo: 1, bar: cyclicalArray },
+ },
+ {
+ type: "object",
+ value: nestedObject,
+ jsonValue: JSON.stringify(nestedObject),
+ },
+ {
+ type: "object",
+ value: cyclicalObject,
+ },
+ {
+ type: "object",
+ value: objectWithSaneGetter,
+ jsonValue: '{"foo":5}',
+ },
+ {
+ type: "object",
+ value: new objectWithSaneGetter2(),
+ jsonValue: "{}",
+ },
+ {
+ type: "object",
+ value: objectWithThrowingGetter,
+ exception: true,
+ },
+ {
+ type: "object",
+ array: true,
+ value: [9, 8, 7],
+ jsonValue: "[9,8,7]",
+ },
+ {
+ type: "object",
+ array: true,
+ value: [9, false, 10.5, { foo: "bar" }],
+ jsonValue: '[9,false,10.5,{"foo":"bar"}]',
+ },
+ {
+ type: "object",
+ shouldEqual: true,
+ value: null,
+ },
+ {
+ type: "undefined",
+ shouldEqual: true,
+ value: undefined,
+ },
+ {
+ type: "string",
+ shouldEqual: true,
+ value: "Hello",
+ },
+ {
+ type: "string",
+ shouldEqual: true,
+ value: JSON.stringify({ foo: "bar" }),
+ compareValue: '{"foo":"bar"}',
+ },
+ {
+ type: "number",
+ shouldEqual: true,
+ value: 1,
+ },
+ {
+ type: "number",
+ shouldEqual: true,
+ value: 0,
+ },
+ {
+ type: "number",
+ shouldEqual: true,
+ value: -1,
+ },
+ {
+ type: "number",
+ shouldEqual: true,
+ value: 12345678901234567000,
+ },
+ {
+ type: "number",
+ shouldEqual: true,
+ value: -12345678901234567000,
+ },
+ {
+ type: "number",
+ shouldEqual: true,
+ value: 0.25,
+ },
+ {
+ type: "number",
+ shouldEqual: true,
+ value: -0.25,
+ },
+ {
+ type: "boolean",
+ shouldEqual: true,
+ value: true,
+ },
+ {
+ type: "boolean",
+ shouldEqual: true,
+ value: false,
+ },
+ {
+ type: "object",
+ value(foo) {
+ return "Bad!";
+ },
+ exception: true,
+ },
+ {
+ type: "number",
+ isNaN: true,
+ value: NaN,
+ },
+ {
+ type: "number",
+ isInfinity: true,
+ value: Infinity,
+ },
+ {
+ type: "number",
+ isNegativeInfinity: true,
+ value: -Infinity,
+ },
+ {
+ type: "object",
+ value: new Int32Array(10),
+ jsonValue: '{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0}',
+ },
+ {
+ type: "object",
+ value: new Float32Array(5),
+ jsonValue: '{"0":0,"1":0,"2":0,"3":0,"4":0}',
+ },
+ {
+ type: "object",
+ value: typedArrayWithValues,
+ jsonValue: '{"0":0,"1":1,"2":2,"3":3,"4":4}',
+ },
+ {
+ type: "number",
+ value: typedArrayWithValues[2],
+ compareValue: 2,
+ shouldEqual: true,
+ },
+ {
+ type: "object",
+ value: typedArrayWithValues.buffer,
+ jsonValue: "{}",
+ },
+ {
+ type: "object",
+ value: typedArrayWithFunBuffer2,
+ jsonValue: '{"0":-1}',
+ },
+ {
+ type: "object",
+ value: { foo: typedArrayWithFunBuffer2 },
+ jsonValue: '{"foo":{"0":-1}}',
+ },
+ {
+ type: "object",
+ value: [typedArrayWithFunBuffer2],
+ jsonValue: '[{"0":-1}]',
+ },
+ {
+ type: "object",
+ value: {
+ foo(a) {
+ alert(b);
+ },
+ },
+ exception: true,
+ },
+ {
+ type: "object",
+ value: xhr,
+ exception: true,
+ },
+ {
+ type: "number",
+ value: xhr.readyState,
+ shouldEqual: true,
+ },
+ {
+ type: "object",
+ value: { xhr },
+ exception: true,
+ },
+ {
+ type: "object",
+ value: self,
+ exception: true,
+ },
+ {
+ type: "object",
+ value: { p: ArrayBuffer.prototype },
+ exception: true,
+ },
+ {
+ type: "string",
+ shouldEqual: true,
+ value: "testFinished",
+ },
+];
+
+for (let index = 0; index < messages.length; index++) {
+ var message = messages[index];
+ if (message.hasOwnProperty("compareValue")) {
+ continue;
+ }
+ if (
+ message.hasOwnProperty("shouldEqual") ||
+ message.hasOwnProperty("shouldCompare")
+ ) {
+ message.compareValue = message.value;
+ }
+}
+
+onmessage = function (event) {
+ for (let index = 0; index < messages.length; index++) {
+ var exception = undefined;
+
+ try {
+ postMessage(messages[index].value);
+ } catch (e) {
+ if (e instanceof DOMException) {
+ if (e.code != DOMException.DATA_CLONE_ERR) {
+ throw "DOMException with the wrong code: " + e.code;
+ }
+ } else if (e != throwingGetterThrownString) {
+ throw "Exception of the wrong type: " + e;
+ }
+ exception = e;
+ }
+
+ if (
+ (exception !== undefined && !messages[index].exception) ||
+ (exception === undefined && messages[index].exception)
+ ) {
+ throw (
+ "Exception inconsistency [index = " +
+ index +
+ ", " +
+ messages[index].toSource() +
+ "]: " +
+ exception
+ );
+ }
+ }
+};
diff --git a/dom/workers/test/loadEncoding_worker.js b/dom/workers/test/loadEncoding_worker.js
new file mode 100644
index 0000000000..5e40478445
--- /dev/null
+++ b/dom/workers/test/loadEncoding_worker.js
@@ -0,0 +1,7 @@
+/*
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+*/
+// Bug 484305 - Load workers as UTF-8.
+postMessage({ encoding: "KOI8-R", text: "ðÒÉ×ÅÔ" });
+postMessage({ encoding: "UTF-8", text: "Привет" });
diff --git a/dom/workers/test/location_worker.js b/dom/workers/test/location_worker.js
new file mode 100644
index 0000000000..2f16364e1f
--- /dev/null
+++ b/dom/workers/test/location_worker.js
@@ -0,0 +1,12 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+for (var string in self.location) {
+ var value =
+ typeof self.location[string] === "function"
+ ? self.location[string]()
+ : self.location[string];
+ postMessage({ string, value });
+}
+postMessage({ string: "testfinished" });
diff --git a/dom/workers/test/longThread_worker.js b/dom/workers/test/longThread_worker.js
new file mode 100644
index 0000000000..4096354aca
--- /dev/null
+++ b/dom/workers/test/longThread_worker.js
@@ -0,0 +1,14 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+onmessage = function (event) {
+ switch (event.data) {
+ case "start":
+ for (var i = 0; i < 10000000; i++) {}
+ postMessage("done");
+ break;
+ default:
+ throw "Bad message: " + event.data;
+ }
+};
diff --git a/dom/workers/test/marionette/manifest.ini b/dom/workers/test/marionette/manifest.ini
new file mode 100644
index 0000000000..05aa421e49
--- /dev/null
+++ b/dom/workers/test/marionette/manifest.ini
@@ -0,0 +1,2 @@
+[test_service_workers_at_startup.py]
+[test_service_workers_disabled.py]
diff --git a/dom/workers/test/marionette/service_worker_utils.py b/dom/workers/test/marionette/service_worker_utils.py
new file mode 100644
index 0000000000..b29789b6f0
--- /dev/null
+++ b/dom/workers/test/marionette/service_worker_utils.py
@@ -0,0 +1,63 @@
+# 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/.
+
+import os
+
+from marionette_driver import Wait
+from marionette_harness import MarionetteTestCase
+
+
+class MarionetteServiceWorkerTestCase(MarionetteTestCase):
+ def install_service_worker(self, path):
+ install_url = self.marionette.absolute_url(path)
+ self.marionette.navigate(install_url)
+ Wait(self.marionette).until(
+ lambda _: self.is_service_worker_registered,
+ message="Service worker not successfully installed",
+ )
+
+ # Wait for the registered service worker to be stored in the Firefox
+ # profile before restarting the instance to prevent intermittent
+ # failures (Bug 1665184).
+ Wait(self.marionette, timeout=10).until(
+ lambda _: self.profile_serviceworker_txt_exists,
+ message="Service worker not stored in profile",
+ )
+
+ # self.marionette.restart(in_app=True) will restore service workers if
+ # we don't navigate away before restarting.
+ self.marionette.navigate("about:blank")
+
+ # Using @property helps avoid the case where missing parens at the call site
+ # yields an unvarying 'true' value.
+ @property
+ def profile_serviceworker_txt_exists(self):
+ return "serviceworker.txt" in os.listdir(self.marionette.profile_path)
+
+ @property
+ def is_service_worker_registered(self):
+ with self.marionette.using_context("chrome"):
+ return self.marionette.execute_script(
+ """
+ let swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+ Ci.nsIServiceWorkerManager
+ );
+ let ssm = Services.scriptSecurityManager;
+
+ let principal = ssm.createContentPrincipalFromOrigin(arguments[0]);
+
+ let serviceWorkers = swm.getAllRegistrations();
+ for (let i = 0; i < serviceWorkers.length; i++) {
+ let sw = serviceWorkers.queryElementAt(
+ i,
+ Ci.nsIServiceWorkerRegistrationInfo
+ );
+ if (sw.principal.origin == principal.origin) {
+ return true;
+ }
+ }
+ return false;
+ """,
+ script_args=(self.marionette.absolute_url(""),),
+ )
diff --git a/dom/workers/test/marionette/test_service_workers_at_startup.py b/dom/workers/test/marionette/test_service_workers_at_startup.py
new file mode 100644
index 0000000000..14ff32f87f
--- /dev/null
+++ b/dom/workers/test/marionette/test_service_workers_at_startup.py
@@ -0,0 +1,31 @@
+# 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/.
+
+import os
+import sys
+
+# Add this directory to the import path.
+sys.path.append(os.path.dirname(__file__))
+
+from marionette_driver import Wait
+from service_worker_utils import MarionetteServiceWorkerTestCase
+
+
+class ServiceWorkerAtStartupTestCase(MarionetteServiceWorkerTestCase):
+ def setUp(self):
+ super(ServiceWorkerAtStartupTestCase, self).setUp()
+ self.install_service_worker("serviceworker/install_serviceworker.html")
+
+ def tearDown(self):
+ self.marionette.restart(in_app=False, clean=True)
+ super(ServiceWorkerAtStartupTestCase, self).tearDown()
+
+ def test_registered_service_worker_after_restart(self):
+ self.marionette.restart()
+
+ Wait(self.marionette).until(
+ lambda _: self.is_service_worker_registered,
+ message="Service worker not registered after restart",
+ )
+ self.assertTrue(self.is_service_worker_registered)
diff --git a/dom/workers/test/marionette/test_service_workers_disabled.py b/dom/workers/test/marionette/test_service_workers_disabled.py
new file mode 100644
index 0000000000..deed164242
--- /dev/null
+++ b/dom/workers/test/marionette/test_service_workers_disabled.py
@@ -0,0 +1,37 @@
+# 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/.
+
+import os
+import sys
+
+# Add this directory to the import path.
+sys.path.append(os.path.dirname(__file__))
+
+from service_worker_utils import MarionetteServiceWorkerTestCase
+
+
+class ServiceWorkersDisabledTestCase(MarionetteServiceWorkerTestCase):
+ def setUp(self):
+ super(ServiceWorkersDisabledTestCase, self).setUp()
+ self.install_service_worker("serviceworker/install_serviceworker.html")
+
+ def tearDown(self):
+ self.marionette.restart(in_app=False, clean=True)
+ super(ServiceWorkersDisabledTestCase, self).tearDown()
+
+ def test_service_workers_disabled_at_startup(self):
+ # self.marionette.set_pref sets preferences after startup. Using it
+ # here causes intermittent failures.
+ self.marionette.instance.profile.set_preferences(
+ {
+ "dom.serviceWorkers.enabled": False,
+ }
+ )
+
+ self.marionette.restart()
+
+ self.assertFalse(
+ self.is_service_worker_registered,
+ "Service worker registration should have been purged",
+ )
diff --git a/dom/workers/test/mochitest.ini b/dom/workers/test/mochitest.ini
new file mode 100644
index 0000000000..66d5ea6422
--- /dev/null
+++ b/dom/workers/test/mochitest.ini
@@ -0,0 +1,249 @@
+[DEFAULT]
+support-files =
+ WorkerTest_badworker.js
+ atob_worker.js
+ blank.html
+ bug978260_worker.js
+ bug1014466_data1.txt
+ bug1014466_data2.txt
+ bug1014466_worker.js
+ bug1020226_worker.js
+ bug1020226_frame.html
+ bug998474_worker.js
+ bug1063538_worker.js
+ bug1063538.sjs
+ clearTimeouts_worker.js
+ clearTimeoutsImplicit_worker.js
+ content_worker.js
+ console_worker.js
+ consoleReplaceable_worker.js
+ csp_worker.js
+ csp_worker.js^headers^
+ dynamicImport_nested.mjs
+ dynamicImport_postMessage.mjs
+ dynamicImport_worker.js
+ 404_server.sjs
+ errorPropagation_iframe.html
+ errorPropagation_worker.js
+ errorwarning_worker.js
+ eventDispatch_worker.js
+ fibonacci_worker.js
+ file_bug1010784_worker.js
+ foreign.js
+ importForeignScripts_worker.js
+ importScripts_mixedcontent.html
+ importScripts_worker.js
+ importScripts_worker_imported1.js
+ importScripts_worker_imported2.js
+ importScripts_worker_imported3.js
+ importScripts_worker_imported4.js
+ instanceof_worker.js
+ json_worker.js
+ loadEncoding_worker.js
+ location_worker.js
+ longThread_worker.js
+ multi_sharedWorker_frame.html
+ multi_sharedWorker_sharedWorker.js
+ navigator_languages_worker.js
+ navigator_worker.js
+ newError_worker.js
+ notification_worker.js
+ notification_worker_child-child.js
+ notification_worker_child-parent.js
+ notification_permission_worker.js
+ onLine_worker.js
+ onLine_worker_child.js
+ onLine_worker_head.js
+ promise_worker.js
+ recursion_worker.js
+ recursiveOnerror_worker.js
+ redirect_to_foreign.sjs
+ rvals_worker.js
+ sharedWorker_sharedWorker.js
+ simpleThread_worker.js
+ suspend_window.html
+ suspend_worker.js
+ terminate_worker.js
+ test_csp.html^headers^
+ test_csp.js
+ referrer_worker.html
+ sourcemap_header_iframe.html
+ sourcemap_header_worker.js
+ sourcemap_header_worker.js^headers^
+ threadErrors_worker1.js
+ threadErrors_worker2.js
+ threadErrors_worker3.js
+ threadErrors_worker4.js
+ threadTimeouts_worker.js
+ throwingOnerror_worker.js
+ timeoutTracing_worker.js
+ transferable_worker.js
+ test_worker_interfaces.js
+ worker_driver.js
+ worker_wrapper.js
+ bug1060621_worker.js
+ bug1062920_worker.js
+ bug1104064_worker.js
+ worker_consoleAndBlobs.js
+ bug1132395_sharedWorker.js
+ bug1132924_worker.js
+ empty.html
+ referrer.sjs
+ referrer_test_server.sjs
+ sharedWorker_ports.js
+ sharedWorker_lifetime.js
+ worker_referrer.js
+ importScripts_3rdParty_worker.js
+ invalid.js
+ worker_bug1278777.js
+ worker_setTimeoutWith0.js
+ worker_bug1301094.js
+ script_createFile.js
+ worker_suspended.js
+ window_suspended.html
+ suspend_blank.html
+ multi_sharedWorker_manager.js
+ multi_sharedWorker_frame_nobfcache.html
+ multi_sharedWorker_frame_nobfcache.html^headers^
+ multi_sharedWorker_frame_bfcache.html
+ navigate.html
+ !/dom/notification/test/mochitest/MockServices.js
+ !/dom/notification/test/mochitest/NotificationTest.js
+ !/dom/xhr/tests/relativeLoad_import.js
+ !/dom/xhr/tests/relativeLoad_worker.js
+ !/dom/xhr/tests/relativeLoad_worker2.js
+ !/dom/xhr/tests/subdir/relativeLoad_sub_worker.js
+ !/dom/xhr/tests/subdir/relativeLoad_sub_worker2.js
+ !/dom/xhr/tests/subdir/relativeLoad_sub_import.js
+ !/dom/events/test/event_leak_utils.js
+
+[test_404.html]
+[test_atob.html]
+[test_blobConstructor.html]
+[test_blobWorkers.html]
+[test_bug949946.html]
+[test_bug978260.html]
+[test_bug998474.html]
+[test_bug1002702.html]
+[test_bug1010784.html]
+[test_bug1014466.html]
+[test_bug1020226.html]
+[test_bug1036484.html]
+[test_bug1060621.html]
+[test_bug1062920.html]
+[test_bug1063538.html]
+skip-if =
+ http3
+[test_bug1104064.html]
+[test_bug1132395.html]
+skip-if = true # bug 1176225
+[test_bug1132924.html]
+[test_chromeWorker.html]
+[test_clearTimeouts.html]
+[test_clearTimeoutsImplicit.html]
+[test_console.html]
+[test_consoleAndBlobs.html]
+[test_consoleReplaceable.html]
+[test_contentWorker.html]
+[test_csp.html]
+[test_dataURLWorker.html]
+[test_dynamicImport.html]
+[test_dynamicImport_early_termination.html]
+[test_dynamicImport_and_terminate.html]
+support-files = worker_dynamicImport.mjs
+[test_errorPropagation.html]
+skip-if =
+ http3
+[test_errorwarning.html]
+[test_eventDispatch.html]
+[test_fibonacci.html]
+[test_importScripts.html]
+[test_importScripts_mixedcontent.html]
+tags = mcb
+[test_instanceof.html]
+[test_json.html]
+[test_loadEncoding.html]
+[test_loadError.html]
+[test_location.html]
+skip-if =
+ http3
+[test_longThread.html]
+[test_multi_sharedWorker.html]
+skip-if =
+ http3
+[test_multi_sharedWorker_lifetimes_nobfcache.html]
+[test_multi_sharedWorker_lifetimes_bfcache.html]
+[test_navigator.html]
+support-files =
+ test_navigator.js
+ test_navigator_iframe.html
+ test_navigator_iframe.js
+skip-if =
+ http3
+[test_navigator_secureContext.html]
+scheme=https
+support-files =
+ test_navigator.js
+ test_navigator_iframe.html
+ test_navigator_iframe.js
+[test_navigator_languages.html]
+[test_newError.html]
+[test_notification.html]
+[test_notification_child.html]
+[test_notification_permission.html]
+[test_onLine.html]
+[test_promise.html]
+[test_promise_resolved_with_string.html]
+[test_recursion.html]
+[test_recursiveOnerror.html]
+skip-if =
+ http3
+[test_resolveWorker.html]
+[test_resolveWorker-assignment.html]
+[test_rvals.html]
+[test_sharedWorker.html]
+[test_sharedWorker_thirdparty.html]
+support-files =
+ sharedWorker_thirdparty_frame.html
+ sharedWorker_thirdparty_window.html
+skip-if =
+ http3
+[test_simpleThread.html]
+skip-if =
+ http3
+[test_suspend.html]
+[test_terminate.html]
+[test_threadErrors.html]
+[test_threadTimeouts.html]
+[test_throwingOnerror.html]
+[test_timeoutTracing.html]
+[test_transferable.html]
+[test_worker_interfaces.html]
+skip-if =
+ http3
+[test_worker_interfaces_secureContext.html]
+scheme=https
+[test_referrer.html]
+[test_referrer_header_worker.html]
+skip-if =
+ http3
+[test_importScripts_3rdparty.html]
+skip-if =
+ http3
+[test_sharedWorker_ports.html]
+[test_sharedWorker_lifetime.html]
+[test_navigator_workers_hardwareConcurrency.html]
+[test_bug1278777.html]
+[test_setTimeoutWith0.html]
+[test_bug1301094.html]
+[test_subworkers_suspended.html]
+scheme=https
+[test_bug1317725.html]
+support-files = test_bug1317725.js
+[test_bug1824498.html]
+support-files = worker_bug1824498.mjs
+[test_sharedworker_event_listener_leaks.html]
+skip-if =
+ (bits == 64 && os == 'linux' && asan && !debug) # Disabled on Linux64 opt asan, bug 1493563
+ os == "win" && debug && xorigin # high frequency intermittent
+[test_fileReaderSync_when_closing.html]
diff --git a/dom/workers/test/multi_sharedWorker_frame.html b/dom/workers/test/multi_sharedWorker_frame.html
new file mode 100644
index 0000000000..94866b918d
--- /dev/null
+++ b/dom/workers/test/multi_sharedWorker_frame.html
@@ -0,0 +1,58 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for SharedWorker</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ "use strict";
+
+ function postMessageToParentOrOpener(message) {
+ if (parent != window) {
+ parent.postMessage(message, "*");
+ }
+ }
+
+ function debug(message) {
+ if (typeof(message) != "string") {
+ throw new Error("debug() only accepts strings!");
+ }
+ postMessageToParentOrOpener(message);
+ }
+
+ let worker;
+
+ window.addEventListener("message", function(event) {
+ if (!worker) {
+ worker = new SharedWorker("multi_sharedWorker_sharedWorker.js",
+ "FrameWorker");
+ worker.onerror = function(error) {
+ debug("Worker error: " + error.message);
+ error.preventDefault();
+
+ let data = {
+ type: "error",
+ message: error.message,
+ filename: error.filename,
+ lineno: error.lineno,
+ isErrorEvent: error instanceof ErrorEvent
+ };
+ postMessageToParentOrOpener(data);
+ };
+
+ worker.port.onmessage = function(msg) {
+ debug("Worker message: " + JSON.stringify(msg.data));
+ postMessageToParentOrOpener(msg.data);
+ };
+ }
+
+ debug("Posting message: " + JSON.stringify(event.data));
+ worker.port.postMessage(event.data);
+ });
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/multi_sharedWorker_frame_bfcache.html b/dom/workers/test/multi_sharedWorker_frame_bfcache.html
new file mode 100644
index 0000000000..dea14a8f78
--- /dev/null
+++ b/dom/workers/test/multi_sharedWorker_frame_bfcache.html
@@ -0,0 +1,13 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for SharedWorker</title>
+ </head>
+ <body>
+ <script type="text/javascript" src=multi_sharedWorker_manager.js></script>
+ </body>
+</html>
diff --git a/dom/workers/test/multi_sharedWorker_frame_nobfcache.html b/dom/workers/test/multi_sharedWorker_frame_nobfcache.html
new file mode 100644
index 0000000000..dea14a8f78
--- /dev/null
+++ b/dom/workers/test/multi_sharedWorker_frame_nobfcache.html
@@ -0,0 +1,13 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for SharedWorker</title>
+ </head>
+ <body>
+ <script type="text/javascript" src=multi_sharedWorker_manager.js></script>
+ </body>
+</html>
diff --git a/dom/workers/test/multi_sharedWorker_frame_nobfcache.html^headers^ b/dom/workers/test/multi_sharedWorker_frame_nobfcache.html^headers^
new file mode 100644
index 0000000000..2567dc2fe5
--- /dev/null
+++ b/dom/workers/test/multi_sharedWorker_frame_nobfcache.html^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store \ No newline at end of file
diff --git a/dom/workers/test/multi_sharedWorker_manager.js b/dom/workers/test/multi_sharedWorker_manager.js
new file mode 100644
index 0000000000..c0a9455ef1
--- /dev/null
+++ b/dom/workers/test/multi_sharedWorker_manager.js
@@ -0,0 +1,58 @@
+var query = window.location.search;
+var bc = new BroadcastChannel("bugSharedWorkerLiftetime" + query);
+bc.onmessage = msgEvent => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "postToWorker") {
+ postToWorker(msg.workerMessage);
+ } else if (command == "navigate") {
+ window.location = msg.location;
+ } else if (command == "finish") {
+ bc.postMessage({ command: "finished" });
+ bc.close();
+ window.close();
+ }
+};
+
+window.onload = () => {
+ bc.postMessage({ command: "loaded" });
+};
+
+function debug(message) {
+ if (typeof message != "string") {
+ throw new Error("debug() only accepts strings!");
+ }
+ bc.postMessage({ command: "debug", message });
+}
+
+let worker;
+
+function postToWorker(msg) {
+ if (!worker) {
+ worker = new SharedWorker(
+ "multi_sharedWorker_sharedWorker.js",
+ "FrameWorker"
+ );
+ worker.onerror = function (error) {
+ debug("Worker error: " + error.message);
+ error.preventDefault();
+
+ let data = {
+ type: "error",
+ message: error.message,
+ filename: error.filename,
+ lineno: error.lineno,
+ isErrorEvent: error instanceof ErrorEvent,
+ };
+ bc.postMessage({ command: "fromWorker", workerMessage: data });
+ };
+
+ worker.port.onmessage = function (message) {
+ debug("Worker message: " + JSON.stringify(message.data));
+ bc.postMessage({ command: "fromWorker", workerMessage: message.data });
+ };
+ }
+
+ debug("Posting message: " + JSON.stringify(msg));
+ worker.port.postMessage(msg);
+}
diff --git a/dom/workers/test/multi_sharedWorker_sharedWorker.js b/dom/workers/test/multi_sharedWorker_sharedWorker.js
new file mode 100644
index 0000000000..ec0b459d55
--- /dev/null
+++ b/dom/workers/test/multi_sharedWorker_sharedWorker.js
@@ -0,0 +1,72 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+if (self.name != "FrameWorker") {
+ throw new Error("Bad worker name: " + self.name);
+}
+
+var registeredPorts = [];
+var errorCount = 0;
+var storedData;
+
+self.onconnect = function (event) {
+ var port = event.ports[0];
+
+ if (registeredPorts.length) {
+ let data = {
+ type: "connect",
+ };
+
+ registeredPorts.forEach(function (registeredPort) {
+ registeredPort.postMessage(data);
+ });
+ }
+
+ port.onmessage = function (msg) {
+ switch (msg.data.command) {
+ case "start":
+ break;
+
+ case "error":
+ throw new Error("Expected");
+
+ case "store":
+ storedData = msg.data.data;
+ break;
+
+ case "retrieve":
+ var data = {
+ type: "result",
+ data: storedData,
+ };
+ port.postMessage(data);
+ break;
+
+ default:
+ throw new Error("Unknown command '" + error.data.command + "'");
+ }
+ };
+
+ registeredPorts.push(port);
+};
+
+self.onerror = function (message, filename, lineno) {
+ if (!errorCount++) {
+ var data = {
+ type: "worker-error",
+ message,
+ filename,
+ lineno,
+ };
+
+ registeredPorts.forEach(function (registeredPort) {
+ registeredPort.postMessage(data);
+ });
+
+ // Prevent the error from propagating the first time only.
+ return true;
+ }
+};
diff --git a/dom/workers/test/navigate.html b/dom/workers/test/navigate.html
new file mode 100644
index 0000000000..e38ab4540a
--- /dev/null
+++ b/dom/workers/test/navigate.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<script>
+ var bc = new BroadcastChannel("navigate");
+ bc.onmessage = (event) => {
+ if (event.data.command == "navigate") {
+ window.location = event.data.location;
+ bc.close();
+ }
+ }
+ window.onload = () => {
+ bc.postMessage({command: "loaded"});
+ }
+</script>
diff --git a/dom/workers/test/navigator_languages_worker.js b/dom/workers/test/navigator_languages_worker.js
new file mode 100644
index 0000000000..22ece09ef2
--- /dev/null
+++ b/dom/workers/test/navigator_languages_worker.js
@@ -0,0 +1,11 @@
+var active = true;
+onmessage = function (e) {
+ if (e.data == "finish") {
+ active = false;
+ return;
+ }
+
+ if (active) {
+ postMessage(navigator.languages);
+ }
+};
diff --git a/dom/workers/test/navigator_worker.js b/dom/workers/test/navigator_worker.js
new file mode 100644
index 0000000000..e9379032a6
--- /dev/null
+++ b/dom/workers/test/navigator_worker.js
@@ -0,0 +1,85 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// IMPORTANT: Do not change the list below without review from a DOM peer!
+var supportedProps = [
+ "appCodeName",
+ "appName",
+ "appVersion",
+ "platform",
+ "product",
+ "userAgent",
+ "onLine",
+ "language",
+ "languages",
+ { name: "locks", isSecureContext: true },
+ "mediaCapabilities",
+ "hardwareConcurrency",
+ { name: "storage", isSecureContext: true },
+ "connection",
+];
+
+self.onmessage = function (event) {
+ if (!event || !event.data) {
+ return;
+ }
+
+ startTest(event.data);
+};
+
+function startTest(channelData) {
+ // Prepare the interface map showing if a propery should exist in this build.
+ // For example, if interfaceMap[foo] = true means navigator.foo should exist.
+ var interfaceMap = {};
+
+ for (var prop of supportedProps) {
+ if (typeof prop === "string") {
+ interfaceMap[prop] = true;
+ continue;
+ }
+
+ if (
+ prop.nightly === !channelData.isNightly ||
+ prop.release === !channelData.isRelease ||
+ prop.isSecureContext === !isSecureContext
+ ) {
+ interfaceMap[prop.name] = false;
+ continue;
+ }
+
+ interfaceMap[prop.name] = true;
+ }
+
+ for (var prop in navigator) {
+ // Make sure the list is current!
+ if (!interfaceMap[prop]) {
+ throw "Navigator has the '" + prop + "' property that isn't in the list!";
+ }
+ }
+
+ var obj;
+
+ for (var prop in interfaceMap) {
+ // Skip the property that is not supposed to exist in this build.
+ if (!interfaceMap[prop]) {
+ continue;
+ }
+
+ if (typeof navigator[prop] == "undefined") {
+ throw "Navigator has no '" + prop + "' property!";
+ }
+
+ obj = { name: prop };
+ obj.value = navigator[prop];
+
+ postMessage(JSON.stringify(obj));
+ }
+
+ obj = {
+ name: "testFinished",
+ };
+
+ postMessage(JSON.stringify(obj));
+}
diff --git a/dom/workers/test/newError_worker.js b/dom/workers/test/newError_worker.js
new file mode 100644
index 0000000000..46e6226f73
--- /dev/null
+++ b/dom/workers/test/newError_worker.js
@@ -0,0 +1,5 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+throw new Error("foo!");
diff --git a/dom/workers/test/notification_permission_worker.js b/dom/workers/test/notification_permission_worker.js
new file mode 100644
index 0000000000..0e6b96d975
--- /dev/null
+++ b/dom/workers/test/notification_permission_worker.js
@@ -0,0 +1,59 @@
+function info(message) {
+ dump("INFO: " + message + "\n");
+}
+
+function ok(test, message) {
+ postMessage({ type: "ok", test, message });
+}
+
+function is(a, b, message) {
+ postMessage({ type: "is", test1: a, test2: b, message });
+}
+
+if (self.Notification) {
+ var steps = [
+ function (done) {
+ info("Test notification permission");
+ ok(typeof Notification === "function", "Notification constructor exists");
+ ok(
+ Notification.permission === "denied",
+ "Notification.permission is denied."
+ );
+
+ var n = new Notification("Hello");
+ n.onerror = function (e) {
+ ok(true, "error called due to permission denied.");
+ done();
+ };
+ },
+ ];
+
+ onmessage = function (e) {
+ var context = {};
+ (function executeRemainingTests(remainingTests) {
+ if (!remainingTests.length) {
+ postMessage({ type: "finish" });
+ return;
+ }
+
+ var nextTest = remainingTests.shift();
+ var finishTest = executeRemainingTests.bind(null, remainingTests);
+ var startTest = nextTest.call.bind(nextTest, context, finishTest);
+
+ try {
+ startTest();
+ // if no callback was defined for test function,
+ // we must manually invoke finish to continue
+ if (nextTest.length === 0) {
+ finishTest();
+ }
+ } catch (ex) {
+ ok(false, "Test threw exception! " + nextTest + " " + ex);
+ finishTest();
+ }
+ })(steps);
+ };
+} else {
+ ok(true, "Notifications are not enabled in workers on the platform.");
+ postMessage({ type: "finish" });
+}
diff --git a/dom/workers/test/notification_worker.js b/dom/workers/test/notification_worker.js
new file mode 100644
index 0000000000..87aa02ac05
--- /dev/null
+++ b/dom/workers/test/notification_worker.js
@@ -0,0 +1,104 @@
+function ok(test, message) {
+ postMessage({ type: "ok", test, message });
+}
+
+function is(a, b, message) {
+ postMessage({ type: "is", test1: a, test2: b, message });
+}
+
+if (self.Notification) {
+ var steps = [
+ function () {
+ ok(typeof Notification === "function", "Notification constructor exists");
+ ok(Notification.permission, "Notification.permission exists");
+ ok(
+ typeof Notification.requestPermission === "undefined",
+ "Notification.requestPermission should not exist"
+ );
+ },
+
+ function (done) {
+ var options = {
+ dir: "auto",
+ lang: "",
+ body: "This is a notification body",
+ tag: "sometag",
+ icon: "icon.png",
+ data: ["a complex object that should be", { structured: "cloned" }],
+ mozbehavior: { vibrationPattern: [30, 200, 30] },
+ };
+ var notification = new Notification("This is a title", options);
+
+ ok(notification !== undefined, "Notification exists");
+ is(notification.onclick, null, "onclick() should be null");
+ is(notification.onshow, null, "onshow() should be null");
+ is(notification.onerror, null, "onerror() should be null");
+ is(notification.onclose, null, "onclose() should be null");
+ is(typeof notification.close, "function", "close() should exist");
+
+ is(notification.dir, options.dir, "auto should get set");
+ is(notification.lang, options.lang, "lang should get set");
+ is(notification.body, options.body, "body should get set");
+ is(notification.tag, options.tag, "tag should get set");
+ is(notification.icon, options.icon, "icon should get set");
+ is(
+ notification.data[0],
+ "a complex object that should be",
+ "data item 0 should be a matching string"
+ );
+ is(
+ notification.data[1].structured,
+ "cloned",
+ "data item 1 should be a matching object literal"
+ );
+
+ // store notification in test context
+ this.notification = notification;
+
+ notification.onshow = function () {
+ ok(true, "onshow handler should be called");
+ done();
+ };
+ },
+
+ function (done) {
+ var notification = this.notification;
+
+ notification.onclose = function () {
+ ok(true, "onclose handler should be called");
+ done();
+ };
+
+ notification.close();
+ },
+ ];
+
+ onmessage = function (e) {
+ var context = {};
+ (function executeRemainingTests(remainingTests) {
+ if (!remainingTests.length) {
+ postMessage({ type: "finish" });
+ return;
+ }
+
+ var nextTest = remainingTests.shift();
+ var finishTest = executeRemainingTests.bind(null, remainingTests);
+ var startTest = nextTest.call.bind(nextTest, context, finishTest);
+
+ try {
+ startTest();
+ // if no callback was defined for test function,
+ // we must manually invoke finish to continue
+ if (nextTest.length === 0) {
+ finishTest();
+ }
+ } catch (ex) {
+ ok(false, "Test threw exception! " + nextTest + " " + ex);
+ finishTest();
+ }
+ })(steps);
+ };
+} else {
+ ok(true, "Notifications are not enabled in workers on the platform.");
+ postMessage({ type: "finish" });
+}
diff --git a/dom/workers/test/notification_worker_child-child.js b/dom/workers/test/notification_worker_child-child.js
new file mode 100644
index 0000000000..236e314e47
--- /dev/null
+++ b/dom/workers/test/notification_worker_child-child.js
@@ -0,0 +1,93 @@
+function ok(test, message) {
+ postMessage({ type: "ok", test, message });
+}
+
+function is(a, b, message) {
+ postMessage({ type: "is", test1: a, test2: b, message });
+}
+
+if (self.Notification) {
+ var steps = [
+ function () {
+ ok(typeof Notification === "function", "Notification constructor exists");
+ ok(Notification.permission, "Notification.permission exists");
+ ok(
+ typeof Notification.requestPermission === "undefined",
+ "Notification.requestPermission should not exist"
+ );
+ //ok(typeof Notification.get === "function", "Notification.get exists");
+ },
+
+ function (done) {
+ var options = {
+ dir: "auto",
+ lang: "",
+ body: "This is a notification body",
+ tag: "sometag",
+ icon: "icon.png",
+ };
+ var notification = new Notification("This is a title", options);
+
+ ok(notification !== undefined, "Notification exists");
+ is(notification.onclick, null, "onclick() should be null");
+ is(notification.onshow, null, "onshow() should be null");
+ is(notification.onerror, null, "onerror() should be null");
+ is(notification.onclose, null, "onclose() should be null");
+ is(typeof notification.close, "function", "close() should exist");
+
+ is(notification.dir, options.dir, "auto should get set");
+ is(notification.lang, options.lang, "lang should get set");
+ is(notification.body, options.body, "body should get set");
+ is(notification.tag, options.tag, "tag should get set");
+ is(notification.icon, options.icon, "icon should get set");
+
+ // store notification in test context
+ this.notification = notification;
+
+ notification.onshow = function () {
+ ok(true, "onshow handler should be called");
+ done();
+ };
+ },
+
+ function (done) {
+ var notification = this.notification;
+
+ notification.onclose = function () {
+ ok(true, "onclose handler should be called");
+ done();
+ };
+
+ notification.close();
+ },
+ ];
+
+ onmessage = function (e) {
+ var context = {};
+ (function executeRemainingTests(remainingTests) {
+ if (!remainingTests.length) {
+ postMessage({ type: "finish" });
+ return;
+ }
+
+ var nextTest = remainingTests.shift();
+ var finishTest = executeRemainingTests.bind(null, remainingTests);
+ var startTest = nextTest.call.bind(nextTest, context, finishTest);
+
+ try {
+ startTest();
+ // if no callback was defined for test function,
+ // we must manually invoke finish to continue
+ if (nextTest.length === 0) {
+ finishTest();
+ }
+ } catch (ex) {
+ ok(false, "Test threw exception! " + nextTest + " " + ex);
+ finishTest();
+ }
+ })(steps);
+ };
+} else {
+ ok(true, "Notifications are not enabled in workers on the platform.");
+ postMessage({ type: "finish" });
+}
diff --git a/dom/workers/test/notification_worker_child-parent.js b/dom/workers/test/notification_worker_child-parent.js
new file mode 100644
index 0000000000..45dd061993
--- /dev/null
+++ b/dom/workers/test/notification_worker_child-parent.js
@@ -0,0 +1,26 @@
+function ok(test, message) {
+ postMessage({ type: "ok", test, message });
+}
+
+function is(a, b, message) {
+ postMessage({ type: "is", test1: a, test2: b, message });
+}
+
+if (self.Notification) {
+ var child = new Worker("notification_worker_child-child.js");
+ child.onerror = function (e) {
+ ok(false, "Error loading child worker " + e);
+ postMessage({ type: "finish" });
+ };
+
+ child.onmessage = function (e) {
+ postMessage(e.data);
+ };
+
+ onmessage = function (e) {
+ child.postMessage("start");
+ };
+} else {
+ ok(true, "Notifications are not enabled in workers on the platform.");
+ postMessage({ type: "finish" });
+}
diff --git a/dom/workers/test/onLine_worker.js b/dom/workers/test/onLine_worker.js
new file mode 100644
index 0000000000..94c10c699c
--- /dev/null
+++ b/dom/workers/test/onLine_worker.js
@@ -0,0 +1,70 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+importScripts("onLine_worker_head.js");
+
+var N_CHILDREN = 3;
+var children = [];
+var finishedChildrenCount = 0;
+var lastTest = false;
+
+for (var event of ["online", "offline"]) {
+ addEventListener(
+ event,
+ makeHandler(
+ "addEventListener('%1', ..., false)",
+ event,
+ 1,
+ "Parent Worker"
+ ),
+ false
+ );
+}
+
+onmessage = function (e) {
+ if (e.data === "lastTest") {
+ children.forEach(function (w) {
+ w.postMessage({ type: "lastTest" });
+ });
+ lastTest = true;
+ }
+};
+
+function setupChildren(cb) {
+ var readyCount = 0;
+ for (var i = 0; i < N_CHILDREN; ++i) {
+ var w = new Worker("onLine_worker_child.js");
+ children.push(w);
+
+ w.onerror = function (e) {
+ info("Error creating child " + e.message);
+ };
+
+ w.onmessage = function (e) {
+ if (e.data.type === "ready") {
+ info("Got ready from child");
+ readyCount++;
+ if (readyCount === N_CHILDREN) {
+ cb();
+ }
+ } else if (e.data.type === "finished") {
+ finishedChildrenCount++;
+
+ if (lastTest && finishedChildrenCount === N_CHILDREN) {
+ postMessage({ type: "finished" });
+ children = [];
+ close();
+ }
+ } else if (e.data.type === "ok") {
+ // Pass on test to page.
+ postMessage(e.data);
+ }
+ };
+ }
+}
+
+setupChildren(function () {
+ postMessage({ type: "ready" });
+});
diff --git a/dom/workers/test/onLine_worker_child.js b/dom/workers/test/onLine_worker_child.js
new file mode 100644
index 0000000000..92542c018f
--- /dev/null
+++ b/dom/workers/test/onLine_worker_child.js
@@ -0,0 +1,91 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+function info(text) {
+ dump("Test for Bug 925437: worker: " + text + "\n");
+}
+
+function ok(test, message) {
+ postMessage({ type: "ok", test, message });
+}
+
+/**
+ * Returns a handler function for an online/offline event. The returned handler
+ * ensures the passed event object has expected properties and that the handler
+ * is called at the right moment (according to the gState variable).
+ * @param nameTemplate The string identifying the hanlder. '%1' in that
+ * string will be replaced with the event name.
+ * @param eventName 'online' or 'offline'
+ * @param expectedState value of gState at the moment the handler is called.
+ * The handler increases gState by one before checking
+ * if it matches expectedState.
+ */
+function makeHandler(nameTemplate, eventName, expectedState, prefix, custom) {
+ prefix += ": ";
+ return function (e) {
+ var name = nameTemplate.replace(/%1/, eventName);
+ ok(e.constructor == Event, prefix + "event should be an Event");
+ ok(e.type == eventName, prefix + "event type should be " + eventName);
+ ok(!e.bubbles, prefix + "event should not bubble");
+ ok(!e.cancelable, prefix + "event should not be cancelable");
+ ok(
+ e.target == self,
+ prefix + "the event target should be the worker scope"
+ );
+ ok(
+ eventName == "online" ? navigator.onLine : !navigator.onLine,
+ prefix +
+ "navigator.onLine " +
+ navigator.onLine +
+ " should reflect event " +
+ eventName
+ );
+
+ if (custom) {
+ custom();
+ }
+ };
+}
+
+var lastTest = false;
+
+function lastTestTest() {
+ if (lastTest) {
+ postMessage({ type: "finished" });
+ close();
+ }
+}
+
+for (var event of ["online", "offline"]) {
+ addEventListener(
+ event,
+ makeHandler(
+ "addEventListener('%1', ..., false)",
+ event,
+ 1,
+ "Child Worker",
+ lastTestTest
+ ),
+ false
+ );
+}
+
+onmessage = function (e) {
+ if (e.data.type === "lastTest") {
+ lastTest = true;
+ } else if (e.data.type === "navigatorState") {
+ ok(
+ e.data.state === navigator.onLine,
+ "Child and parent navigator state should match"
+ );
+ }
+};
+
+postMessage({ type: "ready" });
diff --git a/dom/workers/test/onLine_worker_head.js b/dom/workers/test/onLine_worker_head.js
new file mode 100644
index 0000000000..632821b1f4
--- /dev/null
+++ b/dom/workers/test/onLine_worker_head.js
@@ -0,0 +1,50 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+function info(text) {
+ dump("Test for Bug 925437: worker: " + text + "\n");
+}
+
+function ok(test, message) {
+ postMessage({ type: "ok", test, message });
+}
+
+/**
+ * Returns a handler function for an online/offline event. The returned handler
+ * ensures the passed event object has expected properties and that the handler
+ * is called at the right moment (according to the gState variable).
+ * @param nameTemplate The string identifying the hanlder. '%1' in that
+ * string will be replaced with the event name.
+ * @param eventName 'online' or 'offline'
+ * @param expectedState value of gState at the moment the handler is called.
+ * The handler increases gState by one before checking
+ * if it matches expectedState.
+ */
+function makeHandler(nameTemplate, eventName, expectedState, prefix, custom) {
+ prefix += ": ";
+ return function (e) {
+ var name = nameTemplate.replace(/%1/, eventName);
+ ok(e.constructor == Event, prefix + "event should be an Event");
+ ok(e.type == eventName, prefix + "event type should be " + eventName);
+ ok(!e.bubbles, prefix + "event should not bubble");
+ ok(!e.cancelable, prefix + "event should not be cancelable");
+ ok(
+ e.target == self,
+ prefix + "the event target should be the worker scope"
+ );
+ ok(
+ eventName == "online" ? navigator.onLine : !navigator.onLine,
+ prefix +
+ "navigator.onLine " +
+ navigator.onLine +
+ " should reflect event " +
+ eventName
+ );
+
+ if (custom) {
+ custom();
+ }
+ };
+}
diff --git a/dom/workers/test/promise_worker.js b/dom/workers/test/promise_worker.js
new file mode 100644
index 0000000000..fd4a9177d6
--- /dev/null
+++ b/dom/workers/test/promise_worker.js
@@ -0,0 +1,1007 @@
+"use strict";
+
+function ok(a, msg) {
+ dump("OK: " + !!a + " => " + a + " " + msg + "\n");
+ postMessage({ type: "status", status: !!a, msg: a + ": " + msg });
+}
+
+function is(a, b, msg) {
+ dump("IS: " + (a === b) + " => " + a + " | " + b + " " + msg + "\n");
+ postMessage({
+ type: "status",
+ status: a === b,
+ msg: a + " === " + b + ": " + msg,
+ });
+}
+
+function isnot(a, b, msg) {
+ dump("ISNOT: " + (a !== b) + " => " + a + " | " + b + " " + msg + "\n");
+ postMessage({
+ type: "status",
+ status: a !== b,
+ msg: a + " !== " + b + ": " + msg,
+ });
+}
+
+function promiseResolve() {
+ ok(Promise, "Promise object should exist");
+
+ var promise = new Promise(function (resolve, reject) {
+ ok(resolve, "Promise.resolve exists");
+ ok(reject, "Promise.reject exists");
+
+ resolve(42);
+ }).then(
+ function (what) {
+ ok(true, "Then - resolveCb has been called");
+ is(what, 42, "ResolveCb received 42");
+ runTest();
+ },
+ function () {
+ ok(false, "Then - rejectCb has been called");
+ runTest();
+ }
+ );
+}
+
+function promiseResolveNoArg() {
+ var promise = new Promise(function (resolve, reject) {
+ ok(resolve, "Promise.resolve exists");
+ ok(reject, "Promise.reject exists");
+
+ resolve();
+ }).then(
+ function (what) {
+ ok(true, "Then - resolveCb has been called");
+ is(what, undefined, "ResolveCb received undefined");
+ runTest();
+ },
+ function () {
+ ok(false, "Then - rejectCb has been called");
+ runTest();
+ }
+ );
+}
+
+function promiseRejectNoHandler() {
+ // This test only checks that the code that reports unhandled errors in the
+ // Promises implementation does not crash or leak.
+ var promise = new Promise(function (res, rej) {
+ noSuchMethod();
+ });
+ runTest();
+}
+
+function promiseReject() {
+ var promise = new Promise(function (resolve, reject) {
+ reject(42);
+ }).then(
+ function (what) {
+ ok(false, "Then - resolveCb has been called");
+ runTest();
+ },
+ function (what) {
+ ok(true, "Then - rejectCb has been called");
+ is(what, 42, "RejectCb received 42");
+ runTest();
+ }
+ );
+}
+
+function promiseRejectNoArg() {
+ var promise = new Promise(function (resolve, reject) {
+ reject();
+ }).then(
+ function (what) {
+ ok(false, "Then - resolveCb has been called");
+ runTest();
+ },
+ function (what) {
+ ok(true, "Then - rejectCb has been called");
+ is(what, undefined, "RejectCb received undefined");
+ runTest();
+ }
+ );
+}
+
+function promiseException() {
+ var promise = new Promise(function (resolve, reject) {
+ throw 42;
+ }).then(
+ function (what) {
+ ok(false, "Then - resolveCb has been called");
+ runTest();
+ },
+ function (what) {
+ ok(true, "Then - rejectCb has been called");
+ is(what, 42, "RejectCb received 42");
+ runTest();
+ }
+ );
+}
+
+function promiseAsync_TimeoutResolveThen() {
+ var handlerExecuted = false;
+
+ setTimeout(function () {
+ ok(handlerExecuted, "Handler should have been called before the timeout.");
+
+ // Allow other assertions to run so the test could fail before the next one.
+ setTimeout(runTest, 0);
+ }, 0);
+
+ Promise.resolve().then(function () {
+ handlerExecuted = true;
+ });
+
+ ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
+}
+
+function promiseAsync_ResolveTimeoutThen() {
+ var handlerExecuted = false;
+
+ var promise = Promise.resolve();
+
+ setTimeout(function () {
+ ok(handlerExecuted, "Handler should have been called before the timeout.");
+
+ // Allow other assertions to run so the test could fail before the next one.
+ setTimeout(runTest, 0);
+ }, 0);
+
+ promise.then(function () {
+ handlerExecuted = true;
+ });
+
+ ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
+}
+
+function promiseAsync_ResolveThenTimeout() {
+ var handlerExecuted = false;
+
+ Promise.resolve().then(function () {
+ handlerExecuted = true;
+ });
+
+ setTimeout(function () {
+ ok(handlerExecuted, "Handler should have been called before the timeout.");
+
+ // Allow other assertions to run so the test could fail before the next one.
+ setTimeout(runTest, 0);
+ }, 0);
+
+ ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
+}
+
+function promiseAsync_SyncXHRAndImportScripts() {
+ var handlerExecuted = false;
+
+ Promise.resolve().then(function () {
+ handlerExecuted = true;
+
+ // Allow other assertions to run so the test could fail before the next one.
+ setTimeout(runTest, 0);
+ });
+
+ ok(!handlerExecuted, "Handlers are not called until the next microtask.");
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "testXHR.txt", false);
+ xhr.send(null);
+
+ ok(!handlerExecuted, "Sync XHR should not trigger microtask execution.");
+
+ importScripts("../../../dom/xhr/tests/relativeLoad_import.js");
+
+ ok(!handlerExecuted, "importScripts should not trigger microtask execution.");
+}
+
+function promiseDoubleThen() {
+ var steps = 0;
+ var promise = new Promise(function (r1, r2) {
+ r1(42);
+ });
+
+ promise.then(
+ function (what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 42, "Value == 42");
+ steps++;
+ },
+ function (what) {
+ ok(false, "Then.reject has been called");
+ }
+ );
+
+ promise.then(
+ function (what) {
+ ok(true, "Then.resolve has been called");
+ is(steps, 1, "Then.resolve - step == 1");
+ is(what, 42, "Value == 42");
+ runTest();
+ },
+ function (what) {
+ ok(false, "Then.reject has been called");
+ }
+ );
+}
+
+function promiseThenException() {
+ var promise = new Promise(function (resolve, reject) {
+ resolve(42);
+ });
+
+ promise
+ .then(function (what) {
+ ok(true, "Then.resolve has been called");
+ throw "booh";
+ })
+ .catch(function (e) {
+ ok(true, "Catch has been called!");
+ runTest();
+ });
+}
+
+function promiseThenCatchThen() {
+ var promise = new Promise(function (resolve, reject) {
+ resolve(42);
+ });
+
+ var promise2 = promise.then(
+ function (what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 42, "Value == 42");
+ return what + 1;
+ },
+ function (what) {
+ ok(false, "Then.reject has been called");
+ }
+ );
+
+ isnot(promise, promise2, "These 2 promise objs are different");
+
+ promise2
+ .then(
+ function (what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 43, "Value == 43");
+ return what + 1;
+ },
+ function (what) {
+ ok(false, "Then.reject has been called");
+ }
+ )
+ .catch(function () {
+ ok(false, "Catch has been called");
+ })
+ .then(
+ function (what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 44, "Value == 44");
+ runTest();
+ },
+ function (what) {
+ ok(false, "Then.reject has been called");
+ }
+ );
+}
+
+function promiseRejectThenCatchThen() {
+ var promise = new Promise(function (resolve, reject) {
+ reject(42);
+ });
+
+ var promise2 = promise.then(
+ function (what) {
+ ok(false, "Then.resolve has been called");
+ },
+ function (what) {
+ ok(true, "Then.reject has been called");
+ is(what, 42, "Value == 42");
+ return what + 1;
+ }
+ );
+
+ isnot(promise, promise2, "These 2 promise objs are different");
+
+ promise2
+ .then(function (what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 43, "Value == 43");
+ return what + 1;
+ })
+ .catch(function (what) {
+ ok(false, "Catch has been called");
+ })
+ .then(function (what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 44, "Value == 44");
+ runTest();
+ });
+}
+
+function promiseRejectThenCatchThen2() {
+ var promise = new Promise(function (resolve, reject) {
+ reject(42);
+ });
+
+ promise
+ .then(function (what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 42, "Value == 42");
+ return what + 1;
+ })
+ .catch(function (what) {
+ is(what, 42, "Value == 42");
+ ok(true, "Catch has been called");
+ return what + 1;
+ })
+ .then(function (what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 43, "Value == 43");
+ runTest();
+ });
+}
+
+function promiseRejectThenCatchExceptionThen() {
+ var promise = new Promise(function (resolve, reject) {
+ reject(42);
+ });
+
+ promise
+ .then(
+ function (what) {
+ ok(false, "Then.resolve has been called");
+ },
+ function (what) {
+ ok(true, "Then.reject has been called");
+ is(what, 42, "Value == 42");
+ throw what + 1;
+ }
+ )
+ .catch(function (what) {
+ ok(true, "Catch has been called");
+ is(what, 43, "Value == 43");
+ return what + 1;
+ })
+ .then(function (what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 44, "Value == 44");
+ runTest();
+ });
+}
+
+function promiseThenCatchOrderingResolve() {
+ var global = 0;
+ var f = new Promise(function (r1, r2) {
+ r1(42);
+ });
+
+ f.then(function () {
+ f.then(function () {
+ global++;
+ });
+ f.catch(function () {
+ global++;
+ });
+ f.then(function () {
+ global++;
+ });
+ setTimeout(function () {
+ is(global, 2, "Many steps... should return 2");
+ runTest();
+ }, 0);
+ });
+}
+
+function promiseThenCatchOrderingReject() {
+ var global = 0;
+ var f = new Promise(function (r1, r2) {
+ r2(42);
+ });
+
+ f.then(
+ function () {},
+ function () {
+ f.then(function () {
+ global++;
+ });
+ f.catch(function () {
+ global++;
+ });
+ f.then(
+ function () {},
+ function () {
+ global++;
+ }
+ );
+ setTimeout(function () {
+ is(global, 2, "Many steps... should return 2");
+ runTest();
+ }, 0);
+ }
+ );
+}
+
+function promiseThenNoArg() {
+ var promise = new Promise(function (resolve, reject) {
+ resolve(42);
+ });
+
+ var clone = promise.then();
+ isnot(promise, clone, "These 2 promise objs are different");
+ promise.then(function (v) {
+ clone.then(function (cv) {
+ is(v, cv, "Both resolve to the same value");
+ runTest();
+ });
+ });
+}
+
+function promiseThenUndefinedResolveFunction() {
+ var promise = new Promise(function (resolve, reject) {
+ reject(42);
+ });
+
+ try {
+ promise.then(undefined, function (v) {
+ is(v, 42, "Promise rejected with 42");
+ runTest();
+ });
+ } catch (e) {
+ ok(false, "then should not throw on undefined resolve function");
+ }
+}
+
+function promiseThenNullResolveFunction() {
+ var promise = new Promise(function (resolve, reject) {
+ reject(42);
+ });
+
+ try {
+ promise.then(null, function (v) {
+ is(v, 42, "Promise rejected with 42");
+ runTest();
+ });
+ } catch (e) {
+ ok(false, "then should not throw on null resolve function");
+ }
+}
+
+function promiseCatchNoArg() {
+ var promise = new Promise(function (resolve, reject) {
+ reject(42);
+ });
+
+ var clone = promise.catch();
+ isnot(promise, clone, "These 2 promise objs are different");
+ promise.catch(function (v) {
+ clone.catch(function (cv) {
+ is(v, cv, "Both reject to the same value");
+ runTest();
+ });
+ });
+}
+
+function promiseNestedPromise() {
+ new Promise(function (resolve, reject) {
+ resolve(
+ new Promise(function (r) {
+ ok(true, "Nested promise is executed");
+ r(42);
+ })
+ );
+ }).then(function (value) {
+ is(value, 42, "Nested promise is executed and then == 42");
+ runTest();
+ });
+}
+
+function promiseNestedNestedPromise() {
+ new Promise(function (resolve, reject) {
+ resolve(
+ new Promise(function (r) {
+ ok(true, "Nested promise is executed");
+ r(42);
+ }).then(function (what) {
+ return what + 1;
+ })
+ );
+ }).then(function (value) {
+ is(value, 43, "Nested promise is executed and then == 43");
+ runTest();
+ });
+}
+
+function promiseWrongNestedPromise() {
+ new Promise(function (resolve, reject) {
+ resolve(
+ new Promise(function (r, r2) {
+ ok(true, "Nested promise is executed");
+ r(42);
+ })
+ );
+ reject(42);
+ }).then(
+ function (value) {
+ is(value, 42, "Nested promise is executed and then == 42");
+ runTest();
+ },
+ function (value) {
+ ok(false, "This is wrong");
+ }
+ );
+}
+
+function promiseLoop() {
+ new Promise(function (resolve, reject) {
+ resolve(
+ new Promise(function (r1, r2) {
+ ok(true, "Nested promise is executed");
+ r1(
+ new Promise(function (r3, r4) {
+ ok(true, "Nested nested promise is executed");
+ r3(42);
+ })
+ );
+ })
+ );
+ }).then(
+ function (value) {
+ is(value, 42, "Nested nested promise is executed and then == 42");
+ runTest();
+ },
+ function (value) {
+ ok(false, "This is wrong");
+ }
+ );
+}
+
+function promiseStaticReject() {
+ var promise = Promise.reject(42).then(
+ function (what) {
+ ok(false, "This should not be called");
+ },
+ function (what) {
+ is(what, 42, "Value == 42");
+ runTest();
+ }
+ );
+}
+
+function promiseStaticResolve() {
+ var promise = Promise.resolve(42).then(
+ function (what) {
+ is(what, 42, "Value == 42");
+ runTest();
+ },
+ function () {
+ ok(false, "This should not be called");
+ }
+ );
+}
+
+function promiseResolveNestedPromise() {
+ var promise = Promise.resolve(
+ new Promise(
+ function (r, r2) {
+ ok(true, "Nested promise is executed");
+ r(42);
+ },
+ function () {
+ ok(false, "This should not be called");
+ }
+ )
+ ).then(
+ function (what) {
+ is(what, 42, "Value == 42");
+ runTest();
+ },
+ function () {
+ ok(false, "This should not be called");
+ }
+ );
+}
+
+function promiseUtilitiesDefined() {
+ ok(Promise.all, "Promise.all must be defined when Promise is enabled.");
+ ok(Promise.race, "Promise.race must be defined when Promise is enabled.");
+ runTest();
+}
+
+function promiseAllArray() {
+ var p = Promise.all([1, new Date(), Promise.resolve("firefox")]);
+ ok(p instanceof Promise, "Return value of Promise.all should be a Promise.");
+ p.then(
+ function (values) {
+ ok(Array.isArray(values), "Resolved value should be an array.");
+ is(
+ values.length,
+ 3,
+ "Resolved array length should match iterable's length."
+ );
+ is(values[0], 1, "Array values should match.");
+ ok(values[1] instanceof Date, "Array values should match.");
+ is(values[2], "firefox", "Array values should match.");
+ runTest();
+ },
+ function () {
+ ok(
+ false,
+ "Promise.all shouldn't fail when iterable has no rejected Promises."
+ );
+ runTest();
+ }
+ );
+}
+
+function promiseAllWaitsForAllPromises() {
+ var arr = [
+ new Promise(function (resolve) {
+ setTimeout(resolve.bind(undefined, 1), 50);
+ }),
+ new Promise(function (resolve) {
+ setTimeout(resolve.bind(undefined, 2), 10);
+ }),
+ new Promise(function (resolve) {
+ setTimeout(
+ resolve.bind(
+ undefined,
+ new Promise(function (resolve2) {
+ resolve2(3);
+ })
+ ),
+ 10
+ );
+ }),
+ new Promise(function (resolve) {
+ setTimeout(resolve.bind(undefined, 4), 20);
+ }),
+ ];
+
+ var p = Promise.all(arr);
+ p.then(
+ function (values) {
+ ok(Array.isArray(values), "Resolved value should be an array.");
+ is(
+ values.length,
+ 4,
+ "Resolved array length should match iterable's length."
+ );
+ is(values[0], 1, "Array values should match.");
+ is(values[1], 2, "Array values should match.");
+ is(values[2], 3, "Array values should match.");
+ is(values[3], 4, "Array values should match.");
+ runTest();
+ },
+ function () {
+ ok(
+ false,
+ "Promise.all shouldn't fail when iterable has no rejected Promises."
+ );
+ runTest();
+ }
+ );
+}
+
+function promiseAllRejectFails() {
+ var arr = [
+ new Promise(function (resolve) {
+ setTimeout(resolve.bind(undefined, 1), 50);
+ }),
+ new Promise(function (resolve, reject) {
+ setTimeout(reject.bind(undefined, 2), 10);
+ }),
+ new Promise(function (resolve) {
+ setTimeout(resolve.bind(undefined, 3), 10);
+ }),
+ new Promise(function (resolve) {
+ setTimeout(resolve.bind(undefined, 4), 20);
+ }),
+ ];
+
+ var p = Promise.all(arr);
+ p.then(
+ function (values) {
+ ok(
+ false,
+ "Promise.all shouldn't resolve when iterable has rejected Promises."
+ );
+ runTest();
+ },
+ function (e) {
+ ok(
+ true,
+ "Promise.all should reject when iterable has rejected Promises."
+ );
+ is(e, 2, "Rejection value should match.");
+ runTest();
+ }
+ );
+}
+
+function promiseRaceEmpty() {
+ var p = Promise.race([]);
+ ok(p instanceof Promise, "Should return a Promise.");
+ // An empty race never resolves!
+ runTest();
+}
+
+function promiseRaceValuesArray() {
+ var p = Promise.race([true, new Date(), 3]);
+ ok(p instanceof Promise, "Should return a Promise.");
+ p.then(
+ function (winner) {
+ is(winner, true, "First value should win.");
+ runTest();
+ },
+ function (err) {
+ ok(false, "Should not fail " + err + ".");
+ runTest();
+ }
+ );
+}
+
+function promiseRacePromiseArray() {
+ var arr = [
+ new Promise(function (resolve) {
+ resolve("first");
+ }),
+ Promise.resolve("second"),
+ new Promise(function () {}),
+ new Promise(function (resolve) {
+ setTimeout(function () {
+ setTimeout(function () {
+ resolve("fourth");
+ }, 0);
+ }, 0);
+ }),
+ ];
+
+ var p = Promise.race(arr);
+ p.then(function (winner) {
+ is(winner, "first", "First queued resolution should win the race.");
+ runTest();
+ });
+}
+
+function promiseRaceReject() {
+ var p = Promise.race([
+ Promise.reject(new Error("Fail bad!")),
+ new Promise(function (resolve) {
+ setTimeout(resolve, 0);
+ }),
+ ]);
+
+ p.then(
+ function () {
+ ok(false, "Should not resolve when winning Promise rejected.");
+ runTest();
+ },
+ function (e) {
+ ok(true, "Should be rejected");
+ ok(e instanceof Error, "Should reject with Error.");
+ ok(e.message == "Fail bad!", "Message should match.");
+ runTest();
+ }
+ );
+}
+
+function promiseRaceThrow() {
+ var p = Promise.race([
+ new Promise(function (resolve) {
+ nonExistent();
+ }),
+ new Promise(function (resolve) {
+ setTimeout(resolve, 0);
+ }),
+ ]);
+
+ p.then(
+ function () {
+ ok(false, "Should not resolve when winning Promise had an error.");
+ runTest();
+ },
+ function (e) {
+ ok(true, "Should be rejected");
+ ok(
+ e instanceof ReferenceError,
+ "Should reject with ReferenceError for function nonExistent()."
+ );
+ runTest();
+ }
+ );
+}
+
+function promiseResolveArray() {
+ var p = Promise.resolve([1, 2, 3]);
+ ok(p instanceof Promise, "Should return a Promise.");
+ p.then(function (v) {
+ ok(Array.isArray(v), "Resolved value should be an Array");
+ is(v.length, 3, "Length should match");
+ is(v[0], 1, "Resolved value should match original");
+ is(v[1], 2, "Resolved value should match original");
+ is(v[2], 3, "Resolved value should match original");
+ runTest();
+ });
+}
+
+function promiseResolveThenable() {
+ var p = Promise.resolve({
+ then(onFulfill, onReject) {
+ onFulfill(2);
+ },
+ });
+ ok(p instanceof Promise, "Should cast to a Promise.");
+ p.then(
+ function (v) {
+ is(v, 2, "Should resolve to 2.");
+ runTest();
+ },
+ function (e) {
+ ok(false, "promiseResolveThenable should've resolved");
+ runTest();
+ }
+ );
+}
+
+function promiseResolvePromise() {
+ var original = Promise.resolve(true);
+ var cast = Promise.resolve(original);
+
+ ok(cast instanceof Promise, "Should cast to a Promise.");
+ is(cast, original, "Should return original Promise.");
+ cast.then(function (v) {
+ is(v, true, "Should resolve to true.");
+ runTest();
+ });
+}
+
+// Bug 1009569.
+// Ensure that thenables are run on a clean stack asynchronously.
+// Test case adopted from
+// https://gist.github.com/getify/d64bb01751b50ed6b281#file-bug1-js.
+function promiseResolveThenableCleanStack() {
+ function immed(s) {
+ x++;
+ s();
+ }
+ function incX() {
+ x++;
+ }
+
+ var x = 0;
+ var thenable = { then: immed };
+ var results = [];
+
+ var p = Promise.resolve(thenable).then(incX);
+ results.push(x);
+
+ // check what happens after all "next cycle" steps
+ // have had a chance to complete
+ setTimeout(function () {
+ // Result should be [0, 2] since `thenable` will be called async.
+ is(results[0], 0, "Expected thenable to be called asynchronously");
+ // See Bug 1023547 comment 13 for why this check has to be gated on p.
+ p.then(function () {
+ results.push(x);
+ is(results[1], 2, "Expected thenable to be called asynchronously");
+ runTest();
+ });
+ }, 1000);
+}
+
+// Bug 1062323
+function promiseWrapperAsyncResolution() {
+ var p = new Promise(function (resolve, reject) {
+ resolve();
+ });
+
+ var results = [];
+ var q = p
+ .then(function () {
+ results.push("1-1");
+ })
+ .then(function () {
+ results.push("1-2");
+ })
+ .then(function () {
+ results.push("1-3");
+ });
+
+ var r = p
+ .then(function () {
+ results.push("2-1");
+ })
+ .then(function () {
+ results.push("2-2");
+ })
+ .then(function () {
+ results.push("2-3");
+ });
+
+ Promise.all([q, r]).then(
+ function () {
+ var match =
+ results[0] == "1-1" &&
+ results[1] == "2-1" &&
+ results[2] == "1-2" &&
+ results[3] == "2-2" &&
+ results[4] == "1-3" &&
+ results[5] == "2-3";
+ ok(match, "Chained promises should resolve asynchronously.");
+ runTest();
+ },
+ function () {
+ ok(false, "promiseWrapperAsyncResolution: One of the promises failed.");
+ runTest();
+ }
+ );
+}
+
+var tests = [
+ promiseResolve,
+ promiseReject,
+ promiseException,
+ promiseAsync_TimeoutResolveThen,
+ promiseAsync_ResolveTimeoutThen,
+ promiseAsync_ResolveThenTimeout,
+ promiseAsync_SyncXHRAndImportScripts,
+ promiseDoubleThen,
+ promiseThenException,
+ promiseThenCatchThen,
+ promiseRejectThenCatchThen,
+ promiseRejectThenCatchThen2,
+ promiseRejectThenCatchExceptionThen,
+ promiseThenCatchOrderingResolve,
+ promiseThenCatchOrderingReject,
+ promiseNestedPromise,
+ promiseNestedNestedPromise,
+ promiseWrongNestedPromise,
+ promiseLoop,
+ promiseStaticReject,
+ promiseStaticResolve,
+ promiseResolveNestedPromise,
+ promiseResolveNoArg,
+ promiseRejectNoArg,
+
+ promiseThenNoArg,
+ promiseThenUndefinedResolveFunction,
+ promiseThenNullResolveFunction,
+ promiseCatchNoArg,
+ promiseRejectNoHandler,
+
+ promiseUtilitiesDefined,
+
+ promiseAllArray,
+ promiseAllWaitsForAllPromises,
+ promiseAllRejectFails,
+
+ promiseRaceEmpty,
+ promiseRaceValuesArray,
+ promiseRacePromiseArray,
+ promiseRaceReject,
+ promiseRaceThrow,
+
+ promiseResolveArray,
+ promiseResolveThenable,
+ promiseResolvePromise,
+
+ promiseResolveThenableCleanStack,
+
+ promiseWrapperAsyncResolution,
+];
+
+function runTest() {
+ if (!tests.length) {
+ postMessage({ type: "finish" });
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+onmessage = function () {
+ runTest();
+};
diff --git a/dom/workers/test/recursion_worker.js b/dom/workers/test/recursion_worker.js
new file mode 100644
index 0000000000..73c86358bc
--- /dev/null
+++ b/dom/workers/test/recursion_worker.js
@@ -0,0 +1,46 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This function should never run on a too much recursion error.
+onerror = function (event) {
+ postMessage(event.message);
+};
+
+// Pure JS recursion
+function recurse() {
+ recurse();
+}
+
+// JS -> C++ -> JS -> C++ recursion
+function recurse2() {
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function () {
+ xhr.open("GET", "nonexistent.file");
+ };
+ xhr.open("GET", "nonexistent.file");
+}
+
+var messageCount = 0;
+onmessage = function (event) {
+ switch (++messageCount) {
+ case 2:
+ recurse2();
+
+ // An exception thrown from an event handler like xhr.onreadystatechange
+ // should not leave an exception pending in the code that generated the
+ // event.
+ postMessage("Done");
+ return;
+
+ case 1:
+ recurse();
+ throw "Exception should have prevented us from getting here!";
+
+ default:
+ throw "Weird number of messages: " + messageCount;
+ }
+
+ throw "Impossible to get here!";
+};
diff --git a/dom/workers/test/recursiveOnerror_worker.js b/dom/workers/test/recursiveOnerror_worker.js
new file mode 100644
index 0000000000..35d2e2b80d
--- /dev/null
+++ b/dom/workers/test/recursiveOnerror_worker.js
@@ -0,0 +1,11 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+onerror = function (message, filename, lineno) {
+ throw new Error("2");
+};
+
+onmessage = function (event) {
+ throw new Error("1");
+};
diff --git a/dom/workers/test/redirect_to_foreign.sjs b/dom/workers/test/redirect_to_foreign.sjs
new file mode 100644
index 0000000000..06fd12052b
--- /dev/null
+++ b/dom/workers/test/redirect_to_foreign.sjs
@@ -0,0 +1,7 @@
+function handleRequest(request, response) {
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader(
+ "Location",
+ "http://example.org/tests/dom/workers/test/foreign.js"
+ );
+}
diff --git a/dom/workers/test/referrer.sjs b/dom/workers/test/referrer.sjs
new file mode 100644
index 0000000000..f91c434c16
--- /dev/null
+++ b/dom/workers/test/referrer.sjs
@@ -0,0 +1,14 @@
+function handleRequest(request, response) {
+ if (request.queryString == "result") {
+ response.write(getState("referer"));
+ setState("referer", "INVALID");
+ } else if (request.queryString == "worker") {
+ response.setHeader("Content-Type", "text/javascript", false);
+ response.write("onmessage = function() { postMessage(42); }");
+ setState("referer", request.getHeader("referer"));
+ } else if (request.queryString == "import") {
+ setState("referer", request.getHeader("referer"));
+ response.setHeader("Content-Type", "text/javascript", false);
+ response.write("'hello world'");
+ }
+}
diff --git a/dom/workers/test/referrer_test_server.sjs b/dom/workers/test/referrer_test_server.sjs
new file mode 100644
index 0000000000..44ec10c456
--- /dev/null
+++ b/dom/workers/test/referrer_test_server.sjs
@@ -0,0 +1,99 @@
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+const SJS = "referrer_test_server.sjs?";
+const SHARED_KEY = SJS;
+
+var SAME_ORIGIN = "https://example.com/tests/dom/workers/test/" + SJS;
+var CROSS_ORIGIN = "https://test2.example.com/tests/dom/workers/test/" + SJS;
+var DOWNGRADE = "http://example.com/tests/dom/workers/test/" + SJS;
+
+function createUrl(aRequestType, aPolicy) {
+ var searchParams = new URLSearchParams();
+ searchParams.append("ACTION", "request-worker");
+ searchParams.append("Referrer-Policy", aPolicy);
+ searchParams.append("TYPE", aRequestType);
+
+ var url = SAME_ORIGIN;
+
+ if (aRequestType === "cross-origin") {
+ url = CROSS_ORIGIN;
+ } else if (aRequestType === "downgrade") {
+ url = DOWNGRADE;
+ }
+
+ return url + searchParams.toString();
+}
+function createWorker(aRequestType, aPolicy) {
+ return `
+ onmessage = function() {
+ fetch("${createUrl(aRequestType, aPolicy)}").then(function () {
+ postMessage(42);
+ close();
+ });
+ }
+ `;
+}
+
+function handleRequest(request, response) {
+ var params = new URLSearchParams(request.queryString);
+ var policy = params.get("Referrer-Policy");
+ var type = params.get("TYPE");
+ var action = params.get("ACTION");
+ response.setHeader("Content-Security-Policy", "default-src *", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+
+ if (policy) {
+ response.setHeader("Referrer-Policy", policy, false);
+ }
+
+ if (action === "test") {
+ response.setHeader("Content-Type", "text/javascript", false);
+ response.write(createWorker(type, policy));
+ return;
+ }
+
+ if (action === "resetState") {
+ setSharedState(SHARED_KEY, "{}");
+ response.write("");
+ return;
+ }
+
+ if (action === "get-test-results") {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(getSharedState(SHARED_KEY));
+ return;
+ }
+
+ if (action === "request-worker") {
+ var result = getSharedState(SHARED_KEY);
+ result = result ? JSON.parse(result) : {};
+ var referrerLevel = "none";
+ var test = {};
+
+ if (request.hasHeader("Referer")) {
+ var referrer = request.getHeader("Referer");
+ if (referrer.indexOf("referrer_test_server") > 0) {
+ referrerLevel = "full";
+ } else if (referrer.indexOf("https://example.com") == 0) {
+ referrerLevel = "origin";
+ } else {
+ // this is never supposed to happen
+ referrerLevel = "other-origin";
+ }
+ test.referrer = referrer;
+ } else {
+ test.referrer = "";
+ }
+
+ test.policy = referrerLevel;
+ test.expected = policy;
+
+ // test id equals type + "-" + policy
+ // Ex: same-origin-default
+ result[type + "-" + policy] = test;
+ setSharedState(SHARED_KEY, JSON.stringify(result));
+
+ response.write("'hello world'");
+ return;
+ }
+}
diff --git a/dom/workers/test/referrer_worker.html b/dom/workers/test/referrer_worker.html
new file mode 100644
index 0000000000..f4c6719912
--- /dev/null
+++ b/dom/workers/test/referrer_worker.html
@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onload="tests.next();">
+<script type="text/javascript">
+const SJS = "referrer_test_server.sjs?";
+const BASE_URL = "https://example.com/tests/dom/workers/test/" + SJS;
+const GET_RESULT = BASE_URL + 'ACTION=get-test-results';
+const RESET_STATE = BASE_URL + 'ACTION=resetState';
+
+function ok(val, message) {
+ val = val ? "true" : "false";
+ window.parent.postMessage("SimpleTest.ok(" + val + ", '" + message + "');", "*");
+}
+
+function info(val) {
+ window.parent.postMessage("SimpleTest.info(" + val + ");", "*");
+}
+
+function is(a, b, message) {
+ ok(a == b, message);
+}
+
+function finish() {
+ // Let window.onerror have a chance to fire
+ setTimeout(function() {
+ setTimeout(function() {
+ tests.return();
+ window.parent.postMessage("SimpleTest.finish();", "*");
+ }, 0);
+ }, 0);
+}
+
+var testCases = {
+ 'same-origin': { 'Referrer-Policy' : { 'default' : 'full',
+ 'origin' : 'origin',
+ 'origin-when-cross-origin' : 'full',
+ 'unsafe-url' : 'full',
+ 'same-origin' : 'full',
+ 'strict-origin' : 'origin',
+ 'strict-origin-when-cross-origin' : 'full',
+ 'no-referrer' : 'none',
+ 'unsafe-url, no-referrer' : 'none',
+ 'invalid' : 'full' }},
+
+ 'cross-origin': { 'Referrer-Policy' : { 'default' : 'origin',
+ 'origin' : 'origin',
+ 'origin-when-cross-origin' : 'origin',
+ 'unsafe-url' : 'full',
+ 'same-origin' : 'none',
+ 'strict-origin' : 'origin',
+ 'strict-origin-when-cross-origin' : 'origin',
+ 'no-referrer' : 'none',
+ 'unsafe-url, no-referrer' : 'none',
+ 'invalid' : 'origin' }},
+
+ // Downgrading in worker is blocked entirely without unblock option
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1198078#c17
+ // Skip the downgrading test
+ /* 'downgrade': { 'Referrer-Policy' : { 'default' : 'full',
+ 'origin' : 'full',
+ 'origin-when-cross-origin"' : 'full',
+ 'unsafe-url' : 'full',
+ 'same-origin' : 'none',
+ 'strict-origin' : 'none',
+ 'strict-origin-when-cross-origin' : 'none',
+ 'no-referrer' : 'full',
+ 'unsafe-url, no-referrer' : 'none',
+ 'invalid' : 'full' }}, */
+
+
+};
+
+var advance = function() { tests.next(); };
+
+/**
+ * helper to perform an XHR
+ * to do checkIndividualResults and resetState
+ */
+function doXHR(aUrl, onSuccess, onFail) {
+ var xhr = new XMLHttpRequest({mozSystem: true});
+ xhr.responseType = "json";
+ xhr.onload = function () {
+ onSuccess(xhr);
+ };
+ xhr.onerror = function () {
+ onFail(xhr);
+ };
+ xhr.open('GET', aUrl, true);
+ xhr.send(null);
+}
+
+
+function resetState() {
+ doXHR(RESET_STATE,
+ advance,
+ function(xhr) {
+ ok(false, "error in reset state");
+ finish();
+ });
+}
+
+function checkIndividualResults(aType, aPolicy, aExpected) {
+ var onload = xhr => {
+ var results = xhr.response;
+ dump(JSON.stringify(xhr.response));
+ // test id equals type + "-" + policy
+ // Ex: same-origin-default
+ var id = aType + "-" + aPolicy;
+ ok(id in results, id + " tests have to be performed.");
+ is(results[id].policy, aExpected, id + ' --- ' + results[id].policy + ' (' + results[id].referrer + ')');
+ advance();
+ };
+ var onerror = xhr => {
+ ok(false, "Can't get results from the counter server.");
+ finish();
+ };
+ doXHR(GET_RESULT, onload, onerror);
+}
+
+var tests = (function*() {
+
+ for (var type in testCases) {
+ for (var policy in testCases[type]['Referrer-Policy']) {
+ yield resetState();
+ var searchParams = new URLSearchParams();
+ searchParams.append("TYPE", type);
+ searchParams.append("ACTION", "test");
+ searchParams.append("Referrer-Policy", policy);
+ var worker = new Worker(BASE_URL + searchParams.toString());
+ worker.onmessage = function () {
+ advance();
+ };
+ yield worker.postMessage(42);
+ yield checkIndividualResults(type, policy, escape(testCases[type]['Referrer-Policy'][policy]));
+ }
+ }
+
+ finish();
+})();
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/rvals_worker.js b/dom/workers/test/rvals_worker.js
new file mode 100644
index 0000000000..221d701443
--- /dev/null
+++ b/dom/workers/test/rvals_worker.js
@@ -0,0 +1,13 @@
+onmessage = function (evt) {
+ postMessage(postMessage("ignore") == undefined);
+
+ var id = setInterval(function () {}, 200);
+ postMessage(clearInterval(id) == undefined);
+
+ id = setTimeout(function () {}, 200);
+ postMessage(clearTimeout(id) == undefined);
+
+ postMessage(dump(42 + "\n") == undefined);
+
+ postMessage("finished");
+};
diff --git a/dom/workers/test/script_createFile.js b/dom/workers/test/script_createFile.js
new file mode 100644
index 0000000000..d49ea18867
--- /dev/null
+++ b/dom/workers/test/script_createFile.js
@@ -0,0 +1,42 @@
+/* eslint-env mozilla/chrome-script */
+
+Cu.importGlobalProperties(["File"]);
+
+addMessageListener("file.open", function (e) {
+ var tmpFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ tmpFile.append("file.txt");
+ tmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+ File.createFromNsIFile(tmpFile).then(function (file) {
+ sendAsyncMessage("file.opened", { data: file });
+ });
+});
+
+addMessageListener("nonEmptyFile.open", function (e) {
+ var tmpFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ tmpFile.append("file.txt");
+ tmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+ var outStream = Cc[
+ "@mozilla.org/network/file-output-stream;1"
+ ].createInstance(Ci.nsIFileOutputStream);
+ outStream.init(
+ tmpFile,
+ 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666,
+ 0
+ );
+ var fileData = "Hello world!";
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ File.createFromNsIFile(tmpFile).then(function (file) {
+ sendAsyncMessage("nonEmptyFile.opened", { data: file });
+ });
+});
diff --git a/dom/workers/test/server_fetch_synthetic.sjs b/dom/workers/test/server_fetch_synthetic.sjs
new file mode 100644
index 0000000000..703b26d0d2
--- /dev/null
+++ b/dom/workers/test/server_fetch_synthetic.sjs
@@ -0,0 +1,50 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+function log(str) {
+ //dump(`SJS LOG: ${str}\n`);
+}
+
+/**
+ * Given a multipart/form-data encoded string that we know to have only a single
+ * part, return the contents of the part. (MIME multipart encoding is too
+ * exciting to delve into.)
+ */
+function extractBlobFromMultipartFormData(text) {
+ const lines = text.split(/\r\n/g);
+ const firstBlank = lines.indexOf("");
+ const foo = lines.slice(firstBlank + 1, -2).join("\n");
+ return foo;
+}
+
+async function handleRequest(request, response) {
+ let blobContents = "";
+ if (request.method !== "POST") {
+ } else {
+ var body = new BinaryInputStream(request.bodyInputStream);
+
+ var avail;
+ var bytes = [];
+
+ while ((avail = body.available()) > 0) {
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+ }
+ let requestBodyContents = String.fromCharCode.apply(null, bytes);
+ log(requestBodyContents);
+ blobContents = extractBlobFromMultipartFormData(requestBodyContents);
+ }
+
+ log("Setting Headers");
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(request.httpVersion, "200", "OK");
+ response.write(`<!DOCTYPE HTML><head><meta charset="utf-8"/></head><body>
+ <h1 id="url">${request.scheme}${request.host}${request.port}${request.path}</h1>
+ <div id="source">ServerJS</div>
+ <div id="blob">${blobContents}</div>
+ </body>`);
+ log("Done");
+}
diff --git a/dom/workers/test/sharedWorker_console.js b/dom/workers/test/sharedWorker_console.js
new file mode 100644
index 0000000000..f9c8416ea6
--- /dev/null
+++ b/dom/workers/test/sharedWorker_console.js
@@ -0,0 +1,11 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+onconnect = function (evt) {
+ console.profile("Hello profiling from a SharedWorker!");
+ console.log("Hello world from a SharedWorker!");
+ evt.ports[0].postMessage("ok!");
+};
diff --git a/dom/workers/test/sharedWorker_lifetime.js b/dom/workers/test/sharedWorker_lifetime.js
new file mode 100644
index 0000000000..594bd5833d
--- /dev/null
+++ b/dom/workers/test/sharedWorker_lifetime.js
@@ -0,0 +1,5 @@
+onconnect = function (e) {
+ setTimeout(function () {
+ e.ports[0].postMessage("Still alive!");
+ }, 500);
+};
diff --git a/dom/workers/test/sharedWorker_ports.js b/dom/workers/test/sharedWorker_ports.js
new file mode 100644
index 0000000000..6bdb5695db
--- /dev/null
+++ b/dom/workers/test/sharedWorker_ports.js
@@ -0,0 +1,30 @@
+var port;
+onconnect = function (evt) {
+ evt.source.postMessage({ type: "connected" });
+
+ if (!port) {
+ port = evt.source;
+ evt.source.onmessage = function (evtFromPort) {
+ port.postMessage({
+ type: "status",
+ test: "Port from the main-thread!" == evtFromPort.data,
+ msg: "The message is coming from the main-thread",
+ });
+ port.postMessage({
+ type: "status",
+ test: evtFromPort.ports.length == 1,
+ msg: "1 port transferred",
+ });
+
+ evtFromPort.ports[0].onmessage = function (evtFromPort2) {
+ port.postMessage({
+ type: "status",
+ test: evtFromPort2.data.type == "connected",
+ msg: "The original message received",
+ });
+ port.postMessage({ type: "finish" });
+ close();
+ };
+ };
+ }
+};
diff --git a/dom/workers/test/sharedWorker_privateBrowsing.js b/dom/workers/test/sharedWorker_privateBrowsing.js
new file mode 100644
index 0000000000..c16bd209f0
--- /dev/null
+++ b/dom/workers/test/sharedWorker_privateBrowsing.js
@@ -0,0 +1,4 @@
+var counter = 0;
+onconnect = function (evt) {
+ evt.ports[0].postMessage(++counter);
+};
diff --git a/dom/workers/test/sharedWorker_sharedWorker.js b/dom/workers/test/sharedWorker_sharedWorker.js
new file mode 100644
index 0000000000..f5dd52d72b
--- /dev/null
+++ b/dom/workers/test/sharedWorker_sharedWorker.js
@@ -0,0 +1,99 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+if (!("self" in this)) {
+ throw new Error("No 'self' exists on SharedWorkerGlobalScope!");
+}
+if (this !== self) {
+ throw new Error("'self' not equal to global object!");
+}
+if (!(self instanceof SharedWorkerGlobalScope)) {
+ throw new Error("self not a SharedWorkerGlobalScope instance!");
+}
+
+var propsToCheck = [
+ "location",
+ "navigator",
+ "close",
+ "importScripts",
+ "setTimeout",
+ "clearTimeout",
+ "setInterval",
+ "clearInterval",
+ "dump",
+ "atob",
+ "btoa",
+];
+
+for (var index = 0; index < propsToCheck.length; index++) {
+ var prop = propsToCheck[index];
+ if (!(prop in self)) {
+ throw new Error("SharedWorkerGlobalScope has no '" + prop + "' property!");
+ }
+}
+
+onconnect = function (event) {
+ if (!("SharedWorkerGlobalScope" in self)) {
+ throw new Error("SharedWorkerGlobalScope should be visible!");
+ }
+ if (!(self instanceof SharedWorkerGlobalScope)) {
+ throw new Error("The global should be a SharedWorkerGlobalScope!");
+ }
+ if (!(self instanceof WorkerGlobalScope)) {
+ throw new Error("The global should be a WorkerGlobalScope!");
+ }
+ if ("DedicatedWorkerGlobalScope" in self) {
+ throw new Error("DedicatedWorkerGlobalScope should not be visible!");
+ }
+ if (!(event instanceof MessageEvent)) {
+ throw new Error("'connect' event is not a MessageEvent!");
+ }
+ if (!("ports" in event)) {
+ throw new Error("'connect' event doesn't have a 'ports' property!");
+ }
+ if (event.ports.length != 1) {
+ throw new Error(
+ "'connect' event has a 'ports' property with length '" +
+ event.ports.length +
+ "'!"
+ );
+ }
+ if (!event.ports[0]) {
+ throw new Error("'connect' event has a null 'ports[0]' property!");
+ }
+ if (!(event.ports[0] instanceof MessagePort)) {
+ throw new Error(
+ "'connect' event has a 'ports[0]' property that isn't a " + "MessagePort!"
+ );
+ }
+ if (!(event.ports[0] == event.source)) {
+ throw new Error("'connect' event source property is incorrect!");
+ }
+ if (event.data) {
+ throw new Error("'connect' event has data: " + event.data);
+ }
+
+ // Statement after return should trigger a warning, but NOT fire error events
+ // at us.
+ (function () {
+ return;
+ 1;
+ });
+
+ event.ports[0].onmessage = function (msg) {
+ if (!(msg instanceof MessageEvent)) {
+ throw new Error("'message' event is not a MessageEvent!");
+ }
+ if (!("ports" in msg)) {
+ throw new Error("'message' event doesn't have a 'ports' property!");
+ }
+ if (msg.ports === null) {
+ throw new Error("'message' event has a null 'ports' property!");
+ }
+ msg.target.postMessage(msg.data);
+ throw new Error(msg.data);
+ };
+};
diff --git a/dom/workers/test/sharedWorker_thirdparty_frame.html b/dom/workers/test/sharedWorker_thirdparty_frame.html
new file mode 100644
index 0000000000..ebd78412a6
--- /dev/null
+++ b/dom/workers/test/sharedWorker_thirdparty_frame.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<script>
+ let params = new URLSearchParams(document.location.search.substring(1));
+ let name = params.get('name');
+ try {
+ let worker = new SharedWorker('sharedWorker_sharedWorker.js',
+ { name });
+ worker.port.addEventListener('message', evt => {
+ parent.postMessage( { name, result: 'allowed' }, '*');
+ }, { once: true });
+ worker.port.start();
+ worker.port.postMessage('ping');
+ } catch(e) {
+ parent.postMessage({ name, result: 'blocked' }, '*');
+ }
+</script>
diff --git a/dom/workers/test/sharedWorker_thirdparty_window.html b/dom/workers/test/sharedWorker_thirdparty_window.html
new file mode 100644
index 0000000000..f5f01066c2
--- /dev/null
+++ b/dom/workers/test/sharedWorker_thirdparty_window.html
@@ -0,0 +1,26 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for SharedWorker in 3rd Party Iframes</title>
+</head>
+<body>
+ <script>
+
+ let url = new URL(window.location);
+
+ let frame = document.createElement('iframe');
+ frame.src =
+ 'http://example.org/tests/dom/workers/test/sharedWorker_thirdparty_frame.html?name=' + url.searchParams.get('name');
+ document.body.appendChild(frame);
+ window.addEventListener('message', evt => {
+ frame.remove();
+ opener.postMessage(evt.data, "*");
+ }, {once: true});
+
+ </script>
+</body>
+</html>
diff --git a/dom/workers/test/simpleThread_worker.js b/dom/workers/test/simpleThread_worker.js
new file mode 100644
index 0000000000..9d6fb30f2f
--- /dev/null
+++ b/dom/workers/test/simpleThread_worker.js
@@ -0,0 +1,52 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+function messageListener(event) {
+ var exception;
+ try {
+ event.bubbles = true;
+ } catch (e) {
+ exception = e;
+ }
+
+ if (!(exception instanceof TypeError)) {
+ throw exception;
+ }
+
+ switch (event.data) {
+ case "no-op":
+ break;
+ case "components":
+ postMessage(Components.toString());
+ break;
+ case "start":
+ for (var i = 0; i < 1000; i++) {}
+ postMessage("started");
+ break;
+ case "stop":
+ self.postMessage("no-op");
+ postMessage("stopped");
+ self.removeEventListener("message", messageListener);
+ break;
+ default:
+ throw "Bad message: " + event.data;
+ }
+}
+
+if (!("DedicatedWorkerGlobalScope" in self)) {
+ throw new Error("DedicatedWorkerGlobalScope should be visible!");
+}
+if (!(self instanceof DedicatedWorkerGlobalScope)) {
+ throw new Error("The global should be a SharedWorkerGlobalScope!");
+}
+if (!(self instanceof WorkerGlobalScope)) {
+ throw new Error("The global should be a WorkerGlobalScope!");
+}
+if ("SharedWorkerGlobalScope" in self) {
+ throw new Error("SharedWorkerGlobalScope should not be visible!");
+}
+
+addEventListener("message", { handleEvent: messageListener });
diff --git a/dom/workers/test/sourcemap_header.js b/dom/workers/test/sourcemap_header.js
new file mode 100644
index 0000000000..9f10b35ed9
--- /dev/null
+++ b/dom/workers/test/sourcemap_header.js
@@ -0,0 +1,65 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+(async () => {
+ SimpleTest.waitForExplicitFinish();
+
+ const HTTP_BASE_URL = "http://mochi.test:8888/tests/dom/workers/test/";
+ const IFRAME_URL = HTTP_BASE_URL + "sourcemap_header_iframe.html";
+ const WORKER_URL = HTTP_BASE_URL + "sourcemap_header_worker.js";
+ const DEBUGGER_URL = BASE_URL + "sourcemap_header_debugger.js";
+
+ const workerFrame = document.getElementById("worker-frame");
+ ok(workerFrame, "has frame");
+
+ await new Promise(r => {
+ workerFrame.onload = r;
+ workerFrame.src = IFRAME_URL;
+ });
+
+ info("Start worker and watch for registration");
+ const workerLoadedChannel = new MessageChannel();
+
+ const loadDebuggerAndWorker = Promise.all([
+ waitForRegister(WORKER_URL, DEBUGGER_URL),
+ // We need to wait for the worker to load so a Debugger.Source will be
+ // guaranteed to exist.
+ new Promise(r => {
+ workerLoadedChannel.port1.onmessage = r;
+ }),
+ ]);
+ workerFrame.contentWindow.postMessage(WORKER_URL, "*", [
+ workerLoadedChannel.port2,
+ ]);
+ const [dbg] = await loadDebuggerAndWorker;
+
+ // Wait for the debugger server to reply with the sourceMapURL of the
+ // loaded worker scripts.
+ info("Querying for the sourceMapURL of the worker script");
+ const urls = await new Promise(res => {
+ dbg.addListener({
+ onMessage(msg) {
+ const data = JSON.parse(msg);
+ if (data.type !== "response-sourceMapURL") {
+ return;
+ }
+ dbg.removeListener(this);
+ res(data.value);
+ },
+ });
+ dbg.postMessage(
+ JSON.stringify({
+ type: "request-sourceMapURL",
+ url: WORKER_URL,
+ })
+ );
+ });
+
+ ok(Array.isArray(urls) && urls.length === 1, "has a single source actor");
+ is(urls[0], "worker-header.js.map", "has the right map URL");
+
+ SimpleTest.finish();
+})();
diff --git a/dom/workers/test/sourcemap_header_debugger.js b/dom/workers/test/sourcemap_header_debugger.js
new file mode 100644
index 0000000000..bb8ed0c1f7
--- /dev/null
+++ b/dom/workers/test/sourcemap_header_debugger.js
@@ -0,0 +1,29 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+addEventListener("message", function (event) {
+ let data;
+ try {
+ data = JSON.parse(event.data);
+ } catch {}
+
+ switch (data.type) {
+ case "request-sourceMapURL":
+ const dbg = new Debugger(global);
+ const sourceMapURLs = dbg
+ .findSources()
+ .filter(source => source.url === data.url)
+ .map(source => source.sourceMapURL);
+
+ postMessage(
+ JSON.stringify({
+ type: "response-sourceMapURL",
+ value: sourceMapURLs,
+ })
+ );
+ break;
+ }
+});
diff --git a/dom/workers/test/sourcemap_header_iframe.html b/dom/workers/test/sourcemap_header_iframe.html
new file mode 100644
index 0000000000..82278f41a1
--- /dev/null
+++ b/dom/workers/test/sourcemap_header_iframe.html
@@ -0,0 +1,19 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script>
+ self.onmessage = (msg) => {
+ const workerLoadedPort = msg.ports[0];
+ const worker = new Worker(msg.data);
+ worker.onmessage = () => {
+ workerLoadedPort.postMessage("worker loaded");
+ };
+ };
+ </script>
+</head>
+<body></body>
+</html>
diff --git a/dom/workers/test/sourcemap_header_worker.js b/dom/workers/test/sourcemap_header_worker.js
new file mode 100644
index 0000000000..ca094686d3
--- /dev/null
+++ b/dom/workers/test/sourcemap_header_worker.js
@@ -0,0 +1,8 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Send a message pack so the test knows that the source has loaded before
+// it tries to search for the Debugger.Source.
+postMessage("loaded");
diff --git a/dom/workers/test/sourcemap_header_worker.js^headers^ b/dom/workers/test/sourcemap_header_worker.js^headers^
new file mode 100644
index 0000000000..833288ef02
--- /dev/null
+++ b/dom/workers/test/sourcemap_header_worker.js^headers^
@@ -0,0 +1 @@
+X-SourceMap: worker-header.js.map
diff --git a/dom/workers/test/suspend_blank.html b/dom/workers/test/suspend_blank.html
new file mode 100644
index 0000000000..b6dd0075e6
--- /dev/null
+++ b/dom/workers/test/suspend_blank.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<script>
+ var interval;
+ var finish = false;
+ var bc = new BroadcastChannel("suspendBlank");
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "navigateBack") {
+ finish = true;
+ history.back();
+ }
+ }
+ window.onpagehide = () => {
+ bc.postMessage({command: "pagehide"});
+ if (finish) {
+ bc.close();
+ }
+ }
+ window.onload = () => {
+ bc.postMessage({command: "loaded"});
+ }
+</script>
diff --git a/dom/workers/test/suspend_window.html b/dom/workers/test/suspend_window.html
new file mode 100644
index 0000000000..9bede3aaa6
--- /dev/null
+++ b/dom/workers/test/suspend_window.html
@@ -0,0 +1,82 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for DOM Worker Threads Suspending</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<div id="output"></div>
+<script class="testbody" type="text/javascript">
+
+ var worker;
+ var finish = false;
+ var bc = new BroadcastChannel("suspendWindow");
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "startWorker") {
+ startWorker();
+ } else if (command == "navigate") {
+ window.location = "suspend_blank.html";
+ } else if (command == "finish") {
+ finish = true;
+ terminateWorker();
+ bc.postMessage({command: "finished"});
+ bc.close();
+ window.close();
+ }
+ }
+
+ function messageCallback(data) {
+ if (finish) {
+ return;
+ }
+ bc.postMessage({command: "messageCallback", data});
+ }
+
+ function errorCallback(msg) {
+ if (finish) {
+ return;
+ }
+ bc.postMessage({command: "errorCallback", data: msg});
+ }
+
+ var output = document.getElementById("output");
+
+ function terminateWorker() {
+ if (worker) {
+ worker.postMessage("stop");
+ worker = null;
+ }
+ }
+
+ function startWorker() {
+ var lastData;
+ worker = new Worker("suspend_worker.js");
+
+ worker.onmessage = function(event) {
+ output.textContent = (lastData ? lastData + " -> " : "") + event.data;
+ lastData = event.data;
+ messageCallback(event.data);
+ };
+
+ worker.onerror = function(event) {
+ this.terminate();
+ errorCallback(event.message);
+ };
+ }
+
+ window.onload = () => {
+ bc.postMessage({command: "loaded"});
+ }
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/suspend_worker.js b/dom/workers/test/suspend_worker.js
new file mode 100644
index 0000000000..e024972737
--- /dev/null
+++ b/dom/workers/test/suspend_worker.js
@@ -0,0 +1,13 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+var counter = 0;
+
+var interval = setInterval(function () {
+ postMessage(++counter);
+}, 100);
+
+onmessage = function (event) {
+ clearInterval(interval);
+};
diff --git a/dom/workers/test/terminate_worker.js b/dom/workers/test/terminate_worker.js
new file mode 100644
index 0000000000..7b9984e869
--- /dev/null
+++ b/dom/workers/test/terminate_worker.js
@@ -0,0 +1,11 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+onmessage = function (event) {
+ throw "No messages should reach me!";
+};
+
+setInterval(function () {
+ postMessage("Still alive!");
+}, 100);
diff --git a/dom/workers/test/test_404.html b/dom/workers/test/test_404.html
new file mode 100644
index 0000000000..59ab691e02
--- /dev/null
+++ b/dom/workers/test/test_404.html
@@ -0,0 +1,39 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads
+-->
+<head>
+ <title>Test for DOM Worker Threads</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("nonexistent_worker.js");
+
+ worker.onmessage = function(event) {
+ ok(false, "Shouldn't ever get a message!");
+ SimpleTest.finish();
+ }
+
+ worker.onerror = function(event) {
+ is(event.target, worker);
+ event.preventDefault();
+ SimpleTest.finish();
+ };
+
+ worker.postMessage("dummy");
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_WorkerDebugger.initialize.xhtml b/dom/workers/test/test_WorkerDebugger.initialize.xhtml
new file mode 100644
index 0000000000..9e7ec2e9a6
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger.initialize.xhtml
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebugger.initialize"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ const WORKER_URL = "WorkerDebugger.initialize_worker.js";
+ const CHILD_WORKER_URL = "WorkerDebugger.initialize_childWorker.js";
+ const DEBUGGER_URL = BASE_URL + "WorkerDebugger.initialize_debugger.js";
+
+ function test() {
+ (async function() {
+ SimpleTest.waitForExplicitFinish();
+
+ info("Create a worker that creates a child worker, wait for their " +
+ "debuggers to be registered, and initialize them.");
+ let promise = waitForMultiple([
+ waitForRegister(WORKER_URL, DEBUGGER_URL),
+ waitForRegister(CHILD_WORKER_URL, DEBUGGER_URL)
+ ]);
+ let worker = new Worker(WORKER_URL);
+ await promise;
+
+ info("Check that the debuggers are initialized before the workers " +
+ "start running.");
+ await waitForMultiple([
+ waitForWorkerMessage(worker, "debugger"),
+ waitForWorkerMessage(worker, "worker"),
+ waitForWorkerMessage(worker, "child:debugger"),
+ waitForWorkerMessage(worker, "child:worker")
+ ]);
+
+ SimpleTest.finish();
+ })();
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_WorkerDebugger.postMessage.xhtml b/dom/workers/test/test_WorkerDebugger.postMessage.xhtml
new file mode 100644
index 0000000000..58e15b3a05
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger.postMessage.xhtml
@@ -0,0 +1,59 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebugger.postMessage"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ const WORKER_URL = "WorkerDebugger.postMessage_worker.js";
+ const CHILD_WORKER_URL = "WorkerDebugger.postMessage_childWorker.js";
+ const DEBUGGER_URL = BASE_URL + "WorkerDebugger.postMessage_debugger.js";
+
+ function test() {
+ (async function() {
+ SimpleTest.waitForExplicitFinish();
+
+ info("Create a worker that creates a child worker, wait for their " +
+ "debuggers to be registered, and initialize them.");
+ let promise = waitForMultiple([
+ waitForRegister(WORKER_URL, DEBUGGER_URL),
+ waitForRegister(CHILD_WORKER_URL, DEBUGGER_URL)
+ ]);
+ let worker = new Worker(WORKER_URL);
+ let [dbg, childDbg] = await promise;
+
+ info("Send a request to the worker debugger. This should cause the " +
+ "the worker debugger to send a response.");
+ promise = waitForDebuggerMessage(dbg, "pong");
+ dbg.postMessage("ping");
+ await promise;
+
+ info("Send a request to the child worker debugger. This should cause " +
+ "the child worker debugger to send a response.");
+ promise = waitForDebuggerMessage(childDbg, "pong");
+ childDbg.postMessage("ping");
+ await promise;
+
+ SimpleTest.finish();
+ })();
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_WorkerDebugger.xhtml b/dom/workers/test/test_WorkerDebugger.xhtml
new file mode 100644
index 0000000000..ed7040a437
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger.xhtml
@@ -0,0 +1,145 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebugger"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ const WORKER_URL = "WorkerDebugger_worker.js";
+ const CHILD_WORKER_URL = "WorkerDebugger_childWorker.js";
+ const SHARED_WORKER_URL = "WorkerDebugger_sharedWorker.js";
+
+ function test() {
+ (async function() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestLongerTimeout(5);
+
+ info("Create a top-level chrome worker that creates a non-top-level " +
+ "content worker and wait for their debuggers to be registered.");
+ let promise = waitForMultiple([
+ waitForRegister(WORKER_URL),
+ waitForRegister(CHILD_WORKER_URL)
+ ]);
+ worker = new ChromeWorker(WORKER_URL);
+ let [dbg, childDbg] = await promise;
+
+ info("Check that the top-level chrome worker debugger has the " +
+ "correct properties.");
+ is(dbg.isChrome, true,
+ "Chrome worker debugger should be chrome.");
+ is(dbg.parent, null,
+ "Top-level debugger should not have parent.");
+ is(dbg.type, Ci.nsIWorkerDebugger.TYPE_DEDICATED,
+ "Chrome worker debugger should be dedicated.");
+ is(dbg.window, window,
+ "Top-level dedicated worker debugger should have window.");
+
+ info("Check that the non-top-level content worker debugger has the " +
+ "correct properties.");
+ is(childDbg.isChrome, false,
+ "Content worker debugger should be content.");
+ is(childDbg.parent, dbg,
+ "Non-top-level worker debugger should have parent.");
+ is(childDbg.type, Ci.nsIWorkerDebugger.TYPE_DEDICATED,
+ "Content worker debugger should be dedicated.");
+ is(childDbg.window, window,
+ "Non-top-level worker debugger should have window.");
+
+ info("Terminate the top-level chrome worker and the non-top-level " +
+ "content worker, and wait for their debuggers to be " +
+ "unregistered and closed.");
+ promise = waitForMultiple([
+ waitForUnregister(CHILD_WORKER_URL),
+ waitForDebuggerClose(childDbg),
+ waitForUnregister(WORKER_URL),
+ waitForDebuggerClose(dbg),
+ ]);
+ worker.terminate();
+ await promise;
+
+ info("Create a shared worker and wait for its debugger to be " +
+ "registered");
+ promise = waitForRegister(SHARED_WORKER_URL);
+ worker = new SharedWorker(SHARED_WORKER_URL);
+ let sharedDbg = await promise;
+
+ info("Check that the shared worker debugger has the correct " +
+ "properties.");
+ is(sharedDbg.isChrome, false,
+ "Shared worker debugger should be content.");
+ is(sharedDbg.parent, null,
+ "Shared worker debugger should not have parent.");
+ is(sharedDbg.type, Ci.nsIWorkerDebugger.TYPE_SHARED,
+ "Shared worker debugger should be shared.");
+ is(sharedDbg.window, null,
+ "Shared worker debugger should not have window.");
+
+ info("Create a shared worker with the same URL and check that its " +
+ "debugger is not registered again.");
+ let listener = {
+ onRegistered: function () {
+ ok(false,
+ "Shared worker debugger should not be registered again.");
+ },
+ };
+ wdm.addListener(listener);
+
+ worker = new SharedWorker(SHARED_WORKER_URL);
+
+ info("Send a message to the shared worker to tell it to close " +
+ "itself, and wait for its debugger to be closed.");
+ promise = waitForMultiple([
+ waitForUnregister(SHARED_WORKER_URL),
+ waitForDebuggerClose(sharedDbg)
+ ]);
+ worker.port.start();
+ worker.port.postMessage("close");
+ await promise;
+
+ promise = waitForRegister(SHARED_WORKER_URL);
+ worker = new SharedWorker(SHARED_WORKER_URL);
+ sharedDbg = await promise;
+
+ info("Send a message to the shared worker to tell it to close " +
+ "itself, then loop forever, and wait for its debugger to be closed.");
+ promise = waitForMultiple([
+ waitForUnregister(SHARED_WORKER_URL),
+ waitForDebuggerClose(sharedDbg)
+ ]);
+
+ // When the closing process begins, we schedule a timer to terminate
+ // the worker in case it's in an infinite loop, which is exactly what
+ // we do in this test. We want a duration long enough that we can be
+ // confident that the infinite loop was entered as measured by
+ // performance.now() and that we terminated it, but not as long as our
+ // 30 second default we currently ship.
+ await SpecialPowers.pushPrefEnv({"set": [[ "dom.worker.canceling.timeoutMilliseconds", 15000 ]]});
+
+ worker.port.start();
+ worker.port.postMessage("close_loop");
+ await promise;
+
+ wdm.removeListener(listener);
+ SimpleTest.finish();
+ })();
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_WorkerDebuggerGlobalScope.createSandbox.xhtml b/dom/workers/test/test_WorkerDebuggerGlobalScope.createSandbox.xhtml
new file mode 100644
index 0000000000..89114f5e49
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebuggerGlobalScope.createSandbox.xhtml
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebuggerGlobalScope.createSandbox"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ const WORKER_URL = "WorkerDebuggerGlobalScope.createSandbox_worker.js";
+ const DEBUGGER_URL = BASE_URL + "WorkerDebuggerGlobalScope.createSandbox_debugger.js";
+
+ function test() {
+ (async function() {
+ SimpleTest.waitForExplicitFinish();
+
+ info("Create a worker, wait for its debugger to be registered, and " +
+ "initialize it.");
+ let promise = waitForRegister(WORKER_URL, DEBUGGER_URL);
+ let worker = new Worker(WORKER_URL);
+ let dbg = await promise;
+
+ info("Send a request to the worker debugger. This should cause the " +
+ "worker debugger to send a response from within a sandbox.");
+ promise = waitForDebuggerMessage(dbg, "pong");
+ dbg.postMessage("ping");
+ await promise;
+
+ SimpleTest.finish();
+ })();
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_WorkerDebuggerGlobalScope.enterEventLoop.xhtml b/dom/workers/test/test_WorkerDebuggerGlobalScope.enterEventLoop.xhtml
new file mode 100644
index 0000000000..d5cff95d39
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebuggerGlobalScope.enterEventLoop.xhtml
@@ -0,0 +1,124 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebuggerGlobalScope.enterEventLoop"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ const WORKER_URL = "WorkerDebuggerGlobalScope.enterEventLoop_worker.js";
+ const CHILD_WORKER_URL = "WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js";
+ const DEBUGGER_URL = BASE_URL + "WorkerDebuggerGlobalScope.enterEventLoop_debugger.js";
+
+ function test() {
+ (async function() {
+ SimpleTest.waitForExplicitFinish();
+
+ info("Create a worker that creates a child worker, wait for their " +
+ "debuggers to be registered, and initialize them.");
+ let promise = waitForMultiple([
+ waitForRegister(WORKER_URL, DEBUGGER_URL),
+ waitForRegister(CHILD_WORKER_URL, DEBUGGER_URL)
+ ]);
+ let worker = new Worker(WORKER_URL);
+ let [dbg, childDbg] = await promise;
+
+ info("Send a request to the child worker. This should cause the " +
+ "child worker debugger to enter a nested event loop.");
+ promise = waitForDebuggerMessage(childDbg, "paused");
+ worker.postMessage("child:ping");
+ await promise;
+
+ info("Send a request to the child worker debugger. This should cause " +
+ "the child worker debugger to enter a second nested event loop.");
+ promise = waitForDebuggerMessage(childDbg, "paused");
+ childDbg.postMessage("eval");
+ await promise;
+
+ info("Send a request to the child worker debugger. This should cause " +
+ "the child worker debugger to leave its second nested event " +
+ "loop. The child worker debugger should not send a response " +
+ "for its previous request until after it has left the nested " +
+ "event loop.");
+ promise = waitForMultiple([
+ waitForDebuggerMessage(childDbg, "resumed"),
+ waitForDebuggerMessage(childDbg, "evalled")
+ ]);
+ childDbg.postMessage("resume");
+ await promise;
+
+ info("Send a request to the child worker debugger. This should cause " +
+ "the child worker debugger to leave its first nested event loop." +
+ "The child worker should not send a response for its earlier " +
+ "request until after the child worker debugger has left the " +
+ "nested event loop.");
+ promise = waitForMultiple([
+ waitForDebuggerMessage(childDbg, "resumed"),
+ waitForWorkerMessage(worker, "child:pong")
+ ]);
+ childDbg.postMessage("resume");
+ await promise;
+
+ info("Send a request to the worker. This should cause the worker " +
+ "debugger to enter a nested event loop.");
+ promise = waitForDebuggerMessage(dbg, "paused");
+ worker.postMessage("ping");
+ await promise;
+
+ info("Terminate the worker. This should not cause the worker " +
+ "debugger to terminate as well.");
+ worker.terminate();
+
+ worker.onmessage = function () {
+ ok(false, "Worker should have been terminated.");
+ };
+
+ info("Send a request to the worker debugger. This should cause the " +
+ "worker debugger to enter a second nested event loop.");
+ promise = waitForDebuggerMessage(dbg, "paused");
+ dbg.postMessage("eval");
+ await promise;
+
+ info("Send a request to the worker debugger. This should cause the " +
+ "worker debugger to leave its second nested event loop. The " +
+ "worker debugger should not send a response for the previous " +
+ "request until after leaving the nested event loop.");
+ promise = waitForMultiple([
+ waitForDebuggerMessage(dbg, "resumed"),
+ waitForDebuggerMessage(dbg, "evalled")
+ ]);
+ dbg.postMessage("resume");
+ await promise;
+
+ info("Send a request to the worker debugger. This should cause the " +
+ "worker debugger to leave its first nested event loop. The " +
+ "worker should not send a response for its earlier request, " +
+ "since it has been terminated.");
+ promise = waitForMultiple([
+ waitForDebuggerMessage(dbg, "resumed"),
+ ]);
+ dbg.postMessage("resume");
+ await promise;
+
+ SimpleTest.finish();
+ })();
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_WorkerDebuggerGlobalScope.reportError.xhtml b/dom/workers/test/test_WorkerDebuggerGlobalScope.reportError.xhtml
new file mode 100644
index 0000000000..20e731c1cf
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebuggerGlobalScope.reportError.xhtml
@@ -0,0 +1,95 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebuggerGlobalScope.reportError"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ const WORKER_URL = "WorkerDebuggerGlobalScope.reportError_worker.js";
+ const CHILD_WORKER_URL = "WorkerDebuggerGlobalScope.reportError_childWorker.js";
+ const DEBUGGER_URL = BASE_URL + "WorkerDebuggerGlobalScope.reportError_debugger.js";
+
+ function test() {
+ (async function() {
+ SimpleTest.waitForExplicitFinish();
+
+ info("Create a worker that creates a child worker, wait for their " +
+ "debuggers to be registered, and initialize them.");
+ let promise = waitForMultiple([
+ waitForRegister(WORKER_URL, DEBUGGER_URL),
+ waitForRegister(CHILD_WORKER_URL, DEBUGGER_URL)
+ ]);
+ let worker = new Worker(WORKER_URL);
+ let [dbg, childDbg] = await promise;
+
+ worker.onmessage = function () {
+ ok(false, "Debugger error events should not be fired at workers.");
+ };
+
+ info("Send a request to the worker debugger. This should cause the " +
+ "worker debugger to report an error.");
+ promise = waitForDebuggerError(dbg);
+ dbg.postMessage("report");
+ let error = await promise;
+ is(error.fileName, DEBUGGER_URL,
+ "fileName should be name of file from which error is reported.");
+ is(error.lineNumber, 6,
+ "lineNumber should be line number from which error is reported.");
+ is(error.message, "reported", "message should be reported.");
+
+ info("Send a request to the worker debugger. This should cause the " +
+ "worker debugger to throw an error.");
+ promise = waitForDebuggerError(dbg);
+ dbg.postMessage("throw");
+ error = await promise;
+ is(error.fileName, DEBUGGER_URL,
+ "fileName should be name of file from which error is thrown");
+ is(error.lineNumber, 9,
+ "lineNumber should be line number from which error is thrown");
+ is(error.message, "Error: thrown", "message should be Error: thrown");
+
+ info("Send a reqeust to the child worker debugger. This should cause " +
+ "the child worker debugger to report an error.");
+ promise = waitForDebuggerError(childDbg);
+ childDbg.postMessage("report");
+ error = await promise;
+ is(error.fileName, DEBUGGER_URL,
+ "fileName should be name of file from which error is reported.");
+ is(error.lineNumber, 6,
+ "lineNumber should be line number from which error is reported.");
+ is(error.message, "reported", "message should be reported.");
+
+ info("Send a message to the child worker debugger. This should cause " +
+ "the child worker debugger to throw an error.");
+ promise = waitForDebuggerError(childDbg);
+ childDbg.postMessage("throw");
+ error = await promise;
+ is(error.fileName, DEBUGGER_URL,
+ "fileName should be name of file from which error is thrown");
+ is(error.lineNumber, 9,
+ "lineNumber should be line number from which error is thrown");
+ is(error.message, "Error: thrown", "message should be Error: thrown");
+
+ SimpleTest.finish();
+ })();
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_WorkerDebuggerGlobalScope.setImmediate.xhtml b/dom/workers/test/test_WorkerDebuggerGlobalScope.setImmediate.xhtml
new file mode 100644
index 0000000000..4b09f01708
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebuggerGlobalScope.setImmediate.xhtml
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebuggerGlobalScope.setImmediate"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ const WORKER_URL = "WorkerDebuggerGlobalScope.setImmediate_worker.js";
+ const DEBUGGER_URL = BASE_URL + "WorkerDebuggerGlobalScope.setImmediate_debugger.js";
+
+ function test() {
+ (async function() {
+ SimpleTest.waitForExplicitFinish();
+
+ let promise = waitForRegister(WORKER_URL, DEBUGGER_URL);
+ let worker = new Worker(WORKER_URL);
+ let dbg = await promise;
+
+ info("Send a request to the worker debugger. This should cause a " +
+ "the worker debugger to send two responses. The worker debugger " +
+ "should send the second response before the first one, since " +
+ "the latter is delayed until the next tick of the event loop.");
+ promise = waitForMultiple([
+ waitForDebuggerMessage(dbg, "pong2"),
+ waitForDebuggerMessage(dbg, "pong1")
+ ]);
+ dbg.postMessage("ping");
+ await promise;
+
+ SimpleTest.finish();
+ })();
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_WorkerDebuggerManager.xhtml b/dom/workers/test/test_WorkerDebuggerManager.xhtml
new file mode 100644
index 0000000000..c75f5a453e
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebuggerManager.xhtml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebuggerManager"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ const WORKER_URL = "WorkerDebuggerManager_worker.js";
+ const CHILD_WORKER_URL = "WorkerDebuggerManager_childWorker.js";
+
+ function test() {
+ (async function() {
+ SimpleTest.waitForExplicitFinish();
+
+ info("Check that worker debuggers are not enumerated before they are " +
+ "registered.");
+ ok(!findDebugger(WORKER_URL),
+ "Worker debugger should not be enumerated before it is registered.");
+ ok(!findDebugger(CHILD_WORKER_URL),
+ "Child worker debugger should not be enumerated before it is " +
+ "registered.");
+
+ info("Create a worker that creates a child worker, and wait for " +
+ "their debuggers to be registered.");
+ let promise = waitForMultiple([
+ waitForRegister(WORKER_URL),
+ waitForRegister(CHILD_WORKER_URL)
+ ]);
+ let worker = new Worker(WORKER_URL);
+ let [dbg, childDbg] = await promise;
+
+ info("Check that worker debuggers are enumerated after they are " +
+ "registered.");
+ ok(findDebugger(WORKER_URL),
+ "Worker debugger should be enumerated after it is registered.");
+ ok(findDebugger(CHILD_WORKER_URL),
+ "Child worker debugger should be enumerated after it is " +
+ "registered.");
+
+ info("Check that worker debuggers are not closed before they are " +
+ "unregistered.");
+ is(dbg.isClosed, false,
+ "Worker debugger should not be closed before it is unregistered.");
+ is(childDbg.isClosed, false,
+ "Child worker debugger should not be closed before it is " +
+ "unregistered");
+
+ info("Terminate the worker and the child worker, and wait for their " +
+ "debuggers to be unregistered.");
+ promise = waitForMultiple([
+ waitForUnregister(CHILD_WORKER_URL),
+ waitForUnregister(WORKER_URL),
+ ]);
+ worker.terminate();
+ await promise;
+
+ info("Check that worker debuggers are not enumerated after they are " +
+ "unregistered.");
+ ok(!findDebugger(WORKER_URL),
+ "Worker debugger should not be enumerated after it is " +
+ "unregistered.");
+ ok(!findDebugger(CHILD_WORKER_URL),
+ "Child worker debugger should not be enumerated after it is " +
+ "unregistered.");
+
+ info("Check that worker debuggers are closed after they are " +
+ "unregistered.");
+ is(dbg.isClosed, true,
+ "Worker debugger should be closed after it is unregistered.");
+ is(childDbg.isClosed, true,
+ "Child worker debugger should be closed after it is unregistered.");
+
+ info("Check that property accesses on worker debuggers throws " +
+ "after they are closed.");
+ assertThrows(() => dbg.url,
+ "Property accesses on worker debugger should throw " +
+ "after it is closed.");
+ assertThrows(() => childDbg.url,
+ "Property accesses on child worker debugger should " +
+ "throw after it is closed.");
+
+ SimpleTest.finish();
+ })();
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_WorkerDebugger_console.xhtml b/dom/workers/test/test_WorkerDebugger_console.xhtml
new file mode 100644
index 0000000000..2363b53be0
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger_console.xhtml
@@ -0,0 +1,98 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebuggerGlobalScope.console methods"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ const WORKER_URL = "WorkerDebugger.console_worker.js";
+ const CHILD_WORKER_URL = "WorkerDebugger.console_childWorker.js";
+ const DEBUGGER_URL = BASE_URL + "WorkerDebugger.console_debugger.js";
+
+ consoleMessagesReceived = 0;
+ function test() {
+ const ConsoleAPIStorage = SpecialPowers.Cc[
+ "@mozilla.org/consoleAPI-storage;1"
+ ].getService(SpecialPowers.Ci.nsIConsoleAPIStorage);
+
+ function consoleListener() {
+ this.observe = this.observe.bind(this);
+ ConsoleAPIStorage.addLogEventListener(this.observe, SpecialPowers.wrap(document).nodePrincipal);
+ }
+
+ consoleListener.prototype = {
+ observe: function(aSubject) {
+ var obj = aSubject.wrappedJSObject;
+ if (obj.arguments[0] == "Hello from the debugger script!" &&
+ !consoleMessagesReceived) {
+ consoleMessagesReceived++;
+ ok(true, "Something has been received");
+ ConsoleAPIStorage.removeLogEventListener(this.observe);
+ }
+ }
+ }
+
+ var cl = new consoleListener();
+
+ (async function() {
+ SimpleTest.waitForExplicitFinish();
+
+ info("Create a worker that creates a child worker, wait for their " +
+ "debuggers to be registered, and initialize them.");
+ let promise = waitForMultiple([
+ waitForRegister(WORKER_URL, DEBUGGER_URL),
+ waitForRegister(CHILD_WORKER_URL, DEBUGGER_URL)
+ ]);
+ let worker = new Worker(WORKER_URL);
+ let [dbg, childDbg] = await promise;
+
+ info("Send a request to the worker debugger. This should cause the " +
+ "the worker debugger to send a response.");
+ dbg.addListener({
+ onMessage: function(msg) {
+ try {
+ msg = JSON.parse(msg);
+ } catch(e) {
+ ok(false, "Something went wrong");
+ return;
+ }
+
+ if (msg.type == 'finish') {
+ ok(consoleMessagesReceived, "We received something via debugger console!");
+ dbg.removeListener(this);
+ SimpleTest.finish();
+ return;
+ }
+
+ if (msg.type == 'status') {
+ ok(msg.what, msg.msg);
+ return;
+ }
+
+ ok(false, "Something went wrong");
+ }
+ });
+
+ dbg.postMessage("do magic");
+ })();
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_WorkerDebugger_frozen.xhtml b/dom/workers/test/test_WorkerDebugger_frozen.xhtml
new file mode 100644
index 0000000000..57424ae612
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger_frozen.xhtml
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebugger with frozen workers"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ const WINDOW1_URL = "WorkerDebugger_frozen_window1.html";
+ const WINDOW2_URL = "WorkerDebugger_frozen_window2.html";
+
+ const WORKER1_URL = "WorkerDebugger_frozen_worker1.js";
+ const WORKER2_URL = "WorkerDebugger_frozen_worker2.js";
+
+ function test() {
+ (async function() {
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({set:
+ [["browser.sessionhistory.max_total_viewers", 10]]});
+
+ let promise = waitForMultiple([
+ waitForRegister(WORKER1_URL),
+ waitForWindowMessage(window, "ready"),
+ ]);
+ let testWin = window.open(WINDOW1_URL, "testWin");;
+ let [dbg1] = await promise;
+ is(dbg1.isClosed, false,
+ "debugger for worker on page 1 should not be closed");
+
+ promise = waitForMultiple([
+ waitForUnregister(WORKER1_URL),
+ waitForDebuggerClose(dbg1),
+ waitForRegister(WORKER2_URL),
+ waitForWindowMessage(window, "ready"),
+ ]);
+ testWin.location = WINDOW2_URL;
+ let [,, dbg2] = await promise;
+ is(dbg1.isClosed, true,
+ "debugger for worker on page 1 should be closed");
+ is(dbg2.isClosed, false,
+ "debugger for worker on page 2 should not be closed");
+
+ promise = Promise.all([
+ waitForUnregister(WORKER2_URL),
+ waitForDebuggerClose(dbg2),
+ waitForRegister(WORKER1_URL)
+ ]);
+ testWin.history.back();
+ [,, dbg1] = await promise;
+ is(dbg1.isClosed, false,
+ "debugger for worker on page 1 should not be closed");
+ is(dbg2.isClosed, true,
+ "debugger for worker on page 2 should be closed");
+
+ SimpleTest.finish();
+ })();
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_WorkerDebugger_promise.xhtml b/dom/workers/test/test_WorkerDebugger_promise.xhtml
new file mode 100644
index 0000000000..14d50969b5
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger_promise.xhtml
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
++ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebugger with DOM Promises"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ const WORKER_URL = "WorkerDebugger_promise_worker.js";
+ const DEBUGGER_URL = BASE_URL + "WorkerDebugger_promise_debugger.js";
+
+ function test() {
+ (async function() {
+ SimpleTest.waitForExplicitFinish();
+
+ let promise = waitForRegister(WORKER_URL, DEBUGGER_URL);
+ let worker = new Worker(WORKER_URL);
+ let dbg = await promise;
+
+ info("Send a request to the worker. This should cause the worker " +
+ "to send a response.");
+ promise = waitForWorkerMessage(worker, "resolved");
+ worker.postMessage("resolve");
+ await promise;
+
+ info("Send a request to the debugger. This should cause the debugger " +
+ "to send a response.");
+ promise = waitForDebuggerMessage(dbg, "resolved");
+ dbg.postMessage("resolve");
+ await promise;
+
+ info("Send a request to the worker. This should cause the debugger " +
+ "to enter a nested event loop.");
+ promise = waitForDebuggerMessage(dbg, "paused");
+ worker.postMessage("pause");
+ await promise;
+
+ info("Send a request to the debugger. This should cause the debugger " +
+ "to leave the nested event loop.");
+ promise = waitForMultiple([
+ waitForDebuggerMessage(dbg, "resumed"),
+ waitForWorkerMessage(worker, "resumed")
+ ]);
+ dbg.postMessage("resume");
+ await promise;
+
+ SimpleTest.finish();
+ })();
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_WorkerDebugger_suspended.xhtml b/dom/workers/test/test_WorkerDebugger_suspended.xhtml
new file mode 100644
index 0000000000..d0c9bff552
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger_suspended.xhtml
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebugger with suspended workers"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ const WORKER_URL = "WorkerDebugger_suspended_worker.js";
+ const DEBUGGER_URL = BASE_URL + "WorkerDebugger_suspended_debugger.js";
+
+ function test() {
+ (async function() {
+ SimpleTest.waitForExplicitFinish();
+
+ info("Create a worker, wait for its debugger to be registered, and " +
+ "initialize it.");
+ let promise = waitForRegister(WORKER_URL, DEBUGGER_URL);
+ let worker = new Worker(WORKER_URL);
+ let dbg = await promise;
+
+ info("Send a request to the worker. This should cause both the " +
+ "worker and the worker debugger to send a response.");
+ promise = waitForMultiple([
+ waitForWorkerMessage(worker, "worker"),
+ waitForDebuggerMessage(dbg, "debugger")
+ ]);
+ worker.postMessage("ping");
+ await promise;
+
+ info("Suspend the workers for this window, and send another request " +
+ "to the worker. This should cause only the worker debugger to " +
+ "send a response.");
+ let windowUtils = window.windowUtils;
+ windowUtils.suspendTimeouts();
+ function onmessage() {
+ ok(false, "The worker should not send a response.");
+ };
+ worker.addEventListener("message", onmessage);
+ promise = waitForDebuggerMessage(dbg, "debugger");
+ worker.postMessage("ping");
+ await promise;
+ worker.removeEventListener("message", onmessage);
+
+ info("Resume the workers for this window. This should cause the " +
+ "worker to send a response to the previous request.");
+ promise = waitForWorkerMessage(worker, "worker");
+ windowUtils.resumeTimeouts();
+ await promise;
+
+ SimpleTest.finish();
+ })();
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_atob.html b/dom/workers/test/test_atob.html
new file mode 100644
index 0000000000..0e82029b46
--- /dev/null
+++ b/dom/workers/test/test_atob.html
@@ -0,0 +1,57 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for DOM Worker Threads</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script src="atob_worker.js" language="javascript"></script>
+<script class="testbody" type="text/javascript">
+
+ var dataIndex = 0;
+
+ var worker = new Worker("atob_worker.js");
+ worker.onmessage = function(event) {
+ switch (event.data.type) {
+ case "done":
+ is(dataIndex, data.length, "Saw all values");
+ SimpleTest.finish();
+ return;
+ case "btoa":
+ is(btoa(data[dataIndex]), event.data.value,
+ "Good btoa value " + dataIndex);
+ break;
+ case "atob":
+ is(atob(btoa(data[dataIndex])) + "", event.data.value,
+ "Good round trip value " + dataIndex);
+ dataIndex++;
+ break;
+ default:
+ ok(false, "Worker posted a bad message: " + event.message);
+ worker.terminate();
+ SimpleTest.finish();
+ }
+ }
+
+ worker.onerror = function(event) {
+ ok(false, "Worker threw an error: " + event.message);
+ worker.terminate();
+ SimpleTest.finish();
+ }
+
+ worker.postMessage("go");
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_blobConstructor.html b/dom/workers/test/test_blobConstructor.html
new file mode 100644
index 0000000000..4aff5b545b
--- /dev/null
+++ b/dom/workers/test/test_blobConstructor.html
@@ -0,0 +1,60 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+<!--
+Tests of DOM Worker Blob constructor
+-->
+<head>
+ <title>Test for DOM Worker Blob constructor</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+(function() {
+ onerror = function(e) {
+ ok(false, "Main Thread had an error: " + event.data);
+ SimpleTest.finish();
+ };
+ function f() {
+ onmessage = function(e) {
+ var b = new Blob([e.data, "World"],{type: "text/plain"});
+ var fr = new FileReaderSync();
+ postMessage({text: fr.readAsText(b), type: b.type});
+ };
+ }
+ var b = new Blob([f,"f();"]);
+ var u = URL.createObjectURL(b);
+ var w = new Worker(u);
+ w.onmessage = function(e) {
+ URL.revokeObjectURL(u);
+ is(e.data.text, fr.result);
+ is(e.data.type, "text/plain");
+ SimpleTest.finish();
+ };
+ w.onerror = function(e) {
+ is(e.target, w);
+ ok(false, "Worker had an error: " + e.message);
+ SimpleTest.finish();
+ };
+
+ b = new Blob(["Hello, "]);
+ var fr = new FileReader();
+ fr.readAsText(new Blob([b, "World"],{}));
+ fr.onload = function() {
+ w.postMessage(b);
+ };
+ SimpleTest.waitForExplicitFinish();
+})();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_blobWorkers.html b/dom/workers/test/test_blobWorkers.html
new file mode 100644
index 0000000000..6ecd6c6f4b
--- /dev/null
+++ b/dom/workers/test/test_blobWorkers.html
@@ -0,0 +1,31 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <script src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <script type="text/javascript">
+ const message = "hi";
+
+ const workerScript =
+ "onmessage = function(event) {" +
+ " postMessage(event.data);" +
+ "};";
+
+ var worker = new Worker(URL.createObjectURL(new Blob([workerScript])));
+ worker.onmessage = function(event) {
+ is(event.data, message, "Got correct message");
+ SimpleTest.finish();
+ };
+ worker.postMessage(message);
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/test_bug1002702.html b/dom/workers/test/test_bug1002702.html
new file mode 100644
index 0000000000..c020dcfd05
--- /dev/null
+++ b/dom/workers/test/test_bug1002702.html
@@ -0,0 +1,26 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 1002702</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+var port = new SharedWorker('data:application/javascript,1').port;
+port.close();
+SpecialPowers.forceGC();
+ok(true, "No crash \\o/");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug1010784.html b/dom/workers/test/test_bug1010784.html
new file mode 100644
index 0000000000..3e2f62971d
--- /dev/null
+++ b/dom/workers/test/test_bug1010784.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1010784
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1010784</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1010784">Mozilla Bug 1010784</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+ var worker = new Worker("file_bug1010784_worker.js");
+
+ worker.onmessage = function(event) {
+ is(event.data, "done", "Got correct result");
+ SimpleTest.finish();
+ }
+
+ worker.postMessage("testXHR.txt");
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug1014466.html b/dom/workers/test/test_bug1014466.html
new file mode 100644
index 0000000000..26ef2fb316
--- /dev/null
+++ b/dom/workers/test/test_bug1014466.html
@@ -0,0 +1,42 @@
+<!--
+2 Any copyright is dedicated to the Public Domain.
+3 http://creativecommons.org/publicdomain/zero/1.0/
+4 -->
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1014466
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1014466</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1014466">Mozilla Bug 1014466</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+ var worker = new Worker("bug1014466_worker.js");
+
+ worker.onmessage = function(event) {
+ if (event.data.type == 'finish') {
+ SimpleTest.finish();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ }
+ };
+
+ worker.postMessage(true);
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug1020226.html b/dom/workers/test/test_bug1020226.html
new file mode 100644
index 0000000000..1ed69db41e
--- /dev/null
+++ b/dom/workers/test/test_bug1020226.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1020226
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1020226</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1020226">Mozilla Bug 1020226</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<iframe id="iframe" src="bug1020226_frame.html" onload="finishTest();">
+</iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+function finishTest() {
+ document.getElementById("iframe").onload = null;
+ window.onmessage = function(e) {
+ info("Got message");
+ document.getElementById("iframe").src = "about:blank";
+ // We aren't really interested in the test, it shouldn't crash when the
+ // worker is GCed later.
+ ok(true, "Should not crash");
+ SimpleTest.finish();
+ };
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug1036484.html b/dom/workers/test/test_bug1036484.html
new file mode 100644
index 0000000000..feada50f5a
--- /dev/null
+++ b/dom/workers/test/test_bug1036484.html
@@ -0,0 +1,52 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads: bug 1036484
+-->
+<head>
+ <title>Test for DOM Worker Threads: bug 1036484</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function test(script) {
+ var worker = new Worker(script);
+
+ worker.onmessage = function(event) {
+ ok(false, "Shouldn't ever get a message!");
+ }
+
+ worker.onerror = function(event) {
+ is(event.target, worker);
+ event.preventDefault();
+ runTests();
+ };
+
+ worker.postMessage("dummy");
+}
+
+var tests = [ '404_server.sjs', '404_server.sjs?js' ];
+function runTests() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var script = tests.shift();
+ test(script);
+}
+
+SimpleTest.waitForExplicitFinish();
+runTests();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug1060621.html b/dom/workers/test/test_bug1060621.html
new file mode 100644
index 0000000000..d2af81c93e
--- /dev/null
+++ b/dom/workers/test/test_bug1060621.html
@@ -0,0 +1,30 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for URLSearchParams object in workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("bug1060621_worker.js");
+
+ worker.onmessage = function(event) {
+ ok(true, "The operation is done. We should not leak.");
+ SimpleTest.finish();
+ };
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug1062920.html b/dom/workers/test/test_bug1062920.html
new file mode 100644
index 0000000000..7a42bd2dfd
--- /dev/null
+++ b/dom/workers/test/test_bug1062920.html
@@ -0,0 +1,70 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for navigator property override</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ function checkValues() {
+ var worker = new Worker("bug1062920_worker.js");
+
+ worker.onmessage = function(event) {
+ var ifr = document.createElement('IFRAME');
+ ifr.src = "about:blank";
+
+ ifr.addEventListener('load', function() {
+ var nav = ifr.contentWindow.navigator;
+ is(event.data.appCodeName, nav.appCodeName, "appCodeName should match");
+ is(event.data.appName, nav.appName, "appName should match");
+ is(event.data.appVersion, nav.appVersion, "appVersion should match");
+ is(event.data.platform, nav.platform, "platform should match");
+ is(event.data.userAgent, nav.userAgent, "userAgent should match");
+ is(event.data.product, nav.product, "product should match");
+ runTests();
+ });
+
+ document.getElementById('content').appendChild(ifr);
+ };
+ }
+
+ function replaceAndCheckValues() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["general.appname.override", "appName overridden"],
+ ["general.appversion.override", "appVersion overridden"],
+ ["general.platform.override", "platform overridden"],
+ ["general.useragent.override", "userAgent overridden"]
+ ]}, checkValues);
+ }
+
+ var tests = [
+ checkValues,
+ replaceAndCheckValues
+ ];
+
+ function runTests() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTests();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug1062920.xhtml b/dom/workers/test/test_bug1062920.xhtml
new file mode 100644
index 0000000000..d76e320def
--- /dev/null
+++ b/dom/workers/test/test_bug1062920.xhtml
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="DOM Worker Threads Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+
+ function checkValues() {
+ var worker = new Worker("bug1062920_worker.js");
+
+ worker.onmessage = function(event) {
+ is(event.data.appCodeName, navigator.appCodeName, "appCodeName should match");
+ is(event.data.appName, navigator.appName, "appName should match");
+ isnot(event.data.appName, "appName overridden", "appName is not overridden");
+ is(event.data.appVersion, navigator.appVersion, "appVersion should match");
+ isnot(event.data.appVersion, "appVersion overridden", "appVersion is not overridden");
+ is(event.data.platform, navigator.platform, "platform should match");
+ isnot(event.data.platform, "platform overridden", "platform is not overridden");
+ is(event.data.userAgent, navigator.userAgent, "userAgent should match");
+ is(event.data.product, navigator.product, "product should match");
+ runTests();
+ };
+ }
+
+ function replaceAndCheckValues() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["general.appname.override", "appName overridden"],
+ ["general.appversion.override", "appVersion overridden"],
+ ["general.platform.override", "platform overridden"],
+ ["general.useragent.override", "userAgent overridden"]
+ ]}, checkValues);
+ }
+
+ var tests = [
+ replaceAndCheckValues,
+ checkValues
+ ];
+
+ function runTests() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTests();
+
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_bug1063538.html b/dom/workers/test/test_bug1063538.html
new file mode 100644
index 0000000000..a1dc6624b9
--- /dev/null
+++ b/dom/workers/test/test_bug1063538.html
@@ -0,0 +1,47 @@
+<!--
+2 Any copyright is dedicated to the Public Domain.
+3 http://creativecommons.org/publicdomain/zero/1.0/
+4 -->
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1063538
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1063538</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1063538">Mozilla Bug 1063538</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+function runTest() {
+ var worker = new Worker("bug1063538_worker.js");
+
+ worker.onmessage = function(e) {
+ if (e.data.type == 'finish') {
+ ok(e.data.progressFired, "Progress was fired.");
+ SimpleTest.finish();
+ }
+ };
+
+ worker.postMessage(true);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTest);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug1104064.html b/dom/workers/test/test_bug1104064.html
new file mode 100644
index 0000000000..1c8b3ac92c
--- /dev/null
+++ b/dom/workers/test/test_bug1104064.html
@@ -0,0 +1,27 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 1104064</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+var worker = new Worker("bug1104064_worker.js");
+worker.onmessage = function() {
+ ok(true, "setInterval has been called twice.");
+ SimpleTest.finish();
+}
+worker.postMessage("go");
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug1132395.html b/dom/workers/test/test_bug1132395.html
new file mode 100644
index 0000000000..8d424e5ad4
--- /dev/null
+++ b/dom/workers/test/test_bug1132395.html
@@ -0,0 +1,40 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for 1132395</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+
+// This test is full of dummy debug messages. This is because I need to follow
+// an hard-to-reproduce timeout failure.
+
+info("test started");
+var sw = new SharedWorker('bug1132395_sharedWorker.js');
+sw.port.onmessage = function(event) {
+ info("sw.onmessage received");
+ ok(true, "We didn't crash.");
+ SimpleTest.finish();
+}
+
+sw.onerror = function(event) {
+ ok(false, "Failed to create a ServiceWorker");
+ SimpleTest.finish();
+}
+
+info("sw.postmessage called");
+sw.port.postMessage('go');
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug1132924.html b/dom/workers/test/test_bug1132924.html
new file mode 100644
index 0000000000..b5b952e908
--- /dev/null
+++ b/dom/workers/test/test_bug1132924.html
@@ -0,0 +1,28 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for 1132924</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+var w = new Worker('bug1132924_worker.js');
+w.onmessage = function(event) {
+ ok(true, "We are still alive.");
+ SimpleTest.finish();
+}
+
+w.postMessage('go');
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug1278777.html b/dom/workers/test/test_bug1278777.html
new file mode 100644
index 0000000000..c995212bd0
--- /dev/null
+++ b/dom/workers/test/test_bug1278777.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1278777
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1278777</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1278777">Mozilla Bug 1278777</a>
+ <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var worker = new Worker('worker_bug1278777.js');
+worker.onerror = function() {
+ ok(false, "We should not see any error.");
+ SimpleTest.finish();
+}
+
+worker.onmessage = function(e) {
+ ok(e.data, "Everything seems ok.");
+ SimpleTest.finish();
+}
+
+ </script>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug1301094.html b/dom/workers/test/test_bug1301094.html
new file mode 100644
index 0000000000..efe25fa3a9
--- /dev/null
+++ b/dom/workers/test/test_bug1301094.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1301094
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1301094</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1301094">Mozilla Bug 1301094</a>
+ <input id="file" type="file"></input>
+ <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var url = SimpleTest.getTestFileURL("script_createFile.js");
+script = SpecialPowers.loadChromeScript(url);
+
+var mainThreadOk, workerOk;
+
+function maybeFinish() {
+ if (mainThreadOk & workerOk) {
+ SimpleTest.finish();
+ }
+}
+
+function onOpened(message) {
+ var input = document.getElementById('file');
+ SpecialPowers.wrap(input).mozSetDndFilesAndDirectories([message.data]);
+
+ var worker = new Worker('worker_bug1301094.js');
+ worker.onerror = function() {
+ ok(false, "We should not see any error.");
+ SimpleTest.finish();
+ }
+
+ worker.onmessage = function(e) {
+ ok(e.data, "Everything seems OK on the worker-side.");
+
+ workerOk = true;
+ maybeFinish();
+ }
+
+ is(input.files.length, 1, "We have something");
+ ok(input.files[0] instanceof Blob, "We have one Blob");
+ worker.postMessage(input.files[0]);
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", 'worker_bug1301094.js', false);
+ xhr.onload = function() {
+ ok(xhr.responseText, "Everything seems OK on the main-thread-side.");
+ mainThreadOk = true;
+ maybeFinish();
+ };
+
+ var fd = new FormData();
+ fd.append('file', input.files[0]);
+ xhr.send(fd);
+}
+
+script.addMessageListener("file.opened", onOpened);
+script.sendAsyncMessage("file.open");
+
+ </script>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug1317725.html b/dom/workers/test/test_bug1317725.html
new file mode 100644
index 0000000000..7da3fc2644
--- /dev/null
+++ b/dom/workers/test/test_bug1317725.html
@@ -0,0 +1,49 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 1317725</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<input type="file" id="file" />
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var url = SimpleTest.getTestFileURL("script_createFile.js");
+script = SpecialPowers.loadChromeScript(url);
+
+function onOpened(message) {
+ var input = document.getElementById('file');
+ SpecialPowers.wrap(input).mozSetFileArray([message.data]);
+
+ var worker = new Worker("test_bug1317725.js");
+ worker.onerror = function(e) {
+ ok(false, "We should not see any error.");
+ SimpleTest.finish();
+ }
+
+ worker.onmessage = function(e) {
+ ok(e.data, "Everything seems OK on the worker-side.");
+ SimpleTest.finish();
+ }
+
+ is(input.files.length, 1, "We have something");
+ ok(input.files[0] instanceof Blob, "We have one Blob");
+ worker.postMessage(input.files[0]);
+}
+
+script.addMessageListener("file.opened", onOpened);
+script.sendAsyncMessage("file.open");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug1317725.js b/dom/workers/test/test_bug1317725.js
new file mode 100644
index 0000000000..858488fac3
--- /dev/null
+++ b/dom/workers/test/test_bug1317725.js
@@ -0,0 +1,8 @@
+onmessage = function (e) {
+ var data = new FormData();
+ data.append("Filedata", e.data.slice(0, 127), encodeURI(e.data.name));
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", location.href, false);
+ xhr.send(data);
+ postMessage("No crash \\o/");
+};
diff --git a/dom/workers/test/test_bug1824498.html b/dom/workers/test/test_bug1824498.html
new file mode 100644
index 0000000000..ea01cabe1b
--- /dev/null
+++ b/dom/workers/test/test_bug1824498.html
@@ -0,0 +1,38 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Worker Import failure (Bug 1824498)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1824498">
+Worker Import failure test: Bug 1824498
+</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+document.addEventListener("DOMContentLoaded", async () => {
+ await SpecialPowers.pushPrefEnv(
+ { set: [["dom.workers.modules.enabled", true ]] });
+
+ const worker = new Worker("worker_bug1824498.mjs", {"type": "module"})
+ worker.onerror = function(event) {
+ ok(true, "not assert");
+ SimpleTest.finish();
+ };
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug949946.html b/dom/workers/test/test_bug949946.html
new file mode 100644
index 0000000000..41b021a098
--- /dev/null
+++ b/dom/workers/test/test_bug949946.html
@@ -0,0 +1,26 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 949946</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+new SharedWorker('sharedWorker_sharedWorker.js');
+new SharedWorker('sharedWorker_sharedWorker.js', ':');
+new SharedWorker('sharedWorker_sharedWorker.js', '|||');
+ok(true, "3 SharedWorkers created!");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug978260.html b/dom/workers/test/test_bug978260.html
new file mode 100644
index 0000000000..056b8b6c72
--- /dev/null
+++ b/dom/workers/test/test_bug978260.html
@@ -0,0 +1,35 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for DOM Worker Threads</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ var xhr = new XMLHttpRequest();
+ xhr.onload = function () {
+ var worker = new Worker("bug978260_worker.js");
+ worker.onmessage = function(event) {
+ is(event.data, "loaded");
+ SimpleTest.finish();
+ }
+ }
+
+ xhr.open('GET', '/', false);
+ xhr.send();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug998474.html b/dom/workers/test/test_bug998474.html
new file mode 100644
index 0000000000..93632a5de4
--- /dev/null
+++ b/dom/workers/test/test_bug998474.html
@@ -0,0 +1,40 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 998474</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="boom();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+function boom()
+{
+ var worker = new SharedWorker("bug998474_worker.js");
+
+ setTimeout(function() {
+ port = worker.port;
+ port.postMessage("");
+
+ setTimeout(function() {
+ port.start();
+ ok(true, "Still alive!");
+ SimpleTest.finish();
+ }, 150);
+ }, 150);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_chromeWorker.html b/dom/workers/test/test_chromeWorker.html
new file mode 100644
index 0000000000..35d5c08928
--- /dev/null
+++ b/dom/workers/test/test_chromeWorker.html
@@ -0,0 +1,26 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Test for DOM Worker Threads</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ try {
+ var worker = new ChromeWorker("simpleThread_worker.js");
+ ok(false, "ChromeWorker constructor should be blocked!");
+ }
+ catch (e) {
+ ok(true, "ChromeWorker constructor wasn't blocked!");
+ }
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_chromeWorker.xhtml b/dom/workers/test/test_chromeWorker.xhtml
new file mode 100644
index 0000000000..65d14f8851
--- /dev/null
+++ b/dom/workers/test/test_chromeWorker.xhtml
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="DOM Worker Threads Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ add_task(async function classic_worker_test() {
+ let worker = window.classicWorker = new ChromeWorker("chromeWorker_worker.js");
+ await new Promise((resolve, reject) => {
+ worker.onmessage = function(event) {
+ is(event.data, "Done!", "Got the done message!");
+ resolve();
+ };
+ worker.onerror = function(event) {
+ ok(false, "Classic Worker had an error: " + event.message);
+ worker.terminate();
+ reject();
+ };
+ worker.postMessage("go");
+ });
+ });
+
+ add_task(async function module_worker_test() {
+ waitForWorkerFinish();
+
+ let worker = window.moduleWorker = new ChromeWorker("chromeWorker_worker.sys.mjs", { type: "module" });
+ await new Promise((resolve, reject) => {
+ worker.onmessage = function(event) {
+ is(event.data, "Done!", "Got the done message!");
+ resolve();
+ };
+ worker.onerror = function(event) {
+ ok(false, "Module Worker had an error: " + event.message);
+ worker.terminate();
+ reject();
+ };
+ worker.postMessage("go");
+ });
+ });
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_chromeWorkerJSM.xhtml b/dom/workers/test/test_chromeWorkerJSM.xhtml
new file mode 100644
index 0000000000..6341737815
--- /dev/null
+++ b/dom/workers/test/test_chromeWorkerJSM.xhtml
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="DOM Worker Threads Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ function test()
+ {
+ waitForWorkerFinish();
+
+ var worker;
+
+ function done()
+ {
+ worker = null;
+ finish();
+ }
+
+ function messageCallback(event) {
+ is(event.data, "Done", "Correct message");
+ done();
+ }
+
+ function errorCallback(event) {
+ ok(false, "Worker had an error: " + event.message);
+ done();
+ }
+
+ const {WorkerTest} = ChromeUtils.import("chrome://mochitests/content/chrome/dom/workers/test/WorkerTest.jsm");
+
+ worker = WorkerTest.go(window.location.href, messageCallback,
+ errorCallback);
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_clearTimeouts.html b/dom/workers/test/test_clearTimeouts.html
new file mode 100644
index 0000000000..caa87fbf56
--- /dev/null
+++ b/dom/workers/test/test_clearTimeouts.html
@@ -0,0 +1,31 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for DOM Worker Threads</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ new Worker("clearTimeouts_worker.js").onmessage = function(event) {
+ event.target.terminate();
+
+ is(event.data, "ready", "Correct message");
+ setTimeout(function() { SimpleTest.finish(); }, 1000);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_clearTimeoutsImplicit.html b/dom/workers/test/test_clearTimeoutsImplicit.html
new file mode 100644
index 0000000000..59a37974ca
--- /dev/null
+++ b/dom/workers/test/test_clearTimeoutsImplicit.html
@@ -0,0 +1,31 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for DOM Worker Threads</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ new Worker("clearTimeoutsImplicit_worker.js").onmessage = function(event) {
+ event.target.terminate();
+
+ is(event.data, "ready", "Correct message");
+ setTimeout(function() { SimpleTest.finish(); }, 1000);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_console.html b/dom/workers/test/test_console.html
new file mode 100644
index 0000000000..b8c0f189ab
--- /dev/null
+++ b/dom/workers/test/test_console.html
@@ -0,0 +1,44 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Console
+-->
+<head>
+ <title>Test for DOM Worker Console</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" language="javascript">
+ var worker = new Worker("console_worker.js");
+
+ worker.onmessage = function(event) {
+ is(event.target, worker, "Worker and target match!");
+ ok(event.data.status, event.data.event);
+
+ if (!event.data.status || event.data.last)
+ SimpleTest.finish();
+ };
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ SimpleTest.finish();
+ }
+
+ worker.postMessage(true);
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_consoleAndBlobs.html b/dom/workers/test/test_consoleAndBlobs.html
new file mode 100644
index 0000000000..e07cfe5dca
--- /dev/null
+++ b/dom/workers/test/test_consoleAndBlobs.html
@@ -0,0 +1,46 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for console API and blobs</title>
+ <script src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ </head>
+ <body>
+ <script type="text/javascript">
+ const ConsoleAPIStorage = SpecialPowers.Cc[
+ "@mozilla.org/consoleAPI-storage;1"
+ ].getService(SpecialPowers.Ci.nsIConsoleAPIStorage);
+
+ function consoleListener() {
+ this.observe = this.observe.bind(this);
+ ConsoleAPIStorage.addLogEventListener(this.observe, SpecialPowers.wrap(document).nodePrincipal);
+ }
+
+ var order = 0;
+ consoleListener.prototype = {
+ observe(aSubject) {
+ ok(true, "Something has been received");
+
+ var obj = aSubject.wrappedJSObject;
+ if (obj.arguments[0] && obj.arguments[0].msg === 'consoleAndBlobs') {
+ ConsoleAPIStorage.removeLogEventListener(this.observe);
+ is(obj.arguments[0].blob.size, 3, "The size is correct");
+ is(obj.arguments[0].blob.type, 'foo/bar', "The type is correct");
+ SimpleTest.finish();
+ }
+ }
+ }
+
+ var cl = new consoleListener();
+
+ new Worker('worker_consoleAndBlobs.js');
+ SimpleTest.waitForExplicitFinish();
+
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/test_consoleReplaceable.html b/dom/workers/test/test_consoleReplaceable.html
new file mode 100644
index 0000000000..b8a60411e4
--- /dev/null
+++ b/dom/workers/test/test_consoleReplaceable.html
@@ -0,0 +1,44 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Console
+-->
+<head>
+ <title>Test for DOM Worker Console</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" language="javascript">
+ var worker = new Worker("consoleReplaceable_worker.js");
+
+ worker.onmessage = function(event) {
+ is(event.target, worker, "Worker and target match!");
+ ok(event.data.status, event.data.event);
+
+ if (event.data.last)
+ SimpleTest.finish();
+ };
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ SimpleTest.finish();
+ }
+
+ worker.postMessage(true);
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_contentWorker.html b/dom/workers/test/test_contentWorker.html
new file mode 100644
index 0000000000..38891a88c5
--- /dev/null
+++ b/dom/workers/test/test_contentWorker.html
@@ -0,0 +1,48 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for DOM Worker privileged properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" language="javascript">
+
+ var workerFilename = "content_worker.js";
+ var worker = new Worker(workerFilename);
+
+ var props = {
+ 'ctypes': 1,
+ 'OS': 1
+ };
+
+ worker.onmessage = function(event) {
+ if (event.data.testfinished) {
+ SimpleTest.finish();
+ return;
+ }
+ var prop = event.data.prop;
+ ok(prop in props, "checking " + prop);
+ is(event.data.value, undefined, prop + " should be undefined");
+ };
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_csp.html b/dom/workers/test/test_csp.html
new file mode 100644
index 0000000000..f3ef747372
--- /dev/null
+++ b/dom/workers/test/test_csp.html
@@ -0,0 +1,18 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for DOM Worker + CSP</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+<script type="text/javascript" src="test_csp.js"></script>
+</html>
diff --git a/dom/workers/test/test_csp.html^headers^ b/dom/workers/test/test_csp.html^headers^
new file mode 100644
index 0000000000..1c93210799
--- /dev/null
+++ b/dom/workers/test/test_csp.html^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-cache
+Content-Security-Policy: default-src 'self' blob:
diff --git a/dom/workers/test/test_csp.js b/dom/workers/test/test_csp.js
new file mode 100644
index 0000000000..8c2b53586a
--- /dev/null
+++ b/dom/workers/test/test_csp.js
@@ -0,0 +1,54 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+var tests = 3;
+
+SimpleTest.waitForExplicitFinish();
+
+testDone = function (event) {
+ if (!--tests) {
+ SimpleTest.finish();
+ }
+};
+
+// Workers don't inherit CSP
+worker = new Worker("csp_worker.js");
+worker.postMessage({ do: "eval" });
+worker.onmessage = function (event) {
+ is(event.data, 42, "Eval succeeded!");
+ testDone();
+};
+
+// blob: workers *do* inherit CSP
+xhr = new XMLHttpRequest();
+xhr.open("GET", "csp_worker.js");
+xhr.responseType = "blob";
+xhr.send();
+xhr.onload = e => {
+ uri = URL.createObjectURL(e.target.response);
+ worker = new Worker(uri);
+ worker.postMessage({ do: "eval" });
+ worker.onmessage = function (event) {
+ is(event.data, "EvalError: call to eval() blocked by CSP", "Eval threw");
+ testDone();
+ };
+};
+
+xhr = new XMLHttpRequest();
+xhr.open("GET", "csp_worker.js");
+xhr.responseType = "blob";
+xhr.send();
+xhr.onload = e => {
+ uri = URL.createObjectURL(e.target.response);
+ worker = new Worker(uri);
+ worker.postMessage({ do: "nest", uri, level: 3 });
+ worker.onmessage = function (event) {
+ is(
+ event.data,
+ "EvalError: call to eval() blocked by CSP",
+ "Eval threw in nested worker"
+ );
+ testDone();
+ };
+};
diff --git a/dom/workers/test/test_dataURLWorker.html b/dom/workers/test/test_dataURLWorker.html
new file mode 100644
index 0000000000..145b0e43f1
--- /dev/null
+++ b/dom/workers/test/test_dataURLWorker.html
@@ -0,0 +1,30 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <script src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <script type="text/javascript">
+ const message = "hi";
+ const url = "DATA:text/plain," +
+ "onmessage = function(event) {" +
+ " postMessage(event.data);" +
+ "};";
+
+ var worker = new Worker(url);
+ worker.onmessage = function(event) {
+ is(event.data, message, "Got correct message");
+ SimpleTest.finish();
+ };
+ worker.postMessage(message);
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/test_dynamicImport.html b/dom/workers/test/test_dynamicImport.html
new file mode 100644
index 0000000000..bffef87d2d
--- /dev/null
+++ b/dom/workers/test/test_dynamicImport.html
@@ -0,0 +1,76 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of Worker Dynamic Import (Bug 1540913)
+Ensure that the script loader doesn't accidentally reorder events due to async work
+done by dynamic import
+-->
+<head>
+ <title>Test for Worker Dynamic Import (Bug 1540913)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="onLoad()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1540913">Worker Dynamic Import
+ Bug 1540913</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+async function onLoad() {
+ await SpecialPowers.pushPrefEnv(
+ { set: [["dom.workers.modules.enabled", true ]] });
+
+ const workers = [
+ new Worker("dynamicImport_worker.js", {type: "classic"}),
+ new Worker("dynamicImport_worker.js", {type: "module"})
+ ];
+
+ let successCount = 0;
+
+ for (const worker of workers) {
+ const events = [];
+ worker.onmessage = function(event) {
+ switch (event.data) {
+ case "first":
+ ok(events.length === 1 && events[0] === "second",
+ "first dynamic import returned");
+ events.push(event.data);
+ successCount++;
+ // Cheap way to make sure we only finish successfully after
+ // both the module and classic test is finished.
+ if (successCount == 2) {
+ SimpleTest.finish();
+ }
+ break;
+ case "second":
+ ok(events.length === 0,
+ "second dynamic import returned");
+ events.push(event.data);
+ break;
+ default:
+ ok(false, "Unexpected message:" + event.data);
+ SimpleTest.finish();
+ }
+ };
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error:" + event.message);
+ SimpleTest.finish();
+ }
+
+ worker.postMessage("start");
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_dynamicImport_and_terminate.html b/dom/workers/test/test_dynamicImport_and_terminate.html
new file mode 100644
index 0000000000..cf355572e6
--- /dev/null
+++ b/dom/workers/test/test_dynamicImport_and_terminate.html
@@ -0,0 +1,34 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Worker create script loader failure</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+document.addEventListener("DOMContentLoaded", () => {
+ const worker = new Worker("worker_dynamicImport.mjs", {"type": "module"});
+ setTimeout(() => {
+ worker.terminate();
+ ok(true, "done");
+ SimpleTest.finish();
+ }, 0);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_dynamicImport_early_termination.html b/dom/workers/test/test_dynamicImport_early_termination.html
new file mode 100644
index 0000000000..fb9096df14
--- /dev/null
+++ b/dom/workers/test/test_dynamicImport_early_termination.html
@@ -0,0 +1,79 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of Worker Dynamic Import (Bug 1540913)
+Ensure that the script loader doesn't fail if requests are terminated early.
+-->
+<head>
+ <title>Test for Worker Dynamic Import (Bug 1540913)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="onLoad()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1540913">Worker Dynamic Import
+ Bug 1540913</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+async function onLoad() {
+ await SpecialPowers.pushPrefEnv(
+ { set: [["dom.workers.modules.enabled", true ]] });
+
+ const workers = [
+ new Worker("dynamicImport_worker.js", {type: "classic"}),
+ new Worker("dynamicImport_worker.js", {type: "module"})
+ ]
+
+ let successCount = 0;
+
+ // In the implementation of dynamic import, every dynamic import has
+ // it's own ScriptLoader. To ensure that this is working correctly,
+ // this tests that if we re-order the dynamic import order,
+ // worker termination works as expected.
+ for (const worker of workers) {
+ const events = [];
+ worker.onmessage = function(event) {
+ switch (event.data) {
+ case "first":
+ ok(false, "first dynamic import returned");
+ SimpleTest.finish();
+ break;
+ case "second":
+ ok(events.length === 0,
+ "second dynamic import returned");
+ events.push(event.data);
+ worker.terminate()
+ successCount++;
+ // Cheap way to make sure we only finish successfully after
+ // both the module and classic test is finished.
+ if (successCount == 2) {
+ SimpleTest.finish();
+ }
+ break;
+ default:
+ ok(false, "Unexpected message:" + event.data);
+ SimpleTest.finish();
+ }
+ };
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error:" + event.message);
+ SimpleTest.finish();
+ }
+
+ worker.postMessage("start");
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_errorPropagation.html b/dom/workers/test/test_errorPropagation.html
new file mode 100644
index 0000000000..5d6afabe05
--- /dev/null
+++ b/dom/workers/test/test_errorPropagation.html
@@ -0,0 +1,65 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <iframe id="workerFrame" src="errorPropagation_iframe.html"
+ onload="workerFrameLoaded();"></iframe>
+ <script type="text/javascript">
+ const workerCount = 3;
+
+ const errorMessage = "Error: expectedError";
+ const errorFilename = "http://mochi.test:8888/tests/dom/workers/test/" +
+ "errorPropagation_worker.js";
+ const errorLineno = 48;
+
+ var workerFrame;
+
+ scopeErrorCount = 0;
+ workerErrorCount = 0;
+ windowErrorCount = 0;
+
+ function messageListener(event) {
+ if (event.type == "scope") {
+ scopeErrorCount++;
+ }
+ else if (event.type == "worker") {
+ workerErrorCount++;
+ }
+ else if (event.type == "window") {
+ windowErrorCount++;
+ }
+ else {
+ ok(false, "Bad event type: " + event.type);
+ }
+
+ is(event.data.message, errorMessage, "Correct message event.message");
+ is(event.data.filename, errorFilename,
+ "Correct message event.filename");
+ is(event.data.lineno, errorLineno, "Correct message event.lineno");
+
+ if (windowErrorCount == 1) {
+ is(scopeErrorCount, workerCount, "Good number of scope errors");
+ is(workerErrorCount, workerCount, "Good number of worker errors");
+ workerFrame.stop();
+ SimpleTest.finish();
+ }
+ }
+
+ function workerFrameLoaded() {
+ workerFrame = document.getElementById("workerFrame").contentWindow;
+ workerFrame.start(workerCount, messageListener);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/test_errorwarning.html b/dom/workers/test/test_errorwarning.html
new file mode 100644
index 0000000000..c65363200d
--- /dev/null
+++ b/dom/workers/test/test_errorwarning.html
@@ -0,0 +1,95 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Test javascript.options.strict in Workers
+-->
+<head>
+ <title>Test javascript.options.strict in Workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" language="javascript">
+
+ var errors = 0;
+ function errorHandler(e) {
+ ok(true, "An error has been received!");
+ errors++;
+ }
+
+ function test_noErrors() {
+ errors = 0;
+
+ var worker = new Worker('errorwarning_worker.js');
+ worker.onerror = errorHandler;
+ worker.onmessage = function(e) {
+ if (e.data.type == 'ignore')
+ return;
+
+ if (e.data.type == 'error') {
+ errorHandler();
+ return;
+ }
+
+ if (e.data.type == 'finish') {
+ ok(errors == 0, "Here we are with 0 errors!");
+ runTests();
+ return;
+ }
+ }
+
+ onerror = errorHandler;
+ worker.postMessage({ loop: 5, errors: false });
+ }
+
+ function test_errors() {
+ errors = 0;
+
+ var worker = new Worker('errorwarning_worker.js');
+ worker.onerror = errorHandler;
+ worker.onmessage = function(e) {
+ if (e.data.type == 'ignore')
+ return;
+
+ if (e.data.type == 'error') {
+ errorHandler();
+ return;
+ }
+
+ if (e.data.type == 'finish') {
+ ok(errors != 0, "Here we are with errors!");
+ runTests();
+ return;
+ }
+ }
+
+ onerror = errorHandler;
+ worker.postMessage({ loop: 5, errors: true });
+ }
+
+ var tests = [ test_noErrors, test_errors ];
+ function runTests() {
+ var test = tests.shift();
+ if (test) {
+ test();
+ } else {
+ SimpleTest.finish();
+ }
+ }
+
+ runTests();
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_eventDispatch.html b/dom/workers/test/test_eventDispatch.html
new file mode 100644
index 0000000000..e6bbd7e2d1
--- /dev/null
+++ b/dom/workers/test/test_eventDispatch.html
@@ -0,0 +1,32 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <script src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <script type="text/javascript">
+ const message = "Hi";
+
+ var messageCount = 0;
+
+ var worker = new Worker("eventDispatch_worker.js");
+ worker.onmessage = function(event) {
+ is(event.data, message, "Got correct data.");
+ if (!messageCount++) {
+ event.target.postMessage(event.data);
+ return;
+ }
+ SimpleTest.finish();
+ }
+ worker.postMessage(message);
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/test_fibonacci.html b/dom/workers/test/test_fibonacci.html
new file mode 100644
index 0000000000..c3e3e98574
--- /dev/null
+++ b/dom/workers/test/test_fibonacci.html
@@ -0,0 +1,51 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads with Fibonacci
+-->
+<head>
+ <title>Test for DOM Worker Threads with Fibonacci</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450452">DOM Worker Threads Fibonacci</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ const seqNum = 5;
+
+ function recursivefib(n) {
+ return n < 2 ? n : recursivefib(n - 1) + recursivefib(n - 2);
+ }
+
+ var worker = new Worker("fibonacci_worker.js");
+
+ worker.onmessage = function(event) {
+ is(event.target, worker);
+ is(event.data, recursivefib(seqNum));
+ SimpleTest.finish();
+ };
+
+ worker.onerror = function(event) {
+ is(event.target, worker);
+ ok(false, "Worker had an error: " + event.message);
+ SimpleTest.finish();
+ };
+
+ worker.postMessage(seqNum);
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_file.xhtml b/dom/workers/test/test_file.xhtml
new file mode 100644
index 0000000000..2b628e7f4d
--- /dev/null
+++ b/dom/workers/test/test_file.xhtml
@@ -0,0 +1,96 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=123456
+-->
+<window title="Mozilla Bug 123456"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=123456"
+ target="_blank">Mozilla Bug 123456</a>
+
+ <div id="content" style="display: none">
+ <input id="fileList" type="file"></input>
+ </div>
+
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 123456 **/
+
+ var fileNum = 0;
+
+ /**
+ * Create a file which contains the given data and optionally adds the specified file extension.
+ */
+ function createFileWithData(fileData, /** optional */ extension) {
+ var testFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ var fileExtension = (extension == undefined) ? "" : "." + extension;
+ testFile.append("workerFile" + fileNum++ + fileExtension);
+
+ var outStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666, 0);
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ var fileList = document.getElementById('fileList');
+ fileList.value = testFile.path;
+
+ return fileList.files[0];
+ }
+
+ /**
+ * Create a worker to access file properties.
+ */
+ function accessFileProperties(file, expectedSize, expectedType) {
+ waitForWorkerFinish();
+
+ var worker = new Worker("file_worker.js");
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ worker.onmessage = function(event) {
+ is(event.data.size, expectedSize, "size proproperty accessed from worker is not the same as on main thread.");
+ is(event.data.type, expectedType, "type proproperty accessed from worker is incorrect.");
+ is(event.data.name, file.name, "name proproperty accessed from worker is incorrect.");
+ is(event.data.lastModified, file.lastModified, "lastModified proproperty accessed from worker is incorrect.");
+ finish();
+ };
+
+ worker.postMessage(file);
+ }
+
+ // Empty file.
+ accessFileProperties(createFileWithData(""), 0, "");
+
+ // Typical use case.
+ accessFileProperties(createFileWithData("Hello"), 5, "");
+
+ // Longish file.
+ var text = "";
+ for (var i = 0; i < 10000; ++i) {
+ text += "long";
+ }
+ accessFileProperties(createFileWithData(text), 40000, "");
+
+ // Type detection based on extension.
+ accessFileProperties(createFileWithData("text", "txt"), 4, "text/plain");
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/workers/test/test_fileBlobPosting.xhtml b/dom/workers/test/test_fileBlobPosting.xhtml
new file mode 100644
index 0000000000..61f4a8e909
--- /dev/null
+++ b/dom/workers/test/test_fileBlobPosting.xhtml
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=664783
+-->
+<window title="Mozilla Bug 664783"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783"
+ target="_blank">Mozilla Bug 664783</a>
+
+ <div id="content" style="display: none">
+ <input id="fileList" type="file"></input>
+ </div>
+
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 664783 **/
+
+ var fileNum = 0;
+
+ /**
+ * Create a file which contains the given data.
+ */
+ function createFileWithData(fileData) {
+ var testFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ testFile.append("workerBlobPosting" + fileNum++);
+
+ var outStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666, 0);
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ var fileList = document.getElementById('fileList');
+ fileList.value = testFile.path;
+
+ return fileList.files[0];
+ }
+
+ /**
+ * Create a worker which posts the same blob given. Used to test cloning of blobs.
+ * Checks the size, type, name and path of the file posted from the worker to ensure it
+ * is the same as the original.
+ */
+ function postBlob(file) {
+ var worker = new Worker("filePosting_worker.js");
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ worker.onmessage = function(event) {
+ console.log(event.data);
+ is(event.data.size, file.size, "size of file posted from worker does not match file posted to worker.");
+ finish();
+ };
+
+ var blob = file.slice();
+ worker.postMessage(blob);
+ waitForWorkerFinish();
+ }
+
+ // Empty file.
+ postBlob(createFileWithData(""));
+
+ // Typical use case.
+ postBlob(createFileWithData("Hello"));
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/workers/test/test_fileBlobSubWorker.xhtml b/dom/workers/test/test_fileBlobSubWorker.xhtml
new file mode 100644
index 0000000000..8b67552788
--- /dev/null
+++ b/dom/workers/test/test_fileBlobSubWorker.xhtml
@@ -0,0 +1,97 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=664783
+-->
+<window title="Mozilla Bug 664783"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783"
+ target="_blank">Mozilla Bug 664783</a>
+
+ <div id="content" style="display: none">
+ <input id="fileList" type="file"></input>
+ </div>
+
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 664783 **/
+
+ var fileNum = 0;
+
+ /**
+ * Create a file which contains the given data and optionally adds the specified file extension.
+ */
+ function createFileWithData(fileData, /** optional */ extension) {
+ var testFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ var fileExtension = (extension == undefined) ? "" : "." + extension;
+ testFile.append("workerBlobSubWorker" + fileNum++ + fileExtension);
+
+ var outStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666, 0);
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ var fileList = document.getElementById('fileList');
+ fileList.value = testFile.path;
+
+ return fileList.files[0];
+ }
+
+ /**
+ * Create a worker to access blob properties.
+ */
+ function accessFileProperties(file, expectedSize) {
+ var worker = new Worker("fileBlobSubWorker_worker.js");
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ worker.onmessage = function(event) {
+ if (event.data == undefined) {
+ ok(false, "Worker had an error.");
+ } else {
+ is(event.data.size, expectedSize, "size proproperty accessed from worker is not the same as on main thread.");
+ }
+ finish();
+ };
+
+ var blob = file.slice();
+ worker.postMessage(blob);
+ waitForWorkerFinish();
+ }
+
+ // Empty file.
+ accessFileProperties(createFileWithData(""), 0);
+
+ // Typical use case.
+ accessFileProperties(createFileWithData("Hello"), 5);
+
+ // Longish file.
+ var text = "";
+ for (var i = 0; i < 10000; ++i) {
+ text += "long";
+ }
+ accessFileProperties(createFileWithData(text), 40000);
+
+ // Type detection based on extension.
+ accessFileProperties(createFileWithData("text", "txt"), 4);
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/workers/test/test_filePosting.xhtml b/dom/workers/test/test_filePosting.xhtml
new file mode 100644
index 0000000000..3ffa219516
--- /dev/null
+++ b/dom/workers/test/test_filePosting.xhtml
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=664783
+-->
+<window title="Mozilla Bug 664783"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783"
+ target="_blank">Mozilla Bug 664783</a>
+
+ <div id="content" style="display: none">
+ <input id="fileList" type="file"></input>
+ </div>
+
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 664783 **/
+
+ var fileNum = 0;
+
+ /**
+ * Create a file which contains the given data.
+ */
+ function createFileWithData(fileData) {
+ var testFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ testFile.append("workerFilePosting" + fileNum++);
+
+ var outStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666, 0);
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ var fileList = document.getElementById('fileList');
+ fileList.value = testFile.path;
+
+ return fileList.files[0];
+ }
+
+ /**
+ * Create a worker which posts the same file given. Used to test cloning of files.
+ * Checks the size, type, name and path of the file posted from the worker to ensure it
+ * is the same as the original.
+ */
+ function postFile(file) {
+ var worker = new Worker("file_worker.js");
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ worker.onmessage = function(event) {
+ is(event.data.size, file.size, "size of file posted from worker does not match file posted to worker.");
+ is(event.data.type, file.type, "type of file posted from worker does not match file posted to worker.");
+ is(event.data.name, file.name, "name of file posted from worker does not match file posted to worker.");
+ finish();
+ };
+
+ worker.postMessage(file);
+ waitForWorkerFinish();
+ }
+
+ // Empty file.
+ postFile(createFileWithData(""));
+
+ // Typical use case.
+ postFile(createFileWithData("Hello"));
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/workers/test/test_fileReadSlice.xhtml b/dom/workers/test/test_fileReadSlice.xhtml
new file mode 100644
index 0000000000..b035fa2872
--- /dev/null
+++ b/dom/workers/test/test_fileReadSlice.xhtml
@@ -0,0 +1,93 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=664783
+-->
+<window title="Mozilla Bug 664783"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783"
+ target="_blank">Mozilla Bug 664783</a>
+
+ <div id="content" style="display: none">
+ <input id="fileList" type="file"></input>
+ </div>
+
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ if (navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 1);
+ }
+
+ /** Test for Bug 664783 **/
+
+ var fileNum = 0;
+
+ /**
+ * Create a file which contains the given data.
+ */
+ function createFileWithData(fileData) {
+ var testFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ testFile.append("workerReadSlice" + fileNum++);
+
+ var outStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666, 0);
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ var fileList = document.getElementById('fileList');
+ fileList.value = testFile.path;
+
+ return fileList.files[0];
+ }
+
+ /**
+ * Creates a worker which slices a blob to the given start and end offset and
+ * reads the content as text.
+ */
+ function readSlice(blob, start, end, expectedText) {
+ var worker = new Worker("fileReadSlice_worker.js");
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ worker.onmessage = function(event) {
+ is(event.data, expectedText, "Text from sliced blob in worker is incorrect.");
+ finish();
+ };
+
+ var params = {blob: blob, start: start, end: end};
+ worker.postMessage(params);
+ waitForWorkerFinish();
+ }
+
+ // Empty file.
+ readSlice(createFileWithData(""), 0, 0, "");
+
+ // Typical use case.
+ readSlice(createFileWithData("HelloBye"), 5, 8, "Bye");
+
+ // End offset too large.
+ readSlice(createFileWithData("HelloBye"), 5, 9, "Bye");
+
+ // Start of file.
+ readSlice(createFileWithData("HelloBye"), 0, 5, "Hello");
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/workers/test/test_fileReaderSync.xhtml b/dom/workers/test/test_fileReaderSync.xhtml
new file mode 100644
index 0000000000..7cdcaed791
--- /dev/null
+++ b/dom/workers/test/test_fileReaderSync.xhtml
@@ -0,0 +1,198 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=664783
+-->
+<window title="Mozilla Bug 664783"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783"
+ target="_blank">Mozilla Bug 664783</a>
+
+ <div id="content" style="display: none">
+ <input id="fileList" type="file"></input>
+ </div>
+
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 664783 **/
+
+ var fileNum = 0;
+
+ /**
+ * Create a file which contains the given data and optionally adds the specified file extension.
+ */
+ function createFileWithData(fileData, /** optional */ extension) {
+ var testFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ var fileExtension = (extension == undefined) ? "" : "." + extension;
+ testFile.append("workerFileReaderSync" + fileNum++ + fileExtension);
+
+ var outStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666, 0);
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ var fileList = document.getElementById('fileList');
+ fileList.value = testFile.path;
+
+ return fileList.files[0];
+ }
+
+ function convertToUTF16(s) {
+ res = "";
+ for (var i = 0; i < s.length; ++i) {
+ c = s.charCodeAt(i);
+ res += String.fromCharCode(c & 255, c >>> 8);
+ }
+ return res;
+ }
+
+ /**
+ * Converts the given string to a data URL of the specified mime type.
+ */
+ function convertToDataURL(mime, s) {
+ return "data:" + mime + ";base64," + btoa(s);
+ }
+
+ /**
+ * Create a worker to read a file containing fileData using FileReaderSync and
+ * checks the return type against the expected type. Optionally set an encoding
+ * for reading the file as text.
+ */
+ function readFileData(fileData, expectedText, /** optional */ encoding) {
+ var worker = new Worker("fileReaderSync_worker.js");
+
+ worker.onmessage = function(event) {
+ is(event.data.text, expectedText, "readAsText in worker returned incorrect result.");
+ is(event.data.bin, fileData, "readAsBinaryString in worker returned incorrect result.");
+ is(event.data.url, convertToDataURL("application/octet-stream", fileData), "readAsDataURL in worker returned incorrect result.");
+ is(event.data.arrayBuffer.byteLength, fileData.length, "readAsArrayBuffer returned buffer of incorrect length.");
+ finish();
+ };
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ var params = {file: createFileWithData(fileData), encoding: encoding};
+
+ worker.postMessage(params);
+
+ waitForWorkerFinish();
+ }
+
+ /**
+ * Create a worker which reuses a FileReaderSync to read multiple files as DataURLs.
+ */
+ function reuseReaderForURL(files, expected) {
+ var worker = new Worker("fileReaderSync_worker.js");
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ var k = 0;
+ worker.onmessage = function(event) {
+ is(event.data.url, expected[k], "readAsDataURL in worker returned incorrect result when reusing FileReaderSync.");
+ k++;
+ finish();
+ };
+
+ for (var i = 0; i < files.length; ++i) {
+ var params = {file: files[i], encoding: undefined};
+ worker.postMessage(params);
+ waitForWorkerFinish();
+ }
+ }
+
+ /**
+ * Create a worker which reuses a FileReaderSync to read multiple files as text.
+ */
+ function reuseReaderForText(fileData, expected) {
+ var worker = new Worker("fileReaderSync_worker.js");
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ var k = 0;
+ worker.onmessage = function(event) {
+ is(event.data.text, expected[k++], "readAsText in worker returned incorrect result when reusing FileReaderSync.");
+ finish();
+ };
+
+ for (var i = 0; i < fileData.length; ++i) {
+ var params = {file: createFileWithData(fileData[i]), encoding: undefined};
+ worker.postMessage(params);
+ waitForWorkerFinish();
+ }
+ }
+
+
+ /**
+ * Creates a a worker which reads a file containing fileData as an ArrayBuffer.
+ * Verifies that the ArrayBuffer when interpreted as a string matches the original data.
+ */
+ function readArrayBuffer(fileData) {
+ var worker = new Worker("fileReaderSync_worker.js");
+
+ worker.onmessage = function(event) {
+ var view = new Uint8Array(event.data.arrayBuffer);
+ is(event.data.arrayBuffer.byteLength, fileData.length, "readAsArrayBuffer returned buffer of incorrect length.");
+ is(String.fromCharCode.apply(String, view), fileData, "readAsArrayBuffer returned buffer containing incorrect data.");
+ finish();
+ };
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ var params = {file: createFileWithData(fileData), encoding: undefined};
+
+ worker.postMessage(params);
+
+ waitForWorkerFinish();
+ }
+
+ // Empty file.
+ readFileData("", "");
+
+ // Typical use case.
+ readFileData("text", "text");
+
+ // Test reading UTF-16 characters.
+ readFileData(convertToUTF16("text"), "text", "UTF-16");
+
+ // First read a file of type "text/plain", then read a file of type "application/octet-stream".
+ reuseReaderForURL([createFileWithData("text", "txt"), createFileWithData("text")],
+ [convertToDataURL("text/plain", "text"),
+ convertToDataURL("application/octet-stream", "text")]);
+
+ // First read UTF-16 characters marked using BOM, then read UTF-8 characters.
+ reuseReaderForText([convertToUTF16("\ufefftext"), "text"],
+ ["text", "text"]);
+
+ // Reading data as ArrayBuffer.
+ readArrayBuffer("");
+ readArrayBuffer("text");
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/workers/test/test_fileReaderSyncErrors.xhtml b/dom/workers/test/test_fileReaderSyncErrors.xhtml
new file mode 100644
index 0000000000..626b67e1ba
--- /dev/null
+++ b/dom/workers/test/test_fileReaderSyncErrors.xhtml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=664783
+-->
+<window title="Mozilla Bug 664783"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783"
+ target="_blank">Mozilla Bug 664783</a>
+
+ <div id="content" style="display: none">
+ <input id="fileList" type="file"></input>
+ </div>
+
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 664783 **/
+
+ var fileNum = 0;
+
+ /**
+ * Create a file which contains the given data.
+ */
+ function createFileWithData(fileData) {
+ var testFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ testFile.append("workerFileReaderSyncErrors" + fileNum++);
+
+ var outStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666, 0);
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ var fileList = document.getElementById('fileList');
+ fileList.value = testFile.path;
+
+ return fileList.files[0];
+ }
+
+ /**
+ * Creates a worker which runs errors cases.
+ */
+ function runWorkerErrors(file) {
+ var worker = new Worker("fileReaderSyncErrors_worker.js");
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ worker.onmessage = function(event) {
+ if(event.data == undefined) {
+ // Worker returns undefined when tests have finished running.
+ finish();
+ } else {
+ // Otherwise worker will return results of tests to be evaluated.
+ is(event.data.actual, event.data.expected, event.data.message);
+ }
+ };
+
+ worker.postMessage(file);
+ waitForWorkerFinish();
+ }
+
+ // Run worker which creates exceptions.
+ runWorkerErrors(createFileWithData("text"));
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/workers/test/test_fileReaderSync_when_closing.html b/dom/workers/test/test_fileReaderSync_when_closing.html
new file mode 100644
index 0000000000..d5fadbaa65
--- /dev/null
+++ b/dom/workers/test/test_fileReaderSync_when_closing.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for FileReaderSync when the worker is closing</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script type="application/javascript">
+
+// In order to exercise FileReaderSync::SyncRead's syncLoop-using AsyncWait()
+// path, we need to provide a stream that will both 1) not have all the data
+// immediately available (eliminating memory-backed Blobs) and 2) return
+// NS_BASE_STREAM_WOULD_BLOCK. Under e10s, any Blob/File sourced from the
+// parent process (as loadChromeScript performs) will be backed by an
+// RemoteLazyInputStream and will behave this way on first use (when it is in
+// the eInit state). For ease of testing, we reuse script_createFile.js which
+// involves a file on disk, but a memory-backed Blob from the parent process
+// would be equally fine. Under non-e10s, this File will not do the right
+// thing because a synchronous nsFileInputStream will be made directly
+// available and the AsyncWait path won't be taken, but the test will still
+// pass.
+
+var url = SimpleTest.getTestFileURL("script_createFile.js");
+var script = SpecialPowers.loadChromeScript(url);
+
+function onOpened(message) {
+ function workerCode() {
+ onmessage = function(e) {
+ self.close();
+ var fr = new FileReaderSync();
+ self.postMessage(fr.readAsText(e.data));
+ }
+ }
+
+ var b = new Blob([workerCode+'workerCode();']);
+ var w = new Worker(URL.createObjectURL(b));
+ w.onmessage = function(e) {
+ is(e.data, "Hello world!", "The blob content is OK!");
+ SimpleTest.finish();
+ }
+
+ w.postMessage(message.data);
+}
+
+script.addMessageListener("nonEmptyFile.opened", onOpened);
+script.sendAsyncMessage("nonEmptyFile.open");
+
+SimpleTest.waitForExplicitFinish();
+
+ </script>
+</body>
+</html>
diff --git a/dom/workers/test/test_fileSlice.xhtml b/dom/workers/test/test_fileSlice.xhtml
new file mode 100644
index 0000000000..b683db2ec9
--- /dev/null
+++ b/dom/workers/test/test_fileSlice.xhtml
@@ -0,0 +1,105 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=664783
+-->
+<window title="Mozilla Bug 664783"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783"
+ target="_blank">Mozilla Bug 664783</a>
+
+ <div id="content" style="display: none">
+ <input id="fileList" type="file"></input>
+ </div>
+
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 664783 **/
+
+ var fileNum = 0;
+
+ /**
+ * Create a file which contains the given data and optionally adds the specified file extension.
+ */
+ function createFileWithData(fileData, /** optional */ extension) {
+ var testFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ var fileExtension = (extension == undefined) ? "" : "." + extension;
+ testFile.append("workerSlice" + fileNum++ + fileExtension);
+
+ var outStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666, 0);
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ var fileList = document.getElementById('fileList');
+ fileList.value = testFile.path;
+
+ return fileList.files[0];
+ }
+
+ /**
+ * Starts a worker which slices the blob to the given start offset and optional end offset and
+ * content type. It then verifies that the size and type of the sliced blob is correct.
+ */
+ function createSlice(blob, start, expectedLength, /** optional */ end, /** optional */ contentType) {
+ var worker = new Worker("fileSlice_worker.js");
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ worker.onmessage = function(event) {
+ is(event.data.size, expectedLength, "size property of slice is incorrect.");
+ is(event.data.type, contentType ? contentType : blob.type, "type property of slice is incorrect.");
+ finish();
+ };
+
+ var params = {blob: blob, start: start, end: end, contentType: contentType};
+ worker.postMessage(params);
+ waitForWorkerFinish();
+ }
+
+ // Empty file.
+ createSlice(createFileWithData(""), 0, 0, 0);
+
+ // Typical use case.
+ createSlice(createFileWithData("Hello"), 1, 1, 2);
+
+ // Longish file.
+ var text = "";
+ for (var i = 0; i < 10000; ++i) {
+ text += "long";
+ }
+ createSlice(createFileWithData(text), 2000, 2000, 4000);
+
+ // Slice to different type.
+ createSlice(createFileWithData("text", "txt"), 0, 2, 2, "image/png");
+
+ // Length longer than blob.
+ createSlice(createFileWithData("text"), 0, 4, 20);
+
+ // Start longer than blob.
+ createSlice(createFileWithData("text"), 20, 0, 4);
+
+ // No optional arguments
+ createSlice(createFileWithData("text"), 0, 4);
+ createSlice(createFileWithData("text"), 2, 2);
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/workers/test/test_fileSubWorker.xhtml b/dom/workers/test/test_fileSubWorker.xhtml
new file mode 100644
index 0000000000..92aa3b1a17
--- /dev/null
+++ b/dom/workers/test/test_fileSubWorker.xhtml
@@ -0,0 +1,98 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=664783
+-->
+<window title="Mozilla Bug 664783"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783"
+ target="_blank">Mozilla Bug 664783</a>
+
+ <div id="content" style="display: none">
+ <input id="fileList" type="file"></input>
+ </div>
+
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 664783 **/
+
+ var fileNum = 0;
+
+ /**
+ * Create a file which contains the given data and optionally adds the specified file extension.
+ */
+ function createFileWithData(fileData, /** optional */ extension) {
+ var testFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ var fileExtension = (extension == undefined) ? "" : "." + extension;
+ testFile.append("workerSubWorker" + fileNum++ + fileExtension);
+
+ var outStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666, 0);
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ var fileList = document.getElementById('fileList');
+ fileList.value = testFile.path;
+
+ return fileList.files[0];
+ }
+
+ /**
+ * Create a worker to access file properties.
+ */
+ function accessFileProperties(file, expectedSize, expectedType) {
+ var worker = new Worker("fileSubWorker_worker.js");
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ worker.onmessage = function(event) {
+ if (event.data == undefined) {
+ ok(false, "Worker had an error.");
+ } else {
+ is(event.data.size, expectedSize, "size proproperty accessed from worker is not the same as on main thread.");
+ is(event.data.type, expectedType, "type proproperty accessed from worker is incorrect.");
+ is(event.data.name, file.name, "name proproperty accessed from worker is incorrect.");
+ }
+ finish();
+ };
+
+ worker.postMessage(file);
+ waitForWorkerFinish();
+ }
+
+ // Empty file.
+ accessFileProperties(createFileWithData(""), 0, "");
+
+ // Typical use case.
+ accessFileProperties(createFileWithData("Hello"), 5, "");
+
+ // Longish file.
+ var text = "";
+ for (var i = 0; i < 10000; ++i) {
+ text += "long";
+ }
+ accessFileProperties(createFileWithData(text), 40000, "");
+
+ // Type detection based on extension.
+ accessFileProperties(createFileWithData("text", "txt"), 4, "text/plain");
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/workers/test/test_importScripts.html b/dom/workers/test/test_importScripts.html
new file mode 100644
index 0000000000..0e6a3dde5e
--- /dev/null
+++ b/dom/workers/test/test_importScripts.html
@@ -0,0 +1,53 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads (Bug 437152)
+-->
+<head>
+ <title>Test for DOM Worker Threads (Bug 437152)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("importScripts_worker.js");
+
+ worker.onmessage = function(event) {
+ switch (event.data) {
+ case "started":
+ worker.postMessage("stop");
+ break;
+ case "stopped":
+ ok(true, "worker correctly stopped");
+ SimpleTest.finish();
+ break;
+ default:
+ ok(false, "Unexpected message:" + event.data);
+ SimpleTest.finish();
+ }
+ };
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error:" + event.message);
+ SimpleTest.finish();
+ }
+
+ worker.postMessage("start");
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_importScripts_3rdparty.html b/dom/workers/test/test_importScripts_3rdparty.html
new file mode 100644
index 0000000000..2e8953edc6
--- /dev/null
+++ b/dom/workers/test/test_importScripts_3rdparty.html
@@ -0,0 +1,136 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for 3rd party imported script and muted errors</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script type="text/javascript">
+
+const workerURL = 'http://mochi.test:8888/tests/dom/workers/test/importScripts_3rdParty_worker.js';
+
+const sameOriginURL = 'http://mochi.test:8888/tests/dom/workers/test/invalid.js'
+
+var tests = [
+ function() {
+ var worker = new Worker("importScripts_3rdParty_worker.js");
+ worker.onmessage = function(event) {
+ ok("result" in event.data && event.data.result, "It seems we don't share data!");
+ next();
+ };
+
+ worker.postMessage({ url: sameOriginURL, test: 'try', nested: false });
+ },
+
+ function() {
+ var worker = new Worker("importScripts_3rdParty_worker.js");
+ worker.onmessage = function(event) {
+ ok("result" in event.data && event.data.result, "It seems we don't share data in nested workers!");
+ next();
+ };
+
+ worker.postMessage({ url: sameOriginURL, test: 'try', nested: true });
+ },
+
+ function() {
+ var worker = new Worker("importScripts_3rdParty_worker.js");
+ worker.onmessage = function(event) {
+ ok("result" in event.data && event.data.result, "It seems we don't share data via eventListener!");
+ next();
+ };
+
+ worker.postMessage({ url: sameOriginURL, test: 'eventListener', nested: false });
+ },
+
+ function() {
+ var worker = new Worker("importScripts_3rdParty_worker.js");
+ worker.onmessage = function(event) {
+ ok("result" in event.data && event.data.result, "It seems we don't share data in nested workers via eventListener!");
+ next();
+ };
+
+ worker.postMessage({ url: sameOriginURL, test: 'eventListener', nested: true });
+ },
+
+ function() {
+ var worker = new Worker("importScripts_3rdParty_worker.js");
+ worker.onmessage = function(event) {
+ ok("result" in event.data && event.data.result, "It seems we don't share data via onerror!");
+ next();
+ };
+ worker.onerror = function(event) {
+ event.preventDefault();
+ }
+
+ worker.postMessage({ url: sameOriginURL, test: 'onerror', nested: false });
+ },
+
+ function() {
+ var worker = new Worker("importScripts_3rdParty_worker.js");
+ worker.onerror = function(event) {
+ event.preventDefault();
+ ok(event instanceof ErrorEvent, "ErrorEvent received.");
+ is(event.filename, workerURL, "ErrorEvent.filename is correct");
+ next();
+ };
+
+ worker.postMessage({ url: sameOriginURL, test: 'none', nested: false });
+ },
+
+ function() {
+ var worker = new Worker("importScripts_3rdParty_worker.js");
+ worker.addEventListener("error", function(event) {
+ event.preventDefault();
+ ok(event instanceof ErrorEvent, "ErrorEvent received.");
+ is(event.filename, workerURL, "ErrorEvent.filename is correct");
+ next();
+ });
+
+ worker.postMessage({ url: sameOriginURL, test: 'none', nested: false });
+ },
+
+ function() {
+ var worker = new Worker("importScripts_3rdParty_worker.js");
+ worker.onerror = function(event) {
+ ok(false, "No error should be received!");
+ };
+
+ worker.onmessage = function(event) {
+ ok("error" in event.data && event.data.error, "The error has been fully received from a nested worker");
+ next();
+ };
+ worker.postMessage({ url: sameOriginURL, test: 'none', nested: true });
+ },
+
+ function() {
+ var url = URL.createObjectURL(new Blob(["%&%^&%^"]));
+ var worker = new Worker(url);
+ worker.onerror = function(event) {
+ event.preventDefault();
+ ok(event instanceof ErrorEvent, "ErrorEvent received.");
+ next();
+ };
+ }
+];
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+next();
+
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/test_importScripts_mixedcontent.html b/dom/workers/test/test_importScripts_mixedcontent.html
new file mode 100644
index 0000000000..04281deaea
--- /dev/null
+++ b/dom/workers/test/test_importScripts_mixedcontent.html
@@ -0,0 +1,50 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1198078 - test that we respect mixed content blocking in importScript() inside workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1198078">DOM Worker Threads Bug 1198078</a>
+<iframe></iframe>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ onmessage = function(event) {
+ switch (event.data.status) {
+ case "done":
+ SimpleTest.finish();
+ break;
+ case "ok":
+ ok(event.data.data, event.data.msg);
+ break;
+ default:
+ ok(false, "Unexpected message:" + event.data);
+ SimpleTest.finish();
+ }
+ };
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.workers.sharedWorkers.enabled", true],
+ ["security.mixed_content.block_active_content", false],
+ ]}, function() {
+ var iframe = document.querySelector("iframe");
+ iframe.src = "https://example.com/tests/dom/workers/test/importScripts_mixedcontent.html";
+ });
+ };
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_instanceof.html b/dom/workers/test/test_instanceof.html
new file mode 100644
index 0000000000..3a66674877
--- /dev/null
+++ b/dom/workers/test/test_instanceof.html
@@ -0,0 +1,40 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker JSON messages
+-->
+<head>
+ <title>Test for DOM Worker Navigator</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script src="json_worker.js" language="javascript"></script>
+<script class="testbody" language="javascript">
+
+ var worker = new Worker("instanceof_worker.js");
+
+ worker.onmessage = function(event) {
+ ok(event.data.status, event.data.event);
+
+ if (event.data.last)
+ SimpleTest.finish();
+ };
+
+ worker.postMessage(42);
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_json.html b/dom/workers/test/test_json.html
new file mode 100644
index 0000000000..26e951aa63
--- /dev/null
+++ b/dom/workers/test/test_json.html
@@ -0,0 +1,89 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker JSON messages
+-->
+<head>
+ <title>Test for DOM Worker Navigator</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script src="json_worker.js" language="javascript"></script>
+<script class="testbody" language="javascript">
+
+ ok(messages.length, "No messages to test!");
+
+ var worker = new Worker("json_worker.js");
+
+ var index = 0;
+ worker.onmessage = function(event) {
+ var key = messages[index++];
+
+ // Loop for the ones we shouldn't receive.
+ while (key.exception) {
+ key = messages[index++];
+ }
+
+ is(typeof event.data, key.type, "Bad type! " + messages.indexOf(key));
+
+ if (key.array) {
+ is(event.data instanceof Array, key.array,
+ "Array mismatch! " + messages.indexOf(key));
+ }
+
+ if (key.isNaN) {
+ ok(isNaN(event.data), "Should be NaN!" + messages.indexOf(key));
+ }
+
+ if (key.isInfinity) {
+ is(event.data, Infinity, "Should be Infinity!" + messages.indexOf(key));
+ }
+
+ if (key.isNegativeInfinity) {
+ is(event.data, -Infinity, "Should be -Infinity!" + messages.indexOf(key));
+ }
+
+ if (key.shouldCompare || key.shouldEqual) {
+ ok(event.data == key.compareValue,
+ "Values don't compare! " + messages.indexOf(key));
+ }
+
+ if (key.shouldEqual) {
+ ok(event.data === key.compareValue,
+ "Values don't equal! " + messages.indexOf(key));
+ }
+
+ if (key.jsonValue) {
+ is(JSON.stringify(event.data), key.jsonValue,
+ "Object stringification inconsistent!" + messages.indexOf(key));
+ }
+
+ if (event.data == "testFinished") {
+ is(index, messages.length, "Didn't see the right number of messages!");
+ SimpleTest.finish();
+ }
+ };
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ SimpleTest.finish();
+ }
+
+ worker.postMessage("start");
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_loadEncoding.html b/dom/workers/test/test_loadEncoding.html
new file mode 100644
index 0000000000..e7afb2fc82
--- /dev/null
+++ b/dom/workers/test/test_loadEncoding.html
@@ -0,0 +1,50 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 484305 - Load workers as UTF-8</title>
+ <meta http-equiv="content-type" content="text/html; charset=KOI8-R">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=484305">Bug 484305 - Load workers as UTF-8</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var canonical = String.fromCharCode(0x41F, 0x440, 0x438, 0x432, 0x435, 0x442);
+ok(document.inputEncoding === "KOI8-R", "Document encoding is KOI8-R");
+
+// Worker sends two strings, one with `canonical` encoded in KOI8-R and one as UTF-8.
+// Since Worker scripts should always be decoded using UTF-8, even if the owning document's charset is different, the UTF-8 decode should match, while KOI8-R should fail.
+var counter = 0;
+var worker = new Worker("loadEncoding_worker.js");
+worker.onmessage = function(e) {
+ if (e.data.encoding === "KOI8-R") {
+ ok(e.data.text !== canonical, "KOI8-R decoded text should not match");
+ } else if (e.data.encoding === "UTF-8") {
+ ok(e.data.text === canonical, "UTF-8 decoded text should match");
+ }
+ counter++;
+ if (counter === 2)
+ SimpleTest.finish();
+}
+
+worker.onerror = function(e) {
+ ok(false, "Worker error");
+ SimpleTest.finish();
+}
+</script>
+
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_loadError.html b/dom/workers/test/test_loadError.html
new file mode 100644
index 0000000000..dec29d00c4
--- /dev/null
+++ b/dom/workers/test/test_loadError.html
@@ -0,0 +1,67 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Test for DOM Worker Threads</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+"use strict";
+
+function nextTest() {
+ (function(){
+ function workerfunc() {
+ var subworker = new Worker("about:blank");
+ subworker.onerror = function(e) {
+ e.preventDefault();
+ postMessage("ERROR");
+ }
+ }
+ var b = new Blob([workerfunc+'workerfunc();']);
+ var u = URL.createObjectURL(b);
+ function callworker(i) {
+ var w = new Worker(u);
+ URL.revokeObjectURL(u);
+ w.onmessage = function(e) {
+ is(i, 0, 'Message received');
+ is(e.data, "ERROR",
+ "Should catch the error when loading inner script");
+ if (++i < 2) callworker(i);
+ else SimpleTest.finish();
+ };
+ w.onerror = function(e) {
+ is(i, 1, 'OnError received');
+ SimpleTest.finish();
+ }
+ }
+ callworker(0);
+ })();
+}
+
+try {
+ var worker = new Worker("about:blank");
+ worker.onerror = function(e) {
+ e.preventDefault();
+ nextTest();
+ }
+
+ worker.onmessage = function(event) {
+ ok(false, "Shouldn't get a message!");
+ SimpleTest.finish();
+ }
+} catch (e) {
+ ok(false, "This should not happen.");
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_location.html b/dom/workers/test/test_location.html
new file mode 100644
index 0000000000..55c94ae1cf
--- /dev/null
+++ b/dom/workers/test/test_location.html
@@ -0,0 +1,72 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Location
+-->
+<head>
+ <title>Test for DOM Worker Location</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" language="javascript">
+
+ var thisFilename = "test_location.html";
+ var workerFilename = "location_worker.js";
+
+ var href = window.location.href
+ var queryPos = href.lastIndexOf(window.location.search);
+ var baseHref = href.substr(0, href.substr(0, queryPos).lastIndexOf("/") + 1);
+
+ var path = window.location.pathname;
+ var basePath = path.substr(0, path.lastIndexOf("/") + 1);
+
+ var strings = {
+ "toString": baseHref + workerFilename,
+ "href": baseHref + workerFilename,
+ "protocol": window.location.protocol,
+ "host": window.location.host,
+ "hostname": window.location.hostname,
+ "port": window.location.port,
+ "pathname": basePath + workerFilename,
+ "search": "",
+ "hash": "",
+ "origin": "http://mochi.test:8888"
+ };
+
+ var lastSlash = href.substr(0, queryPos).lastIndexOf("/") + 1;
+ is(thisFilename,
+ href.substr(lastSlash, queryPos - lastSlash),
+ "Correct filename ");
+
+ var worker = new Worker(workerFilename);
+
+ worker.onmessage = function(event) {
+ if (event.data.string == "testfinished") {
+ SimpleTest.finish();
+ return;
+ }
+ ok(event.data.string in strings, event.data.string);
+ is(event.data.value, strings[event.data.string], event.data.string);
+ };
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_longThread.html b/dom/workers/test/test_longThread.html
new file mode 100644
index 0000000000..80602419ca
--- /dev/null
+++ b/dom/workers/test/test_longThread.html
@@ -0,0 +1,58 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads (Bug 437152)
+-->
+<head>
+ <title>Test for DOM Worker Threads (Bug 437152)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ const numThreads = 5;
+ var doneThreads = 0;
+
+ function onmessage(event) {
+ switch (event.data) {
+ case "done":
+ if (++doneThreads == numThreads) {
+ ok(true, "All messages received from workers");
+ SimpleTest.finish();
+ }
+ break;
+ default:
+ ok(false, "Unexpected message");
+ SimpleTest.finish();
+ }
+ }
+
+ function onerror(event) {
+ ok(false, "Worker had an error");
+ SimpleTest.finish();
+ }
+
+ for (var i = 0; i < numThreads; i++) {
+ var worker = new Worker("longThread_worker.js");
+ worker.onmessage = onmessage;
+ worker.onerror = onerror;
+ worker.postMessage("start");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_multi_sharedWorker.html b/dom/workers/test/test_multi_sharedWorker.html
new file mode 100644
index 0000000000..7bfbeaa9e9
--- /dev/null
+++ b/dom/workers/test/test_multi_sharedWorker.html
@@ -0,0 +1,241 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for SharedWorker</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ const basePath =
+ location.pathname.substring(0,
+ location.pathname.lastIndexOf("/") + 1);
+ const baseURL = location.origin + basePath;
+
+ const frameRelativeURL = "multi_sharedWorker_frame.html";
+ const frameAbsoluteURL = baseURL + frameRelativeURL;
+ const workerAbsoluteURL =
+ baseURL + "multi_sharedWorker_sharedWorker.js";
+
+ const storedData = "0123456789abcdefghijklmnopqrstuvwxyz";
+ const errorMessage = "Error: Expected";
+ const errorLineno = 34;
+
+ let testGenerator = (function*() {
+ SimpleTest.waitForExplicitFinish();
+
+ window.addEventListener("message", function(event) {
+ if (typeof(event.data) == "string") {
+ info(event.data);
+ } else {
+ sendToGenerator(event);
+ }
+ });
+
+ let frame1 = document.getElementById("frame1");
+ frame1.src = frameRelativeURL;
+ frame1.onload = sendToGenerator;
+
+ yield undefined;
+
+ frame1 = frame1.contentWindow;
+
+ let frame2 = document.getElementById("frame2");
+ frame2.src = frameAbsoluteURL;
+ frame2.onload = sendToGenerator;
+
+ yield undefined;
+
+ frame2 = frame2.contentWindow;
+
+ let data = {
+ command: "start"
+ };
+
+ frame1.postMessage(data, "*");
+ frame2.postMessage(data, "*");
+
+ let event = yield undefined;
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ is(event.source, frame1, "First window got the event");
+ is(event.data.type, "connect", "Got a connect message");
+
+ data = {
+ command: "retrieve"
+ };
+ frame1.postMessage(data, "*");
+
+ event = yield undefined;
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ is(event.source, frame1, "First window got the event");
+ is(event.data.type, "result", "Got a result message");
+ is(event.data.data, undefined, "No data stored yet");
+
+ frame2.postMessage(data, "*");
+
+ event = yield undefined;
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ is(event.source, frame2, "Second window got the event");
+ is(event.data.type, "result", "Got a result message");
+ is(event.data.data, undefined, "No data stored yet");
+
+ data = {
+ command: "store",
+ data: storedData
+ };
+ frame2.postMessage(data, "*");
+
+ data = {
+ command: "retrieve"
+ };
+ frame1.postMessage(data, "*");
+
+ event = yield undefined;
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ is(event.source, frame1, "First window got the event");
+ is(event.data.type, "result", "Got a result message");
+ is(event.data.data, storedData, "Got stored data");
+
+ // This will generate two MessageEvents, one for each window.
+ let sawFrame1Error = false;
+ let sawFrame2Error = false;
+
+ data = {
+ command: "error"
+ };
+ frame1.postMessage(data, "*");
+
+ // First event.
+ event = yield undefined;
+
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ is(event.data.type, "worker-error", "Got an error message");
+ is(event.data.message, errorMessage, "Got correct error message");
+ is(event.data.filename, workerAbsoluteURL, "Got correct filename");
+ is(event.data.lineno, errorLineno, "Got correct lineno");
+ if (event.source == frame1) {
+ is(sawFrame1Error, false, "Haven't seen error for frame1 yet");
+ sawFrame1Error = true;
+ } else if (event.source == frame2) {
+ is(sawFrame2Error, false, "Haven't seen error for frame1 yet");
+ sawFrame2Error = true;
+ } else {
+ ok(false, "Saw error from unknown window");
+ }
+
+ // Second event
+ event = yield undefined;
+
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ is(event.data.type, "worker-error", "Got an error message");
+ is(event.data.message, errorMessage, "Got correct error message");
+ is(event.data.filename, workerAbsoluteURL, "Got correct filename");
+ is(event.data.lineno, errorLineno, "Got correct lineno");
+ if (event.source == frame1) {
+ is(sawFrame1Error, false, "Haven't seen error for frame1 yet");
+ sawFrame1Error = true;
+ } else if (event.source == frame2) {
+ is(sawFrame2Error, false, "Haven't seen error for frame1 yet");
+ sawFrame2Error = true;
+ } else {
+ ok(false, "Saw error from unknown window");
+ }
+
+ is(sawFrame1Error, true, "Saw error for frame1");
+ is(sawFrame2Error, true, "Saw error for frame2");
+
+ // This will generate two MessageEvents, one for each window.
+ sawFrame1Error = false;
+ sawFrame2Error = false;
+
+ data = {
+ command: "error"
+ };
+ frame1.postMessage(data, "*");
+
+ // First event.
+ event = yield undefined;
+
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ is(event.data.type, "error", "Got an error message");
+ is(event.data.message, errorMessage, "Got correct error message");
+ is(event.data.filename, workerAbsoluteURL, "Got correct filename");
+ is(event.data.lineno, errorLineno, "Got correct lineno");
+ is(event.data.isErrorEvent, true, "Frame got an ErrorEvent");
+ if (event.source == frame1) {
+ is(sawFrame1Error, false, "Haven't seen error for frame1 yet");
+ sawFrame1Error = true;
+ } else if (event.source == frame2) {
+ is(sawFrame2Error, false, "Haven't seen error for frame1 yet");
+ sawFrame2Error = true;
+ } else {
+ ok(false, "Saw error from unknown window");
+ }
+
+ // Second event
+ event = yield undefined;
+
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ is(event.data.type, "error", "Got an error message");
+ is(event.data.message, errorMessage, "Got correct error message");
+ is(event.data.filename, workerAbsoluteURL, "Got correct filename");
+ is(event.data.lineno, errorLineno, "Got correct lineno");
+ is(event.data.isErrorEvent, true, "Frame got an ErrorEvent");
+ if (event.source == frame1) {
+ is(sawFrame1Error, false, "Haven't seen error for frame1 yet");
+ sawFrame1Error = true;
+ } else if (event.source == frame2) {
+ is(sawFrame2Error, false, "Haven't seen error for frame1 yet");
+ sawFrame2Error = true;
+ } else {
+ ok(false, "Saw error from unknown window");
+ }
+
+ is(sawFrame1Error, true, "Saw error for frame1");
+ is(sawFrame2Error, true, "Saw error for frame2");
+
+ // Try a shared worker in a different origin.
+ frame1 = document.getElementById("frame1");
+ frame1.src = "http://example.org" + basePath + frameRelativeURL;
+ frame1.onload = sendToGenerator;
+ yield undefined;
+
+ frame1 = frame1.contentWindow;
+
+ data = {
+ command: "retrieve"
+ };
+ frame1.postMessage(data, "*");
+
+ event = yield undefined;
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ is(event.source, frame1, "First window got the event");
+ is(event.data.type, "result", "Got a result message");
+ is(event.data.data, undefined, "No data stored yet");
+
+ frame2.postMessage(data, "*");
+
+ event = yield undefined;
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ is(event.source, frame2, "First window got the event");
+ is(event.data.type, "result", "Got a result message");
+ is(event.data.data, storedData, "Got stored data");
+
+ window.removeEventListener("message", sendToGenerator);
+
+ SimpleTest.finish();
+ })();
+
+ let sendToGenerator = testGenerator.next.bind(testGenerator);
+
+ </script>
+ </head>
+ <body onload="testGenerator.next();">
+ <iframe id="frame1"></iframe>
+ <iframe id="frame2"></iframe>
+ </body>
+</html>
diff --git a/dom/workers/test/test_multi_sharedWorker_lifetimes_bfcache.html b/dom/workers/test/test_multi_sharedWorker_lifetimes_bfcache.html
new file mode 100644
index 0000000000..346950020f
--- /dev/null
+++ b/dom/workers/test/test_multi_sharedWorker_lifetimes_bfcache.html
@@ -0,0 +1,151 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for SharedWorker</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <script class="testbody" type="text/javascript">
+ "use strict";
+ const windowRelativeURL = "multi_sharedWorker_frame_bfcache.html";
+ // This suffix will be used as a search query parameter when we are
+ // navigating from navigate.html to the multi_sharedWorker_frame_bfcache
+ // page again. Since we are not using history.back() we are not loading
+ // that page from bfcache, but instead loading it anew, while the page
+ // we loaded initially stays in bfcache. In order to not kick out the
+ // page that is currently in the bfcache, we need to use a different
+ // BroadcastChannel. So we use search query param as part of
+ // BroadcastChannel's name.
+ const suffix = "?3";
+ const storedData = "0123456789abcdefghijklmnopqrstuvwxyz";
+ var bc, bc2, bc3;
+ SimpleTest.waitForExplicitFinish();
+
+
+ function postToWorker(aBc, workerMessage) {
+ aBc.postMessage({command: "postToWorker", workerMessage});
+ }
+
+ let testGenerator = (function*() {
+
+ bc = new BroadcastChannel("bugSharedWorkerLiftetime");
+ bc3 = new BroadcastChannel("bugSharedWorkerLiftetime" + suffix);
+ bc.onmessage = (event) => {
+ var msg = event.data;
+ var command = msg.command;
+ if (command == "debug") {
+ info(msg.message);
+ } else if (command == "fromWorker" || command == "loaded") {
+ sendToGenerator(msg.workerMessage);
+ }
+ }
+ bc3.onmessage = (event) => {
+ var msg = event.data;
+ var command = msg.command;
+ if (command == "finished") {
+ bc.close();
+ bc3.close();
+ bc2.close();
+ SimpleTest.finish();
+ } else if (command == "debug") {
+ info(msg.message);
+ } else if (command == "fromWorker" || command == "loaded") {
+ sendToGenerator(msg.workerMessage);
+ }
+ }
+ bc2 = new BroadcastChannel("navigate");
+ bc2.onmessage = (event) => {
+ if (event.data.command == "loaded") {
+ sendToGenerator();
+ }
+ }
+
+ // Open the window
+ window.open(windowRelativeURL, "testWin", "noopener");
+ yield undefined;
+
+ postToWorker(bc, { command: "retrieve" });
+
+ var msg = yield undefined;
+ is(msg.type, "result", "Got a result message");
+ is(msg.data, undefined, "No data stored");
+
+ postToWorker(bc, { command: "store", data: storedData });
+ postToWorker(bc, { command: "retrieve" });
+
+ msg = yield undefined;
+ is(msg.type, "result", "Got a result message");
+ is(msg.data, storedData, "Got stored data");
+
+
+ // Navigate when the bfcache is enabled.
+ info("Navigating to a different page");
+ bc.postMessage({command: "navigate", location: "navigate.html"});
+ yield undefined;
+
+ for (let i = 0; i < 3; i++) {
+ info("Running GC");
+ SpecialPowers.exactGC(sendToGenerator);
+ yield undefined;
+
+ // It seems using SpecialPowers.executeSoon() would make the
+ // entryGlobal being the BrowserChildGlobal (and that would make the
+ // baseURI in the location assignment below being incorrect);
+ // setTimeout on the otherhand ensures the entryGlobal is this
+ // window.
+ info("Waiting the event queue to clear");
+ setTimeout(sendToGenerator, 0);
+ yield undefined;
+ }
+
+ info("Navigating to " + windowRelativeURL);
+ bc2.postMessage({command: "navigate", location: windowRelativeURL + suffix});
+ yield undefined;
+
+ postToWorker(bc3, { command: "retrieve" });
+
+ msg = yield undefined;
+ is(msg.type, "result", "Got a result message");
+ is(msg.data, storedData, "Still have data stored");
+
+ bc3.postMessage({command: "finish"});
+ })();
+
+ let sendToGenerator = testGenerator.next.bind(testGenerator);
+
+ function runTest() {
+ if (isXOrigin) {
+ // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5)
+ // Acquire storage access permission here so that the BroadcastChannel used to
+ // communicate with the opened windows works in xorigin tests. Otherwise,
+ // the iframe containing this page is isolated from first-party storage access,
+ // which isolates BroadcastChannel communication.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ SpecialPowers.pushPermissions([{'type': 'storageAccessAPI', 'allow': 1, 'context': document}], () =>{
+ SpecialPowers.wrap(document).requestStorageAccess().then(() => {
+ // If Fission is disabled, the pref is no-op.
+ SpecialPowers.pushPrefEnv({set: [
+ ["fission.bfcacheInParent", true],
+ ["privacy.partition.always_partition_third_party_non_cookie_storage", false],
+ ]}, () => {
+ testGenerator.next();
+ });
+ }).then(() => {
+ SpecialPowers.removePermission("3rdPartyStorage^http://mochi.test:8888", "http://mochi.xorigin-test:8888");
+ });
+ });
+ } else {
+ // If Fission is disabled, the pref is no-op.
+ SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]}, () => {
+ testGenerator.next();
+ });
+ }
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ </body>
+</html>
diff --git a/dom/workers/test/test_multi_sharedWorker_lifetimes_nobfcache.html b/dom/workers/test/test_multi_sharedWorker_lifetimes_nobfcache.html
new file mode 100644
index 0000000000..5049ead1a9
--- /dev/null
+++ b/dom/workers/test/test_multi_sharedWorker_lifetimes_nobfcache.html
@@ -0,0 +1,126 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for SharedWorker</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ function runTest() {
+ const windowRelativeURL = "multi_sharedWorker_frame_nobfcache.html";
+ const storedData = "0123456789abcdefghijklmnopqrstuvwxyz";
+ var bc, bc2;
+ bc = new BroadcastChannel("bugSharedWorkerLiftetime");
+ bc.onmessage = (event) => {
+ var msg = event.data;
+ var command = msg.command;
+ if (command == "finished") {
+ bc.close();
+ bc2.close();
+ SimpleTest.finish();
+ } else if (command == "debug") {
+ info(msg.message);
+ } else if (command == "fromWorker" || command == "loaded") {
+ sendToGenerator(msg.workerMessage);
+ }
+ }
+ bc2 = new BroadcastChannel("navigate");
+ bc2.onmessage = (event) => {
+ if (event.data.command == "loaded") {
+ sendToGenerator();
+ }
+ }
+
+ function postToWorker(workerMessage) {
+ bc.postMessage({command: "postToWorker", workerMessage});
+ }
+
+ let testGenerator = (function*() {
+ SimpleTest.waitForExplicitFinish();
+
+ // Open the window
+ window.open(windowRelativeURL, "testWin", "noopener");
+ yield undefined;
+
+ // Retrieve data from worker
+ postToWorker({ command: "retrieve" });
+
+ let msg = yield undefined;
+
+ // Verify there is no data stored
+ is(msg.type, "result", "Got a result message");
+ is(msg.data, undefined, "No data stored yet");
+
+ // Store data, and retrieve it
+ postToWorker({ command: "store", data: storedData });
+ postToWorker({ command: "retrieve" });
+
+ msg = yield undefined;
+ // Verify there is data stored
+ is(msg.type, "result", "Got a result message");
+ is(msg.data, storedData, "Got stored data");
+
+
+ info("Navigating to a different page");
+ // Current subpage should not go into bfcache because of the Cache-Control
+ // headers we have set.
+ bc.postMessage({command: "navigate", location: "navigate.html"});
+ yield undefined;
+
+ info("Navigating to " + windowRelativeURL);
+ bc2.postMessage({command: "navigate", location: windowRelativeURL });
+ yield undefined;
+
+ postToWorker({ command: "retrieve" });
+
+ msg = yield undefined;
+ is(msg.type, "result", "Got a result message");
+ is(msg.data, undefined, "No data stored");
+
+ postToWorker({ command: "store", data: storedData });
+ postToWorker({ command: "retrieve" });
+
+ msg = yield undefined;
+ is(msg.type, "result", "Got a result message");
+ is(msg.data, storedData, "Got stored data");
+
+ bc.postMessage({command: "finish"});
+ })();
+
+ let sendToGenerator = testGenerator.next.bind(testGenerator);
+ testGenerator.next();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ if (isXOrigin) {
+ // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5)
+ // Acquire storage access permission here so that the BroadcastChannel used to
+ // communicate with the opened windows works in xorigin tests. Otherwise,
+ // the iframe containing this page is isolated from first-party storage access,
+ // which isolates BroadcastChannel communication.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ SpecialPowers.pushPrefEnv({
+ set: [["privacy.partition.always_partition_third_party_non_cookie_storage", false]],
+ }).then(() => {
+ SpecialPowers.pushPermissions([{'type': 'storageAccessAPI', 'allow': 1, 'context': document}], () =>{
+ SpecialPowers.wrap(document).requestStorageAccess().then(() => {
+ runTest();
+ }).then(() => {
+ SpecialPowers.removePermission("3rdPartyStorage^http://mochi.test:8888", "http://mochi.xorigin-test:8888");
+ });
+ });
+ });
+ } else {
+ runTest();
+ }
+
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/dom/workers/test/test_navigator.html b/dom/workers/test/test_navigator.html
new file mode 100644
index 0000000000..a9ca9cad66
--- /dev/null
+++ b/dom/workers/test/test_navigator.html
@@ -0,0 +1,27 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Navigator
+-->
+<head>
+ <title>Test for DOM Worker Navigator</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script>
+ ok(!self.isSecureContext, "This test should not be running in a secure context");
+</script>
+<script type="text/javascript" src="test_navigator.js"></script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_navigator.js b/dom/workers/test/test_navigator.js
new file mode 100644
index 0000000000..c1eab3aa89
--- /dev/null
+++ b/dom/workers/test/test_navigator.js
@@ -0,0 +1,10 @@
+SimpleTest.waitForExplicitFinish();
+
+// This test loads in an iframe, to ensure that the navigator instance is
+// loaded with the correct value of the preference.
+SpecialPowers.pushPrefEnv({ set: [["dom.netinfo.enabled", true]] }, () => {
+ let iframe = document.createElement("iframe");
+ iframe.id = "f1";
+ iframe.src = "test_navigator_iframe.html";
+ document.body.appendChild(iframe);
+});
diff --git a/dom/workers/test/test_navigator_iframe.html b/dom/workers/test/test_navigator_iframe.html
new file mode 100644
index 0000000000..907739e90a
--- /dev/null
+++ b/dom/workers/test/test_navigator_iframe.html
@@ -0,0 +1,24 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Sub-tests of DOM Worker Navigator tests.
+-->
+<head>
+ <title>Test for DOM Worker Navigator</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="text/javascript" src="test_navigator_iframe.js"></script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_navigator_iframe.js b/dom/workers/test/test_navigator_iframe.js
new file mode 100644
index 0000000000..fd6269cebe
--- /dev/null
+++ b/dom/workers/test/test_navigator_iframe.js
@@ -0,0 +1,64 @@
+var worker = new Worker("navigator_worker.js");
+
+var is = window.parent.is;
+var ok = window.parent.ok;
+var SimpleTest = window.parent.SimpleTest;
+
+worker.onmessage = function (event) {
+ var args = JSON.parse(event.data);
+
+ if (args.name == "testFinished") {
+ SimpleTest.finish();
+ return;
+ }
+
+ if (typeof navigator[args.name] == "undefined") {
+ ok(false, "Navigator has no '" + args.name + "' property!");
+ return;
+ }
+
+ if (args.name === "languages") {
+ is(
+ navigator.languages.toString(),
+ args.value.toString(),
+ "languages matches"
+ );
+ return;
+ }
+
+ const objectProperties = [
+ "connection",
+ "gpu",
+ "locks",
+ "mediaCapabilities",
+ "storage",
+ ];
+
+ if (objectProperties.includes(args.name)) {
+ is(
+ typeof navigator[args.name],
+ typeof args.value,
+ `${args.name} type matches`
+ );
+ return;
+ }
+
+ is(
+ navigator[args.name],
+ args.value,
+ "Mismatched navigator string for " + args.name + "!"
+ );
+};
+
+worker.onerror = function (event) {
+ ok(false, "Worker had an error: " + event.message);
+ SimpleTest.finish();
+};
+
+var { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+var isNightly = AppConstants.NIGHTLY_BUILD;
+var isRelease = AppConstants.RELEASE_OR_BETA;
+
+worker.postMessage({ isNightly, isRelease });
diff --git a/dom/workers/test/test_navigator_languages.html b/dom/workers/test/test_navigator_languages.html
new file mode 100644
index 0000000000..e4b6fec9a6
--- /dev/null
+++ b/dom/workers/test/test_navigator_languages.html
@@ -0,0 +1,58 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Navigator
+-->
+<head>
+ <title>Test for DOM Worker Navigator.languages</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" language="javascript">
+
+ var tests = [
+ { expectedLanguages: 'en,it', inputLanguages: 'en,it' },
+ { expectedLanguages: 'it,en,fr', inputLanguages: 'it,en,fr' },
+ { expectedLanguages: SpecialPowers.Services.locale.webExposedLocales[0], inputLanguages: '' },
+ { expectedLanguages: 'en,it', inputLanguages: 'en,it' },
+ ];
+ var test;
+ function runTests() {
+ if (!tests.length) {
+ worker.postMessage('finish');
+ SimpleTest.finish();
+ return;
+ }
+
+ test = tests.shift();
+ SpecialPowers.pushPrefEnv({"set": [["intl.accept_languages", test.inputLanguages]]}, function() {
+ worker.postMessage(true);
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ var worker = new Worker("navigator_languages_worker.js");
+
+ worker.onmessage = function(event) {
+ is(event.data.toString(), navigator.languages.toString(), "The languages match.");
+ is(event.data.toString(), test.expectedLanguages, "This is the correct result.");
+ runTests();
+ }
+
+ runTests();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_navigator_secureContext.html b/dom/workers/test/test_navigator_secureContext.html
new file mode 100644
index 0000000000..ac5ceb6628
--- /dev/null
+++ b/dom/workers/test/test_navigator_secureContext.html
@@ -0,0 +1,27 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Navigator
+-->
+<head>
+ <title>Test for DOM Worker Navigator</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script>
+ ok(self.isSecureContext, "This test should be running in a secure context");
+</script>
+<script type="text/javascript" src="test_navigator.js"></script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_navigator_workers_hardwareConcurrency.html b/dom/workers/test/test_navigator_workers_hardwareConcurrency.html
new file mode 100644
index 0000000000..e80c211ce7
--- /dev/null
+++ b/dom/workers/test/test_navigator_workers_hardwareConcurrency.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Navigator.hardwareConcurrency</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ function getWorkerHardwareConcurrency(onmessage) {
+ var script = "postMessage(navigator.hardwareConcurrency)";
+ var url = URL.createObjectURL(new Blob([script]));
+ var w = new Worker(url);
+ w.onmessage = onmessage;
+ }
+
+ function resistFingerprinting(value) {
+ return SpecialPowers.pushPrefEnv({"set": [["privacy.resistFingerprinting", value]]});
+ }
+
+ getWorkerHardwareConcurrency(e => {
+ var x = e.data;
+ is(typeof x, "number", "hardwareConcurrency should be a number.");
+ ok(x > 0, "hardwareConcurrency should be greater than 0.");
+
+ resistFingerprinting(true).then(() => {
+ getWorkerHardwareConcurrency(msg => {
+ const y = msg.data;
+ ok(y === 2, "hardwareConcurrency should always be 2 when we're resisting fingerprinting.");
+
+ resistFingerprinting(false).then(() => {
+ getWorkerHardwareConcurrency(msg1 => {
+ const z = msg1.data;
+ ok(z === x, "hardwareConcurrency should be the same as before we were resisting fingerprinting.");
+
+ SimpleTest.finish();
+ });
+ });
+ });
+ });
+ });
+
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_newError.html b/dom/workers/test/test_newError.html
new file mode 100644
index 0000000000..9dd0889df8
--- /dev/null
+++ b/dom/workers/test/test_newError.html
@@ -0,0 +1,33 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Test for DOM Worker Threads</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("newError_worker.js");
+
+ worker.onmessage = function(event) {
+ ok(false, "Shouldn't get a message!");
+ SimpleTest.finish();
+ }
+
+ worker.onerror = function(event) {
+ is(event.message, "Error: foo!", "Got wrong error message!");
+ event.preventDefault();
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_notification.html b/dom/workers/test/test_notification.html
new file mode 100644
index 0000000000..0171dea0f2
--- /dev/null
+++ b/dom/workers/test/test_notification.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=916893
+-->
+<head>
+ <title>Bug 916893</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/NotificationTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916893">Bug 916893</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script type="text/javascript">
+ SimpleTest.requestFlakyTimeout("Mock alert service dispatches show and click events.");
+
+ function runTest() {
+ MockServices.register();
+ var w = new Worker("notification_worker.js");
+ w.onmessage = function(e) {
+ if (e.data.type === 'finish') {
+ MockServices.unregister();
+ SimpleTest.finish();
+ } else if (e.data.type === 'ok') {
+ ok(e.data.test, e.data.message);
+ } else if (e.data.type === 'is') {
+ is(e.data.test1, e.data.test2, e.data.message);
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ // turn on testing pref (used by notification.cpp, and mock the alerts
+ SpecialPowers.setBoolPref("notification.prompt.testing", true);
+ w.postMessage('start')
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest();
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/test_notification_child.html b/dom/workers/test/test_notification_child.html
new file mode 100644
index 0000000000..4f9523a03f
--- /dev/null
+++ b/dom/workers/test/test_notification_child.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=916893
+-->
+<head>
+ <title>Bug 916893 - Test Notifications in child workers.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/NotificationTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916893">Bug 916893</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script type="text/javascript">
+ SimpleTest.requestFlakyTimeout("Mock alert service dispatches show event.");
+ function runTest() {
+ MockServices.register();
+ var w = new Worker("notification_worker_child-parent.js");
+ w.onmessage = function(e) {
+ if (e.data.type === 'finish') {
+ MockServices.unregister();
+ SimpleTest.finish();
+ } else if (e.data.type === 'ok') {
+ ok(e.data.test, e.data.message);
+ } else if (e.data.type === 'is') {
+ is(e.data.test1, e.data.test2, e.data.message);
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ // turn on testing pref (used by notification.cpp, and mock the alerts
+ SpecialPowers.setBoolPref("notification.prompt.testing", true);
+ w.postMessage('start')
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest();
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/test_notification_permission.html b/dom/workers/test/test_notification_permission.html
new file mode 100644
index 0000000000..904cfcdef2
--- /dev/null
+++ b/dom/workers/test/test_notification_permission.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=916893
+-->
+<head>
+ <title>Bug 916893 - Make sure error is fired on Notification if permission is denied.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/NotificationTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916893">Bug 916893</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script type="text/javascript">
+ SimpleTest.requestFlakyTimeout("Mock alert service dispatches show event.");
+ function runTest() {
+ MockServices.register();
+ var w = new Worker("notification_permission_worker.js");
+ w.onmessage = function(e) {
+ if (e.data.type === 'finish') {
+ SpecialPowers.setBoolPref("notification.prompt.testing.allow", true);
+ MockServices.unregister();
+ SimpleTest.finish();
+ } else if (e.data.type === 'ok') {
+ ok(e.data.test, e.data.message);
+ } else if (e.data.type === 'is') {
+ is(e.data.test1, e.data.test2, e.data.message);
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ // turn on testing pref (used by notification.cpp, and mock the alerts
+ SpecialPowers.setBoolPref("notification.prompt.testing", true);
+ SpecialPowers.setBoolPref("notification.prompt.testing.allow", false);
+ w.postMessage('start')
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest();
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/test_onLine.html b/dom/workers/test/test_onLine.html
new file mode 100644
index 0000000000..b0429e7d96
--- /dev/null
+++ b/dom/workers/test/test_onLine.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 925437: online/offline events tests.
+
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/licenses/publicdomain/
+-->
+<head>
+ <title>Test for Bug 925437 (worker online/offline events)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=925437">Mozilla Bug 925437</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="text/javascript">
+
+addLoadEvent(function() {
+ var w = new Worker("onLine_worker.js");
+
+ w.onmessage = function(e) {
+ if (e.data.type === 'ready') {
+ // XXX Important trick here.
+ //
+ // Setting iosvc.offline would trigger a sync notifyObservers call, and if
+ // there exists a preloaded about:newtab (see tabbrowser._handleNewTab),
+ // that tab will be notified.
+ //
+ // This implies a sync call across different tabGroups, and will hit the
+ // assertion in SchedulerGroup::ValidateAccess(). So use executeSoon to
+ // re-dispatch an unlabeled runnable to the event queue.
+ SpecialPowers.executeSoon(doTest);
+ } else if (e.data.type === 'ok') {
+ ok(e.data.test, e.data.message);
+ } else if (e.data.type === 'finished') {
+ SimpleTest.finish();
+ }
+ }
+
+ function doTest() {
+ var iosvc = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService);
+ iosvc.manageOfflineStatus = false;
+
+ info("setting iosvc.offline = true");
+ iosvc.offline = true;
+
+ info("setting iosvc.offline = false");
+ iosvc.offline = false;
+
+ info("setting iosvc.offline = true");
+ iosvc.offline = true;
+
+ for (var i = 0; i < 10; ++i) {
+ iosvc.offline = !iosvc.offline;
+ }
+
+ info("setting iosvc.offline = false");
+ w.postMessage('lastTest');
+ iosvc.offline = false;
+ }
+});
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/test_promise.html b/dom/workers/test/test_promise.html
new file mode 100644
index 0000000000..7c3ef09a98
--- /dev/null
+++ b/dom/workers/test/test_promise.html
@@ -0,0 +1,43 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Promise object in workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ function runTest() {
+ var worker = new Worker("promise_worker.js");
+
+ worker.onmessage = function(event) {
+
+ if (event.data.type == 'finish') {
+ SimpleTest.finish();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ }
+ }
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ SimpleTest.finish();
+ };
+
+ worker.postMessage(true);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_promise_resolved_with_string.html b/dom/workers/test/test_promise_resolved_with_string.html
new file mode 100644
index 0000000000..8c0b0aca49
--- /dev/null
+++ b/dom/workers/test/test_promise_resolved_with_string.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1027221
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1027221</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1027221 **/
+ // Set up a permanent atom
+ SimpleTest.waitForExplicitFinish();
+ var x = "x";
+ // Trigger some incremental gc
+ SpecialPowers.Cu.getJSTestingFunctions().gcslice(1);
+
+ // Kick off a worker that uses this same atom
+ var w = new Worker("data:text/plain,Promise.resolve('x').then(function() { postMessage(1); });");
+ // Maybe trigger some more incremental gc
+ SpecialPowers.Cu.getJSTestingFunctions().gcslice(1);
+
+ w.onmessage = function() {
+ ok(true, "Got here");
+ SimpleTest.finish();
+ };
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1027221">Mozilla Bug 1027221</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_readableStream_when_closing.html b/dom/workers/test/test_readableStream_when_closing.html
new file mode 100644
index 0000000000..24d5bf3821
--- /dev/null
+++ b/dom/workers/test/test_readableStream_when_closing.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for ReadableStream+fetch when the worker is closing</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+</head>
+<body>
+ <script>
+
+function workerCode() {
+ onmessage = () => {
+ const BIG_BUFFER_SIZE = 1000000;
+ const fibStream = new ReadableStream({
+ start(controller) {},
+
+ pull(controller) {
+ const buffer = new Uint8Array(BIG_BUFFER_SIZE);
+ buffer.fill(42);
+ controller.enqueue(buffer);
+ }
+ });
+
+ const r = new Response(fibStream);
+
+ const p = r.blob();
+ self.postMessage("reading");
+
+ p.then(() => {
+ // really?
+ });
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+const b = new Blob([workerCode+'workerCode();']);
+const url = URL.createObjectURL(b);
+const w = new Worker(url);
+w.onmessage = function(e) {
+ ok(true, 'Worker is reading');
+
+ const wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"].
+ getService(Ci.nsIWorkerDebuggerManager);
+ wdm.addListener({
+ onUnregister (dbg) {
+ if (dbg.url == url) {
+ ok(true, "Debugger with url " + url + " should be unregistered.");
+ wdm.removeListener(this);
+ SimpleTest.finish();
+ }
+ }
+ });
+
+ w.terminate();
+}
+w.postMessage("start");
+ </script>
+</body>
+</html>
diff --git a/dom/workers/test/test_recursion.html b/dom/workers/test/test_recursion.html
new file mode 100644
index 0000000000..8e38a80351
--- /dev/null
+++ b/dom/workers/test/test_recursion.html
@@ -0,0 +1,69 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads
+-->
+<head>
+ <title>Test for DOM Worker Threads Recursion</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ // Intermittently triggers one assertion on Mac (bug 848098).
+ if (navigator.platform.indexOf("Mac") == 0) {
+ SimpleTest.expectAssertions(0, 1);
+ }
+
+ const testCount = 2;
+ var errorCount = 0;
+
+ var worker = new Worker("recursion_worker.js");
+
+ function done() {
+ worker.terminate();
+ SimpleTest.finish();
+ }
+
+ worker.onmessage = function(event) {
+ if (event.data == "Done") {
+ ok(true, "correct message");
+ }
+ else {
+ ok(false, "Bad message: " + event.data);
+ }
+ done();
+ }
+
+ worker.onerror = function(event) {
+ event.preventDefault();
+ if (event.message == "too much recursion") {
+ ok(true, "got correct error message");
+ ++errorCount;
+ }
+ else {
+ ok(false, "got bad error message: " + event.message);
+ done();
+ }
+ }
+
+ for (var i = 0; i < testCount; i++) {
+ worker.postMessage("");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_recursiveOnerror.html b/dom/workers/test/test_recursiveOnerror.html
new file mode 100644
index 0000000000..e6c040439d
--- /dev/null
+++ b/dom/workers/test/test_recursiveOnerror.html
@@ -0,0 +1,44 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <script src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <script type="text/javascript">
+ const filename = "http://mochi.test:8888/tests/dom/workers/test/" +
+ "recursiveOnerror_worker.js";
+ const errors = [
+ { message: "Error: 2", lineno: 6 },
+ { message: "Error: 1", lineno: 10 }
+ ]
+
+ var errorCount = 0;
+
+ var worker = new Worker("recursiveOnerror_worker.js");
+ worker.postMessage("go");
+
+ worker.onerror = function(event) {
+ event.preventDefault();
+
+ ok(errorCount < errors.length, "Correct number of error events");
+ const error = errors[errorCount++];
+
+ is(event.message, error.message, "Correct message");
+ is(event.filename, filename, "Correct filename");
+ is(event.lineno, error.lineno, "Correct lineno");
+
+ if (errorCount == errors.length) {
+ SimpleTest.finish();
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/test_referrer.html b/dom/workers/test/test_referrer.html
new file mode 100644
index 0000000000..a95fe91809
--- /dev/null
+++ b/dom/workers/test/test_referrer.html
@@ -0,0 +1,58 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the referrer of workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ function test_mainScript() {
+ var worker = new Worker("referrer.sjs?worker");
+ worker.onmessage = function() {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', 'referrer.sjs?result', true);
+ xhr.onload = function() {
+ is(xhr.responseText, location.href, "The referrer has been sent.");
+ next();
+ }
+ xhr.send();
+ }
+ worker.postMessage(42);
+ }
+
+ function test_importScript() {
+ var worker = new Worker("worker_referrer.js");
+ worker.onmessage = function(e) {
+ is(e.data, location.href.replace("test_referrer.html", "worker_referrer.js").split("?")[0], "The referrer has been sent.");
+ next();
+ }
+ worker.postMessage(42);
+ }
+
+ var tests = [ test_mainScript, test_importScript ];
+ function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ next();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_referrer_header_worker.html b/dom/workers/test/test_referrer_header_worker.html
new file mode 100644
index 0000000000..64d245232f
--- /dev/null
+++ b/dom/workers/test/test_referrer_header_worker.html
@@ -0,0 +1,38 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test the referrer of workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <script class="testbody" type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv(
+ {"set": [
+ ['security.mixed_content.block_display_content', false],
+ ['security.mixed_content.block_active_content', false]
+ ]},
+ function() {
+ SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], test);
+ });
+
+ function test() {
+ function messageListener(event) {
+ eval(event.data);
+ }
+ window.addEventListener("message", messageListener);
+
+ var ifr = document.createElement('iframe');
+ ifr.setAttribute('src', 'https://example.com/tests/dom/workers/test/referrer_worker.html');
+ document.body.appendChild(ifr);
+ }
+ </script>
+</body>
+</html>
diff --git a/dom/workers/test/test_resolveWorker-assignment.html b/dom/workers/test/test_resolveWorker-assignment.html
new file mode 100644
index 0000000000..2f3be19668
--- /dev/null
+++ b/dom/workers/test/test_resolveWorker-assignment.html
@@ -0,0 +1,30 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <script type="application/javascript">
+ window.Worker = 17; // resolve through assignment
+
+ var desc = Object.getOwnPropertyDescriptor(window, "Worker");
+ ok(typeof desc === "object" && desc !== null, "Worker property must exist");
+
+ is(desc.value, 17, "Overwrite didn't work correctly");
+ is(desc.enumerable, false,
+ "Initial descriptor was non-enumerable, and [[Put]] changes the " +
+ "property value but not its enumerability");
+ is(desc.configurable, true,
+ "Initial descriptor was configurable, and [[Put]] changes the " +
+ "property value but not its configurability");
+ is(desc.writable, true,
+ "Initial descriptor was writable, and [[Put]] changes the " +
+ "property value but not its writability");
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/test_resolveWorker.html b/dom/workers/test/test_resolveWorker.html
new file mode 100644
index 0000000000..0d91a9f90a
--- /dev/null
+++ b/dom/workers/test/test_resolveWorker.html
@@ -0,0 +1,31 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <script type="application/javascript">
+ window.Worker; // resolve not through assignment
+ Worker = 17;
+
+ var desc = Object.getOwnPropertyDescriptor(window, "Worker");
+ ok(typeof desc === "object" && desc !== null, "Worker property must exist");
+
+ is(desc.value, 17, "Overwrite didn't work correctly");
+ is(desc.enumerable, false,
+ "Initial descriptor was non-enumerable, and [[Put]] changes the " +
+ "property value but not its enumerability");
+ is(desc.configurable, true,
+ "Initial descriptor was configurable, and [[Put]] changes the " +
+ "property value but not its configurability");
+ is(desc.writable, true,
+ "Initial descriptor was writable, and [[Put]] changes the " +
+ "property value but not its writability");
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/test_rvals.html b/dom/workers/test/test_rvals.html
new file mode 100644
index 0000000000..799c42779d
--- /dev/null
+++ b/dom/workers/test/test_rvals.html
@@ -0,0 +1,35 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 911085</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("rvals_worker.js");
+
+ worker.onmessage = function(event) {
+ if (event.data == 'ignore') return;
+
+ if (event.data == 'finished') {
+ is(worker.terminate(), undefined, "Terminate() returns 'undefined'");
+ SimpleTest.finish();
+ return;
+ }
+
+ ok(event.data, "something good returns 'undefined' in workers");
+ };
+
+ is(worker.postMessage(42), undefined, "PostMessage() returns 'undefined' on main thread");
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_setTimeoutWith0.html b/dom/workers/test/test_setTimeoutWith0.html
new file mode 100644
index 0000000000..8685eacfe5
--- /dev/null
+++ b/dom/workers/test/test_setTimeoutWith0.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+ <title>Test for DOM Worker setTimeout and strings containing 0</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script>
+
+var a = new Worker('worker_setTimeoutWith0.js');
+a.onmessage = function(e) {
+ is(e.data, 2, "We want to see 2 here");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/test_sharedWorker.html b/dom/workers/test/test_sharedWorker.html
new file mode 100644
index 0000000000..b8ced379d5
--- /dev/null
+++ b/dom/workers/test/test_sharedWorker.html
@@ -0,0 +1,71 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for SharedWorker</title>
+ <script src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ </head>
+ <body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ <script class="testbody">
+ "use strict";
+
+ const href = window.location.href;
+ const filename = "sharedWorker_sharedWorker.js";
+ const sentMessage = "ping";
+ const errorFilename = href.substring(0, href.lastIndexOf("/") + 1) +
+ filename;
+ const errorLine = 97;
+ const errorColumn = 11;
+
+ var worker = new SharedWorker(filename);
+
+ ok(worker instanceof SharedWorker, "Got SharedWorker instance");
+ ok(!("postMessage" in worker), "SharedWorker has no 'postMessage'");
+ ok(worker.port instanceof MessagePort,
+ "Shared worker has MessagePort");
+
+ var receivedMessage;
+ var receivedError;
+
+ worker.port.onmessage = function(event) {
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ ok(event.target === worker.port,
+ "MessageEvent has correct 'target' property");
+ is(event.data, sentMessage, "Got correct message");
+ ok(receivedMessage === undefined, "Haven't gotten message yet");
+ receivedMessage = event.data;
+ if (receivedError) {
+ SimpleTest.finish();
+ }
+ };
+
+ worker.onerror = function(event) {
+ ok(event instanceof ErrorEvent, "Got an ErrorEvent");
+ is(event.message, "Error: " + sentMessage, "Got correct error");
+ is(event.filename, errorFilename, "Got correct filename");
+ is(event.lineno, errorLine, "Got correct lineno");
+ is(event.colno, errorColumn, "Got correct column");
+ ok(receivedError === undefined, "Haven't gotten error yet");
+ receivedError = event.message;
+ event.preventDefault();
+ if (receivedMessage) {
+ SimpleTest.finish();
+ }
+ };
+
+ worker.port.postMessage(sentMessage);
+
+ SimpleTest.waitForExplicitFinish();
+
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/workers/test/test_sharedWorker_lifetime.html b/dom/workers/test/test_sharedWorker_lifetime.html
new file mode 100644
index 0000000000..b987f0d7cf
--- /dev/null
+++ b/dom/workers/test/test_sharedWorker_lifetime.html
@@ -0,0 +1,30 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for MessagePort and SharedWorkers</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+ <body>
+ <script class="testbody" type="text/javascript">
+
+var gced = false;
+
+var sw = new SharedWorker('sharedWorker_lifetime.js');
+sw.port.onmessage = function(event) {
+ ok(gced, "The SW is still alive also after GC");
+ SimpleTest.finish();
+}
+
+sw = null;
+SpecialPowers.forceGC();
+gced = true;
+
+SimpleTest.waitForExplicitFinish();
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/test_sharedWorker_ports.html b/dom/workers/test/test_sharedWorker_ports.html
new file mode 100644
index 0000000000..6befd4920c
--- /dev/null
+++ b/dom/workers/test/test_sharedWorker_ports.html
@@ -0,0 +1,41 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for MessagePort and SharedWorkers</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+ <body>
+ <script class="testbody" type="text/javascript">
+
+var sw1 = new SharedWorker('sharedWorker_ports.js');
+sw1.port.onmessage = function(event) {
+ if (event.data.type == "connected") {
+ ok(true, "The SharedWorker is alive.");
+
+ var sw2 = new SharedWorker('sharedWorker_ports.js');
+ sw1.port.postMessage("Port from the main-thread!", [sw2.port]);
+ return;
+ }
+
+ if (event.data.type == "status") {
+ ok(event.data.test, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "finish") {
+ info("Finished!");
+ ok(sw1.port, "The port still exists");
+ sw1.port.foo = sw1; // Just a test to see if we leak.
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/test_sharedWorker_privateBrowsing.html b/dom/workers/test/test_sharedWorker_privateBrowsing.html
new file mode 100644
index 0000000000..f76e3db307
--- /dev/null
+++ b/dom/workers/test/test_sharedWorker_privateBrowsing.html
@@ -0,0 +1,102 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for SharedWorker - Private Browsing</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+</head>
+<body>
+
+<script type="application/javascript">
+const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+var mainWindow;
+
+var contentPage = "http://mochi.test:8888/chrome/dom/workers/test/empty.html";
+
+function testOnWindow(aIsPrivate, aCallback) {
+ var win = mainWindow.OpenBrowserWindow({private: aIsPrivate});
+ win.addEventListener("load", function() {
+ win.addEventListener("DOMContentLoaded", function onInnerLoad() {
+ if (win.content.location.href != contentPage) {
+ BrowserTestUtils.loadURIString(win.gBrowser, contentPage);
+ return;
+ }
+
+ win.removeEventListener("DOMContentLoaded", onInnerLoad, true);
+ SimpleTest.executeSoon(function() { aCallback(win); });
+ }, true);
+ }, {capture: true, once: true});
+}
+
+function setupWindow() {
+ mainWindow = window.browsingContext.topChromeWindow;
+ runTest();
+}
+
+var wN;
+var wP;
+
+function doTests() {
+ testOnWindow(false, function(aWin) {
+ wN = aWin;
+
+ testOnWindow(true, function(win) {
+ wP = win;
+
+ var sharedWorker1 = new wP.content.SharedWorker('sharedWorker_privateBrowsing.js');
+ sharedWorker1.port.onmessage = function(event) {
+ is(event.data, 1, "Only 1 sharedworker expected in the private window");
+
+ var sharedWorker2 = new wN.content.SharedWorker('sharedWorker_privateBrowsing.js');
+ sharedWorker2.port.onmessage = function(event1) {
+ is(event1.data, 1, "Only 1 sharedworker expected in the normal window");
+
+ var sharedWorker3 = new wP.content.SharedWorker('sharedWorker_privateBrowsing.js');
+ sharedWorker3.port.onmessage = function(event2) {
+ is(event2.data, 2, "Only 2 sharedworker expected in the private window");
+ runTest();
+ }
+ }
+ }
+ });
+ });
+}
+
+function doSystemSharedWorkerTest() {
+ try {
+ let chromeShared =
+ new wP.SharedWorker("chrome://mochitests/content/dom/workers/test/sharedWorker_privateBrowsing.js");
+ ok(true, "system SharedWorker created without throwing or crashing!");
+ } catch (_ex) {
+ ok(false, "system SharedWorker should not throw or crash");
+ }
+ runTest();
+}
+
+var steps = [
+ setupWindow,
+ doTests,
+ doSystemSharedWorkerTest,
+];
+
+function runTest() {
+ if (!steps.length) {
+ wN.close();
+ wP.close();
+
+ SimpleTest.finish();
+ return;
+ }
+
+ var step = steps.shift();
+ step();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [
+ ["browser.startup.page", 0],
+ ["browser.startup.homepage_override.mstone", "ignore"],
+]}, runTest);
+
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/test_sharedWorker_thirdparty.html b/dom/workers/test/test_sharedWorker_thirdparty.html
new file mode 100644
index 0000000000..caeb122bba
--- /dev/null
+++ b/dom/workers/test/test_sharedWorker_thirdparty.html
@@ -0,0 +1,54 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for SharedWorker in 3rd Party Iframes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"> </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+ <script class="testbody">
+
+ function testThirdPartyFrame(name) {
+ return new Promise(resolve => {
+ // Let's use a window, loading the same origin, in order to have the new
+ // cookie-policy applied.
+ let w = window.open("sharedWorker_thirdparty_window.html?name=" + name);
+ window.addEventListener('message', function messageListener(evt) {
+ if (evt.data.name !== name) {
+ return;
+ }
+ w.close();
+ window.removeEventListener('message', messageListener);
+ resolve(evt.data.result);
+ });
+ });
+ }
+
+ const COOKIE_BEHAVIOR_ACCEPT = 0;
+ const COOKIE_BEHAVIOR_REJECTFOREIGN = 1;
+
+ add_task(async function allowed() {
+ await SpecialPowers.pushPrefEnv({ set: [
+ ["network.cookie.cookieBehavior", COOKIE_BEHAVIOR_ACCEPT]
+ ]});
+ let result = await testThirdPartyFrame('allowed');
+ ok(result === 'allowed',
+ 'SharedWorker should be allowed when 3rd party iframes can access storage');
+ });
+
+ add_task(async function blocked() {
+ await SpecialPowers.pushPrefEnv({ set: [
+ ["network.cookie.cookieBehavior", COOKIE_BEHAVIOR_REJECTFOREIGN]
+ ]});
+ let result = await testThirdPartyFrame('blocked');
+ ok(result === 'blocked',
+ 'SharedWorker should not be allowed when 3rd party iframes are denied storage');
+ });
+
+ </script>
+</body>
+</html>
diff --git a/dom/workers/test/test_sharedworker_event_listener_leaks.html b/dom/workers/test/test_sharedworker_event_listener_leaks.html
new file mode 100644
index 0000000000..4016cdda48
--- /dev/null
+++ b/dom/workers/test/test_sharedworker_event_listener_leaks.html
@@ -0,0 +1,51 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1450358 - Test SharedWorker event listener leak conditions</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/events/test/event_leak_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+// Manipulate SharedWorker objects in the frame's context.
+// Its important here that we create a listener callback from
+// the DOM objects back to the frame's global in order to
+// exercise the leak condition.
+async function useSharedWorker(contentWindow) {
+ contentWindow.messageCount = 0;
+
+ let sw = new contentWindow.SharedWorker("data:application/javascript,self.onconnect = e => e.ports[0].postMessage({})");
+ sw.onerror = _ => {
+ contentWindow.errorCount += 1;
+ }
+ await new Promise(resolve => {
+ sw.port.onmessage = e => {
+ contentWindow.messageCount += 1;
+ resolve();
+ };
+ });
+
+ is(contentWindow.messageCount, 1, "message should be received");
+}
+
+async function runTest() {
+ try {
+ await checkForEventListenerLeaks("SharedWorker", useSharedWorker);
+ } catch (e) {
+ ok(false, e);
+ } finally {
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addEventListener("load", runTest, { once: true });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_shutdownCheck.xhtml b/dom/workers/test/test_shutdownCheck.xhtml
new file mode 100644
index 0000000000..257b37fe32
--- /dev/null
+++ b/dom/workers/test/test_shutdownCheck.xhtml
@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+
+<window title="Worker shutdown check"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+SimpleTest.waitForExplicitFinish()
+
+const URL = "worker_shutdownCheck.js";
+
+function checkWorker() {
+ const wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"].
+ getService(Ci.nsIWorkerDebuggerManager);
+
+ let e = wdm.getWorkerDebuggerEnumerator();
+ while (e.hasMoreElements()) {
+ let dbg = e.getNext().QueryInterface(Ci.nsIWorkerDebugger);
+ if (dbg.url == URL) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+new Promise(resolve => {
+ var w = new Worker(URL);
+ ok(checkWorker(), "We have the worker");
+ w.onmessage = () => { resolve(); }
+}).then(() => {
+ info("Waiting...");
+
+ // We don't know if the worker thread is able to shutdown when calling
+ // CC/GC. Better to check again in case.
+ function checkGC() {
+ Cu.forceCC();
+ Cu.forceGC();
+ if (!checkWorker()) {
+ ok(true, "We don't have the worker");
+ SimpleTest.finish();
+ return;
+ }
+ setTimeout(checkGC, 200);
+ }
+
+ checkGC();
+});
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/workers/test/test_simpleThread.html b/dom/workers/test/test_simpleThread.html
new file mode 100644
index 0000000000..aee5765170
--- /dev/null
+++ b/dom/workers/test/test_simpleThread.html
@@ -0,0 +1,74 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads (Bug 437152)
+-->
+<head>
+ <title>Test for DOM Worker Threads (Bug 437152)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("simpleThread_worker.js");
+
+ worker.addEventListener("message",function(event) {
+ is(event.target, worker);
+ switch (event.data) {
+ case "no-op":
+ break;
+ case "started":
+ is(gotErrors, true);
+ worker.postMessage("no-op");
+ worker.postMessage("stop");
+ break;
+ case "stopped":
+ worker.postMessage("no-op");
+ SimpleTest.finish();
+ break;
+ default:
+ ok(false, "Unexpected message:" + event.data);
+ SimpleTest.finish();
+ }
+ });
+
+ var gotErrors = false;
+ worker.onerror = function(event) {
+ event.preventDefault();
+ is(event.target, worker);
+ is(event.message, "uncaught exception: Bad message: asdf");
+
+ worker.onerror = function(otherEvent) {
+ otherEvent.preventDefault();
+ is(otherEvent.target, worker);
+ is(otherEvent.message, "ReferenceError: Components is not defined");
+ gotErrors = true;
+
+ worker.onerror = function(oneMoreEvent) {
+ ok(false, "Worker had an error:" + oneMoreEvent.message);
+ SimpleTest.finish();
+ };
+ };
+ };
+
+ worker.postMessage("asdf");
+ worker.postMessage("components");
+ worker.postMessage("start");
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_sourcemap_header.html b/dom/workers/test/test_sourcemap_header.html
new file mode 100644
index 0000000000..250b30f079
--- /dev/null
+++ b/dom/workers/test/test_sourcemap_header.html
@@ -0,0 +1,22 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for DOM Worker + SourceMap header</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="dom_worker_helper.js"></script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+
+<iframe id="worker-frame"></iframe>
+</body>
+<script type="text/javascript" src="sourcemap_header.js"></script>
+</html>
diff --git a/dom/workers/test/test_subworkers_suspended.html b/dom/workers/test/test_subworkers_suspended.html
new file mode 100644
index 0000000000..d5c62a28d0
--- /dev/null
+++ b/dom/workers/test/test_subworkers_suspended.html
@@ -0,0 +1,145 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for sub workers+bfcache behavior</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script type="application/javascript">
+
+
+
+ /**
+ * - main page opens testUrl1
+ * - testUrl1 ---"onpageshow"---> to main page
+ * - main page ---"startWorker"---> testUrl1
+ * - testUrl1 starts workers, also ---"verifyCacheData"---> main page
+ * - main page ---"changeLocation"---> testUrl1
+ * - testUrl1 navigated to testUrl2
+ * - testUrl2 ---"onpageshow"---> to main page
+ * - main page ---"startWorker"---> testUrl2
+ * - testUrl2 starts workers, also ---"verifyCacheData"---> main page
+ * - main page ---"goBack"---> testUrl2
+ * - testUrl2 navigates back to testUrl1
+ * - testUrl1 ---"onpageshow"---> to main page
+ * - main page checks cache data and ---"finish"---> testUrl2
+ * - testUrl1 ---"finished"---> to main page
+ */
+ var testUrl1 = "window_suspended.html?page1Shown";
+ var counter = 0;
+ const SUB_WORKERS = 3;
+
+ function cacheData() {
+ return caches.open("test")
+ .then(function(cache) {
+ return cache.match("http://mochi.test:888/foo");
+ })
+ .then(function(response) {
+ return response.text();
+ });
+ }
+
+ function runTest() {
+ var bc1 = new BroadcastChannel("page1Shown");
+ bc1.onmessage = async (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ info(`Main page, received command=${command}`);
+ if (command == "onpageshow") {
+ info("Page1Shown: " + msg.location);
+ // First time this page is shown.
+ if (counter == 0) {
+ ok(!msg.persisted, "test page should have been persisted initially");
+ var workerMessage = { type: "page1", count: SUB_WORKERS };
+ bc1.postMessage({command: "startWorker", workerMessage});
+ } else {
+ is(msg.persisted, true, "test page should have been persisted in pageshow");
+ var promise = new Promise((resolve, reject) => {
+ info("Waiting a few seconds...");
+ setTimeout(resolve, 10000);
+ });
+
+ promise.then(function() {
+ info("Retrieving data from cache...");
+ return cacheData();
+ })
+
+ .then(function(content) {
+ is(content.indexOf("page1-"), 0, "We have data from the worker");
+ })
+ .then(function() {
+ bc1.postMessage({command: "finish"});
+ });
+ }
+ counter++;
+ } else if (command == "workerMessage") {
+ is(msg.workerMessage, "ready", "We want to receive: -ready-");
+ } else if (command == "verifyCacheData") {
+ var content = await cacheData();
+ is(content.indexOf("page1-"), 0, "We have data from the worker");
+ bc1.postMessage({command: "changeLocation"});
+ } else if (command == "finished") {
+ bc1.close();
+ bc2.close();
+ SimpleTest.finish();
+ }
+ }
+ var bc2 = new BroadcastChannel("page2Shown");
+ bc2.onmessage = async (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "onpageshow") {
+ info("Page1Shown: " + msg.location);
+ var workerMessage = { type: "page2" };
+ bc2.postMessage({command: "startWorker", workerMessage});
+ } else if (command == "workerMessage") {
+ is(msg.workerMessage, "ready", "We want to receive: -ready-");
+ } else if (command == "verifyCacheData") {
+ var content = await cacheData();
+ is(content, "page2-0", "We have data from the second worker");
+ bc2.postMessage({command: "goBack"});
+ }
+ }
+
+ SpecialPowers.pushPrefEnv({ set: [
+ ["dom.caches.enabled", true],
+ ["dom.caches.testing.enabled", true],
+ // If Fission is disabled, the pref is no-op.
+ ["fission.bfcacheInParent", true],
+ ] },
+ function() {
+ window.open(testUrl1, "", "noopener");
+ });
+
+ }
+
+ if (isXOrigin) {
+ // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5)
+ // Acquire storage access permission here so that the BroadcastChannel used to
+ // communicate with the opened windows works in xorigin tests. Otherwise,
+ // the iframe containing this page is isolated from first-party storage access,
+ // which isolates BroadcastChannel communication.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ SpecialPowers.pushPrefEnv({
+ set: [["privacy.partition.always_partition_third_party_non_cookie_storage", false]],
+ }).then(() => {
+ SpecialPowers.pushPermissions([{'type': 'storageAccessAPI', 'allow': 1, 'context': document}], () =>{
+ SpecialPowers.wrap(document).requestStorageAccess().then(() => {
+ runTest();
+ }).then(() => {
+ SpecialPowers.removePermission("3rdPartyStorage^http://mochi.test:8888", "http://mochi.xorigin-test:8888");
+ });
+ });
+ });
+ } else {
+ runTest();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+
+ </script>
+</body>
+</html>
diff --git a/dom/workers/test/test_suspend.html b/dom/workers/test/test_suspend.html
new file mode 100644
index 0000000000..9ab1a6a7ec
--- /dev/null
+++ b/dom/workers/test/test_suspend.html
@@ -0,0 +1,188 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for DOM Worker Threads</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ /**
+ * - main page tells subpage to call startWorker()
+ * - subpage starts worker
+ * - worker calls setInterval() and keeps calling postMessage()
+ * - onmessage(), as setup by the subpage, calls messageCallback
+ * - when messageCallback gets called more than 25 times
+ * - subpage gets navigated to blank.html
+ * - blank page posts message to main page, and main page calls suspendCallback()
+ * - suspendCallback() schedules waitInterval() to be fired off every second
+ * - after 5 times, it clears the interval and navigates subpage back
+ * - suspend_window subpage starts receiving messages again and
+ * does a final call to messageCallback()
+ * - finishTest() is called
+ */
+
+ var lastCount;
+
+ var suspended = false;
+ var resumed = false;
+ var finished = false;
+ var suspendBlankPageCurrentlyShowing = false;
+
+ var interval;
+ var oldMessageCount;
+ var waitCount = 0;
+
+ var bcSuspendWindow, bcSuspendBlank;
+
+ function runTest() {
+ bcSuspendWindow = new BroadcastChannel("suspendWindow");
+ bcSuspendWindow.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ var data = msg.data;
+ if (command == "loaded") {
+ if (finished) {
+ return;
+ }
+ bcSuspendWindow.postMessage({command: "startWorker"});
+ } else if (command == "messageCallback") {
+ messageCallback(data);
+ } else if (command == "errorCallback") {
+ errorCallback(data);
+ } else if (command == "finished") {
+ SimpleTest.finish();
+ }
+ }
+
+ bcSuspendBlank = new BroadcastChannel("suspendBlank");
+ bcSuspendBlank.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "loaded") {
+ suspendBlankPageCurrentlyShowing = true;
+ if (suspended) {
+ badOnloadCallback();
+ } else {
+ suspendCallback();
+ }
+ } else if (command == "pagehide") {
+ suspendBlankPageCurrentlyShowing = false;
+ }
+ }
+
+ // If Fission is disabled, the pref is no-op.
+ SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]}, () => {
+ window.open("suspend_window.html", "testWin", "noopener");
+ });
+ }
+
+ function finishTest() {
+ if (finished) {
+ return;
+ }
+ finished = true;
+ bcSuspendWindow.postMessage({command: "finish"});
+ }
+
+ function waitInterval() {
+ if (finished) {
+ return;
+ }
+ ok(suspendBlankPageCurrentlyShowing, "correct page is showing");
+ is(suspended, true, "Not suspended?");
+ is(resumed, false, "Already resumed?!");
+ is(lastCount, oldMessageCount, "Received a message while suspended!");
+ if (++waitCount == 5) {
+ clearInterval(interval);
+ resumed = true;
+ bcSuspendBlank.postMessage({command: "navigateBack"});
+ }
+ }
+
+ function badOnloadCallback() {
+ if (finished) {
+ return;
+ }
+ ok(false, "We don't want suspend_window.html to fire a new load event, we want it to come out of the bfcache!");
+ finishTest();
+ }
+
+ function suspendCallback() {
+ if (finished) {
+ return;
+ }
+ ok(suspendBlankPageCurrentlyShowing, "correct page is showing");
+ is(suspended, false, "Already suspended?");
+ is(resumed, false, "Already resumed?");
+ suspended = true;
+ oldMessageCount = lastCount;
+ interval = setInterval(waitInterval, 1000);
+ }
+
+ function messageCallback(data) {
+ if (finished) {
+ return;
+ }
+
+ if (!suspended) {
+ ok(lastCount === undefined || lastCount == data - 1,
+ "Got good data, lastCount = " + lastCount + ", data = " + data);
+ lastCount = data;
+ if (lastCount == 25) {
+ bcSuspendWindow.postMessage({command: "navigate"});
+ }
+ return;
+ }
+
+ ok(!suspendBlankPageCurrentlyShowing, "correct page is showing");
+ is(resumed, true, "Got message before resumed!");
+ is(lastCount, data - 1, "Missed a message, suspend failed!");
+ finishTest();
+ }
+
+ function errorCallback(data) {
+ if (finished) {
+ return;
+ }
+ ok(false, "testWin had an error: '" + data + "'");
+ finishTest();
+ }
+
+ if (isXOrigin) {
+ // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5)
+ // Acquire storage access permission here so that the BroadcastChannel used to
+ // communicate with the opened windows works in xorigin tests. Otherwise,
+ // the iframe containing this page is isolated from first-party storage access,
+ // which isolates BroadcastChannel communication.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ SpecialPowers.pushPrefEnv({
+ set: [["privacy.partition.always_partition_third_party_non_cookie_storage", false]],
+ }).then(() => {
+ SpecialPowers.pushPermissions([{'type': 'storageAccessAPI', 'allow': 1, 'context': document}], () =>{
+ SpecialPowers.wrap(document).requestStorageAccess().then(() => {
+ runTest();
+ }).then(() => {
+ SpecialPowers.removePermission("3rdPartyStorage^http://mochi.test:8888", "http://mochi.xorigin-test:8888");
+ });
+ });
+ });
+ } else {
+ runTest();
+ }
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_terminate.html b/dom/workers/test/test_terminate.html
new file mode 100644
index 0000000000..c19d65770b
--- /dev/null
+++ b/dom/workers/test/test_terminate.html
@@ -0,0 +1,100 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker terminate feature
+-->
+<head>
+ <title>Test for DOM Worker Navigator</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" language="javascript">
+
+
+ var messageCount = 0;
+ var intervalCount = 0;
+
+ var interval;
+
+ var worker;
+
+ function messageListener(event) {
+ is(event.data, "Still alive!", "Correct message!");
+ if (++messageCount == 20) {
+ ok(worker.onmessage === messageListener,
+ "Correct listener before terminate");
+
+ worker.terminate();
+
+ var exception = false;
+ try {
+ worker.addEventListener("message", messageListener);
+ }
+ catch (e) {
+ exception = true;
+ }
+ is(exception, false, "addEventListener didn't throw after terminate");
+
+ exception = false;
+ try {
+ worker.removeEventListener("message", messageListener);
+ }
+ catch (e) {
+ exception = true;
+ }
+ is(exception, false, "removeEventListener didn't throw after terminate");
+
+ exception = false;
+ try {
+ worker.postMessage("foo");
+ }
+ catch (e) {
+ exception = true;
+ }
+ is(exception, false, "postMessage didn't throw after terminate");
+
+ exception = false;
+ try {
+ worker.terminate();
+ }
+ catch (e) {
+ exception = true;
+ }
+ is(exception, false, "terminate didn't throw after terminate");
+
+ ok(worker.onmessage === messageListener,
+ "Correct listener after terminate");
+
+ worker.onmessage = function(msg) { }
+
+ interval = setInterval(testCount, 1000);
+ }
+ }
+
+ function testCount() {
+ is(messageCount, 20, "Received another message after terminated!");
+ if (intervalCount++ == 5) {
+ clearInterval(interval);
+ SimpleTest.finish();
+ }
+ }
+
+ worker = new Worker("terminate_worker.js");
+ worker.onmessage = messageListener;
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_threadErrors.html b/dom/workers/test/test_threadErrors.html
new file mode 100644
index 0000000000..1eb33244ba
--- /dev/null
+++ b/dom/workers/test/test_threadErrors.html
@@ -0,0 +1,64 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads (Bug 437152)
+-->
+<head>
+ <title>Test for DOM Worker Threads (Bug 437152)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ const expectedErrorCount = 4;
+
+ function messageListener(event) {
+ ok(false, "Unexpected message: " + event.data);
+ SimpleTest.finish();
+ };
+
+ var actualErrorCount = 0;
+ var failedWorkers = [];
+
+ function errorListener(event) {
+ event.preventDefault();
+
+ if (failedWorkers.includes(event.target)) {
+ ok(false, "Seen an extra error from this worker");
+ SimpleTest.finish();
+ return;
+ }
+
+ failedWorkers.push(event.target);
+ actualErrorCount++;
+
+ if (actualErrorCount == expectedErrorCount) {
+ ok(true, "all errors correctly detected");
+ SimpleTest.finish();
+ }
+ };
+
+ for (var i = 1; i <= expectedErrorCount; i++) {
+ var worker = new Worker("threadErrors_worker" + i + ".js");
+ worker.onmessage = messageListener;
+ worker.onerror = errorListener;
+ worker.postMessage("Hi");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_threadTimeouts.html b/dom/workers/test/test_threadTimeouts.html
new file mode 100644
index 0000000000..93a8d9243c
--- /dev/null
+++ b/dom/workers/test/test_threadTimeouts.html
@@ -0,0 +1,60 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads (Bug 437152)
+-->
+<head>
+ <title>Test for DOM Worker Threads (Bug 437152)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("threadTimeouts_worker.js");
+
+ worker.onmessage = function(event) {
+ is(event.target, worker);
+ switch (event.data) {
+ case "timeoutFinished":
+ event.target.postMessage("startInterval");
+ break;
+ case "intervalFinished":
+ event.target.postMessage("cancelInterval");
+ break;
+ case "intervalCanceled":
+ worker.postMessage("startExpression");
+ break;
+ case "expressionFinished":
+ SimpleTest.finish();
+ break;
+ default:
+ ok(false, "Unexpected message");
+ SimpleTest.finish();
+ }
+ };
+
+ worker.onerror = function(event) {
+ is(event.target, worker);
+ ok(false, "Worker had an error: " + event.message);
+ SimpleTest.finish();
+ };
+
+ worker.postMessage("startTimeout");
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_throwingOnerror.html b/dom/workers/test/test_throwingOnerror.html
new file mode 100644
index 0000000000..0ed1f74247
--- /dev/null
+++ b/dom/workers/test/test_throwingOnerror.html
@@ -0,0 +1,54 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads
+-->
+<head>
+ <title>Test for DOM Worker Threads Recursion</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("throwingOnerror_worker.js");
+
+ var errors = ["foo", "bar"];
+
+ worker.onerror = function(event) {
+ event.preventDefault();
+ var found = false;
+ for (var index in errors) {
+ if (event.message == "uncaught exception: " + errors[index]) {
+ errors.splice(index, 1);
+ found = true;
+ break;
+ }
+ }
+ is(found, true, "Unexpected error!");
+ };
+
+ worker.onmessage = function(event) {
+ is(errors.length, 0, "Didn't see expected errors!");
+ SimpleTest.finish();
+ };
+
+ for (var i = 0; i < 2; i++) {
+ worker.postMessage("");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_timeoutTracing.html b/dom/workers/test/test_timeoutTracing.html
new file mode 100644
index 0000000000..8e64564b72
--- /dev/null
+++ b/dom/workers/test/test_timeoutTracing.html
@@ -0,0 +1,47 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads
+-->
+<head>
+ <title>Test for DOM Worker Threads</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("timeoutTracing_worker.js");
+
+ worker.onmessage = function(event) {
+ // begin
+ worker.onmessage = null;
+
+ // 1 second should be enough to crash.
+ window.setTimeout(function() {
+ ok(true, "Didn't crash!");
+ SimpleTest.finish();
+ }, 1000);
+
+ var os = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+ os.notifyObservers(null, "memory-pressure", "heap-minimize");
+ }
+
+ worker.onerror = function(event) {
+ ok(false, "I was expecting a crash, not an error");
+ SimpleTest.finish();
+ };
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_transferable.html b/dom/workers/test/test_transferable.html
new file mode 100644
index 0000000000..ac490f369a
--- /dev/null
+++ b/dom/workers/test/test_transferable.html
@@ -0,0 +1,123 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker transferable objects
+-->
+<head>
+ <title>Test for DOM Worker transferable objects</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" language="javascript">
+
+ function test1(sizes) {
+ if (!sizes.length) {
+ runTests();
+ return;
+ }
+
+ var size = sizes.pop();
+
+ var worker = new Worker("transferable_worker.js");
+ worker.onmessage = function(event) {
+ ok(event.data.status, event.data.event);
+ if (!event.data.status) {
+ runTests();
+ return;
+ }
+
+ if ("notEmpty" in event.data && "byteLength" in event.data.notEmpty) {
+ ok(event.data.notEmpty.byteLength != 0,
+ "P: NotEmpty object received: " + event.data.notEmpty.byteLength);
+ }
+
+ if (!event.data.last)
+ return;
+
+ test1(sizes);
+ }
+ worker.onerror = function(event) {
+ ok(false, "No errors!");
+ }
+
+ try {
+ worker.postMessage(42, true);
+ ok(false, "P: PostMessage - Exception for wrong type");
+ } catch(e) {
+ ok(true, "P: PostMessage - Exception for wrong type");
+ }
+
+ try {
+ ab = new ArrayBuffer(size);
+ worker.postMessage(42,[ab, ab]);
+ ok(false, "P: PostMessage - Exception for duplicate");
+ } catch(e) {
+ ok(true, "P: PostMessage - Exception for duplicate");
+ }
+
+ var ab = new ArrayBuffer(size);
+ ok(ab.byteLength == size, "P: The size is: " + size + " == " + ab.byteLength);
+ worker.postMessage({ data: 0, timeout: 0, ab, cb: ab, size }, [ab]);
+ ok(ab.byteLength == 0, "P: PostMessage - The size is: 0 == " + ab.byteLength)
+ }
+
+ function test2() {
+ var worker = new Worker("transferable_worker.js");
+ worker.onmessage = function(event) {
+ ok(event.data.status, event.data.event);
+ if (!event.data.status) {
+ runTests();
+ return;
+ }
+
+ if ("notEmpty" in event.data && "byteLength" in event.data.notEmpty) {
+ ok(event.data.notEmpty.byteLength != 0,
+ "P: NotEmpty object received: " + event.data.notEmpty.byteLength);
+ }
+
+ if (event.data.last) {
+ runTests();
+ }
+ }
+ worker.onerror = function(event) {
+ ok(false, "No errors!");
+ }
+
+ var f = new Float32Array([0,1,2,3]);
+ ok(f.byteLength != 0, "P: The size is: " + f.byteLength + " is not 0");
+ worker.postMessage({ event: "P: postMessage with Float32Array", status: true,
+ size: 4, notEmpty: f, bc: [ f, f, { dd: f } ] }, [f.buffer]);
+ ok(f.byteLength == 0, "P: The size is: " + f.byteLength + " is 0");
+ }
+
+ var tests = [
+ function() { test1([1024 * 1024 * 32, 128, 4]); },
+ test2
+ ];
+ function runTests() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTests();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_worker_interfaces.html b/dom/workers/test/test_worker_interfaces.html
new file mode 100644
index 0000000000..b051e01242
--- /dev/null
+++ b/dom/workers/test/test_worker_interfaces.html
@@ -0,0 +1,19 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Validate Interfaces Exposed to Workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="worker_driver.js"></script>
+</head>
+<body>
+<script>
+ ok(!self.isSecureContext, "This test should not be running in a secure context");
+</script>
+<script class="testbody" type="text/javascript">
+workerTestExec("test_worker_interfaces.js");
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/test_worker_interfaces.js b/dom/workers/test/test_worker_interfaces.js
new file mode 100644
index 0000000000..d2e42f5922
--- /dev/null
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -0,0 +1,543 @@
+// This is a list of all interfaces that are exposed to workers.
+// Please only add things to this list with great care and proper review
+// from the associated module peers.
+
+// This file lists global interfaces we want exposed and verifies they
+// are what we intend. Each entry in the arrays below can either be a
+// simple string with the interface name, or an object with a 'name'
+// property giving the interface name as a string, and additional
+// properties which qualify the exposure of that interface. For example:
+//
+// [
+// "AGlobalInterface", // secure context only
+// { name: "ExperimentalThing", release: false },
+// { name: "ReallyExperimentalThing", nightly: true },
+// { name: "DesktopOnlyThing", desktop: true },
+// { name: "FancyControl", xbl: true },
+// { name: "DisabledEverywhere", disabled: true },
+// ];
+//
+// See createInterfaceMap() below for a complete list of properties.
+//
+// The values of the properties need to be literal true/false
+// (e.g. indicating whether something is enabled on a particular
+// channel/OS). If we ever end up in a situation where a propert
+// value needs to depend on channel or OS, we will need to make sure
+// we have that information before setting up the property lists.
+
+// IMPORTANT: Do not change this list without review from
+// a JavaScript Engine peer!
+let wasmGlobalEntry = {
+ name: "WebAssembly",
+ insecureContext: true,
+ disabled: !getJSTestingFunctions().wasmIsSupportedByHardware(),
+};
+let wasmGlobalInterfaces = [
+ { name: "Module", insecureContext: true },
+ { name: "Instance", insecureContext: true },
+ { name: "Memory", insecureContext: true },
+ { name: "Table", insecureContext: true },
+ { name: "Global", insecureContext: true },
+ { name: "CompileError", insecureContext: true },
+ { name: "LinkError", insecureContext: true },
+ { name: "RuntimeError", insecureContext: true },
+ { name: "Function", insecureContext: true, nightly: true },
+ { name: "Exception", insecureContext: true },
+ { name: "Tag", insecureContext: true },
+ { name: "compile", insecureContext: true },
+ { name: "compileStreaming", insecureContext: true },
+ { name: "instantiate", insecureContext: true },
+ { name: "instantiateStreaming", insecureContext: true },
+ { name: "validate", insecureContext: true },
+];
+// IMPORTANT: Do not change this list without review from
+// a JavaScript Engine peer!
+let ecmaGlobals = [
+ { name: "AggregateError", insecureContext: true },
+ { name: "Array", insecureContext: true },
+ { name: "ArrayBuffer", insecureContext: true },
+ { name: "Atomics", insecureContext: true },
+ { name: "BigInt", insecureContext: true },
+ { name: "BigInt64Array", insecureContext: true },
+ { name: "BigUint64Array", insecureContext: true },
+ { name: "Boolean", insecureContext: true },
+ { name: "DataView", insecureContext: true },
+ { name: "Date", insecureContext: true },
+ { name: "Error", insecureContext: true },
+ { name: "EvalError", insecureContext: true },
+ { name: "FinalizationRegistry", insecureContext: true },
+ { name: "Float32Array", insecureContext: true },
+ { name: "Float64Array", insecureContext: true },
+ { name: "Function", insecureContext: true },
+ { name: "Infinity", insecureContext: true },
+ { name: "Int16Array", insecureContext: true },
+ { name: "Int32Array", insecureContext: true },
+ { name: "Int8Array", insecureContext: true },
+ { name: "InternalError", insecureContext: true },
+ { name: "Intl", insecureContext: true },
+ { name: "JSON", insecureContext: true },
+ { name: "Map", insecureContext: true },
+ { name: "MediaCapabilities", insecureContext: true },
+ { name: "MediaCapabilitiesInfo", insecureContext: true },
+ { name: "Math", insecureContext: true },
+ { name: "NaN", insecureContext: true },
+ { name: "Number", insecureContext: true },
+ { name: "Object", insecureContext: true },
+ { name: "Promise", insecureContext: true },
+ { name: "Proxy", insecureContext: true },
+ { name: "RangeError", insecureContext: true },
+ { name: "ReferenceError", insecureContext: true },
+ { name: "Reflect", insecureContext: true },
+ { name: "RegExp", insecureContext: true },
+ { name: "Set", insecureContext: true },
+ {
+ name: "SharedArrayBuffer",
+ insecureContext: true,
+ crossOringinIsolated: true,
+ },
+ { name: "String", insecureContext: true },
+ { name: "Symbol", insecureContext: true },
+ { name: "SyntaxError", insecureContext: true },
+ { name: "TypeError", insecureContext: true },
+ { name: "Uint16Array", insecureContext: true },
+ { name: "Uint32Array", insecureContext: true },
+ { name: "Uint8Array", insecureContext: true },
+ { name: "Uint8ClampedArray", insecureContext: true },
+ { name: "URIError", insecureContext: true },
+ { name: "WeakMap", insecureContext: true },
+ { name: "WeakRef", insecureContext: true },
+ { name: "WeakSet", insecureContext: true },
+ wasmGlobalEntry,
+ { name: "decodeURI", insecureContext: true },
+ { name: "decodeURIComponent", insecureContext: true },
+ { name: "encodeURI", insecureContext: true },
+ { name: "encodeURIComponent", insecureContext: true },
+ { name: "escape", insecureContext: true },
+ { name: "eval", insecureContext: true },
+ { name: "globalThis", insecureContext: true },
+ { name: "isFinite", insecureContext: true },
+ { name: "isNaN", insecureContext: true },
+ { name: "parseFloat", insecureContext: true },
+ { name: "parseInt", insecureContext: true },
+ { name: "undefined", insecureContext: true },
+ { name: "unescape", insecureContext: true },
+];
+// IMPORTANT: Do not change the list above without review from
+// a JavaScript Engine peer!
+
+// IMPORTANT: Do not change the list below without review from a DOM peer!
+let interfaceNamesInGlobalScope = [
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "AbortController", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "AbortSignal", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "Blob", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "BroadcastChannel", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ByteLengthQueuingStrategy", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "Cache",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "CacheStorage",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "CanvasGradient", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "CanvasPattern", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "CloseEvent", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "CompressionStream", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "CountQueuingStrategy", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "Crypto", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "CryptoKey" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "CustomEvent", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "DecompressionStream", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "DedicatedWorkerGlobalScope", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "Directory", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "DOMException", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "DOMMatrix", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "DOMMatrixReadOnly", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "DOMPoint", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "DOMPointReadOnly", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "DOMQuad", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "DOMRect", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "DOMRectReadOnly", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "DOMRequest", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "DOMStringList", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ErrorEvent", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "Event", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "EventSource", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "EventTarget", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "File", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "FileList", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "FileReader", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "FileReaderSync", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "FileSystemDirectoryHandle" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "FileSystemFileHandle" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "FileSystemHandle" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "FileSystemSyncAccessHandle" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "FileSystemWritableFileStream" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "FontFace", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "FontFaceSet", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "FontFaceSetLoadEvent", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "FormData", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "Headers", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "IDBCursor", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "IDBCursorWithValue", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "IDBDatabase", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "IDBFactory", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "IDBIndex", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "IDBKeyRange", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "IDBObjectStore", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "IDBOpenDBRequest", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "IDBRequest", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "IDBTransaction", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "IDBVersionChangeEvent", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ImageBitmap", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ImageBitmapRenderingContext", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ImageData", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "Lock",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "LockManager",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "MessageChannel", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "MessageEvent", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "MessagePort", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "NetworkInformation", insecureContext: true, disabled: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "Notification", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "OffscreenCanvas", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "OffscreenCanvasRenderingContext2D", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "Path2D", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "Performance", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PerformanceEntry", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PerformanceMark", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PerformanceMeasure", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PerformanceObserver", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PerformanceObserverEntryList", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PerformanceResourceTiming", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PerformanceServerTiming", insecureContext: false },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ProgressEvent", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PromiseRejectionEvent", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ReadableByteStreamController", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ReadableStream", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ReadableStreamBYOBReader", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ReadableStreamBYOBRequest", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ReadableStreamDefaultController", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ReadableStreamDefaultReader", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "Request", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "Response", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "Scheduler", insecureContext: true, nightly: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "StorageManager", fennec: false },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "SubtleCrypto" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "TaskController", insecureContext: true, nightly: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "TaskPriorityChangeEvent", insecureContext: true, nightly: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "TaskSignal", insecureContext: true, nightly: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "TextDecoder", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "TextDecoderStream", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "TextEncoder", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "TextEncoderStream", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "TextMetrics", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "TransformStream", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ {
+ name: "TransformStreamDefaultController",
+ insecureContext: true,
+ },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "XMLHttpRequest", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "XMLHttpRequestEventTarget", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "XMLHttpRequestUpload", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "URL", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "URLSearchParams", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGL2RenderingContext", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLActiveInfo", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLBuffer", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLContextEvent", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLFramebuffer", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLProgram", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLQuery", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLRenderbuffer", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLRenderingContext", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLSampler", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLShader", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLShaderPrecisionFormat", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLSync", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLTexture", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLTransformFeedback", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLUniformLocation", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLVertexArrayObject", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebSocket", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebTransport", insecureContext: false },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebTransportBidirectionalStream", insecureContext: false },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebTransportDatagramDuplexStream", insecureContext: false },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebTransportError", insecureContext: false },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebTransportReceiveStream", insecureContext: false },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebTransportSendStream", insecureContext: false },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "Worker", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WorkerGlobalScope", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WorkerLocation", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WorkerNavigator", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WritableStream", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WritableStreamDefaultController", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WritableStreamDefaultWriter", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "cancelAnimationFrame", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "close", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "console", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "name", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "onmessage", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "onmessageerror", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "postMessage", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "requestAnimationFrame", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+];
+// IMPORTANT: Do not change the list above without review from a DOM peer!
+
+// List of functions defined on the global by the test harness or this test
+// file.
+let testFunctions = [
+ "ok",
+ "is",
+ "workerTestArrayEquals",
+ "workerTestDone",
+ "workerTestGetPermissions",
+ "workerTestGetHelperData",
+ "entryDisabled",
+ "createInterfaceMap",
+ "runTest",
+];
+
+function entryDisabled(
+ entry,
+ {
+ isNightly,
+ isEarlyBetaOrEarlier,
+ isRelease,
+ isDesktop,
+ isAndroid,
+ isInsecureContext,
+ isFennec,
+ isCrossOringinIsolated,
+ }
+) {
+ return (
+ entry.nightly === !isNightly ||
+ (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) ||
+ entry.desktop === !isDesktop ||
+ (entry.android === !isAndroid && !entry.nightlyAndroid) ||
+ entry.fennecOrDesktop === (isAndroid && !isFennec) ||
+ entry.fennec === !isFennec ||
+ entry.release === !isRelease ||
+ // The insecureContext test is very purposefully converting
+ // entry.insecureContext to boolean, so undefined will convert to
+ // false. That way entries without an insecureContext annotation
+ // will get treated as "insecureContext: false", which means exposed
+ // only in secure contexts.
+ (isInsecureContext && !entry.insecureContext) ||
+ entry.earlyBetaOrEarlier === !isEarlyBetaOrEarlier ||
+ entry.crossOringinIsolated === !isCrossOringinIsolated ||
+ entry.disabled
+ );
+}
+
+function createInterfaceMap(data, ...interfaceGroups) {
+ var interfaceMap = {};
+
+ function addInterfaces(interfaces) {
+ for (var entry of interfaces) {
+ if (typeof entry === "string") {
+ ok(!(entry in interfaceMap), "duplicate entry for " + entry);
+ interfaceMap[entry] = !data.isInsecureContext;
+ } else {
+ ok(!(entry.name in interfaceMap), "duplicate entry for " + entry.name);
+ ok(!("pref" in entry), "Bogus pref annotation for " + entry.name);
+ interfaceMap[entry.name] = !entryDisabled(entry, data);
+ }
+ }
+ }
+
+ for (let interfaceGroup of interfaceGroups) {
+ addInterfaces(interfaceGroup);
+ }
+
+ return interfaceMap;
+}
+
+function runTest(parentName, parent, data, ...interfaceGroups) {
+ var interfaceMap = createInterfaceMap(data, ...interfaceGroups);
+ for (var name of Object.getOwnPropertyNames(parent)) {
+ // Ignore functions on the global that are part of the test (harness).
+ if (parent === self && testFunctions.includes(name)) {
+ continue;
+ }
+ ok(
+ interfaceMap[name],
+ "If this is failing: DANGER, are you sure you want to expose the new interface " +
+ name +
+ " to all webpages as a property of " +
+ parentName +
+ "? Do not make a change to this file without a " +
+ " review from a DOM peer for that specific change!!! (or a JS peer for changes to ecmaGlobals)"
+ );
+ delete interfaceMap[name];
+ }
+ for (var name of Object.keys(interfaceMap)) {
+ ok(
+ name in parent === interfaceMap[name],
+ name +
+ " should " +
+ (interfaceMap[name] ? "" : " NOT") +
+ " be defined on " +
+ parentName
+ );
+ if (!interfaceMap[name]) {
+ delete interfaceMap[name];
+ }
+ }
+ is(
+ Object.keys(interfaceMap).length,
+ 0,
+ "The following interface(s) are not enumerated: " +
+ Object.keys(interfaceMap).join(", ")
+ );
+}
+
+workerTestGetHelperData(function (data) {
+ runTest("self", self, data, ecmaGlobals, interfaceNamesInGlobalScope);
+ if (WebAssembly && !entryDisabled(wasmGlobalEntry, data)) {
+ runTest("WebAssembly", WebAssembly, data, wasmGlobalInterfaces);
+ }
+ workerTestDone();
+});
diff --git a/dom/workers/test/test_worker_interfaces_secureContext.html b/dom/workers/test/test_worker_interfaces_secureContext.html
new file mode 100644
index 0000000000..7d26cd2131
--- /dev/null
+++ b/dom/workers/test/test_worker_interfaces_secureContext.html
@@ -0,0 +1,19 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Validate Interfaces Exposed to Workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="worker_driver.js"></script>
+</head>
+<body>
+<script>
+ ok(self.isSecureContext, "This test should be running in a secure context");
+</script>
+<script class="testbody" type="text/javascript">
+workerTestExec("test_worker_interfaces.js");
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/threadErrors_worker1.js b/dom/workers/test/threadErrors_worker1.js
new file mode 100644
index 0000000000..c0ddade82c
--- /dev/null
+++ b/dom/workers/test/threadErrors_worker1.js
@@ -0,0 +1,8 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+// Syntax error
+onmessage = function(event) {
+ for (var i = 0; i < 10) { }
+}
diff --git a/dom/workers/test/threadErrors_worker2.js b/dom/workers/test/threadErrors_worker2.js
new file mode 100644
index 0000000000..da79569def
--- /dev/null
+++ b/dom/workers/test/threadErrors_worker2.js
@@ -0,0 +1,8 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+// Bad function error
+onmessage = function (event) {
+ foopy();
+};
diff --git a/dom/workers/test/threadErrors_worker3.js b/dom/workers/test/threadErrors_worker3.js
new file mode 100644
index 0000000000..e470680981
--- /dev/null
+++ b/dom/workers/test/threadErrors_worker3.js
@@ -0,0 +1,8 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+// Unhandled exception in body
+onmessage = function (event) {};
+
+throw new Error("Bah!");
diff --git a/dom/workers/test/threadErrors_worker4.js b/dom/workers/test/threadErrors_worker4.js
new file mode 100644
index 0000000000..88b089aa3b
--- /dev/null
+++ b/dom/workers/test/threadErrors_worker4.js
@@ -0,0 +1,8 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+// Throwing message listener
+onmessage = function (event) {
+ throw new Error("Bah!");
+};
diff --git a/dom/workers/test/threadTimeouts_worker.js b/dom/workers/test/threadTimeouts_worker.js
new file mode 100644
index 0000000000..7aaac03d28
--- /dev/null
+++ b/dom/workers/test/threadTimeouts_worker.js
@@ -0,0 +1,44 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+var gTimeoutId;
+var gTimeoutCount = 0;
+var gIntervalCount = 0;
+
+function timeoutFunc() {
+ if (++gTimeoutCount > 1) {
+ throw new Error("Timeout called more than once!");
+ }
+ postMessage("timeoutFinished");
+}
+
+function intervalFunc() {
+ if (++gIntervalCount == 2) {
+ postMessage("intervalFinished");
+ }
+}
+
+function messageListener(event) {
+ switch (event.data) {
+ case "startTimeout":
+ gTimeoutId = setTimeout(timeoutFunc, 2000);
+ clearTimeout(gTimeoutId);
+ gTimeoutId = setTimeout(timeoutFunc, 2000);
+ break;
+ case "startInterval":
+ gTimeoutId = setInterval(intervalFunc, 2000);
+ break;
+ case "cancelInterval":
+ clearInterval(gTimeoutId);
+ postMessage("intervalCanceled");
+ break;
+ case "startExpression":
+ setTimeout("this.postMessage('expressionFinished');", 2000);
+ break;
+ default:
+ throw "Bad message: " + event.data;
+ }
+}
+
+addEventListener("message", messageListener, false);
diff --git a/dom/workers/test/throwingOnerror_worker.js b/dom/workers/test/throwingOnerror_worker.js
new file mode 100644
index 0000000000..47b727f56a
--- /dev/null
+++ b/dom/workers/test/throwingOnerror_worker.js
@@ -0,0 +1,15 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+onerror = function (event) {
+ throw "bar";
+};
+
+var count = 0;
+onmessage = function (event) {
+ if (!count++) {
+ throw "foo";
+ }
+ postMessage("");
+};
diff --git a/dom/workers/test/timeoutTracing_worker.js b/dom/workers/test/timeoutTracing_worker.js
new file mode 100644
index 0000000000..0a0c6d6fdf
--- /dev/null
+++ b/dom/workers/test/timeoutTracing_worker.js
@@ -0,0 +1,15 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+onmessage = function (event) {
+ throw "No messages should reach me!";
+};
+
+setInterval(function () {
+ postMessage("Still alive!");
+}, 20);
+setInterval(";", 20);
+
+postMessage("Begin!");
diff --git a/dom/workers/test/transferable_worker.js b/dom/workers/test/transferable_worker.js
new file mode 100644
index 0000000000..d0fa41cad1
--- /dev/null
+++ b/dom/workers/test/transferable_worker.js
@@ -0,0 +1,40 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+onmessage = function (event) {
+ if ("notEmpty" in event.data && "byteLength" in event.data.notEmpty) {
+ postMessage({
+ event: "W: NotEmpty object received: " + event.data.notEmpty.byteLength,
+ status: event.data.notEmpty.byteLength != 0,
+ last: false,
+ });
+ }
+
+ var ab = new ArrayBuffer(event.data.size);
+ postMessage({
+ event: "W: The size is: " + event.data.size + " == " + ab.byteLength,
+ status: ab.byteLength == event.data.size,
+ last: false,
+ });
+
+ postMessage(
+ {
+ event: "W: postMessage with arrayBuffer",
+ status: true,
+ notEmpty: ab,
+ ab,
+ bc: [ab, ab, { dd: ab }],
+ },
+ [ab]
+ );
+
+ postMessage({
+ event: "W: The size is: 0 == " + ab.byteLength,
+ status: ab.byteLength == 0,
+ last: false,
+ });
+
+ postMessage({ event: "W: last one!", status: true, last: true });
+};
diff --git a/dom/workers/test/window_suspended.html b/dom/workers/test/window_suspended.html
new file mode 100644
index 0000000000..ae5d25df58
--- /dev/null
+++ b/dom/workers/test/window_suspended.html
@@ -0,0 +1,71 @@
+<script>
+const WORKER_URL = "worker_suspended.js";
+var testUrl2 = "window_suspended.html?page2Shown";
+
+let cacheDataPromise = {};
+cacheDataPromise.promise = new Promise(resolve => {
+ cacheDataPromise.resolve = resolve;
+});
+var bcName = location.search.split('?')[1];
+var bc = new BroadcastChannel(bcName);
+if (bcName == "page1Shown") {
+ bc.onmessage = async (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "startWorker") {
+ // Create a worker and subworkers
+ let { worker, promise } = postMessageWorker(msg.workerMessage);
+ promise.then(function() {
+ bc.postMessage({command: "verifyCacheData"});
+ return cacheDataPromise.promise;
+ })
+ .then(function() {
+ location.href = testUrl2;
+ });
+ } else if (command == "changeLocation") {
+ cacheDataPromise.resolve();
+ } else if (command == "finish") {
+ bc.postMessage({command: "finished"});
+ bc.close();
+ window.close();
+ }
+ }
+} else if (bcName == "page2Shown") {
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "startWorker") {
+ let { worker, promise } = postMessageWorker(msg.workerMessage);
+ promise.then(function() {
+ bc.postMessage({command: "verifyCacheData"});
+ return cacheDataPromise.promise;
+ })
+ .then(function() {
+ bc.close();
+ history.back();
+ });
+ } else if (command == "goBack") {
+ cacheDataPromise.resolve();
+ }
+ }
+}
+
+function postMessageWorker(message) {
+ let worker = new window.Worker(WORKER_URL);
+
+ var promise = new Promise((resolve, reject) => {
+ // Waiting until workers are ready
+ worker.addEventListener("message", function onmessage(msg) {
+ bc.postMessage({command: "workerMessage", workerMessage: msg.data});
+ worker.removeEventListener("message", onmessage);
+ resolve();
+ });
+ worker.postMessage(message);
+ });
+ return { worker, promise };
+}
+
+onpageshow = function(e) {
+ bc.postMessage({command: "onpageshow", persisted: e.persisted, location: location.href});
+}
+</script>
diff --git a/dom/workers/test/worker_bug1278777.js b/dom/workers/test/worker_bug1278777.js
new file mode 100644
index 0000000000..f596ee978b
--- /dev/null
+++ b/dom/workers/test/worker_bug1278777.js
@@ -0,0 +1,9 @@
+var xhr = new XMLHttpRequest();
+xhr.responseType = "blob";
+xhr.open("GET", "worker_bug1278777.js");
+
+xhr.onload = function () {
+ postMessage(xhr.response instanceof Blob);
+};
+
+xhr.send();
diff --git a/dom/workers/test/worker_bug1301094.js b/dom/workers/test/worker_bug1301094.js
new file mode 100644
index 0000000000..90fbe178b5
--- /dev/null
+++ b/dom/workers/test/worker_bug1301094.js
@@ -0,0 +1,11 @@
+onmessage = function (e) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", "worker_bug1301094.js", false);
+ xhr.onload = function () {
+ self.postMessage("OK");
+ };
+
+ var fd = new FormData();
+ fd.append("file", e.data);
+ xhr.send(fd);
+};
diff --git a/dom/workers/test/worker_bug1824498.mjs b/dom/workers/test/worker_bug1824498.mjs
new file mode 100644
index 0000000000..932bb530ac
--- /dev/null
+++ b/dom/workers/test/worker_bug1824498.mjs
@@ -0,0 +1,4 @@
+/* eslint-disable import/no-unassigned-import */
+/* eslint-disable import/no-unresolved */
+import {} from "./foo";
+import {} from "./bar";
diff --git a/dom/workers/test/worker_consoleAndBlobs.js b/dom/workers/test/worker_consoleAndBlobs.js
new file mode 100644
index 0000000000..e95a87fb83
--- /dev/null
+++ b/dom/workers/test/worker_consoleAndBlobs.js
@@ -0,0 +1,8 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+var b = new Blob(["123"], { type: "foo/bar" });
+console.log({ msg: "consoleAndBlobs", blob: b });
diff --git a/dom/workers/test/worker_driver.js b/dom/workers/test/worker_driver.js
new file mode 100644
index 0000000000..29a0d50025
--- /dev/null
+++ b/dom/workers/test/worker_driver.js
@@ -0,0 +1,84 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+//
+// Utility script for writing worker tests. In your main document do:
+//
+// <script type="text/javascript" src="worker_driver.js"></script>
+// <script type="text/javascript">
+// workerTestExec('myWorkerTestCase.js')
+// </script>
+//
+// This will then spawn a worker, define some utility functions, and then
+// execute the code in myWorkerTestCase.js. You can then use these
+// functions in your worker-side test:
+//
+// ok() - like the SimpleTest assert
+// is() - like the SimpleTest assert
+// workerTestDone() - like SimpleTest.finish() indicating the test is complete
+//
+// There are also some functions for requesting information that requires
+// SpecialPowers or other main-thread-only resources:
+//
+// workerTestGetVersion() - request the current version string from the MT
+// workerTestGetUserAgent() - request the user agent string from the MT
+// workerTestGetOSCPU() - request the navigator.oscpu string from the MT
+//
+// For an example see test_worker_interfaces.html and test_worker_interfaces.js.
+
+function workerTestExec(script) {
+ SimpleTest.waitForExplicitFinish();
+ var worker = new Worker("worker_wrapper.js");
+ worker.onmessage = function (event) {
+ if (event.data.type == "finish") {
+ SimpleTest.finish();
+ } else if (event.data.type == "status") {
+ ok(event.data.status, event.data.msg);
+ } else if (event.data.type == "getHelperData") {
+ const { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+ );
+ const isNightly = AppConstants.NIGHTLY_BUILD;
+ const isEarlyBetaOrEarlier = AppConstants.EARLY_BETA_OR_EARLIER;
+ const isRelease = AppConstants.RELEASE_OR_BETA;
+ const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
+ const isMac = AppConstants.platform == "macosx";
+ const isWindows = AppConstants.platform == "win";
+ const isAndroid = AppConstants.platform == "android";
+ const isLinux = AppConstants.platform == "linux";
+ const isInsecureContext = !window.isSecureContext;
+ // Currently, MOZ_APP_NAME is always "fennec" for all mobile builds, so we can't use AppConstants for this
+ const isFennec =
+ isAndroid &&
+ SpecialPowers.Cc["@mozilla.org/android/bridge;1"].getService(
+ SpecialPowers.Ci.nsIAndroidBridge
+ ).isFennec;
+ const isCrossOriginIsolated = window.crossOriginIsolated;
+
+ const result = {
+ isNightly,
+ isEarlyBetaOrEarlier,
+ isRelease,
+ isDesktop,
+ isMac,
+ isWindows,
+ isAndroid,
+ isLinux,
+ isInsecureContext,
+ isFennec,
+ isCrossOriginIsolated,
+ };
+
+ worker.postMessage({
+ type: "returnHelperData",
+ result,
+ });
+ }
+ };
+
+ worker.onerror = function (event) {
+ ok(false, "Worker had an error: " + event.data);
+ SimpleTest.finish();
+ };
+
+ worker.postMessage({ script });
+}
diff --git a/dom/workers/test/worker_dynamicImport.mjs b/dom/workers/test/worker_dynamicImport.mjs
new file mode 100644
index 0000000000..b5c50d30f7
--- /dev/null
+++ b/dom/workers/test/worker_dynamicImport.mjs
@@ -0,0 +1,2 @@
+/* eslint-disable import/no-unresolved */
+const { o } = await import("./404.js");
diff --git a/dom/workers/test/worker_referrer.js b/dom/workers/test/worker_referrer.js
new file mode 100644
index 0000000000..ec9fb1f8a0
--- /dev/null
+++ b/dom/workers/test/worker_referrer.js
@@ -0,0 +1,9 @@
+onmessage = function () {
+ importScripts(["referrer.sjs?import"]);
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "referrer.sjs?result", true);
+ xhr.onload = function () {
+ postMessage(xhr.responseText);
+ };
+ xhr.send();
+};
diff --git a/dom/workers/test/worker_setTimeoutWith0.js b/dom/workers/test/worker_setTimeoutWith0.js
new file mode 100644
index 0000000000..2d8e5e6c22
--- /dev/null
+++ b/dom/workers/test/worker_setTimeoutWith0.js
@@ -0,0 +1,3 @@
+var x = 0;
+setTimeout("x++; '\x00'; x++;");
+setTimeout("postMessage(x);");
diff --git a/dom/workers/test/worker_shutdownCheck.js b/dom/workers/test/worker_shutdownCheck.js
new file mode 100644
index 0000000000..f51279daf6
--- /dev/null
+++ b/dom/workers/test/worker_shutdownCheck.js
@@ -0,0 +1 @@
+postMessage("Ok!");
diff --git a/dom/workers/test/worker_suspended.js b/dom/workers/test/worker_suspended.js
new file mode 100644
index 0000000000..f2b4146cba
--- /dev/null
+++ b/dom/workers/test/worker_suspended.js
@@ -0,0 +1,39 @@
+var count = 0;
+
+function do_magic(data) {
+ caches
+ .open("test")
+ .then(function (cache) {
+ return cache.put(
+ "http://mochi.test:888/foo",
+ new Response(data.type + "-" + count++)
+ );
+ })
+ .then(function () {
+ if (count == 1) {
+ postMessage("ready");
+ }
+
+ if (data.loop) {
+ setTimeout(function () {
+ do_magic(data);
+ }, 500);
+ }
+ });
+}
+
+onmessage = function (e) {
+ if (e.data.type == "page1") {
+ if (e.data.count > 0) {
+ var a = new Worker("worker_suspended.js");
+ a.postMessage({ type: "page1", count: e.data - 1 });
+ a.onmessage = function () {
+ postMessage("ready");
+ };
+ } else {
+ do_magic({ type: e.data.type, loop: true });
+ }
+ } else if (e.data.type == "page2") {
+ do_magic({ type: e.data.type, loop: false });
+ }
+};
diff --git a/dom/workers/test/worker_wrapper.js b/dom/workers/test/worker_wrapper.js
new file mode 100644
index 0000000000..6a630d92d0
--- /dev/null
+++ b/dom/workers/test/worker_wrapper.js
@@ -0,0 +1,79 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+//
+// Worker-side wrapper script for the worker_driver.js helper code. See
+// the comments at the top of worker_driver.js for more information.
+
+function ok(a, msg) {
+ dump("OK: " + !!a + " => " + a + ": " + msg + "\n");
+ postMessage({ type: "status", status: !!a, msg: a + ": " + msg });
+}
+
+function is(a, b, msg) {
+ dump("IS: " + (a === b) + " => " + a + " | " + b + ": " + msg + "\n");
+ postMessage({
+ type: "status",
+ status: a === b,
+ msg: a + " === " + b + ": " + msg,
+ });
+}
+
+function workerTestArrayEquals(a, b) {
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length != b.length) {
+ return false;
+ }
+ for (var i = 0, n = a.length; i < n; ++i) {
+ if (a[i] !== b[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function workerTestDone() {
+ postMessage({ type: "finish" });
+}
+
+function workerTestGetPermissions(permissions, cb) {
+ addEventListener("message", function workerTestGetPermissionsCB(e) {
+ if (
+ e.data.type != "returnPermissions" ||
+ !workerTestArrayEquals(permissions, e.data.permissions)
+ ) {
+ return;
+ }
+ removeEventListener("message", workerTestGetPermissionsCB);
+ cb(e.data.result);
+ });
+ postMessage({
+ type: "getPermissions",
+ permissions,
+ });
+}
+
+function workerTestGetHelperData(cb) {
+ addEventListener("message", function workerTestGetHelperDataCB(e) {
+ if (e.data.type !== "returnHelperData") {
+ return;
+ }
+ removeEventListener("message", workerTestGetHelperDataCB);
+ cb(e.data.result);
+ });
+ postMessage({
+ type: "getHelperData",
+ });
+}
+
+addEventListener("message", function workerWrapperOnMessage(e) {
+ removeEventListener("message", workerWrapperOnMessage);
+ var data = e.data;
+ try {
+ importScripts(data.script);
+ } catch (ex) {
+ postMessage({
+ type: "status",
+ status: false,
+ msg: "worker failed to import " + data.script + "; error: " + ex.message,
+ });
+ }
+});
diff --git a/dom/workers/test/xpcshell/data/chrome.manifest b/dom/workers/test/xpcshell/data/chrome.manifest
new file mode 100644
index 0000000000..611e81fd4e
--- /dev/null
+++ b/dom/workers/test/xpcshell/data/chrome.manifest
@@ -0,0 +1 @@
+content workers ./
diff --git a/dom/workers/test/xpcshell/data/worker.js b/dom/workers/test/xpcshell/data/worker.js
new file mode 100644
index 0000000000..0a455f51c3
--- /dev/null
+++ b/dom/workers/test/xpcshell/data/worker.js
@@ -0,0 +1,6 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+self.onmessage = function (msg) {
+ self.postMessage("OK");
+};
diff --git a/dom/workers/test/xpcshell/data/worker_fileReader.js b/dom/workers/test/xpcshell/data/worker_fileReader.js
new file mode 100644
index 0000000000..44e7e6499b
--- /dev/null
+++ b/dom/workers/test/xpcshell/data/worker_fileReader.js
@@ -0,0 +1,7 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+self.onmessage = function (msg) {
+ var fr = new FileReader();
+ self.postMessage("OK");
+};
diff --git a/dom/workers/test/xpcshell/test_ext_redirects_sw_scripts.js b/dom/workers/test/xpcshell/test_ext_redirects_sw_scripts.js
new file mode 100644
index 0000000000..8dbfc5857f
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_ext_redirects_sw_scripts.js
@@ -0,0 +1,415 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+
+const { ExtensionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ExtensionXPCShellUtils.sys.mjs"
+);
+
+const { createHttpServer } = AddonTestUtils;
+
+// Force ServiceWorkerRegistrar to init by calling do_get_profile.
+// (This has to be called before AddonTestUtils.init, because it does
+// also call do_get_profile internally but it doesn't notify
+// profile-after-change).
+do_get_profile(true);
+
+AddonTestUtils.init(this);
+ExtensionTestUtils.init(this);
+
+const server = createHttpServer({ hosts: ["localhost"] });
+
+server.registerPathHandler("/page.html", (request, response) => {
+ info(`/page.html is being requested: ${JSON.stringify(request)}`);
+ response.write(`<!DOCTYPE html>`);
+});
+
+server.registerPathHandler("/sw.js", (request, response) => {
+ info(`/sw.js is being requested: ${JSON.stringify(request)}`);
+ response.setHeader("Content-Type", "application/javascript");
+ response.write(`
+ dump('Executing http://localhost/sw.js\\n');
+ importScripts('sw-imported.js');
+ dump('Executed importScripts from http://localhost/sw.js\\n');
+ `);
+});
+
+server.registerPathHandler("/sw-imported.js", (request, response) => {
+ info(`/sw-imported.js is being requested: ${JSON.stringify(request)}`);
+ response.setHeader("Content-Type", "application/javascript");
+ response.write(`
+ dump('importScript loaded from http://localhost/sw-imported.js\\n');
+ self.onmessage = evt => evt.ports[0].postMessage('original-imported-script');
+ `);
+});
+
+Services.prefs.setBoolPref("dom.serviceWorkers.testing.enabled", true);
+// Make sure this test file doesn't run with the legacy behavior by
+// setting explicitly the expected default value.
+Services.prefs.setBoolPref(
+ "extensions.filterResponseServiceWorkerScript.disabled",
+ false
+);
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("dom.serviceWorkers.testing.enabled");
+ Services.prefs.clearUserPref(
+ "extensions.filterResponseServiceWorkerScript.disabled"
+ );
+});
+
+// Helper function used to be sure to clear any data that a previous test case
+// may have left (e.g. service worker registration, cached service worker
+// scripts).
+//
+// NOTE: Given that xpcshell test are running isolated from each other (unlike
+// mochitests), we can just clear every storage type supported by clear data
+// (instead of cherry picking what we want to clear based on the test cases
+// part of this test file).
+async function ensureDataCleanup() {
+ info("Clear any service worker or data previous test cases may have left");
+ await new Promise(resolve =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve)
+ );
+}
+
+// Note that the update algorithm (https://w3c.github.io/ServiceWorker/#update-algorithm)
+// builds an "updatedResourceMap" as part of its check process. This means that only a
+// single fetch will be performed for "sw-imported.js" as part of the update check and its
+// resulting install invocation. The installation's call to importScripts when evaluated
+// will load the script directly out of the Cache API.
+function testSWUpdate(contentPage) {
+ return contentPage.legacySpawn([], async () => {
+ const oldReg = await this.content.navigator.serviceWorker.ready;
+ const reg = await oldReg.update();
+ const sw = reg.installing || reg.waiting || reg.active;
+ return new Promise(resolve => {
+ const { port1, port2 } = new MessageChannel();
+ port1.onmessage = evt => resolve(evt.data);
+ sw.postMessage("worker-message", [port2]);
+ });
+ });
+}
+
+add_task(async function test_extension_invalid_sw_scripts_redirect_ignored() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["<all_urls>", "webRequest", "webRequestBlocking"],
+ },
+ background() {
+ browser.webRequest.onBeforeRequest.addListener(
+ req => {
+ if (req.url == "http://localhost/sw.js") {
+ const filter = browser.webRequest.filterResponseData(req.requestId);
+ filter.ondata = event => filter.write(event.data);
+ filter.onstop = event => filter.disconnect();
+ filter.onerror = () => {
+ browser.test.sendMessage(
+ "filter-response-error:mainscript",
+ filter.error
+ );
+ };
+ return {
+ redirectUrl: browser.runtime.getURL("sw-unexpected-redirect.js"),
+ };
+ }
+
+ if (req.url == "http://localhost/sw-imported.js") {
+ const filter = browser.webRequest.filterResponseData(req.requestId);
+ filter.ondata = event => filter.write(event.data);
+ filter.onstop = event => filter.disconnect();
+ filter.onerror = () => {
+ browser.test.sendMessage(
+ "filter-response-error:importscript",
+ filter.error
+ );
+ };
+ return { redirectUrl: "about:blank" };
+ }
+
+ return {};
+ },
+ { urls: ["http://localhost/sw.js", "http://localhost/sw-imported.js"] },
+ ["blocking"]
+ );
+ },
+ files: {
+ "sw-unexpected-redirect.js": `
+ dump('importScript redirected to moz-extension://UUID/sw-unexpected-redirect.js\\n');
+ self.onmessage = evt => evt.ports[0].postMessage('sw-unexpected-redirect');
+ `,
+ },
+ });
+
+ // Start the test extension to redirect importScripts requests.
+ await extension.startup();
+
+ function awaitConsoleMessage(regexp) {
+ return new Promise(resolve => {
+ Services.console.registerListener(function listener(message) {
+ if (regexp.test(message.message)) {
+ Services.console.unregisterListener(listener);
+ resolve(message);
+ }
+ });
+ });
+ }
+
+ const awaitIgnoredMainScriptRedirect = awaitConsoleMessage(
+ /Invalid redirectUrl .* on service worker main script/
+ );
+ const awaitIgnoredImportScriptRedirect = awaitConsoleMessage(
+ /Invalid redirectUrl .* on service worker imported script/
+ );
+
+ let contentPage = await ExtensionTestUtils.loadContentPage(
+ "http://localhost/page.html"
+ );
+
+ // Register the worker while the test extension isn't loaded and cannot
+ // intercept and redirect the importedScripts requests.
+ info("Register service worker from a content webpage");
+ let workerMessage = await contentPage.legacySpawn([], async () => {
+ const reg = await this.content.navigator.serviceWorker.register("/sw.js");
+ return new Promise(resolve => {
+ const { port1, port2 } = new MessageChannel();
+ port1.onmessage = evt => resolve(evt.data);
+ const sw = reg.active || reg.waiting || reg.installing;
+ sw.postMessage("worker-message", [port2]);
+ });
+ });
+
+ equal(
+ workerMessage,
+ "original-imported-script",
+ "Got expected worker reply (importScripts not intercepted)"
+ );
+
+ info("Wait for the expected error message on main script redirect");
+ const errorMsg = await awaitIgnoredMainScriptRedirect;
+ ok(errorMsg?.message, `Got error message: ${errorMsg?.message}`);
+ ok(
+ errorMsg?.message?.includes(extension.id),
+ "error message should include the addon id"
+ );
+ ok(
+ errorMsg?.message?.includes("http://localhost/sw.js"),
+ "error message should include the sw main script url"
+ );
+
+ info("Wait for the expected error message on import script redirect");
+ const errorMsg2 = await awaitIgnoredImportScriptRedirect;
+ ok(errorMsg2?.message, `Got error message: ${errorMsg2?.message}`);
+ ok(
+ errorMsg2?.message?.includes(extension.id),
+ "error message should include the addon id"
+ );
+ ok(
+ errorMsg2?.message?.includes("http://localhost/sw-imported.js"),
+ "error message should include the sw main script url"
+ );
+
+ info("Wait filterResponse error on main script");
+ equal(
+ await extension.awaitMessage("filter-response-error:mainscript"),
+ "Invalid request ID",
+ "Got expected error on main script"
+ );
+ info("Wait filterResponse error on import script");
+ equal(
+ await extension.awaitMessage("filter-response-error:importscript"),
+ "Invalid request ID",
+ "Got expected error on import script"
+ );
+
+ await extension.unload();
+ await contentPage.close();
+});
+
+add_task(async function test_filter_sw_script() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: [
+ "<all_urls>",
+ "webRequest",
+ "webRequestBlocking",
+ "webRequestFilterResponse.serviceWorkerScript",
+ ],
+ },
+ background() {
+ browser.webRequest.onBeforeRequest.addListener(
+ req => {
+ if (req.url == "http://localhost/sw.js") {
+ const filter = browser.webRequest.filterResponseData(req.requestId);
+ let decoder = new TextDecoder("utf-8");
+ let encoder = new TextEncoder();
+ filter.ondata = event => {
+ let str = decoder.decode(event.data, { stream: true });
+ browser.test.log(`Got filter ondata event: ${str}\n`);
+ str = `
+ dump('Executing filterResponse script for http://localhost/sw.js\\n');
+ self.onmessage = evt => evt.ports[0].postMessage('filter-response-script');
+ dump('Executed firlterResponse script for http://localhost/sw.js\\n');
+ `;
+ filter.write(encoder.encode(str));
+ filter.disconnect();
+ };
+ }
+
+ return {};
+ },
+ { urls: ["http://localhost/sw.js", "http://localhost/sw-imported.js"] },
+ ["blocking"]
+ );
+ },
+ });
+
+ // Start the test extension to redirect importScripts requests.
+ await extension.startup();
+
+ await ensureDataCleanup();
+ let contentPage = await ExtensionTestUtils.loadContentPage(
+ "http://localhost/page.html"
+ );
+
+ let workerMessage = await contentPage.legacySpawn([], async () => {
+ const reg = await this.content.navigator.serviceWorker.register("/sw.js");
+ return new Promise(resolve => {
+ const { port1, port2 } = new MessageChannel();
+ port1.onmessage = evt => resolve(evt.data);
+ const sw = reg.active || reg.waiting || reg.installing;
+ sw.postMessage("worker-message", [port2]);
+ });
+ });
+
+ equal(
+ workerMessage,
+ "filter-response-script",
+ "Got expected worker reply (filterResponse script)"
+ );
+
+ await extension.unload();
+ workerMessage = await testSWUpdate(contentPage);
+ equal(
+ workerMessage,
+ "original-imported-script",
+ "Got expected worker reply (original script)"
+ );
+
+ await contentPage.close();
+});
+
+add_task(async function test_extension_redirect_sw_imported_script() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["<all_urls>", "webRequest", "webRequestBlocking"],
+ web_accessible_resources: ["sw-imported-1.js", "sw-imported-2.js"],
+ },
+ background() {
+ let i = 1;
+ browser.webRequest.onBeforeRequest.addListener(
+ req => {
+ browser.test.log(
+ "Extension is redirecting http://localhost/sw-imported.js"
+ );
+ browser.test.sendMessage("request-redirected");
+ return {
+ redirectUrl: browser.runtime.getURL(`sw-imported-${i++}.js`),
+ };
+ },
+ { urls: ["http://localhost/sw-imported.js"] },
+ ["blocking"]
+ );
+ },
+ files: {
+ "sw-imported-1.js": `
+ dump('importScript redirected to moz-extension://UUID/sw-imported1.js \\n');
+ self.onmessage = evt => evt.ports[0].postMessage('redirected-imported-script-1');
+ `,
+ "sw-imported-2.js": `
+ dump('importScript redirected to moz-extension://UUID/sw-imported2.js \\n');
+ self.onmessage = evt => evt.ports[0].postMessage('redirected-imported-script-2');
+ `,
+ },
+ });
+
+ await ensureDataCleanup();
+ const contentPage = await ExtensionTestUtils.loadContentPage(
+ "http://localhost/page.html"
+ );
+
+ // Register the worker while the test extension isn't loaded and cannot
+ // intercept and redirect the importedScripts requests.
+ let workerMessage = await contentPage.legacySpawn([], async () => {
+ const reg = await this.content.navigator.serviceWorker.register("/sw.js");
+ return new Promise(resolve => {
+ const { port1, port2 } = new MessageChannel();
+ port1.onmessage = evt => resolve(evt.data);
+ const sw = reg.active || reg.waiting || reg.installing;
+ sw.postMessage("worker-message", [port2]);
+ });
+ });
+
+ equal(
+ workerMessage,
+ "original-imported-script",
+ "Got expected worker reply (importScripts not intercepted)"
+ );
+
+ // Start the test extension to redirect importScripts requests.
+ await extension.startup();
+
+ // Trigger an update on the registered service worker, then assert that the
+ // reply got is coming from the script where the extension is redirecting the
+ // request.
+ info("Update service worker and expect extension script to reply");
+ workerMessage = await testSWUpdate(contentPage);
+ await extension.awaitMessage("request-redirected");
+ equal(
+ workerMessage,
+ "redirected-imported-script-1",
+ "Got expected worker reply (importScripts redirected to moz-extension url)"
+ );
+
+ // Trigger a new update of the registered service worker, then assert that the
+ // reply got is coming from a different script where the extension is
+ // redirecting the second request (this confirms that the extension can
+ // intercept and can change the redirected imported scripts on new service
+ // worker updates).
+ info("Update service worker and expect new extension script to reply");
+ workerMessage = await testSWUpdate(contentPage);
+ await extension.awaitMessage("request-redirected");
+ equal(
+ workerMessage,
+ "redirected-imported-script-2",
+ "Got expected worker reply (importScripts redirected to moz-extension url again)"
+ );
+
+ // Uninstall the extension and trigger one more update of the registered
+ // service worker, then assert that the reply got is the one coming from the
+ // server (because difference from the one got from the cache).
+ // This verify that the service worker are updated as expected after the
+ // extension is uninstalled and the worker is not stuck on the script where
+ // the extension did redirect the request the last time.
+ info(
+ "Unload extension, update service worker and expect original script to reply"
+ );
+ await extension.unload();
+ workerMessage = await testSWUpdate(contentPage);
+ equal(
+ workerMessage,
+ "original-imported-script",
+ "Got expected worker reply (importScripts not intercepted)"
+ );
+
+ await contentPage.close();
+});
diff --git a/dom/workers/test/xpcshell/test_fileReader.js b/dom/workers/test/xpcshell/test_fileReader.js
new file mode 100644
index 0000000000..02bc3fa667
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_fileReader.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Worker must be loaded from a chrome:// uri, not a file://
+// uri, so we first need to load it.
+var WORKER_SOURCE_URI = "chrome://workers/content/worker_fileReader.js";
+do_load_manifest("data/chrome.manifest");
+
+function talk_with_worker(worker) {
+ return new Promise((resolve, reject) => {
+ worker.onmessage = function (event) {
+ let success = true;
+ if (event.data == "OK") {
+ resolve();
+ } else {
+ success = false;
+ reject(event);
+ }
+ Assert.ok(success);
+ worker.terminate();
+ };
+ worker.onerror = function (event) {
+ let error = new Error(event.message, event.filename, event.lineno);
+ worker.terminate();
+ reject(error);
+ };
+ worker.postMessage("START");
+ });
+}
+
+add_task(function test_chrome_worker() {
+ return talk_with_worker(new ChromeWorker(WORKER_SOURCE_URI));
+});
diff --git a/dom/workers/test/xpcshell/test_remoteworker_launch_new_process.js b/dom/workers/test/xpcshell/test_remoteworker_launch_new_process.js
new file mode 100644
index 0000000000..e13b0fc96c
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_remoteworker_launch_new_process.js
@@ -0,0 +1,94 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+const { createHttpServer } = AddonTestUtils;
+
+// Force ServiceWorkerRegistrar to init by calling do_get_profile.
+// (This has to be called before AddonTestUtils.init, because it does
+// also call do_get_profile internally but it doesn't notify
+// profile-after-change).
+do_get_profile(true);
+
+AddonTestUtils.init(this);
+
+const server = createHttpServer({ hosts: ["localhost"] });
+
+server.registerPathHandler("/sw.js", (request, response) => {
+ info(`/sw.js is being requested: ${JSON.stringify(request)}`);
+ response.setHeader("Content-Type", "application/javascript");
+ response.write("");
+});
+
+add_task(async function setup_prefs() {
+ equal(
+ Services.prefs.getBoolPref("browser.tabs.remote.autostart"),
+ true,
+ "e10s is expected to be enabled"
+ );
+
+ // Enable nsIServiceWorkerManager.registerForTest.
+ Services.prefs.setBoolPref("dom.serviceWorkers.testing.enabled", true);
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("dom.serviceWorkers.testing.enabled");
+ });
+});
+
+/**
+ * This test installs a ServiceWorker via test API and verify that the install
+ * process spawns a new process. (Normally ServiceWorker installation won't
+ * cause a new content process to be spawned because the call to register must
+ * be coming from within an existing content process, but the registerForTest
+ * API allows us to bypass this restriction.)
+ *
+ * This models the real-world situation of a push notification being received
+ * from the network which results in a ServiceWorker being spawned without their
+ * necessarily being an existing content process to host it (especially under Fission).
+ */
+add_task(async function launch_remoteworkers_in_new_processes() {
+ const swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+ Ci.nsIServiceWorkerManager
+ );
+
+ const ssm = Services.scriptSecurityManager;
+
+ const initialChildCount = Services.ppmm.childCount;
+
+ // A test service worker that should spawn a regular web content child process.
+ const swRegInfoWeb = await swm.registerForTest(
+ ssm.createContentPrincipal(Services.io.newURI("http://localhost"), {}),
+ "http://localhost/scope",
+ "http://localhost/sw.js"
+ );
+ swRegInfoWeb.QueryInterface(Ci.nsIServiceWorkerRegistrationInfo);
+
+ info(
+ `web content service worker registered: ${JSON.stringify({
+ principal: swRegInfoWeb.principal.spec,
+ scope: swRegInfoWeb.scope,
+ })}`
+ );
+
+ info("Wait new process to be launched");
+ await TestUtils.waitForCondition(() => {
+ return Services.ppmm.childCount - initialChildCount >= 1;
+ }, "wait for a new child processes to be started");
+
+ // Wait both workers to become active to be sure that. besides spawning
+ // the new child processes as expected, the two remote worker have been
+ // able to run successfully (in other word their remote worker data did
+ // pass successfull the IsRemoteTypeAllowed check in RemoteworkerChild).
+ info("Wait for webcontent worker to become active");
+ await TestUtils.waitForCondition(
+ () => swRegInfoWeb.activeWorker,
+ `wait workers for scope ${swRegInfoWeb.scope} to be active`
+ );
+});
diff --git a/dom/workers/test/xpcshell/test_workers.js b/dom/workers/test/xpcshell/test_workers.js
new file mode 100644
index 0000000000..5b768f69bb
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_workers.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Worker must be loaded from a chrome:// uri, not a file://
+// uri, so we first need to load it.
+var WORKER_SOURCE_URI = "chrome://workers/content/worker.js";
+do_load_manifest("data/chrome.manifest");
+
+function talk_with_worker(worker) {
+ return new Promise((resolve, reject) => {
+ worker.onmessage = function (event) {
+ let success = true;
+ if (event.data == "OK") {
+ resolve();
+ } else {
+ success = false;
+ reject(event);
+ }
+ Assert.ok(success);
+ worker.terminate();
+ };
+ worker.onerror = function (event) {
+ let error = new Error(event.message, event.filename, event.lineno);
+ worker.terminate();
+ reject(error);
+ };
+ worker.postMessage("START");
+ });
+}
+
+add_task(function test_chrome_worker() {
+ return talk_with_worker(new ChromeWorker(WORKER_SOURCE_URI));
+});
+
+add_task(function test_worker() {
+ return talk_with_worker(new Worker(WORKER_SOURCE_URI));
+});
diff --git a/dom/workers/test/xpcshell/test_workers_clone_error.js b/dom/workers/test/xpcshell/test_workers_clone_error.js
new file mode 100644
index 0000000000..c69f9de3fd
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_workers_clone_error.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Worker must be loaded from a chrome:// uri, not a file://
+// uri, so we first need to load it.
+var WORKER_SOURCE_URI = "chrome://workers/content/worker.js";
+do_load_manifest("data/chrome.manifest");
+
+function talk_with_worker(worker) {
+ return new Promise((resolve, reject) => {
+ worker.onmessage = function (event) {
+ let success = true;
+ if (event.data == "OK") {
+ resolve();
+ } else {
+ success = false;
+ reject(event);
+ }
+ Assert.ok(success);
+ worker.terminate();
+ };
+ worker.onerror = function (event) {
+ let error = new Error(event.message, event.filename, event.lineno);
+ worker.terminate();
+ reject(error);
+ };
+
+ try {
+ eval("/");
+ } catch (e) {
+ worker.postMessage(new ClonedErrorHolder(e));
+ }
+ });
+}
+
+add_task(function test_chrome_worker() {
+ return talk_with_worker(new ChromeWorker(WORKER_SOURCE_URI));
+});
+
+add_task(function test_worker() {
+ return talk_with_worker(new Worker(WORKER_SOURCE_URI));
+});
diff --git a/dom/workers/test/xpcshell/xpcshell.ini b/dom/workers/test/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..7b24396197
--- /dev/null
+++ b/dom/workers/test/xpcshell/xpcshell.ini
@@ -0,0 +1,28 @@
+[DEFAULT]
+head =
+skip-if = toolkit == 'android'
+support-files =
+ data/worker.js
+ data/worker_fileReader.js
+ data/chrome.manifest
+
+[test_ext_redirects_sw_scripts.js]
+# The following firefox-appdir make sure that ExtensionTestUtils.loadExtension
+# will be able to successfully start the background page (it does fail without
+# it because there wouldn't be a global.tabTracker implementation as we would
+# expect in a real Firefox, Fenix or Thunderbird instance).
+firefox-appdir = browser
+[test_workers.js]
+[test_workers_clone_error.js]
+[test_fileReader.js]
+[test_remoteworker_launch_new_process.js]
+# The following firefox-appdir make sure that this xpcshell test will run
+# with e10s enabled (which is needed to make sure that the test case is
+# going to launch the expected new processes)
+firefox-appdir = browser
+# Disable plugin loading to make it rr able to record and replay this test.
+prefs =
+ plugin.disable=true
+skip-if =
+ socketprocess_networking # Bug 1759035
+