summaryrefslogtreecommitdiffstats
path: root/dom/workers/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/workers/test
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/workers/test')
-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.js7
-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.js10
-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.js16
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js29
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js29
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.reportError_childWorker.js5
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.reportError_debugger.js11
-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.js26
-rw-r--r--dom/workers/test/WorkerDebugger_sharedWorker.js16
-rw-r--r--dom/workers/test/WorkerDebugger_suspended_debugger.js6
-rw-r--r--dom/workers/test/WorkerDebugger_suspended_worker.js7
-rw-r--r--dom/workers/test/WorkerDebugger_worker.js9
-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.toml53
-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.js95
-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.js180
-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.js7
-rw-r--r--dom/workers/test/chrome.toml120
-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.js113
-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.js26
-rw-r--r--dom/workers/test/csp_worker.js^headers^1
-rw-r--r--dom/workers/test/dom_worker_helper.js157
-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.js14
-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.html56
-rw-r--r--dom/workers/test/errorPropagation_worker.js51
-rw-r--r--dom/workers/test/errorwarning_worker.js46
-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.js62
-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.toml5
-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.toml365
-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.js73
-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.js87
-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.js47
-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.sjs97
-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.js43
-rw-r--r--dom/workers/test/server_fetch_synthetic.sjs50
-rw-r--r--dom/workers/test/sharedWorker_console.js12
-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.js100
-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.xhtml99
-rw-r--r--dom/workers/test/test_WorkerDebugger_console.xhtml98
-rw-r--r--dom/workers/test/test_WorkerDebugger_frozen.xhtml72
-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.html69
-rw-r--r--dom/workers/test/test_bug1062920.xhtml64
-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.html93
-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_1.html35
-rw-r--r--dom/workers/test/test_importScripts_2.html34
-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.js65
-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.html39
-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.html104
-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.html144
-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.js563
-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.js45
-rw-r--r--dom/workers/test/throwingOnerror_worker.js15
-rw-r--r--dom/workers/test/timeoutTracing_worker.js16
-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.js4
-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/base_uri_module.mjs23
-rw-r--r--dom/workers/test/xpcshell/data/base_uri_module2.mjs1
-rw-r--r--dom/workers/test/xpcshell/data/base_uri_worker.js27
-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.js558
-rw-r--r--dom/workers/test/xpcshell/test_ext_worker_offline_fetch.js112
-rw-r--r--dom/workers/test/xpcshell/test_fileReader.js33
-rw-r--r--dom/workers/test/xpcshell/test_import_base_uri.js35
-rw-r--r--dom/workers/test/xpcshell/test_remoteworker_launch_new_process.js88
-rw-r--r--dom/workers/test/xpcshell/test_workers.js37
-rw-r--r--dom/workers/test/xpcshell/test_workers_clone_error.js43
-rw-r--r--dom/workers/test/xpcshell/xpcshell.toml38
339 files changed, 16609 insertions, 0 deletions
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..a3a6d2bf80
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.initialize_childWorker.js
@@ -0,0 +1,7 @@
+"use strict";
+
+self.onmessage = function () {};
+
+// eslint-disable-next-line no-debugger
+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..2317335edc
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.initialize_worker.js
@@ -0,0 +1,10 @@
+"use strict";
+
+var worker = new Worker("WorkerDebugger.initialize_childWorker.js");
+worker.onmessage = function (event) {
+ postMessage("child:" + event.data);
+};
+
+// eslint-disable-next-line no-debugger
+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..495212066c
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js
@@ -0,0 +1,16 @@
+"use strict";
+
+function f() {
+ // eslint-disable-next-line no-debugger
+ debugger;
+}
+
+self.onmessage = function (event) {
+ switch (event.data) {
+ case "ping":
+ // eslint-disable-next-line no-debugger
+ 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..7b30109615
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js
@@ -0,0 +1,29 @@
+"use strict";
+
+function f() {
+ // eslint-disable-next-line no-debugger
+ 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":
+ // eslint-disable-next-line no-debugger
+ 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..b3d8e5d440
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_debugger.js
@@ -0,0 +1,11 @@
+"use strict";
+
+this.onmessage = function (event) {
+ switch (event.data) {
+ case "report":
+ reportError("reported");
+ break;
+ case "throw":
+ throw new Error("thrown");
+ }
+};
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..04db24b512
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_promise_worker.js
@@ -0,0 +1,26 @@
+"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");
+ });
+ // eslint-disable-next-line no-debugger
+ 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..037abe6d6d
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_sharedWorker.js
@@ -0,0 +1,16 @@
+"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) {}
+ }
+ };
+};
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..0ea27d29e8
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_suspended_worker.js
@@ -0,0 +1,7 @@
+"use strict";
+
+self.onmessage = function () {
+ postMessage("worker");
+ // eslint-disable-next-line no-debugger
+ debugger;
+};
diff --git a/dom/workers/test/WorkerDebugger_worker.js b/dom/workers/test/WorkerDebugger_worker.js
new file mode 100644
index 0000000000..e33eeaa4d3
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_worker.js
@@ -0,0 +1,9 @@
+"use strict";
+
+var worker = new Worker("WorkerDebugger_childWorker.js");
+self.onmessage = function (event) {
+ postMessage("child:" + event.data);
+};
+// eslint-disable-next-line no-debugger
+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.toml b/dom/workers/test/browser.toml
new file mode 100644
index 0000000000..fb8148a159
--- /dev/null
+++ b/dom/workers/test/browser.toml
@@ -0,0 +1,53 @@
+[DEFAULT]
+support-files = [
+ "bug1047663_tab.html",
+ "bug1047663_worker.sjs",
+ "head.js",
+ "!/dom/base/test/file_empty.html",
+]
+
+["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)
+
+["browser_bug1047663.js"]
+
+["browser_bug1104623.js"]
+run-if = ["buildapp == 'browser'"]
+
+["browser_consoleSharedWorkers.js"]
+skip-if = ["release_or_beta"] # requires dom.postMessage.sharedArrayBuffer.bypassCOOP_COEP.insecure.enabled
+support-files = [
+ "sharedWorker_console.js",
+ "empty.html",
+]
+
+["browser_fileURL.js"]
+support-files = [
+ "empty.html",
+ "empty_worker.js",
+]
+
+["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_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",
+]
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..54c1d8a73f
--- /dev/null
+++ b/dom/workers/test/browser_consoleSharedWorkers.js
@@ -0,0 +1,95 @@
+/* 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() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "dom.postMessage.sharedArrayBuffer.bypassCOOP_COEP.insecure.enabled",
+ true,
+ ],
+ ],
+ });
+
+ 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;
+ if (order == 1) {
+ 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 first log message.");
+ } else {
+ is(
+ obj.arguments[0],
+ "Here is a SAB",
+ "A message from a SharedWorker \\o/"
+ );
+ is(
+ obj.arguments[1].constructor.name,
+ "SharedArrayBuffer",
+ "We got a direct reference to the SharedArrayBuffer coming from the worker thread"
+ );
+ is(obj.ID, "sharedWorker_console.js", "The ID is SharedWorker");
+ is(obj.innerID, "SharedWorker", "The ID is SharedWorker");
+ is(order++, 2, "Then a second 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..ae7d71c222
--- /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
+ );
+ Assert.greater(
+ telemetrySums.SERVICE_WORKER_RUNNING_All,
+ initialSums.SERVICE_WORKER_RUNNING_All,
+ "ServiceWorker running count changed"
+ );
+ Assert.greater(
+ 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..d651da31d8
--- /dev/null
+++ b/dom/workers/test/browser_worker_use_counters.js
@@ -0,0 +1,180 @@
+/* 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 unscream(s) {
+ // Takes SCREAMINGCASE `s` and returns "Screamingcase".
+ return s.charAt(0) + s.slice(1).toLowerCase();
+}
+
+function screamToCamel(s) {
+ // Takes SCREAMING_CASE `s` and returns "screamingCase".
+ const pascal = s.split("_").map(unscream).join("");
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
+}
+
+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 instrumentation we're
+ // interested in.
+ await Services.fog.testFlushAllChildren();
+ let glean_before =
+ Glean[`useCounterWorker${unscream(worker_type)}`][
+ screamToCamel(use_counter_name)
+ ].testGetValue();
+ let glean_destructions_before =
+ Glean.useCounter[
+ `${worker_type.toLowerCase()}WorkersDestroyed`
+ ].testGetValue();
+
+ BrowserTestUtils.startLoadingURIString(
+ 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 data again and compare.
+ // We'd like for this to be synchronous, but use counters are reported on
+ // worker destruction which we don't directly observe.
+ // So we check in a quick loop.
+ await BrowserTestUtils.waitForCondition(async () => {
+ await Services.fog.testFlushAllChildren();
+ return (
+ glean_before !=
+ Glean[`useCounterWorker${unscream(worker_type)}`][
+ screamToCamel(use_counter_name)
+ ].testGetValue()
+ );
+ });
+ let glean_after =
+ Glean[`useCounterWorker${unscream(worker_type)}`][
+ screamToCamel(use_counter_name)
+ ].testGetValue();
+ let glean_destructions_after =
+ Glean.useCounter[
+ `${worker_type.toLowerCase()}WorkersDestroyed`
+ ].testGetValue();
+
+ is(
+ glean_after,
+ glean_before + 1,
+ `Glean counter ${use_counter_name} for ${worker_type} worker is correct.`
+ );
+ // There might be other workers created by prior tests get destroyed during
+ // this tests.
+ Assert.greater(
+ glean_destructions_after,
+ glean_destructions_before ?? 0,
+ `Glean ${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..6f6b6a9212
--- /dev/null
+++ b/dom/workers/test/bug998474_worker.js
@@ -0,0 +1,7 @@
+self.addEventListener("connect", function (e) {
+ var port = e.ports[0];
+ port.onmessage = function (msg) {
+ // eslint-disable-next-line no-eval
+ port.postMessage(eval(msg.data));
+ };
+});
diff --git a/dom/workers/test/chrome.toml b/dom/workers/test/chrome.toml
new file mode 100644
index 0000000000..0b2d68da39
--- /dev/null
+++ b/dom/workers/test/chrome.toml
@@ -0,0 +1,120 @@
+[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"]
+
+["test_WorkerDebuggerGlobalScope.createSandbox.xhtml"]
+
+["test_WorkerDebuggerGlobalScope.enterEventLoop.xhtml"]
+
+["test_WorkerDebuggerGlobalScope.reportError.xhtml"]
+
+["test_WorkerDebuggerGlobalScope.setImmediate.xhtml"]
+
+["test_WorkerDebuggerManager.xhtml"]
+
+["test_WorkerDebugger_console.xhtml"]
+
+["test_WorkerDebugger_frozen.xhtml"]
+
+["test_WorkerDebugger_promise.xhtml"]
+
+["test_WorkerDebugger_suspended.xhtml"]
+
+["test_bug1062920.xhtml"]
+
+["test_chromeWorker.xhtml"]
+
+["test_chromeWorkerJSM.xhtml"]
+
+["test_file.xhtml"]
+skip-if = [
+ "os == 'linux' && bits == 64 && debug", # Bug 1765445
+ "apple_catalina && !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_readableStream_when_closing.html"]
+
+["test_sharedWorker_privateBrowsing.html"]
+
+["test_shutdownCheck.xhtml"]
+support-files = ["worker_shutdownCheck.js"]
+
+["test_sourcemap_header.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..811dc12bae
--- /dev/null
+++ b/dom/workers/test/console_worker.js
@@ -0,0 +1,113 @@
+/**
+ * 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",
+ // eslint-disable-next-line no-self-compare
+ 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..04afa4bda1
--- /dev/null
+++ b/dom/workers/test/csp_worker.js
@@ -0,0 +1,26 @@
+/**
+ * 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 {
+ // eslint-disable-next-line no-eval
+ 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..e61b4793f2
--- /dev/null
+++ b/dom/workers/test/dom_worker_helper.js
@@ -0,0 +1,157 @@
+/**
+ * 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) {
+ // eslint-disable-next-line no-self-compare
+ 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) {
+ // There used to be old logic which expects promises to be resolved in
+ // succession, but where it seems like this was an incorrect assumption.
+ // Assuming this change sticks, bug 1861778 tracks removing this method
+ // entirely in favor of Promise.all at the call-sites or transform the callers
+ // into explicitly documented awaited sequences.
+ return Promise.all(promises);
+}
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..90bd422efe
--- /dev/null
+++ b/dom/workers/test/dynamicImport_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":
+ import("./dynamicImport_nested.mjs").then(m => postMessage(m.message));
+ break;
+ default:
+ throw new Error("Bad message: " + event.data);
+ }
+};
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..cbaed1778f
--- /dev/null
+++ b/dom/workers/test/errorPropagation_iframe.html
@@ -0,0 +1,56 @@
+<!--
+ 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;
+ }
+ return undefined;
+ };
+
+ 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..679181e83a
--- /dev/null
+++ b/dom/workers/test/errorPropagation_worker.js
@@ -0,0 +1,51 @@
+/**
+ * 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;
+ }
+ return undefined;
+};
+
+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..39e71f8414
--- /dev/null
+++ b/dom/workers/test/errorwarning_worker.js
@@ -0,0 +1,46 @@
+/**
+ * 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;
+// eslint-disable-next-line no-self-assign
+onerror = onerror;
+// eslint-disable-next-line no-self-compare
+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..ca46c949b9
--- /dev/null
+++ b/dom/workers/test/importScripts_worker.js
@@ -0,0 +1,62 @@
+/**
+ * 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);
+ }
+};
+
+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.toml b/dom/workers/test/marionette/manifest.toml
new file mode 100644
index 0000000000..b801ae5719
--- /dev/null
+++ b/dom/workers/test/marionette/manifest.toml
@@ -0,0 +1,5 @@
+[DEFAULT]
+
+["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.toml b/dom/workers/test/mochitest.toml
new file mode 100644
index 0000000000..5ae8094b58
--- /dev/null
+++ b/dom/workers/test/mochitest.toml
@@ -0,0 +1,365 @@
+[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",
+ "http2",
+]
+
+["test_bug1104064.html"]
+
+["test_bug1132395.html"]
+skip-if = ["true"] # bug 1176225
+
+["test_bug1132924.html"]
+
+["test_bug1278777.html"]
+
+["test_bug1301094.html"]
+
+["test_bug1317725.html"]
+support-files = ["test_bug1317725.js"]
+
+["test_bug1824498.html"]
+support-files = ["worker_bug1824498.mjs"]
+
+["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_and_terminate.html"]
+support-files = ["worker_dynamicImport.mjs"]
+
+["test_dynamicImport_early_termination.html"]
+
+["test_errorPropagation.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_errorwarning.html"]
+
+["test_eventDispatch.html"]
+
+["test_fibonacci.html"]
+
+["test_fileReaderSync_when_closing.html"]
+
+["test_importScripts.html"]
+
+["test_importScripts_1.html"]
+
+["test_importScripts_2.html"]
+
+["test_importScripts_3rdparty.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_importScripts_mixedcontent.html"]
+tags = "mcb"
+
+["test_instanceof.html"]
+
+["test_json.html"]
+
+["test_loadEncoding.html"]
+
+["test_loadError.html"]
+
+["test_location.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_longThread.html"]
+
+["test_multi_sharedWorker.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_multi_sharedWorker_lifetimes_bfcache.html"]
+
+["test_multi_sharedWorker_lifetimes_nobfcache.html"]
+
+["test_navigator.html"]
+support-files = [
+ "test_navigator.js",
+ "test_navigator_iframe.html",
+ "test_navigator_iframe.js",
+]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_navigator_languages.html"]
+
+["test_navigator_secureContext.html"]
+scheme = "https"
+support-files = [
+ "test_navigator.js",
+ "test_navigator_iframe.html",
+ "test_navigator_iframe.js",
+]
+
+["test_navigator_workers_hardwareConcurrency.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",
+ "http2",
+]
+
+["test_referrer.html"]
+
+["test_referrer_header_worker.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_resolveWorker-assignment.html"]
+
+["test_resolveWorker.html"]
+
+["test_rvals.html"]
+
+["test_setTimeoutWith0.html"]
+
+["test_sharedWorker.html"]
+
+["test_sharedWorker_lifetime.html"]
+
+["test_sharedWorker_ports.html"]
+
+["test_sharedWorker_thirdparty.html"]
+support-files = [
+ "sharedWorker_thirdparty_frame.html",
+ "sharedWorker_thirdparty_window.html",
+]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_sharedworker_event_listener_leaks.html"]
+skip-if = [
+ "bits == 64 && os == 'linux' && asan", # Disabled on Linux64 opt asan, bug 1493563
+ "os == 'win' && debug && xorigin", # high frequency intermittent
+]
+
+["test_simpleThread.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_subworkers_suspended.html"]
+scheme = "https"
+
+["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",
+ "http2",
+]
+
+["test_worker_interfaces_secureContext.html"]
+scheme = "https"
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..3c3e4c5780
--- /dev/null
+++ b/dom/workers/test/multi_sharedWorker_sharedWorker.js
@@ -0,0 +1,73 @@
+/**
+ * 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;
+ }
+ return undefined;
+};
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..a9cd4f29d5
--- /dev/null
+++ b/dom/workers/test/navigator_worker.js
@@ -0,0 +1,87 @@
+/**
+ * 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",
+ "globalPrivacyControl",
+ "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.isNightly === !channelData.isNightly ||
+ prop.release === !channelData.isRelease ||
+ prop.isSecureContext === !isSecureContext ||
+ prop.isAndroid === !channelData.isAndroid
+ ) {
+ 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..84eb6d9740
--- /dev/null
+++ b/dom/workers/test/recursion_worker.js
@@ -0,0 +1,47 @@
+/**
+ * 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;
+ }
+
+ // eslint-disable-next-line no-unreachable
+ 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..80bd2bee54
--- /dev/null
+++ b/dom/workers/test/referrer_test_server.sjs
@@ -0,0 +1,97 @@
+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'");
+ }
+}
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..09f7be509e
--- /dev/null
+++ b/dom/workers/test/script_createFile.js
@@ -0,0 +1,43 @@
+/* eslint-env mozilla/chrome-script */
+
+// eslint-disable-next-line mozilla/reject-importGlobalProperties
+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..d78fca94c6
--- /dev/null
+++ b/dom/workers/test/sharedWorker_console.js
@@ -0,0 +1,12 @@
+/**
+ * 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!");
+ console.log("Here is a SAB", new SharedArrayBuffer(1024));
+ 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..a7f859919f
--- /dev/null
+++ b/dom/workers/test/sharedWorker_sharedWorker.js
@@ -0,0 +1,100 @@
+/**
+ * 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;
+ // eslint-disable-next-line no-unreachable
+ 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..d0810b851a
--- /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">
+
+ <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";
+
+ add_task(
+ async function runTest() {
+ 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 () {
+ ok(false,
+ "Shared worker debugger should not be registered again.");
+ },
+ };
+ wdm.addListener(listener);
+
+ let secondWorker = 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)
+ ]);
+ secondWorker.port.start();
+ secondWorker.port.postMessage("close");
+ await promise;
+ worker = null;
+ secondWorker = null;
+
+ info("Create a SharedWorker again for the infinite loop test.")
+ promise = waitForRegister(SHARED_WORKER_URL);
+ // Give it an explicit name so we don't reuse the above SharedWorker.
+ worker = new SharedWorker(SHARED_WORKER_URL, "loopy");
+ 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. The default delay is 30 seconds. This test
+ // previously waited 15 seconds for reasons that were poorly justified.
+ // We now set it to 100ms because we just want to make sure that the
+ // timeout mechanism to force cancellation from the parent properly
+ // works (as long as the parent thread isn't blocked).
+ await SpecialPowers.pushPrefEnv({"set": [[ "dom.worker.canceling.timeoutMilliseconds", 100 ]]});
+
+ worker.port.start();
+ worker.port.postMessage("close_loop");
+ await promise;
+
+ wdm.removeListener(listener);
+ }
+ );
+
+ ]]>
+ </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..1ed09563de
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebuggerManager.xhtml
@@ -0,0 +1,99 @@
+<?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">
+
+ <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";
+
+ add_task(
+ async function runTest() {
+ 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.");
+ }
+ );
+
+ ]]>
+ </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..b38ccdac45
--- /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(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(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..f29def78ce
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger_frozen.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 frozen workers"
+ 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[
+
+ 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";
+
+ add_task(
+ async function runTest() {
+ await 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");
+ }
+ );
+
+ ]]>
+ </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..bea2b7f461
--- /dev/null
+++ b/dom/workers/test/test_bug1062920.html
@@ -0,0 +1,69 @@
+<!--
+ 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.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..0dfeccfb77
--- /dev/null
+++ b/dom/workers/test/test_bug1062920.xhtml
@@ -0,0 +1,64 @@
+<?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.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.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..8eb899fe7e
--- /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 = 49;
+
+ 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..282b46ec20
--- /dev/null
+++ b/dom/workers/test/test_errorwarning.html
@@ -0,0 +1,93 @@
+<!--
+ 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();
+ }
+ }
+
+ 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();
+ }
+ }
+
+ 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..fa396e88e8
--- /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, start, 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..bba2890b5e
--- /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};
+
+ 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..c352404cc1
--- /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, start, end, 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_1.html b/dom/workers/test/test_importScripts_1.html
new file mode 100644
index 0000000000..2c75a3ed91
--- /dev/null
+++ b/dom/workers/test/test_importScripts_1.html
@@ -0,0 +1,35 @@
+<head>
+ <title>Test for ServiceWorker importScripts</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script id="worker" type="javascript/worker">
+onconnect = async function(e) {
+ e.ports[0].onmessage = async function(msg) {
+ try {
+ self.importScripts("N:", "");
+ } catch (ex) {
+ e.source.postMessage("done");
+ }
+ };
+};
+</script>
+<script>
+SimpleTest.waitForExplicitFinish();
+document.addEventListener("DOMContentLoaded", async () => {
+ const blob = new Blob([document.querySelector('#worker').textContent],
+ {type: "text/javascript"});
+ const sw = new SharedWorker(window.URL.createObjectURL(blob));
+ sw.port.postMessage([], []);
+ sw.port.onmessage = function(e) {
+ if (e.data == "done") {
+ ok(true);
+ SimpleTest.finish();
+ }
+ };
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_importScripts_2.html b/dom/workers/test/test_importScripts_2.html
new file mode 100644
index 0000000000..f40b9a64ed
--- /dev/null
+++ b/dom/workers/test/test_importScripts_2.html
@@ -0,0 +1,34 @@
+<head>
+ <title>Test for Worker importScripts</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script id="worker" type="javascript/worker">
+onmessage = async function(msg) {
+ try {
+ self.importScripts("N:", "");
+ } catch (ex) {
+ postMessage("done");
+ }
+};
+
+</script>
+<script>
+SimpleTest.waitForExplicitFinish();
+document.addEventListener("DOMContentLoaded", async () => {
+ const blob = new Blob([document.querySelector('#worker').textContent],
+ {type: "text/javascript"});
+ const worker = new Worker(window.URL.createObjectURL(blob));
+ worker.postMessage([], []);
+ worker.onmessage = function(e) {
+ if (e.data == "done") {
+ ok(true);
+ SimpleTest.finish();
+ }
+ };
+});
+</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..7f10f23faf
--- /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 Event, "Event 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..248d7ed2cd
--- /dev/null
+++ b/dom/workers/test/test_navigator_iframe.js
@@ -0,0 +1,65 @@
+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;
+var isAndroid = AppConstants.platform == "android";
+
+worker.postMessage({ isNightly, isRelease, isAndroid });
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..6d350801cb
--- /dev/null
+++ b/dom/workers/test/test_referrer_header_worker.html
@@ -0,0 +1,39 @@
+<!--
+ 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) {
+ // eslint-disable-next-line no-eval
+ 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..0a3cde2d32
--- /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 = 98;
+ 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..e93d65b3b8
--- /dev/null
+++ b/dom/workers/test/test_sharedWorker_privateBrowsing.html
@@ -0,0 +1,104 @@
+<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.importESModule(
+ "resource://testing-common/BrowserTestUtils.sys.mjs"
+);
+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.startLoadingURIString(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..01157671d7
--- /dev/null
+++ b/dom/workers/test/test_subworkers_suspended.html
@@ -0,0 +1,144 @@
+<!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.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..3ea89ad6b5
--- /dev/null
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -0,0 +1,563 @@
+// 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, disabled: 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: "EncodedVideoChunk", insecureContext: true, nightly: 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: "RTCEncodedAudioFrame", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "RTCEncodedVideoFrame", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "RTCRtpScriptTransformer", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "RTCTransformEvent", 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: "VideoColorSpace", insecureContext: true, nightly: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "VideoDecoder", nightly: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "VideoEncoder", nightly: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "VideoFrame", insecureContext: true, nightly: 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: "onrtctransform", 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..27e514b391
--- /dev/null
+++ b/dom/workers/test/threadTimeouts_worker.js
@@ -0,0 +1,45 @@
+/**
+ * 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":
+ // eslint-disable-next-line no-implied-eval
+ 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..2eac367535
--- /dev/null
+++ b/dom/workers/test/timeoutTracing_worker.js
@@ -0,0 +1,16 @@
+/**
+ * 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);
+// eslint-disable-next-line no-implied-eval
+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..91de9c5a73
--- /dev/null
+++ b/dom/workers/test/worker_setTimeoutWith0.js
@@ -0,0 +1,4 @@
+/* eslint-disable no-implied-eval */
+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/base_uri_module.mjs b/dom/workers/test/xpcshell/data/base_uri_module.mjs
new file mode 100644
index 0000000000..7604baed82
--- /dev/null
+++ b/dom/workers/test/xpcshell/data/base_uri_module.mjs
@@ -0,0 +1,23 @@
+// This file is for testing the module loader's path handling.
+// ESLint rules that modifies path shouldn't be applied.
+
+export const obj = {};
+
+export async function doImport() {
+ // This file is loaded as resource://test/data/base_uri_module.mjs
+ // Relative/absolute paths should be resolved based on the URI, instead of
+ // file: path.
+
+ const namespaceWithURI = await import(
+ "resource://test/data/base_uri_module2.mjs"
+ );
+ const namespaceWithCurrentDir = await import("./base_uri_module2.mjs");
+ const namespaceWithParentDir = await import("../data/base_uri_module2.mjs");
+ const namespaceWithAbsoluteDir = await import("/data/base_uri_module2.mjs");
+
+ return {
+ equal1: namespaceWithURI.obj2 == namespaceWithCurrentDir.obj2,
+ equal2: namespaceWithURI.obj2 == namespaceWithParentDir.obj2,
+ equal3: namespaceWithURI.obj2 == namespaceWithAbsoluteDir.obj2,
+ };
+}
diff --git a/dom/workers/test/xpcshell/data/base_uri_module2.mjs b/dom/workers/test/xpcshell/data/base_uri_module2.mjs
new file mode 100644
index 0000000000..2358d27a83
--- /dev/null
+++ b/dom/workers/test/xpcshell/data/base_uri_module2.mjs
@@ -0,0 +1 @@
+export const obj2 = {};
diff --git a/dom/workers/test/xpcshell/data/base_uri_worker.js b/dom/workers/test/xpcshell/data/base_uri_worker.js
new file mode 100644
index 0000000000..74137cc20b
--- /dev/null
+++ b/dom/workers/test/xpcshell/data/base_uri_worker.js
@@ -0,0 +1,27 @@
+// This file is for testing the module loader's path handling.
+// ESLint rules that modifies path shouldn't be applied.
+
+onmessage = async event => {
+ // This file is loaded as resource://test/data/base_uri_worker.js
+ // Relative/absolute paths should be resolved based on the URI, instead of
+ // file: path.
+
+ const namespaceWithURI = await import(
+ "resource://test/data/base_uri_module.mjs"
+ );
+ const namespaceWithCurrentDir = await import("./base_uri_module.mjs");
+ const namespaceWithParentDir = await import("../data/base_uri_module.mjs");
+ const namespaceWithAbsoluteDir = await import("/data/base_uri_module.mjs");
+
+ postMessage({
+ scriptToModule: {
+ equal1: namespaceWithURI.obj == namespaceWithCurrentDir.obj,
+ equal2: namespaceWithURI.obj == namespaceWithParentDir.obj,
+ equal3: namespaceWithURI.obj == namespaceWithAbsoluteDir.obj,
+ },
+ moduleToModuleURI: await namespaceWithURI.doImport(),
+ moduleToModuleCurrent: await namespaceWithCurrentDir.doImport(),
+ moduleToModuleParent: await namespaceWithParentDir.doImport(),
+ moduleToModuleAbsolute: await namespaceWithAbsoluteDir.doImport(),
+ });
+};
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..3028e5d539
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_ext_redirects_sw_scripts.js
@@ -0,0 +1,558 @@
+/* -*- 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 { 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
+);
+Services.prefs.setBoolPref("extensions.dnr.enabled", true);
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("dom.serviceWorkers.testing.enabled");
+ Services.prefs.clearUserPref(
+ "extensions.filterResponseServiceWorkerScript.disabled"
+ );
+ Services.prefs.clearUserPref("extensions.dnr.enabled");
+});
+
+// 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.spawn([], 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 { MessageChannel } = this.content;
+ 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"],
+ // In this test task, the extension resource is not expected to be
+ // requested at all, so it does not really matter whether the file is
+ // listed in web_accessible_resources. Regardless, add it to make sure
+ // that any load failure is not caused by the lack of being listed here.
+ web_accessible_resources: ["sw-unexpected-redirect.js"],
+ },
+ 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('main worker 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 after loading the test extension, which should not be
+ // able to intercept and redirect the importedScripts requests because of
+ // invalid destinations.
+ info("Register service worker from a content webpage");
+ let workerMessage = await contentPage.spawn([], async () => {
+ const reg = await this.content.navigator.serviceWorker.register("/sw.js");
+ return new Promise(resolve => {
+ const { MessageChannel } = this.content;
+ 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.spawn([], async () => {
+ const reg = await this.content.navigator.serviceWorker.register("/sw.js");
+ return new Promise(resolve => {
+ const { MessageChannel } = this.content;
+ 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.spawn([], async () => {
+ const reg = await this.content.navigator.serviceWorker.register("/sw.js");
+ return new Promise(resolve => {
+ const { MessageChannel } = this.content;
+ 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();
+});
+
+// Test cases for redirects with declarativeNetRequest instead of webRequest,
+// testing the same scenarios as:
+// - test_extension_invalid_sw_scripts_redirect_ignored
+// i.e. fail to redirect SW main script, fail to redirect SW to about:blank.
+// - test_extension_redirect_sw_imported_script
+// i.e. allowed to redirect importScripts to moz-extension.
+add_task(async function test_dnr_redirect_sw_script_or_import() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ manifest_version: 3,
+ permissions: ["declarativeNetRequest"],
+ host_permissions: ["<all_urls>"],
+ granted_host_permissions: true,
+ web_accessible_resources: [
+ {
+ resources: ["sw-bad-redirect.js", "sw-dnr-redirect.js", "sw-nest.js"],
+ matches: ["*://*/*"],
+ },
+ ],
+ },
+ temporarilyInstalled: true, // <-- for granted_host_permissions
+ background: async () => {
+ await browser.declarativeNetRequest.updateSessionRules({
+ addRules: [
+ {
+ id: 1,
+ condition: { urlFilter: "|http://localhost/sw.js?dnr_redir_bad" },
+ action: {
+ type: "redirect",
+ redirect: { extensionPath: "/sw-bad-redirect.js" },
+ },
+ },
+ {
+ id: 2,
+ condition: { urlFilter: "|http://localhost/sw-imported.js|" },
+ action: {
+ type: "redirect",
+ redirect: { extensionPath: "/sw-dnr-redirect.js" },
+ },
+ },
+ {
+ id: 3,
+ condition: { urlFilter: "|http://localhost/sw-nest.js|" },
+ action: {
+ type: "redirect",
+ redirect: { extensionPath: "/sw-nest.js" },
+ },
+ },
+ {
+ id: 4,
+ condition: { urlFilter: "|http://localhost/sw-imported.js?about|" },
+ action: {
+ type: "redirect",
+ redirect: { url: "about:blank" },
+ },
+ },
+ ],
+ });
+ browser.test.sendMessage("dnr_registered");
+ },
+ files: {
+ "sw-bad-redirect.js": String.raw`
+ dump('main worker redirected to moz-extension://UUID/sw-bad-redirect.js\n');
+ self.onmessage = evt => evt.ports[0].postMessage('sw-bad-redirect');
+ `,
+ "sw-dnr-redirect.js": String.raw`
+ dump('importScript redirected to moz-extension://UUID/sw-dnr-redirect.js\n');
+ self.onmessage = evt => evt.ports[0].postMessage('sw-dnr-before-nest');
+
+ importScripts("/sw-nest.js");
+ // ^ sw-nest.js does not exist on the server, so if importScripts()
+ // succeeded, then that means that the DNR-triggered redirect worked.
+
+ self.onmessage = evt => evt.ports[0].postMessage('sw-before-about');
+ try {
+ importScripts("/sw-imported.js?about");
+ // ^ DNR redirects to about:blank, which should throw here.
+ self.onmessage = evt => evt.ports[0].postMessage('sw-dnr-about-bad');
+ } catch (e) {
+ // All is good.
+ self.onmessage = evt => evt.ports[0].postMessage('sw-dnr-redirect');
+ }
+ `,
+ "sw-nest.js": String.raw`
+ dump('importScript redirected to moz-extension://UUID/sw-nest.js\n');
+ // No other code here. The caller verifies success by confirming that
+ // the importScripts() call did not throw.
+ `,
+ },
+ });
+
+ // Start the test extension to redirect importScripts requests.
+ await extension.startup();
+ await extension.awaitMessage("dnr_registered");
+
+ await ensureDataCleanup();
+ let contentPage = await ExtensionTestUtils.loadContentPage(
+ "http://localhost/page.html"
+ );
+
+ // Register the worker after loading the test extension, which should not be
+ // able to intercept and redirect the importedScripts requests because of
+ // invalid destinations.
+ info("Register service worker from a content webpage (disallowed redirects)");
+ await contentPage.spawn([], async () => {
+ await Assert.rejects(
+ this.content.navigator.serviceWorker.register("/sw.js?dnr_redir_bad1"),
+ /SecurityError: The operation is insecure/,
+ "Redirect of main service worker script is not allowed"
+ );
+ });
+ info("Register service worker from a content webpage (with import redirect)");
+ let workerMessage = await contentPage.spawn([], async () => {
+ const reg = await this.content.navigator.serviceWorker.register("/sw.js");
+ return new Promise(resolve => {
+ const { MessageChannel } = this.content;
+ 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,
+ "sw-dnr-redirect",
+ "Got expected worker reply (importScripts redirected to moz-extension:-URL)"
+ );
+
+ await extension.unload();
+ await contentPage.close();
+});
diff --git a/dom/workers/test/xpcshell/test_ext_worker_offline_fetch.js b/dom/workers/test/xpcshell/test_ext_worker_offline_fetch.js
new file mode 100644
index 0000000000..718093422b
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_ext_worker_offline_fetch.js
@@ -0,0 +1,112 @@
+/* -*- 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 { 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: ["example.com"] });
+server.registerPathHandler("/dummy", (request, response) => {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("dummy page");
+});
+
+add_setup(() => {
+ info("Making sure Services.io.offline is true");
+ // Explicitly setting Services.io.offline to true makes this test able
+ // to hit on Desktop builds the same issue that test_ext_cache_api.js
+ // was hitting on Android builds (Bug 1844825).
+ Services.io.offline = true;
+});
+
+// Regression test derived from Bug 1844825.
+add_task(async function test_fetch_request_from_ext_shared_worker() {
+ if (!WebExtensionPolicy.useRemoteWebExtensions) {
+ // Ensure RemoteWorkerService has been initialized in the main
+ // process.
+ Services.obs.notifyObservers(null, "profile-after-change");
+ }
+
+ const background = async function () {
+ const testUrl = `http://example.com/dummy`;
+ const worker = new SharedWorker("worker.js");
+ const { data: result } = await new Promise(resolve => {
+ worker.port.onmessage = resolve;
+ worker.port.postMessage(["worker-fetch-test", testUrl]);
+ });
+
+ browser.test.sendMessage("test-sharedworker-fetch:done", result);
+ };
+
+ const extension = ExtensionTestUtils.loadExtension({
+ background,
+ manifest: { permissions: ["http://example.com/*"] },
+ files: {
+ "worker.js": function () {
+ self.onconnect = evt => {
+ const port = evt.ports[0];
+ port.onmessage = async evt => {
+ let result = {};
+ let message;
+ try {
+ const [msg, url] = evt.data;
+ message = msg;
+ const response = await fetch(url);
+ dump(`fetch call resolved: ${response}\n`);
+ result.fetchResolvesTo = `${response}`;
+ } catch (err) {
+ dump(`fetch call rejected: ${err}\n`);
+ result.error = err.name;
+ throw err;
+ } finally {
+ port.postMessage([`${message}:result`, result]);
+ }
+ };
+ };
+ },
+ },
+ });
+
+ await extension.startup();
+ const result = await extension.awaitMessage("test-sharedworker-fetch:done");
+ if (Services.io.offline && WebExtensionPolicy.useRemoteWebExtensions) {
+ // If the network is offline and the extensions are running in the
+ // child extension process, expect the fetch call to be rejected
+ // with an TypeError.
+ Assert.deepEqual(
+ ["worker-fetch-test:result", { error: "TypeError" }],
+ result,
+ "fetch should have been rejected with an TypeError"
+ );
+ } else {
+ // If the network is not offline or the extension are running in the
+ // parent process, we expect the fetch call to resolve to a Response.
+ Assert.deepEqual(
+ ["worker-fetch-test:result", { fetchResolvesTo: "[object Response]" }],
+ result,
+ "fetch should have been resolved to a Response instance"
+ );
+ }
+ await extension.unload();
+});
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_import_base_uri.js b/dom/workers/test/xpcshell/test_import_base_uri.js
new file mode 100644
index 0000000000..7c88a946f4
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_import_base_uri.js
@@ -0,0 +1,35 @@
+/* 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 testSyncImportBeforeAsyncImportDependencyInWorker() {
+ const worker = new ChromeWorker("resource://test/data/base_uri_worker.js");
+
+ const { promise, resolve } = Promise.withResolvers();
+ worker.onmessage = event => {
+ resolve(event.data);
+ };
+ worker.postMessage("");
+
+ const result = await promise;
+
+ Assert.ok(result.scriptToModule.equal1);
+ Assert.ok(result.scriptToModule.equal2);
+ Assert.ok(result.scriptToModule.equal3);
+
+ Assert.ok(result.moduleToModuleURI.equal1);
+ Assert.ok(result.moduleToModuleURI.equal2);
+ Assert.ok(result.moduleToModuleURI.equal3);
+
+ Assert.ok(result.moduleToModuleCurrent.equal1);
+ Assert.ok(result.moduleToModuleCurrent.equal2);
+ Assert.ok(result.moduleToModuleCurrent.equal3);
+
+ Assert.ok(result.moduleToModuleParent.equal1);
+ Assert.ok(result.moduleToModuleParent.equal2);
+ Assert.ok(result.moduleToModuleParent.equal3);
+
+ Assert.ok(result.moduleToModuleAbsolute.equal1);
+ Assert.ok(result.moduleToModuleAbsolute.equal2);
+ Assert.ok(result.moduleToModuleAbsolute.equal3);
+});
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..b95ad4bcaf
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_remoteworker_launch_new_process.js
@@ -0,0 +1,88 @@
+/* -*- 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() {
+ // 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..f3fd430457
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_workers_clone_error.js
@@ -0,0 +1,43 @@
+/* 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 {
+ // eslint-disable-next-line no-eval
+ 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.toml b/dom/workers/test/xpcshell/xpcshell.toml
new file mode 100644
index 0000000000..61b1eef0c2
--- /dev/null
+++ b/dom/workers/test/xpcshell/xpcshell.toml
@@ -0,0 +1,38 @@
+[DEFAULT]
+skip-if = ["os == 'android'"]
+support-files = [
+ "data/worker.js",
+ "data/worker_fileReader.js",
+ "data/chrome.manifest",
+ "data/base_uri_worker.js",
+ "data/base_uri_module.mjs",
+ "data/base_uri_module2.mjs",
+]
+
+["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_ext_worker_offline_fetch.js"]
+firefox-appdir = "browser"
+
+["test_fileReader.js"]
+
+["test_import_base_uri.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
+
+
+["test_workers.js"]
+
+["test_workers_clone_error.js"]