summaryrefslogtreecommitdiffstats
path: root/dom/workers
diff options
context:
space:
mode:
Diffstat (limited to 'dom/workers')
-rw-r--r--dom/workers/ChromeWorkerScope.cpp74
-rw-r--r--dom/workers/ChromeWorkerScope.h19
-rw-r--r--dom/workers/FileReaderSync.cpp264
-rw-r--r--dom/workers/FileReaderSync.h53
-rw-r--r--dom/workers/PServiceWorkerManager.ipdl45
-rw-r--r--dom/workers/Principal.cpp51
-rw-r--r--dom/workers/Principal.h22
-rw-r--r--dom/workers/Queue.h203
-rw-r--r--dom/workers/RegisterBindings.cpp55
-rw-r--r--dom/workers/RuntimeService.cpp2966
-rw-r--r--dom/workers/RuntimeService.h287
-rw-r--r--dom/workers/ScriptLoader.cpp2290
-rw-r--r--dom/workers/ScriptLoader.h68
-rw-r--r--dom/workers/ServiceWorker.cpp103
-rw-r--r--dom/workers/ServiceWorker.h85
-rw-r--r--dom/workers/ServiceWorkerClient.cpp232
-rw-r--r--dom/workers/ServiceWorkerClient.h118
-rw-r--r--dom/workers/ServiceWorkerClients.cpp864
-rw-r--r--dom/workers/ServiceWorkerClients.h64
-rw-r--r--dom/workers/ServiceWorkerCommon.h25
-rw-r--r--dom/workers/ServiceWorkerContainer.cpp341
-rw-r--r--dom/workers/ServiceWorkerContainer.h87
-rw-r--r--dom/workers/ServiceWorkerEvents.cpp1280
-rw-r--r--dom/workers/ServiceWorkerEvents.h316
-rw-r--r--dom/workers/ServiceWorkerInfo.cpp229
-rw-r--r--dom/workers/ServiceWorkerInfo.h151
-rw-r--r--dom/workers/ServiceWorkerJob.cpp246
-rw-r--r--dom/workers/ServiceWorkerJob.h155
-rw-r--r--dom/workers/ServiceWorkerJobQueue.cpp134
-rw-r--r--dom/workers/ServiceWorkerJobQueue.h49
-rw-r--r--dom/workers/ServiceWorkerManager.cpp3969
-rw-r--r--dom/workers/ServiceWorkerManager.h521
-rw-r--r--dom/workers/ServiceWorkerManagerChild.cpp107
-rw-r--r--dom/workers/ServiceWorkerManagerChild.h63
-rw-r--r--dom/workers/ServiceWorkerManagerParent.cpp330
-rw-r--r--dom/workers/ServiceWorkerManagerParent.h72
-rw-r--r--dom/workers/ServiceWorkerManagerService.cpp237
-rw-r--r--dom/workers/ServiceWorkerManagerService.h67
-rw-r--r--dom/workers/ServiceWorkerPrivate.cpp2088
-rw-r--r--dom/workers/ServiceWorkerPrivate.h236
-rw-r--r--dom/workers/ServiceWorkerRegisterJob.cpp67
-rw-r--r--dom/workers/ServiceWorkerRegisterJob.h40
-rw-r--r--dom/workers/ServiceWorkerRegistrar.cpp884
-rw-r--r--dom/workers/ServiceWorkerRegistrar.h101
-rw-r--r--dom/workers/ServiceWorkerRegistrarTypes.ipdlh23
-rw-r--r--dom/workers/ServiceWorkerRegistration.cpp1327
-rw-r--r--dom/workers/ServiceWorkerRegistration.h124
-rw-r--r--dom/workers/ServiceWorkerRegistrationInfo.cpp546
-rw-r--r--dom/workers/ServiceWorkerRegistrationInfo.h184
-rw-r--r--dom/workers/ServiceWorkerScriptCache.cpp1060
-rw-r--r--dom/workers/ServiceWorkerScriptCache.h59
-rw-r--r--dom/workers/ServiceWorkerUnregisterJob.cpp151
-rw-r--r--dom/workers/ServiceWorkerUnregisterJob.h45
-rw-r--r--dom/workers/ServiceWorkerUpdateJob.cpp552
-rw-r--r--dom/workers/ServiceWorkerUpdateJob.h104
-rw-r--r--dom/workers/ServiceWorkerWindowClient.cpp558
-rw-r--r--dom/workers/ServiceWorkerWindowClient.h64
-rw-r--r--dom/workers/SharedWorker.cpp207
-rw-r--r--dom/workers/SharedWorker.h105
-rw-r--r--dom/workers/WorkerDebuggerManager.cpp361
-rw-r--r--dom/workers/WorkerDebuggerManager.h121
-rw-r--r--dom/workers/WorkerHolder.cpp60
-rw-r--r--dom/workers/WorkerHolder.h96
-rw-r--r--dom/workers/WorkerInlines.h25
-rw-r--r--dom/workers/WorkerLocation.cpp43
-rw-r--r--dom/workers/WorkerLocation.h116
-rw-r--r--dom/workers/WorkerNavigator.cpp184
-rw-r--r--dom/workers/WorkerNavigator.h114
-rw-r--r--dom/workers/WorkerPrefs.h49
-rw-r--r--dom/workers/WorkerPrivate.cpp6752
-rw-r--r--dom/workers/WorkerPrivate.h1594
-rw-r--r--dom/workers/WorkerRunnable.cpp777
-rw-r--r--dom/workers/WorkerRunnable.h509
-rw-r--r--dom/workers/WorkerScope.cpp978
-rw-r--r--dom/workers/WorkerScope.h389
-rw-r--r--dom/workers/WorkerThread.cpp355
-rw-r--r--dom/workers/WorkerThread.h110
-rw-r--r--dom/workers/Workers.h383
-rw-r--r--dom/workers/moz.build131
-rw-r--r--dom/workers/nsIWorkerDebugger.idl50
-rw-r--r--dom/workers/nsIWorkerDebuggerManager.idl22
-rw-r--r--dom/workers/test/404_server.sjs10
-rw-r--r--dom/workers/test/WorkerDebugger.console_childWorker.js3
-rw-r--r--dom/workers/test/WorkerDebugger.console_debugger.js41
-rw-r--r--dom/workers/test/WorkerDebugger.console_worker.js10
-rw-r--r--dom/workers/test/WorkerDebugger.initialize_childWorker.js6
-rw-r--r--dom/workers/test/WorkerDebugger.initialize_debugger.js6
-rw-r--r--dom/workers/test/WorkerDebugger.initialize_worker.js9
-rw-r--r--dom/workers/test/WorkerDebugger.postMessage_childWorker.js3
-rw-r--r--dom/workers/test/WorkerDebugger.postMessage_debugger.js9
-rw-r--r--dom/workers/test/WorkerDebugger.postMessage_worker.js3
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_debugger.js9
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_sandbox.js9
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_worker.js3
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js14
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js29
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js25
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.reportError_childWorker.js5
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.reportError_debugger.js12
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.reportError_worker.js11
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_debugger.js12
-rw-r--r--dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_worker.js3
-rw-r--r--dom/workers/test/WorkerDebuggerManager_childWorker.js3
-rw-r--r--dom/workers/test/WorkerDebuggerManager_worker.js3
-rw-r--r--dom/workers/test/WorkerDebugger_childWorker.js3
-rw-r--r--dom/workers/test/WorkerDebugger_frozen_iframe1.html15
-rw-r--r--dom/workers/test/WorkerDebugger_frozen_iframe2.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.js30
-rw-r--r--dom/workers/test/WorkerDebugger_promise_worker.js25
-rw-r--r--dom/workers/test/WorkerDebugger_sharedWorker.js11
-rw-r--r--dom/workers/test/WorkerDebugger_suspended_debugger.js6
-rw-r--r--dom/workers/test/WorkerDebugger_suspended_worker.js6
-rw-r--r--dom/workers/test/WorkerDebugger_worker.js8
-rw-r--r--dom/workers/test/WorkerTest.jsm17
-rw-r--r--dom/workers/test/WorkerTest_badworker.js7
-rw-r--r--dom/workers/test/WorkerTest_subworker.js43
-rw-r--r--dom/workers/test/WorkerTest_worker.js11
-rw-r--r--dom/workers/test/atob_worker.js46
-rw-r--r--dom/workers/test/browser.ini12
-rw-r--r--dom/workers/test/browser_bug1047663.js50
-rw-r--r--dom/workers/test/browser_bug1104623.js53
-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.js64
-rw-r--r--dom/workers/test/bug1020226_frame.html20
-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.sjs40
-rw-r--r--dom/workers/test/bug1060621_worker.js2
-rw-r--r--dom/workers/test/bug1062920_worker.js6
-rw-r--r--dom/workers/test/bug1063538_worker.js25
-rw-r--r--dom/workers/test/bug1104064_worker.js10
-rw-r--r--dom/workers/test/bug1132395_sharedWorker.js12
-rw-r--r--dom/workers/test/bug1132924_worker.js10
-rw-r--r--dom/workers/test/bug978260_worker.js7
-rw-r--r--dom/workers/test/bug998474_worker.js6
-rw-r--r--dom/workers/test/chrome.ini86
-rw-r--r--dom/workers/test/chromeWorker_subworker.js7
-rw-r--r--dom/workers/test/chromeWorker_worker.js20
-rw-r--r--dom/workers/test/clearTimeouts_worker.js12
-rw-r--r--dom/workers/test/consoleReplaceable_worker.js16
-rw-r--r--dom/workers/test/console_worker.js109
-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/779707.html19
-rw-r--r--dom/workers/test/crashtests/943516.html10
-rw-r--r--dom/workers/test/crashtests/crashtests.list5
-rw-r--r--dom/workers/test/csp_worker.js28
-rw-r--r--dom/workers/test/csp_worker.js^headers^1
-rw-r--r--dom/workers/test/dom_worker_helper.js176
-rw-r--r--dom/workers/test/empty.html0
-rw-r--r--dom/workers/test/errorPropagation_iframe.html55
-rw-r--r--dom/workers/test/errorPropagation_worker.js50
-rw-r--r--dom/workers/test/errorwarning_worker.js42
-rw-r--r--dom/workers/test/eventDispatch_worker.js67
-rw-r--r--dom/workers/test/extensions/bootstrap/bootstrap.js141
-rw-r--r--dom/workers/test/extensions/bootstrap/install.rdf31
-rw-r--r--dom/workers/test/extensions/bootstrap/jar.mn3
-rw-r--r--dom/workers/test/extensions/bootstrap/moz.build20
-rw-r--r--dom/workers/test/extensions/bootstrap/worker.js7
-rw-r--r--dom/workers/test/extensions/bootstrap/workerbootstrap-test@mozilla.org.xpibin0 -> 7202 bytes
-rw-r--r--dom/workers/test/extensions/moz.build7
-rw-r--r--dom/workers/test/extensions/traditional/WorkerTest.js122
-rw-r--r--dom/workers/test/extensions/traditional/WorkerTest.manifest3
-rw-r--r--dom/workers/test/extensions/traditional/install.rdf30
-rw-r--r--dom/workers/test/extensions/traditional/jar.mn3
-rw-r--r--dom/workers/test/extensions/traditional/moz.build30
-rw-r--r--dom/workers/test/extensions/traditional/nsIWorkerTest.idl23
-rw-r--r--dom/workers/test/extensions/traditional/worker-test@mozilla.org.xpibin0 -> 8333 bytes
-rw-r--r--dom/workers/test/extensions/traditional/worker.js7
-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.js74
-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_worker.js16
-rw-r--r--dom/workers/test/fileapi_chromeScript.js29
-rw-r--r--dom/workers/test/foreign.js1
-rw-r--r--dom/workers/test/frame_script.js72
-rw-r--r--dom/workers/test/gtest/TestReadWrite.cpp499
-rw-r--r--dom/workers/test/gtest/moz.build13
-rw-r--r--dom/workers/test/head.js91
-rw-r--r--dom/workers/test/importForeignScripts_worker.js55
-rw-r--r--dom/workers/test/importScripts_3rdParty_worker.js82
-rw-r--r--dom/workers/test/importScripts_mixedcontent.html46
-rw-r--r--dom/workers/test/importScripts_worker.js64
-rw-r--r--dom/workers/test/importScripts_worker_imported1.js10
-rw-r--r--dom/workers/test/importScripts_worker_imported2.js10
-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.js12
-rw-r--r--dom/workers/test/json_worker.js338
-rw-r--r--dom/workers/test/jsversion_worker.js14
-rw-r--r--dom/workers/test/loadEncoding_worker.js7
-rw-r--r--dom/workers/test/location_worker.js11
-rw-r--r--dom/workers/test/longThread_worker.js14
-rw-r--r--dom/workers/test/mochitest.ini227
-rw-r--r--dom/workers/test/multi_sharedWorker_frame.html52
-rw-r--r--dom/workers/test/multi_sharedWorker_sharedWorker.js72
-rw-r--r--dom/workers/test/navigator_languages_worker.js11
-rw-r--r--dom/workers/test/navigator_worker.js79
-rw-r--r--dom/workers/test/newError_worker.js5
-rw-r--r--dom/workers/test/notification_permission_worker.js56
-rw-r--r--dom/workers/test/notification_worker.js93
-rw-r--r--dom/workers/test/notification_worker_child-child.js92
-rw-r--r--dom/workers/test/notification_worker_child-parent.js26
-rw-r--r--dom/workers/test/onLine_worker.js65
-rw-r--r--dom/workers/test/onLine_worker_child.js75
-rw-r--r--dom/workers/test/onLine_worker_head.js43
-rw-r--r--dom/workers/test/promise_worker.js856
-rw-r--r--dom/workers/test/recursion_worker.js46
-rw-r--r--dom/workers/test/recursiveOnerror_worker.js11
-rw-r--r--dom/workers/test/redirect_to_foreign.sjs4
-rw-r--r--dom/workers/test/referrer.sjs15
-rw-r--r--dom/workers/test/referrer_test_server.sjs101
-rw-r--r--dom/workers/test/referrer_worker.html145
-rw-r--r--dom/workers/test/rvals_worker.js13
-rw-r--r--dom/workers/test/script_createFile.js15
-rw-r--r--dom/workers/test/serviceworkers/activate_event_error_worker.js4
-rw-r--r--dom/workers/test/serviceworkers/blocking_install_event_worker.js23
-rw-r--r--dom/workers/test/serviceworkers/browser.ini10
-rw-r--r--dom/workers/test/serviceworkers/browser_base_force_refresh.html30
-rw-r--r--dom/workers/test/serviceworkers/browser_cached_force_refresh.html64
-rw-r--r--dom/workers/test/serviceworkers/browser_download.js83
-rw-r--r--dom/workers/test/serviceworkers/browser_force_refresh.js91
-rw-r--r--dom/workers/test/serviceworkers/bug1151916_driver.html53
-rw-r--r--dom/workers/test/serviceworkers/bug1151916_worker.js13
-rw-r--r--dom/workers/test/serviceworkers/bug1240436_worker.js2
-rw-r--r--dom/workers/test/serviceworkers/chrome.ini16
-rw-r--r--dom/workers/test/serviceworkers/chrome_helpers.js74
-rw-r--r--dom/workers/test/serviceworkers/claim_clients/client.html44
-rw-r--r--dom/workers/test/serviceworkers/claim_fetch_worker.js12
-rw-r--r--dom/workers/test/serviceworkers/claim_oninstall_worker.js7
-rw-r--r--dom/workers/test/serviceworkers/claim_worker_1.js28
-rw-r--r--dom/workers/test/serviceworkers/claim_worker_2.js27
-rw-r--r--dom/workers/test/serviceworkers/close_test.js19
-rw-r--r--dom/workers/test/serviceworkers/controller/index.html74
-rw-r--r--dom/workers/test/serviceworkers/create_another_sharedWorker.html6
-rw-r--r--dom/workers/test/serviceworkers/download/window.html46
-rw-r--r--dom/workers/test/serviceworkers/download/worker.js30
-rw-r--r--dom/workers/test/serviceworkers/empty.js0
-rw-r--r--dom/workers/test/serviceworkers/error_reporting_helpers.js68
-rw-r--r--dom/workers/test/serviceworkers/eval_worker.js1
-rw-r--r--dom/workers/test/serviceworkers/eventsource/eventsource.resource22
-rw-r--r--dom/workers/test/serviceworkers/eventsource/eventsource.resource^headers^3
-rw-r--r--dom/workers/test/serviceworkers/eventsource/eventsource_cors_response.html75
-rw-r--r--dom/workers/test/serviceworkers/eventsource/eventsource_cors_response_intercept_worker.js20
-rw-r--r--dom/workers/test/serviceworkers/eventsource/eventsource_mixed_content_cors_response.html75
-rw-r--r--dom/workers/test/serviceworkers/eventsource/eventsource_mixed_content_cors_response_intercept_worker.js19
-rw-r--r--dom/workers/test/serviceworkers/eventsource/eventsource_opaque_response.html75
-rw-r--r--dom/workers/test/serviceworkers/eventsource/eventsource_opaque_response_intercept_worker.js20
-rw-r--r--dom/workers/test/serviceworkers/eventsource/eventsource_register_worker.html27
-rw-r--r--dom/workers/test/serviceworkers/eventsource/eventsource_synthetic_response.html75
-rw-r--r--dom/workers/test/serviceworkers/eventsource/eventsource_synthetic_response_intercept_worker.js24
-rw-r--r--dom/workers/test/serviceworkers/eventsource/eventsource_worker_helper.js12
-rw-r--r--dom/workers/test/serviceworkers/fetch.js11
-rw-r--r--dom/workers/test/serviceworkers/fetch/context/beacon.sjs43
-rw-r--r--dom/workers/test/serviceworkers/fetch/context/context_test.js135
-rw-r--r--dom/workers/test/serviceworkers/fetch/context/csp-violate.sjs6
-rw-r--r--dom/workers/test/serviceworkers/fetch/context/index.html422
-rw-r--r--dom/workers/test/serviceworkers/fetch/context/parentsharedworker.js8
-rw-r--r--dom/workers/test/serviceworkers/fetch/context/parentworker.js4
-rw-r--r--dom/workers/test/serviceworkers/fetch/context/ping.html7
-rw-r--r--dom/workers/test/serviceworkers/fetch/context/realaudio.oggbin0 -> 6416 bytes
-rw-r--r--dom/workers/test/serviceworkers/fetch/context/realimg.jpgbin0 -> 3595 bytes
-rw-r--r--dom/workers/test/serviceworkers/fetch/context/register.html14
-rw-r--r--dom/workers/test/serviceworkers/fetch/context/sharedworker.js5
-rw-r--r--dom/workers/test/serviceworkers/fetch/context/unregister.html12
-rw-r--r--dom/workers/test/serviceworkers/fetch/context/worker.js1
-rw-r--r--dom/workers/test/serviceworkers/fetch/context/xml.xml3
-rw-r--r--dom/workers/test/serviceworkers/fetch/deliver-gzip.sjs17
-rw-r--r--dom/workers/test/serviceworkers/fetch/fetch_tests.js416
-rw-r--r--dom/workers/test/serviceworkers/fetch/fetch_worker_script.js29
-rw-r--r--dom/workers/test/serviceworkers/fetch/hsts/embedder.html7
-rw-r--r--dom/workers/test/serviceworkers/fetch/hsts/hsts_test.js11
-rw-r--r--dom/workers/test/serviceworkers/fetch/hsts/image-20px.pngbin0 -> 87 bytes
-rw-r--r--dom/workers/test/serviceworkers/fetch/hsts/image-40px.pngbin0 -> 123 bytes
-rw-r--r--dom/workers/test/serviceworkers/fetch/hsts/image.html13
-rw-r--r--dom/workers/test/serviceworkers/fetch/hsts/realindex.html8
-rw-r--r--dom/workers/test/serviceworkers/fetch/hsts/register.html14
-rw-r--r--dom/workers/test/serviceworkers/fetch/hsts/register.html^headers^2
-rw-r--r--dom/workers/test/serviceworkers/fetch/hsts/unregister.html12
-rw-r--r--dom/workers/test/serviceworkers/fetch/https/clonedresponse/https_test.js15
-rw-r--r--dom/workers/test/serviceworkers/fetch/https/clonedresponse/index.html4
-rw-r--r--dom/workers/test/serviceworkers/fetch/https/clonedresponse/register.html14
-rw-r--r--dom/workers/test/serviceworkers/fetch/https/clonedresponse/unregister.html12
-rw-r--r--dom/workers/test/serviceworkers/fetch/https/https_test.js23
-rw-r--r--dom/workers/test/serviceworkers/fetch/https/index.html4
-rw-r--r--dom/workers/test/serviceworkers/fetch/https/register.html20
-rw-r--r--dom/workers/test/serviceworkers/fetch/https/unregister.html12
-rw-r--r--dom/workers/test/serviceworkers/fetch/imagecache-maxage/image-20px.pngbin0 -> 87 bytes
-rw-r--r--dom/workers/test/serviceworkers/fetch/imagecache-maxage/image-40px.pngbin0 -> 123 bytes
-rw-r--r--dom/workers/test/serviceworkers/fetch/imagecache-maxage/index.html29
-rw-r--r--dom/workers/test/serviceworkers/fetch/imagecache-maxage/maxage_test.js41
-rw-r--r--dom/workers/test/serviceworkers/fetch/imagecache-maxage/register.html14
-rw-r--r--dom/workers/test/serviceworkers/fetch/imagecache-maxage/unregister.html12
-rw-r--r--dom/workers/test/serviceworkers/fetch/imagecache/image-20px.pngbin0 -> 87 bytes
-rw-r--r--dom/workers/test/serviceworkers/fetch/imagecache/image-40px.pngbin0 -> 123 bytes
-rw-r--r--dom/workers/test/serviceworkers/fetch/imagecache/imagecache_test.js15
-rw-r--r--dom/workers/test/serviceworkers/fetch/imagecache/index.html20
-rw-r--r--dom/workers/test/serviceworkers/fetch/imagecache/postmortem.html9
-rw-r--r--dom/workers/test/serviceworkers/fetch/imagecache/register.html16
-rw-r--r--dom/workers/test/serviceworkers/fetch/imagecache/unregister.html12
-rw-r--r--dom/workers/test/serviceworkers/fetch/importscript-mixedcontent/https_test.js28
-rw-r--r--dom/workers/test/serviceworkers/fetch/importscript-mixedcontent/register.html14
-rw-r--r--dom/workers/test/serviceworkers/fetch/importscript-mixedcontent/unregister.html12
-rw-r--r--dom/workers/test/serviceworkers/fetch/index.html183
-rw-r--r--dom/workers/test/serviceworkers/fetch/interrupt.sjs20
-rw-r--r--dom/workers/test/serviceworkers/fetch/origin/https/index-https.sjs4
-rw-r--r--dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js29
-rw-r--r--dom/workers/test/serviceworkers/fetch/origin/https/realindex.html6
-rw-r--r--dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^1
-rw-r--r--dom/workers/test/serviceworkers/fetch/origin/https/register.html14
-rw-r--r--dom/workers/test/serviceworkers/fetch/origin/https/unregister.html12
-rw-r--r--dom/workers/test/serviceworkers/fetch/origin/index-to-https.sjs4
-rw-r--r--dom/workers/test/serviceworkers/fetch/origin/index.sjs4
-rw-r--r--dom/workers/test/serviceworkers/fetch/origin/origin_test.js41
-rw-r--r--dom/workers/test/serviceworkers/fetch/origin/realindex.html6
-rw-r--r--dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^1
-rw-r--r--dom/workers/test/serviceworkers/fetch/origin/register.html14
-rw-r--r--dom/workers/test/serviceworkers/fetch/origin/unregister.html12
-rw-r--r--dom/workers/test/serviceworkers/fetch/plugin/plugins.html43
-rw-r--r--dom/workers/test/serviceworkers/fetch/plugin/worker.js14
-rw-r--r--dom/workers/test/serviceworkers/fetch/real-file.txt1
-rw-r--r--dom/workers/test/serviceworkers/fetch/redirect.sjs4
-rw-r--r--dom/workers/test/serviceworkers/fetch/requesturl/index.html7
-rw-r--r--dom/workers/test/serviceworkers/fetch/requesturl/redirect.sjs4
-rw-r--r--dom/workers/test/serviceworkers/fetch/requesturl/redirector.html2
-rw-r--r--dom/workers/test/serviceworkers/fetch/requesturl/register.html14
-rw-r--r--dom/workers/test/serviceworkers/fetch/requesturl/requesturl_test.js17
-rw-r--r--dom/workers/test/serviceworkers/fetch/requesturl/secret.html5
-rw-r--r--dom/workers/test/serviceworkers/fetch/requesturl/unregister.html12
-rw-r--r--dom/workers/test/serviceworkers/fetch/sandbox/index.html5
-rw-r--r--dom/workers/test/serviceworkers/fetch/sandbox/intercepted_index.html5
-rw-r--r--dom/workers/test/serviceworkers/fetch/sandbox/register.html14
-rw-r--r--dom/workers/test/serviceworkers/fetch/sandbox/sandbox_test.js5
-rw-r--r--dom/workers/test/serviceworkers/fetch/sandbox/unregister.html12
-rw-r--r--dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html10
-rw-r--r--dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html^headers^1
-rw-r--r--dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-20px.pngbin0 -> 87 bytes
-rw-r--r--dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-40px.pngbin0 -> 123 bytes
-rw-r--r--dom/workers/test/serviceworkers/fetch/upgrade-insecure/image.html13
-rw-r--r--dom/workers/test/serviceworkers/fetch/upgrade-insecure/realindex.html4
-rw-r--r--dom/workers/test/serviceworkers/fetch/upgrade-insecure/register.html14
-rw-r--r--dom/workers/test/serviceworkers/fetch/upgrade-insecure/unregister.html12
-rw-r--r--dom/workers/test/serviceworkers/fetch/upgrade-insecure/upgrade-insecure_test.js11
-rw-r--r--dom/workers/test/serviceworkers/fetch_event_worker.js337
-rw-r--r--dom/workers/test/serviceworkers/file_blob_response_worker.js38
-rw-r--r--dom/workers/test/serviceworkers/force_refresh_browser_worker.js34
-rw-r--r--dom/workers/test/serviceworkers/force_refresh_worker.js33
-rw-r--r--dom/workers/test/serviceworkers/gzip_redirect_worker.js13
-rw-r--r--dom/workers/test/serviceworkers/header_checker.sjs9
-rw-r--r--dom/workers/test/serviceworkers/hello.html9
-rw-r--r--dom/workers/test/serviceworkers/importscript.sjs11
-rw-r--r--dom/workers/test/serviceworkers/importscript_worker.js37
-rw-r--r--dom/workers/test/serviceworkers/install_event_error_worker.js4
-rw-r--r--dom/workers/test/serviceworkers/install_event_worker.js3
-rw-r--r--dom/workers/test/serviceworkers/lorem_script.js8
-rw-r--r--dom/workers/test/serviceworkers/match_all_advanced_worker.js5
-rw-r--r--dom/workers/test/serviceworkers/match_all_client/match_all_client_id.html31
-rw-r--r--dom/workers/test/serviceworkers/match_all_client_id_worker.js28
-rw-r--r--dom/workers/test/serviceworkers/match_all_clients/match_all_controlled.html65
-rw-r--r--dom/workers/test/serviceworkers/match_all_properties_worker.js20
-rw-r--r--dom/workers/test/serviceworkers/match_all_worker.js10
-rw-r--r--dom/workers/test/serviceworkers/message_posting_worker.js8
-rw-r--r--dom/workers/test/serviceworkers/message_receiver.html6
-rw-r--r--dom/workers/test/serviceworkers/mochitest.ini317
-rw-r--r--dom/workers/test/serviceworkers/notification/listener.html27
-rw-r--r--dom/workers/test/serviceworkers/notification/register.html11
-rw-r--r--dom/workers/test/serviceworkers/notification/unregister.html12
-rw-r--r--dom/workers/test/serviceworkers/notification_alt/register.html11
-rw-r--r--dom/workers/test/serviceworkers/notification_alt/unregister.html12
-rw-r--r--dom/workers/test/serviceworkers/notification_constructor_error.js1
-rw-r--r--dom/workers/test/serviceworkers/notification_get_sw.js49
-rw-r--r--dom/workers/test/serviceworkers/notificationclick-otherwindow.html30
-rw-r--r--dom/workers/test/serviceworkers/notificationclick.html27
-rw-r--r--dom/workers/test/serviceworkers/notificationclick.js19
-rw-r--r--dom/workers/test/serviceworkers/notificationclick_focus.html28
-rw-r--r--dom/workers/test/serviceworkers/notificationclick_focus.js40
-rw-r--r--dom/workers/test/serviceworkers/notificationclose.html37
-rw-r--r--dom/workers/test/serviceworkers/notificationclose.js19
-rw-r--r--dom/workers/test/serviceworkers/notify_loaded.js1
-rw-r--r--dom/workers/test/serviceworkers/opaque_intercept_worker.js25
-rw-r--r--dom/workers/test/serviceworkers/openWindow_worker.js116
-rw-r--r--dom/workers/test/serviceworkers/open_window/client.html48
-rw-r--r--dom/workers/test/serviceworkers/parse_error_worker.js2
-rw-r--r--dom/workers/test/serviceworkers/redirect.sjs5
-rw-r--r--dom/workers/test/serviceworkers/redirect_post.sjs35
-rw-r--r--dom/workers/test/serviceworkers/redirect_serviceworker.sjs4
-rw-r--r--dom/workers/test/serviceworkers/register_https.html21
-rw-r--r--dom/workers/test/serviceworkers/sanitize/example_check_and_unregister.html23
-rw-r--r--dom/workers/test/serviceworkers/sanitize/frame.html11
-rw-r--r--dom/workers/test/serviceworkers/sanitize/register.html10
-rw-r--r--dom/workers/test/serviceworkers/sanitize_worker.js5
-rw-r--r--dom/workers/test/serviceworkers/scope/scope_worker.js2
-rw-r--r--dom/workers/test/serviceworkers/serviceworker.html12
-rw-r--r--dom/workers/test/serviceworkers/serviceworker_not_sharedworker.js21
-rw-r--r--dom/workers/test/serviceworkers/serviceworker_wrapper.js101
-rw-r--r--dom/workers/test/serviceworkers/serviceworkerinfo_iframe.html26
-rw-r--r--dom/workers/test/serviceworkers/serviceworkermanager_iframe.html34
-rw-r--r--dom/workers/test/serviceworkers/serviceworkerregistrationinfo_iframe.html30
-rw-r--r--dom/workers/test/serviceworkers/sharedWorker_fetch.js29
-rw-r--r--dom/workers/test/serviceworkers/simpleregister/index.html51
-rw-r--r--dom/workers/test/serviceworkers/simpleregister/ready.html15
-rw-r--r--dom/workers/test/serviceworkers/skip_waiting_installed_worker.js6
-rw-r--r--dom/workers/test/serviceworkers/skip_waiting_scope/index.html37
-rw-r--r--dom/workers/test/serviceworkers/source_message_posting_worker.js16
-rw-r--r--dom/workers/test/serviceworkers/strict_mode_warning.js4
-rw-r--r--dom/workers/test/serviceworkers/sw_bad_mime_type.js1
-rw-r--r--dom/workers/test/serviceworkers/sw_bad_mime_type.js^headers^1
-rw-r--r--dom/workers/test/serviceworkers/sw_clients/file_blob_upload_frame.html77
-rw-r--r--dom/workers/test/serviceworkers/sw_clients/navigator.html35
-rw-r--r--dom/workers/test/serviceworkers/sw_clients/refresher.html39
-rw-r--r--dom/workers/test/serviceworkers/sw_clients/refresher_cached.html38
-rw-r--r--dom/workers/test/serviceworkers/sw_clients/refresher_cached_compressed.htmlbin0 -> 559 bytes
-rw-r--r--dom/workers/test/serviceworkers/sw_clients/refresher_cached_compressed.html^headers^2
-rw-r--r--dom/workers/test/serviceworkers/sw_clients/refresher_compressed.htmlbin0 -> 608 bytes
-rw-r--r--dom/workers/test/serviceworkers/sw_clients/refresher_compressed.html^headers^2
-rw-r--r--dom/workers/test/serviceworkers/sw_clients/service_worker_controlled.html38
-rw-r--r--dom/workers/test/serviceworkers/sw_clients/simple.html30
-rw-r--r--dom/workers/test/serviceworkers/swa/worker_scope_different.js0
-rw-r--r--dom/workers/test/serviceworkers/swa/worker_scope_different.js^headers^1
-rw-r--r--dom/workers/test/serviceworkers/swa/worker_scope_different2.js0
-rw-r--r--dom/workers/test/serviceworkers/swa/worker_scope_different2.js^headers^1
-rw-r--r--dom/workers/test/serviceworkers/swa/worker_scope_precise.js0
-rw-r--r--dom/workers/test/serviceworkers/swa/worker_scope_precise.js^headers^1
-rw-r--r--dom/workers/test/serviceworkers/swa/worker_scope_too_deep.js0
-rw-r--r--dom/workers/test/serviceworkers/swa/worker_scope_too_deep.js^headers^1
-rw-r--r--dom/workers/test/serviceworkers/swa/worker_scope_too_narrow.js0
-rw-r--r--dom/workers/test/serviceworkers/swa/worker_scope_too_narrow.js^headers^1
-rw-r--r--dom/workers/test/serviceworkers/test_bug1151916.html105
-rw-r--r--dom/workers/test/serviceworkers/test_bug1240436.html34
-rw-r--r--dom/workers/test/serviceworkers/test_claim.html172
-rw-r--r--dom/workers/test/serviceworkers/test_claim_fetch.html98
-rw-r--r--dom/workers/test/serviceworkers/test_claim_oninstall.html78
-rw-r--r--dom/workers/test/serviceworkers/test_client_focus.html96
-rw-r--r--dom/workers/test/serviceworkers/test_close.html64
-rw-r--r--dom/workers/test/serviceworkers/test_controller.html84
-rw-r--r--dom/workers/test/serviceworkers/test_cross_origin_url_after_redirect.html50
-rw-r--r--dom/workers/test/serviceworkers/test_csp_upgrade-insecure_intercept.html55
-rw-r--r--dom/workers/test/serviceworkers/test_empty_serviceworker.html46
-rw-r--r--dom/workers/test/serviceworkers/test_error_reporting.html76
-rw-r--r--dom/workers/test/serviceworkers/test_escapedSlashes.html102
-rw-r--r--dom/workers/test/serviceworkers/test_eval_allowed.html51
-rw-r--r--dom/workers/test/serviceworkers/test_eval_allowed.html^headers^1
-rw-r--r--dom/workers/test/serviceworkers/test_eventsource_intercept.html103
-rw-r--r--dom/workers/test/serviceworkers/test_fetch_event.html83
-rw-r--r--dom/workers/test/serviceworkers/test_fetch_integrity.html178
-rw-r--r--dom/workers/test/serviceworkers/test_file_blob_response.html86
-rw-r--r--dom/workers/test/serviceworkers/test_file_blob_upload.html145
-rw-r--r--dom/workers/test/serviceworkers/test_force_refresh.html84
-rw-r--r--dom/workers/test/serviceworkers/test_gzip_redirect.html84
-rw-r--r--dom/workers/test/serviceworkers/test_hsts_upgrade_intercept.html66
-rw-r--r--dom/workers/test/serviceworkers/test_https_fetch.html62
-rw-r--r--dom/workers/test/serviceworkers/test_https_fetch_cloned_response.html56
-rw-r--r--dom/workers/test/serviceworkers/test_https_origin_after_redirect.html57
-rw-r--r--dom/workers/test/serviceworkers/test_https_origin_after_redirect_cached.html57
-rw-r--r--dom/workers/test/serviceworkers/test_https_synth_fetch_from_cached_sw.html69
-rw-r--r--dom/workers/test/serviceworkers/test_imagecache.html55
-rw-r--r--dom/workers/test/serviceworkers/test_imagecache_max_age.html71
-rw-r--r--dom/workers/test/serviceworkers/test_importscript.html72
-rw-r--r--dom/workers/test/serviceworkers/test_importscript_mixedcontent.html53
-rw-r--r--dom/workers/test/serviceworkers/test_install_event.html144
-rw-r--r--dom/workers/test/serviceworkers/test_install_event_gc.html121
-rw-r--r--dom/workers/test/serviceworkers/test_installation_simple.html212
-rw-r--r--dom/workers/test/serviceworkers/test_match_all.html80
-rw-r--r--dom/workers/test/serviceworkers/test_match_all_advanced.html100
-rw-r--r--dom/workers/test/serviceworkers/test_match_all_client_id.html91
-rw-r--r--dom/workers/test/serviceworkers/test_match_all_client_properties.html97
-rw-r--r--dom/workers/test/serviceworkers/test_navigator.html40
-rw-r--r--dom/workers/test/serviceworkers/test_not_intercept_plugin.html78
-rw-r--r--dom/workers/test/serviceworkers/test_notification_constructor_error.html52
-rw-r--r--dom/workers/test/serviceworkers/test_notification_get.html213
-rw-r--r--dom/workers/test/serviceworkers/test_notificationclick-otherwindow.html62
-rw-r--r--dom/workers/test/serviceworkers/test_notificationclick.html62
-rw-r--r--dom/workers/test/serviceworkers/test_notificationclick_focus.html63
-rw-r--r--dom/workers/test/serviceworkers/test_notificationclose.html62
-rw-r--r--dom/workers/test/serviceworkers/test_opaque_intercept.html85
-rw-r--r--dom/workers/test/serviceworkers/test_openWindow.html117
-rw-r--r--dom/workers/test/serviceworkers/test_origin_after_redirect.html58
-rw-r--r--dom/workers/test/serviceworkers/test_origin_after_redirect_cached.html58
-rw-r--r--dom/workers/test/serviceworkers/test_origin_after_redirect_to_https.html57
-rw-r--r--dom/workers/test/serviceworkers/test_origin_after_redirect_to_https_cached.html58
-rw-r--r--dom/workers/test/serviceworkers/test_post_message.html80
-rw-r--r--dom/workers/test/serviceworkers/test_post_message_advanced.html109
-rw-r--r--dom/workers/test/serviceworkers/test_post_message_source.html68
-rw-r--r--dom/workers/test/serviceworkers/test_privateBrowsing.html113
-rw-r--r--dom/workers/test/serviceworkers/test_register_base.html41
-rw-r--r--dom/workers/test/serviceworkers/test_register_https_in_http.html46
-rw-r--r--dom/workers/test/serviceworkers/test_request_context.js75
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_audio.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_beacon.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_cache.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_cspreport.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_embed.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_fetch.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_font.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_frame.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_iframe.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_image.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_imagesrcset.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_internal.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_nestedworker.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_nestedworkerinsharedworker.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_object.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_picture.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_ping.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_plugin.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_script.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_sharedworker.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_style.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_track.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_video.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_worker.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_xhr.html19
-rw-r--r--dom/workers/test/serviceworkers/test_request_context_xslt.html19
-rw-r--r--dom/workers/test/serviceworkers/test_sandbox_intercept.html50
-rw-r--r--dom/workers/test/serviceworkers/test_sanitize.html87
-rw-r--r--dom/workers/test/serviceworkers/test_sanitize_domain.html90
-rw-r--r--dom/workers/test/serviceworkers/test_scopes.html121
-rw-r--r--dom/workers/test/serviceworkers/test_service_worker_allowed.html74
-rw-r--r--dom/workers/test/serviceworkers/test_serviceworker_header.html41
-rw-r--r--dom/workers/test/serviceworkers/test_serviceworker_interfaces.html106
-rw-r--r--dom/workers/test/serviceworkers/test_serviceworker_interfaces.js278
-rw-r--r--dom/workers/test/serviceworkers/test_serviceworker_not_sharedworker.html66
-rw-r--r--dom/workers/test/serviceworkers/test_serviceworkerinfo.xul115
-rw-r--r--dom/workers/test/serviceworkers/test_serviceworkermanager.xul80
-rw-r--r--dom/workers/test/serviceworkers/test_serviceworkerregistrationinfo.xul115
-rw-r--r--dom/workers/test/serviceworkers/test_skip_waiting.html95
-rw-r--r--dom/workers/test/serviceworkers/test_strict_mode_warning.html42
-rw-r--r--dom/workers/test/serviceworkers/test_third_party_iframes.html175
-rw-r--r--dom/workers/test/serviceworkers/test_unregister.html138
-rw-r--r--dom/workers/test/serviceworkers/test_unresolved_fetch_interception.html100
-rw-r--r--dom/workers/test/serviceworkers/test_workerUnregister.html82
-rw-r--r--dom/workers/test/serviceworkers/test_workerUpdate.html62
-rw-r--r--dom/workers/test/serviceworkers/test_workerupdatefoundevent.html85
-rw-r--r--dom/workers/test/serviceworkers/test_xslt.html128
-rw-r--r--dom/workers/test/serviceworkers/thirdparty/iframe1.html30
-rw-r--r--dom/workers/test/serviceworkers/thirdparty/iframe2.html7
-rw-r--r--dom/workers/test/serviceworkers/thirdparty/register.html27
-rw-r--r--dom/workers/test/serviceworkers/thirdparty/sw.js14
-rw-r--r--dom/workers/test/serviceworkers/thirdparty/unregister.html11
-rw-r--r--dom/workers/test/serviceworkers/unregister/index.html26
-rw-r--r--dom/workers/test/serviceworkers/unregister/unregister.html22
-rw-r--r--dom/workers/test/serviceworkers/unresolved_fetch_worker.js19
-rw-r--r--dom/workers/test/serviceworkers/updatefoundevent.html13
-rw-r--r--dom/workers/test/serviceworkers/worker.js1
-rw-r--r--dom/workers/test/serviceworkers/worker2.js1
-rw-r--r--dom/workers/test/serviceworkers/worker3.js1
-rw-r--r--dom/workers/test/serviceworkers/workerUpdate/update.html24
-rw-r--r--dom/workers/test/serviceworkers/worker_unregister.js16
-rw-r--r--dom/workers/test/serviceworkers/worker_update.js19
-rw-r--r--dom/workers/test/serviceworkers/worker_updatefoundevent.js23
-rw-r--r--dom/workers/test/serviceworkers/worker_updatefoundevent2.js1
-rw-r--r--dom/workers/test/serviceworkers/xslt/test.xml6
-rw-r--r--dom/workers/test/serviceworkers/xslt/xslt.sjs12
-rw-r--r--dom/workers/test/serviceworkers/xslt_worker.js52
-rw-r--r--dom/workers/test/sharedWorker_console.js11
-rw-r--r--dom/workers/test/sharedWorker_lifetime.js5
-rw-r--r--dom/workers/test/sharedWorker_ports.js24
-rw-r--r--dom/workers/test/sharedWorker_privateBrowsing.js5
-rw-r--r--dom/workers/test/sharedWorker_sharedWorker.js93
-rw-r--r--dom/workers/test/simpleThread_worker.js53
-rw-r--r--dom/workers/test/suspend_iframe.html47
-rw-r--r--dom/workers/test/suspend_worker.js13
-rw-r--r--dom/workers/test/terminate_worker.js9
-rw-r--r--dom/workers/test/test_404.html41
-rw-r--r--dom/workers/test/test_WorkerDebugger.initialize.xul58
-rw-r--r--dom/workers/test/test_WorkerDebugger.postMessage.xul61
-rw-r--r--dom/workers/test/test_WorkerDebugger.xul122
-rw-r--r--dom/workers/test/test_WorkerDebuggerGlobalScope.createSandbox.xul52
-rw-r--r--dom/workers/test/test_WorkerDebuggerGlobalScope.enterEventLoop.xul126
-rw-r--r--dom/workers/test/test_WorkerDebuggerGlobalScope.reportError.xul98
-rw-r--r--dom/workers/test/test_WorkerDebuggerGlobalScope.setImmediate.xul54
-rw-r--r--dom/workers/test/test_WorkerDebuggerManager.xul106
-rw-r--r--dom/workers/test/test_WorkerDebugger_console.xul97
-rw-r--r--dom/workers/test/test_WorkerDebugger_frozen.xul90
-rw-r--r--dom/workers/test/test_WorkerDebugger_promise.xul70
-rw-r--r--dom/workers/test/test_WorkerDebugger_suspended.xul75
-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.html32
-rw-r--r--dom/workers/test/test_bug1002702.html27
-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.html54
-rw-r--r--dom/workers/test/test_bug1060621.html30
-rw-r--r--dom/workers/test/test_bug1062920.html70
-rw-r--r--dom/workers/test/test_bug1062920.xul69
-rw-r--r--dom/workers/test/test_bug1063538.html49
-rw-r--r--dom/workers/test/test_bug1104064.html28
-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.html62
-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.html27
-rw-r--r--dom/workers/test/test_chromeWorker.xul45
-rw-r--r--dom/workers/test/test_chromeWorkerJSM.xul56
-rw-r--r--dom/workers/test/test_clearTimeouts.html31
-rw-r--r--dom/workers/test/test_console.html44
-rw-r--r--dom/workers/test/test_consoleAndBlobs.html43
-rw-r--r--dom/workers/test/test_consoleReplaceable.html44
-rw-r--r--dom/workers/test/test_consoleSharedWorkers.html56
-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.js48
-rw-r--r--dom/workers/test/test_dataURLWorker.html31
-rw-r--r--dom/workers/test/test_errorPropagation.html66
-rw-r--r--dom/workers/test/test_errorwarning.html95
-rw-r--r--dom/workers/test/test_eventDispatch.html33
-rw-r--r--dom/workers/test/test_extension.xul55
-rw-r--r--dom/workers/test/test_extensionBootstrap.xul66
-rw-r--r--dom/workers/test/test_fibonacci.html52
-rw-r--r--dom/workers/test/test_file.xul97
-rw-r--r--dom/workers/test/test_fileBlobPosting.xul86
-rw-r--r--dom/workers/test/test_fileBlobSubWorker.xul98
-rw-r--r--dom/workers/test/test_filePosting.xul86
-rw-r--r--dom/workers/test/test_fileReadSlice.xul94
-rw-r--r--dom/workers/test/test_fileReader.html100
-rw-r--r--dom/workers/test/test_fileReaderSync.xul199
-rw-r--r--dom/workers/test/test_fileReaderSyncErrors.xul84
-rw-r--r--dom/workers/test/test_fileSlice.xul106
-rw-r--r--dom/workers/test/test_fileSubWorker.xul99
-rw-r--r--dom/workers/test/test_importScripts.html53
-rw-r--r--dom/workers/test/test_importScripts_3rdparty.html134
-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_jsversion.html68
-rw-r--r--dom/workers/test/test_loadEncoding.html50
-rw-r--r--dom/workers/test/test_loadError.html77
-rw-r--r--dom/workers/test/test_location.html72
-rw-r--r--dom/workers/test/test_longThread.html59
-rw-r--r--dom/workers/test/test_multi_sharedWorker.html242
-rw-r--r--dom/workers/test/test_multi_sharedWorker_lifetimes.html156
-rw-r--r--dom/workers/test/test_navigator.html68
-rw-r--r--dom/workers/test/test_navigator_languages.html53
-rw-r--r--dom/workers/test/test_navigator_workers_hardwareConcurrency.html30
-rw-r--r--dom/workers/test/test_newError.html34
-rw-r--r--dom/workers/test/test_notification.html50
-rw-r--r--dom/workers/test/test_notification_child.html49
-rw-r--r--dom/workers/test/test_notification_permission.html51
-rw-r--r--dom/workers/test/test_onLine.html64
-rw-r--r--dom/workers/test/test_promise.html44
-rw-r--r--dom/workers/test/test_promise_resolved_with_string.html41
-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.html37
-rw-r--r--dom/workers/test/test_setTimeoutWith0.html20
-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.html42
-rw-r--r--dom/workers/test/test_sharedWorker_privateBrowsing.html101
-rw-r--r--dom/workers/test/test_simpleThread.html75
-rw-r--r--dom/workers/test/test_subworkers_suspended.html135
-rw-r--r--dom/workers/test/test_suspend.html138
-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.html61
-rw-r--r--dom/workers/test/test_throwingOnerror.html54
-rw-r--r--dom/workers/test/test_timeoutTracing.html48
-rw-r--r--dom/workers/test/test_transferable.html123
-rw-r--r--dom/workers/test/test_webSocket_sharedWorker.html30
-rw-r--r--dom/workers/test/test_websocket1.html46
-rw-r--r--dom/workers/test/test_websocket2.html46
-rw-r--r--dom/workers/test/test_websocket3.html46
-rw-r--r--dom/workers/test/test_websocket4.html46
-rw-r--r--dom/workers/test/test_websocket5.html46
-rw-r--r--dom/workers/test/test_websocket_basic.html57
-rw-r--r--dom/workers/test/test_websocket_https.html30
-rw-r--r--dom/workers/test/test_websocket_loadgroup.html61
-rw-r--r--dom/workers/test/test_worker_interfaces.html16
-rw-r--r--dom/workers/test/test_worker_interfaces.js291
-rw-r--r--dom/workers/test/test_workersDisabled.html54
-rw-r--r--dom/workers/test/test_workersDisabled.xul49
-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.js9
-rw-r--r--dom/workers/test/threadErrors_worker4.js8
-rw-r--r--dom/workers/test/threadTimeouts_worker.js44
-rw-r--r--dom/workers/test/throwingOnerror_worker.js15
-rw-r--r--dom/workers/test/timeoutTracing_worker.js13
-rw-r--r--dom/workers/test/transferable_worker.js23
-rw-r--r--dom/workers/test/webSocket_sharedWorker.js20
-rw-r--r--dom/workers/test/websocket_basic_worker.js39
-rw-r--r--dom/workers/test/websocket_helpers.js19
-rw-r--r--dom/workers/test/websocket_https.html14
-rw-r--r--dom/workers/test/websocket_https_worker.js9
-rw-r--r--dom/workers/test/websocket_loadgroup_worker.js24
-rw-r--r--dom/workers/test/websocket_worker1.js19
-rw-r--r--dom/workers/test/websocket_worker2.js19
-rw-r--r--dom/workers/test/websocket_worker3.js17
-rw-r--r--dom/workers/test/websocket_worker4.js19
-rw-r--r--dom/workers/test/websocket_worker5.js13
-rw-r--r--dom/workers/test/window_suspended.html5
-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_consoleAndBlobs.js8
-rw-r--r--dom/workers/test/worker_driver.js64
-rw-r--r--dom/workers/test/worker_fileReader.js417
-rw-r--r--dom/workers/test/worker_referrer.js9
-rw-r--r--dom/workers/test/worker_setTimeoutWith0.js3
-rw-r--r--dom/workers/test/worker_suspended.js31
-rw-r--r--dom/workers/test/worker_wrapper.js99
-rw-r--r--dom/workers/test/workersDisabled_worker.js7
-rw-r--r--dom/workers/test/xpcshell/data/chrome.manifest1
-rw-r--r--dom/workers/test/xpcshell/data/worker.js7
-rw-r--r--dom/workers/test/xpcshell/data/worker_fileReader.js8
-rw-r--r--dom/workers/test/xpcshell/test_fileReader.js40
-rw-r--r--dom/workers/test/xpcshell/test_workers.js44
-rw-r--r--dom/workers/test/xpcshell/xpcshell.ini11
730 files changed, 66297 insertions, 0 deletions
diff --git a/dom/workers/ChromeWorkerScope.cpp b/dom/workers/ChromeWorkerScope.cpp
new file mode 100644
index 000000000..68ebebfc3
--- /dev/null
+++ b/dom/workers/ChromeWorkerScope.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ChromeWorkerScope.h"
+
+#include "jsapi.h"
+
+#include "nsXPCOM.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsString.h"
+
+#include "WorkerPrivate.h"
+
+using namespace mozilla::dom;
+USING_WORKERS_NAMESPACE
+
+namespace {
+
+#ifdef BUILD_CTYPES
+
+char*
+UnicodeToNative(JSContext* aCx, const char16_t* aSource, size_t aSourceLen)
+{
+ nsDependentString unicode(aSource, aSourceLen);
+
+ nsAutoCString native;
+ if (NS_FAILED(NS_CopyUnicodeToNative(unicode, native))) {
+ JS_ReportErrorASCII(aCx, "Could not convert string to native charset!");
+ return nullptr;
+ }
+
+ char* result = static_cast<char*>(JS_malloc(aCx, native.Length() + 1));
+ if (!result) {
+ return nullptr;
+ }
+
+ memcpy(result, native.get(), native.Length());
+ result[native.Length()] = 0;
+ return result;
+}
+
+#endif // BUILD_CTYPES
+
+} // namespace
+
+BEGIN_WORKERS_NAMESPACE
+
+bool
+DefineChromeWorkerFunctions(JSContext* aCx, JS::Handle<JSObject*> aGlobal)
+{
+ // Currently ctypes is the only special property given to ChromeWorkers.
+#ifdef BUILD_CTYPES
+ {
+ JS::Rooted<JS::Value> ctypes(aCx);
+ if (!JS_InitCTypesClass(aCx, aGlobal) ||
+ !JS_GetProperty(aCx, aGlobal, "ctypes", &ctypes)) {
+ return false;
+ }
+
+ static const JSCTypesCallbacks callbacks = {
+ UnicodeToNative
+ };
+
+ JS_SetCTypesCallbacks(ctypes.toObjectOrNull(), &callbacks);
+ }
+#endif // BUILD_CTYPES
+
+ return true;
+}
+
+END_WORKERS_NAMESPACE
diff --git a/dom/workers/ChromeWorkerScope.h b/dom/workers/ChromeWorkerScope.h
new file mode 100644
index 000000000..306397b66
--- /dev/null
+++ b/dom/workers/ChromeWorkerScope.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_chromeworkerscope_h__
+#define mozilla_dom_workers_chromeworkerscope_h__
+
+#include "Workers.h"
+
+BEGIN_WORKERS_NAMESPACE
+
+bool
+DefineChromeWorkerFunctions(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
+
+END_WORKERS_NAMESPACE
+
+#endif // mozilla_dom_workers_chromeworkerscope_h__
diff --git a/dom/workers/FileReaderSync.cpp b/dom/workers/FileReaderSync.cpp
new file mode 100644
index 000000000..18efcb194
--- /dev/null
+++ b/dom/workers/FileReaderSync.cpp
@@ -0,0 +1,264 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FileReaderSync.h"
+
+#include "jsfriendapi.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Base64.h"
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/File.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/FileReaderSyncBinding.h"
+#include "nsCExternalHandlerService.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsDOMClassInfoID.h"
+#include "nsError.h"
+#include "nsIConverterInputStream.h"
+#include "nsIInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsISupportsImpl.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+
+#include "RuntimeService.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using mozilla::dom::Optional;
+using mozilla::dom::GlobalObject;
+
+// static
+already_AddRefed<FileReaderSync>
+FileReaderSync::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
+{
+ RefPtr<FileReaderSync> frs = new FileReaderSync();
+
+ return frs.forget();
+}
+
+bool
+FileReaderSync::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector)
+{
+ return FileReaderSyncBinding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+void
+FileReaderSync::ReadAsArrayBuffer(JSContext* aCx,
+ JS::Handle<JSObject*> aScopeObj,
+ Blob& aBlob,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ uint64_t blobSize = aBlob.GetSize(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ UniquePtr<char[], JS::FreePolicy> bufferData(js_pod_malloc<char>(blobSize));
+ if (!bufferData) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ aBlob.GetInternalStream(getter_AddRefs(stream), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ uint32_t numRead;
+ aRv = stream->Read(bufferData.get(), blobSize, &numRead);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ NS_ASSERTION(numRead == blobSize, "failed to read data");
+
+ JSObject* arrayBuffer = JS_NewArrayBufferWithContents(aCx, blobSize, bufferData.get());
+ if (!arrayBuffer) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ // arrayBuffer takes the ownership when it is not null. Otherwise we
+ // need to release it explicitly.
+ mozilla::Unused << bufferData.release();
+
+ aRetval.set(arrayBuffer);
+}
+
+void
+FileReaderSync::ReadAsBinaryString(Blob& aBlob,
+ nsAString& aResult,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIInputStream> stream;
+ aBlob.GetInternalStream(getter_AddRefs(stream), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ uint32_t numRead;
+ do {
+ char readBuf[4096];
+ aRv = stream->Read(readBuf, sizeof(readBuf), &numRead);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ uint32_t oldLength = aResult.Length();
+ AppendASCIItoUTF16(Substring(readBuf, readBuf + numRead), aResult);
+ if (aResult.Length() - oldLength != numRead) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ } while (numRead > 0);
+}
+
+void
+FileReaderSync::ReadAsText(Blob& aBlob,
+ const Optional<nsAString>& aEncoding,
+ nsAString& aResult,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIInputStream> stream;
+ aBlob.GetInternalStream(getter_AddRefs(stream), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ nsAutoCString encoding;
+ unsigned char sniffBuf[3] = { 0, 0, 0 };
+ uint32_t numRead;
+ aRv = stream->Read(reinterpret_cast<char*>(sniffBuf),
+ sizeof(sniffBuf), &numRead);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ // The BOM sniffing is baked into the "decode" part of the Encoding
+ // Standard, which the File API references.
+ if (!nsContentUtils::CheckForBOM(sniffBuf, numRead, encoding)) {
+ // BOM sniffing failed. Try the API argument.
+ if (!aEncoding.WasPassed() ||
+ !EncodingUtils::FindEncodingForLabel(aEncoding.Value(),
+ encoding)) {
+ // API argument failed. Try the type property of the blob.
+ nsAutoString type16;
+ aBlob.GetType(type16);
+ NS_ConvertUTF16toUTF8 type(type16);
+ nsAutoCString specifiedCharset;
+ bool haveCharset;
+ int32_t charsetStart, charsetEnd;
+ NS_ExtractCharsetFromContentType(type,
+ specifiedCharset,
+ &haveCharset,
+ &charsetStart,
+ &charsetEnd);
+ if (!EncodingUtils::FindEncodingForLabel(specifiedCharset, encoding)) {
+ // Type property failed. Use UTF-8.
+ encoding.AssignLiteral("UTF-8");
+ }
+ }
+ }
+
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(stream);
+ if (!seekable) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Seek to 0 because to undo the BOM sniffing advance. UTF-8 and UTF-16
+ // decoders will swallow the BOM.
+ aRv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ aRv = ConvertStream(stream, encoding.get(), aResult);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+}
+
+void
+FileReaderSync::ReadAsDataURL(Blob& aBlob, nsAString& aResult,
+ ErrorResult& aRv)
+{
+ nsAutoString scratchResult;
+ scratchResult.AssignLiteral("data:");
+
+ nsString contentType;
+ aBlob.GetType(contentType);
+
+ if (contentType.IsEmpty()) {
+ scratchResult.AppendLiteral("application/octet-stream");
+ } else {
+ scratchResult.Append(contentType);
+ }
+ scratchResult.AppendLiteral(";base64,");
+
+ nsCOMPtr<nsIInputStream> stream;
+ aBlob.GetInternalStream(getter_AddRefs(stream), aRv);
+ if (NS_WARN_IF(aRv.Failed())){
+ return;
+ }
+
+ uint64_t size = aBlob.GetSize(aRv);
+ if (NS_WARN_IF(aRv.Failed())){
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> bufferedStream;
+ aRv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream, size);
+ if (NS_WARN_IF(aRv.Failed())){
+ return;
+ }
+
+ nsAutoString encodedData;
+ aRv = Base64EncodeInputStream(bufferedStream, encodedData, size);
+ if (NS_WARN_IF(aRv.Failed())){
+ return;
+ }
+
+ scratchResult.Append(encodedData);
+
+ aResult = scratchResult;
+}
+
+nsresult
+FileReaderSync::ConvertStream(nsIInputStream *aStream,
+ const char *aCharset,
+ nsAString &aResult)
+{
+ nsCOMPtr<nsIConverterInputStream> converterStream =
+ do_CreateInstance("@mozilla.org/intl/converter-input-stream;1");
+ NS_ENSURE_TRUE(converterStream, NS_ERROR_FAILURE);
+
+ nsresult rv = converterStream->Init(aStream, aCharset, 8192,
+ nsIConverterInputStream::DEFAULT_REPLACEMENT_CHARACTER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIUnicharInputStream> unicharStream =
+ do_QueryInterface(converterStream);
+ NS_ENSURE_TRUE(unicharStream, NS_ERROR_FAILURE);
+
+ uint32_t numChars;
+ nsString result;
+ while (NS_SUCCEEDED(unicharStream->ReadString(8192, result, &numChars)) &&
+ numChars > 0) {
+ uint32_t oldLength = aResult.Length();
+ aResult.Append(result);
+ if (aResult.Length() - oldLength != result.Length()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ return rv;
+}
+
diff --git a/dom/workers/FileReaderSync.h b/dom/workers/FileReaderSync.h
new file mode 100644
index 000000000..db8f9d574
--- /dev/null
+++ b/dom/workers/FileReaderSync.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_filereadersync_h__
+#define mozilla_dom_filereadersync_h__
+
+#include "Workers.h"
+
+class nsIInputStream;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+class Blob;
+class GlobalObject;
+template<typename> class Optional;
+
+class FileReaderSync final
+{
+ NS_INLINE_DECL_REFCOUNTING(FileReaderSync)
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~FileReaderSync()
+ {
+ }
+
+ nsresult ConvertStream(nsIInputStream *aStream, const char *aCharset,
+ nsAString &aResult);
+
+public:
+ static already_AddRefed<FileReaderSync>
+ Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aReflector);
+
+ void ReadAsArrayBuffer(JSContext* aCx, JS::Handle<JSObject*> aScopeObj,
+ Blob& aBlob, JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv);
+ void ReadAsBinaryString(Blob& aBlob, nsAString& aResult, ErrorResult& aRv);
+ void ReadAsText(Blob& aBlob, const Optional<nsAString>& aEncoding,
+ nsAString& aResult, ErrorResult& aRv);
+ void ReadAsDataURL(Blob& aBlob, nsAString& aResult, ErrorResult& aRv);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_filereadersync_h__
diff --git a/dom/workers/PServiceWorkerManager.ipdl b/dom/workers/PServiceWorkerManager.ipdl
new file mode 100644
index 000000000..e7b97672d
--- /dev/null
+++ b/dom/workers/PServiceWorkerManager.ipdl
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PBackground;
+
+include PBackgroundSharedTypes;
+include ServiceWorkerRegistrarTypes;
+
+using mozilla::PrincipalOriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+
+namespace mozilla {
+namespace dom {
+
+protocol PServiceWorkerManager
+{
+ manager PBackground;
+
+parent:
+ async Register(ServiceWorkerRegistrationData data);
+
+ async Unregister(PrincipalInfo principalInfo, nsString scope);
+
+ async PropagateSoftUpdate(PrincipalOriginAttributes originAttributes,
+ nsString scope);
+ async PropagateUnregister(PrincipalInfo principalInfo, nsString scope);
+
+ async PropagateRemove(nsCString host);
+
+ async PropagateRemoveAll();
+
+ async Shutdown();
+
+child:
+ async NotifyRegister(ServiceWorkerRegistrationData data);
+ async NotifySoftUpdate(PrincipalOriginAttributes originAttributes, nsString scope);
+ async NotifyUnregister(PrincipalInfo principalInfo, nsString scope);
+ async NotifyRemove(nsCString host);
+ async NotifyRemoveAll();
+
+ async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/Principal.cpp b/dom/workers/Principal.cpp
new file mode 100644
index 000000000..8fcd9ed10
--- /dev/null
+++ b/dom/workers/Principal.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Principal.h"
+
+#include "jsapi.h"
+#include "mozilla/Assertions.h"
+
+BEGIN_WORKERS_NAMESPACE
+
+struct WorkerPrincipal final : public JSPrincipals
+{
+ bool write(JSContext* aCx, JSStructuredCloneWriter* aWriter) override {
+ MOZ_CRASH("WorkerPrincipal::write not implemented");
+ return false;
+ }
+};
+
+JSPrincipals*
+GetWorkerPrincipal()
+{
+ static WorkerPrincipal sPrincipal;
+
+ /*
+ * To make sure the the principals refcount is initialized to one, atomically
+ * increment it on every pass though this function. If we discover this wasn't
+ * the first time, decrement it again. This avoids the need for
+ * synchronization.
+ */
+ int32_t prevRefcount = sPrincipal.refcount++;
+ if (prevRefcount > 0) {
+ --sPrincipal.refcount;
+ } else {
+#ifdef DEBUG
+ sPrincipal.debugToken = kJSPrincipalsDebugToken;
+#endif
+ }
+
+ return &sPrincipal;
+}
+
+void
+DestroyWorkerPrincipals(JSPrincipals* aPrincipals)
+{
+ MOZ_ASSERT_UNREACHABLE("Worker principals refcount should never fall below one");
+}
+
+END_WORKERS_NAMESPACE
diff --git a/dom/workers/Principal.h b/dom/workers/Principal.h
new file mode 100644
index 000000000..5bfe19443
--- /dev/null
+++ b/dom/workers/Principal.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_principal_h__
+#define mozilla_dom_workers_principal_h__
+
+#include "Workers.h"
+
+BEGIN_WORKERS_NAMESPACE
+
+JSPrincipals*
+GetWorkerPrincipal();
+
+void
+DestroyWorkerPrincipals(JSPrincipals* aPrincipals);
+
+END_WORKERS_NAMESPACE
+
+#endif /* mozilla_dom_workers_principal_h__ */
diff --git a/dom/workers/Queue.h b/dom/workers/Queue.h
new file mode 100644
index 000000000..c7a99158b
--- /dev/null
+++ b/dom/workers/Queue.h
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_queue_h__
+#define mozilla_dom_workers_queue_h__
+
+#include "Workers.h"
+
+#include "mozilla/Mutex.h"
+#include "nsTArray.h"
+
+BEGIN_WORKERS_NAMESPACE
+
+template <typename T, int TCount>
+struct StorageWithTArray
+{
+ typedef AutoTArray<T, TCount> StorageType;
+
+ static void Reverse(StorageType& aStorage)
+ {
+ uint32_t length = aStorage.Length();
+ for (uint32_t index = 0; index < length / 2; index++) {
+ uint32_t reverseIndex = length - 1 - index;
+
+ T t1 = aStorage.ElementAt(index);
+ T t2 = aStorage.ElementAt(reverseIndex);
+
+ aStorage.ReplaceElementsAt(index, 1, t2);
+ aStorage.ReplaceElementsAt(reverseIndex, 1, t1);
+ }
+ }
+
+ static bool IsEmpty(const StorageType& aStorage)
+ {
+ return !!aStorage.IsEmpty();
+ }
+
+ static bool Push(StorageType& aStorage, const T& aEntry)
+ {
+ return !!aStorage.AppendElement(aEntry);
+ }
+
+ static bool Pop(StorageType& aStorage, T& aEntry)
+ {
+ if (IsEmpty(aStorage)) {
+ return false;
+ }
+
+ uint32_t index = aStorage.Length() - 1;
+ aEntry = aStorage.ElementAt(index);
+ aStorage.RemoveElementAt(index);
+ return true;
+ }
+
+ static void Clear(StorageType& aStorage)
+ {
+ aStorage.Clear();
+ }
+
+ static void Compact(StorageType& aStorage)
+ {
+ aStorage.Compact();
+ }
+};
+
+class LockingWithMutex
+{
+ mozilla::Mutex mMutex;
+
+protected:
+ LockingWithMutex()
+ : mMutex("LockingWithMutex::mMutex")
+ { }
+
+ void Lock()
+ {
+ mMutex.Lock();
+ }
+
+ void Unlock()
+ {
+ mMutex.Unlock();
+ }
+
+ class AutoLock
+ {
+ LockingWithMutex& mHost;
+
+ public:
+ explicit AutoLock(LockingWithMutex& aHost)
+ : mHost(aHost)
+ {
+ mHost.Lock();
+ }
+
+ ~AutoLock()
+ {
+ mHost.Unlock();
+ }
+ };
+
+ friend class AutoLock;
+};
+
+class NoLocking
+{
+protected:
+ void Lock()
+ { }
+
+ void Unlock()
+ { }
+
+ class AutoLock
+ {
+ public:
+ explicit AutoLock(NoLocking& aHost)
+ { }
+
+ ~AutoLock()
+ { }
+ };
+};
+
+template <typename T,
+ int TCount = 256,
+ class LockingPolicy = NoLocking,
+ class StoragePolicy = StorageWithTArray<T, TCount % 2 ?
+ TCount / 2 + 1 :
+ TCount / 2> >
+class Queue : public LockingPolicy
+{
+ typedef typename StoragePolicy::StorageType StorageType;
+ typedef typename LockingPolicy::AutoLock AutoLock;
+
+ StorageType mStorage1;
+ StorageType mStorage2;
+
+ StorageType* mFront;
+ StorageType* mBack;
+
+public:
+ Queue()
+ : mFront(&mStorage1), mBack(&mStorage2)
+ { }
+
+ bool IsEmpty()
+ {
+ AutoLock lock(*this);
+ return StoragePolicy::IsEmpty(*mFront) &&
+ StoragePolicy::IsEmpty(*mBack);
+ }
+
+ bool Push(const T& aEntry)
+ {
+ AutoLock lock(*this);
+ return StoragePolicy::Push(*mBack, aEntry);
+ }
+
+ bool Pop(T& aEntry)
+ {
+ AutoLock lock(*this);
+ if (StoragePolicy::IsEmpty(*mFront)) {
+ StoragePolicy::Compact(*mFront);
+ StoragePolicy::Reverse(*mBack);
+ StorageType* tmp = mFront;
+ mFront = mBack;
+ mBack = tmp;
+ }
+ return StoragePolicy::Pop(*mFront, aEntry);
+ }
+
+ void Clear()
+ {
+ AutoLock lock(*this);
+ StoragePolicy::Clear(*mFront);
+ StoragePolicy::Clear(*mBack);
+ }
+
+ // XXX Do we need this?
+ void Lock()
+ {
+ LockingPolicy::Lock();
+ }
+
+ // XXX Do we need this?
+ void Unlock()
+ {
+ LockingPolicy::Unlock();
+ }
+
+private:
+ // Queue is not copyable.
+ Queue(const Queue&);
+ Queue & operator=(const Queue&);
+};
+
+END_WORKERS_NAMESPACE
+
+#endif /* mozilla_dom_workers_queue_h__ */
diff --git a/dom/workers/RegisterBindings.cpp b/dom/workers/RegisterBindings.cpp
new file mode 100644
index 000000000..b6c1e9cfd
--- /dev/null
+++ b/dom/workers/RegisterBindings.cpp
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WorkerPrivate.h"
+#include "ChromeWorkerScope.h"
+#include "RuntimeService.h"
+
+#include "jsapi.h"
+#include "mozilla/dom/RegisterWorkerBindings.h"
+#include "mozilla/dom/RegisterWorkerDebuggerBindings.h"
+#include "mozilla/OSFileConstants.h"
+
+USING_WORKERS_NAMESPACE
+using namespace mozilla::dom;
+
+bool
+WorkerPrivate::RegisterBindings(JSContext* aCx, JS::Handle<JSObject*> aGlobal)
+{
+ // Init Web IDL bindings
+ if (!RegisterWorkerBindings(aCx, aGlobal)) {
+ return false;
+ }
+
+ if (IsChromeWorker()) {
+ if (!DefineChromeWorkerFunctions(aCx, aGlobal) ||
+ !DefineOSFileConstants(aCx, aGlobal)) {
+ return false;
+ }
+ }
+
+ if (!JS_DefineProfilingFunctions(aCx, aGlobal)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+WorkerPrivate::RegisterDebuggerBindings(JSContext* aCx,
+ JS::Handle<JSObject*> aGlobal)
+{
+ // Init Web IDL bindings
+ if (!RegisterWorkerDebuggerBindings(aCx, aGlobal)) {
+ return false;
+ }
+
+ if (!JS_DefineDebuggerObject(aCx, aGlobal)) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp
new file mode 100644
index 000000000..d1d76e3d1
--- /dev/null
+++ b/dom/workers/RuntimeService.cpp
@@ -0,0 +1,2966 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RuntimeService.h"
+
+#include "nsAutoPtr.h"
+#include "nsIChannel.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIDocument.h"
+#include "nsIDOMChromeWindow.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIObserverService.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISupportsPriority.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsPIDOMWindow.h"
+
+#include <algorithm>
+#include "BackgroundChild.h"
+#include "GeckoProfiler.h"
+#include "jsfriendapi.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/asmjscache/AsmJSCache.h"
+#include "mozilla/dom/AtomList.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/ErrorEventBinding.h"
+#include "mozilla/dom/EventTargetBinding.h"
+#include "mozilla/dom/MessageChannel.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/WorkerBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/IndexedDatabaseManager.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Navigator.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollector.h"
+#include "nsDOMJSUtils.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
+#include "nsISupportsImpl.h"
+#include "nsLayoutStatics.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOM.h"
+#include "nsXPCOMPrivate.h"
+#include "OSFileConstants.h"
+#include "xpcpublic.h"
+
+#include "Principal.h"
+#include "SharedWorker.h"
+#include "WorkerDebuggerManager.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "WorkerScope.h"
+#include "WorkerThread.h"
+#include "prsystem.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+USING_WORKERS_NAMESPACE
+
+using mozilla::MutexAutoLock;
+using mozilla::MutexAutoUnlock;
+using mozilla::Preferences;
+
+// The size of the worker runtime heaps in bytes. May be changed via pref.
+#define WORKER_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024
+
+// The size of the generational GC nursery for workers, in bytes.
+#define WORKER_DEFAULT_NURSERY_SIZE 1 * 1024 * 1024
+
+// The size of the worker JS allocation threshold in MB. May be changed via pref.
+#define WORKER_DEFAULT_ALLOCATION_THRESHOLD 30
+
+// Half the size of the actual C stack, to be safe.
+#define WORKER_CONTEXT_NATIVE_STACK_LIMIT 128 * sizeof(size_t) * 1024
+
+// The maximum number of hardware concurrency, overridable via pref.
+#define MAX_HARDWARE_CONCURRENCY 8
+
+// The maximum number of threads to use for workers, overridable via pref.
+#define MAX_WORKERS_PER_DOMAIN 512
+
+static_assert(MAX_WORKERS_PER_DOMAIN >= 1,
+ "We should allow at least one worker per domain.");
+
+// The default number of seconds that close handlers will be allowed to run for
+// content workers.
+#define MAX_SCRIPT_RUN_TIME_SEC 10
+
+// The number of seconds that idle threads can hang around before being killed.
+#define IDLE_THREAD_TIMEOUT_SEC 30
+
+// The maximum number of threads that can be idle at one time.
+#define MAX_IDLE_THREADS 20
+
+#define PREF_WORKERS_PREFIX "dom.workers."
+#define PREF_WORKERS_MAX_PER_DOMAIN PREF_WORKERS_PREFIX "maxPerDomain"
+#define PREF_WORKERS_MAX_HARDWARE_CONCURRENCY "dom.maxHardwareConcurrency"
+
+#define PREF_MAX_SCRIPT_RUN_TIME_CONTENT "dom.max_script_run_time"
+#define PREF_MAX_SCRIPT_RUN_TIME_CHROME "dom.max_chrome_script_run_time"
+
+#define GC_REQUEST_OBSERVER_TOPIC "child-gc-request"
+#define CC_REQUEST_OBSERVER_TOPIC "child-cc-request"
+#define MEMORY_PRESSURE_OBSERVER_TOPIC "memory-pressure"
+
+#define BROADCAST_ALL_WORKERS(_func, ...) \
+ PR_BEGIN_MACRO \
+ AssertIsOnMainThread(); \
+ \
+ AutoTArray<WorkerPrivate*, 100> workers; \
+ { \
+ MutexAutoLock lock(mMutex); \
+ \
+ AddAllTopLevelWorkersToArray(workers); \
+ } \
+ \
+ if (!workers.IsEmpty()) { \
+ for (uint32_t index = 0; index < workers.Length(); index++) { \
+ workers[index]-> _func (__VA_ARGS__); \
+ } \
+ } \
+ PR_END_MACRO
+
+// Prefixes for observing preference changes.
+#define PREF_JS_OPTIONS_PREFIX "javascript.options."
+#define PREF_WORKERS_OPTIONS_PREFIX PREF_WORKERS_PREFIX "options."
+#define PREF_MEM_OPTIONS_PREFIX "mem."
+#define PREF_GCZEAL "gcZeal"
+
+namespace {
+
+const uint32_t kNoIndex = uint32_t(-1);
+
+uint32_t gMaxWorkersPerDomain = MAX_WORKERS_PER_DOMAIN;
+uint32_t gMaxHardwareConcurrency = MAX_HARDWARE_CONCURRENCY;
+
+// Does not hold an owning reference.
+RuntimeService* gRuntimeService = nullptr;
+
+// Only true during the call to Init.
+bool gRuntimeServiceDuringInit = false;
+
+class LiteralRebindingCString : public nsDependentCString
+{
+public:
+ template<int N>
+ void RebindLiteral(const char (&aStr)[N])
+ {
+ Rebind(aStr, N-1);
+ }
+};
+
+template <typename T>
+struct PrefTraits;
+
+template <>
+struct PrefTraits<bool>
+{
+ typedef bool PrefValueType;
+
+ static const PrefValueType kDefaultValue = false;
+
+ static inline PrefValueType
+ Get(const char* aPref)
+ {
+ AssertIsOnMainThread();
+ return Preferences::GetBool(aPref);
+ }
+
+ static inline bool
+ Exists(const char* aPref)
+ {
+ AssertIsOnMainThread();
+ return Preferences::GetType(aPref) == nsIPrefBranch::PREF_BOOL;
+ }
+};
+
+template <>
+struct PrefTraits<int32_t>
+{
+ typedef int32_t PrefValueType;
+
+ static inline PrefValueType
+ Get(const char* aPref)
+ {
+ AssertIsOnMainThread();
+ return Preferences::GetInt(aPref);
+ }
+
+ static inline bool
+ Exists(const char* aPref)
+ {
+ AssertIsOnMainThread();
+ return Preferences::GetType(aPref) == nsIPrefBranch::PREF_INT;
+ }
+};
+
+template <typename T>
+T
+GetWorkerPref(const nsACString& aPref,
+ const T aDefault = PrefTraits<T>::kDefaultValue)
+{
+ AssertIsOnMainThread();
+
+ typedef PrefTraits<T> PrefHelper;
+
+ T result;
+
+ nsAutoCString prefName;
+ prefName.AssignLiteral(PREF_WORKERS_OPTIONS_PREFIX);
+ prefName.Append(aPref);
+
+ if (PrefHelper::Exists(prefName.get())) {
+ result = PrefHelper::Get(prefName.get());
+ }
+ else {
+ prefName.AssignLiteral(PREF_JS_OPTIONS_PREFIX);
+ prefName.Append(aPref);
+
+ if (PrefHelper::Exists(prefName.get())) {
+ result = PrefHelper::Get(prefName.get());
+ }
+ else {
+ result = aDefault;
+ }
+ }
+
+ return result;
+}
+
+// This fn creates a key for a SharedWorker that contains the name, script
+// spec, and the serialized origin attributes:
+// "name|scriptSpec^key1=val1&key2=val2&key3=val3"
+void
+GenerateSharedWorkerKey(const nsACString& aScriptSpec,
+ const nsACString& aName,
+ const PrincipalOriginAttributes& aAttrs,
+ nsCString& aKey)
+{
+ nsAutoCString suffix;
+ aAttrs.CreateSuffix(suffix);
+
+ aKey.Truncate();
+ aKey.SetCapacity(aName.Length() + aScriptSpec.Length() + suffix.Length() + 2);
+ aKey.Append(aName);
+ aKey.Append('|');
+ aKey.Append(aScriptSpec);
+ aKey.Append(suffix);
+}
+
+void
+LoadContextOptions(const char* aPrefName, void* /* aClosure */)
+{
+ AssertIsOnMainThread();
+
+ RuntimeService* rts = RuntimeService::GetService();
+ if (!rts) {
+ // May be shutting down, just bail.
+ return;
+ }
+
+ const nsDependentCString prefName(aPrefName);
+
+ // Several other pref branches will get included here so bail out if there is
+ // another callback that will handle this change.
+ if (StringBeginsWith(prefName,
+ NS_LITERAL_CSTRING(PREF_JS_OPTIONS_PREFIX
+ PREF_MEM_OPTIONS_PREFIX)) ||
+ StringBeginsWith(prefName,
+ NS_LITERAL_CSTRING(PREF_WORKERS_OPTIONS_PREFIX
+ PREF_MEM_OPTIONS_PREFIX))) {
+ return;
+ }
+
+#ifdef JS_GC_ZEAL
+ if (prefName.EqualsLiteral(PREF_JS_OPTIONS_PREFIX PREF_GCZEAL) ||
+ prefName.EqualsLiteral(PREF_WORKERS_OPTIONS_PREFIX PREF_GCZEAL)) {
+ return;
+ }
+#endif
+
+ // Context options.
+ JS::ContextOptions contextOptions;
+ contextOptions.setAsmJS(GetWorkerPref<bool>(NS_LITERAL_CSTRING("asmjs")))
+ .setWasm(GetWorkerPref<bool>(NS_LITERAL_CSTRING("wasm")))
+ .setThrowOnAsmJSValidationFailure(GetWorkerPref<bool>(
+ NS_LITERAL_CSTRING("throw_on_asmjs_validation_failure")))
+ .setBaseline(GetWorkerPref<bool>(NS_LITERAL_CSTRING("baselinejit")))
+ .setIon(GetWorkerPref<bool>(NS_LITERAL_CSTRING("ion")))
+ .setNativeRegExp(GetWorkerPref<bool>(NS_LITERAL_CSTRING("native_regexp")))
+ .setAsyncStack(GetWorkerPref<bool>(NS_LITERAL_CSTRING("asyncstack")))
+ .setWerror(GetWorkerPref<bool>(NS_LITERAL_CSTRING("werror")))
+ .setExtraWarnings(GetWorkerPref<bool>(NS_LITERAL_CSTRING("strict")));
+
+ RuntimeService::SetDefaultContextOptions(contextOptions);
+
+ if (rts) {
+ rts->UpdateAllWorkerContextOptions();
+ }
+}
+
+#ifdef JS_GC_ZEAL
+void
+LoadGCZealOptions(const char* /* aPrefName */, void* /* aClosure */)
+{
+ AssertIsOnMainThread();
+
+ RuntimeService* rts = RuntimeService::GetService();
+ if (!rts) {
+ // May be shutting down, just bail.
+ return;
+ }
+
+ int32_t gczeal = GetWorkerPref<int32_t>(NS_LITERAL_CSTRING(PREF_GCZEAL), -1);
+ if (gczeal < 0) {
+ gczeal = 0;
+ }
+
+ int32_t frequency =
+ GetWorkerPref<int32_t>(NS_LITERAL_CSTRING("gcZeal.frequency"), -1);
+ if (frequency < 0) {
+ frequency = JS_DEFAULT_ZEAL_FREQ;
+ }
+
+ RuntimeService::SetDefaultGCZeal(uint8_t(gczeal), uint32_t(frequency));
+
+ if (rts) {
+ rts->UpdateAllWorkerGCZeal();
+ }
+}
+#endif
+
+void
+UpdateCommonJSGCMemoryOption(RuntimeService* aRuntimeService,
+ const nsACString& aPrefName, JSGCParamKey aKey)
+{
+ AssertIsOnMainThread();
+ NS_ASSERTION(!aPrefName.IsEmpty(), "Empty pref name!");
+
+ int32_t prefValue = GetWorkerPref(aPrefName, -1);
+ uint32_t value =
+ (prefValue < 0 || prefValue >= 10000) ? 0 : uint32_t(prefValue);
+
+ RuntimeService::SetDefaultJSGCSettings(aKey, value);
+
+ if (aRuntimeService) {
+ aRuntimeService->UpdateAllWorkerMemoryParameter(aKey, value);
+ }
+}
+
+void
+UpdateOtherJSGCMemoryOption(RuntimeService* aRuntimeService,
+ JSGCParamKey aKey, uint32_t aValue)
+{
+ AssertIsOnMainThread();
+
+ RuntimeService::SetDefaultJSGCSettings(aKey, aValue);
+
+ if (aRuntimeService) {
+ aRuntimeService->UpdateAllWorkerMemoryParameter(aKey, aValue);
+ }
+}
+
+
+void
+LoadJSGCMemoryOptions(const char* aPrefName, void* /* aClosure */)
+{
+ AssertIsOnMainThread();
+
+ RuntimeService* rts = RuntimeService::GetService();
+
+ if (!rts) {
+ // May be shutting down, just bail.
+ return;
+ }
+
+ NS_NAMED_LITERAL_CSTRING(jsPrefix, PREF_JS_OPTIONS_PREFIX);
+ NS_NAMED_LITERAL_CSTRING(workersPrefix, PREF_WORKERS_OPTIONS_PREFIX);
+
+ const nsDependentCString fullPrefName(aPrefName);
+
+ // Pull out the string that actually distinguishes the parameter we need to
+ // change.
+ nsDependentCSubstring memPrefName;
+ if (StringBeginsWith(fullPrefName, jsPrefix)) {
+ memPrefName.Rebind(fullPrefName, jsPrefix.Length());
+ }
+ else if (StringBeginsWith(fullPrefName, workersPrefix)) {
+ memPrefName.Rebind(fullPrefName, workersPrefix.Length());
+ }
+ else {
+ NS_ERROR("Unknown pref name!");
+ return;
+ }
+
+#ifdef DEBUG
+ // During Init() we get called back with a branch string here, so there should
+ // be no just a "mem." pref here.
+ if (!rts) {
+ NS_ASSERTION(memPrefName.EqualsLiteral(PREF_MEM_OPTIONS_PREFIX), "Huh?!");
+ }
+#endif
+
+ // If we're running in Init() then do this for every pref we care about.
+ // Otherwise we just want to update the parameter that changed.
+ for (uint32_t index = !gRuntimeServiceDuringInit
+ ? JSSettings::kGCSettingsArraySize - 1 : 0;
+ index < JSSettings::kGCSettingsArraySize;
+ index++) {
+ LiteralRebindingCString matchName;
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "max");
+ if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 0)) {
+ int32_t prefValue = GetWorkerPref(matchName, -1);
+ uint32_t value = (prefValue <= 0 || prefValue >= 0x1000) ?
+ uint32_t(-1) :
+ uint32_t(prefValue) * 1024 * 1024;
+ UpdateOtherJSGCMemoryOption(rts, JSGC_MAX_BYTES, value);
+ continue;
+ }
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "high_water_mark");
+ if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 1)) {
+ int32_t prefValue = GetWorkerPref(matchName, 128);
+ UpdateOtherJSGCMemoryOption(rts, JSGC_MAX_MALLOC_BYTES,
+ uint32_t(prefValue) * 1024 * 1024);
+ continue;
+ }
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
+ "gc_high_frequency_time_limit_ms");
+ if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 2)) {
+ UpdateCommonJSGCMemoryOption(rts, matchName,
+ JSGC_HIGH_FREQUENCY_TIME_LIMIT);
+ continue;
+ }
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
+ "gc_low_frequency_heap_growth");
+ if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 3)) {
+ UpdateCommonJSGCMemoryOption(rts, matchName,
+ JSGC_LOW_FREQUENCY_HEAP_GROWTH);
+ continue;
+ }
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
+ "gc_high_frequency_heap_growth_min");
+ if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 4)) {
+ UpdateCommonJSGCMemoryOption(rts, matchName,
+ JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN);
+ continue;
+ }
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
+ "gc_high_frequency_heap_growth_max");
+ if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 5)) {
+ UpdateCommonJSGCMemoryOption(rts, matchName,
+ JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX);
+ continue;
+ }
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
+ "gc_high_frequency_low_limit_mb");
+ if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 6)) {
+ UpdateCommonJSGCMemoryOption(rts, matchName,
+ JSGC_HIGH_FREQUENCY_LOW_LIMIT);
+ continue;
+ }
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
+ "gc_high_frequency_high_limit_mb");
+ if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 7)) {
+ UpdateCommonJSGCMemoryOption(rts, matchName,
+ JSGC_HIGH_FREQUENCY_HIGH_LIMIT);
+ continue;
+ }
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX
+ "gc_allocation_threshold_mb");
+ if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 8)) {
+ UpdateCommonJSGCMemoryOption(rts, matchName, JSGC_ALLOCATION_THRESHOLD);
+ continue;
+ }
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_incremental_slice_ms");
+ if (memPrefName == matchName || (gRuntimeServiceDuringInit && index == 9)) {
+ int32_t prefValue = GetWorkerPref(matchName, -1);
+ uint32_t value =
+ (prefValue <= 0 || prefValue >= 100000) ? 0 : uint32_t(prefValue);
+ UpdateOtherJSGCMemoryOption(rts, JSGC_SLICE_TIME_BUDGET, value);
+ continue;
+ }
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_dynamic_heap_growth");
+ if (memPrefName == matchName ||
+ (gRuntimeServiceDuringInit && index == 10)) {
+ bool prefValue = GetWorkerPref(matchName, false);
+ UpdateOtherJSGCMemoryOption(rts, JSGC_DYNAMIC_HEAP_GROWTH,
+ prefValue ? 0 : 1);
+ continue;
+ }
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_dynamic_mark_slice");
+ if (memPrefName == matchName ||
+ (gRuntimeServiceDuringInit && index == 11)) {
+ bool prefValue = GetWorkerPref(matchName, false);
+ UpdateOtherJSGCMemoryOption(rts, JSGC_DYNAMIC_MARK_SLICE,
+ prefValue ? 0 : 1);
+ continue;
+ }
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_min_empty_chunk_count");
+ if (memPrefName == matchName ||
+ (gRuntimeServiceDuringInit && index == 12)) {
+ UpdateCommonJSGCMemoryOption(rts, matchName, JSGC_MIN_EMPTY_CHUNK_COUNT);
+ continue;
+ }
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_max_empty_chunk_count");
+ if (memPrefName == matchName ||
+ (gRuntimeServiceDuringInit && index == 13)) {
+ UpdateCommonJSGCMemoryOption(rts, matchName, JSGC_MAX_EMPTY_CHUNK_COUNT);
+ continue;
+ }
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_compacting");
+ if (memPrefName == matchName ||
+ (gRuntimeServiceDuringInit && index == 14)) {
+ bool prefValue = GetWorkerPref(matchName, false);
+ UpdateOtherJSGCMemoryOption(rts, JSGC_COMPACTING_ENABLED,
+ prefValue ? 0 : 1);
+ continue;
+ }
+
+ matchName.RebindLiteral(PREF_MEM_OPTIONS_PREFIX "gc_refresh_frame_slices_enabled");
+ if (memPrefName == matchName ||
+ (gRuntimeServiceDuringInit && index == 15)) {
+ bool prefValue = GetWorkerPref(matchName, false);
+ UpdateOtherJSGCMemoryOption(rts, JSGC_REFRESH_FRAME_SLICES_ENABLED,
+ prefValue ? 0 : 1);
+ continue;
+ }
+
+#ifdef DEBUG
+ nsAutoCString message("Workers don't support the 'mem.");
+ message.Append(memPrefName);
+ message.AppendLiteral("' preference!");
+ NS_WARNING(message.get());
+#endif
+ }
+}
+
+bool
+InterruptCallback(JSContext* aCx)
+{
+ WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
+ MOZ_ASSERT(worker);
+
+ // Now is a good time to turn on profiling if it's pending.
+ profiler_js_operation_callback();
+
+ return worker->InterruptCallback(aCx);
+}
+
+class LogViolationDetailsRunnable final : public WorkerMainThreadRunnable
+{
+ nsString mFileName;
+ uint32_t mLineNum;
+
+public:
+ LogViolationDetailsRunnable(WorkerPrivate* aWorker,
+ const nsString& aFileName,
+ uint32_t aLineNum)
+ : WorkerMainThreadRunnable(aWorker,
+ NS_LITERAL_CSTRING("RuntimeService :: LogViolationDetails"))
+ , mFileName(aFileName), mLineNum(aLineNum)
+ {
+ MOZ_ASSERT(aWorker);
+ }
+
+ virtual bool MainThreadRun() override;
+
+private:
+ ~LogViolationDetailsRunnable() {}
+};
+
+bool
+ContentSecurityPolicyAllows(JSContext* aCx)
+{
+ WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
+ worker->AssertIsOnWorkerThread();
+
+ if (worker->GetReportCSPViolations()) {
+ nsString fileName;
+ uint32_t lineNum = 0;
+
+ JS::AutoFilename file;
+ if (JS::DescribeScriptedCaller(aCx, &file, &lineNum) && file.get()) {
+ fileName = NS_ConvertUTF8toUTF16(file.get());
+ } else {
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+ }
+
+ RefPtr<LogViolationDetailsRunnable> runnable =
+ new LogViolationDetailsRunnable(worker, fileName, lineNum);
+
+ ErrorResult rv;
+ runnable->Dispatch(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ }
+ }
+
+ return worker->IsEvalAllowed();
+}
+
+void
+CTypesActivityCallback(JSContext* aCx,
+ js::CTypesActivityType aType)
+{
+ WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
+ worker->AssertIsOnWorkerThread();
+
+ switch (aType) {
+ case js::CTYPES_CALL_BEGIN:
+ worker->BeginCTypesCall();
+ break;
+
+ case js::CTYPES_CALL_END:
+ worker->EndCTypesCall();
+ break;
+
+ case js::CTYPES_CALLBACK_BEGIN:
+ worker->BeginCTypesCallback();
+ break;
+
+ case js::CTYPES_CALLBACK_END:
+ worker->EndCTypesCallback();
+ break;
+
+ default:
+ MOZ_CRASH("Unknown type flag!");
+ }
+}
+
+static nsIPrincipal*
+GetPrincipalForAsmJSCacheOp()
+{
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ if (!workerPrivate) {
+ return nullptr;
+ }
+
+ // asmjscache::OpenEntryForX guarnatee to only access the given nsIPrincipal
+ // from the main thread.
+ return workerPrivate->GetPrincipalDontAssertMainThread();
+}
+
+static bool
+AsmJSCacheOpenEntryForRead(JS::Handle<JSObject*> aGlobal,
+ const char16_t* aBegin,
+ const char16_t* aLimit,
+ size_t* aSize,
+ const uint8_t** aMemory,
+ intptr_t *aHandle)
+{
+ nsIPrincipal* principal = GetPrincipalForAsmJSCacheOp();
+ if (!principal) {
+ return false;
+ }
+
+ return asmjscache::OpenEntryForRead(principal, aBegin, aLimit, aSize, aMemory,
+ aHandle);
+}
+
+static JS::AsmJSCacheResult
+AsmJSCacheOpenEntryForWrite(JS::Handle<JSObject*> aGlobal,
+ bool aInstalled,
+ const char16_t* aBegin,
+ const char16_t* aEnd,
+ size_t aSize,
+ uint8_t** aMemory,
+ intptr_t* aHandle)
+{
+ nsIPrincipal* principal = GetPrincipalForAsmJSCacheOp();
+ if (!principal) {
+ return JS::AsmJSCache_InternalError;
+ }
+
+ return asmjscache::OpenEntryForWrite(principal, aInstalled, aBegin, aEnd,
+ aSize, aMemory, aHandle);
+}
+
+class AsyncTaskWorkerHolder final : public WorkerHolder
+{
+ bool Notify(Status aStatus) override
+ {
+ // The async task must complete in bounded time and there is not (currently)
+ // a clean way to cancel it. Async tasks do not run arbitrary content.
+ return true;
+ }
+
+public:
+ WorkerPrivate* Worker() const
+ {
+ return mWorkerPrivate;
+ }
+};
+
+template <class RunnableBase>
+class AsyncTaskBase : public RunnableBase
+{
+ UniquePtr<AsyncTaskWorkerHolder> mHolder;
+
+ // Disable the usual pre/post-dispatch thread assertions since we are
+ // dispatching from some random JS engine internal thread:
+
+ bool PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ return true;
+ }
+
+ void PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ { }
+
+protected:
+ explicit AsyncTaskBase(UniquePtr<AsyncTaskWorkerHolder> aHolder)
+ : RunnableBase(aHolder->Worker(),
+ WorkerRunnable::WorkerThreadUnchangedBusyCount)
+ , mHolder(Move(aHolder))
+ {
+ MOZ_ASSERT(mHolder);
+ }
+
+ ~AsyncTaskBase()
+ {
+ MOZ_ASSERT(!mHolder);
+ }
+
+ void DestroyHolder()
+ {
+ MOZ_ASSERT(mHolder);
+ mHolder.reset();
+ }
+
+public:
+ UniquePtr<AsyncTaskWorkerHolder> StealHolder()
+ {
+ return Move(mHolder);
+ }
+};
+
+class AsyncTaskRunnable final : public AsyncTaskBase<WorkerRunnable>
+{
+ JS::AsyncTask* mTask;
+
+ ~AsyncTaskRunnable()
+ {
+ MOZ_ASSERT(!mTask);
+ }
+
+ void PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ // For the benefit of the destructor assert.
+ if (!aDispatchResult) {
+ mTask = nullptr;
+ }
+ }
+
+public:
+ AsyncTaskRunnable(UniquePtr<AsyncTaskWorkerHolder> aHolder,
+ JS::AsyncTask* aTask)
+ : AsyncTaskBase<WorkerRunnable>(Move(aHolder))
+ , mTask(aTask)
+ {
+ MOZ_ASSERT(mTask);
+ }
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
+ MOZ_ASSERT(aCx == mWorkerPrivate->GetJSContext());
+ MOZ_ASSERT(mTask);
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ mTask->finish(mWorkerPrivate->GetJSContext());
+ mTask = nullptr; // mTask may delete itself
+
+ DestroyHolder();
+
+ return true;
+ }
+
+ nsresult Cancel() override
+ {
+ MOZ_ASSERT(mTask);
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ mTask->cancel(mWorkerPrivate->GetJSContext());
+ mTask = nullptr; // mTask may delete itself
+
+ DestroyHolder();
+
+ return WorkerRunnable::Cancel();
+ }
+};
+
+class AsyncTaskControlRunnable final
+ : public AsyncTaskBase<WorkerControlRunnable>
+{
+public:
+ explicit AsyncTaskControlRunnable(UniquePtr<AsyncTaskWorkerHolder> aHolder)
+ : AsyncTaskBase<WorkerControlRunnable>(Move(aHolder))
+ { }
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ // See comment in FinishAsyncTaskCallback.
+ DestroyHolder();
+ return true;
+ }
+};
+
+static bool
+StartAsyncTaskCallback(JSContext* aCx, JS::AsyncTask* aTask)
+{
+ WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
+ worker->AssertIsOnWorkerThread();
+
+ auto holder = MakeUnique<AsyncTaskWorkerHolder>();
+ if (!holder->HoldWorker(worker, Status::Closing)) {
+ return false;
+ }
+
+ // Matched by a UniquePtr in FinishAsyncTaskCallback which, by
+ // interface contract, must be called in the future.
+ aTask->user = holder.release();
+ return true;
+}
+
+static bool
+FinishAsyncTaskCallback(JS::AsyncTask* aTask)
+{
+ // May execute either on the worker thread or a random JS-internal helper
+ // thread.
+
+ // Match the release() in StartAsyncTaskCallback.
+ UniquePtr<AsyncTaskWorkerHolder> holder(
+ static_cast<AsyncTaskWorkerHolder*>(aTask->user));
+
+ RefPtr<AsyncTaskRunnable> r = new AsyncTaskRunnable(Move(holder), aTask);
+
+ // WorkerRunnable::Dispatch() can fail during worker shutdown. In that case,
+ // report failure back to the JS engine but make sure to release the
+ // WorkerHolder on the worker thread using a control runnable. Control
+ // runables aren't suitable for calling AsyncTask::finish() since they are run
+ // via the interrupt callback which breaks JS run-to-completion.
+ if (!r->Dispatch()) {
+ RefPtr<AsyncTaskControlRunnable> cr =
+ new AsyncTaskControlRunnable(r->StealHolder());
+
+ MOZ_ALWAYS_TRUE(cr->Dispatch());
+ return false;
+ }
+
+ return true;
+}
+
+class WorkerJSContext;
+
+class WorkerThreadContextPrivate : private PerThreadAtomCache
+{
+ friend class WorkerJSContext;
+
+ WorkerPrivate* mWorkerPrivate;
+
+public:
+ // This can't return null, but we can't lose the "Get" prefix in the name or
+ // it will be ambiguous with the WorkerPrivate class name.
+ WorkerPrivate*
+ GetWorkerPrivate() const
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(mWorkerPrivate);
+
+ return mWorkerPrivate;
+ }
+
+private:
+ explicit
+ WorkerThreadContextPrivate(WorkerPrivate* aWorkerPrivate)
+ : mWorkerPrivate(aWorkerPrivate)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // Zero out the base class members.
+ memset(this, 0, sizeof(PerThreadAtomCache));
+
+ MOZ_ASSERT(mWorkerPrivate);
+ }
+
+ ~WorkerThreadContextPrivate()
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ WorkerThreadContextPrivate(const WorkerThreadContextPrivate&) = delete;
+
+ WorkerThreadContextPrivate&
+ operator=(const WorkerThreadContextPrivate&) = delete;
+};
+
+bool
+InitJSContextForWorker(WorkerPrivate* aWorkerPrivate, JSContext* aWorkerCx)
+{
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ NS_ASSERTION(!aWorkerPrivate->GetJSContext(), "Already has a context!");
+
+ JSSettings settings;
+ aWorkerPrivate->CopyJSSettings(settings);
+
+ {
+ JS::UniqueChars defaultLocale = aWorkerPrivate->AdoptDefaultLocale();
+ MOZ_ASSERT(defaultLocale,
+ "failure of a WorkerPrivate to have a default locale should "
+ "have made the worker fail to spawn");
+
+ if (!JS_SetDefaultLocale(aWorkerCx, defaultLocale.get())) {
+ NS_WARNING("failed to set workerCx's default locale");
+ return false;
+ }
+ }
+
+ JS::ContextOptionsRef(aWorkerCx) = settings.contextOptions;
+
+ JSSettings::JSGCSettingsArray& gcSettings = settings.gcSettings;
+
+ // This is the real place where we set the max memory for the runtime.
+ for (uint32_t index = 0; index < ArrayLength(gcSettings); index++) {
+ const JSSettings::JSGCSetting& setting = gcSettings[index];
+ if (setting.IsSet()) {
+ NS_ASSERTION(setting.value, "Can't handle 0 values!");
+ JS_SetGCParameter(aWorkerCx, setting.key, setting.value);
+ }
+ }
+
+ JS_SetNativeStackQuota(aWorkerCx, WORKER_CONTEXT_NATIVE_STACK_LIMIT);
+
+ // Security policy:
+ static const JSSecurityCallbacks securityCallbacks = {
+ ContentSecurityPolicyAllows
+ };
+ JS_SetSecurityCallbacks(aWorkerCx, &securityCallbacks);
+
+ // Set up the asm.js cache callbacks
+ static const JS::AsmJSCacheOps asmJSCacheOps = {
+ AsmJSCacheOpenEntryForRead,
+ asmjscache::CloseEntryForRead,
+ AsmJSCacheOpenEntryForWrite,
+ asmjscache::CloseEntryForWrite
+ };
+ JS::SetAsmJSCacheOps(aWorkerCx, &asmJSCacheOps);
+
+ JS::SetAsyncTaskCallbacks(aWorkerCx, StartAsyncTaskCallback, FinishAsyncTaskCallback);
+
+ if (!JS::InitSelfHostedCode(aWorkerCx)) {
+ NS_WARNING("Could not init self-hosted code!");
+ return false;
+ }
+
+ JS_AddInterruptCallback(aWorkerCx, InterruptCallback);
+
+ js::SetCTypesActivityCallback(aWorkerCx, CTypesActivityCallback);
+
+#ifdef JS_GC_ZEAL
+ JS_SetGCZeal(aWorkerCx, settings.gcZeal, settings.gcZealFrequency);
+#endif
+
+ return true;
+}
+
+static bool
+PreserveWrapper(JSContext *cx, JSObject *obj)
+{
+ MOZ_ASSERT(cx);
+ MOZ_ASSERT(obj);
+ MOZ_ASSERT(mozilla::dom::IsDOMObject(obj));
+
+ return mozilla::dom::TryPreserveWrapper(obj);
+}
+
+JSObject*
+Wrap(JSContext *cx, JS::HandleObject existing, JS::HandleObject obj)
+{
+ JSObject* targetGlobal = JS::CurrentGlobalOrNull(cx);
+ if (!IsDebuggerGlobal(targetGlobal) && !IsDebuggerSandbox(targetGlobal)) {
+ MOZ_CRASH("There should be no edges from the debuggee to the debugger.");
+ }
+
+ JSObject* originGlobal = js::GetGlobalForObjectCrossCompartment(obj);
+
+ const js::Wrapper* wrapper = nullptr;
+ if (IsDebuggerGlobal(originGlobal) || IsDebuggerSandbox(originGlobal)) {
+ wrapper = &js::CrossCompartmentWrapper::singleton;
+ } else {
+ wrapper = &js::OpaqueCrossCompartmentWrapper::singleton;
+ }
+
+ if (existing) {
+ js::Wrapper::Renew(cx, existing, obj, wrapper);
+ }
+ return js::Wrapper::New(cx, obj, wrapper);
+}
+
+static const JSWrapObjectCallbacks WrapObjectCallbacks = {
+ Wrap,
+ nullptr,
+};
+
+class MOZ_STACK_CLASS WorkerJSContext final : public mozilla::CycleCollectedJSContext
+{
+public:
+ // The heap size passed here doesn't matter, we will change it later in the
+ // call to JS_SetGCParameter inside InitJSContextForWorker.
+ explicit WorkerJSContext(WorkerPrivate* aWorkerPrivate)
+ : mWorkerPrivate(aWorkerPrivate)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ }
+
+ ~WorkerJSContext()
+ {
+ JSContext* cx = MaybeContext();
+ if (!cx) {
+ return; // Initialize() must have failed
+ }
+
+ delete static_cast<WorkerThreadContextPrivate*>(JS_GetContextPrivate(cx));
+ JS_SetContextPrivate(cx, nullptr);
+
+ // The worker global should be unrooted and the shutdown cycle collection
+ // should break all remaining cycles. The superclass destructor will run
+ // the GC one final time and finalize any JSObjects that were participating
+ // in cycles that were broken during CC shutdown.
+ nsCycleCollector_shutdown();
+
+ // The CC is shut down, and the superclass destructor will GC, so make sure
+ // we don't try to CC again.
+ mWorkerPrivate = nullptr;
+ }
+
+ nsresult Initialize(JSContext* aParentContext)
+ {
+ nsresult rv =
+ CycleCollectedJSContext::Initialize(aParentContext,
+ WORKER_DEFAULT_RUNTIME_HEAPSIZE,
+ WORKER_DEFAULT_NURSERY_SIZE);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ JSContext* cx = Context();
+
+ JS_SetContextPrivate(cx, new WorkerThreadContextPrivate(mWorkerPrivate));
+
+ js::SetPreserveWrapperCallback(cx, PreserveWrapper);
+ JS_InitDestroyPrincipalsCallback(cx, DestroyWorkerPrincipals);
+ JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
+ if (mWorkerPrivate->IsDedicatedWorker()) {
+ JS_SetFutexCanWait(cx);
+ }
+
+ return NS_OK;
+ }
+
+ virtual void
+ PrepareForForgetSkippable() override
+ {
+ }
+
+ virtual void
+ BeginCycleCollectionCallback() override
+ {
+ }
+
+ virtual void
+ EndCycleCollectionCallback(CycleCollectorResults &aResults) override
+ {
+ }
+
+ void
+ DispatchDeferredDeletion(bool aContinuation, bool aPurge) override
+ {
+ MOZ_ASSERT(!aContinuation);
+
+ // Do it immediately, no need for asynchronous behavior here.
+ nsCycleCollector_doDeferredDeletion();
+ }
+
+ virtual void CustomGCCallback(JSGCStatus aStatus) override
+ {
+ if (!mWorkerPrivate) {
+ // We're shutting down, no need to do anything.
+ return;
+ }
+
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (aStatus == JSGC_END) {
+ nsCycleCollector_collect(nullptr);
+ }
+ }
+
+ virtual void AfterProcessTask(uint32_t aRecursionDepth) override
+ {
+ // Only perform the Promise microtask checkpoint on the outermost event
+ // loop. Don't run it, for example, during sync XHR or importScripts.
+ if (aRecursionDepth == 2) {
+ CycleCollectedJSContext::AfterProcessTask(aRecursionDepth);
+ } else if (aRecursionDepth > 2) {
+ AutoDisableMicroTaskCheckpoint disableMicroTaskCheckpoint;
+ CycleCollectedJSContext::AfterProcessTask(aRecursionDepth);
+ }
+ }
+
+ virtual void DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunnable) override
+ {
+ RefPtr<nsIRunnable> runnable(aRunnable);
+
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(runnable);
+
+ std::queue<nsCOMPtr<nsIRunnable>>* microTaskQueue = nullptr;
+
+ JSContext* cx = GetCurrentThreadJSContext();
+ NS_ASSERTION(cx, "This should never be null!");
+
+ JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+ NS_ASSERTION(global, "This should never be null!");
+
+ // On worker threads, if the current global is the worker global, we use the
+ // main promise micro task queue. Otherwise, the current global must be
+ // either the debugger global or a debugger sandbox, and we use the debugger
+ // promise micro task queue instead.
+ if (IsWorkerGlobal(global)) {
+ microTaskQueue = &mPromiseMicroTaskQueue;
+ } else {
+ MOZ_ASSERT(IsDebuggerGlobal(global) || IsDebuggerSandbox(global));
+
+ microTaskQueue = &mDebuggerPromiseMicroTaskQueue;
+ }
+
+ microTaskQueue->push(runnable.forget());
+ }
+
+private:
+ WorkerPrivate* mWorkerPrivate;
+};
+
+class WorkerThreadPrimaryRunnable final : public Runnable
+{
+ WorkerPrivate* mWorkerPrivate;
+ RefPtr<WorkerThread> mThread;
+ JSContext* mParentContext;
+
+ class FinishedRunnable final : public Runnable
+ {
+ RefPtr<WorkerThread> mThread;
+
+ public:
+ explicit FinishedRunnable(already_AddRefed<WorkerThread> aThread)
+ : mThread(aThread)
+ {
+ MOZ_ASSERT(mThread);
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ private:
+ ~FinishedRunnable()
+ { }
+
+ NS_DECL_NSIRUNNABLE
+ };
+
+public:
+ WorkerThreadPrimaryRunnable(WorkerPrivate* aWorkerPrivate,
+ WorkerThread* aThread,
+ JSContext* aParentContext)
+ : mWorkerPrivate(aWorkerPrivate), mThread(aThread), mParentContext(aParentContext)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aThread);
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+private:
+ ~WorkerThreadPrimaryRunnable()
+ { }
+
+ NS_DECL_NSIRUNNABLE
+};
+
+class WorkerTaskRunnable final : public WorkerRunnable
+{
+ RefPtr<WorkerTask> mTask;
+
+public:
+ WorkerTaskRunnable(WorkerPrivate* aWorkerPrivate, WorkerTask* aTask)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), mTask(aTask)
+ {
+ MOZ_ASSERT(aTask);
+ }
+
+private:
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ // May be called on any thread!
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ // May be called on any thread!
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ return mTask->RunTask(aCx);
+ }
+};
+
+void
+PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */)
+{
+ AssertIsOnMainThread();
+
+ nsTArray<nsString> languages;
+ Navigator::GetAcceptLanguages(languages);
+
+ RuntimeService* runtime = RuntimeService::GetService();
+ if (runtime) {
+ runtime->UpdateAllWorkerLanguages(languages);
+ }
+}
+
+void
+AppNameOverrideChanged(const char* /* aPrefName */, void* /* aClosure */)
+{
+ AssertIsOnMainThread();
+
+ const nsAdoptingString& override =
+ mozilla::Preferences::GetString("general.appname.override");
+
+ RuntimeService* runtime = RuntimeService::GetService();
+ if (runtime) {
+ runtime->UpdateAppNameOverridePreference(override);
+ }
+}
+
+void
+AppVersionOverrideChanged(const char* /* aPrefName */, void* /* aClosure */)
+{
+ AssertIsOnMainThread();
+
+ const nsAdoptingString& override =
+ mozilla::Preferences::GetString("general.appversion.override");
+
+ RuntimeService* runtime = RuntimeService::GetService();
+ if (runtime) {
+ runtime->UpdateAppVersionOverridePreference(override);
+ }
+}
+
+void
+PlatformOverrideChanged(const char* /* aPrefName */, void* /* aClosure */)
+{
+ AssertIsOnMainThread();
+
+ const nsAdoptingString& override =
+ mozilla::Preferences::GetString("general.platform.override");
+
+ RuntimeService* runtime = RuntimeService::GetService();
+ if (runtime) {
+ runtime->UpdatePlatformOverridePreference(override);
+ }
+}
+
+class BackgroundChildCallback final
+ : public nsIIPCBackgroundChildCreateCallback
+{
+public:
+ BackgroundChildCallback()
+ {
+ AssertIsOnMainThread();
+ }
+
+ NS_DECL_ISUPPORTS
+
+private:
+ ~BackgroundChildCallback()
+ {
+ AssertIsOnMainThread();
+ }
+
+ virtual void
+ ActorCreated(PBackgroundChild* aActor) override
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aActor);
+ }
+
+ virtual void
+ ActorFailed() override
+ {
+ AssertIsOnMainThread();
+ MOZ_CRASH("Unable to connect PBackground actor for the main thread!");
+ }
+};
+
+NS_IMPL_ISUPPORTS(BackgroundChildCallback, nsIIPCBackgroundChildCreateCallback)
+
+} /* anonymous namespace */
+
+BEGIN_WORKERS_NAMESPACE
+
+void
+CancelWorkersForWindow(nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnMainThread();
+ RuntimeService* runtime = RuntimeService::GetService();
+ if (runtime) {
+ runtime->CancelWorkersForWindow(aWindow);
+ }
+}
+
+void
+FreezeWorkersForWindow(nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnMainThread();
+ RuntimeService* runtime = RuntimeService::GetService();
+ if (runtime) {
+ runtime->FreezeWorkersForWindow(aWindow);
+ }
+}
+
+void
+ThawWorkersForWindow(nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnMainThread();
+ RuntimeService* runtime = RuntimeService::GetService();
+ if (runtime) {
+ runtime->ThawWorkersForWindow(aWindow);
+ }
+}
+
+void
+SuspendWorkersForWindow(nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnMainThread();
+ RuntimeService* runtime = RuntimeService::GetService();
+ if (runtime) {
+ runtime->SuspendWorkersForWindow(aWindow);
+ }
+}
+
+void
+ResumeWorkersForWindow(nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnMainThread();
+ RuntimeService* runtime = RuntimeService::GetService();
+ if (runtime) {
+ runtime->ResumeWorkersForWindow(aWindow);
+ }
+}
+
+WorkerCrossThreadDispatcher::WorkerCrossThreadDispatcher(
+ WorkerPrivate* aWorkerPrivate)
+: mMutex("WorkerCrossThreadDispatcher::mMutex"),
+ mWorkerPrivate(aWorkerPrivate)
+{
+ MOZ_ASSERT(aWorkerPrivate);
+}
+
+bool
+WorkerCrossThreadDispatcher::PostTask(WorkerTask* aTask)
+{
+ MOZ_ASSERT(aTask);
+
+ MutexAutoLock lock(mMutex);
+
+ if (!mWorkerPrivate) {
+ NS_WARNING("Posted a task to a WorkerCrossThreadDispatcher that is no "
+ "longer accepting tasks!");
+ return false;
+ }
+
+ RefPtr<WorkerTaskRunnable> runnable =
+ new WorkerTaskRunnable(mWorkerPrivate, aTask);
+ return runnable->Dispatch();
+}
+
+WorkerPrivate*
+GetWorkerPrivateFromContext(JSContext* aCx)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aCx);
+
+ void* cxPrivate = JS_GetContextPrivate(aCx);
+ if (!cxPrivate) {
+ return nullptr;
+ }
+
+ return
+ static_cast<WorkerThreadContextPrivate*>(cxPrivate)->GetWorkerPrivate();
+}
+
+WorkerPrivate*
+GetCurrentThreadWorkerPrivate()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
+ if (!ccjscx) {
+ return nullptr;
+ }
+
+ JSContext* cx = ccjscx->Context();
+ MOZ_ASSERT(cx);
+
+ void* cxPrivate = JS_GetContextPrivate(cx);
+ if (!cxPrivate) {
+ // This can happen if the nsCycleCollector_shutdown() in ~WorkerJSContext()
+ // triggers any calls to GetCurrentThreadWorkerPrivate(). At this stage
+ // CycleCollectedJSContext::Get() will still return a context, but
+ // the context private has already been cleared.
+ return nullptr;
+ }
+
+ return
+ static_cast<WorkerThreadContextPrivate*>(cxPrivate)->GetWorkerPrivate();
+}
+
+bool
+IsCurrentThreadRunningChromeWorker()
+{
+ return GetCurrentThreadWorkerPrivate()->UsesSystemPrincipal();
+}
+
+JSContext*
+GetCurrentThreadJSContext()
+{
+ WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
+ if (!wp) {
+ return nullptr;
+ }
+ return wp->GetJSContext();
+}
+
+JSObject*
+GetCurrentThreadWorkerGlobal()
+{
+ WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
+ if (!wp) {
+ return nullptr;
+ }
+ WorkerGlobalScope* scope = wp->GlobalScope();
+ if (!scope) {
+ return nullptr;
+ }
+ return scope->GetGlobalJSObject();
+}
+
+END_WORKERS_NAMESPACE
+
+struct RuntimeService::IdleThreadInfo
+{
+ RefPtr<WorkerThread> mThread;
+ mozilla::TimeStamp mExpirationTime;
+};
+
+// This is only touched on the main thread. Initialized in Init() below.
+JSSettings RuntimeService::sDefaultJSSettings;
+bool RuntimeService::sDefaultPreferences[WORKERPREF_COUNT] = { false };
+
+RuntimeService::RuntimeService()
+: mMutex("RuntimeService::mMutex"), mObserved(false),
+ mShuttingDown(false), mNavigatorPropertiesLoaded(false)
+{
+ AssertIsOnMainThread();
+ NS_ASSERTION(!gRuntimeService, "More than one service!");
+}
+
+RuntimeService::~RuntimeService()
+{
+ AssertIsOnMainThread();
+
+ // gRuntimeService can be null if Init() fails.
+ NS_ASSERTION(!gRuntimeService || gRuntimeService == this,
+ "More than one service!");
+
+ gRuntimeService = nullptr;
+}
+
+// static
+RuntimeService*
+RuntimeService::GetOrCreateService()
+{
+ AssertIsOnMainThread();
+
+ if (!gRuntimeService) {
+ // The observer service now owns us until shutdown.
+ gRuntimeService = new RuntimeService();
+ if (NS_FAILED(gRuntimeService->Init())) {
+ NS_WARNING("Failed to initialize!");
+ gRuntimeService->Cleanup();
+ gRuntimeService = nullptr;
+ return nullptr;
+ }
+ }
+
+ return gRuntimeService;
+}
+
+// static
+RuntimeService*
+RuntimeService::GetService()
+{
+ return gRuntimeService;
+}
+
+bool
+RuntimeService::RegisterWorker(WorkerPrivate* aWorkerPrivate)
+{
+ aWorkerPrivate->AssertIsOnParentThread();
+
+ WorkerPrivate* parent = aWorkerPrivate->GetParent();
+ if (!parent) {
+ AssertIsOnMainThread();
+
+ if (mShuttingDown) {
+ return false;
+ }
+ }
+
+ const bool isServiceWorker = aWorkerPrivate->IsServiceWorker();
+ const bool isSharedWorker = aWorkerPrivate->IsSharedWorker();
+ const bool isDedicatedWorker = aWorkerPrivate->IsDedicatedWorker();
+ if (isServiceWorker) {
+ AssertIsOnMainThread();
+ Telemetry::Accumulate(Telemetry::SERVICE_WORKER_SPAWN_ATTEMPTS, 1);
+ }
+
+ nsCString sharedWorkerScriptSpec;
+ if (isSharedWorker) {
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIURI> scriptURI = aWorkerPrivate->GetResolvedScriptURI();
+ NS_ASSERTION(scriptURI, "Null script URI!");
+
+ nsresult rv = scriptURI->GetSpec(sharedWorkerScriptSpec);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("GetSpec failed?!");
+ return false;
+ }
+
+ NS_ASSERTION(!sharedWorkerScriptSpec.IsEmpty(), "Empty spec!");
+ }
+
+ bool exemptFromPerDomainMax = false;
+ if (isServiceWorker) {
+ AssertIsOnMainThread();
+ exemptFromPerDomainMax = Preferences::GetBool("dom.serviceWorkers.exemptFromPerDomainMax",
+ false);
+ }
+
+ const nsCString& domain = aWorkerPrivate->Domain();
+
+ WorkerDomainInfo* domainInfo;
+ bool queued = false;
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (!mDomainMap.Get(domain, &domainInfo)) {
+ NS_ASSERTION(!parent, "Shouldn't have a parent here!");
+
+ domainInfo = new WorkerDomainInfo();
+ domainInfo->mDomain = domain;
+ mDomainMap.Put(domain, domainInfo);
+ }
+
+ queued = gMaxWorkersPerDomain &&
+ domainInfo->ActiveWorkerCount() >= gMaxWorkersPerDomain &&
+ !domain.IsEmpty() &&
+ !exemptFromPerDomainMax;
+
+ if (queued) {
+ domainInfo->mQueuedWorkers.AppendElement(aWorkerPrivate);
+
+ // Worker spawn gets queued due to hitting max workers per domain
+ // limit so let's log a warning.
+ WorkerPrivate::ReportErrorToConsole("HittingMaxWorkersPerDomain2");
+
+ if (isServiceWorker) {
+ Telemetry::Accumulate(Telemetry::SERVICE_WORKER_SPAWN_GETS_QUEUED, 1);
+ } else if (isSharedWorker) {
+ Telemetry::Accumulate(Telemetry::SHARED_WORKER_SPAWN_GETS_QUEUED, 1);
+ } else if (isDedicatedWorker) {
+ Telemetry::Accumulate(Telemetry::DEDICATED_WORKER_SPAWN_GETS_QUEUED, 1);
+ }
+ }
+ else if (parent) {
+ domainInfo->mChildWorkerCount++;
+ }
+ else if (isServiceWorker) {
+ domainInfo->mActiveServiceWorkers.AppendElement(aWorkerPrivate);
+ }
+ else {
+ domainInfo->mActiveWorkers.AppendElement(aWorkerPrivate);
+ }
+
+ if (isSharedWorker) {
+ const nsCString& sharedWorkerName = aWorkerPrivate->WorkerName();
+ nsAutoCString key;
+ GenerateSharedWorkerKey(sharedWorkerScriptSpec, sharedWorkerName,
+ aWorkerPrivate->GetOriginAttributes(), key);
+ MOZ_ASSERT(!domainInfo->mSharedWorkerInfos.Get(key));
+
+ SharedWorkerInfo* sharedWorkerInfo =
+ new SharedWorkerInfo(aWorkerPrivate, sharedWorkerScriptSpec,
+ sharedWorkerName);
+ domainInfo->mSharedWorkerInfos.Put(key, sharedWorkerInfo);
+ }
+ }
+
+ // From here on out we must call UnregisterWorker if something fails!
+ if (parent) {
+ if (!parent->AddChildWorker(aWorkerPrivate)) {
+ UnregisterWorker(aWorkerPrivate);
+ return false;
+ }
+ }
+ else {
+ if (!mNavigatorPropertiesLoaded) {
+ Navigator::AppName(mNavigatorProperties.mAppName,
+ false /* aUsePrefOverriddenValue */);
+ if (NS_FAILED(Navigator::GetAppVersion(mNavigatorProperties.mAppVersion,
+ false /* aUsePrefOverriddenValue */)) ||
+ NS_FAILED(Navigator::GetPlatform(mNavigatorProperties.mPlatform,
+ false /* aUsePrefOverriddenValue */))) {
+ UnregisterWorker(aWorkerPrivate);
+ return false;
+ }
+
+ // The navigator overridden properties should have already been read.
+
+ Navigator::GetAcceptLanguages(mNavigatorProperties.mLanguages);
+ mNavigatorPropertiesLoaded = true;
+ }
+
+ nsPIDOMWindowInner* window = aWorkerPrivate->GetWindow();
+
+ if (!isServiceWorker) {
+ // Service workers are excluded since their lifetime is separate from
+ // that of dom windows.
+ nsTArray<WorkerPrivate*>* windowArray;
+ if (!mWindowMap.Get(window, &windowArray)) {
+ windowArray = new nsTArray<WorkerPrivate*>(1);
+ mWindowMap.Put(window, windowArray);
+ }
+
+ if (!windowArray->Contains(aWorkerPrivate)) {
+ windowArray->AppendElement(aWorkerPrivate);
+ } else {
+ MOZ_ASSERT(aWorkerPrivate->IsSharedWorker());
+ }
+ }
+ }
+
+ if (!queued && !ScheduleWorker(aWorkerPrivate)) {
+ return false;
+ }
+
+ if (isServiceWorker) {
+ AssertIsOnMainThread();
+ Telemetry::Accumulate(Telemetry::SERVICE_WORKER_WAS_SPAWNED, 1);
+ }
+ return true;
+}
+
+void
+RuntimeService::RemoveSharedWorker(WorkerDomainInfo* aDomainInfo,
+ WorkerPrivate* aWorkerPrivate)
+{
+ for (auto iter = aDomainInfo->mSharedWorkerInfos.Iter();
+ !iter.Done();
+ iter.Next()) {
+ SharedWorkerInfo* data = iter.UserData();
+ if (data->mWorkerPrivate == aWorkerPrivate) {
+#ifdef DEBUG
+ nsAutoCString key;
+ GenerateSharedWorkerKey(data->mScriptSpec, data->mName,
+ aWorkerPrivate->GetOriginAttributes(), key);
+ MOZ_ASSERT(iter.Key() == key);
+#endif
+ iter.Remove();
+ break;
+ }
+ }
+}
+
+void
+RuntimeService::UnregisterWorker(WorkerPrivate* aWorkerPrivate)
+{
+ aWorkerPrivate->AssertIsOnParentThread();
+
+ WorkerPrivate* parent = aWorkerPrivate->GetParent();
+ if (!parent) {
+ AssertIsOnMainThread();
+ }
+
+ const nsCString& domain = aWorkerPrivate->Domain();
+
+ WorkerPrivate* queuedWorker = nullptr;
+ {
+ MutexAutoLock lock(mMutex);
+
+ WorkerDomainInfo* domainInfo;
+ if (!mDomainMap.Get(domain, &domainInfo)) {
+ NS_ERROR("Don't have an entry for this domain!");
+ }
+
+ // Remove old worker from everywhere.
+ uint32_t index = domainInfo->mQueuedWorkers.IndexOf(aWorkerPrivate);
+ if (index != kNoIndex) {
+ // Was queued, remove from the list.
+ domainInfo->mQueuedWorkers.RemoveElementAt(index);
+ }
+ else if (parent) {
+ MOZ_ASSERT(domainInfo->mChildWorkerCount, "Must be non-zero!");
+ domainInfo->mChildWorkerCount--;
+ }
+ else if (aWorkerPrivate->IsServiceWorker()) {
+ MOZ_ASSERT(domainInfo->mActiveServiceWorkers.Contains(aWorkerPrivate),
+ "Don't know about this worker!");
+ domainInfo->mActiveServiceWorkers.RemoveElement(aWorkerPrivate);
+ }
+ else {
+ MOZ_ASSERT(domainInfo->mActiveWorkers.Contains(aWorkerPrivate),
+ "Don't know about this worker!");
+ domainInfo->mActiveWorkers.RemoveElement(aWorkerPrivate);
+ }
+
+ if (aWorkerPrivate->IsSharedWorker()) {
+ RemoveSharedWorker(domainInfo, aWorkerPrivate);
+ }
+
+ // See if there's a queued worker we can schedule.
+ if (domainInfo->ActiveWorkerCount() < gMaxWorkersPerDomain &&
+ !domainInfo->mQueuedWorkers.IsEmpty()) {
+ queuedWorker = domainInfo->mQueuedWorkers[0];
+ domainInfo->mQueuedWorkers.RemoveElementAt(0);
+
+ if (queuedWorker->GetParent()) {
+ domainInfo->mChildWorkerCount++;
+ }
+ else if (queuedWorker->IsServiceWorker()) {
+ domainInfo->mActiveServiceWorkers.AppendElement(queuedWorker);
+ }
+ else {
+ domainInfo->mActiveWorkers.AppendElement(queuedWorker);
+ }
+ }
+
+ if (domainInfo->HasNoWorkers()) {
+ MOZ_ASSERT(domainInfo->mQueuedWorkers.IsEmpty());
+ mDomainMap.Remove(domain);
+ }
+ }
+
+ if (aWorkerPrivate->IsServiceWorker()) {
+ AssertIsOnMainThread();
+ Telemetry::AccumulateTimeDelta(Telemetry::SERVICE_WORKER_LIFE_TIME,
+ aWorkerPrivate->CreationTimeStamp());
+ }
+
+ if (aWorkerPrivate->IsSharedWorker() ||
+ aWorkerPrivate->IsServiceWorker()) {
+ AssertIsOnMainThread();
+ aWorkerPrivate->CloseAllSharedWorkers();
+ }
+
+ if (parent) {
+ parent->RemoveChildWorker(aWorkerPrivate);
+ }
+ else if (aWorkerPrivate->IsSharedWorker()) {
+ AssertIsOnMainThread();
+
+ for (auto iter = mWindowMap.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<nsTArray<WorkerPrivate*>>& workers = iter.Data();
+ MOZ_ASSERT(workers.get());
+
+ if (workers->RemoveElement(aWorkerPrivate)) {
+ MOZ_ASSERT(!workers->Contains(aWorkerPrivate),
+ "Added worker more than once!");
+
+ if (workers->IsEmpty()) {
+ iter.Remove();
+ }
+ }
+ }
+ }
+ else if (aWorkerPrivate->IsDedicatedWorker()) {
+ // May be null.
+ nsPIDOMWindowInner* window = aWorkerPrivate->GetWindow();
+
+ nsTArray<WorkerPrivate*>* windowArray;
+ MOZ_ALWAYS_TRUE(mWindowMap.Get(window, &windowArray));
+
+ MOZ_ALWAYS_TRUE(windowArray->RemoveElement(aWorkerPrivate));
+
+ if (windowArray->IsEmpty()) {
+ mWindowMap.Remove(window);
+ }
+ }
+
+ if (queuedWorker && !ScheduleWorker(queuedWorker)) {
+ UnregisterWorker(queuedWorker);
+ }
+}
+
+bool
+RuntimeService::ScheduleWorker(WorkerPrivate* aWorkerPrivate)
+{
+ if (!aWorkerPrivate->Start()) {
+ // This is ok, means that we didn't need to make a thread for this worker.
+ return true;
+ }
+
+ RefPtr<WorkerThread> thread;
+ {
+ MutexAutoLock lock(mMutex);
+ if (!mIdleThreadArray.IsEmpty()) {
+ uint32_t index = mIdleThreadArray.Length() - 1;
+ mIdleThreadArray[index].mThread.swap(thread);
+ mIdleThreadArray.RemoveElementAt(index);
+ }
+ }
+
+ const WorkerThreadFriendKey friendKey;
+
+ if (!thread) {
+ thread = WorkerThread::Create(friendKey);
+ if (!thread) {
+ UnregisterWorker(aWorkerPrivate);
+ return false;
+ }
+ }
+
+ int32_t priority = aWorkerPrivate->IsChromeWorker() ?
+ nsISupportsPriority::PRIORITY_NORMAL :
+ nsISupportsPriority::PRIORITY_LOW;
+
+ if (NS_FAILED(thread->SetPriority(priority))) {
+ NS_WARNING("Could not set the thread's priority!");
+ }
+
+ JSContext* cx = CycleCollectedJSContext::Get()->Context();
+ nsCOMPtr<nsIRunnable> runnable =
+ new WorkerThreadPrimaryRunnable(aWorkerPrivate, thread,
+ JS_GetParentContext(cx));
+ if (NS_FAILED(thread->DispatchPrimaryRunnable(friendKey, runnable.forget()))) {
+ UnregisterWorker(aWorkerPrivate);
+ return false;
+ }
+
+ return true;
+}
+
+// static
+void
+RuntimeService::ShutdownIdleThreads(nsITimer* aTimer, void* /* aClosure */)
+{
+ AssertIsOnMainThread();
+
+ RuntimeService* runtime = RuntimeService::GetService();
+ NS_ASSERTION(runtime, "This should never be null!");
+
+ NS_ASSERTION(aTimer == runtime->mIdleThreadTimer, "Wrong timer!");
+
+ // Cheat a little and grab all threads that expire within one second of now.
+ TimeStamp now = TimeStamp::NowLoRes() + TimeDuration::FromSeconds(1);
+
+ TimeStamp nextExpiration;
+
+ AutoTArray<RefPtr<WorkerThread>, 20> expiredThreads;
+ {
+ MutexAutoLock lock(runtime->mMutex);
+
+ for (uint32_t index = 0; index < runtime->mIdleThreadArray.Length();
+ index++) {
+ IdleThreadInfo& info = runtime->mIdleThreadArray[index];
+ if (info.mExpirationTime > now) {
+ nextExpiration = info.mExpirationTime;
+ break;
+ }
+
+ RefPtr<WorkerThread>* thread = expiredThreads.AppendElement();
+ thread->swap(info.mThread);
+ }
+
+ if (!expiredThreads.IsEmpty()) {
+ runtime->mIdleThreadArray.RemoveElementsAt(0, expiredThreads.Length());
+ }
+ }
+
+ if (!nextExpiration.IsNull()) {
+ TimeDuration delta = nextExpiration - TimeStamp::NowLoRes();
+ uint32_t delay(delta > TimeDuration(0) ? delta.ToMilliseconds() : 0);
+
+ // Reschedule the timer.
+ MOZ_ALWAYS_SUCCEEDS(
+ aTimer->InitWithFuncCallback(ShutdownIdleThreads,
+ nullptr,
+ delay,
+ nsITimer::TYPE_ONE_SHOT));
+ }
+
+ for (uint32_t index = 0; index < expiredThreads.Length(); index++) {
+ if (NS_FAILED(expiredThreads[index]->Shutdown())) {
+ NS_WARNING("Failed to shutdown thread!");
+ }
+ }
+}
+
+nsresult
+RuntimeService::Init()
+{
+ AssertIsOnMainThread();
+
+ nsLayoutStatics::AddRef();
+
+ // Make sure PBackground actors are connected as soon as possible for the main
+ // thread in case workers clone remote blobs here.
+ if (!BackgroundChild::GetForCurrentThread()) {
+ RefPtr<BackgroundChildCallback> callback = new BackgroundChildCallback();
+ if (!BackgroundChild::GetOrCreateForCurrentThread(callback)) {
+ MOZ_CRASH("Unable to connect PBackground actor for the main thread!");
+ }
+ }
+
+ // Initialize JSSettings.
+ if (!sDefaultJSSettings.gcSettings[0].IsSet()) {
+ sDefaultJSSettings.contextOptions = JS::ContextOptions();
+ sDefaultJSSettings.chrome.maxScriptRuntime = -1;
+ sDefaultJSSettings.chrome.compartmentOptions.behaviors().setVersion(JSVERSION_LATEST);
+ sDefaultJSSettings.content.maxScriptRuntime = MAX_SCRIPT_RUN_TIME_SEC;
+#ifdef JS_GC_ZEAL
+ sDefaultJSSettings.gcZealFrequency = JS_DEFAULT_ZEAL_FREQ;
+ sDefaultJSSettings.gcZeal = 0;
+#endif
+ SetDefaultJSGCSettings(JSGC_MAX_BYTES, WORKER_DEFAULT_RUNTIME_HEAPSIZE);
+ SetDefaultJSGCSettings(JSGC_ALLOCATION_THRESHOLD,
+ WORKER_DEFAULT_ALLOCATION_THRESHOLD);
+ }
+
+ mIdleThreadTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ NS_ENSURE_STATE(mIdleThreadTimer);
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
+
+ nsresult rv =
+ obs->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mObserved = true;
+
+ if (NS_FAILED(obs->AddObserver(this, GC_REQUEST_OBSERVER_TOPIC, false))) {
+ NS_WARNING("Failed to register for GC request notifications!");
+ }
+
+ if (NS_FAILED(obs->AddObserver(this, CC_REQUEST_OBSERVER_TOPIC, false))) {
+ NS_WARNING("Failed to register for CC request notifications!");
+ }
+
+ if (NS_FAILED(obs->AddObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC,
+ false))) {
+ NS_WARNING("Failed to register for memory pressure notifications!");
+ }
+
+ if (NS_FAILED(obs->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false))) {
+ NS_WARNING("Failed to register for offline notification event!");
+ }
+
+ MOZ_ASSERT(!gRuntimeServiceDuringInit, "This should be false!");
+ gRuntimeServiceDuringInit = true;
+
+ if (NS_FAILED(Preferences::RegisterCallback(
+ LoadJSGCMemoryOptions,
+ PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX,
+ nullptr)) ||
+ NS_FAILED(Preferences::RegisterCallbackAndCall(
+ LoadJSGCMemoryOptions,
+ PREF_WORKERS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX,
+ nullptr)) ||
+#ifdef JS_GC_ZEAL
+ NS_FAILED(Preferences::RegisterCallback(
+ LoadGCZealOptions,
+ PREF_JS_OPTIONS_PREFIX PREF_GCZEAL,
+ nullptr)) ||
+#endif
+
+#define WORKER_SIMPLE_PREF(name, getter, NAME) \
+ NS_FAILED(Preferences::RegisterCallbackAndCall( \
+ WorkerPrefChanged, \
+ name, \
+ reinterpret_cast<void*>(WORKERPREF_##NAME))) ||
+#define WORKER_PREF(name, callback) \
+ NS_FAILED(Preferences::RegisterCallbackAndCall( \
+ callback, \
+ name, \
+ nullptr)) ||
+#include "WorkerPrefs.h"
+#undef WORKER_SIMPLE_PREF
+#undef WORKER_PREF
+
+ NS_FAILED(Preferences::RegisterCallbackAndCall(
+ LoadContextOptions,
+ PREF_WORKERS_OPTIONS_PREFIX,
+ nullptr)) ||
+ NS_FAILED(Preferences::RegisterCallback(LoadContextOptions,
+ PREF_JS_OPTIONS_PREFIX,
+ nullptr))) {
+ NS_WARNING("Failed to register pref callbacks!");
+ }
+
+ MOZ_ASSERT(gRuntimeServiceDuringInit, "Should be true!");
+ gRuntimeServiceDuringInit = false;
+
+ // We assume atomic 32bit reads/writes. If this assumption doesn't hold on
+ // some wacky platform then the worst that could happen is that the close
+ // handler will run for a slightly different amount of time.
+ if (NS_FAILED(Preferences::AddIntVarCache(
+ &sDefaultJSSettings.content.maxScriptRuntime,
+ PREF_MAX_SCRIPT_RUN_TIME_CONTENT,
+ MAX_SCRIPT_RUN_TIME_SEC)) ||
+ NS_FAILED(Preferences::AddIntVarCache(
+ &sDefaultJSSettings.chrome.maxScriptRuntime,
+ PREF_MAX_SCRIPT_RUN_TIME_CHROME, -1))) {
+ NS_WARNING("Failed to register timeout cache!");
+ }
+
+ int32_t maxPerDomain = Preferences::GetInt(PREF_WORKERS_MAX_PER_DOMAIN,
+ MAX_WORKERS_PER_DOMAIN);
+ gMaxWorkersPerDomain = std::max(0, maxPerDomain);
+
+ int32_t maxHardwareConcurrency =
+ Preferences::GetInt(PREF_WORKERS_MAX_HARDWARE_CONCURRENCY,
+ MAX_HARDWARE_CONCURRENCY);
+ gMaxHardwareConcurrency = std::max(0, maxHardwareConcurrency);
+
+ rv = InitOSFileConstants();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+void
+RuntimeService::Shutdown()
+{
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(!mShuttingDown);
+ // That's it, no more workers.
+ mShuttingDown = true;
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ NS_WARNING_ASSERTION(obs, "Failed to get observer service?!");
+
+ // Tell anyone that cares that they're about to lose worker support.
+ if (obs && NS_FAILED(obs->NotifyObservers(nullptr, WORKERS_SHUTDOWN_TOPIC,
+ nullptr))) {
+ NS_WARNING("NotifyObservers failed!");
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ AutoTArray<WorkerPrivate*, 100> workers;
+ AddAllTopLevelWorkersToArray(workers);
+
+ if (!workers.IsEmpty()) {
+ // Cancel all top-level workers.
+ {
+ MutexAutoUnlock unlock(mMutex);
+
+ for (uint32_t index = 0; index < workers.Length(); index++) {
+ if (!workers[index]->Kill()) {
+ NS_WARNING("Failed to cancel worker!");
+ }
+ }
+ }
+ }
+ }
+}
+
+// This spins the event loop until all workers are finished and their threads
+// have been joined.
+void
+RuntimeService::Cleanup()
+{
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ NS_WARNING_ASSERTION(obs, "Failed to get observer service?!");
+
+ if (mIdleThreadTimer) {
+ if (NS_FAILED(mIdleThreadTimer->Cancel())) {
+ NS_WARNING("Failed to cancel idle timer!");
+ }
+ mIdleThreadTimer = nullptr;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ AutoTArray<WorkerPrivate*, 100> workers;
+ AddAllTopLevelWorkersToArray(workers);
+
+ if (!workers.IsEmpty()) {
+ nsIThread* currentThread = NS_GetCurrentThread();
+ NS_ASSERTION(currentThread, "This should never be null!");
+
+ // Shut down any idle threads.
+ if (!mIdleThreadArray.IsEmpty()) {
+ AutoTArray<RefPtr<WorkerThread>, 20> idleThreads;
+
+ uint32_t idleThreadCount = mIdleThreadArray.Length();
+ idleThreads.SetLength(idleThreadCount);
+
+ for (uint32_t index = 0; index < idleThreadCount; index++) {
+ NS_ASSERTION(mIdleThreadArray[index].mThread, "Null thread!");
+ idleThreads[index].swap(mIdleThreadArray[index].mThread);
+ }
+
+ mIdleThreadArray.Clear();
+
+ MutexAutoUnlock unlock(mMutex);
+
+ for (uint32_t index = 0; index < idleThreadCount; index++) {
+ if (NS_FAILED(idleThreads[index]->Shutdown())) {
+ NS_WARNING("Failed to shutdown thread!");
+ }
+ }
+ }
+
+ // And make sure all their final messages have run and all their threads
+ // have joined.
+ while (mDomainMap.Count()) {
+ MutexAutoUnlock unlock(mMutex);
+
+ if (!NS_ProcessNextEvent(currentThread)) {
+ NS_WARNING("Something bad happened!");
+ break;
+ }
+ }
+ }
+ }
+
+ NS_ASSERTION(!mWindowMap.Count(), "All windows should have been released!");
+
+ if (mObserved) {
+ if (NS_FAILED(Preferences::UnregisterCallback(LoadContextOptions,
+ PREF_JS_OPTIONS_PREFIX,
+ nullptr)) ||
+ NS_FAILED(Preferences::UnregisterCallback(LoadContextOptions,
+ PREF_WORKERS_OPTIONS_PREFIX,
+ nullptr)) ||
+
+#define WORKER_SIMPLE_PREF(name, getter, NAME) \
+ NS_FAILED(Preferences::UnregisterCallback( \
+ WorkerPrefChanged, \
+ name, \
+ reinterpret_cast<void*>(WORKERPREF_##NAME))) ||
+#define WORKER_PREF(name, callback) \
+ NS_FAILED(Preferences::UnregisterCallback( \
+ callback, \
+ name, \
+ nullptr)) ||
+#include "WorkerPrefs.h"
+#undef WORKER_SIMPLE_PREF
+#undef WORKER_PREF
+
+#ifdef JS_GC_ZEAL
+ NS_FAILED(Preferences::UnregisterCallback(
+ LoadGCZealOptions,
+ PREF_JS_OPTIONS_PREFIX PREF_GCZEAL,
+ nullptr)) ||
+#endif
+ NS_FAILED(Preferences::UnregisterCallback(
+ LoadJSGCMemoryOptions,
+ PREF_JS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX,
+ nullptr)) ||
+ NS_FAILED(Preferences::UnregisterCallback(
+ LoadJSGCMemoryOptions,
+ PREF_WORKERS_OPTIONS_PREFIX PREF_MEM_OPTIONS_PREFIX,
+ nullptr))) {
+ NS_WARNING("Failed to unregister pref callbacks!");
+ }
+
+ if (obs) {
+ if (NS_FAILED(obs->RemoveObserver(this, GC_REQUEST_OBSERVER_TOPIC))) {
+ NS_WARNING("Failed to unregister for GC request notifications!");
+ }
+
+ if (NS_FAILED(obs->RemoveObserver(this, CC_REQUEST_OBSERVER_TOPIC))) {
+ NS_WARNING("Failed to unregister for CC request notifications!");
+ }
+
+ if (NS_FAILED(obs->RemoveObserver(this,
+ MEMORY_PRESSURE_OBSERVER_TOPIC))) {
+ NS_WARNING("Failed to unregister for memory pressure notifications!");
+ }
+
+ if (NS_FAILED(obs->RemoveObserver(this,
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC))) {
+ NS_WARNING("Failed to unregister for offline notification event!");
+ }
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID);
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ mObserved = false;
+ }
+ }
+
+ CleanupOSFileConstants();
+ nsLayoutStatics::Release();
+}
+
+void
+RuntimeService::AddAllTopLevelWorkersToArray(nsTArray<WorkerPrivate*>& aWorkers)
+{
+ for (auto iter = mDomainMap.Iter(); !iter.Done(); iter.Next()) {
+
+ WorkerDomainInfo* aData = iter.UserData();
+
+#ifdef DEBUG
+ for (uint32_t index = 0; index < aData->mActiveWorkers.Length(); index++) {
+ MOZ_ASSERT(!aData->mActiveWorkers[index]->GetParent(),
+ "Shouldn't have a parent in this list!");
+ }
+ for (uint32_t index = 0; index < aData->mActiveServiceWorkers.Length(); index++) {
+ MOZ_ASSERT(!aData->mActiveServiceWorkers[index]->GetParent(),
+ "Shouldn't have a parent in this list!");
+ }
+#endif
+
+ aWorkers.AppendElements(aData->mActiveWorkers);
+ aWorkers.AppendElements(aData->mActiveServiceWorkers);
+
+ // These might not be top-level workers...
+ for (uint32_t index = 0; index < aData->mQueuedWorkers.Length(); index++) {
+ WorkerPrivate* worker = aData->mQueuedWorkers[index];
+ if (!worker->GetParent()) {
+ aWorkers.AppendElement(worker);
+ }
+ }
+ }
+}
+
+void
+RuntimeService::GetWorkersForWindow(nsPIDOMWindowInner* aWindow,
+ nsTArray<WorkerPrivate*>& aWorkers)
+{
+ AssertIsOnMainThread();
+
+ nsTArray<WorkerPrivate*>* workers;
+ if (mWindowMap.Get(aWindow, &workers)) {
+ NS_ASSERTION(!workers->IsEmpty(), "Should have been removed!");
+ aWorkers.AppendElements(*workers);
+ }
+ else {
+ NS_ASSERTION(aWorkers.IsEmpty(), "Should be empty!");
+ }
+}
+
+void
+RuntimeService::CancelWorkersForWindow(nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnMainThread();
+
+ nsTArray<WorkerPrivate*> workers;
+ GetWorkersForWindow(aWindow, workers);
+
+ if (!workers.IsEmpty()) {
+ for (uint32_t index = 0; index < workers.Length(); index++) {
+ WorkerPrivate*& worker = workers[index];
+
+ if (worker->IsSharedWorker()) {
+ worker->CloseSharedWorkersForWindow(aWindow);
+ } else {
+ worker->Cancel();
+ }
+ }
+ }
+}
+
+void
+RuntimeService::FreezeWorkersForWindow(nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aWindow);
+
+ nsTArray<WorkerPrivate*> workers;
+ GetWorkersForWindow(aWindow, workers);
+
+ for (uint32_t index = 0; index < workers.Length(); index++) {
+ workers[index]->Freeze(aWindow);
+ }
+}
+
+void
+RuntimeService::ThawWorkersForWindow(nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aWindow);
+
+ nsTArray<WorkerPrivate*> workers;
+ GetWorkersForWindow(aWindow, workers);
+
+ for (uint32_t index = 0; index < workers.Length(); index++) {
+ workers[index]->Thaw(aWindow);
+ }
+}
+
+void
+RuntimeService::SuspendWorkersForWindow(nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aWindow);
+
+ nsTArray<WorkerPrivate*> workers;
+ GetWorkersForWindow(aWindow, workers);
+
+ for (uint32_t index = 0; index < workers.Length(); index++) {
+ workers[index]->ParentWindowPaused();
+ }
+}
+
+void
+RuntimeService::ResumeWorkersForWindow(nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aWindow);
+
+ nsTArray<WorkerPrivate*> workers;
+ GetWorkersForWindow(aWindow, workers);
+
+ for (uint32_t index = 0; index < workers.Length(); index++) {
+ workers[index]->ParentWindowResumed();
+ }
+}
+
+nsresult
+RuntimeService::CreateSharedWorker(const GlobalObject& aGlobal,
+ const nsAString& aScriptURL,
+ const nsACString& aName,
+ SharedWorker** aSharedWorker)
+{
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(window);
+
+ JSContext* cx = aGlobal.Context();
+
+ WorkerLoadInfo loadInfo;
+ nsresult rv = WorkerPrivate::GetLoadInfo(cx, window, nullptr, aScriptURL,
+ false,
+ WorkerPrivate::OverrideLoadGroup,
+ WorkerTypeShared, &loadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CreateSharedWorkerFromLoadInfo(cx, &loadInfo, aScriptURL, aName,
+ aSharedWorker);
+}
+
+nsresult
+RuntimeService::CreateSharedWorkerFromLoadInfo(JSContext* aCx,
+ WorkerLoadInfo* aLoadInfo,
+ const nsAString& aScriptURL,
+ const nsACString& aName,
+ SharedWorker** aSharedWorker)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aLoadInfo);
+ MOZ_ASSERT(aLoadInfo->mResolvedScriptURI);
+
+ RefPtr<WorkerPrivate> workerPrivate;
+ {
+ MutexAutoLock lock(mMutex);
+
+ WorkerDomainInfo* domainInfo;
+ SharedWorkerInfo* sharedWorkerInfo;
+
+ nsCString scriptSpec;
+ nsresult rv = aLoadInfo->mResolvedScriptURI->GetSpec(scriptSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT(aLoadInfo->mPrincipal);
+ nsAutoCString key;
+ GenerateSharedWorkerKey(scriptSpec, aName,
+ BasePrincipal::Cast(aLoadInfo->mPrincipal)->OriginAttributesRef(), key);
+
+ if (mDomainMap.Get(aLoadInfo->mDomain, &domainInfo) &&
+ domainInfo->mSharedWorkerInfos.Get(key, &sharedWorkerInfo)) {
+ workerPrivate = sharedWorkerInfo->mWorkerPrivate;
+ }
+ }
+
+ // Keep a reference to the window before spawning the worker. If the worker is
+ // a Shared/Service worker and the worker script loads and executes before
+ // the SharedWorker object itself is created before then WorkerScriptLoaded()
+ // will reset the loadInfo's window.
+ nsCOMPtr<nsPIDOMWindowInner> window = aLoadInfo->mWindow;
+
+ // shouldAttachToWorkerPrivate tracks whether our SharedWorker should actually
+ // get attached to the WorkerPrivate we're using. It will become false if the
+ // WorkerPrivate already exists and its secure context state doesn't match
+ // what we want for the new SharedWorker.
+ bool shouldAttachToWorkerPrivate = true;
+ bool created = false;
+ ErrorResult rv;
+ if (!workerPrivate) {
+ workerPrivate =
+ WorkerPrivate::Constructor(aCx, aScriptURL, false,
+ WorkerTypeShared, aName, aLoadInfo, rv);
+ NS_ENSURE_TRUE(workerPrivate, rv.StealNSResult());
+
+ created = true;
+ } else {
+ // Check whether the secure context state matches. The current compartment
+ // of aCx is the compartment of the SharedWorker constructor that was
+ // invoked, which is the compartment of the document that will be hooked up
+ // to the worker, so that's what we want to check.
+ shouldAttachToWorkerPrivate =
+ workerPrivate->IsSecureContext() ==
+ JS_GetIsSecureContext(js::GetContextCompartment(aCx));
+
+ // If we're attaching to an existing SharedWorker private, then we
+ // must update the overriden load group to account for our document's
+ // load group.
+ if (shouldAttachToWorkerPrivate) {
+ workerPrivate->UpdateOverridenLoadGroup(aLoadInfo->mLoadGroup);
+ }
+ }
+
+ // We don't actually care about this MessageChannel, but we use it to 'steal'
+ // its 2 connected ports.
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(window);
+ RefPtr<MessageChannel> channel = MessageChannel::Constructor(global, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ RefPtr<SharedWorker> sharedWorker = new SharedWorker(window, workerPrivate,
+ channel->Port1());
+
+ if (!shouldAttachToWorkerPrivate) {
+ // We're done here. Just queue up our error event and return our
+ // dead-on-arrival SharedWorker.
+ RefPtr<AsyncEventDispatcher> errorEvent =
+ new AsyncEventDispatcher(sharedWorker, NS_LITERAL_STRING("error"), false);
+ errorEvent->PostDOMEvent();
+ sharedWorker.forget(aSharedWorker);
+ return NS_OK;
+ }
+
+ if (!workerPrivate->RegisterSharedWorker(sharedWorker, channel->Port2())) {
+ NS_WARNING("Worker is unreachable, this shouldn't happen!");
+ sharedWorker->Close();
+ return NS_ERROR_FAILURE;
+ }
+
+ // This is normally handled in RegisterWorker, but that wasn't called if the
+ // worker already existed.
+ if (!created) {
+ nsTArray<WorkerPrivate*>* windowArray;
+ if (!mWindowMap.Get(window, &windowArray)) {
+ windowArray = new nsTArray<WorkerPrivate*>(1);
+ mWindowMap.Put(window, windowArray);
+ }
+
+ if (!windowArray->Contains(workerPrivate)) {
+ windowArray->AppendElement(workerPrivate);
+ }
+ }
+
+ sharedWorker.forget(aSharedWorker);
+ return NS_OK;
+}
+
+void
+RuntimeService::ForgetSharedWorker(WorkerPrivate* aWorkerPrivate)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aWorkerPrivate->IsSharedWorker());
+
+ MutexAutoLock lock(mMutex);
+
+ WorkerDomainInfo* domainInfo;
+ if (mDomainMap.Get(aWorkerPrivate->Domain(), &domainInfo)) {
+ RemoveSharedWorker(domainInfo, aWorkerPrivate);
+ }
+}
+
+void
+RuntimeService::NoteIdleThread(WorkerThread* aThread)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aThread);
+
+ bool shutdownThread = mShuttingDown;
+ bool scheduleTimer = false;
+
+ if (!shutdownThread) {
+ static TimeDuration timeout =
+ TimeDuration::FromSeconds(IDLE_THREAD_TIMEOUT_SEC);
+
+ TimeStamp expirationTime = TimeStamp::NowLoRes() + timeout;
+
+ MutexAutoLock lock(mMutex);
+
+ uint32_t previousIdleCount = mIdleThreadArray.Length();
+
+ if (previousIdleCount < MAX_IDLE_THREADS) {
+ IdleThreadInfo* info = mIdleThreadArray.AppendElement();
+ info->mThread = aThread;
+ info->mExpirationTime = expirationTime;
+
+ scheduleTimer = previousIdleCount == 0;
+ } else {
+ shutdownThread = true;
+ }
+ }
+
+ MOZ_ASSERT_IF(shutdownThread, !scheduleTimer);
+ MOZ_ASSERT_IF(scheduleTimer, !shutdownThread);
+
+ // Too many idle threads, just shut this one down.
+ if (shutdownThread) {
+ MOZ_ALWAYS_SUCCEEDS(aThread->Shutdown());
+ } else if (scheduleTimer) {
+ MOZ_ALWAYS_SUCCEEDS(
+ mIdleThreadTimer->InitWithFuncCallback(ShutdownIdleThreads,
+ nullptr,
+ IDLE_THREAD_TIMEOUT_SEC * 1000,
+ nsITimer::TYPE_ONE_SHOT));
+ }
+}
+
+void
+RuntimeService::UpdateAllWorkerContextOptions()
+{
+ BROADCAST_ALL_WORKERS(UpdateContextOptions, sDefaultJSSettings.contextOptions);
+}
+
+void
+RuntimeService::UpdateAppNameOverridePreference(const nsAString& aValue)
+{
+ AssertIsOnMainThread();
+ mNavigatorProperties.mAppNameOverridden = aValue;
+}
+
+void
+RuntimeService::UpdateAppVersionOverridePreference(const nsAString& aValue)
+{
+ AssertIsOnMainThread();
+ mNavigatorProperties.mAppVersionOverridden = aValue;
+}
+
+void
+RuntimeService::UpdatePlatformOverridePreference(const nsAString& aValue)
+{
+ AssertIsOnMainThread();
+ mNavigatorProperties.mPlatformOverridden = aValue;
+}
+
+void
+RuntimeService::UpdateAllWorkerPreference(WorkerPreference aPref, bool aValue)
+{
+ BROADCAST_ALL_WORKERS(UpdatePreference, aPref, aValue);
+}
+
+void
+RuntimeService::UpdateAllWorkerLanguages(const nsTArray<nsString>& aLanguages)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mNavigatorProperties.mLanguages = aLanguages;
+ BROADCAST_ALL_WORKERS(UpdateLanguages, aLanguages);
+}
+
+void
+RuntimeService::UpdateAllWorkerMemoryParameter(JSGCParamKey aKey,
+ uint32_t aValue)
+{
+ BROADCAST_ALL_WORKERS(UpdateJSWorkerMemoryParameter, aKey, aValue);
+}
+
+#ifdef JS_GC_ZEAL
+void
+RuntimeService::UpdateAllWorkerGCZeal()
+{
+ BROADCAST_ALL_WORKERS(UpdateGCZeal, sDefaultJSSettings.gcZeal,
+ sDefaultJSSettings.gcZealFrequency);
+}
+#endif
+
+void
+RuntimeService::GarbageCollectAllWorkers(bool aShrinking)
+{
+ BROADCAST_ALL_WORKERS(GarbageCollect, aShrinking);
+}
+
+void
+RuntimeService::CycleCollectAllWorkers()
+{
+ BROADCAST_ALL_WORKERS(CycleCollect, /* dummy = */ false);
+}
+
+void
+RuntimeService::SendOfflineStatusChangeEventToAllWorkers(bool aIsOffline)
+{
+ BROADCAST_ALL_WORKERS(OfflineStatusChangeEvent, aIsOffline);
+}
+
+void
+RuntimeService::MemoryPressureAllWorkers()
+{
+ BROADCAST_ALL_WORKERS(MemoryPressure, /* dummy = */ false);
+}
+
+uint32_t
+RuntimeService::ClampedHardwareConcurrency() const
+{
+ // This needs to be atomic, because multiple workers, and even mainthread,
+ // could race to initialize it at once.
+ static Atomic<uint32_t> clampedHardwareConcurrency;
+
+ // No need to loop here: if compareExchange fails, that just means that some
+ // other worker has initialized numberOfProcessors, so we're good to go.
+ if (!clampedHardwareConcurrency) {
+ int32_t numberOfProcessors = PR_GetNumberOfProcessors();
+ if (numberOfProcessors <= 0) {
+ numberOfProcessors = 1; // Must be one there somewhere
+ }
+ uint32_t clampedValue = std::min(uint32_t(numberOfProcessors),
+ gMaxHardwareConcurrency);
+ clampedHardwareConcurrency.compareExchange(0, clampedValue);
+ }
+
+ return clampedHardwareConcurrency;
+}
+
+// nsISupports
+NS_IMPL_ISUPPORTS(RuntimeService, nsIObserver)
+
+// nsIObserver
+NS_IMETHODIMP
+RuntimeService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ AssertIsOnMainThread();
+
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ Shutdown();
+ return NS_OK;
+ }
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)) {
+ Cleanup();
+ return NS_OK;
+ }
+ if (!strcmp(aTopic, GC_REQUEST_OBSERVER_TOPIC)) {
+ GarbageCollectAllWorkers(/* shrinking = */ false);
+ return NS_OK;
+ }
+ if (!strcmp(aTopic, CC_REQUEST_OBSERVER_TOPIC)) {
+ CycleCollectAllWorkers();
+ return NS_OK;
+ }
+ if (!strcmp(aTopic, MEMORY_PRESSURE_OBSERVER_TOPIC)) {
+ GarbageCollectAllWorkers(/* shrinking = */ true);
+ CycleCollectAllWorkers();
+ MemoryPressureAllWorkers();
+ return NS_OK;
+ }
+ if (!strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) {
+ SendOfflineStatusChangeEventToAllWorkers(NS_IsOffline());
+ return NS_OK;
+ }
+
+ NS_NOTREACHED("Unknown observer topic!");
+ return NS_OK;
+}
+
+/* static */ void
+RuntimeService::WorkerPrefChanged(const char* aPrefName, void* aClosure)
+{
+ AssertIsOnMainThread();
+
+ const WorkerPreference key =
+ static_cast<WorkerPreference>(reinterpret_cast<uintptr_t>(aClosure));
+
+ switch (key) {
+#define WORKER_SIMPLE_PREF(name, getter, NAME) case WORKERPREF_##NAME:
+#define WORKER_PREF(name, callback)
+#include "WorkerPrefs.h"
+#undef WORKER_SIMPLE_PREF
+#undef WORKER_PREF
+ sDefaultPreferences[key] = Preferences::GetBool(aPrefName, false);
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid pref key");
+ break;
+ }
+
+ RuntimeService* rts = RuntimeService::GetService();
+ if (rts) {
+ rts->UpdateAllWorkerPreference(key, sDefaultPreferences[key]);
+ }
+}
+
+void
+RuntimeService::JSVersionChanged(const char* /* aPrefName */, void* /* aClosure */)
+{
+ AssertIsOnMainThread();
+
+ bool useLatest = Preferences::GetBool("dom.workers.latestJSVersion", false);
+ JS::CompartmentOptions& options = sDefaultJSSettings.content.compartmentOptions;
+ options.behaviors().setVersion(useLatest ? JSVERSION_LATEST : JSVERSION_DEFAULT);
+}
+
+bool
+LogViolationDetailsRunnable::MainThreadRun()
+{
+ AssertIsOnMainThread();
+
+ nsIContentSecurityPolicy* csp = mWorkerPrivate->GetCSP();
+ if (csp) {
+ NS_NAMED_LITERAL_STRING(scriptSample,
+ "Call to eval() or related function blocked by CSP.");
+ if (mWorkerPrivate->GetReportCSPViolations()) {
+ csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
+ mFileName, scriptSample, mLineNum,
+ EmptyString(), EmptyString());
+ }
+ }
+
+ return true;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(WorkerThreadPrimaryRunnable, Runnable)
+
+NS_IMETHODIMP
+WorkerThreadPrimaryRunnable::Run()
+{
+ using mozilla::ipc::BackgroundChild;
+
+ char stackBaseGuess;
+
+ PR_SetCurrentThreadName("DOM Worker");
+
+ nsAutoCString threadName;
+ threadName.AssignLiteral("DOM Worker '");
+ threadName.Append(NS_LossyConvertUTF16toASCII(mWorkerPrivate->ScriptURL()));
+ threadName.Append('\'');
+
+ profiler_register_thread(threadName.get(), &stackBaseGuess);
+
+ // Note: SynchronouslyCreateForCurrentThread() must be called prior to
+ // mWorkerPrivate->SetThread() in order to avoid accidentally consuming
+ // worker messages here.
+ if (NS_WARN_IF(!BackgroundChild::SynchronouslyCreateForCurrentThread())) {
+ // XXX need to fire an error at parent.
+ // Failed in creating BackgroundChild: probably in shutdown. Continue to run
+ // without BackgroundChild created.
+ }
+
+ class MOZ_STACK_CLASS SetThreadHelper final
+ {
+ // Raw pointer: this class is on the stack.
+ WorkerPrivate* mWorkerPrivate;
+
+ public:
+ SetThreadHelper(WorkerPrivate* aWorkerPrivate, WorkerThread* aThread)
+ : mWorkerPrivate(aWorkerPrivate)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aThread);
+
+ mWorkerPrivate->SetThread(aThread);
+ }
+
+ ~SetThreadHelper()
+ {
+ if (mWorkerPrivate) {
+ mWorkerPrivate->SetThread(nullptr);
+ }
+ }
+
+ void Nullify()
+ {
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->SetThread(nullptr);
+ mWorkerPrivate = nullptr;
+ }
+ };
+
+ SetThreadHelper threadHelper(mWorkerPrivate, mThread);
+
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ {
+ nsCycleCollector_startup();
+
+ WorkerJSContext context(mWorkerPrivate);
+ nsresult rv = context.Initialize(mParentContext);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ JSContext* cx = context.Context();
+
+ if (!InitJSContextForWorker(mWorkerPrivate, cx)) {
+ // XXX need to fire an error at parent.
+ NS_ERROR("Failed to create context!");
+ return NS_ERROR_FAILURE;
+ }
+
+ {
+#ifdef MOZ_ENABLE_PROFILER_SPS
+ PseudoStack* stack = mozilla_get_pseudo_stack();
+ if (stack) {
+ stack->sampleContext(cx);
+ }
+#endif
+
+ {
+ JSAutoRequest ar(cx);
+
+ mWorkerPrivate->DoRunLoop(cx);
+ // The AutoJSAPI in DoRunLoop should have reported any exceptions left
+ // on cx. Note that we still need the JSAutoRequest above because
+ // AutoJSAPI on workers does NOT enter a request!
+ MOZ_ASSERT(!JS_IsExceptionPending(cx));
+ }
+
+ BackgroundChild::CloseForCurrentThread();
+
+#ifdef MOZ_ENABLE_PROFILER_SPS
+ if (stack) {
+ stack->sampleContext(nullptr);
+ }
+#endif
+ }
+
+ // There may still be runnables on the debugger event queue that hold a
+ // strong reference to the debugger global scope. These runnables are not
+ // visible to the cycle collector, so we need to make sure to clear the
+ // debugger event queue before we try to destroy the context. If we don't,
+ // the garbage collector will crash.
+ mWorkerPrivate->ClearDebuggerEventQueue();
+
+ // Perform a full GC. This will collect the main worker global and CC,
+ // which should break all cycles that touch JS.
+ JS_GC(cx);
+
+ // Before shutting down the cycle collector we need to do one more pass
+ // through the event loop to clean up any C++ objects that need deferred
+ // cleanup.
+ mWorkerPrivate->ClearMainEventQueue(WorkerPrivate::WorkerRan);
+
+ // Now WorkerJSContext goes out of scope and its destructor will shut
+ // down the cycle collector. This breaks any remaining cycles and collects
+ // any remaining C++ objects.
+ }
+
+ threadHelper.Nullify();
+
+ mWorkerPrivate->ScheduleDeletion(WorkerPrivate::WorkerRan);
+
+ // It is no longer safe to touch mWorkerPrivate.
+ mWorkerPrivate = nullptr;
+
+ // Now recycle this thread.
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ MOZ_ASSERT(mainThread);
+
+ RefPtr<FinishedRunnable> finishedRunnable =
+ new FinishedRunnable(mThread.forget());
+ MOZ_ALWAYS_SUCCEEDS(mainThread->Dispatch(finishedRunnable,
+ NS_DISPATCH_NORMAL));
+
+ profiler_unregister_thread();
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(WorkerThreadPrimaryRunnable::FinishedRunnable,
+ Runnable)
+
+NS_IMETHODIMP
+WorkerThreadPrimaryRunnable::FinishedRunnable::Run()
+{
+ AssertIsOnMainThread();
+
+ RefPtr<WorkerThread> thread;
+ mThread.swap(thread);
+
+ RuntimeService* rts = RuntimeService::GetService();
+ if (rts) {
+ rts->NoteIdleThread(thread);
+ }
+ else if (thread->ShutdownRequired()) {
+ MOZ_ALWAYS_SUCCEEDS(thread->Shutdown());
+ }
+
+ return NS_OK;
+}
diff --git a/dom/workers/RuntimeService.h b/dom/workers/RuntimeService.h
new file mode 100644
index 000000000..2e5cc1dad
--- /dev/null
+++ b/dom/workers/RuntimeService.h
@@ -0,0 +1,287 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_runtimeservice_h__
+#define mozilla_dom_workers_runtimeservice_h__
+
+#include "Workers.h"
+
+#include "nsIObserver.h"
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+
+class nsITimer;
+class nsPIDOMWindowInner;
+
+BEGIN_WORKERS_NAMESPACE
+
+class SharedWorker;
+class WorkerThread;
+
+class RuntimeService final : public nsIObserver
+{
+ struct SharedWorkerInfo
+ {
+ WorkerPrivate* mWorkerPrivate;
+ nsCString mScriptSpec;
+ nsCString mName;
+
+ SharedWorkerInfo(WorkerPrivate* aWorkerPrivate,
+ const nsACString& aScriptSpec,
+ const nsACString& aName)
+ : mWorkerPrivate(aWorkerPrivate), mScriptSpec(aScriptSpec), mName(aName)
+ { }
+ };
+
+ struct WorkerDomainInfo
+ {
+ nsCString mDomain;
+ nsTArray<WorkerPrivate*> mActiveWorkers;
+ nsTArray<WorkerPrivate*> mActiveServiceWorkers;
+ nsTArray<WorkerPrivate*> mQueuedWorkers;
+ nsClassHashtable<nsCStringHashKey, SharedWorkerInfo> mSharedWorkerInfos;
+ uint32_t mChildWorkerCount;
+
+ WorkerDomainInfo()
+ : mActiveWorkers(1), mChildWorkerCount(0)
+ { }
+
+ uint32_t
+ ActiveWorkerCount() const
+ {
+ return mActiveWorkers.Length() +
+ mChildWorkerCount;
+ }
+
+ uint32_t
+ ActiveServiceWorkerCount() const
+ {
+ return mActiveServiceWorkers.Length();
+ }
+
+ bool
+ HasNoWorkers() const
+ {
+ return ActiveWorkerCount() == 0 &&
+ ActiveServiceWorkerCount() == 0;
+ }
+ };
+
+ struct IdleThreadInfo;
+
+ mozilla::Mutex mMutex;
+
+ // Protected by mMutex.
+ nsClassHashtable<nsCStringHashKey, WorkerDomainInfo> mDomainMap;
+
+ // Protected by mMutex.
+ nsTArray<IdleThreadInfo> mIdleThreadArray;
+
+ // *Not* protected by mMutex.
+ nsClassHashtable<nsPtrHashKey<nsPIDOMWindowInner>,
+ nsTArray<WorkerPrivate*> > mWindowMap;
+
+ // Only used on the main thread.
+ nsCOMPtr<nsITimer> mIdleThreadTimer;
+
+ static JSSettings sDefaultJSSettings;
+ static bool sDefaultPreferences[WORKERPREF_COUNT];
+
+public:
+ struct NavigatorProperties
+ {
+ nsString mAppName;
+ nsString mAppNameOverridden;
+ nsString mAppVersion;
+ nsString mAppVersionOverridden;
+ nsString mPlatform;
+ nsString mPlatformOverridden;
+ nsTArray<nsString> mLanguages;
+ };
+
+private:
+ NavigatorProperties mNavigatorProperties;
+
+ // True when the observer service holds a reference to this object.
+ bool mObserved;
+ bool mShuttingDown;
+ bool mNavigatorPropertiesLoaded;
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ static RuntimeService*
+ GetOrCreateService();
+
+ static RuntimeService*
+ GetService();
+
+ bool
+ RegisterWorker(WorkerPrivate* aWorkerPrivate);
+
+ void
+ UnregisterWorker(WorkerPrivate* aWorkerPrivate);
+
+ void
+ RemoveSharedWorker(WorkerDomainInfo* aDomainInfo,
+ WorkerPrivate* aWorkerPrivate);
+
+ void
+ CancelWorkersForWindow(nsPIDOMWindowInner* aWindow);
+
+ void
+ FreezeWorkersForWindow(nsPIDOMWindowInner* aWindow);
+
+ void
+ ThawWorkersForWindow(nsPIDOMWindowInner* aWindow);
+
+ void
+ SuspendWorkersForWindow(nsPIDOMWindowInner* aWindow);
+
+ void
+ ResumeWorkersForWindow(nsPIDOMWindowInner* aWindow);
+
+ nsresult
+ CreateSharedWorker(const GlobalObject& aGlobal,
+ const nsAString& aScriptURL,
+ const nsACString& aName,
+ SharedWorker** aSharedWorker);
+
+ void
+ ForgetSharedWorker(WorkerPrivate* aWorkerPrivate);
+
+ const NavigatorProperties&
+ GetNavigatorProperties() const
+ {
+ return mNavigatorProperties;
+ }
+
+ void
+ NoteIdleThread(WorkerThread* aThread);
+
+ static void
+ GetDefaultJSSettings(JSSettings& aSettings)
+ {
+ AssertIsOnMainThread();
+ aSettings = sDefaultJSSettings;
+ }
+
+ static void
+ GetDefaultPreferences(bool aPreferences[WORKERPREF_COUNT])
+ {
+ AssertIsOnMainThread();
+ memcpy(aPreferences, sDefaultPreferences, WORKERPREF_COUNT * sizeof(bool));
+ }
+
+ static void
+ SetDefaultContextOptions(const JS::ContextOptions& aContextOptions)
+ {
+ AssertIsOnMainThread();
+ sDefaultJSSettings.contextOptions = aContextOptions;
+ }
+
+ void
+ UpdateAppNameOverridePreference(const nsAString& aValue);
+
+ void
+ UpdateAppVersionOverridePreference(const nsAString& aValue);
+
+ void
+ UpdatePlatformOverridePreference(const nsAString& aValue);
+
+ void
+ UpdateAllWorkerContextOptions();
+
+ void
+ UpdateAllWorkerLanguages(const nsTArray<nsString>& aLanguages);
+
+ void
+ UpdateAllWorkerPreference(WorkerPreference aPref, bool aValue);
+
+ static void
+ SetDefaultJSGCSettings(JSGCParamKey aKey, uint32_t aValue)
+ {
+ AssertIsOnMainThread();
+ sDefaultJSSettings.ApplyGCSetting(aKey, aValue);
+ }
+
+ void
+ UpdateAllWorkerMemoryParameter(JSGCParamKey aKey, uint32_t aValue);
+
+#ifdef JS_GC_ZEAL
+ static void
+ SetDefaultGCZeal(uint8_t aGCZeal, uint32_t aFrequency)
+ {
+ AssertIsOnMainThread();
+ sDefaultJSSettings.gcZeal = aGCZeal;
+ sDefaultJSSettings.gcZealFrequency = aFrequency;
+ }
+
+ void
+ UpdateAllWorkerGCZeal();
+#endif
+
+ void
+ GarbageCollectAllWorkers(bool aShrinking);
+
+ void
+ CycleCollectAllWorkers();
+
+ void
+ SendOfflineStatusChangeEventToAllWorkers(bool aIsOffline);
+
+ void
+ MemoryPressureAllWorkers();
+
+ uint32_t ClampedHardwareConcurrency() const;
+
+private:
+ RuntimeService();
+ ~RuntimeService();
+
+ nsresult
+ Init();
+
+ void
+ Shutdown();
+
+ void
+ Cleanup();
+
+ void
+ AddAllTopLevelWorkersToArray(nsTArray<WorkerPrivate*>& aWorkers);
+
+ void
+ GetWorkersForWindow(nsPIDOMWindowInner* aWindow,
+ nsTArray<WorkerPrivate*>& aWorkers);
+
+ bool
+ ScheduleWorker(WorkerPrivate* aWorkerPrivate);
+
+ static void
+ ShutdownIdleThreads(nsITimer* aTimer, void* aClosure);
+
+ static void
+ WorkerPrefChanged(const char* aPrefName, void* aClosure);
+
+ static void
+ JSVersionChanged(const char* aPrefName, void* aClosure);
+
+ nsresult
+ CreateSharedWorkerFromLoadInfo(JSContext* aCx,
+ WorkerLoadInfo* aLoadInfo,
+ const nsAString& aScriptURL,
+ const nsACString& aName,
+ SharedWorker** aSharedWorker);
+};
+
+END_WORKERS_NAMESPACE
+
+#endif /* mozilla_dom_workers_runtimeservice_h__ */
diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp
new file mode 100644
index 000000000..46545e737
--- /dev/null
+++ b/dom/workers/ScriptLoader.cpp
@@ -0,0 +1,2290 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScriptLoader.h"
+
+#include "nsIChannel.h"
+#include "nsIContentPolicy.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIDocShell.h"
+#include "nsIDOMDocument.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInputStreamPump.h"
+#include "nsIIOService.h"
+#include "nsIProtocolHandler.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIStreamLoader.h"
+#include "nsIStreamListenerTee.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIURI.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "nsError.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentUtils.h"
+#include "nsDocShellCID.h"
+#include "nsISupportsPrimitives.h"
+#include "nsNetUtil.h"
+#include "nsIPipe.h"
+#include "nsIOutputStream.h"
+#include "nsPrintfCString.h"
+#include "nsScriptLoader.h"
+#include "nsString.h"
+#include "nsStreamUtils.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOM.h"
+#include "xpcpublic.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/LoadContext.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/dom/CacheBinding.h"
+#include "mozilla/dom/cache/CacheTypes.h"
+#include "mozilla/dom/cache/Cache.h"
+#include "mozilla/dom/cache/CacheStorage.h"
+#include "mozilla/dom/ChannelInfo.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/InternalResponse.h"
+#include "mozilla/dom/nsCSPService.h"
+#include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/Response.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/SRILogHelper.h"
+#include "mozilla/UniquePtr.h"
+#include "Principal.h"
+#include "WorkerHolder.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "WorkerScope.h"
+
+#define MAX_CONCURRENT_SCRIPTS 1000
+
+USING_WORKERS_NAMESPACE
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using mozilla::dom::cache::Cache;
+using mozilla::dom::cache::CacheStorage;
+using mozilla::ipc::PrincipalInfo;
+
+namespace {
+
+nsIURI*
+GetBaseURI(bool aIsMainScript, WorkerPrivate* aWorkerPrivate)
+{
+ MOZ_ASSERT(aWorkerPrivate);
+ nsIURI* baseURI;
+ WorkerPrivate* parentWorker = aWorkerPrivate->GetParent();
+ if (aIsMainScript) {
+ if (parentWorker) {
+ baseURI = parentWorker->GetBaseURI();
+ NS_ASSERTION(baseURI, "Should have been set already!");
+ }
+ else {
+ // May be null.
+ baseURI = aWorkerPrivate->GetBaseURI();
+ }
+ }
+ else {
+ baseURI = aWorkerPrivate->GetBaseURI();
+ NS_ASSERTION(baseURI, "Should have been set already!");
+ }
+
+ return baseURI;
+}
+
+nsresult
+ChannelFromScriptURL(nsIPrincipal* principal,
+ nsIURI* baseURI,
+ nsIDocument* parentDoc,
+ nsILoadGroup* loadGroup,
+ nsIIOService* ios,
+ nsIScriptSecurityManager* secMan,
+ const nsAString& aScriptURL,
+ bool aIsMainScript,
+ WorkerScriptType aWorkerScriptType,
+ nsContentPolicyType aContentPolicyType,
+ nsLoadFlags aLoadFlags,
+ bool aDefaultURIEncoding,
+ nsIChannel** aChannel)
+{
+ AssertIsOnMainThread();
+
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+
+ if (aDefaultURIEncoding) {
+ rv = NS_NewURI(getter_AddRefs(uri), aScriptURL, nullptr, baseURI);
+ } else {
+ rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri),
+ aScriptURL, parentDoc,
+ baseURI);
+ }
+
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ // If we have the document, use it. Unfortunately, for dedicated workers
+ // 'parentDoc' ends up being the parent document, which is not the document
+ // that we want to use. So make sure to avoid using 'parentDoc' in that
+ // situation.
+ if (parentDoc && parentDoc->NodePrincipal() != principal) {
+ parentDoc = nullptr;
+ }
+
+ aLoadFlags |= nsIChannel::LOAD_CLASSIFY_URI;
+ uint32_t secFlags = aIsMainScript ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
+ : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
+
+ if (aWorkerScriptType == DebuggerScript) {
+ // A DebuggerScript needs to be a local resource like chrome: or resource:
+ bool isUIResource = false;
+ rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE,
+ &isUIResource);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!isUIResource) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ secFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
+ }
+
+ // Note: this is for backwards compatibility and goes against spec.
+ // We should find a better solution.
+ bool isData = false;
+ if (aIsMainScript && NS_SUCCEEDED(uri->SchemeIs("data", &isData)) && isData) {
+ secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ // If we have the document, use it. Unfortunately, for dedicated workers
+ // 'parentDoc' ends up being the parent document, which is not the document
+ // that we want to use. So make sure to avoid using 'parentDoc' in that
+ // situation.
+ if (parentDoc && parentDoc->NodePrincipal() == principal) {
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ parentDoc,
+ secFlags,
+ aContentPolicyType,
+ loadGroup,
+ nullptr, // aCallbacks
+ aLoadFlags,
+ ios);
+ } else {
+ // We must have a loadGroup with a load context for the principal to
+ // traverse the channel correctly.
+ MOZ_ASSERT(loadGroup);
+ MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal));
+
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ principal,
+ secFlags,
+ aContentPolicyType,
+ loadGroup,
+ nullptr, // aCallbacks
+ aLoadFlags,
+ ios);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel)) {
+ rv = nsContentUtils::SetFetchReferrerURIWithPolicy(principal, parentDoc,
+ httpChannel, mozilla::net::RP_Default);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ channel.forget(aChannel);
+ return rv;
+}
+
+struct ScriptLoadInfo
+{
+ ScriptLoadInfo()
+ : mScriptTextBuf(nullptr)
+ , mScriptTextLength(0)
+ , mLoadResult(NS_ERROR_NOT_INITIALIZED)
+ , mLoadingFinished(false)
+ , mExecutionScheduled(false)
+ , mExecutionResult(false)
+ , mCacheStatus(Uncached)
+ { }
+
+ ~ScriptLoadInfo()
+ {
+ if (mScriptTextBuf) {
+ js_free(mScriptTextBuf);
+ }
+ }
+
+ bool
+ ReadyToExecute()
+ {
+ return !mChannel && NS_SUCCEEDED(mLoadResult) && !mExecutionScheduled;
+ }
+
+ nsString mURL;
+
+ // This full URL string is populated only if this object is used in a
+ // ServiceWorker.
+ nsString mFullURL;
+
+ // This promise is set only when the script is for a ServiceWorker but
+ // it's not in the cache yet. The promise is resolved when the full body is
+ // stored into the cache. mCachePromise will be set to nullptr after
+ // resolution.
+ RefPtr<Promise> mCachePromise;
+
+ // The reader stream the cache entry should be filled from, for those cases
+ // when we're going to have an mCachePromise.
+ nsCOMPtr<nsIInputStream> mCacheReadStream;
+
+ nsCOMPtr<nsIChannel> mChannel;
+ char16_t* mScriptTextBuf;
+ size_t mScriptTextLength;
+
+ nsresult mLoadResult;
+ bool mLoadingFinished;
+ bool mExecutionScheduled;
+ bool mExecutionResult;
+
+ enum CacheStatus {
+ // By default a normal script is just loaded from the network. But for
+ // ServiceWorkers, we have to check if the cache contains the script and
+ // load it from the cache.
+ Uncached,
+
+ WritingToCache,
+
+ ReadingFromCache,
+
+ // This script has been loaded from the ServiceWorker cache.
+ Cached,
+
+ // This script must be stored in the ServiceWorker cache.
+ ToBeCached,
+
+ // Something went wrong or the worker went away.
+ Cancel
+ };
+
+ CacheStatus mCacheStatus;
+
+ Maybe<bool> mMutedErrorFlag;
+
+ bool Finished() const
+ {
+ return mLoadingFinished && !mCachePromise && !mChannel;
+ }
+};
+
+class ScriptLoaderRunnable;
+
+class ScriptExecutorRunnable final : public MainThreadWorkerSyncRunnable
+{
+ ScriptLoaderRunnable& mScriptLoader;
+ bool mIsWorkerScript;
+ uint32_t mFirstIndex;
+ uint32_t mLastIndex;
+
+public:
+ ScriptExecutorRunnable(ScriptLoaderRunnable& aScriptLoader,
+ nsIEventTarget* aSyncLoopTarget,
+ bool aIsWorkerScript,
+ uint32_t aFirstIndex,
+ uint32_t aLastIndex);
+
+private:
+ ~ScriptExecutorRunnable()
+ { }
+
+ virtual bool
+ IsDebuggerRunnable() const override;
+
+ virtual bool
+ PreRun(WorkerPrivate* aWorkerPrivate) override;
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
+
+ virtual void
+ PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
+ override;
+
+ nsresult
+ Cancel() override;
+
+ void
+ ShutdownScriptLoader(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate,
+ bool aResult,
+ bool aMutedError);
+
+ void LogExceptionToConsole(JSContext* aCx,
+ WorkerPrivate* WorkerPrivate);
+};
+
+class CacheScriptLoader;
+
+class CacheCreator final : public PromiseNativeHandler
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit CacheCreator(WorkerPrivate* aWorkerPrivate)
+ : mCacheName(aWorkerPrivate->ServiceWorkerCacheName())
+ , mOriginAttributes(aWorkerPrivate->GetOriginAttributes())
+ {
+ MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
+ MOZ_ASSERT(aWorkerPrivate->LoadScriptAsPartOfLoadingServiceWorkerScript());
+ AssertIsOnMainThread();
+ }
+
+ void
+ AddLoader(CacheScriptLoader* aLoader)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mCacheStorage);
+ mLoaders.AppendElement(aLoader);
+ }
+
+ virtual void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+ virtual void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+ // Try to load from cache with aPrincipal used for cache access.
+ nsresult
+ Load(nsIPrincipal* aPrincipal);
+
+ Cache*
+ Cache_() const
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mCache);
+ return mCache;
+ }
+
+ nsIGlobalObject*
+ Global() const
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mSandboxGlobalObject);
+ return mSandboxGlobalObject;
+ }
+
+ void
+ DeleteCache();
+
+private:
+ ~CacheCreator()
+ {
+ }
+
+ nsresult
+ CreateCacheStorage(nsIPrincipal* aPrincipal);
+
+ void
+ FailLoaders(nsresult aRv);
+
+ RefPtr<Cache> mCache;
+ RefPtr<CacheStorage> mCacheStorage;
+ nsCOMPtr<nsIGlobalObject> mSandboxGlobalObject;
+ nsTArray<RefPtr<CacheScriptLoader>> mLoaders;
+
+ nsString mCacheName;
+ PrincipalOriginAttributes mOriginAttributes;
+};
+
+NS_IMPL_ISUPPORTS0(CacheCreator)
+
+class CacheScriptLoader final : public PromiseNativeHandler
+ , public nsIStreamLoaderObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+ CacheScriptLoader(WorkerPrivate* aWorkerPrivate, ScriptLoadInfo& aLoadInfo,
+ uint32_t aIndex, bool aIsWorkerScript,
+ ScriptLoaderRunnable* aRunnable)
+ : mLoadInfo(aLoadInfo)
+ , mIndex(aIndex)
+ , mRunnable(aRunnable)
+ , mIsWorkerScript(aIsWorkerScript)
+ , mFailed(false)
+ {
+ MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
+ mBaseURI = GetBaseURI(mIsWorkerScript, aWorkerPrivate);
+ AssertIsOnMainThread();
+ }
+
+ void
+ Fail(nsresult aRv);
+
+ void
+ Load(Cache* aCache);
+
+ virtual void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+ virtual void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+private:
+ ~CacheScriptLoader()
+ {
+ AssertIsOnMainThread();
+ }
+
+ ScriptLoadInfo& mLoadInfo;
+ uint32_t mIndex;
+ RefPtr<ScriptLoaderRunnable> mRunnable;
+ bool mIsWorkerScript;
+ bool mFailed;
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ nsCOMPtr<nsIURI> mBaseURI;
+ mozilla::dom::ChannelInfo mChannelInfo;
+ UniquePtr<PrincipalInfo> mPrincipalInfo;
+};
+
+NS_IMPL_ISUPPORTS(CacheScriptLoader, nsIStreamLoaderObserver)
+
+class CachePromiseHandler final : public PromiseNativeHandler
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ CachePromiseHandler(ScriptLoaderRunnable* aRunnable,
+ ScriptLoadInfo& aLoadInfo,
+ uint32_t aIndex)
+ : mRunnable(aRunnable)
+ , mLoadInfo(aLoadInfo)
+ , mIndex(aIndex)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mRunnable);
+ }
+
+ virtual void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+ virtual void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+private:
+ ~CachePromiseHandler()
+ {
+ AssertIsOnMainThread();
+ }
+
+ RefPtr<ScriptLoaderRunnable> mRunnable;
+ ScriptLoadInfo& mLoadInfo;
+ uint32_t mIndex;
+};
+
+NS_IMPL_ISUPPORTS0(CachePromiseHandler)
+
+class LoaderListener final : public nsIStreamLoaderObserver
+ , public nsIRequestObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ LoaderListener(ScriptLoaderRunnable* aRunnable, uint32_t aIndex)
+ : mRunnable(aRunnable)
+ , mIndex(aIndex)
+ {
+ MOZ_ASSERT(mRunnable);
+ }
+
+ NS_IMETHOD
+ OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
+ nsresult aStatus, uint32_t aStringLen,
+ const uint8_t* aString) override;
+
+ NS_IMETHOD
+ OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) override;
+
+ NS_IMETHOD
+ OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
+ nsresult aStatusCode) override
+ {
+ // Nothing to do here!
+ return NS_OK;
+ }
+
+private:
+ ~LoaderListener() {}
+
+ RefPtr<ScriptLoaderRunnable> mRunnable;
+ uint32_t mIndex;
+};
+
+NS_IMPL_ISUPPORTS(LoaderListener, nsIStreamLoaderObserver, nsIRequestObserver)
+
+class ScriptLoaderHolder;
+
+class ScriptLoaderRunnable final : public nsIRunnable
+{
+ friend class ScriptExecutorRunnable;
+ friend class ScriptLoaderHolder;
+ friend class CachePromiseHandler;
+ friend class CacheScriptLoader;
+ friend class LoaderListener;
+
+ WorkerPrivate* mWorkerPrivate;
+ nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
+ nsTArray<ScriptLoadInfo> mLoadInfos;
+ RefPtr<CacheCreator> mCacheCreator;
+ bool mIsMainScript;
+ WorkerScriptType mWorkerScriptType;
+ bool mCanceled;
+ bool mCanceledMainThread;
+ ErrorResult& mRv;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate,
+ nsIEventTarget* aSyncLoopTarget,
+ nsTArray<ScriptLoadInfo>& aLoadInfos,
+ bool aIsMainScript,
+ WorkerScriptType aWorkerScriptType,
+ ErrorResult& aRv)
+ : mWorkerPrivate(aWorkerPrivate), mSyncLoopTarget(aSyncLoopTarget),
+ mIsMainScript(aIsMainScript), mWorkerScriptType(aWorkerScriptType),
+ mCanceled(false), mCanceledMainThread(false), mRv(aRv)
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(aSyncLoopTarget);
+ MOZ_ASSERT_IF(aIsMainScript, aLoadInfos.Length() == 1);
+
+ mLoadInfos.SwapElements(aLoadInfos);
+ }
+
+private:
+ ~ScriptLoaderRunnable()
+ { }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+
+ nsresult rv = RunInternal();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CancelMainThread(rv);
+ }
+
+ return NS_OK;
+ }
+
+ void
+ LoadingFinished(uint32_t aIndex, nsresult aRv)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aIndex < mLoadInfos.Length());
+ ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
+
+ loadInfo.mLoadResult = aRv;
+
+ MOZ_ASSERT(!loadInfo.mLoadingFinished);
+ loadInfo.mLoadingFinished = true;
+
+ MaybeExecuteFinishedScripts(aIndex);
+ }
+
+ void
+ MaybeExecuteFinishedScripts(uint32_t aIndex)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aIndex < mLoadInfos.Length());
+ ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
+
+ // We execute the last step if we don't have a pending operation with the
+ // cache and the loading is completed.
+ if (loadInfo.Finished()) {
+ ExecuteFinishedScripts();
+ }
+ }
+
+ nsresult
+ OnStreamComplete(nsIStreamLoader* aLoader, uint32_t aIndex,
+ nsresult aStatus, uint32_t aStringLen,
+ const uint8_t* aString)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aIndex < mLoadInfos.Length());
+
+ nsresult rv = OnStreamCompleteInternal(aLoader, aStatus, aStringLen,
+ aString, mLoadInfos[aIndex]);
+ LoadingFinished(aIndex, rv);
+ return NS_OK;
+ }
+
+ nsresult
+ OnStartRequest(nsIRequest* aRequest, uint32_t aIndex)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aIndex < mLoadInfos.Length());
+
+ // If one load info cancels or hits an error, it can race with the start
+ // callback coming from another load info.
+ if (mCanceledMainThread || !mCacheCreator) {
+ aRequest->Cancel(NS_ERROR_FAILURE);
+ return NS_ERROR_FAILURE;
+ }
+
+ ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ MOZ_ASSERT(channel == loadInfo.mChannel);
+
+ // We synthesize the result code, but its never exposed to content.
+ RefPtr<mozilla::dom::InternalResponse> ir =
+ new mozilla::dom::InternalResponse(200, NS_LITERAL_CSTRING("OK"));
+ ir->SetBody(loadInfo.mCacheReadStream, InternalResponse::UNKNOWN_BODY_SIZE);
+ // Drop our reference to the stream now that we've passed it along, so it
+ // doesn't hang around once the cache is done with it and keep data alive.
+ loadInfo.mCacheReadStream = nullptr;
+
+ // Set the channel info of the channel on the response so that it's
+ // saved in the cache.
+ ir->InitChannelInfo(channel);
+
+ // Save the principal of the channel since its URI encodes the script URI
+ // rather than the ServiceWorkerRegistrationInfo URI.
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ NS_ASSERTION(ssm, "Should never be null!");
+
+ nsCOMPtr<nsIPrincipal> channelPrincipal;
+ nsresult rv = ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ channel->Cancel(rv);
+ return rv;
+ }
+
+ UniquePtr<PrincipalInfo> principalInfo(new PrincipalInfo());
+ rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ channel->Cancel(rv);
+ return rv;
+ }
+
+ ir->SetPrincipalInfo(Move(principalInfo));
+
+ RefPtr<mozilla::dom::Response> response =
+ new mozilla::dom::Response(mCacheCreator->Global(), ir);
+
+ mozilla::dom::RequestOrUSVString request;
+
+ MOZ_ASSERT(!loadInfo.mFullURL.IsEmpty());
+ request.SetAsUSVString().Rebind(loadInfo.mFullURL.Data(),
+ loadInfo.mFullURL.Length());
+
+ ErrorResult error;
+ RefPtr<Promise> cachePromise =
+ mCacheCreator->Cache_()->Put(request, *response, error);
+ if (NS_WARN_IF(error.Failed())) {
+ nsresult rv = error.StealNSResult();
+ channel->Cancel(rv);
+ return rv;
+ }
+
+ RefPtr<CachePromiseHandler> promiseHandler =
+ new CachePromiseHandler(this, loadInfo, aIndex);
+ cachePromise->AppendNativeHandler(promiseHandler);
+
+ loadInfo.mCachePromise.swap(cachePromise);
+ loadInfo.mCacheStatus = ScriptLoadInfo::WritingToCache;
+
+ return NS_OK;
+ }
+
+ bool
+ Notify(Status aStatus)
+ {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (aStatus >= Terminating && !mCanceled) {
+ mCanceled = true;
+
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_DispatchToMainThread(NewRunnableMethod(this,
+ &ScriptLoaderRunnable::CancelMainThreadWithBindingAborted)));
+ }
+
+ return true;
+ }
+
+ bool
+ IsMainWorkerScript() const
+ {
+ return mIsMainScript && mWorkerScriptType == WorkerScript;
+ }
+
+ void
+ CancelMainThreadWithBindingAborted()
+ {
+ CancelMainThread(NS_BINDING_ABORTED);
+ }
+
+ void
+ CancelMainThread(nsresult aCancelResult)
+ {
+ AssertIsOnMainThread();
+
+ if (mCanceledMainThread) {
+ return;
+ }
+
+ mCanceledMainThread = true;
+
+ if (mCacheCreator) {
+ MOZ_ASSERT(mWorkerPrivate->IsServiceWorker());
+ DeleteCache();
+ }
+
+ // Cancel all the channels that were already opened.
+ for (uint32_t index = 0; index < mLoadInfos.Length(); index++) {
+ ScriptLoadInfo& loadInfo = mLoadInfos[index];
+
+ // If promise or channel is non-null, their failures will lead to
+ // LoadingFinished being called.
+ bool callLoadingFinished = true;
+
+ if (loadInfo.mCachePromise) {
+ MOZ_ASSERT(mWorkerPrivate->IsServiceWorker());
+ loadInfo.mCachePromise->MaybeReject(aCancelResult);
+ loadInfo.mCachePromise = nullptr;
+ callLoadingFinished = false;
+ }
+
+ if (loadInfo.mChannel) {
+ if (NS_SUCCEEDED(loadInfo.mChannel->Cancel(aCancelResult))) {
+ callLoadingFinished = false;
+ } else {
+ NS_WARNING("Failed to cancel channel!");
+ }
+ }
+
+ if (callLoadingFinished && !loadInfo.Finished()) {
+ LoadingFinished(index, aCancelResult);
+ }
+ }
+
+ ExecuteFinishedScripts();
+ }
+
+ void
+ DeleteCache()
+ {
+ AssertIsOnMainThread();
+
+ if (!mCacheCreator) {
+ return;
+ }
+
+ mCacheCreator->DeleteCache();
+ mCacheCreator = nullptr;
+ }
+
+ nsresult
+ RunInternal()
+ {
+ AssertIsOnMainThread();
+
+ if (IsMainWorkerScript() && mWorkerPrivate->IsServiceWorker()) {
+ mWorkerPrivate->SetLoadingWorkerScript(true);
+ }
+
+ if (!mWorkerPrivate->IsServiceWorker() ||
+ !mWorkerPrivate->LoadScriptAsPartOfLoadingServiceWorkerScript()) {
+ for (uint32_t index = 0, len = mLoadInfos.Length(); index < len;
+ ++index) {
+ nsresult rv = LoadScript(index);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LoadingFinished(index, rv);
+ return rv;
+ }
+ }
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!mCacheCreator);
+ mCacheCreator = new CacheCreator(mWorkerPrivate);
+
+ for (uint32_t index = 0, len = mLoadInfos.Length(); index < len; ++index) {
+ RefPtr<CacheScriptLoader> loader =
+ new CacheScriptLoader(mWorkerPrivate, mLoadInfos[index], index,
+ IsMainWorkerScript(), this);
+ mCacheCreator->AddLoader(loader);
+ }
+
+ // The worker may have a null principal on first load, but in that case its
+ // parent definitely will have one.
+ nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
+ if (!principal) {
+ WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
+ MOZ_ASSERT(parentWorker, "Must have a parent!");
+ principal = parentWorker->GetPrincipal();
+ }
+
+ nsresult rv = mCacheCreator->Load(principal);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ nsresult
+ LoadScript(uint32_t aIndex)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aIndex < mLoadInfos.Length());
+
+ WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
+
+ // Figure out which principal to use.
+ nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
+ nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup();
+ if (!principal) {
+ NS_ASSERTION(parentWorker, "Must have a principal!");
+ NS_ASSERTION(mIsMainScript, "Must have a principal for importScripts!");
+
+ principal = parentWorker->GetPrincipal();
+ loadGroup = parentWorker->GetLoadGroup();
+ }
+ NS_ASSERTION(principal, "This should never be null here!");
+ MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal));
+
+ // Figure out our base URI.
+ nsCOMPtr<nsIURI> baseURI = GetBaseURI(mIsMainScript, mWorkerPrivate);
+
+ // May be null.
+ nsCOMPtr<nsIDocument> parentDoc = mWorkerPrivate->GetDocument();
+
+ nsCOMPtr<nsIChannel> channel;
+ if (IsMainWorkerScript()) {
+ // May be null.
+ channel = mWorkerPrivate->ForgetWorkerChannel();
+ }
+
+ nsCOMPtr<nsIIOService> ios(do_GetIOService());
+
+ nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+ NS_ASSERTION(secMan, "This should never be null!");
+
+ ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
+ nsresult& rv = loadInfo.mLoadResult;
+
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
+
+ // Get the top-level worker.
+ WorkerPrivate* topWorkerPrivate = mWorkerPrivate;
+ WorkerPrivate* parent = topWorkerPrivate->GetParent();
+ while (parent) {
+ topWorkerPrivate = parent;
+ parent = topWorkerPrivate->GetParent();
+ }
+
+ // If the top-level worker is a dedicated worker and has a window, and the
+ // window has a docshell, the caching behavior of this worker should match
+ // that of that docshell.
+ if (topWorkerPrivate->IsDedicatedWorker()) {
+ nsCOMPtr<nsPIDOMWindowInner> window = topWorkerPrivate->GetWindow();
+ if (window) {
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ if (docShell) {
+ nsresult rv = docShell->GetDefaultLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ // If we are loading a script for a ServiceWorker then we must not
+ // try to intercept it. If the interception matches the current
+ // ServiceWorker's scope then we could deadlock the load.
+ if (mWorkerPrivate->IsServiceWorker()) {
+ loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+ }
+
+ if (!channel) {
+ // Only top level workers' main script use the document charset for the
+ // script uri encoding. Otherwise, default encoding (UTF-8) is applied.
+ bool useDefaultEncoding = !(!parentWorker && IsMainWorkerScript());
+ rv = ChannelFromScriptURL(principal, baseURI, parentDoc, loadGroup, ios,
+ secMan, loadInfo.mURL, IsMainWorkerScript(),
+ mWorkerScriptType,
+ mWorkerPrivate->ContentPolicyType(), loadFlags,
+ useDefaultEncoding,
+ getter_AddRefs(channel));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // We need to know which index we're on in OnStreamComplete so we know
+ // where to put the result.
+ RefPtr<LoaderListener> listener = new LoaderListener(this, aIndex);
+
+ // We don't care about progress so just use the simple stream loader for
+ // OnStreamComplete notification only.
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(getter_AddRefs(loader), listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (loadInfo.mCacheStatus != ScriptLoadInfo::ToBeCached) {
+ rv = channel->AsyncOpen2(loader);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ nsCOMPtr<nsIOutputStream> writer;
+
+ // In case we return early.
+ loadInfo.mCacheStatus = ScriptLoadInfo::Cancel;
+
+ rv = NS_NewPipe(getter_AddRefs(loadInfo.mCacheReadStream),
+ getter_AddRefs(writer), 0,
+ UINT32_MAX, // unlimited size to avoid writer WOULD_BLOCK case
+ true, false); // non-blocking reader, blocking writer
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIStreamListenerTee> tee =
+ do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID);
+ rv = tee->Init(loader, writer, listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsresult rv = channel->AsyncOpen2(tee);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ loadInfo.mChannel.swap(channel);
+
+ return NS_OK;
+ }
+
+ nsresult
+ OnStreamCompleteInternal(nsIStreamLoader* aLoader, nsresult aStatus,
+ uint32_t aStringLen, const uint8_t* aString,
+ ScriptLoadInfo& aLoadInfo)
+ {
+ AssertIsOnMainThread();
+
+ if (!aLoadInfo.mChannel) {
+ return NS_BINDING_ABORTED;
+ }
+
+ aLoadInfo.mChannel = nullptr;
+
+ if (NS_FAILED(aStatus)) {
+ return aStatus;
+ }
+
+ NS_ASSERTION(aString, "This should never be null!");
+
+ nsCOMPtr<nsIRequest> request;
+ nsresult rv = aLoader->GetRequest(getter_AddRefs(request));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ MOZ_ASSERT(channel);
+
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ NS_ASSERTION(ssm, "Should never be null!");
+
+ nsCOMPtr<nsIPrincipal> channelPrincipal;
+ rv = ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
+ if (!principal) {
+ WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
+ MOZ_ASSERT(parentWorker, "Must have a parent!");
+ principal = parentWorker->GetPrincipal();
+ }
+
+ // We don't mute the main worker script becase we've already done
+ // same-origin checks on them so we should be able to see their errors.
+ // Note that for data: url, where we allow it through the same-origin check
+ // but then give it a different origin.
+ aLoadInfo.mMutedErrorFlag.emplace(IsMainWorkerScript()
+ ? false
+ : !principal->Subsumes(channelPrincipal));
+
+ // Make sure we're not seeing the result of a 404 or something by checking
+ // the 'requestSucceeded' attribute on the http channel.
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
+ nsAutoCString tCspHeaderValue, tCspROHeaderValue, tRPHeaderCValue;
+
+ if (httpChannel) {
+ bool requestSucceeded;
+ rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!requestSucceeded) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ httpChannel->GetResponseHeader(
+ NS_LITERAL_CSTRING("content-security-policy"),
+ tCspHeaderValue);
+
+ httpChannel->GetResponseHeader(
+ NS_LITERAL_CSTRING("content-security-policy-report-only"),
+ tCspROHeaderValue);
+
+ httpChannel->GetResponseHeader(
+ NS_LITERAL_CSTRING("referrer-policy"),
+ tRPHeaderCValue);
+ }
+
+ // May be null.
+ nsIDocument* parentDoc = mWorkerPrivate->GetDocument();
+
+ // Use the regular nsScriptLoader for this grunt work! Should be just fine
+ // because we're running on the main thread.
+ // Unlike <script> tags, Worker scripts are always decoded as UTF-8,
+ // per spec. So we explicitly pass in the charset hint.
+ rv = nsScriptLoader::ConvertToUTF16(aLoadInfo.mChannel, aString, aStringLen,
+ NS_LITERAL_STRING("UTF-8"), parentDoc,
+ aLoadInfo.mScriptTextBuf,
+ aLoadInfo.mScriptTextLength);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!aLoadInfo.mScriptTextLength && !aLoadInfo.mScriptTextBuf) {
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM"), parentDoc,
+ nsContentUtils::eDOM_PROPERTIES,
+ "EmptyWorkerSourceWarning");
+ } else if (!aLoadInfo.mScriptTextBuf) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Figure out what we actually loaded.
+ nsCOMPtr<nsIURI> finalURI;
+ rv = NS_GetFinalChannelURI(channel, getter_AddRefs(finalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString filename;
+ rv = finalURI->GetSpec(filename);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!filename.IsEmpty()) {
+ // This will help callers figure out what their script url resolved to in
+ // case of errors.
+ aLoadInfo.mURL.Assign(NS_ConvertUTF8toUTF16(filename));
+ }
+
+ nsCOMPtr<nsILoadInfo> chanLoadInfo = channel->GetLoadInfo();
+ if (chanLoadInfo && chanLoadInfo->GetEnforceSRI()) {
+ // importScripts() and the Worker constructor do not support integrity metadata
+ // (or any fetch options). Until then, we can just block.
+ // If we ever have those data in the future, we'll have to the check to
+ // by using the SRICheck module
+ MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
+ ("Scriptloader::Load, SRI required but not supported in workers"));
+ nsCOMPtr<nsIContentSecurityPolicy> wcsp;
+ chanLoadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(wcsp));
+ MOZ_ASSERT(wcsp, "We sould have a CSP for the worker here");
+ if (wcsp) {
+ wcsp->LogViolationDetails(
+ nsIContentSecurityPolicy::VIOLATION_TYPE_REQUIRE_SRI_FOR_SCRIPT,
+ aLoadInfo.mURL, EmptyString(), 0, EmptyString(), EmptyString());
+ }
+ return NS_ERROR_SRI_CORRUPT;
+ }
+
+ // Update the principal of the worker and its base URI if we just loaded the
+ // worker's primary script.
+ if (IsMainWorkerScript()) {
+ // Take care of the base URI first.
+ mWorkerPrivate->SetBaseURI(finalURI);
+
+ // Store the channel info if needed.
+ mWorkerPrivate->InitChannelInfo(channel);
+
+ // Now to figure out which principal to give this worker.
+ WorkerPrivate* parent = mWorkerPrivate->GetParent();
+
+ NS_ASSERTION(mWorkerPrivate->GetPrincipal() || parent,
+ "Must have one of these!");
+
+ nsCOMPtr<nsIPrincipal> loadPrincipal = mWorkerPrivate->GetPrincipal() ?
+ mWorkerPrivate->GetPrincipal() :
+ parent->GetPrincipal();
+
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ NS_ASSERTION(ssm, "Should never be null!");
+
+ nsCOMPtr<nsIPrincipal> channelPrincipal;
+ rv = ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadGroup> channelLoadGroup;
+ rv = channel->GetLoadGroup(getter_AddRefs(channelLoadGroup));
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(channelLoadGroup);
+
+ // If the load principal is the system principal then the channel
+ // principal must also be the system principal (we do not allow chrome
+ // code to create workers with non-chrome scripts, and if we ever decide
+ // to change this we need to make sure we don't always set
+ // mPrincipalIsSystem to true in WorkerPrivate::GetLoadInfo()). Otherwise
+ // this channel principal must be same origin with the load principal (we
+ // check again here in case redirects changed the location of the script).
+ if (nsContentUtils::IsSystemPrincipal(loadPrincipal)) {
+ if (!nsContentUtils::IsSystemPrincipal(channelPrincipal)) {
+ // See if this is a resource URI. Since JSMs usually come from
+ // resource:// URIs we're currently considering all URIs with the
+ // URI_IS_UI_RESOURCE flag as valid for creating privileged workers.
+ bool isResource;
+ rv = NS_URIChainHasFlags(finalURI,
+ nsIProtocolHandler::URI_IS_UI_RESOURCE,
+ &isResource);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isResource) {
+ // Assign the system principal to the resource:// worker only if it
+ // was loaded from code using the system principal.
+ channelPrincipal = loadPrincipal;
+ } else {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+ }
+
+ // The principal can change, but it should still match the original
+ // load group's appId and browser element flag.
+ MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(channelLoadGroup, channelPrincipal));
+
+ mWorkerPrivate->SetPrincipal(channelPrincipal, channelLoadGroup);
+
+ // We did inherit CSP in bug 1223647. If we do not already have a CSP, we
+ // should get it from the HTTP headers on the worker script.
+ if (!mWorkerPrivate->GetCSP() && CSPService::sCSPEnabled) {
+ NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
+ NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
+
+ nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
+ MOZ_ASSERT(principal, "Should not be null");
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ rv = principal->EnsureCSP(nullptr, getter_AddRefs(csp));
+
+ if (csp) {
+ // If there's a CSP header, apply it.
+ if (!cspHeaderValue.IsEmpty()) {
+ rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // If there's a report-only CSP header, apply it.
+ if (!cspROHeaderValue.IsEmpty()) {
+ rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Set evalAllowed, default value is set in GetAllowsEval
+ bool evalAllowed = false;
+ bool reportEvalViolations = false;
+ rv = csp->GetAllowsEval(&reportEvalViolations, &evalAllowed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mWorkerPrivate->SetCSP(csp);
+ mWorkerPrivate->SetEvalAllowed(evalAllowed);
+ mWorkerPrivate->SetReportCSPViolations(reportEvalViolations);
+
+ // Set ReferrerPolicy, default value is set in GetReferrerPolicy
+ bool hasReferrerPolicy = false;
+ uint32_t rp = mozilla::net::RP_Default;
+ rv = csp->GetReferrerPolicy(&rp, &hasReferrerPolicy);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+
+ if (hasReferrerPolicy) { //FIXME bug 1307366: move RP out of CSP code
+ mWorkerPrivate->SetReferrerPolicy(static_cast<net::ReferrerPolicy>(rp));
+ }
+ }
+ }
+ if (parent) {
+ // XHR Params Allowed
+ mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed());
+ }
+ }
+
+ NS_ConvertUTF8toUTF16 tRPHeaderValue(tRPHeaderCValue);
+ // If there's a Referrer-Policy header, apply it.
+ if (!tRPHeaderValue.IsEmpty()) {
+ net::ReferrerPolicy policy =
+ nsContentUtils::GetReferrerPolicyFromHeader(tRPHeaderValue);
+ if (policy != net::RP_Unset) {
+ mWorkerPrivate->SetReferrerPolicy(policy);
+ }
+ }
+
+ return NS_OK;
+ }
+
+ void
+ DataReceivedFromCache(uint32_t aIndex, const uint8_t* aString,
+ uint32_t aStringLen,
+ const mozilla::dom::ChannelInfo& aChannelInfo,
+ UniquePtr<PrincipalInfo> aPrincipalInfo)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aIndex < mLoadInfos.Length());
+ ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
+ MOZ_ASSERT(loadInfo.mCacheStatus == ScriptLoadInfo::Cached);
+
+ nsCOMPtr<nsIPrincipal> responsePrincipal =
+ PrincipalInfoToPrincipal(*aPrincipalInfo);
+
+ nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
+ if (!principal) {
+ WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
+ MOZ_ASSERT(parentWorker, "Must have a parent!");
+ principal = parentWorker->GetPrincipal();
+ }
+
+ loadInfo.mMutedErrorFlag.emplace(!principal->Subsumes(responsePrincipal));
+
+ // May be null.
+ nsIDocument* parentDoc = mWorkerPrivate->GetDocument();
+
+ MOZ_ASSERT(!loadInfo.mScriptTextBuf);
+
+ nsresult rv =
+ nsScriptLoader::ConvertToUTF16(nullptr, aString, aStringLen,
+ NS_LITERAL_STRING("UTF-8"), parentDoc,
+ loadInfo.mScriptTextBuf,
+ loadInfo.mScriptTextLength);
+ if (NS_SUCCEEDED(rv) && IsMainWorkerScript()) {
+ nsCOMPtr<nsIURI> finalURI;
+ rv = NS_NewURI(getter_AddRefs(finalURI), loadInfo.mFullURL, nullptr, nullptr);
+ if (NS_SUCCEEDED(rv)) {
+ mWorkerPrivate->SetBaseURI(finalURI);
+ }
+
+ mozilla::DebugOnly<nsIPrincipal*> principal = mWorkerPrivate->GetPrincipal();
+ MOZ_ASSERT(principal);
+ nsILoadGroup* loadGroup = mWorkerPrivate->GetLoadGroup();
+ MOZ_ASSERT(loadGroup);
+
+ mozilla::DebugOnly<bool> equal = false;
+ MOZ_ASSERT(responsePrincipal && NS_SUCCEEDED(responsePrincipal->Equals(principal, &equal)));
+ MOZ_ASSERT(equal);
+
+ mWorkerPrivate->InitChannelInfo(aChannelInfo);
+ mWorkerPrivate->SetPrincipal(responsePrincipal, loadGroup);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ DataReceived();
+ }
+
+ LoadingFinished(aIndex, rv);
+ }
+
+ void
+ DataReceived()
+ {
+ if (IsMainWorkerScript()) {
+ WorkerPrivate* parent = mWorkerPrivate->GetParent();
+
+ if (parent) {
+ // XHR Params Allowed
+ mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed());
+
+ // Set Eval and ContentSecurityPolicy
+ mWorkerPrivate->SetCSP(parent->GetCSP());
+ mWorkerPrivate->SetEvalAllowed(parent->IsEvalAllowed());
+ }
+ }
+ }
+
+ void
+ ExecuteFinishedScripts()
+ {
+ AssertIsOnMainThread();
+
+ if (IsMainWorkerScript()) {
+ mWorkerPrivate->WorkerScriptLoaded();
+ }
+
+ uint32_t firstIndex = UINT32_MAX;
+ uint32_t lastIndex = UINT32_MAX;
+
+ // Find firstIndex based on whether mExecutionScheduled is unset.
+ for (uint32_t index = 0; index < mLoadInfos.Length(); index++) {
+ if (!mLoadInfos[index].mExecutionScheduled) {
+ firstIndex = index;
+ break;
+ }
+ }
+
+ // Find lastIndex based on whether mChannel is set, and update
+ // mExecutionScheduled on the ones we're about to schedule.
+ if (firstIndex != UINT32_MAX) {
+ for (uint32_t index = firstIndex; index < mLoadInfos.Length(); index++) {
+ ScriptLoadInfo& loadInfo = mLoadInfos[index];
+
+ if (!loadInfo.Finished()) {
+ break;
+ }
+
+ // We can execute this one.
+ loadInfo.mExecutionScheduled = true;
+
+ lastIndex = index;
+ }
+ }
+
+ // This is the last index, we can unused things before the exection of the
+ // script and the stopping of the sync loop.
+ if (lastIndex == mLoadInfos.Length() - 1) {
+ mCacheCreator = nullptr;
+ }
+
+ if (firstIndex != UINT32_MAX && lastIndex != UINT32_MAX) {
+ RefPtr<ScriptExecutorRunnable> runnable =
+ new ScriptExecutorRunnable(*this, mSyncLoopTarget, IsMainWorkerScript(),
+ firstIndex, lastIndex);
+ if (!runnable->Dispatch()) {
+ MOZ_ASSERT(false, "This should never fail!");
+ }
+ }
+ }
+};
+
+NS_IMPL_ISUPPORTS(ScriptLoaderRunnable, nsIRunnable)
+
+class MOZ_STACK_CLASS ScriptLoaderHolder final : public WorkerHolder
+{
+ // Raw pointer because this holder object follows the mRunnable life-time.
+ ScriptLoaderRunnable* mRunnable;
+
+public:
+ explicit ScriptLoaderHolder(ScriptLoaderRunnable* aRunnable)
+ : mRunnable(aRunnable)
+ {
+ MOZ_ASSERT(aRunnable);
+ }
+
+ virtual bool
+ Notify(Status aStatus) override
+ {
+ mRunnable->Notify(aStatus);
+ return true;
+ }
+};
+
+NS_IMETHODIMP
+LoaderListener::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
+ nsresult aStatus, uint32_t aStringLen,
+ const uint8_t* aString)
+{
+ return mRunnable->OnStreamComplete(aLoader, mIndex, aStatus, aStringLen, aString);
+}
+
+NS_IMETHODIMP
+LoaderListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+ return mRunnable->OnStartRequest(aRequest, mIndex);
+}
+
+void
+CachePromiseHandler::ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ AssertIsOnMainThread();
+ // May already have been canceled by CacheScriptLoader::Fail from
+ // CancelMainThread.
+ MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::WritingToCache ||
+ mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel);
+ MOZ_ASSERT_IF(mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel, !mLoadInfo.mCachePromise);
+
+ if (mLoadInfo.mCachePromise) {
+ mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
+ mLoadInfo.mCachePromise = nullptr;
+ mRunnable->MaybeExecuteFinishedScripts(mIndex);
+ }
+}
+
+void
+CachePromiseHandler::RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ AssertIsOnMainThread();
+ // May already have been canceled by CacheScriptLoader::Fail from
+ // CancelMainThread.
+ MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::WritingToCache ||
+ mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel);
+ mLoadInfo.mCacheStatus = ScriptLoadInfo::Cancel;
+
+ mLoadInfo.mCachePromise = nullptr;
+
+ // This will delete the cache object and will call LoadingFinished() with an
+ // error for each ongoing operation.
+ mRunnable->DeleteCache();
+}
+
+nsresult
+CacheCreator::CreateCacheStorage(nsIPrincipal* aPrincipal)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mCacheStorage);
+ MOZ_ASSERT(aPrincipal);
+
+ nsIXPConnect* xpc = nsContentUtils::XPConnect();
+ MOZ_ASSERT(xpc, "This should never be null!");
+
+ mozilla::AutoSafeJSContext cx;
+ JS::Rooted<JSObject*> sandbox(cx);
+ nsresult rv = xpc->CreateSandbox(cx, aPrincipal, sandbox.address());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mSandboxGlobalObject = xpc::NativeGlobal(sandbox);
+ if (NS_WARN_IF(!mSandboxGlobalObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we're in private browsing mode, don't even try to create the
+ // CacheStorage. Instead, just fail immediately to terminate the
+ // ServiceWorker load.
+ if (NS_WARN_IF(mOriginAttributes.mPrivateBrowsingId > 0)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // Create a CacheStorage bypassing its trusted origin checks. The
+ // ServiceWorker has already performed its own checks before getting
+ // to this point.
+ ErrorResult error;
+ mCacheStorage =
+ CacheStorage::CreateOnMainThread(mozilla::dom::cache::CHROME_ONLY_NAMESPACE,
+ mSandboxGlobalObject,
+ aPrincipal,
+ false, /* privateBrowsing can't be true here */
+ true /* force trusted origin */,
+ error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CacheCreator::Load(nsIPrincipal* aPrincipal)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mLoaders.IsEmpty());
+
+ nsresult rv = CreateCacheStorage(aPrincipal);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ ErrorResult error;
+ MOZ_ASSERT(!mCacheName.IsEmpty());
+ RefPtr<Promise> promise = mCacheStorage->Open(mCacheName, error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ promise->AppendNativeHandler(this);
+ return NS_OK;
+}
+
+void
+CacheCreator::FailLoaders(nsresult aRv)
+{
+ AssertIsOnMainThread();
+
+ // Fail() can call LoadingFinished() which may call ExecuteFinishedScripts()
+ // which sets mCacheCreator to null, so hold a ref.
+ RefPtr<CacheCreator> kungfuDeathGrip = this;
+
+ for (uint32_t i = 0, len = mLoaders.Length(); i < len; ++i) {
+ mLoaders[i]->Fail(aRv);
+ }
+
+ mLoaders.Clear();
+}
+
+void
+CacheCreator::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+ AssertIsOnMainThread();
+ FailLoaders(NS_ERROR_FAILURE);
+}
+
+void
+CacheCreator::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+ AssertIsOnMainThread();
+
+ if (!aValue.isObject()) {
+ FailLoaders(NS_ERROR_FAILURE);
+ return;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+ Cache* cache = nullptr;
+ nsresult rv = UNWRAP_OBJECT(Cache, &obj, cache);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailLoaders(NS_ERROR_FAILURE);
+ return;
+ }
+
+ mCache = cache;
+ MOZ_DIAGNOSTIC_ASSERT(mCache);
+
+ // If the worker is canceled, CancelMainThread() will have cleared the
+ // loaders via DeleteCache().
+ for (uint32_t i = 0, len = mLoaders.Length(); i < len; ++i) {
+ MOZ_DIAGNOSTIC_ASSERT(mLoaders[i]);
+ mLoaders[i]->Load(cache);
+ }
+}
+
+void
+CacheCreator::DeleteCache()
+{
+ AssertIsOnMainThread();
+
+ // This is called when the load is canceled which can occur before
+ // mCacheStorage is initialized.
+ if (mCacheStorage) {
+ // It's safe to do this while Cache::Match() and Cache::Put() calls are
+ // running.
+ IgnoredErrorResult rv;
+ RefPtr<Promise> promise = mCacheStorage->Delete(mCacheName, rv);
+
+ // We don't care to know the result of the promise object.
+ }
+
+ // Always call this here to ensure the loaders array is cleared.
+ FailLoaders(NS_ERROR_FAILURE);
+}
+
+void
+CacheScriptLoader::Fail(nsresult aRv)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(NS_FAILED(aRv));
+
+ if (mFailed) {
+ return;
+ }
+
+ mFailed = true;
+
+ if (mPump) {
+ MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::ReadingFromCache);
+ mPump->Cancel(aRv);
+ mPump = nullptr;
+ }
+
+ mLoadInfo.mCacheStatus = ScriptLoadInfo::Cancel;
+
+ // Stop if the load was aborted on the main thread.
+ // Can't use Finished() because mCachePromise may still be true.
+ if (mLoadInfo.mLoadingFinished) {
+ MOZ_ASSERT(!mLoadInfo.mChannel);
+ MOZ_ASSERT_IF(mLoadInfo.mCachePromise,
+ mLoadInfo.mCacheStatus == ScriptLoadInfo::WritingToCache ||
+ mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel);
+ return;
+ }
+
+ mRunnable->LoadingFinished(mIndex, aRv);
+}
+
+void
+CacheScriptLoader::Load(Cache* aCache)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aCache);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), mLoadInfo.mURL, nullptr,
+ mBaseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ nsAutoCString spec;
+ rv = uri->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ MOZ_ASSERT(mLoadInfo.mFullURL.IsEmpty());
+ CopyUTF8toUTF16(spec, mLoadInfo.mFullURL);
+
+ mozilla::dom::RequestOrUSVString request;
+ request.SetAsUSVString().Rebind(mLoadInfo.mFullURL.Data(),
+ mLoadInfo.mFullURL.Length());
+
+ mozilla::dom::CacheQueryOptions params;
+
+ ErrorResult error;
+ RefPtr<Promise> promise = aCache->Match(request, params, error);
+ if (NS_WARN_IF(error.Failed())) {
+ Fail(error.StealNSResult());
+ return;
+ }
+
+ promise->AppendNativeHandler(this);
+}
+
+void
+CacheScriptLoader::RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::Uncached);
+ Fail(NS_ERROR_FAILURE);
+}
+
+void
+CacheScriptLoader::ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ AssertIsOnMainThread();
+ // If we have already called 'Fail', we should not proceed.
+ if (mFailed) {
+ return;
+ }
+
+ MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::Uncached);
+
+ nsresult rv;
+
+ if (aValue.isUndefined()) {
+ mLoadInfo.mCacheStatus = ScriptLoadInfo::ToBeCached;
+ rv = mRunnable->LoadScript(mIndex);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ }
+ return;
+ }
+
+ MOZ_ASSERT(aValue.isObject());
+
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+ mozilla::dom::Response* response = nullptr;
+ rv = UNWRAP_OBJECT(Response, &obj, response);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ response->GetBody(getter_AddRefs(inputStream));
+ mChannelInfo = response->GetChannelInfo();
+ const UniquePtr<PrincipalInfo>& pInfo = response->GetPrincipalInfo();
+ if (pInfo) {
+ mPrincipalInfo = mozilla::MakeUnique<PrincipalInfo>(*pInfo);
+ }
+
+ if (!inputStream) {
+ mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
+ mRunnable->DataReceivedFromCache(mIndex, (uint8_t*)"", 0, mChannelInfo,
+ Move(mPrincipalInfo));
+ return;
+ }
+
+ MOZ_ASSERT(!mPump);
+ rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inputStream);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ rv = mPump->AsyncRead(loader, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mPump = nullptr;
+ Fail(rv);
+ return;
+ }
+
+
+ nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(mPump);
+ if (rr) {
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ rv = rr->RetargetDeliveryTo(sts);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the nsIInputStreamPump to a IO thread.");
+ }
+ }
+
+ mLoadInfo.mCacheStatus = ScriptLoadInfo::ReadingFromCache;
+}
+
+NS_IMETHODIMP
+CacheScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
+ nsresult aStatus, uint32_t aStringLen,
+ const uint8_t* aString)
+{
+ AssertIsOnMainThread();
+
+ mPump = nullptr;
+
+ if (NS_FAILED(aStatus)) {
+ MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::ReadingFromCache ||
+ mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel);
+ Fail(aStatus);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::ReadingFromCache);
+ mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
+
+ MOZ_ASSERT(mPrincipalInfo);
+ mRunnable->DataReceivedFromCache(mIndex, aString, aStringLen, mChannelInfo,
+ Move(mPrincipalInfo));
+ return NS_OK;
+}
+
+class ChannelGetterRunnable final : public WorkerMainThreadRunnable
+{
+ const nsAString& mScriptURL;
+ nsIChannel** mChannel;
+ nsresult mResult;
+
+public:
+ ChannelGetterRunnable(WorkerPrivate* aParentWorker,
+ const nsAString& aScriptURL,
+ nsIChannel** aChannel)
+ : WorkerMainThreadRunnable(aParentWorker,
+ NS_LITERAL_CSTRING("ScriptLoader :: ChannelGetter"))
+ , mScriptURL(aScriptURL)
+ , mChannel(aChannel)
+ , mResult(NS_ERROR_FAILURE)
+ {
+ MOZ_ASSERT(aParentWorker);
+ aParentWorker->AssertIsOnWorkerThread();
+ }
+
+ virtual bool
+ MainThreadRun() override
+ {
+ AssertIsOnMainThread();
+
+ nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
+ MOZ_ASSERT(principal);
+
+ // Figure out our base URI.
+ nsCOMPtr<nsIURI> baseURI = mWorkerPrivate->GetBaseURI();
+ MOZ_ASSERT(baseURI);
+
+ // May be null.
+ nsCOMPtr<nsIDocument> parentDoc = mWorkerPrivate->GetDocument();
+
+ nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup();
+
+ nsCOMPtr<nsIChannel> channel;
+ mResult =
+ scriptloader::ChannelFromScriptURLMainThread(principal, baseURI,
+ parentDoc, loadGroup,
+ mScriptURL,
+ // Nested workers are always dedicated.
+ nsIContentPolicy::TYPE_INTERNAL_WORKER,
+ // Nested workers use default uri encoding.
+ true,
+ getter_AddRefs(channel));
+ if (NS_SUCCEEDED(mResult)) {
+ channel.forget(mChannel);
+ }
+
+ return true;
+ }
+
+ nsresult
+ GetResult() const
+ {
+ return mResult;
+ }
+
+private:
+ virtual ~ChannelGetterRunnable()
+ { }
+};
+
+ScriptExecutorRunnable::ScriptExecutorRunnable(
+ ScriptLoaderRunnable& aScriptLoader,
+ nsIEventTarget* aSyncLoopTarget,
+ bool aIsWorkerScript,
+ uint32_t aFirstIndex,
+ uint32_t aLastIndex)
+: MainThreadWorkerSyncRunnable(aScriptLoader.mWorkerPrivate, aSyncLoopTarget),
+ mScriptLoader(aScriptLoader), mIsWorkerScript(aIsWorkerScript),
+ mFirstIndex(aFirstIndex), mLastIndex(aLastIndex)
+{
+ MOZ_ASSERT(aFirstIndex <= aLastIndex);
+ MOZ_ASSERT(aLastIndex < aScriptLoader.mLoadInfos.Length());
+}
+
+bool
+ScriptExecutorRunnable::IsDebuggerRunnable() const
+{
+ // ScriptExecutorRunnable is used to execute both worker and debugger scripts.
+ // In the latter case, the runnable needs to be dispatched to the debugger
+ // queue.
+ return mScriptLoader.mWorkerScriptType == DebuggerScript;
+}
+
+bool
+ScriptExecutorRunnable::PreRun(WorkerPrivate* aWorkerPrivate)
+{
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (!mIsWorkerScript) {
+ return true;
+ }
+
+ if (!aWorkerPrivate->GetJSContext()) {
+ return false;
+ }
+
+ MOZ_ASSERT(mFirstIndex == 0);
+ MOZ_ASSERT(!mScriptLoader.mRv.Failed());
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ WorkerGlobalScope* globalScope =
+ aWorkerPrivate->GetOrCreateGlobalScope(jsapi.cx());
+ if (NS_WARN_IF(!globalScope)) {
+ NS_WARNING("Failed to make global!");
+ // There's no way to report the exception on jsapi right now, because there
+ // is no way to even enter a compartment on this thread anymore. Just clear
+ // the exception. We'll report some sort of error to our caller in
+ // ShutdownScriptLoader, but it will get squelched for the same reason we're
+ // squelching here: all the error reporting machinery relies on being able
+ // to enter a compartment to report the error.
+ jsapi.ClearException();
+ return false;
+ }
+
+ return true;
+}
+
+bool
+ScriptExecutorRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+{
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos;
+
+ // Don't run if something else has already failed.
+ for (uint32_t index = 0; index < mFirstIndex; index++) {
+ ScriptLoadInfo& loadInfo = loadInfos.ElementAt(index);
+
+ NS_ASSERTION(!loadInfo.mChannel, "Should no longer have a channel!");
+ NS_ASSERTION(loadInfo.mExecutionScheduled, "Should be scheduled!");
+
+ if (!loadInfo.mExecutionResult) {
+ return true;
+ }
+ }
+
+ // If nothing else has failed, our ErrorResult better not be a failure either.
+ MOZ_ASSERT(!mScriptLoader.mRv.Failed(), "Who failed it and why?");
+
+ // Slightly icky action at a distance, but there's no better place to stash
+ // this value, really.
+ JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+ MOZ_ASSERT(global);
+
+ for (uint32_t index = mFirstIndex; index <= mLastIndex; index++) {
+ ScriptLoadInfo& loadInfo = loadInfos.ElementAt(index);
+
+ NS_ASSERTION(!loadInfo.mChannel, "Should no longer have a channel!");
+ NS_ASSERTION(loadInfo.mExecutionScheduled, "Should be scheduled!");
+ NS_ASSERTION(!loadInfo.mExecutionResult, "Should not have executed yet!");
+
+ MOZ_ASSERT(!mScriptLoader.mRv.Failed(), "Who failed it and why?");
+ mScriptLoader.mRv.MightThrowJSException();
+ if (NS_FAILED(loadInfo.mLoadResult)) {
+ scriptloader::ReportLoadError(mScriptLoader.mRv,
+ loadInfo.mLoadResult, loadInfo.mURL);
+ // Top level scripts only!
+ if (mIsWorkerScript) {
+ aWorkerPrivate->MaybeDispatchLoadFailedRunnable();
+ }
+ return true;
+ }
+
+ NS_ConvertUTF16toUTF8 filename(loadInfo.mURL);
+
+ JS::CompileOptions options(aCx);
+ options.setFileAndLine(filename.get(), 1)
+ .setNoScriptRval(true);
+
+ if (mScriptLoader.mWorkerScriptType == DebuggerScript) {
+ options.setVersion(JSVERSION_LATEST);
+ }
+
+ MOZ_ASSERT(loadInfo.mMutedErrorFlag.isSome());
+ options.setMutedErrors(loadInfo.mMutedErrorFlag.valueOr(true));
+
+ JS::SourceBufferHolder srcBuf(loadInfo.mScriptTextBuf,
+ loadInfo.mScriptTextLength,
+ JS::SourceBufferHolder::GiveOwnership);
+ loadInfo.mScriptTextBuf = nullptr;
+ loadInfo.mScriptTextLength = 0;
+
+ // Our ErrorResult still shouldn't be a failure.
+ MOZ_ASSERT(!mScriptLoader.mRv.Failed(), "Who failed it and why?");
+ JS::Rooted<JS::Value> unused(aCx);
+ if (!JS::Evaluate(aCx, options, srcBuf, &unused)) {
+ mScriptLoader.mRv.StealExceptionFromJSContext(aCx);
+ return true;
+ }
+
+ loadInfo.mExecutionResult = true;
+ }
+
+ return true;
+}
+
+void
+ScriptExecutorRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult)
+{
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx), "Who left an exception on there?");
+
+ nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos;
+
+ if (mLastIndex == loadInfos.Length() - 1) {
+ // All done. If anything failed then return false.
+ bool result = true;
+ bool mutedError = false;
+ for (uint32_t index = 0; index < loadInfos.Length(); index++) {
+ if (!loadInfos[index].mExecutionResult) {
+ mutedError = loadInfos[index].mMutedErrorFlag.valueOr(true);
+ result = false;
+ break;
+ }
+ }
+
+ // The only way we can get here with "result" false but without
+ // mScriptLoader.mRv being a failure is if we're loading the main worker
+ // script and GetOrCreateGlobalScope() fails. In that case we would have
+ // returned false from WorkerRun, so assert that.
+ MOZ_ASSERT_IF(!result && !mScriptLoader.mRv.Failed(),
+ !aRunResult);
+ ShutdownScriptLoader(aCx, aWorkerPrivate, result, mutedError);
+ }
+}
+
+nsresult
+ScriptExecutorRunnable::Cancel()
+{
+ if (mLastIndex == mScriptLoader.mLoadInfos.Length() - 1) {
+ ShutdownScriptLoader(mWorkerPrivate->GetJSContext(), mWorkerPrivate,
+ false, false);
+ }
+ return MainThreadWorkerSyncRunnable::Cancel();
+}
+
+void
+ScriptExecutorRunnable::ShutdownScriptLoader(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate,
+ bool aResult,
+ bool aMutedError)
+{
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(mLastIndex == mScriptLoader.mLoadInfos.Length() - 1);
+
+ if (mIsWorkerScript && aWorkerPrivate->IsServiceWorker()) {
+ aWorkerPrivate->SetLoadingWorkerScript(false);
+ }
+
+ if (!aResult) {
+ // At this point there are two possibilities:
+ //
+ // 1) mScriptLoader.mRv.Failed(). In that case we just want to leave it
+ // as-is, except if it has a JS exception and we need to mute JS
+ // exceptions. In that case, we log the exception without firing any
+ // events and then replace it on the ErrorResult with a NetworkError,
+ // per spec.
+ //
+ // 2) mScriptLoader.mRv succeeded. As far as I can tell, this can only
+ // happen when loading the main worker script and
+ // GetOrCreateGlobalScope() fails or if ScriptExecutorRunnable::Cancel
+ // got called. Does it matter what we throw in this case? I'm not
+ // sure...
+ if (mScriptLoader.mRv.Failed()) {
+ if (aMutedError && mScriptLoader.mRv.IsJSException()) {
+ LogExceptionToConsole(aCx, aWorkerPrivate);
+ mScriptLoader.mRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
+ }
+ } else {
+ mScriptLoader.mRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+ }
+
+ aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, aResult);
+}
+
+void
+ScriptExecutorRunnable::LogExceptionToConsole(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate)
+{
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(mScriptLoader.mRv.IsJSException());
+
+ JS::Rooted<JS::Value> exn(aCx);
+ if (!ToJSValue(aCx, mScriptLoader.mRv, &exn)) {
+ return;
+ }
+
+ // Now the exception state should all be in exn.
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+ MOZ_ASSERT(!mScriptLoader.mRv.Failed());
+
+ js::ErrorReport report(aCx);
+ if (!report.init(aCx, exn, js::ErrorReport::WithSideEffects)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+
+ RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
+ xpcReport->Init(report.report(), report.toStringResult().c_str(),
+ aWorkerPrivate->IsChromeWorker(), aWorkerPrivate->WindowID());
+
+ RefPtr<AsyncErrorReporter> r = new AsyncErrorReporter(xpcReport);
+ NS_DispatchToMainThread(r);
+}
+
+void
+LoadAllScripts(WorkerPrivate* aWorkerPrivate,
+ nsTArray<ScriptLoadInfo>& aLoadInfos, bool aIsMainScript,
+ WorkerScriptType aWorkerScriptType, ErrorResult& aRv)
+{
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ NS_ASSERTION(!aLoadInfos.IsEmpty(), "Bad arguments!");
+
+ AutoSyncLoopHolder syncLoop(aWorkerPrivate);
+
+ RefPtr<ScriptLoaderRunnable> loader =
+ new ScriptLoaderRunnable(aWorkerPrivate, syncLoop.EventTarget(),
+ aLoadInfos, aIsMainScript, aWorkerScriptType,
+ aRv);
+
+ NS_ASSERTION(aLoadInfos.IsEmpty(), "Should have swapped!");
+
+ ScriptLoaderHolder workerHolder(loader);
+
+ if (NS_WARN_IF(!workerHolder.HoldWorker(aWorkerPrivate, Terminating))) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (NS_FAILED(NS_DispatchToMainThread(loader))) {
+ NS_ERROR("Failed to dispatch!");
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ syncLoop.Run();
+}
+
+} /* anonymous namespace */
+
+BEGIN_WORKERS_NAMESPACE
+
+namespace scriptloader {
+
+nsresult
+ChannelFromScriptURLMainThread(nsIPrincipal* aPrincipal,
+ nsIURI* aBaseURI,
+ nsIDocument* aParentDoc,
+ nsILoadGroup* aLoadGroup,
+ const nsAString& aScriptURL,
+ nsContentPolicyType aContentPolicyType,
+ bool aDefaultURIEncoding,
+ nsIChannel** aChannel)
+{
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIIOService> ios(do_GetIOService());
+
+ nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+ NS_ASSERTION(secMan, "This should never be null!");
+
+ return ChannelFromScriptURL(aPrincipal, aBaseURI, aParentDoc, aLoadGroup,
+ ios, secMan, aScriptURL, true, WorkerScript,
+ aContentPolicyType, nsIRequest::LOAD_NORMAL,
+ aDefaultURIEncoding, aChannel);
+}
+
+nsresult
+ChannelFromScriptURLWorkerThread(JSContext* aCx,
+ WorkerPrivate* aParent,
+ const nsAString& aScriptURL,
+ nsIChannel** aChannel)
+{
+ aParent->AssertIsOnWorkerThread();
+
+ RefPtr<ChannelGetterRunnable> getter =
+ new ChannelGetterRunnable(aParent, aScriptURL, aChannel);
+
+ ErrorResult rv;
+ getter->Dispatch(rv);
+ if (rv.Failed()) {
+ NS_ERROR("Failed to dispatch!");
+ return rv.StealNSResult();
+ }
+
+ return getter->GetResult();
+}
+
+void ReportLoadError(ErrorResult& aRv, nsresult aLoadResult,
+ const nsAString& aScriptURL)
+{
+ MOZ_ASSERT(!aRv.Failed());
+
+ switch (aLoadResult) {
+ case NS_ERROR_FILE_NOT_FOUND:
+ case NS_ERROR_NOT_AVAILABLE:
+ aLoadResult = NS_ERROR_DOM_NETWORK_ERR;
+ break;
+
+ case NS_ERROR_MALFORMED_URI:
+ aLoadResult = NS_ERROR_DOM_SYNTAX_ERR;
+ break;
+
+ case NS_BINDING_ABORTED:
+ // Note: we used to pretend like we didn't set an exception for
+ // NS_BINDING_ABORTED, but then ShutdownScriptLoader did it anyway. The
+ // other callsite, in WorkerPrivate::Constructor, never passed in
+ // NS_BINDING_ABORTED. So just throw it directly here. Consumers will
+ // deal as needed. But note that we do NOT want to ThrowDOMException()
+ // for this case, because that will make it impossible for consumers to
+ // realize that our error was NS_BINDING_ABORTED.
+ aRv.Throw(aLoadResult);
+ return;
+
+ case NS_ERROR_DOM_SECURITY_ERR:
+ case NS_ERROR_DOM_SYNTAX_ERR:
+ break;
+
+ case NS_ERROR_DOM_BAD_URI:
+ // This is actually a security error.
+ aLoadResult = NS_ERROR_DOM_SECURITY_ERR;
+ break;
+
+ default:
+ // For lack of anything better, go ahead and throw a NetworkError here.
+ // We don't want to throw a JS exception, because for toplevel script
+ // loads that would get squelched.
+ aRv.ThrowDOMException(NS_ERROR_DOM_NETWORK_ERR,
+ nsPrintfCString("Failed to load worker script at %s (nsresult = 0x%x)",
+ NS_ConvertUTF16toUTF8(aScriptURL).get(),
+ aLoadResult));
+ return;
+ }
+
+ aRv.ThrowDOMException(aLoadResult,
+ NS_LITERAL_CSTRING("Failed to load worker script at \"") +
+ NS_ConvertUTF16toUTF8(aScriptURL) +
+ NS_LITERAL_CSTRING("\""));
+}
+
+void
+LoadMainScript(WorkerPrivate* aWorkerPrivate,
+ const nsAString& aScriptURL,
+ WorkerScriptType aWorkerScriptType,
+ ErrorResult& aRv)
+{
+ nsTArray<ScriptLoadInfo> loadInfos;
+
+ ScriptLoadInfo* info = loadInfos.AppendElement();
+ info->mURL = aScriptURL;
+
+ LoadAllScripts(aWorkerPrivate, loadInfos, true, aWorkerScriptType, aRv);
+}
+
+void
+Load(WorkerPrivate* aWorkerPrivate,
+ const nsTArray<nsString>& aScriptURLs, WorkerScriptType aWorkerScriptType,
+ ErrorResult& aRv)
+{
+ const uint32_t urlCount = aScriptURLs.Length();
+
+ if (!urlCount) {
+ return;
+ }
+
+ if (urlCount > MAX_CONCURRENT_SCRIPTS) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ nsTArray<ScriptLoadInfo> loadInfos;
+ loadInfos.SetLength(urlCount);
+
+ for (uint32_t index = 0; index < urlCount; index++) {
+ loadInfos[index].mURL = aScriptURLs[index];
+ }
+
+ LoadAllScripts(aWorkerPrivate, loadInfos, false, aWorkerScriptType, aRv);
+}
+
+} // namespace scriptloader
+
+END_WORKERS_NAMESPACE
diff --git a/dom/workers/ScriptLoader.h b/dom/workers/ScriptLoader.h
new file mode 100644
index 000000000..c92c369ad
--- /dev/null
+++ b/dom/workers/ScriptLoader.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_scriptloader_h__
+#define mozilla_dom_workers_scriptloader_h__
+
+#include "Workers.h"
+#include "nsIContentPolicyBase.h"
+
+class nsIPrincipal;
+class nsIURI;
+class nsIDocument;
+class nsILoadGroup;
+class nsString;
+class nsIChannel;
+
+namespace mozilla {
+
+class ErrorResult;
+
+} // namespace mozilla
+
+BEGIN_WORKERS_NAMESPACE
+
+enum WorkerScriptType {
+ WorkerScript,
+ DebuggerScript
+};
+
+namespace scriptloader {
+
+nsresult
+ChannelFromScriptURLMainThread(nsIPrincipal* aPrincipal,
+ nsIURI* aBaseURI,
+ nsIDocument* aParentDoc,
+ nsILoadGroup* aLoadGroup,
+ const nsAString& aScriptURL,
+ nsContentPolicyType aContentPolicyType,
+ bool aDefaultURIEncoding,
+ nsIChannel** aChannel);
+
+nsresult
+ChannelFromScriptURLWorkerThread(JSContext* aCx,
+ WorkerPrivate* aParent,
+ const nsAString& aScriptURL,
+ nsIChannel** aChannel);
+
+void ReportLoadError(ErrorResult& aRv, nsresult aLoadResult,
+ const nsAString& aScriptURL);
+
+void LoadMainScript(WorkerPrivate* aWorkerPrivate,
+ const nsAString& aScriptURL,
+ WorkerScriptType aWorkerScriptType,
+ ErrorResult& aRv);
+
+void Load(WorkerPrivate* aWorkerPrivate,
+ const nsTArray<nsString>& aScriptURLs,
+ WorkerScriptType aWorkerScriptType,
+ mozilla::ErrorResult& aRv);
+
+} // namespace scriptloader
+
+END_WORKERS_NAMESPACE
+
+#endif /* mozilla_dom_workers_scriptloader_h__ */
diff --git a/dom/workers/ServiceWorker.cpp b/dom/workers/ServiceWorker.cpp
new file mode 100644
index 000000000..6a6995d59
--- /dev/null
+++ b/dom/workers/ServiceWorker.cpp
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorker.h"
+
+#include "nsIDocument.h"
+#include "nsPIDOMWindow.h"
+#include "ServiceWorkerClient.h"
+#include "ServiceWorkerManager.h"
+#include "ServiceWorkerPrivate.h"
+#include "WorkerPrivate.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
+
+#ifdef XP_WIN
+#undef PostMessage
+#endif
+
+using mozilla::ErrorResult;
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+bool
+ServiceWorkerVisible(JSContext* aCx, JSObject* aObj)
+{
+ if (NS_IsMainThread()) {
+ return Preferences::GetBool("dom.serviceWorkers.enabled", false);
+ }
+
+ return IS_INSTANCE_OF(ServiceWorkerGlobalScope, aObj);
+}
+
+ServiceWorker::ServiceWorker(nsPIDOMWindowInner* aWindow,
+ ServiceWorkerInfo* aInfo)
+ : DOMEventTargetHelper(aWindow),
+ mInfo(aInfo)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aInfo);
+
+ // This will update our state too.
+ mInfo->AppendWorker(this);
+}
+
+ServiceWorker::~ServiceWorker()
+{
+ AssertIsOnMainThread();
+ mInfo->RemoveWorker(this);
+}
+
+NS_IMPL_ADDREF_INHERITED(ServiceWorker, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(ServiceWorker, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServiceWorker)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+JSObject*
+ServiceWorker::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ AssertIsOnMainThread();
+
+ return ServiceWorkerBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+ServiceWorker::GetScriptURL(nsString& aURL) const
+{
+ CopyUTF8toUTF16(mInfo->ScriptSpec(), aURL);
+}
+
+void
+ServiceWorker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv)
+{
+ if (State() == ServiceWorkerState::Redundant) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetParentObject());
+ if (!window || !window->GetExtantDoc()) {
+ NS_WARNING("Trying to call post message from an invalid dom object.");
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ UniquePtr<ServiceWorkerClientInfo> clientInfo(new ServiceWorkerClientInfo(window->GetExtantDoc()));
+ ServiceWorkerPrivate* workerPrivate = mInfo->WorkerPrivate();
+ aRv = workerPrivate->SendMessageEvent(aCx, aMessage, aTransferable, Move(clientInfo));
+}
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/ServiceWorker.h b/dom/workers/ServiceWorker.h
new file mode 100644
index 000000000..e49f334e6
--- /dev/null
+++ b/dom/workers/ServiceWorker.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_serviceworker_h__
+#define mozilla_dom_workers_serviceworker_h__
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ServiceWorkerBinding.h" // For ServiceWorkerState.
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+namespace workers {
+
+class ServiceWorkerInfo;
+class ServiceWorkerManager;
+class SharedWorker;
+
+bool
+ServiceWorkerVisible(JSContext* aCx, JSObject* aObj);
+
+class ServiceWorker final : public DOMEventTargetHelper
+{
+ friend class ServiceWorkerInfo;
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ IMPL_EVENT_HANDLER(statechange)
+ IMPL_EVENT_HANDLER(error)
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ ServiceWorkerState
+ State() const
+ {
+ return mState;
+ }
+
+ void
+ SetState(ServiceWorkerState aState)
+ {
+ mState = aState;
+ }
+
+ void
+ GetScriptURL(nsString& aURL) const;
+
+ void
+ DispatchStateChange(ServiceWorkerState aState)
+ {
+ DOMEventTargetHelper::DispatchTrustedEvent(NS_LITERAL_STRING("statechange"));
+ }
+
+#ifdef XP_WIN
+#undef PostMessage
+#endif
+
+ void
+ PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv);
+
+private:
+ // This class can only be created from ServiceWorkerInfo::GetOrCreateInstance().
+ ServiceWorker(nsPIDOMWindowInner* aWindow, ServiceWorkerInfo* aInfo);
+
+ // This class is reference-counted and will be destroyed from Release().
+ ~ServiceWorker();
+
+ ServiceWorkerState mState;
+ const RefPtr<ServiceWorkerInfo> mInfo;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworker_h__
diff --git a/dom/workers/ServiceWorkerClient.cpp b/dom/workers/ServiceWorkerClient.cpp
new file mode 100644
index 000000000..660512a5f
--- /dev/null
+++ b/dom/workers/ServiceWorkerClient.cpp
@@ -0,0 +1,232 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "ServiceWorkerClient.h"
+#include "ServiceWorkerContainer.h"
+
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/ServiceWorkerMessageEvent.h"
+#include "mozilla/dom/ServiceWorkerMessageEventBinding.h"
+#include "nsGlobalWindow.h"
+#include "nsIBrowserDOMWindow.h"
+#include "nsIDocument.h"
+#include "ServiceWorker.h"
+#include "ServiceWorkerPrivate.h"
+#include "WorkerPrivate.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::workers;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ServiceWorkerClient, mOwner)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerClient)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ServiceWorkerClient)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerClient)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc)
+ : mWindowId(0)
+ , mFrameType(FrameType::None)
+{
+ MOZ_ASSERT(aDoc);
+ nsresult rv = aDoc->GetOrCreateId(mClientId);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to get the UUID of the document.");
+ }
+
+ RefPtr<nsGlobalWindow> innerWindow = nsGlobalWindow::Cast(aDoc->GetInnerWindow());
+ if (innerWindow) {
+ // XXXcatalinb: The inner window can be null if the document is navigating
+ // and was detached.
+ mWindowId = innerWindow->WindowID();
+ }
+
+ nsCOMPtr<nsIURI> originalURI = aDoc->GetOriginalURI();
+ if (originalURI) {
+ nsAutoCString spec;
+ originalURI->GetSpec(spec);
+ CopyUTF8toUTF16(spec, mUrl);
+ }
+ mVisibilityState = aDoc->VisibilityState();
+
+ ErrorResult result;
+ mFocused = aDoc->HasFocus(result);
+ if (result.Failed()) {
+ NS_WARNING("Failed to get focus information.");
+ }
+
+ RefPtr<nsGlobalWindow> outerWindow = nsGlobalWindow::Cast(aDoc->GetWindow());
+ if (!outerWindow) {
+ MOZ_ASSERT(mFrameType == FrameType::None);
+ } else if (!outerWindow->IsTopLevelWindow()) {
+ mFrameType = FrameType::Nested;
+ } else if (outerWindow->HadOriginalOpener()) {
+ mFrameType = FrameType::Auxiliary;
+ } else {
+ mFrameType = FrameType::Top_level;
+ }
+}
+
+JSObject*
+ServiceWorkerClient::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ClientBinding::Wrap(aCx, this, aGivenProto);
+}
+
+namespace {
+
+class ServiceWorkerClientPostMessageRunnable final
+ : public Runnable
+ , public StructuredCloneHolder
+{
+ uint64_t mWindowId;
+
+public:
+ explicit ServiceWorkerClientPostMessageRunnable(uint64_t aWindowId)
+ : StructuredCloneHolder(CloningSupported, TransferringSupported,
+ StructuredCloneScope::SameProcessDifferentThread)
+ , mWindowId(aWindowId)
+ {}
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+ nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId);
+ if (!window) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult result;
+ dom::Navigator* navigator = window->GetNavigator(result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ RefPtr<ServiceWorkerContainer> container = navigator->ServiceWorker();
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(window))) {
+ return NS_ERROR_FAILURE;
+ }
+ JSContext* cx = jsapi.cx();
+
+ return DispatchDOMEvent(cx, container);
+ }
+
+private:
+ NS_IMETHOD
+ DispatchDOMEvent(JSContext* aCx, ServiceWorkerContainer* aTargetContainer)
+ {
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(aTargetContainer->GetParentObject(),
+ "How come we don't have a window here?!");
+
+ JS::Rooted<JS::Value> messageData(aCx);
+ ErrorResult rv;
+ Read(aTargetContainer->GetParentObject(), aCx, &messageData, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ xpc::Throw(aCx, rv.StealNSResult());
+ return NS_ERROR_FAILURE;
+ }
+
+ RootedDictionary<ServiceWorkerMessageEventInit> init(aCx);
+
+ nsCOMPtr<nsIPrincipal> principal = aTargetContainer->GetParentObject()->PrincipalOrNull();
+ NS_WARNING_ASSERTION(principal, "Why is the principal null here?");
+
+ bool isNullPrincipal = false;
+ bool isSystemPrincipal = false;
+ if (principal) {
+ isNullPrincipal = principal->GetIsNullPrincipal();
+ MOZ_ASSERT(!isNullPrincipal);
+ isSystemPrincipal = principal->GetIsSystemPrincipal();
+ MOZ_ASSERT(!isSystemPrincipal);
+ }
+
+ init.mData = messageData;
+ nsAutoCString origin;
+ if (principal && !isNullPrincipal && !isSystemPrincipal) {
+ principal->GetOrigin(origin);
+ }
+ init.mOrigin = NS_ConvertUTF8toUTF16(origin);
+
+ RefPtr<ServiceWorker> serviceWorker = aTargetContainer->GetController();
+ if (serviceWorker) {
+ init.mSource.SetValue().SetAsServiceWorker() = serviceWorker;
+ }
+
+ if (!TakeTransferredPortsAsSequence(init.mPorts)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ RefPtr<ServiceWorkerMessageEvent> event =
+ ServiceWorkerMessageEvent::Constructor(aTargetContainer,
+ NS_LITERAL_STRING("message"),
+ init);
+
+ event->SetTrusted(true);
+ bool status = false;
+ aTargetContainer->DispatchEvent(static_cast<dom::Event*>(event.get()),
+ &status);
+
+ if (!status) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+void
+ServiceWorkerClient::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv)
+{
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ workerPrivate->AssertIsOnWorkerThread();
+
+ JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
+ if (aTransferable.WasPassed()) {
+ const Sequence<JS::Value>& realTransferable = aTransferable.Value();
+
+ JS::HandleValueArray elements =
+ JS::HandleValueArray::fromMarkedLocation(realTransferable.Length(),
+ realTransferable.Elements());
+
+ JSObject* array = JS_NewArrayObject(aCx, elements);
+ if (!array) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ transferable.setObject(*array);
+ }
+
+ RefPtr<ServiceWorkerClientPostMessageRunnable> runnable =
+ new ServiceWorkerClientPostMessageRunnable(mWindowId);
+
+ runnable->Write(aCx, aMessage, transferable, JS::CloneDataPolicy().denySharedArrayBuffer(),
+ aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ aRv = workerPrivate->DispatchToMainThread(runnable.forget());
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+}
+
diff --git a/dom/workers/ServiceWorkerClient.h b/dom/workers/ServiceWorkerClient.h
new file mode 100644
index 000000000..36a9cc168
--- /dev/null
+++ b/dom/workers/ServiceWorkerClient.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef mozilla_dom_workers_serviceworkerclient_h
+#define mozilla_dom_workers_serviceworkerclient_h
+
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ClientBinding.h"
+
+class nsIDocument;
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+class ServiceWorkerClient;
+class ServiceWorkerWindowClient;
+
+// Used as a container object for information needed to create
+// client objects.
+class ServiceWorkerClientInfo final
+{
+ friend class ServiceWorkerClient;
+ friend class ServiceWorkerWindowClient;
+
+public:
+ explicit ServiceWorkerClientInfo(nsIDocument* aDoc);
+
+ const nsString& ClientId() const
+ {
+ return mClientId;
+ }
+
+private:
+ nsString mClientId;
+ uint64_t mWindowId;
+ nsString mUrl;
+
+ // Window Clients
+ VisibilityState mVisibilityState;
+ bool mFocused;
+ FrameType mFrameType;
+};
+
+class ServiceWorkerClient : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ServiceWorkerClient)
+
+ ServiceWorkerClient(nsISupports* aOwner,
+ const ServiceWorkerClientInfo& aClientInfo)
+ : mOwner(aOwner)
+ , mId(aClientInfo.mClientId)
+ , mUrl(aClientInfo.mUrl)
+ , mWindowId(aClientInfo.mWindowId)
+ , mFrameType(aClientInfo.mFrameType)
+ {
+ MOZ_ASSERT(aOwner);
+ }
+
+ nsISupports*
+ GetParentObject() const
+ {
+ return mOwner;
+ }
+
+ void GetId(nsString& aRetval) const
+ {
+ aRetval = mId;
+ }
+
+ void
+ GetUrl(nsAString& aUrl) const
+ {
+ aUrl.Assign(mUrl);
+ }
+
+ mozilla::dom::FrameType
+ FrameType() const
+ {
+ return mFrameType;
+ }
+
+ void
+ PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv);
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+ virtual ~ServiceWorkerClient()
+ { }
+
+private:
+ nsCOMPtr<nsISupports> mOwner;
+ nsString mId;
+ nsString mUrl;
+
+protected:
+ uint64_t mWindowId;
+ mozilla::dom::FrameType mFrameType;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkerclient_h
diff --git a/dom/workers/ServiceWorkerClients.cpp b/dom/workers/ServiceWorkerClients.cpp
new file mode 100644
index 000000000..11f864443
--- /dev/null
+++ b/dom/workers/ServiceWorkerClients.cpp
@@ -0,0 +1,864 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerClients.h"
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseWorkerProxy.h"
+
+#include "ServiceWorkerClient.h"
+#include "ServiceWorkerManager.h"
+#include "ServiceWorkerPrivate.h"
+#include "ServiceWorkerWindowClient.h"
+
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "WorkerScope.h"
+
+#include "nsContentUtils.h"
+#include "nsIBrowserDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIDOMChromeWindow.h"
+#include "nsIDOMWindow.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsIWindowMediator.h"
+#include "nsIWindowWatcher.h"
+#include "nsNetUtil.h"
+#include "nsPIWindowWatcher.h"
+#include "nsWindowWatcher.h"
+#include "nsWeakReference.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+#include "AndroidBridge.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::workers;
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerClients)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ServiceWorkerClients)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ServiceWorkerClients, mWorkerScope)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerClients)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+ServiceWorkerClients::ServiceWorkerClients(ServiceWorkerGlobalScope* aWorkerScope)
+ : mWorkerScope(aWorkerScope)
+{
+ MOZ_ASSERT(mWorkerScope);
+}
+
+JSObject*
+ServiceWorkerClients::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ClientsBinding::Wrap(aCx, this, aGivenProto);
+}
+
+namespace {
+
+class GetRunnable final : public Runnable
+{
+ class ResolvePromiseWorkerRunnable final : public WorkerRunnable
+ {
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ UniquePtr<ServiceWorkerClientInfo> mValue;
+ nsresult mRv;
+
+ public:
+ ResolvePromiseWorkerRunnable(WorkerPrivate* aWorkerPrivate,
+ PromiseWorkerProxy* aPromiseProxy,
+ UniquePtr<ServiceWorkerClientInfo>&& aValue,
+ nsresult aRv)
+ : WorkerRunnable(aWorkerPrivate),
+ mPromiseProxy(aPromiseProxy),
+ mValue(Move(aValue)),
+ mRv(Move(aRv))
+ {
+ AssertIsOnMainThread();
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ Promise* promise = mPromiseProxy->WorkerPromise();
+ MOZ_ASSERT(promise);
+
+ if (NS_FAILED(mRv)) {
+ promise->MaybeReject(mRv);
+ } else if (mValue) {
+ RefPtr<ServiceWorkerWindowClient> windowClient =
+ new ServiceWorkerWindowClient(promise->GetParentObject(), *mValue);
+ promise->MaybeResolve(windowClient.get());
+ } else {
+ promise->MaybeResolveWithUndefined();
+ }
+ mPromiseProxy->CleanUp();
+ return true;
+ }
+ };
+
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ nsString mClientId;
+public:
+ GetRunnable(PromiseWorkerProxy* aPromiseProxy,
+ const nsAString& aClientId)
+ : mPromiseProxy(aPromiseProxy),
+ mClientId(aClientId)
+ {
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return NS_OK;
+ }
+
+ WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ UniquePtr<ServiceWorkerClientInfo> result;
+ ErrorResult rv;
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ rv = NS_ERROR_FAILURE;
+ } else {
+ result = swm->GetClient(workerPrivate->GetPrincipal(), mClientId, rv);
+ }
+
+ RefPtr<ResolvePromiseWorkerRunnable> r =
+ new ResolvePromiseWorkerRunnable(mPromiseProxy->GetWorkerPrivate(),
+ mPromiseProxy, Move(result),
+ rv.StealNSResult());
+ rv.SuppressException();
+
+ r->Dispatch();
+ return NS_OK;
+ }
+};
+
+class MatchAllRunnable final : public Runnable
+{
+ class ResolvePromiseWorkerRunnable final : public WorkerRunnable
+ {
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ nsTArray<ServiceWorkerClientInfo> mValue;
+
+ public:
+ ResolvePromiseWorkerRunnable(WorkerPrivate* aWorkerPrivate,
+ PromiseWorkerProxy* aPromiseProxy,
+ nsTArray<ServiceWorkerClientInfo>& aValue)
+ : WorkerRunnable(aWorkerPrivate),
+ mPromiseProxy(aPromiseProxy)
+ {
+ AssertIsOnMainThread();
+ mValue.SwapElements(aValue);
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ Promise* promise = mPromiseProxy->WorkerPromise();
+ MOZ_ASSERT(promise);
+
+ nsTArray<RefPtr<ServiceWorkerClient>> ret;
+ for (size_t i = 0; i < mValue.Length(); i++) {
+ ret.AppendElement(RefPtr<ServiceWorkerClient>(
+ new ServiceWorkerWindowClient(promise->GetParentObject(),
+ mValue.ElementAt(i))));
+ }
+
+ promise->MaybeResolve(ret);
+ mPromiseProxy->CleanUp();
+ return true;
+ }
+ };
+
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ nsCString mScope;
+ bool mIncludeUncontrolled;
+public:
+ MatchAllRunnable(PromiseWorkerProxy* aPromiseProxy,
+ const nsCString& aScope,
+ bool aIncludeUncontrolled)
+ : mPromiseProxy(aPromiseProxy),
+ mScope(aScope),
+ mIncludeUncontrolled(aIncludeUncontrolled)
+ {
+ MOZ_ASSERT(mPromiseProxy);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return NS_OK;
+ }
+
+ nsTArray<ServiceWorkerClientInfo> result;
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->GetAllClients(mPromiseProxy->GetWorkerPrivate()->GetPrincipal(),
+ mScope, mIncludeUncontrolled, result);
+ }
+ RefPtr<ResolvePromiseWorkerRunnable> r =
+ new ResolvePromiseWorkerRunnable(mPromiseProxy->GetWorkerPrivate(),
+ mPromiseProxy, result);
+
+ r->Dispatch();
+ return NS_OK;
+ }
+};
+
+class ResolveClaimRunnable final : public WorkerRunnable
+{
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ nsresult mResult;
+
+public:
+ ResolveClaimRunnable(WorkerPrivate* aWorkerPrivate,
+ PromiseWorkerProxy* aPromiseProxy,
+ nsresult aResult)
+ : WorkerRunnable(aWorkerPrivate)
+ , mPromiseProxy(aPromiseProxy)
+ , mResult(aResult)
+ {
+ AssertIsOnMainThread();
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<Promise> promise = mPromiseProxy->WorkerPromise();
+ MOZ_ASSERT(promise);
+
+ if (NS_SUCCEEDED(mResult)) {
+ promise->MaybeResolveWithUndefined();
+ } else {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+
+ mPromiseProxy->CleanUp();
+ return true;
+ }
+};
+
+class ClaimRunnable final : public Runnable
+{
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ nsCString mScope;
+ uint64_t mServiceWorkerID;
+
+public:
+ ClaimRunnable(PromiseWorkerProxy* aPromiseProxy, const nsCString& aScope)
+ : mPromiseProxy(aPromiseProxy)
+ , mScope(aScope)
+ // Safe to call GetWorkerPrivate() since we are being called on the worker
+ // thread via script (so no clean up has occured yet).
+ , mServiceWorkerID(aPromiseProxy->GetWorkerPrivate()->ServiceWorkerID())
+ {
+ MOZ_ASSERT(aPromiseProxy);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return NS_OK;
+ }
+
+ WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ nsresult rv = NS_OK;
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ // browser shutdown
+ rv = NS_ERROR_FAILURE;
+ } else {
+ rv = swm->ClaimClients(workerPrivate->GetPrincipal(), mScope,
+ mServiceWorkerID);
+ }
+
+ RefPtr<ResolveClaimRunnable> r =
+ new ResolveClaimRunnable(workerPrivate, mPromiseProxy, rv);
+
+ r->Dispatch();
+ return NS_OK;
+ }
+};
+
+class ResolveOpenWindowRunnable final : public WorkerRunnable
+{
+public:
+ ResolveOpenWindowRunnable(PromiseWorkerProxy* aPromiseProxy,
+ UniquePtr<ServiceWorkerClientInfo>&& aClientInfo,
+ const nsresult aStatus)
+ : WorkerRunnable(aPromiseProxy->GetWorkerPrivate())
+ , mPromiseProxy(aPromiseProxy)
+ , mClientInfo(Move(aClientInfo))
+ , mStatus(aStatus)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPromiseProxy);
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ Promise* promise = mPromiseProxy->WorkerPromise();
+ if (NS_WARN_IF(NS_FAILED(mStatus))) {
+ promise->MaybeReject(mStatus);
+ } else if (mClientInfo) {
+ RefPtr<ServiceWorkerWindowClient> client =
+ new ServiceWorkerWindowClient(promise->GetParentObject(),
+ *mClientInfo);
+ promise->MaybeResolve(client);
+ } else {
+ promise->MaybeResolve(JS::NullHandleValue);
+ }
+
+ mPromiseProxy->CleanUp();
+ return true;
+ }
+
+private:
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ UniquePtr<ServiceWorkerClientInfo> mClientInfo;
+ const nsresult mStatus;
+};
+
+class WebProgressListener final : public nsIWebProgressListener,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(WebProgressListener, nsIWebProgressListener)
+
+ WebProgressListener(PromiseWorkerProxy* aPromiseProxy,
+ ServiceWorkerPrivate* aServiceWorkerPrivate,
+ nsPIDOMWindowOuter* aWindow,
+ nsIURI* aBaseURI)
+ : mPromiseProxy(aPromiseProxy)
+ , mServiceWorkerPrivate(aServiceWorkerPrivate)
+ , mWindow(aWindow)
+ , mBaseURI(aBaseURI)
+ {
+ MOZ_ASSERT(aPromiseProxy);
+ MOZ_ASSERT(aServiceWorkerPrivate);
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsOuterWindow());
+ MOZ_ASSERT(aBaseURI);
+ AssertIsOnMainThread();
+
+ mServiceWorkerPrivate->StoreISupports(static_cast<nsIWebProgressListener*>(this));
+ }
+
+ NS_IMETHOD
+ OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aStateFlags, nsresult aStatus) override
+ {
+ if (!(aStateFlags & STATE_IS_DOCUMENT) ||
+ !(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) {
+ return NS_OK;
+ }
+
+ // Our caller keeps a strong reference, so it is safe to remove the listener
+ // from ServiceWorkerPrivate.
+ mServiceWorkerPrivate->RemoveISupports(static_cast<nsIWebProgressListener*>(this));
+ aWebProgress->RemoveProgressListener(this);
+
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
+ UniquePtr<ServiceWorkerClientInfo> clientInfo;
+ if (doc) {
+ // Check same origin.
+ nsCOMPtr<nsIScriptSecurityManager> securityManager =
+ nsContentUtils::GetSecurityManager();
+ nsresult rv = securityManager->CheckSameOriginURI(doc->GetOriginalURI(),
+ mBaseURI, false);
+ if (NS_SUCCEEDED(rv)) {
+ clientInfo.reset(new ServiceWorkerClientInfo(doc));
+ }
+ }
+
+ RefPtr<ResolveOpenWindowRunnable> r =
+ new ResolveOpenWindowRunnable(mPromiseProxy,
+ Move(clientInfo),
+ NS_OK);
+ r->Dispatch();
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnProgressChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) override
+ {
+ MOZ_ASSERT(false, "Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI* aLocation,
+ uint32_t aFlags) override
+ {
+ MOZ_ASSERT(false, "Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus, const char16_t* aMessage) override
+ {
+ MOZ_ASSERT(false, "Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aState) override
+ {
+ MOZ_ASSERT(false, "Unexpected notification.");
+ return NS_OK;
+ }
+
+private:
+ ~WebProgressListener()
+ { }
+
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
+ nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+ nsCOMPtr<nsIURI> mBaseURI;
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebProgressListener)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebProgressListener)
+NS_IMPL_CYCLE_COLLECTION(WebProgressListener, mPromiseProxy,
+ mServiceWorkerPrivate, mWindow)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebProgressListener)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+class OpenWindowRunnable final : public Runnable
+{
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ nsString mUrl;
+ nsString mScope;
+
+public:
+ OpenWindowRunnable(PromiseWorkerProxy* aPromiseProxy,
+ const nsAString& aUrl,
+ const nsAString& aScope)
+ : mPromiseProxy(aPromiseProxy)
+ , mUrl(aUrl)
+ , mScope(aScope)
+ {
+ MOZ_ASSERT(aPromiseProxy);
+ MOZ_ASSERT(aPromiseProxy->GetWorkerPrivate());
+ aPromiseProxy->GetWorkerPrivate()->AssertIsOnWorkerThread();
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return NS_OK;
+ }
+
+#ifdef MOZ_WIDGET_ANDROID
+ // This fires an intent that will start launching Fennec and foreground it,
+ // if necessary.
+ java::GeckoAppShell::OpenWindowForNotification();
+#endif
+
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ nsresult rv = OpenWindow(getter_AddRefs(window));
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(window);
+
+ rv = nsContentUtils::DispatchFocusChromeEvent(window);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ WorkerPrivate::LocationInfo& info = workerPrivate->GetLocationInfo();
+ nsCOMPtr<nsIURI> baseURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(baseURI), info.mHref);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
+
+ if (!webProgress) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ // browser shutdown
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = workerPrivate->GetPrincipal();
+ MOZ_ASSERT(principal);
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ swm->GetRegistration(principal, NS_ConvertUTF16toUTF8(mScope));
+ if (NS_WARN_IF(!registration)) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<ServiceWorkerInfo> serviceWorkerInfo =
+ registration->GetServiceWorkerInfoById(workerPrivate->ServiceWorkerID());
+ if (NS_WARN_IF(!serviceWorkerInfo)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIWebProgressListener> listener =
+ new WebProgressListener(mPromiseProxy, serviceWorkerInfo->WorkerPrivate(),
+ window, baseURI);
+
+ rv = webProgress->AddProgressListener(listener,
+ nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_OK;
+ }
+#ifdef MOZ_WIDGET_ANDROID
+ else if (rv == NS_ERROR_NOT_AVAILABLE) {
+ // We couldn't get a browser window, so Fennec must not be running.
+ // Send an Intent to launch Fennec and wait for "BrowserChrome:Ready"
+ // to try opening a window again.
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ NS_ENSURE_STATE(os);
+
+ WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ // browser shutdown
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = workerPrivate->GetPrincipal();
+ MOZ_ASSERT(principal);
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ swm->GetRegistration(principal, NS_ConvertUTF16toUTF8(mScope));
+ if (NS_WARN_IF(!registration)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<ServiceWorkerInfo> serviceWorkerInfo =
+ registration->GetServiceWorkerInfoById(workerPrivate->ServiceWorkerID());
+ if (NS_WARN_IF(!serviceWorkerInfo)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ os->AddObserver(static_cast<nsIObserver*>(serviceWorkerInfo->WorkerPrivate()),
+ "BrowserChrome:Ready", true);
+ serviceWorkerInfo->WorkerPrivate()->AddPendingWindow(this);
+ return NS_OK;
+ }
+#endif
+
+ RefPtr<ResolveOpenWindowRunnable> resolveRunnable =
+ new ResolveOpenWindowRunnable(mPromiseProxy, nullptr, rv);
+
+ Unused << NS_WARN_IF(!resolveRunnable->Dispatch());
+
+ return NS_OK;
+ }
+
+private:
+ nsresult
+ OpenWindow(nsPIDOMWindowOuter** aWindow)
+ {
+ MOZ_DIAGNOSTIC_ASSERT(aWindow);
+ WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
+
+ // [[1. Let url be the result of parsing url with entry settings object's API
+ // base URL.]]
+ nsCOMPtr<nsIURI> uri;
+ WorkerPrivate::LocationInfo& info = workerPrivate->GetLocationInfo();
+
+ nsCOMPtr<nsIURI> baseURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(baseURI), info.mHref);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_TYPE_ERR;
+ }
+
+ rv = NS_NewURI(getter_AddRefs(uri), mUrl, nullptr, baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_TYPE_ERR;
+ }
+
+ // [[6.1 Open Window]]
+ nsCOMPtr<nsIWindowMediator> wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID,
+ &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (XRE_IsContentProcess()) {
+ // ContentProcess
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ nsCOMPtr<nsPIWindowWatcher> pwwatch(do_QueryInterface(wwatch));
+ NS_ENSURE_STATE(pwwatch);
+
+ nsCString spec;
+ rv = uri->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ rv = pwwatch->OpenWindow2(nullptr,
+ spec.get(),
+ nullptr,
+ nullptr,
+ false, false, true, nullptr,
+ // Not a spammy popup; we got permission, we swear!
+ /* aIsPopupSpam = */ false,
+ // Don't force noopener. We're not passing in an
+ // opener anyway, and we _do_ want the returned
+ // window.
+ /* aForceNoOpener = */ false,
+ /* aLoadInfp = */ nullptr,
+ getter_AddRefs(newWindow));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> pwindow = nsPIDOMWindowOuter::From(newWindow);
+ pwindow.forget(aWindow);
+ MOZ_DIAGNOSTIC_ASSERT(*aWindow);
+ return NS_OK;
+ }
+
+ // Find the most recent browser window and open a new tab in it.
+ nsCOMPtr<nsPIDOMWindowOuter> browserWindow =
+ nsContentUtils::GetMostRecentNonPBWindow();
+ if (!browserWindow) {
+ // It is possible to be running without a browser window on Mac OS, so
+ // we need to open a new chrome window.
+ // TODO(catalinb): open new chrome window. Bug 1218080
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIDOMChromeWindow> chromeWin = do_QueryInterface(browserWindow);
+ if (NS_WARN_IF(!chromeWin)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIBrowserDOMWindow> bwin;
+ chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin));
+
+ if (NS_WARN_IF(!bwin)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ rv = bwin->OpenURI(uri, nullptr,
+ nsIBrowserDOMWindow::OPEN_DEFAULTWINDOW,
+ nsIBrowserDOMWindow::OPEN_NEW,
+ getter_AddRefs(win));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ NS_ENSURE_STATE(win);
+
+ nsCOMPtr<nsPIDOMWindowOuter> pWin = nsPIDOMWindowOuter::From(win);
+ pWin.forget(aWindow);
+ MOZ_DIAGNOSTIC_ASSERT(*aWindow);
+
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+already_AddRefed<Promise>
+ServiceWorkerClients::Get(const nsAString& aClientId, ErrorResult& aRv)
+{
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ workerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RefPtr<PromiseWorkerProxy> promiseProxy =
+ PromiseWorkerProxy::Create(workerPrivate, promise);
+ if (!promiseProxy) {
+ promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ return promise.forget();
+ }
+
+ RefPtr<GetRunnable> r =
+ new GetRunnable(promiseProxy, aClientId);
+ MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(r.forget()));
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+ServiceWorkerClients::MatchAll(const ClientQueryOptions& aOptions,
+ ErrorResult& aRv)
+{
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ workerPrivate->AssertIsOnWorkerThread();
+
+ nsString scope;
+ mWorkerScope->GetScope(scope);
+
+ if (aOptions.mType != ClientType::Window) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RefPtr<PromiseWorkerProxy> promiseProxy =
+ PromiseWorkerProxy::Create(workerPrivate, promise);
+ if (!promiseProxy) {
+ promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ return promise.forget();
+ }
+
+ RefPtr<MatchAllRunnable> r =
+ new MatchAllRunnable(promiseProxy,
+ NS_ConvertUTF16toUTF8(scope),
+ aOptions.mIncludeUncontrolled);
+ MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(r.forget()));
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+ServiceWorkerClients::OpenWindow(const nsAString& aUrl,
+ ErrorResult& aRv)
+{
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ RefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (aUrl.EqualsLiteral("about:blank")) {
+ promise->MaybeReject(NS_ERROR_TYPE_ERR);
+ return promise.forget();
+ }
+
+ // [[4. If this algorithm is not allowed to show a popup ..]]
+ // In Gecko the service worker is allowed to show a popup only if the user
+ // just clicked on a notification.
+ if (!workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return promise.forget();
+ }
+
+ RefPtr<PromiseWorkerProxy> promiseProxy =
+ PromiseWorkerProxy::Create(workerPrivate, promise);
+
+ if (!promiseProxy) {
+ return nullptr;
+ }
+
+ nsString scope;
+ mWorkerScope->GetScope(scope);
+
+ RefPtr<OpenWindowRunnable> r = new OpenWindowRunnable(promiseProxy,
+ aUrl, scope);
+ MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(r.forget()));
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+ServiceWorkerClients::Claim(ErrorResult& aRv)
+{
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ RefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RefPtr<PromiseWorkerProxy> promiseProxy =
+ PromiseWorkerProxy::Create(workerPrivate, promise);
+ if (!promiseProxy) {
+ promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ return promise.forget();
+ }
+
+ nsString scope;
+ mWorkerScope->GetScope(scope);
+
+ RefPtr<ClaimRunnable> runnable =
+ new ClaimRunnable(promiseProxy, NS_ConvertUTF16toUTF8(scope));
+
+ MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(runnable.forget()));
+ return promise.forget();
+}
diff --git a/dom/workers/ServiceWorkerClients.h b/dom/workers/ServiceWorkerClients.h
new file mode 100644
index 000000000..3c507516f
--- /dev/null
+++ b/dom/workers/ServiceWorkerClients.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef mozilla_dom_workers_serviceworkerclients_h
+#define mozilla_dom_workers_serviceworkerclients_h
+
+#include "nsWrapperCache.h"
+
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ClientsBinding.h"
+#include "mozilla/ErrorResult.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+class ServiceWorkerClients final : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ServiceWorkerClients)
+
+ explicit ServiceWorkerClients(ServiceWorkerGlobalScope* aWorkerScope);
+
+ already_AddRefed<Promise>
+ Get(const nsAString& aClientId, ErrorResult& aRv);
+
+ already_AddRefed<Promise>
+ MatchAll(const ClientQueryOptions& aOptions, ErrorResult& aRv);
+
+ already_AddRefed<Promise>
+ OpenWindow(const nsAString& aUrl, ErrorResult& aRv);
+
+ already_AddRefed<Promise>
+ Claim(ErrorResult& aRv);
+
+ JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ ServiceWorkerGlobalScope*
+ GetParentObject() const
+ {
+ return mWorkerScope;
+ }
+
+private:
+ ~ServiceWorkerClients()
+ {
+ }
+
+ RefPtr<ServiceWorkerGlobalScope> mWorkerScope;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkerclients_h
diff --git a/dom/workers/ServiceWorkerCommon.h b/dom/workers/ServiceWorkerCommon.h
new file mode 100644
index 000000000..1c272c125
--- /dev/null
+++ b/dom/workers/ServiceWorkerCommon.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ServiceWorkerCommon_h
+#define mozilla_dom_ServiceWorkerCommon_h
+
+namespace mozilla {
+namespace dom {
+
+// Use multiples of 2 since they can be bitwise ORed when calling
+// InvalidateServiceWorkerRegistrationWorker.
+enum class WhichServiceWorker {
+ INSTALLING_WORKER = 1,
+ WAITING_WORKER = 2,
+ ACTIVE_WORKER = 4,
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(WhichServiceWorker)
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ServiceWorkerCommon_h
diff --git a/dom/workers/ServiceWorkerContainer.cpp b/dom/workers/ServiceWorkerContainer.cpp
new file mode 100644
index 000000000..274d72d50
--- /dev/null
+++ b/dom/workers/ServiceWorkerContainer.cpp
@@ -0,0 +1,341 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerContainer.h"
+
+#include "nsContentUtils.h"
+#include "nsIDocument.h"
+#include "nsIServiceWorkerManager.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsServiceManagerUtils.h"
+
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ServiceWorkerContainerBinding.h"
+#include "mozilla/dom/workers/bindings/ServiceWorker.h"
+
+#include "ServiceWorker.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServiceWorkerContainer)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper,
+ mControllerWorker, mReadyPromise)
+
+/* static */ bool
+ServiceWorkerContainer::IsEnabled(JSContext* aCx, JSObject* aGlobal)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ JS::Rooted<JSObject*> global(aCx, aGlobal);
+ nsCOMPtr<nsPIDOMWindowInner> window = Navigator::GetWindowFromGlobal(global);
+ if (!window) {
+ return false;
+ }
+
+ nsIDocument* doc = window->GetExtantDoc();
+ if (!doc || nsContentUtils::IsInPrivateBrowsing(doc)) {
+ return false;
+ }
+
+ return Preferences::GetBool("dom.serviceWorkers.enabled", false);
+}
+
+ServiceWorkerContainer::ServiceWorkerContainer(nsPIDOMWindowInner* aWindow)
+ : DOMEventTargetHelper(aWindow)
+{
+}
+
+ServiceWorkerContainer::~ServiceWorkerContainer()
+{
+ RemoveReadyPromise();
+}
+
+void
+ServiceWorkerContainer::DisconnectFromOwner()
+{
+ mControllerWorker = nullptr;
+ RemoveReadyPromise();
+ DOMEventTargetHelper::DisconnectFromOwner();
+}
+
+void
+ServiceWorkerContainer::ControllerChanged(ErrorResult& aRv)
+{
+ mControllerWorker = nullptr;
+ aRv = DispatchTrustedEvent(NS_LITERAL_STRING("controllerchange"));
+}
+
+void
+ServiceWorkerContainer::RemoveReadyPromise()
+{
+ if (nsCOMPtr<nsPIDOMWindowInner> window = GetOwner()) {
+ nsCOMPtr<nsIServiceWorkerManager> swm =
+ mozilla::services::GetServiceWorkerManager();
+ if (!swm) {
+ // If the browser is shutting down, we don't need to remove the promise.
+ return;
+ }
+
+ swm->RemoveReadyPromise(window);
+ }
+}
+
+JSObject*
+ServiceWorkerContainer::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ServiceWorkerContainerBinding::Wrap(aCx, this, aGivenProto);
+}
+
+static nsresult
+CheckForSlashEscapedCharsInPath(nsIURI* aURI)
+{
+ MOZ_ASSERT(aURI);
+
+ // A URL that can't be downcast to a standard URL is an invalid URL and should
+ // be treated as such and fail with SecurityError.
+ nsCOMPtr<nsIURL> url(do_QueryInterface(aURI));
+ if (NS_WARN_IF(!url)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsAutoCString path;
+ nsresult rv = url->GetFilePath(path);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ ToLowerCase(path);
+ if (path.Find("%2f") != kNotFound ||
+ path.Find("%5c") != kNotFound) {
+ return NS_ERROR_DOM_TYPE_ERR;
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<Promise>
+ServiceWorkerContainer::Register(const nsAString& aScriptURL,
+ const RegistrationOptions& aOptions,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsISupports> promise;
+
+ nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
+ if (!swm) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> baseURI;
+
+ nsIDocument* doc = GetEntryDocument();
+ if (doc) {
+ baseURI = doc->GetBaseURI();
+ } else {
+ // XXXnsm. One of our devtools browser test calls register() from a content
+ // script where there is no valid entry document. Use the window to resolve
+ // the uri in that case.
+ nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
+ nsCOMPtr<nsPIDOMWindowOuter> outerWindow;
+ if (window && (outerWindow = window->GetOuterWindow()) &&
+ outerWindow->GetServiceWorkersTestingEnabled()) {
+ baseURI = window->GetDocBaseURI();
+ }
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIURI> scriptURI;
+ rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptURL, nullptr, baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.ThrowTypeError<MSG_INVALID_URL>(aScriptURL);
+ return nullptr;
+ }
+
+ aRv = CheckForSlashEscapedCharsInPath(scriptURI);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // In ServiceWorkerContainer.register() the scope argument is parsed against
+ // different base URLs depending on whether it was passed or not.
+ nsCOMPtr<nsIURI> scopeURI;
+
+ // Step 4. If none passed, parse against script's URL
+ if (!aOptions.mScope.WasPassed()) {
+ NS_NAMED_LITERAL_STRING(defaultScope, "./");
+ rv = NS_NewURI(getter_AddRefs(scopeURI), defaultScope,
+ nullptr, scriptURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ nsAutoCString spec;
+ scriptURI->GetSpec(spec);
+ NS_ConvertUTF8toUTF16 wSpec(spec);
+ aRv.ThrowTypeError<MSG_INVALID_SCOPE>(defaultScope, wSpec);
+ return nullptr;
+ }
+ } else {
+ // Step 5. Parse against entry settings object's base URL.
+ rv = NS_NewURI(getter_AddRefs(scopeURI), aOptions.mScope.Value(),
+ nullptr, baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ nsIURI* uri = baseURI ? baseURI : scriptURI;
+ nsAutoCString spec;
+ uri->GetSpec(spec);
+ NS_ConvertUTF8toUTF16 wSpec(spec);
+ aRv.ThrowTypeError<MSG_INVALID_SCOPE>(aOptions.mScope.Value(), wSpec);
+ return nullptr;
+ }
+
+ aRv = CheckForSlashEscapedCharsInPath(scopeURI);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ // The spec says that the "client" passed to Register() must be the global
+ // where the ServiceWorkerContainer was retrieved from.
+ aRv = swm->Register(GetOwner(), scopeURI, scriptURI, getter_AddRefs(promise));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RefPtr<Promise> ret = static_cast<Promise*>(promise.get());
+ MOZ_ASSERT(ret);
+ return ret.forget();
+}
+
+already_AddRefed<workers::ServiceWorker>
+ServiceWorkerContainer::GetController()
+{
+ if (!mControllerWorker) {
+ nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
+ if (!swm) {
+ return nullptr;
+ }
+
+ // TODO: What should we do here if the ServiceWorker script fails to load?
+ // In theory the DOM ServiceWorker object can exist without the worker
+ // thread running, but it seems our design does not expect that.
+ nsCOMPtr<nsISupports> serviceWorker;
+ nsresult rv = swm->GetDocumentController(GetOwner(),
+ getter_AddRefs(serviceWorker));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ mControllerWorker =
+ static_cast<workers::ServiceWorker*>(serviceWorker.get());
+ }
+
+ RefPtr<workers::ServiceWorker> ref = mControllerWorker;
+ return ref.forget();
+}
+
+already_AddRefed<Promise>
+ServiceWorkerContainer::GetRegistrations(ErrorResult& aRv)
+{
+ nsresult rv;
+ nsCOMPtr<nsIServiceWorkerManager> swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsISupports> promise;
+ aRv = swm->GetRegistrations(GetOwner(), getter_AddRefs(promise));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<Promise> ret = static_cast<Promise*>(promise.get());
+ MOZ_ASSERT(ret);
+ return ret.forget();
+}
+
+already_AddRefed<Promise>
+ServiceWorkerContainer::GetRegistration(const nsAString& aDocumentURL,
+ ErrorResult& aRv)
+{
+ nsresult rv;
+ nsCOMPtr<nsIServiceWorkerManager> swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsISupports> promise;
+ aRv = swm->GetRegistration(GetOwner(), aDocumentURL, getter_AddRefs(promise));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<Promise> ret = static_cast<Promise*>(promise.get());
+ MOZ_ASSERT(ret);
+ return ret.forget();
+}
+
+Promise*
+ServiceWorkerContainer::GetReady(ErrorResult& aRv)
+{
+ if (mReadyPromise) {
+ return mReadyPromise;
+ }
+
+ nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
+ if (!swm) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsISupports> promise;
+ aRv = swm->GetReadyPromise(GetOwner(), getter_AddRefs(promise));
+
+ mReadyPromise = static_cast<Promise*>(promise.get());
+ return mReadyPromise;
+}
+
+// Testing only.
+void
+ServiceWorkerContainer::GetScopeForUrl(const nsAString& aUrl,
+ nsString& aScope,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
+ if (!swm) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
+ if (NS_WARN_IF(!window)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ aRv = swm->GetScopeForUrl(doc->NodePrincipal(),
+ aUrl, aScope);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/ServiceWorkerContainer.h b/dom/workers/ServiceWorkerContainer.h
new file mode 100644
index 000000000..efd70a601
--- /dev/null
+++ b/dom/workers/ServiceWorkerContainer.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_serviceworkercontainer_h__
+#define mozilla_dom_serviceworkercontainer_h__
+
+#include "mozilla/DOMEventTargetHelper.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+struct RegistrationOptions;
+
+namespace workers {
+class ServiceWorker;
+} // namespace workers
+
+// Lightweight serviceWorker APIs collection.
+class ServiceWorkerContainer final : public DOMEventTargetHelper
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper)
+
+ IMPL_EVENT_HANDLER(controllerchange)
+ IMPL_EVENT_HANDLER(error)
+ IMPL_EVENT_HANDLER(message)
+
+ static bool IsEnabled(JSContext* aCx, JSObject* aGlobal);
+
+ explicit ServiceWorkerContainer(nsPIDOMWindowInner* aWindow);
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<Promise>
+ Register(const nsAString& aScriptURL,
+ const RegistrationOptions& aOptions,
+ ErrorResult& aRv);
+
+ already_AddRefed<workers::ServiceWorker>
+ GetController();
+
+ already_AddRefed<Promise>
+ GetRegistration(const nsAString& aDocumentURL,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise>
+ GetRegistrations(ErrorResult& aRv);
+
+ Promise*
+ GetReady(ErrorResult& aRv);
+
+ // Testing only.
+ void
+ GetScopeForUrl(const nsAString& aUrl, nsString& aScope, ErrorResult& aRv);
+
+ // DOMEventTargetHelper
+ void DisconnectFromOwner() override;
+
+ // Invalidates |mControllerWorker| and dispatches a "controllerchange"
+ // event.
+ void
+ ControllerChanged(ErrorResult& aRv);
+
+private:
+ ~ServiceWorkerContainer();
+
+ void RemoveReadyPromise();
+
+ // This only changes when a worker hijacks everything in its scope by calling
+ // claim.
+ RefPtr<workers::ServiceWorker> mControllerWorker;
+
+ RefPtr<Promise> mReadyPromise;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_workers_serviceworkercontainer_h__ */
diff --git a/dom/workers/ServiceWorkerEvents.cpp b/dom/workers/ServiceWorkerEvents.cpp
new file mode 100644
index 000000000..09b09a24b
--- /dev/null
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -0,0 +1,1280 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerEvents.h"
+
+#include "nsAutoPtr.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIOutputStream.h"
+#include "nsIScriptError.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsIUnicodeEncoder.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsSerializationHelper.h"
+#include "nsQueryObject.h"
+#include "ServiceWorkerClient.h"
+#include "ServiceWorkerManager.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/BodyUtil.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMExceptionBinding.h"
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/FetchEventBinding.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/PushEventBinding.h"
+#include "mozilla/dom/PushMessageDataBinding.h"
+#include "mozilla/dom/PushUtil.h"
+#include "mozilla/dom/Request.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/Response.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/dom/workers/bindings/ServiceWorker.h"
+
+#include "js/Conversions.h"
+#include "js/TypeDecls.h"
+#include "WorkerPrivate.h"
+#include "xpcpublic.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::dom::workers;
+
+namespace {
+
+void
+AsyncLog(nsIInterceptedChannel *aInterceptedChannel,
+ const nsACString& aRespondWithScriptSpec,
+ uint32_t aRespondWithLineNumber, uint32_t aRespondWithColumnNumber,
+ const nsACString& aMessageName, const nsTArray<nsString>& aParams)
+{
+ MOZ_ASSERT(aInterceptedChannel);
+ nsCOMPtr<nsIConsoleReportCollector> reporter =
+ aInterceptedChannel->GetConsoleReportCollector();
+ if (reporter) {
+ reporter->AddConsoleReport(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("Service Worker Interception"),
+ nsContentUtils::eDOM_PROPERTIES,
+ aRespondWithScriptSpec,
+ aRespondWithLineNumber,
+ aRespondWithColumnNumber,
+ aMessageName, aParams);
+ }
+}
+
+template<typename... Params>
+void
+AsyncLog(nsIInterceptedChannel* aInterceptedChannel,
+ const nsACString& aRespondWithScriptSpec,
+ uint32_t aRespondWithLineNumber, uint32_t aRespondWithColumnNumber,
+ // We have to list one explicit string so that calls with an
+ // nsTArray of params won't end up in here.
+ const nsACString& aMessageName, const nsAString& aFirstParam,
+ Params&&... aParams)
+{
+ nsTArray<nsString> paramsList(sizeof...(Params) + 1);
+ StringArrayAppender::Append(paramsList, sizeof...(Params) + 1,
+ aFirstParam, Forward<Params>(aParams)...);
+ AsyncLog(aInterceptedChannel, aRespondWithScriptSpec, aRespondWithLineNumber,
+ aRespondWithColumnNumber, aMessageName, paramsList);
+}
+
+} // anonymous namespace
+
+BEGIN_WORKERS_NAMESPACE
+
+CancelChannelRunnable::CancelChannelRunnable(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
+ nsresult aStatus)
+ : mChannel(aChannel)
+ , mRegistration(aRegistration)
+ , mStatus(aStatus)
+{
+}
+
+NS_IMETHODIMP
+CancelChannelRunnable::Run()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mChannel->Cancel(mStatus);
+ mRegistration->MaybeScheduleUpdate();
+ return NS_OK;
+}
+
+FetchEvent::FetchEvent(EventTarget* aOwner)
+ : ExtendableEvent(aOwner)
+ , mPreventDefaultLineNumber(0)
+ , mPreventDefaultColumnNumber(0)
+ , mIsReload(false)
+ , mWaitToRespond(false)
+{
+}
+
+FetchEvent::~FetchEvent()
+{
+}
+
+void
+FetchEvent::PostInit(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
+ const nsACString& aScriptSpec)
+{
+ mChannel = aChannel;
+ mRegistration = aRegistration;
+ mScriptSpec.Assign(aScriptSpec);
+}
+
+/*static*/ already_AddRefed<FetchEvent>
+FetchEvent::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const FetchEventInit& aOptions,
+ ErrorResult& aRv)
+{
+ RefPtr<EventTarget> owner = do_QueryObject(aGlobal.GetAsSupports());
+ MOZ_ASSERT(owner);
+ RefPtr<FetchEvent> e = new FetchEvent(owner);
+ bool trusted = e->Init(owner);
+ e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable);
+ e->SetTrusted(trusted);
+ e->SetComposed(aOptions.mComposed);
+ e->mRequest = aOptions.mRequest;
+ e->mClientId = aOptions.mClientId;
+ e->mIsReload = aOptions.mIsReload;
+ return e.forget();
+}
+
+namespace {
+
+class FinishResponse final : public Runnable
+{
+ nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
+ RefPtr<InternalResponse> mInternalResponse;
+ ChannelInfo mWorkerChannelInfo;
+ const nsCString mScriptSpec;
+ const nsCString mResponseURLSpec;
+
+public:
+ FinishResponse(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
+ InternalResponse* aInternalResponse,
+ const ChannelInfo& aWorkerChannelInfo,
+ const nsACString& aScriptSpec,
+ const nsACString& aResponseURLSpec)
+ : mChannel(aChannel)
+ , mInternalResponse(aInternalResponse)
+ , mWorkerChannelInfo(aWorkerChannelInfo)
+ , mScriptSpec(aScriptSpec)
+ , mResponseURLSpec(aResponseURLSpec)
+ {
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIChannel> underlyingChannel;
+ nsresult rv = mChannel->GetChannel(getter_AddRefs(underlyingChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(underlyingChannel, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsILoadInfo> loadInfo = underlyingChannel->GetLoadInfo();
+
+ if (!CSPPermitsResponse(loadInfo)) {
+ mChannel->Cancel(NS_ERROR_CONTENT_BLOCKED);
+ return NS_OK;
+ }
+
+ ChannelInfo channelInfo;
+ if (mInternalResponse->GetChannelInfo().IsInitialized()) {
+ channelInfo = mInternalResponse->GetChannelInfo();
+ } else {
+ // We are dealing with a synthesized response here, so fall back to the
+ // channel info for the worker script.
+ channelInfo = mWorkerChannelInfo;
+ }
+ rv = mChannel->SetChannelInfo(&channelInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mChannel->Cancel(NS_ERROR_INTERCEPTION_FAILED);
+ return NS_OK;
+ }
+
+ rv = mChannel->SynthesizeStatus(mInternalResponse->GetUnfilteredStatus(),
+ mInternalResponse->GetUnfilteredStatusText());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mChannel->Cancel(NS_ERROR_INTERCEPTION_FAILED);
+ return NS_OK;
+ }
+
+ AutoTArray<InternalHeaders::Entry, 5> entries;
+ mInternalResponse->UnfilteredHeaders()->GetEntries(entries);
+ for (uint32_t i = 0; i < entries.Length(); ++i) {
+ mChannel->SynthesizeHeader(entries[i].mName, entries[i].mValue);
+ }
+
+ loadInfo->MaybeIncreaseTainting(mInternalResponse->GetTainting());
+
+ rv = mChannel->FinishSynthesizedResponse(mResponseURLSpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mChannel->Cancel(NS_ERROR_INTERCEPTION_FAILED);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(underlyingChannel, "service-worker-synthesized-response", nullptr);
+ }
+
+ return rv;
+ }
+ bool CSPPermitsResponse(nsILoadInfo* aLoadInfo)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aLoadInfo);
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+ nsCString url = mInternalResponse->GetUnfilteredURL();
+ if (url.IsEmpty()) {
+ // Synthetic response. The buck stops at the worker script.
+ url = mScriptSpec;
+ }
+ rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, false);
+ int16_t decision = nsIContentPolicy::ACCEPT;
+ rv = NS_CheckContentLoadPolicy(aLoadInfo->InternalContentPolicyType(), uri,
+ aLoadInfo->LoadingPrincipal(),
+ aLoadInfo->LoadingNode(), EmptyCString(),
+ nullptr, &decision);
+ NS_ENSURE_SUCCESS(rv, false);
+ return decision == nsIContentPolicy::ACCEPT;
+ }
+};
+
+class RespondWithHandler final : public PromiseNativeHandler
+{
+ nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
+ const RequestMode mRequestMode;
+ const RequestRedirect mRequestRedirectMode;
+#ifdef DEBUG
+ const bool mIsClientRequest;
+#endif
+ const nsCString mScriptSpec;
+ const nsString mRequestURL;
+ const nsCString mRespondWithScriptSpec;
+ const uint32_t mRespondWithLineNumber;
+ const uint32_t mRespondWithColumnNumber;
+ bool mRequestWasHandled;
+public:
+ NS_DECL_ISUPPORTS
+
+ RespondWithHandler(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
+ RequestMode aRequestMode, bool aIsClientRequest,
+ RequestRedirect aRedirectMode,
+ const nsACString& aScriptSpec,
+ const nsAString& aRequestURL,
+ const nsACString& aRespondWithScriptSpec,
+ uint32_t aRespondWithLineNumber,
+ uint32_t aRespondWithColumnNumber)
+ : mInterceptedChannel(aChannel)
+ , mRegistration(aRegistration)
+ , mRequestMode(aRequestMode)
+ , mRequestRedirectMode(aRedirectMode)
+#ifdef DEBUG
+ , mIsClientRequest(aIsClientRequest)
+#endif
+ , mScriptSpec(aScriptSpec)
+ , mRequestURL(aRequestURL)
+ , mRespondWithScriptSpec(aRespondWithScriptSpec)
+ , mRespondWithLineNumber(aRespondWithLineNumber)
+ , mRespondWithColumnNumber(aRespondWithColumnNumber)
+ , mRequestWasHandled(false)
+ {
+ }
+
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+ void CancelRequest(nsresult aStatus);
+
+ void AsyncLog(const nsACString& aMessageName, const nsTArray<nsString>& aParams)
+ {
+ ::AsyncLog(mInterceptedChannel, mRespondWithScriptSpec, mRespondWithLineNumber,
+ mRespondWithColumnNumber, aMessageName, aParams);
+ }
+
+ void AsyncLog(const nsACString& aSourceSpec, uint32_t aLine, uint32_t aColumn,
+ const nsACString& aMessageName, const nsTArray<nsString>& aParams)
+ {
+ ::AsyncLog(mInterceptedChannel, aSourceSpec, aLine, aColumn, aMessageName,
+ aParams);
+ }
+
+private:
+ ~RespondWithHandler()
+ {
+ if (!mRequestWasHandled) {
+ ::AsyncLog(mInterceptedChannel, mRespondWithScriptSpec,
+ mRespondWithLineNumber, mRespondWithColumnNumber,
+ NS_LITERAL_CSTRING("InterceptionFailedWithURL"), mRequestURL);
+ CancelRequest(NS_ERROR_INTERCEPTION_FAILED);
+ }
+ }
+};
+
+struct RespondWithClosure
+{
+ nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
+ RefPtr<InternalResponse> mInternalResponse;
+ ChannelInfo mWorkerChannelInfo;
+ const nsCString mScriptSpec;
+ const nsCString mResponseURLSpec;
+ const nsString mRequestURL;
+ const nsCString mRespondWithScriptSpec;
+ const uint32_t mRespondWithLineNumber;
+ const uint32_t mRespondWithColumnNumber;
+
+ RespondWithClosure(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
+ InternalResponse* aInternalResponse,
+ const ChannelInfo& aWorkerChannelInfo,
+ const nsCString& aScriptSpec,
+ const nsACString& aResponseURLSpec,
+ const nsAString& aRequestURL,
+ const nsACString& aRespondWithScriptSpec,
+ uint32_t aRespondWithLineNumber,
+ uint32_t aRespondWithColumnNumber)
+ : mInterceptedChannel(aChannel)
+ , mRegistration(aRegistration)
+ , mInternalResponse(aInternalResponse)
+ , mWorkerChannelInfo(aWorkerChannelInfo)
+ , mScriptSpec(aScriptSpec)
+ , mResponseURLSpec(aResponseURLSpec)
+ , mRequestURL(aRequestURL)
+ , mRespondWithScriptSpec(aRespondWithScriptSpec)
+ , mRespondWithLineNumber(aRespondWithLineNumber)
+ , mRespondWithColumnNumber(aRespondWithColumnNumber)
+ {
+ }
+};
+
+void RespondWithCopyComplete(void* aClosure, nsresult aStatus)
+{
+ nsAutoPtr<RespondWithClosure> data(static_cast<RespondWithClosure*>(aClosure));
+ nsCOMPtr<nsIRunnable> event;
+ if (NS_WARN_IF(NS_FAILED(aStatus))) {
+ AsyncLog(data->mInterceptedChannel, data->mRespondWithScriptSpec,
+ data->mRespondWithLineNumber, data->mRespondWithColumnNumber,
+ NS_LITERAL_CSTRING("InterceptionFailedWithURL"),
+ data->mRequestURL);
+ event = new CancelChannelRunnable(data->mInterceptedChannel,
+ data->mRegistration,
+ NS_ERROR_INTERCEPTION_FAILED);
+ } else {
+ event = new FinishResponse(data->mInterceptedChannel,
+ data->mInternalResponse,
+ data->mWorkerChannelInfo,
+ data->mScriptSpec,
+ data->mResponseURLSpec);
+ }
+ // In theory this can happen after the worker thread is terminated.
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ if (worker) {
+ MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(event.forget()));
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(event.forget()));
+ }
+}
+
+namespace {
+
+void
+ExtractErrorValues(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ nsACString& aSourceSpecOut, uint32_t *aLineOut,
+ uint32_t *aColumnOut, nsString& aMessageOut)
+{
+ MOZ_ASSERT(aLineOut);
+ MOZ_ASSERT(aColumnOut);
+
+ if (aValue.isObject()) {
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+ RefPtr<DOMException> domException;
+
+ // Try to process as an Error object. Use the file/line/column values
+ // from the Error as they will be more specific to the root cause of
+ // the problem.
+ JSErrorReport* err = obj ? JS_ErrorFromException(aCx, obj) : nullptr;
+ if (err) {
+ // Use xpc to extract the error message only. We don't actually send
+ // this report anywhere.
+ RefPtr<xpc::ErrorReport> report = new xpc::ErrorReport();
+ report->Init(err,
+ "<unknown>", // toString result
+ false, // chrome
+ 0); // window ID
+
+ if (!report->mFileName.IsEmpty()) {
+ CopyUTF16toUTF8(report->mFileName, aSourceSpecOut);
+ *aLineOut = report->mLineNumber;
+ *aColumnOut = report->mColumn;
+ }
+ aMessageOut.Assign(report->mErrorMsg);
+ }
+
+ // Next, try to unwrap the rejection value as a DOMException.
+ else if(NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, obj, domException))) {
+
+ nsAutoString filename;
+ domException->GetFilename(aCx, filename);
+ if (!filename.IsEmpty()) {
+ CopyUTF16toUTF8(filename, aSourceSpecOut);
+ *aLineOut = domException->LineNumber(aCx);
+ *aColumnOut = domException->ColumnNumber();
+ }
+
+ domException->GetName(aMessageOut);
+ aMessageOut.AppendLiteral(": ");
+
+ nsAutoString message;
+ domException->GetMessageMoz(message);
+ aMessageOut.Append(message);
+ }
+ }
+
+ // If we could not unwrap a specific error type, then perform default safe
+ // string conversions on primitives. Objects will result in "[Object]"
+ // unfortunately.
+ if (aMessageOut.IsEmpty()) {
+ nsAutoJSString jsString;
+ if (jsString.init(aCx, aValue)) {
+ aMessageOut = jsString;
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+ }
+}
+
+} // anonymous namespace
+
+class MOZ_STACK_CLASS AutoCancel
+{
+ RefPtr<RespondWithHandler> mOwner;
+ nsCString mSourceSpec;
+ uint32_t mLine;
+ uint32_t mColumn;
+ nsCString mMessageName;
+ nsTArray<nsString> mParams;
+
+public:
+ AutoCancel(RespondWithHandler* aOwner, const nsString& aRequestURL)
+ : mOwner(aOwner)
+ , mLine(0)
+ , mColumn(0)
+ , mMessageName(NS_LITERAL_CSTRING("InterceptionFailedWithURL"))
+ {
+ mParams.AppendElement(aRequestURL);
+ }
+
+ ~AutoCancel()
+ {
+ if (mOwner) {
+ if (mSourceSpec.IsEmpty()) {
+ mOwner->AsyncLog(mMessageName, mParams);
+ } else {
+ mOwner->AsyncLog(mSourceSpec, mLine, mColumn, mMessageName, mParams);
+ }
+ mOwner->CancelRequest(NS_ERROR_INTERCEPTION_FAILED);
+ }
+ }
+
+ template<typename... Params>
+ void SetCancelMessage(const nsACString& aMessageName, Params&&... aParams)
+ {
+ MOZ_ASSERT(mOwner);
+ MOZ_ASSERT(mMessageName.EqualsLiteral("InterceptionFailedWithURL"));
+ MOZ_ASSERT(mParams.Length() == 1);
+ mMessageName = aMessageName;
+ mParams.Clear();
+ StringArrayAppender::Append(mParams, sizeof...(Params),
+ Forward<Params>(aParams)...);
+ }
+
+ template<typename... Params>
+ void SetCancelMessageAndLocation(const nsACString& aSourceSpec,
+ uint32_t aLine, uint32_t aColumn,
+ const nsACString& aMessageName,
+ Params&&... aParams)
+ {
+ MOZ_ASSERT(mOwner);
+ MOZ_ASSERT(mMessageName.EqualsLiteral("InterceptionFailedWithURL"));
+ MOZ_ASSERT(mParams.Length() == 1);
+
+ mSourceSpec = aSourceSpec;
+ mLine = aLine;
+ mColumn = aColumn;
+
+ mMessageName = aMessageName;
+ mParams.Clear();
+ StringArrayAppender::Append(mParams, sizeof...(Params),
+ Forward<Params>(aParams)...);
+ }
+
+ void Reset()
+ {
+ mOwner = nullptr;
+ }
+};
+
+NS_IMPL_ISUPPORTS0(RespondWithHandler)
+
+void
+RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+ AutoCancel autoCancel(this, mRequestURL);
+
+ if (!aValue.isObject()) {
+ NS_WARNING("FetchEvent::RespondWith was passed a promise resolved to a non-Object value");
+
+ nsCString sourceSpec;
+ uint32_t line = 0;
+ uint32_t column = 0;
+ nsString valueString;
+ ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString);
+
+ autoCancel.SetCancelMessageAndLocation(sourceSpec, line, column,
+ NS_LITERAL_CSTRING("InterceptedNonResponseWithURL"),
+ mRequestURL, valueString);
+ return;
+ }
+
+ RefPtr<Response> response;
+ nsresult rv = UNWRAP_OBJECT(Response, &aValue.toObject(), response);
+ if (NS_FAILED(rv)) {
+ nsCString sourceSpec;
+ uint32_t line = 0;
+ uint32_t column = 0;
+ nsString valueString;
+ ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString);
+
+ autoCancel.SetCancelMessageAndLocation(sourceSpec, line, column,
+ NS_LITERAL_CSTRING("InterceptedNonResponseWithURL"),
+ mRequestURL, valueString);
+ return;
+ }
+
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ worker->AssertIsOnWorkerThread();
+
+ // Section "HTTP Fetch", step 3.3:
+ // If one of the following conditions is true, return a network error:
+ // * response's type is "error".
+ // * request's mode is not "no-cors" and response's type is "opaque".
+ // * request's redirect mode is not "manual" and response's type is
+ // "opaqueredirect".
+ // * request's redirect mode is not "follow" and response's url list
+ // has more than one item.
+
+ if (response->Type() == ResponseType::Error) {
+ autoCancel.SetCancelMessage(
+ NS_LITERAL_CSTRING("InterceptedErrorResponseWithURL"), mRequestURL);
+ return;
+ }
+
+ MOZ_ASSERT_IF(mIsClientRequest, mRequestMode == RequestMode::Same_origin ||
+ mRequestMode == RequestMode::Navigate);
+
+ if (response->Type() == ResponseType::Opaque && mRequestMode != RequestMode::No_cors) {
+ uint32_t mode = static_cast<uint32_t>(mRequestMode);
+ NS_ConvertASCIItoUTF16 modeString(RequestModeValues::strings[mode].value,
+ RequestModeValues::strings[mode].length);
+
+ autoCancel.SetCancelMessage(
+ NS_LITERAL_CSTRING("BadOpaqueInterceptionRequestModeWithURL"),
+ mRequestURL, modeString);
+ return;
+ }
+
+ if (mRequestRedirectMode != RequestRedirect::Manual &&
+ response->Type() == ResponseType::Opaqueredirect) {
+ autoCancel.SetCancelMessage(
+ NS_LITERAL_CSTRING("BadOpaqueRedirectInterceptionWithURL"), mRequestURL);
+ return;
+ }
+
+ if (mRequestRedirectMode != RequestRedirect::Follow && response->Redirected()) {
+ autoCancel.SetCancelMessage(
+ NS_LITERAL_CSTRING("BadRedirectModeInterceptionWithURL"), mRequestURL);
+ return;
+ }
+
+ if (NS_WARN_IF(response->BodyUsed())) {
+ autoCancel.SetCancelMessage(
+ NS_LITERAL_CSTRING("InterceptedUsedResponseWithURL"), mRequestURL);
+ return;
+ }
+
+ RefPtr<InternalResponse> ir = response->GetInternalResponse();
+ if (NS_WARN_IF(!ir)) {
+ return;
+ }
+ // When an opaque response is encountered, we need the original channel's principal
+ // to reflect the final URL. Non-opaque responses are either same-origin or CORS-enabled
+ // cross-origin responses, which are treated as same-origin by consumers.
+ nsCString responseURL;
+ if (response->Type() == ResponseType::Opaque) {
+ responseURL = ir->GetUnfilteredURL();
+ if (NS_WARN_IF(responseURL.IsEmpty())) {
+ return;
+ }
+ }
+ nsAutoPtr<RespondWithClosure> closure(new RespondWithClosure(mInterceptedChannel,
+ mRegistration, ir,
+ worker->GetChannelInfo(),
+ mScriptSpec,
+ responseURL,
+ mRequestURL,
+ mRespondWithScriptSpec,
+ mRespondWithLineNumber,
+ mRespondWithColumnNumber));
+ nsCOMPtr<nsIInputStream> body;
+ ir->GetUnfilteredBody(getter_AddRefs(body));
+ // Errors and redirects may not have a body.
+ if (body) {
+ response->SetBodyUsed();
+
+ nsCOMPtr<nsIOutputStream> responseBody;
+ rv = mInterceptedChannel->GetResponseBody(getter_AddRefs(responseBody));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ const uint32_t kCopySegmentSize = 4096;
+
+ // Depending on how the Response passed to .respondWith() was created, we may
+ // get a non-buffered input stream. In addition, in some configurations the
+ // destination channel's output stream can be unbuffered. We wrap the output
+ // stream side here so that NS_AsyncCopy() works. Wrapping the output side
+ // provides the most consistent operation since there are fewer stream types
+ // we are writing to. The input stream can be a wide variety of concrete
+ // objects which may or many not play well with NS_InputStreamIsBuffered().
+ if (!NS_OutputStreamIsBuffered(responseBody)) {
+ nsCOMPtr<nsIOutputStream> buffered;
+ rv = NS_NewBufferedOutputStream(getter_AddRefs(buffered), responseBody,
+ kCopySegmentSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ responseBody = buffered;
+ }
+
+ nsCOMPtr<nsIEventTarget> stsThread = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(!stsThread)) {
+ return;
+ }
+
+ // XXXnsm, Fix for Bug 1141332 means that if we decide to make this
+ // streaming at some point, we'll need a different solution to that bug.
+ rv = NS_AsyncCopy(body, responseBody, stsThread, NS_ASYNCCOPY_VIA_WRITESEGMENTS,
+ kCopySegmentSize, RespondWithCopyComplete, closure.forget());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ } else {
+ RespondWithCopyComplete(closure.forget(), NS_OK);
+ }
+
+ MOZ_ASSERT(!closure);
+ autoCancel.Reset();
+ mRequestWasHandled = true;
+}
+
+void
+RespondWithHandler::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+ nsCString sourceSpec = mRespondWithScriptSpec;
+ uint32_t line = mRespondWithLineNumber;
+ uint32_t column = mRespondWithColumnNumber;
+ nsString valueString;
+
+ ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString);
+
+ ::AsyncLog(mInterceptedChannel, sourceSpec, line, column,
+ NS_LITERAL_CSTRING("InterceptionRejectedResponseWithURL"),
+ mRequestURL, valueString);
+
+ CancelRequest(NS_ERROR_INTERCEPTION_FAILED);
+}
+
+void
+RespondWithHandler::CancelRequest(nsresult aStatus)
+{
+ nsCOMPtr<nsIRunnable> runnable =
+ new CancelChannelRunnable(mInterceptedChannel, mRegistration, aStatus);
+ // Note, this may run off the worker thread during worker termination.
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ if (worker) {
+ MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(runnable.forget()));
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget()));
+ }
+ mRequestWasHandled = true;
+}
+
+} // namespace
+
+void
+FetchEvent::RespondWith(JSContext* aCx, Promise& aArg, ErrorResult& aRv)
+{
+ if (EventPhase() == nsIDOMEvent::NONE || mWaitToRespond) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+
+ // Record where respondWith() was called in the script so we can include the
+ // information in any error reporting. We should be guaranteed not to get
+ // a file:// string here because service workers require http/https.
+ nsCString spec;
+ uint32_t line = 0;
+ uint32_t column = 0;
+ nsJSUtils::GetCallingLocation(aCx, spec, &line, &column);
+
+ RefPtr<InternalRequest> ir = mRequest->GetInternalRequest();
+
+ nsAutoCString requestURL;
+ ir->GetURL(requestURL);
+
+ StopImmediatePropagation();
+ mWaitToRespond = true;
+ RefPtr<RespondWithHandler> handler =
+ new RespondWithHandler(mChannel, mRegistration, mRequest->Mode(),
+ ir->IsClientRequest(), mRequest->Redirect(),
+ mScriptSpec, NS_ConvertUTF8toUTF16(requestURL),
+ spec, line, column);
+ aArg.AppendNativeHandler(handler);
+
+ // Append directly to the lifecycle promises array. Don't call WaitUntil()
+ // because that will lead to double-reporting any errors.
+ mPromises.AppendElement(&aArg);
+}
+
+void
+FetchEvent::PreventDefault(JSContext* aCx)
+{
+ MOZ_ASSERT(aCx);
+
+ if (mPreventDefaultScriptSpec.IsEmpty()) {
+ // Note when the FetchEvent might have been canceled by script, but don't
+ // actually log the location until we are sure it matters. This is
+ // determined in ServiceWorkerPrivate.cpp. We only remember the first
+ // call to preventDefault() as its the most likely to have actually canceled
+ // the event.
+ nsJSUtils::GetCallingLocation(aCx, mPreventDefaultScriptSpec,
+ &mPreventDefaultLineNumber,
+ &mPreventDefaultColumnNumber);
+ }
+
+ Event::PreventDefault(aCx);
+}
+
+void
+FetchEvent::ReportCanceled()
+{
+ MOZ_ASSERT(!mPreventDefaultScriptSpec.IsEmpty());
+
+ RefPtr<InternalRequest> ir = mRequest->GetInternalRequest();
+ nsAutoCString url;
+ ir->GetURL(url);
+
+ // The variadic template provided by StringArrayAppender requires exactly
+ // an nsString.
+ NS_ConvertUTF8toUTF16 requestURL(url);
+ //nsString requestURL;
+ //CopyUTF8toUTF16(url, requestURL);
+
+ ::AsyncLog(mChannel.get(), mPreventDefaultScriptSpec,
+ mPreventDefaultLineNumber, mPreventDefaultColumnNumber,
+ NS_LITERAL_CSTRING("InterceptionCanceledWithURL"), requestURL);
+}
+
+namespace {
+
+class WaitUntilHandler final : public PromiseNativeHandler
+{
+ WorkerPrivate* mWorkerPrivate;
+ const nsCString mScope;
+ nsCString mSourceSpec;
+ uint32_t mLine;
+ uint32_t mColumn;
+ nsString mRejectValue;
+
+ ~WaitUntilHandler()
+ {
+ }
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ WaitUntilHandler(WorkerPrivate* aWorkerPrivate, JSContext* aCx)
+ : mWorkerPrivate(aWorkerPrivate)
+ , mScope(mWorkerPrivate->WorkerName())
+ , mLine(0)
+ , mColumn(0)
+ {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ // Save the location of the waitUntil() call itself as a fallback
+ // in case the rejection value does not contain any location info.
+ nsJSUtils::GetCallingLocation(aCx, mSourceSpec, &mLine, &mColumn);
+ }
+
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ // do nothing, we are only here to report errors
+ }
+
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ nsCString spec;
+ uint32_t line = 0;
+ uint32_t column = 0;
+ ExtractErrorValues(aCx, aValue, spec, &line, &column, mRejectValue);
+
+ // only use the extracted location if we found one
+ if (!spec.IsEmpty()) {
+ mSourceSpec = spec;
+ mLine = line;
+ mColumn = column;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(
+ NewRunnableMethod(this, &WaitUntilHandler::ReportOnMainThread)));
+ }
+
+ void
+ ReportOnMainThread()
+ {
+ AssertIsOnMainThread();
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ // browser shutdown
+ return;
+ }
+
+ // TODO: Make the error message a localized string. (bug 1222720)
+ nsString message;
+ message.AppendLiteral("Service worker event waitUntil() was passed a "
+ "promise that rejected with '");
+ message.Append(mRejectValue);
+ message.AppendLiteral("'.");
+
+ // Note, there is a corner case where this won't report to the window
+ // that triggered the error. Consider a navigation fetch event that
+ // rejects waitUntil() without holding respondWith() open. In this case
+ // there is no controlling document yet, the window did call .register()
+ // because there is no documeny yet, and the navigation is no longer
+ // being intercepted.
+
+ swm->ReportToAllClients(mScope, message, NS_ConvertUTF8toUTF16(mSourceSpec),
+ EmptyString(), mLine, mColumn,
+ nsIScriptError::errorFlag);
+ }
+};
+
+NS_IMPL_ISUPPORTS0(WaitUntilHandler)
+
+} // anonymous namespace
+
+NS_IMPL_ADDREF_INHERITED(FetchEvent, ExtendableEvent)
+NS_IMPL_RELEASE_INHERITED(FetchEvent, ExtendableEvent)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FetchEvent)
+NS_INTERFACE_MAP_END_INHERITING(ExtendableEvent)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(FetchEvent, ExtendableEvent, mRequest)
+
+ExtendableEvent::ExtendableEvent(EventTarget* aOwner)
+ : Event(aOwner, nullptr, nullptr)
+{
+}
+
+void
+ExtendableEvent::WaitUntil(JSContext* aCx, Promise& aPromise, ErrorResult& aRv)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ if (EventPhase() == nsIDOMEvent::NONE) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ // Append our handler to each waitUntil promise separately so we
+ // can record the location in script where waitUntil was called.
+ RefPtr<WaitUntilHandler> handler =
+ new WaitUntilHandler(GetCurrentThreadWorkerPrivate(), aCx);
+ aPromise.AppendNativeHandler(handler);
+
+ mPromises.AppendElement(&aPromise);
+}
+
+already_AddRefed<Promise>
+ExtendableEvent::GetPromise()
+{
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ worker->AssertIsOnWorkerThread();
+
+ nsIGlobalObject* globalObj = worker->GlobalScope();
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(globalObj)) {
+ return nullptr;
+ }
+ JSContext* cx = jsapi.cx();
+
+ GlobalObject global(cx, globalObj->GetGlobalJSObject());
+
+ ErrorResult result;
+ RefPtr<Promise> p = Promise::All(global, Move(mPromises), result);
+ if (NS_WARN_IF(result.MaybeSetPendingException(cx))) {
+ return nullptr;
+ }
+
+ return p.forget();
+}
+
+NS_IMPL_ADDREF_INHERITED(ExtendableEvent, Event)
+NS_IMPL_RELEASE_INHERITED(ExtendableEvent, Event)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ExtendableEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ExtendableEvent, Event, mPromises)
+
+namespace {
+nsresult
+ExtractBytesFromUSVString(const nsAString& aStr, nsTArray<uint8_t>& aBytes)
+{
+ MOZ_ASSERT(aBytes.IsEmpty());
+ nsCOMPtr<nsIUnicodeEncoder> encoder = EncodingUtils::EncoderForEncoding("UTF-8");
+ if (NS_WARN_IF(!encoder)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ int32_t srcLen = aStr.Length();
+ int32_t destBufferLen;
+ nsresult rv = encoder->GetMaxLength(aStr.BeginReading(), srcLen, &destBufferLen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!aBytes.SetLength(destBufferLen, fallible))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ char* destBuffer = reinterpret_cast<char*>(aBytes.Elements());
+ int32_t outLen = destBufferLen;
+ rv = encoder->Convert(aStr.BeginReading(), &srcLen, destBuffer, &outLen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aBytes.Clear();
+ return rv;
+ }
+
+ aBytes.TruncateLength(outLen);
+
+ return NS_OK;
+}
+
+nsresult
+ExtractBytesFromData(const OwningArrayBufferViewOrArrayBufferOrUSVString& aDataInit, nsTArray<uint8_t>& aBytes)
+{
+ if (aDataInit.IsArrayBufferView()) {
+ const ArrayBufferView& view = aDataInit.GetAsArrayBufferView();
+ if (NS_WARN_IF(!PushUtil::CopyArrayBufferViewToArray(view, aBytes))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+ }
+ if (aDataInit.IsArrayBuffer()) {
+ const ArrayBuffer& buffer = aDataInit.GetAsArrayBuffer();
+ if (NS_WARN_IF(!PushUtil::CopyArrayBufferToArray(buffer, aBytes))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+ }
+ if (aDataInit.IsUSVString()) {
+ return ExtractBytesFromUSVString(aDataInit.GetAsUSVString(), aBytes);
+ }
+ NS_NOTREACHED("Unexpected push message data");
+ return NS_ERROR_FAILURE;
+}
+}
+
+PushMessageData::PushMessageData(nsISupports* aOwner,
+ nsTArray<uint8_t>&& aBytes)
+ : mOwner(aOwner), mBytes(Move(aBytes)) {}
+
+PushMessageData::~PushMessageData()
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushMessageData, mOwner)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PushMessageData)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PushMessageData)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushMessageData)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject*
+PushMessageData::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return mozilla::dom::PushMessageDataBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+PushMessageData::Json(JSContext* cx, JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv)
+{
+ if (NS_FAILED(EnsureDecodedText())) {
+ aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
+ return;
+ }
+ BodyUtil::ConsumeJson(cx, aRetval, mDecodedText, aRv);
+}
+
+void
+PushMessageData::Text(nsAString& aData)
+{
+ if (NS_SUCCEEDED(EnsureDecodedText())) {
+ aData = mDecodedText;
+ }
+}
+
+void
+PushMessageData::ArrayBuffer(JSContext* cx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ uint8_t* data = GetContentsCopy();
+ if (data) {
+ BodyUtil::ConsumeArrayBuffer(cx, aRetval, mBytes.Length(), data, aRv);
+ }
+}
+
+already_AddRefed<mozilla::dom::Blob>
+PushMessageData::Blob(ErrorResult& aRv)
+{
+ uint8_t* data = GetContentsCopy();
+ if (data) {
+ RefPtr<mozilla::dom::Blob> blob = BodyUtil::ConsumeBlob(
+ mOwner, EmptyString(), mBytes.Length(), data, aRv);
+ if (blob) {
+ return blob.forget();
+ }
+ }
+ return nullptr;
+}
+
+nsresult
+PushMessageData::EnsureDecodedText()
+{
+ if (mBytes.IsEmpty() || !mDecodedText.IsEmpty()) {
+ return NS_OK;
+ }
+ nsresult rv = BodyUtil::ConsumeText(
+ mBytes.Length(),
+ reinterpret_cast<uint8_t*>(mBytes.Elements()),
+ mDecodedText
+ );
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mDecodedText.Truncate();
+ return rv;
+ }
+ return NS_OK;
+}
+
+uint8_t*
+PushMessageData::GetContentsCopy()
+{
+ uint32_t length = mBytes.Length();
+ void* data = malloc(length);
+ if (!data) {
+ return nullptr;
+ }
+ memcpy(data, mBytes.Elements(), length);
+ return reinterpret_cast<uint8_t*>(data);
+}
+
+PushEvent::PushEvent(EventTarget* aOwner)
+ : ExtendableEvent(aOwner)
+{
+}
+
+already_AddRefed<PushEvent>
+PushEvent::Constructor(mozilla::dom::EventTarget* aOwner,
+ const nsAString& aType,
+ const PushEventInit& aOptions,
+ ErrorResult& aRv)
+{
+ RefPtr<PushEvent> e = new PushEvent(aOwner);
+ bool trusted = e->Init(aOwner);
+ e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable);
+ e->SetTrusted(trusted);
+ e->SetComposed(aOptions.mComposed);
+ if(aOptions.mData.WasPassed()){
+ nsTArray<uint8_t> bytes;
+ nsresult rv = ExtractBytesFromData(aOptions.mData.Value(), bytes);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ e->mData = new PushMessageData(aOwner, Move(bytes));
+ }
+ return e.forget();
+}
+
+NS_IMPL_ADDREF_INHERITED(PushEvent, ExtendableEvent)
+NS_IMPL_RELEASE_INHERITED(PushEvent, ExtendableEvent)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PushEvent)
+NS_INTERFACE_MAP_END_INHERITING(ExtendableEvent)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(PushEvent, ExtendableEvent, mData)
+
+JSObject*
+PushEvent::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return mozilla::dom::PushEventBinding::Wrap(aCx, this, aGivenProto);
+}
+
+ExtendableMessageEvent::ExtendableMessageEvent(EventTarget* aOwner)
+ : ExtendableEvent(aOwner)
+ , mData(JS::UndefinedValue())
+{
+ mozilla::HoldJSObjects(this);
+}
+
+ExtendableMessageEvent::~ExtendableMessageEvent()
+{
+ mData.setUndefined();
+ DropJSObjects(this);
+}
+
+void
+ExtendableMessageEvent::GetData(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aData,
+ ErrorResult& aRv)
+{
+ aData.set(mData);
+ if (!JS_WrapValue(aCx, aData)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+void
+ExtendableMessageEvent::GetSource(Nullable<OwningClientOrServiceWorkerOrMessagePort>& aValue) const
+{
+ if (mClient) {
+ aValue.SetValue().SetAsClient() = mClient;
+ } else if (mServiceWorker) {
+ aValue.SetValue().SetAsServiceWorker() = mServiceWorker;
+ } else if (mMessagePort) {
+ aValue.SetValue().SetAsMessagePort() = mMessagePort;
+ } else {
+ MOZ_CRASH("Unexpected source value");
+ }
+}
+
+/* static */ already_AddRefed<ExtendableMessageEvent>
+ExtendableMessageEvent::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const ExtendableMessageEventInit& aOptions,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports());
+ return Constructor(t, aType, aOptions, aRv);
+}
+
+/* static */ already_AddRefed<ExtendableMessageEvent>
+ExtendableMessageEvent::Constructor(mozilla::dom::EventTarget* aEventTarget,
+ const nsAString& aType,
+ const ExtendableMessageEventInit& aOptions,
+ ErrorResult& aRv)
+{
+ RefPtr<ExtendableMessageEvent> event = new ExtendableMessageEvent(aEventTarget);
+
+ event->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable);
+ bool trusted = event->Init(aEventTarget);
+ event->SetTrusted(trusted);
+
+ event->mData = aOptions.mData;
+ event->mOrigin = aOptions.mOrigin;
+ event->mLastEventId = aOptions.mLastEventId;
+
+ if (!aOptions.mSource.IsNull()) {
+ if (aOptions.mSource.Value().IsClient()) {
+ event->mClient = aOptions.mSource.Value().GetAsClient();
+ } else if (aOptions.mSource.Value().IsServiceWorker()){
+ event->mServiceWorker = aOptions.mSource.Value().GetAsServiceWorker();
+ } else if (aOptions.mSource.Value().IsMessagePort()){
+ event->mMessagePort = aOptions.mSource.Value().GetAsMessagePort();
+ }
+ }
+
+ event->mPorts.AppendElements(aOptions.mPorts);
+ return event.forget();
+}
+
+void
+ExtendableMessageEvent::GetPorts(nsTArray<RefPtr<MessagePort>>& aPorts)
+{
+ aPorts = mPorts;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ExtendableMessageEvent)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ExtendableMessageEvent, Event)
+ tmp->mData.setUndefined();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mClient)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mServiceWorker)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessagePort)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPorts)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ExtendableMessageEvent, Event)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClient)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorker)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessagePort)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPorts)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ExtendableMessageEvent, Event)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ExtendableMessageEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+NS_IMPL_ADDREF_INHERITED(ExtendableMessageEvent, Event)
+NS_IMPL_RELEASE_INHERITED(ExtendableMessageEvent, Event)
+
+END_WORKERS_NAMESPACE
diff --git a/dom/workers/ServiceWorkerEvents.h b/dom/workers/ServiceWorkerEvents.h
new file mode 100644
index 000000000..25702f8f3
--- /dev/null
+++ b/dom/workers/ServiceWorkerEvents.h
@@ -0,0 +1,316 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_serviceworkerevents_h__
+#define mozilla_dom_workers_serviceworkerevents_h__
+
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/ExtendableEventBinding.h"
+#include "mozilla/dom/ExtendableMessageEventBinding.h"
+#include "mozilla/dom/FetchEventBinding.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Response.h"
+#include "mozilla/dom/workers/bindings/ServiceWorker.h"
+#include "mozilla/dom/workers/Workers.h"
+
+#include "nsProxyRelease.h"
+#include "nsContentUtils.h"
+
+class nsIInterceptedChannel;
+
+namespace mozilla {
+namespace dom {
+class Blob;
+class MessagePort;
+class Request;
+class ResponseOrPromise;
+
+struct PushEventInit;
+} // namespace dom
+} // namespace mozilla
+
+BEGIN_WORKERS_NAMESPACE
+
+class ServiceWorkerRegistrationInfo;
+
+class CancelChannelRunnable final : public Runnable
+{
+ nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
+ const nsresult mStatus;
+public:
+ CancelChannelRunnable(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
+ nsresult aStatus);
+
+ NS_IMETHOD Run() override;
+};
+
+class ExtendableEvent : public Event
+{
+protected:
+ nsTArray<RefPtr<Promise>> mPromises;
+
+ explicit ExtendableEvent(mozilla::dom::EventTarget* aOwner);
+ ~ExtendableEvent() {}
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ExtendableEvent, Event)
+ NS_FORWARD_TO_EVENT
+
+ virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ return mozilla::dom::ExtendableEventBinding::Wrap(aCx, this, aGivenProto);
+ }
+
+ static already_AddRefed<ExtendableEvent>
+ Constructor(mozilla::dom::EventTarget* aOwner,
+ const nsAString& aType,
+ const EventInit& aOptions)
+ {
+ RefPtr<ExtendableEvent> e = new ExtendableEvent(aOwner);
+ bool trusted = e->Init(aOwner);
+ e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable);
+ e->SetTrusted(trusted);
+ e->SetComposed(aOptions.mComposed);
+ return e.forget();
+ }
+
+ static already_AddRefed<ExtendableEvent>
+ Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const EventInit& aOptions,
+ ErrorResult& aRv)
+ {
+ nsCOMPtr<EventTarget> target = do_QueryInterface(aGlobal.GetAsSupports());
+ return Constructor(target, aType, aOptions);
+ }
+
+ void
+ WaitUntil(JSContext* aCx, Promise& aPromise, ErrorResult& aRv);
+
+ already_AddRefed<Promise>
+ GetPromise();
+
+ virtual ExtendableEvent* AsExtendableEvent() override
+ {
+ return this;
+ }
+};
+
+class FetchEvent final : public ExtendableEvent
+{
+ nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
+ RefPtr<Request> mRequest;
+ nsCString mScriptSpec;
+ nsCString mPreventDefaultScriptSpec;
+ nsString mClientId;
+ uint32_t mPreventDefaultLineNumber;
+ uint32_t mPreventDefaultColumnNumber;
+ bool mIsReload;
+ bool mWaitToRespond;
+protected:
+ explicit FetchEvent(EventTarget* aOwner);
+ ~FetchEvent();
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchEvent, ExtendableEvent)
+
+ // Note, we cannot use NS_FORWARD_TO_EVENT because we want a different
+ // PreventDefault(JSContext*) override.
+ NS_FORWARD_NSIDOMEVENT(Event::)
+
+ virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ return FetchEventBinding::Wrap(aCx, this, aGivenProto);
+ }
+
+ void PostInit(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
+ const nsACString& aScriptSpec);
+
+ static already_AddRefed<FetchEvent>
+ Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const FetchEventInit& aOptions,
+ ErrorResult& aRv);
+
+ bool
+ WaitToRespond() const
+ {
+ return mWaitToRespond;
+ }
+
+ Request*
+ Request_() const
+ {
+ MOZ_ASSERT(mRequest);
+ return mRequest;
+ }
+
+ void
+ GetClientId(nsAString& aClientId) const
+ {
+ aClientId = mClientId;
+ }
+
+ bool
+ IsReload() const
+ {
+ return mIsReload;
+ }
+
+ void
+ RespondWith(JSContext* aCx, Promise& aArg, ErrorResult& aRv);
+
+ already_AddRefed<Promise>
+ ForwardTo(const nsAString& aUrl);
+
+ already_AddRefed<Promise>
+ Default();
+
+ void
+ PreventDefault(JSContext* aCx) override;
+
+ void
+ ReportCanceled();
+};
+
+class PushMessageData final : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushMessageData)
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsISupports* GetParentObject() const {
+ return mOwner;
+ }
+
+ void Json(JSContext* cx, JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv);
+ void Text(nsAString& aData);
+ void ArrayBuffer(JSContext* cx, JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv);
+ already_AddRefed<mozilla::dom::Blob> Blob(ErrorResult& aRv);
+
+ PushMessageData(nsISupports* aOwner, nsTArray<uint8_t>&& aBytes);
+private:
+ nsCOMPtr<nsISupports> mOwner;
+ nsTArray<uint8_t> mBytes;
+ nsString mDecodedText;
+ ~PushMessageData();
+
+ nsresult EnsureDecodedText();
+ uint8_t* GetContentsCopy();
+};
+
+class PushEvent final : public ExtendableEvent
+{
+ RefPtr<PushMessageData> mData;
+
+protected:
+ explicit PushEvent(mozilla::dom::EventTarget* aOwner);
+ ~PushEvent() {}
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PushEvent, ExtendableEvent)
+ NS_FORWARD_TO_EVENT
+
+ virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<PushEvent>
+ Constructor(mozilla::dom::EventTarget* aOwner,
+ const nsAString& aType,
+ const PushEventInit& aOptions,
+ ErrorResult& aRv);
+
+ static already_AddRefed<PushEvent>
+ Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const PushEventInit& aOptions,
+ ErrorResult& aRv)
+ {
+ nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
+ return Constructor(owner, aType, aOptions, aRv);
+ }
+
+ PushMessageData*
+ GetData() const
+ {
+ return mData;
+ }
+};
+
+class ExtendableMessageEvent final : public ExtendableEvent
+{
+ JS::Heap<JS::Value> mData;
+ nsString mOrigin;
+ nsString mLastEventId;
+ RefPtr<ServiceWorkerClient> mClient;
+ RefPtr<ServiceWorker> mServiceWorker;
+ RefPtr<MessagePort> mMessagePort;
+ nsTArray<RefPtr<MessagePort>> mPorts;
+
+protected:
+ explicit ExtendableMessageEvent(EventTarget* aOwner);
+ ~ExtendableMessageEvent();
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ExtendableMessageEvent,
+ ExtendableEvent)
+
+ NS_FORWARD_TO_EVENT
+
+ virtual JSObject* WrapObjectInternal(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ return mozilla::dom::ExtendableMessageEventBinding::Wrap(aCx, this, aGivenProto);
+ }
+
+ static already_AddRefed<ExtendableMessageEvent>
+ Constructor(mozilla::dom::EventTarget* aOwner,
+ const nsAString& aType,
+ const ExtendableMessageEventInit& aOptions,
+ ErrorResult& aRv);
+
+ static already_AddRefed<ExtendableMessageEvent>
+ Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const ExtendableMessageEventInit& aOptions,
+ ErrorResult& aRv);
+
+ void GetData(JSContext* aCx, JS::MutableHandle<JS::Value> aData,
+ ErrorResult& aRv);
+
+ void GetSource(Nullable<OwningClientOrServiceWorkerOrMessagePort>& aValue) const;
+
+ NS_IMETHOD GetOrigin(nsAString& aOrigin)
+ {
+ aOrigin = mOrigin;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetLastEventId(nsAString& aLastEventId)
+ {
+ aLastEventId = mLastEventId;
+ return NS_OK;
+ }
+
+ void GetPorts(nsTArray<RefPtr<MessagePort>>& aPorts);
+};
+
+END_WORKERS_NAMESPACE
+
+#endif /* mozilla_dom_workers_serviceworkerevents_h__ */
diff --git a/dom/workers/ServiceWorkerInfo.cpp b/dom/workers/ServiceWorkerInfo.cpp
new file mode 100644
index 000000000..fa08b97a6
--- /dev/null
+++ b/dom/workers/ServiceWorkerInfo.cpp
@@ -0,0 +1,229 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerInfo.h"
+
+#include "ServiceWorkerScriptCache.h"
+
+BEGIN_WORKERS_NAMESPACE
+
+static_assert(nsIServiceWorkerInfo::STATE_INSTALLING == static_cast<uint16_t>(ServiceWorkerState::Installing),
+ "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo.");
+static_assert(nsIServiceWorkerInfo::STATE_INSTALLED == static_cast<uint16_t>(ServiceWorkerState::Installed),
+ "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo.");
+static_assert(nsIServiceWorkerInfo::STATE_ACTIVATING == static_cast<uint16_t>(ServiceWorkerState::Activating),
+ "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo.");
+static_assert(nsIServiceWorkerInfo::STATE_ACTIVATED == static_cast<uint16_t>(ServiceWorkerState::Activated),
+ "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo.");
+static_assert(nsIServiceWorkerInfo::STATE_REDUNDANT == static_cast<uint16_t>(ServiceWorkerState::Redundant),
+ "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo.");
+static_assert(nsIServiceWorkerInfo::STATE_UNKNOWN == static_cast<uint16_t>(ServiceWorkerState::EndGuard_),
+ "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo.");
+
+NS_IMPL_ISUPPORTS(ServiceWorkerInfo, nsIServiceWorkerInfo)
+
+NS_IMETHODIMP
+ServiceWorkerInfo::GetScriptSpec(nsAString& aScriptSpec)
+{
+ AssertIsOnMainThread();
+ CopyUTF8toUTF16(mScriptSpec, aScriptSpec);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerInfo::GetCacheName(nsAString& aCacheName)
+{
+ AssertIsOnMainThread();
+ aCacheName = mCacheName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerInfo::GetState(uint16_t* aState)
+{
+ MOZ_ASSERT(aState);
+ AssertIsOnMainThread();
+ *aState = static_cast<uint16_t>(mState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerInfo::GetDebugger(nsIWorkerDebugger** aResult)
+{
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mServiceWorkerPrivate->GetDebugger(aResult);
+}
+
+NS_IMETHODIMP
+ServiceWorkerInfo::AttachDebugger()
+{
+ return mServiceWorkerPrivate->AttachDebugger();
+}
+
+NS_IMETHODIMP
+ServiceWorkerInfo::DetachDebugger()
+{
+ return mServiceWorkerPrivate->DetachDebugger();
+}
+
+void
+ServiceWorkerInfo::AppendWorker(ServiceWorker* aWorker)
+{
+ MOZ_ASSERT(aWorker);
+#ifdef DEBUG
+ nsAutoString workerURL;
+ aWorker->GetScriptURL(workerURL);
+ MOZ_ASSERT(workerURL.Equals(NS_ConvertUTF8toUTF16(mScriptSpec)));
+#endif
+ MOZ_ASSERT(!mInstances.Contains(aWorker));
+
+ mInstances.AppendElement(aWorker);
+ aWorker->SetState(State());
+}
+
+void
+ServiceWorkerInfo::RemoveWorker(ServiceWorker* aWorker)
+{
+ MOZ_ASSERT(aWorker);
+#ifdef DEBUG
+ nsAutoString workerURL;
+ aWorker->GetScriptURL(workerURL);
+ MOZ_ASSERT(workerURL.Equals(NS_ConvertUTF8toUTF16(mScriptSpec)));
+#endif
+ MOZ_ASSERT(mInstances.Contains(aWorker));
+
+ mInstances.RemoveElement(aWorker);
+}
+
+namespace {
+
+class ChangeStateUpdater final : public Runnable
+{
+public:
+ ChangeStateUpdater(const nsTArray<ServiceWorker*>& aInstances,
+ ServiceWorkerState aState)
+ : mState(aState)
+ {
+ for (size_t i = 0; i < aInstances.Length(); ++i) {
+ mInstances.AppendElement(aInstances[i]);
+ }
+ }
+
+ NS_IMETHOD Run() override
+ {
+ // We need to update the state of all instances atomically before notifying
+ // them to make sure that the observed state for all instances inside
+ // statechange event handlers is correct.
+ for (size_t i = 0; i < mInstances.Length(); ++i) {
+ mInstances[i]->SetState(mState);
+ }
+ for (size_t i = 0; i < mInstances.Length(); ++i) {
+ mInstances[i]->DispatchStateChange(mState);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ AutoTArray<RefPtr<ServiceWorker>, 1> mInstances;
+ ServiceWorkerState mState;
+};
+
+}
+
+void
+ServiceWorkerInfo::UpdateState(ServiceWorkerState aState)
+{
+ AssertIsOnMainThread();
+#ifdef DEBUG
+ // Any state can directly transition to redundant, but everything else is
+ // ordered.
+ if (aState != ServiceWorkerState::Redundant) {
+ MOZ_ASSERT_IF(mState == ServiceWorkerState::EndGuard_, aState == ServiceWorkerState::Installing);
+ MOZ_ASSERT_IF(mState == ServiceWorkerState::Installing, aState == ServiceWorkerState::Installed);
+ MOZ_ASSERT_IF(mState == ServiceWorkerState::Installed, aState == ServiceWorkerState::Activating);
+ MOZ_ASSERT_IF(mState == ServiceWorkerState::Activating, aState == ServiceWorkerState::Activated);
+ }
+ // Activated can only go to redundant.
+ MOZ_ASSERT_IF(mState == ServiceWorkerState::Activated, aState == ServiceWorkerState::Redundant);
+#endif
+ // Flush any pending functional events to the worker when it transitions to the
+ // activated state.
+ // TODO: Do we care that these events will race with the propagation of the
+ // state change?
+ if (aState == ServiceWorkerState::Activated && mState != aState) {
+ mServiceWorkerPrivate->Activated();
+ }
+ mState = aState;
+ nsCOMPtr<nsIRunnable> r = new ChangeStateUpdater(mInstances, mState);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r.forget()));
+ if (mState == ServiceWorkerState::Redundant) {
+ serviceWorkerScriptCache::PurgeCache(mPrincipal, mCacheName);
+ }
+}
+
+ServiceWorkerInfo::ServiceWorkerInfo(nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ const nsACString& aScriptSpec,
+ const nsAString& aCacheName)
+ : mPrincipal(aPrincipal)
+ , mScope(aScope)
+ , mScriptSpec(aScriptSpec)
+ , mCacheName(aCacheName)
+ , mState(ServiceWorkerState::EndGuard_)
+ , mServiceWorkerID(GetNextID())
+ , mServiceWorkerPrivate(new ServiceWorkerPrivate(this))
+ , mSkipWaitingFlag(false)
+{
+ MOZ_ASSERT(mPrincipal);
+ // cache origin attributes so we can use them off main thread
+ mOriginAttributes = BasePrincipal::Cast(mPrincipal)->OriginAttributesRef();
+ MOZ_ASSERT(!mScope.IsEmpty());
+ MOZ_ASSERT(!mScriptSpec.IsEmpty());
+ MOZ_ASSERT(!mCacheName.IsEmpty());
+}
+
+ServiceWorkerInfo::~ServiceWorkerInfo()
+{
+ MOZ_ASSERT(mServiceWorkerPrivate);
+ mServiceWorkerPrivate->NoteDeadServiceWorkerInfo();
+}
+
+static uint64_t gServiceWorkerInfoCurrentID = 0;
+
+uint64_t
+ServiceWorkerInfo::GetNextID() const
+{
+ return ++gServiceWorkerInfoCurrentID;
+}
+
+already_AddRefed<ServiceWorker>
+ServiceWorkerInfo::GetOrCreateInstance(nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aWindow);
+
+ RefPtr<ServiceWorker> ref;
+
+ for (uint32_t i = 0; i < mInstances.Length(); ++i) {
+ MOZ_ASSERT(mInstances[i]);
+ if (mInstances[i]->GetOwner() == aWindow) {
+ ref = mInstances[i];
+ break;
+ }
+ }
+
+ if (!ref) {
+ ref = new ServiceWorker(aWindow, this);
+ }
+
+ return ref.forget();
+}
+
+END_WORKERS_NAMESPACE
diff --git a/dom/workers/ServiceWorkerInfo.h b/dom/workers/ServiceWorkerInfo.h
new file mode 100644
index 000000000..80910bdad
--- /dev/null
+++ b/dom/workers/ServiceWorkerInfo.h
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_serviceworkerinfo_h
+#define mozilla_dom_workers_serviceworkerinfo_h
+
+#include "mozilla/dom/ServiceWorkerBinding.h" // For ServiceWorkerState
+#include "nsIServiceWorkerManager.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+class ServiceWorker;
+class ServiceWorkerPrivate;
+
+/*
+ * Wherever the spec treats a worker instance and a description of said worker
+ * as the same thing; i.e. "Resolve foo with
+ * _GetNewestWorker(serviceWorkerRegistration)", we represent the description
+ * by this class and spawn a ServiceWorker in the right global when required.
+ */
+class ServiceWorkerInfo final : public nsIServiceWorkerInfo
+{
+private:
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ const nsCString mScope;
+ const nsCString mScriptSpec;
+ const nsString mCacheName;
+ ServiceWorkerState mState;
+ PrincipalOriginAttributes mOriginAttributes;
+
+ // This id is shared with WorkerPrivate to match requests issued by service
+ // workers to their corresponding serviceWorkerInfo.
+ uint64_t mServiceWorkerID;
+
+ // We hold rawptrs since the ServiceWorker constructor and destructor ensure
+ // addition and removal.
+ // There is a high chance of there being at least one ServiceWorker
+ // associated with this all the time.
+ AutoTArray<ServiceWorker*, 1> mInstances;
+
+ RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
+ bool mSkipWaitingFlag;
+
+ ~ServiceWorkerInfo();
+
+ // Generates a unique id for the service worker, with zero being treated as
+ // invalid.
+ uint64_t
+ GetNextID() const;
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISERVICEWORKERINFO
+
+ class ServiceWorkerPrivate*
+ WorkerPrivate() const
+ {
+ MOZ_ASSERT(mServiceWorkerPrivate);
+ return mServiceWorkerPrivate;
+ }
+
+ nsIPrincipal*
+ GetPrincipal() const
+ {
+ return mPrincipal;
+ }
+
+ const nsCString&
+ ScriptSpec() const
+ {
+ return mScriptSpec;
+ }
+
+ const nsCString&
+ Scope() const
+ {
+ return mScope;
+ }
+
+ bool SkipWaitingFlag() const
+ {
+ AssertIsOnMainThread();
+ return mSkipWaitingFlag;
+ }
+
+ void SetSkipWaitingFlag()
+ {
+ AssertIsOnMainThread();
+ mSkipWaitingFlag = true;
+ }
+
+ ServiceWorkerInfo(nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ const nsACString& aScriptSpec,
+ const nsAString& aCacheName);
+
+ ServiceWorkerState
+ State() const
+ {
+ return mState;
+ }
+
+ const PrincipalOriginAttributes&
+ GetOriginAttributes() const
+ {
+ return mOriginAttributes;
+ }
+
+ const nsString&
+ CacheName() const
+ {
+ return mCacheName;
+ }
+
+ uint64_t
+ ID() const
+ {
+ return mServiceWorkerID;
+ }
+
+ void
+ UpdateState(ServiceWorkerState aState);
+
+ // Only used to set initial state when loading from disk!
+ void
+ SetActivateStateUncheckedWithoutEvent(ServiceWorkerState aState)
+ {
+ AssertIsOnMainThread();
+ mState = aState;
+ }
+
+ void
+ AppendWorker(ServiceWorker* aWorker);
+
+ void
+ RemoveWorker(ServiceWorker* aWorker);
+
+ already_AddRefed<ServiceWorker>
+ GetOrCreateInstance(nsPIDOMWindowInner* aWindow);
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkerinfo_h
diff --git a/dom/workers/ServiceWorkerJob.cpp b/dom/workers/ServiceWorkerJob.cpp
new file mode 100644
index 000000000..3d0a8e2cd
--- /dev/null
+++ b/dom/workers/ServiceWorkerJob.cpp
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerJob.h"
+
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "Workers.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+ServiceWorkerJob::Type
+ServiceWorkerJob::GetType() const
+{
+ return mType;
+}
+
+ServiceWorkerJob::State
+ServiceWorkerJob::GetState() const
+{
+ return mState;
+}
+
+bool
+ServiceWorkerJob::Canceled() const
+{
+ return mCanceled;
+}
+
+bool
+ServiceWorkerJob::ResultCallbacksInvoked() const
+{
+ return mResultCallbacksInvoked;
+}
+
+bool
+ServiceWorkerJob::IsEquivalentTo(ServiceWorkerJob* aJob) const
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aJob);
+ return mType == aJob->mType &&
+ mScope.Equals(aJob->mScope) &&
+ mScriptSpec.Equals(aJob->mScriptSpec) &&
+ mPrincipal->Equals(aJob->mPrincipal);
+}
+
+void
+ServiceWorkerJob::AppendResultCallback(Callback* aCallback)
+{
+ AssertIsOnMainThread();
+ MOZ_DIAGNOSTIC_ASSERT(mState != State::Finished);
+ MOZ_DIAGNOSTIC_ASSERT(aCallback);
+ MOZ_DIAGNOSTIC_ASSERT(mFinalCallback != aCallback);
+ MOZ_ASSERT(!mResultCallbackList.Contains(aCallback));
+ MOZ_DIAGNOSTIC_ASSERT(!mResultCallbacksInvoked);
+ mResultCallbackList.AppendElement(aCallback);
+}
+
+void
+ServiceWorkerJob::StealResultCallbacksFrom(ServiceWorkerJob* aJob)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aJob);
+ MOZ_ASSERT(aJob->mState == State::Initial);
+
+ // Take the callbacks from the other job immediately to avoid the
+ // any possibility of them existing on both jobs at once.
+ nsTArray<RefPtr<Callback>> callbackList;
+ callbackList.SwapElements(aJob->mResultCallbackList);
+
+ for (RefPtr<Callback>& callback : callbackList) {
+ // Use AppendResultCallback() so that assertion checking is performed on
+ // each callback.
+ AppendResultCallback(callback);
+ }
+}
+
+void
+ServiceWorkerJob::Start(Callback* aFinalCallback)
+{
+ AssertIsOnMainThread();
+ MOZ_DIAGNOSTIC_ASSERT(!mCanceled);
+
+ MOZ_DIAGNOSTIC_ASSERT(aFinalCallback);
+ MOZ_DIAGNOSTIC_ASSERT(!mFinalCallback);
+ MOZ_ASSERT(!mResultCallbackList.Contains(aFinalCallback));
+ mFinalCallback = aFinalCallback;
+
+ MOZ_DIAGNOSTIC_ASSERT(mState == State::Initial);
+ mState = State::Started;
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod(this, &ServiceWorkerJob::AsyncExecute);
+
+ // We may have to wait for the PBackground actor to be initialized
+ // before proceeding. We should always be able to get a ServiceWorkerManager,
+ // however, since Start() should not be called during shutdown.
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ // browser shutdown
+ return;
+ }
+ if (!swm->HasBackgroundActor()) {
+ // waiting to initialize
+ swm->AppendPendingOperation(runnable);
+ return;
+ }
+
+ // Otherwise start asynchronously. We should never run a job synchronously.
+ MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+ NS_DispatchToMainThread(runnable.forget())));
+}
+
+void
+ServiceWorkerJob::Cancel()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mCanceled);
+ mCanceled = true;
+}
+
+ServiceWorkerJob::ServiceWorkerJob(Type aType,
+ nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ const nsACString& aScriptSpec)
+ : mType(aType)
+ , mPrincipal(aPrincipal)
+ , mScope(aScope)
+ , mScriptSpec(aScriptSpec)
+ , mState(State::Initial)
+ , mCanceled(false)
+ , mResultCallbacksInvoked(false)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mPrincipal);
+ MOZ_ASSERT(!mScope.IsEmpty());
+ // Some job types may have an empty script spec
+}
+
+ServiceWorkerJob::~ServiceWorkerJob()
+{
+ AssertIsOnMainThread();
+ // Jobs must finish or never be started. Destroying an actively running
+ // job is an error.
+ MOZ_ASSERT(mState != State::Started);
+ MOZ_ASSERT_IF(mState == State::Finished, mResultCallbacksInvoked);
+}
+
+void
+ServiceWorkerJob::InvokeResultCallbacks(ErrorResult& aRv)
+{
+ AssertIsOnMainThread();
+ MOZ_DIAGNOSTIC_ASSERT(mState == State::Started);
+
+ MOZ_DIAGNOSTIC_ASSERT(!mResultCallbacksInvoked);
+ mResultCallbacksInvoked = true;
+
+ nsTArray<RefPtr<Callback>> callbackList;
+ callbackList.SwapElements(mResultCallbackList);
+
+ for (RefPtr<Callback>& callback : callbackList) {
+ // The callback might consume an exception on the ErrorResult, so we need
+ // to clone in order to maintain the error for the next callback.
+ ErrorResult rv;
+ aRv.CloneTo(rv);
+
+ callback->JobFinished(this, rv);
+
+ // The callback might not consume the error.
+ rv.SuppressException();
+ }
+}
+
+void
+ServiceWorkerJob::InvokeResultCallbacks(nsresult aRv)
+{
+ ErrorResult converted(aRv);
+ InvokeResultCallbacks(converted);
+}
+
+void
+ServiceWorkerJob::Finish(ErrorResult& aRv)
+{
+ AssertIsOnMainThread();
+
+ // Avoid double-completion because it can result on operating on cleaned
+ // up data. This should not happen, though, so also assert to try to
+ // narrow down the causes.
+ MOZ_DIAGNOSTIC_ASSERT(mState == State::Started);
+ if (mState != State::Started) {
+ return;
+ }
+
+ // Ensure that we only surface SecurityErr, TypeErr or InvalidStateErr to script.
+ if (aRv.Failed() && !aRv.ErrorCodeIs(NS_ERROR_DOM_SECURITY_ERR) &&
+ !aRv.ErrorCodeIs(NS_ERROR_DOM_TYPE_ERR) &&
+ !aRv.ErrorCodeIs(NS_ERROR_DOM_INVALID_STATE_ERR)) {
+
+ // Remove the old error code so we can replace it with a TypeError.
+ aRv.SuppressException();
+
+ NS_ConvertUTF8toUTF16 scriptSpec(mScriptSpec);
+ NS_ConvertUTF8toUTF16 scope(mScope);
+
+ // Throw the type error with a generic error message.
+ aRv.ThrowTypeError<MSG_SW_INSTALL_ERROR>(scriptSpec, scope);
+ }
+
+ // The final callback may drop the last ref to this object.
+ RefPtr<ServiceWorkerJob> kungFuDeathGrip = this;
+
+ if (!mResultCallbacksInvoked) {
+ InvokeResultCallbacks(aRv);
+ }
+
+ mState = State::Finished;
+
+ MOZ_DIAGNOSTIC_ASSERT(mFinalCallback);
+ if (mFinalCallback) {
+ mFinalCallback->JobFinished(this, aRv);
+ mFinalCallback = nullptr;
+ }
+
+ // The callback might not consume the error.
+ aRv.SuppressException();
+
+ // Async release this object to ensure that our caller methods complete
+ // as well.
+ NS_ReleaseOnMainThread(kungFuDeathGrip.forget(), true /* always proxy */);
+}
+
+void
+ServiceWorkerJob::Finish(nsresult aRv)
+{
+ ErrorResult converted(aRv);
+ Finish(converted);
+}
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/ServiceWorkerJob.h b/dom/workers/ServiceWorkerJob.h
new file mode 100644
index 000000000..56802ed97
--- /dev/null
+++ b/dom/workers/ServiceWorkerJob.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_serviceworkerjob_h
+#define mozilla_dom_workers_serviceworkerjob_h
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsIPrincipal;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+namespace workers {
+
+class ServiceWorkerJob
+{
+public:
+ // Implement this interface to receive notification when a job completes.
+ class Callback
+ {
+ public:
+ // Called once when the job completes. If the job is started, then this
+ // will be called. If a job is never executed due to browser shutdown,
+ // then this method will never be called. This method is always called
+ // on the main thread asynchronously after Start() completes.
+ virtual void JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus) = 0;
+
+ NS_IMETHOD_(MozExternalRefCountType)
+ AddRef(void) = 0;
+
+ NS_IMETHOD_(MozExternalRefCountType)
+ Release(void) = 0;
+ };
+
+ enum class Type
+ {
+ Register,
+ Update,
+ Unregister
+ };
+
+ enum class State
+ {
+ Initial,
+ Started,
+ Finished
+ };
+
+ Type
+ GetType() const;
+
+ State
+ GetState() const;
+
+ // Determine if the job has been canceled. This does not change the
+ // current State, but indicates that the job should progress to Finished
+ // as soon as possible.
+ bool
+ Canceled() const;
+
+ // Determine if the result callbacks have already been called. This is
+ // equivalent to the spec checked to see if the job promise has settled.
+ bool
+ ResultCallbacksInvoked() const;
+
+ bool
+ IsEquivalentTo(ServiceWorkerJob* aJob) const;
+
+ // Add a callback that will be invoked when the job's result is available.
+ // Some job types will invoke this before the job is actually finished.
+ // If an early callback does not occur, then it will be called automatically
+ // when Finish() is called. These callbacks will be invoked while the job
+ // state is Started.
+ void
+ AppendResultCallback(Callback* aCallback);
+
+ // This takes ownership of any result callbacks associated with the given job
+ // and then appends them to this job's callback list.
+ void
+ StealResultCallbacksFrom(ServiceWorkerJob* aJob);
+
+ // Start the job. All work will be performed asynchronously on
+ // the main thread. The Finish() method must be called exactly
+ // once after this point. A final callback must be provided. It
+ // will be invoked after all other callbacks have been processed.
+ void
+ Start(Callback* aFinalCallback);
+
+ // Set an internal flag indicating that a started job should finish as
+ // soon as possible.
+ void
+ Cancel();
+
+protected:
+ ServiceWorkerJob(Type aType,
+ nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ const nsACString& aScriptSpec);
+
+ virtual ~ServiceWorkerJob();
+
+ // Invoke the result callbacks immediately. The job must be in the
+ // Started state. The callbacks are cleared after being invoked,
+ // so subsequent method calls have no effect.
+ void
+ InvokeResultCallbacks(ErrorResult& aRv);
+
+ // Convenience method that converts to ErrorResult and calls real method.
+ void
+ InvokeResultCallbacks(nsresult aRv);
+
+ // Indicate that the job has completed. The must be called exactly
+ // once after Start() has initiated job execution. It may not be
+ // called until Start() has returned.
+ void
+ Finish(ErrorResult& aRv);
+
+ // Convenience method that converts to ErrorResult and calls real method.
+ void
+ Finish(nsresult aRv);
+
+ // Specific job types should define AsyncExecute to begin their work.
+ // All errors and successes must result in Finish() being called.
+ virtual void
+ AsyncExecute() = 0;
+
+ const Type mType;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ const nsCString mScope;
+ const nsCString mScriptSpec;
+
+private:
+ RefPtr<Callback> mFinalCallback;
+ nsTArray<RefPtr<Callback>> mResultCallbackList;
+ State mState;
+ bool mCanceled;
+ bool mResultCallbacksInvoked;
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(ServiceWorkerJob)
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkerjob_h
diff --git a/dom/workers/ServiceWorkerJobQueue.cpp b/dom/workers/ServiceWorkerJobQueue.cpp
new file mode 100644
index 000000000..15a798a4d
--- /dev/null
+++ b/dom/workers/ServiceWorkerJobQueue.cpp
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerJobQueue.h"
+
+#include "ServiceWorkerJob.h"
+#include "Workers.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+class ServiceWorkerJobQueue::Callback final : public ServiceWorkerJob::Callback
+{
+ RefPtr<ServiceWorkerJobQueue> mQueue;
+
+ ~Callback()
+ {
+ }
+
+public:
+ explicit Callback(ServiceWorkerJobQueue* aQueue)
+ : mQueue(aQueue)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mQueue);
+ }
+
+ virtual void
+ JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus) override
+ {
+ AssertIsOnMainThread();
+ mQueue->JobFinished(aJob);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(ServiceWorkerJobQueue::Callback, override)
+};
+
+ServiceWorkerJobQueue::~ServiceWorkerJobQueue()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mJobList.IsEmpty());
+}
+
+void
+ServiceWorkerJobQueue::JobFinished(ServiceWorkerJob* aJob)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aJob);
+
+ // XXX There are some corner cases where jobs can double-complete. Until
+ // we track all these down we do a non-fatal assert in debug builds and
+ // a runtime check to verify the queue is in the correct state.
+ NS_ASSERTION(!mJobList.IsEmpty(),
+ "Job queue should contain the job that just completed.");
+ NS_ASSERTION(mJobList.SafeElementAt(0, nullptr) == aJob,
+ "Job queue should contain the job that just completed.");
+ if (NS_WARN_IF(mJobList.SafeElementAt(0, nullptr) != aJob)) {
+ return;
+ }
+
+ mJobList.RemoveElementAt(0);
+
+ if (mJobList.IsEmpty()) {
+ return;
+ }
+
+ RunJob();
+}
+
+void
+ServiceWorkerJobQueue::RunJob()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mJobList.IsEmpty());
+ MOZ_ASSERT(mJobList[0]->GetState() == ServiceWorkerJob::State::Initial);
+
+ RefPtr<Callback> callback = new Callback(this);
+ mJobList[0]->Start(callback);
+}
+
+ServiceWorkerJobQueue::ServiceWorkerJobQueue()
+{
+ AssertIsOnMainThread();
+}
+
+void
+ServiceWorkerJobQueue::ScheduleJob(ServiceWorkerJob* aJob)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aJob);
+ MOZ_ASSERT(!mJobList.Contains(aJob));
+
+ if (mJobList.IsEmpty()) {
+ mJobList.AppendElement(aJob);
+ RunJob();
+ return;
+ }
+
+ MOZ_ASSERT(mJobList[0]->GetState() == ServiceWorkerJob::State::Started);
+
+ RefPtr<ServiceWorkerJob>& tailJob = mJobList[mJobList.Length() - 1];
+ if (!tailJob->ResultCallbacksInvoked() && aJob->IsEquivalentTo(tailJob)) {
+ tailJob->StealResultCallbacksFrom(aJob);
+ return;
+ }
+
+ mJobList.AppendElement(aJob);
+}
+
+void
+ServiceWorkerJobQueue::CancelAll()
+{
+ AssertIsOnMainThread();
+
+ for (RefPtr<ServiceWorkerJob>& job : mJobList) {
+ job->Cancel();
+ }
+
+ // Remove jobs that are queued but not started since they should never
+ // run after being canceled. This means throwing away all jobs except
+ // for the job at the front of the list.
+ if (!mJobList.IsEmpty()) {
+ MOZ_ASSERT(mJobList[0]->GetState() == ServiceWorkerJob::State::Started);
+ mJobList.TruncateLength(1);
+ }
+}
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/ServiceWorkerJobQueue.h b/dom/workers/ServiceWorkerJobQueue.h
new file mode 100644
index 000000000..2af8682b3
--- /dev/null
+++ b/dom/workers/ServiceWorkerJobQueue.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_serviceworkerjobqueue_h
+#define mozilla_dom_workers_serviceworkerjobqueue_h
+
+#include "mozilla/RefPtr.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+class ServiceWorkerJob;
+
+class ServiceWorkerJobQueue final
+{
+ class Callback;
+
+ nsTArray<RefPtr<ServiceWorkerJob>> mJobList;
+
+ ~ServiceWorkerJobQueue();
+
+ void
+ JobFinished(ServiceWorkerJob* aJob);
+
+ void
+ RunJob();
+
+public:
+ ServiceWorkerJobQueue();
+
+ void
+ ScheduleJob(ServiceWorkerJob* aJob);
+
+ void
+ CancelAll();
+
+ NS_INLINE_DECL_REFCOUNTING(ServiceWorkerJobQueue)
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkerjobqueue_h
diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
new file mode 100644
index 000000000..a66df0731
--- /dev/null
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -0,0 +1,3969 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerManager.h"
+
+#include "nsAutoPtr.h"
+#include "nsIConsoleService.h"
+#include "nsIDOMEventTarget.h"
+#include "nsIDocument.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIStreamLoader.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIMutableArray.h"
+#include "nsIScriptError.h"
+#include "nsISimpleEnumerator.h"
+#include "nsITimer.h"
+#include "nsIUploadChannel2.h"
+#include "nsPIDOMWindow.h"
+#include "nsScriptLoader.h"
+#include "nsServiceManagerUtils.h"
+#include "nsDebug.h"
+#include "nsISupportsPrimitives.h"
+
+#include "jsapi.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/LoadContext.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/DOMError.h"
+#include "mozilla/dom/ErrorEvent.h"
+#include "mozilla/dom/Headers.h"
+#include "mozilla/dom/InternalHeaders.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/NotificationEvent.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/Request.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/Unused.h"
+#include "mozilla/EnumSet.h"
+
+#include "nsContentPolicyUtils.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "nsQueryObject.h"
+#include "nsTArray.h"
+
+#include "RuntimeService.h"
+#include "ServiceWorker.h"
+#include "ServiceWorkerClient.h"
+#include "ServiceWorkerContainer.h"
+#include "ServiceWorkerInfo.h"
+#include "ServiceWorkerJobQueue.h"
+#include "ServiceWorkerManagerChild.h"
+#include "ServiceWorkerPrivate.h"
+#include "ServiceWorkerRegisterJob.h"
+#include "ServiceWorkerRegistrar.h"
+#include "ServiceWorkerRegistration.h"
+#include "ServiceWorkerScriptCache.h"
+#include "ServiceWorkerEvents.h"
+#include "ServiceWorkerUnregisterJob.h"
+#include "ServiceWorkerUpdateJob.h"
+#include "SharedWorker.h"
+#include "WorkerInlines.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "WorkerScope.h"
+
+#ifdef PostMessage
+#undef PostMessage
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+BEGIN_WORKERS_NAMESPACE
+
+#define PURGE_DOMAIN_DATA "browser:purge-domain-data"
+#define PURGE_SESSION_HISTORY "browser:purge-session-history"
+#define CLEAR_ORIGIN_DATA "clear-origin-attributes-data"
+
+static_assert(nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN == static_cast<uint32_t>(RequestMode::Same_origin),
+ "RequestMode enumeration value should match Necko CORS mode value.");
+static_assert(nsIHttpChannelInternal::CORS_MODE_NO_CORS == static_cast<uint32_t>(RequestMode::No_cors),
+ "RequestMode enumeration value should match Necko CORS mode value.");
+static_assert(nsIHttpChannelInternal::CORS_MODE_CORS == static_cast<uint32_t>(RequestMode::Cors),
+ "RequestMode enumeration value should match Necko CORS mode value.");
+static_assert(nsIHttpChannelInternal::CORS_MODE_NAVIGATE == static_cast<uint32_t>(RequestMode::Navigate),
+ "RequestMode enumeration value should match Necko CORS mode value.");
+
+static_assert(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW == static_cast<uint32_t>(RequestRedirect::Follow),
+ "RequestRedirect enumeration value should make Necko Redirect mode value.");
+static_assert(nsIHttpChannelInternal::REDIRECT_MODE_ERROR == static_cast<uint32_t>(RequestRedirect::Error),
+ "RequestRedirect enumeration value should make Necko Redirect mode value.");
+static_assert(nsIHttpChannelInternal::REDIRECT_MODE_MANUAL == static_cast<uint32_t>(RequestRedirect::Manual),
+ "RequestRedirect enumeration value should make Necko Redirect mode value.");
+static_assert(3 == static_cast<uint32_t>(RequestRedirect::EndGuard_),
+ "RequestRedirect enumeration value should make Necko Redirect mode value.");
+
+static_assert(nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT == static_cast<uint32_t>(RequestCache::Default),
+ "RequestCache enumeration value should match Necko Cache mode value.");
+static_assert(nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE == static_cast<uint32_t>(RequestCache::No_store),
+ "RequestCache enumeration value should match Necko Cache mode value.");
+static_assert(nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD == static_cast<uint32_t>(RequestCache::Reload),
+ "RequestCache enumeration value should match Necko Cache mode value.");
+static_assert(nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE == static_cast<uint32_t>(RequestCache::No_cache),
+ "RequestCache enumeration value should match Necko Cache mode value.");
+static_assert(nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE == static_cast<uint32_t>(RequestCache::Force_cache),
+ "RequestCache enumeration value should match Necko Cache mode value.");
+static_assert(nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED == static_cast<uint32_t>(RequestCache::Only_if_cached),
+ "RequestCache enumeration value should match Necko Cache mode value.");
+static_assert(6 == static_cast<uint32_t>(RequestCache::EndGuard_),
+ "RequestCache enumeration value should match Necko Cache mode value.");
+
+static StaticRefPtr<ServiceWorkerManager> gInstance;
+
+struct ServiceWorkerManager::RegistrationDataPerPrincipal final
+{
+ // Ordered list of scopes for glob matching.
+ // Each entry is an absolute URL representing the scope.
+ // Each value of the hash table is an array of an absolute URLs representing
+ // the scopes.
+ //
+ // An array is used for now since the number of controlled scopes per
+ // domain is expected to be relatively low. If that assumption was proved
+ // wrong this should be replaced with a better structure to avoid the
+ // memmoves associated with inserting stuff in the middle of the array.
+ nsTArray<nsCString> mOrderedScopes;
+
+ // Scope to registration.
+ // The scope should be a fully qualified valid URL.
+ nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerRegistrationInfo> mInfos;
+
+ // Maps scopes to job queues.
+ nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerJobQueue> mJobQueues;
+
+ // Map scopes to scheduled update timers.
+ nsInterfaceHashtable<nsCStringHashKey, nsITimer> mUpdateTimers;
+};
+
+namespace {
+
+nsresult
+PopulateRegistrationData(nsIPrincipal* aPrincipal,
+ const ServiceWorkerRegistrationInfo* aRegistration,
+ ServiceWorkerRegistrationData& aData)
+{
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aRegistration);
+
+ if (NS_WARN_IF(!BasePrincipal::Cast(aPrincipal)->IsCodebasePrincipal())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &aData.principal());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aData.scope() = aRegistration->mScope;
+
+ RefPtr<ServiceWorkerInfo> newest = aRegistration->Newest();
+ if (NS_WARN_IF(!newest)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aRegistration->GetActive()) {
+ aData.currentWorkerURL() = aRegistration->GetActive()->ScriptSpec();
+ aData.cacheName() = aRegistration->GetActive()->CacheName();
+ }
+
+ return NS_OK;
+}
+
+class TeardownRunnable final : public Runnable
+{
+public:
+ explicit TeardownRunnable(ServiceWorkerManagerChild* aActor)
+ : mActor(aActor)
+ {
+ MOZ_ASSERT(mActor);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(mActor);
+ mActor->SendShutdown();
+ return NS_OK;
+ }
+
+private:
+ ~TeardownRunnable() {}
+
+ RefPtr<ServiceWorkerManagerChild> mActor;
+};
+
+} // namespace
+
+//////////////////////////
+// ServiceWorkerManager //
+//////////////////////////
+
+NS_IMPL_ADDREF(ServiceWorkerManager)
+NS_IMPL_RELEASE(ServiceWorkerManager)
+
+NS_INTERFACE_MAP_BEGIN(ServiceWorkerManager)
+ NS_INTERFACE_MAP_ENTRY(nsIServiceWorkerManager)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIServiceWorkerManager)
+NS_INTERFACE_MAP_END
+
+ServiceWorkerManager::ServiceWorkerManager()
+ : mActor(nullptr)
+ , mShuttingDown(false)
+{
+}
+
+ServiceWorkerManager::~ServiceWorkerManager()
+{
+ // The map will assert if it is not empty when destroyed.
+ mRegistrationInfos.Clear();
+ MOZ_ASSERT(!mActor);
+}
+
+void
+ServiceWorkerManager::Init(ServiceWorkerRegistrar* aRegistrar)
+{
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ DebugOnly<nsresult> rv;
+ rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false /* ownsWeak */);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ if (XRE_IsParentProcess()) {
+ MOZ_DIAGNOSTIC_ASSERT(aRegistrar);
+
+ nsTArray<ServiceWorkerRegistrationData> data;
+ aRegistrar->GetRegistrations(data);
+ LoadRegistrations(data);
+
+ if (obs) {
+ DebugOnly<nsresult> rv;
+ rv = obs->AddObserver(this, PURGE_SESSION_HISTORY, false /* ownsWeak */);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = obs->AddObserver(this, PURGE_DOMAIN_DATA, false /* ownsWeak */);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = obs->AddObserver(this, CLEAR_ORIGIN_DATA, false /* ownsWeak */);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ if (!BackgroundChild::GetOrCreateForCurrentThread(this)) {
+ // Make sure to do this last as our failure cleanup expects Init() to have
+ // executed.
+ ActorFailed();
+ }
+}
+
+void
+ServiceWorkerManager::MaybeStartShutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mShuttingDown) {
+ return;
+ }
+
+ mShuttingDown = true;
+
+ for (auto it1 = mRegistrationInfos.Iter(); !it1.Done(); it1.Next()) {
+ for (auto it2 = it1.UserData()->mUpdateTimers.Iter(); !it2.Done(); it2.Next()) {
+ nsCOMPtr<nsITimer> timer = it2.UserData();
+ timer->Cancel();
+ }
+ it1.UserData()->mUpdateTimers.Clear();
+
+ for (auto it2 = it1.UserData()->mJobQueues.Iter(); !it2.Done(); it2.Next()) {
+ RefPtr<ServiceWorkerJobQueue> queue = it2.UserData();
+ queue->CancelAll();
+ }
+ it1.UserData()->mJobQueues.Clear();
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+
+ if (XRE_IsParentProcess()) {
+ obs->RemoveObserver(this, PURGE_SESSION_HISTORY);
+ obs->RemoveObserver(this, PURGE_DOMAIN_DATA);
+ obs->RemoveObserver(this, CLEAR_ORIGIN_DATA);
+ }
+ }
+
+ mPendingOperations.Clear();
+
+ if (!mActor) {
+ return;
+ }
+
+ mActor->ManagerShuttingDown();
+
+ RefPtr<TeardownRunnable> runnable = new TeardownRunnable(mActor);
+ nsresult rv = NS_DispatchToMainThread(runnable);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ mActor = nullptr;
+}
+
+class ServiceWorkerResolveWindowPromiseOnRegisterCallback final : public ServiceWorkerJob::Callback
+{
+ RefPtr<nsPIDOMWindowInner> mWindow;
+ // The promise "returned" by the call to Update up to
+ // navigator.serviceWorker.register().
+ RefPtr<Promise> mPromise;
+
+ ~ServiceWorkerResolveWindowPromiseOnRegisterCallback()
+ {}
+
+ virtual void
+ JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus) override
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aJob);
+
+ if (aStatus.Failed()) {
+ mPromise->MaybeReject(aStatus);
+ return;
+ }
+
+ MOZ_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Register);
+ RefPtr<ServiceWorkerRegisterJob> registerJob =
+ static_cast<ServiceWorkerRegisterJob*>(aJob);
+ RefPtr<ServiceWorkerRegistrationInfo> reg = registerJob->GetRegistration();
+
+ RefPtr<ServiceWorkerRegistration> swr =
+ mWindow->GetServiceWorkerRegistration(NS_ConvertUTF8toUTF16(reg->mScope));
+ mPromise->MaybeResolve(swr);
+ }
+
+public:
+ ServiceWorkerResolveWindowPromiseOnRegisterCallback(nsPIDOMWindowInner* aWindow,
+ Promise* aPromise)
+ : mWindow(aWindow)
+ , mPromise(aPromise)
+ {}
+
+ NS_INLINE_DECL_REFCOUNTING(ServiceWorkerResolveWindowPromiseOnRegisterCallback, override)
+};
+
+namespace {
+
+class PropagateSoftUpdateRunnable final : public Runnable
+{
+public:
+ PropagateSoftUpdateRunnable(const PrincipalOriginAttributes& aOriginAttributes,
+ const nsAString& aScope)
+ : mOriginAttributes(aOriginAttributes)
+ , mScope(aScope)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->PropagateSoftUpdate(mOriginAttributes, mScope);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ ~PropagateSoftUpdateRunnable()
+ {}
+
+ const PrincipalOriginAttributes mOriginAttributes;
+ const nsString mScope;
+};
+
+class PropagateUnregisterRunnable final : public Runnable
+{
+public:
+ PropagateUnregisterRunnable(nsIPrincipal* aPrincipal,
+ nsIServiceWorkerUnregisterCallback* aCallback,
+ const nsAString& aScope)
+ : mPrincipal(aPrincipal)
+ , mCallback(aCallback)
+ , mScope(aScope)
+ {
+ MOZ_ASSERT(aPrincipal);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->PropagateUnregister(mPrincipal, mCallback, mScope);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ ~PropagateUnregisterRunnable()
+ {}
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsIServiceWorkerUnregisterCallback> mCallback;
+ const nsString mScope;
+};
+
+class RemoveRunnable final : public Runnable
+{
+public:
+ explicit RemoveRunnable(const nsACString& aHost)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->Remove(mHost);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ ~RemoveRunnable()
+ {}
+
+ const nsCString mHost;
+};
+
+class PropagateRemoveRunnable final : public Runnable
+{
+public:
+ explicit PropagateRemoveRunnable(const nsACString& aHost)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->PropagateRemove(mHost);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ ~PropagateRemoveRunnable()
+ {}
+
+ const nsCString mHost;
+};
+
+class PropagateRemoveAllRunnable final : public Runnable
+{
+public:
+ PropagateRemoveAllRunnable()
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->PropagateRemoveAll();
+ }
+
+ return NS_OK;
+ }
+
+private:
+ ~PropagateRemoveAllRunnable()
+ {}
+};
+
+} // namespace
+
+// This function implements parts of the step 3 of the following algorithm:
+// https://w3c.github.io/webappsec/specs/powerfulfeatures/#settings-secure
+static bool
+IsFromAuthenticatedOrigin(nsIDocument* aDoc)
+{
+ MOZ_ASSERT(aDoc);
+ nsCOMPtr<nsIDocument> doc(aDoc);
+ nsCOMPtr<nsIContentSecurityManager> csm = do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID);
+ if (NS_WARN_IF(!csm)) {
+ return false;
+ }
+
+ while (doc && !nsContentUtils::IsChromeDoc(doc)) {
+ bool trustworthyOrigin = false;
+
+ // The origin of the document may be different from the document URI
+ // itself. Check the principal, not the document URI itself.
+ nsCOMPtr<nsIPrincipal> documentPrincipal = doc->NodePrincipal();
+
+ // The check for IsChromeDoc() above should mean we never see a system
+ // principal inside the loop.
+ MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(documentPrincipal));
+
+ csm->IsOriginPotentiallyTrustworthy(documentPrincipal, &trustworthyOrigin);
+ if (!trustworthyOrigin) {
+ return false;
+ }
+
+ doc = doc->GetParentDocument();
+ }
+ return true;
+}
+
+// If we return an error code here, the ServiceWorkerContainer will
+// automatically reject the Promise.
+NS_IMETHODIMP
+ServiceWorkerManager::Register(mozIDOMWindow* aWindow,
+ nsIURI* aScopeURI,
+ nsIURI* aScriptURI,
+ nsISupports** aPromise)
+{
+ AssertIsOnMainThread();
+
+ if (NS_WARN_IF(!aWindow)) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ auto* window = nsPIDOMWindowInner::From(aWindow);
+ nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Don't allow service workers to register when the *document* is chrome.
+ if (NS_WARN_IF(nsContentUtils::IsSystemPrincipal(doc->NodePrincipal()))) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> outerWindow = window->GetOuterWindow();
+ bool serviceWorkersTestingEnabled =
+ outerWindow->GetServiceWorkersTestingEnabled();
+
+ bool authenticatedOrigin;
+ if (Preferences::GetBool("dom.serviceWorkers.testing.enabled") ||
+ serviceWorkersTestingEnabled) {
+ authenticatedOrigin = true;
+ } else {
+ authenticatedOrigin = IsFromAuthenticatedOrigin(doc);
+ }
+
+ if (!authenticatedOrigin) {
+ NS_WARNING("ServiceWorker registration from insecure websites is not allowed.");
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // Data URLs are not allowed.
+ nsCOMPtr<nsIPrincipal> documentPrincipal = doc->NodePrincipal();
+
+ nsresult rv = documentPrincipal->CheckMayLoad(aScriptURI, true /* report */,
+ false /* allowIfInheritsPrincipal */);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // Check content policy.
+ int16_t decision = nsIContentPolicy::ACCEPT;
+ rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER,
+ aScriptURI,
+ documentPrincipal,
+ doc,
+ EmptyCString(),
+ nullptr,
+ &decision);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (NS_WARN_IF(decision != nsIContentPolicy::ACCEPT)) {
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+
+
+ rv = documentPrincipal->CheckMayLoad(aScopeURI, true /* report */,
+ false /* allowIfInheritsPrinciple */);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // The IsOriginPotentiallyTrustworthy() check allows file:// and possibly other
+ // URI schemes. We need to explicitly only allows http and https schemes.
+ // Note, we just use the aScriptURI here for the check since its already
+ // been verified as same origin with the document principal. This also
+ // is a good block against accidentally allowing blob: script URIs which
+ // might inherit the origin.
+ bool isHttp = false;
+ bool isHttps = false;
+ aScriptURI->SchemeIs("http", &isHttp);
+ aScriptURI->SchemeIs("https", &isHttps);
+ if (NS_WARN_IF(!isHttp && !isHttps)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsCString cleanedScope;
+ rv = aScopeURI->GetSpecIgnoringRef(cleanedScope);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString spec;
+ rv = aScriptURI->GetSpecIgnoringRef(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(sgo, result);
+ if (result.Failed()) {
+ return result.StealNSResult();
+ }
+
+ nsAutoCString scopeKey;
+ rv = PrincipalToScopeKey(documentPrincipal, scopeKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ AddRegisteringDocument(cleanedScope, doc);
+
+ RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey,
+ cleanedScope);
+
+ RefPtr<ServiceWorkerResolveWindowPromiseOnRegisterCallback> cb =
+ new ServiceWorkerResolveWindowPromiseOnRegisterCallback(window, promise);
+
+ nsCOMPtr<nsILoadGroup> docLoadGroup = doc->GetDocumentLoadGroup();
+ RefPtr<WorkerLoadInfo::InterfaceRequestor> ir =
+ new WorkerLoadInfo::InterfaceRequestor(documentPrincipal, docLoadGroup);
+ ir->MaybeAddTabChild(docLoadGroup);
+
+ // Create a load group that is separate from, yet related to, the document's load group.
+ // This allows checks for interfaces like nsILoadContext to yield the values used by the
+ // the document, yet will not cancel the update job if the document's load group is cancelled.
+ nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+ MOZ_ALWAYS_SUCCEEDS(loadGroup->SetNotificationCallbacks(ir));
+
+ RefPtr<ServiceWorkerRegisterJob> job =
+ new ServiceWorkerRegisterJob(documentPrincipal, cleanedScope, spec,
+ loadGroup);
+ job->AppendResultCallback(cb);
+ queue->ScheduleJob(job);
+
+ AssertIsOnMainThread();
+ Telemetry::Accumulate(Telemetry::SERVICE_WORKER_REGISTRATIONS, 1);
+
+ promise.forget(aPromise);
+ return NS_OK;
+}
+
+void
+ServiceWorkerManager::AppendPendingOperation(nsIRunnable* aRunnable)
+{
+ MOZ_ASSERT(!mActor);
+ MOZ_ASSERT(aRunnable);
+
+ if (!mShuttingDown) {
+ mPendingOperations.AppendElement(aRunnable);
+ }
+}
+
+/*
+ * Implements the async aspects of the getRegistrations algorithm.
+ */
+class GetRegistrationsRunnable final : public Runnable
+{
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<Promise> mPromise;
+public:
+ GetRegistrationsRunnable(nsPIDOMWindowInner* aWindow, Promise* aPromise)
+ : mWindow(aWindow), mPromise(aPromise)
+ {}
+
+ NS_IMETHOD
+ Run() override
+ {
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return NS_OK;
+ }
+
+ nsIDocument* doc = mWindow->GetExtantDoc();
+ if (!doc) {
+ mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> docURI = doc->GetDocumentURI();
+ if (!docURI) {
+ mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+ if (!principal) {
+ mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return NS_OK;
+ }
+
+ nsTArray<RefPtr<ServiceWorkerRegistration>> array;
+
+ if (NS_WARN_IF(!BasePrincipal::Cast(principal)->IsCodebasePrincipal())) {
+ return NS_OK;
+ }
+
+ nsAutoCString scopeKey;
+ nsresult rv = swm->PrincipalToScopeKey(principal, scopeKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ ServiceWorkerManager::RegistrationDataPerPrincipal* data;
+ if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
+ mPromise->MaybeResolve(array);
+ return NS_OK;
+ }
+
+ for (uint32_t i = 0; i < data->mOrderedScopes.Length(); ++i) {
+ RefPtr<ServiceWorkerRegistrationInfo> info =
+ data->mInfos.GetWeak(data->mOrderedScopes[i]);
+ if (info->mPendingUninstall) {
+ continue;
+ }
+
+ NS_ConvertUTF8toUTF16 scope(data->mOrderedScopes[i]);
+
+ nsCOMPtr<nsIURI> scopeURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), scope, nullptr, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mPromise->MaybeReject(rv);
+ break;
+ }
+
+ rv = principal->CheckMayLoad(scopeURI, true /* report */,
+ false /* allowIfInheritsPrincipal */);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ RefPtr<ServiceWorkerRegistration> swr =
+ mWindow->GetServiceWorkerRegistration(scope);
+
+ array.AppendElement(swr);
+ }
+
+ mPromise->MaybeResolve(array);
+ return NS_OK;
+ }
+};
+
+// If we return an error code here, the ServiceWorkerContainer will
+// automatically reject the Promise.
+NS_IMETHODIMP
+ServiceWorkerManager::GetRegistrations(mozIDOMWindow* aWindow,
+ nsISupports** aPromise)
+{
+ AssertIsOnMainThread();
+
+ if (NS_WARN_IF(!aWindow)) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ auto* window = nsPIDOMWindowInner::From(aWindow);
+ nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ // Don't allow service workers to register when the *document* is chrome for
+ // now.
+ MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(doc->NodePrincipal()));
+
+ nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(sgo, result);
+ if (result.Failed()) {
+ return result.StealNSResult();
+ }
+
+ nsCOMPtr<nsIRunnable> runnable =
+ new GetRegistrationsRunnable(window, promise);
+ promise.forget(aPromise);
+ return NS_DispatchToCurrentThread(runnable);
+}
+
+/*
+ * Implements the async aspects of the getRegistration algorithm.
+ */
+class GetRegistrationRunnable final : public Runnable
+{
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<Promise> mPromise;
+ nsString mDocumentURL;
+
+public:
+ GetRegistrationRunnable(nsPIDOMWindowInner* aWindow, Promise* aPromise,
+ const nsAString& aDocumentURL)
+ : mWindow(aWindow), mPromise(aPromise), mDocumentURL(aDocumentURL)
+ {}
+
+ NS_IMETHOD
+ Run() override
+ {
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return NS_OK;
+ }
+
+ nsIDocument* doc = mWindow->GetExtantDoc();
+ if (!doc) {
+ mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> docURI = doc->GetDocumentURI();
+ if (!docURI) {
+ mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), mDocumentURL, nullptr, docURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mPromise->MaybeReject(rv);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+ if (!principal) {
+ mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return NS_OK;
+ }
+
+ rv = principal->CheckMayLoad(uri, true /* report */,
+ false /* allowIfInheritsPrinciple */);
+ if (NS_FAILED(rv)) {
+ mPromise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return NS_OK;
+ }
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ swm->GetServiceWorkerRegistrationInfo(principal, uri);
+
+ if (!registration) {
+ mPromise->MaybeResolveWithUndefined();
+ return NS_OK;
+ }
+
+ NS_ConvertUTF8toUTF16 scope(registration->mScope);
+ RefPtr<ServiceWorkerRegistration> swr =
+ mWindow->GetServiceWorkerRegistration(scope);
+ mPromise->MaybeResolve(swr);
+
+ return NS_OK;
+ }
+};
+
+// If we return an error code here, the ServiceWorkerContainer will
+// automatically reject the Promise.
+NS_IMETHODIMP
+ServiceWorkerManager::GetRegistration(mozIDOMWindow* aWindow,
+ const nsAString& aDocumentURL,
+ nsISupports** aPromise)
+{
+ AssertIsOnMainThread();
+
+ if (NS_WARN_IF(!aWindow)) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ auto* window = nsPIDOMWindowInner::From(aWindow);
+ nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ // Don't allow service workers to register when the *document* is chrome for
+ // now.
+ MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(doc->NodePrincipal()));
+
+ nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(sgo, result);
+ if (result.Failed()) {
+ return result.StealNSResult();
+ }
+
+ nsCOMPtr<nsIRunnable> runnable =
+ new GetRegistrationRunnable(window, promise, aDocumentURL);
+ promise.forget(aPromise);
+ return NS_DispatchToCurrentThread(runnable);
+}
+
+class GetReadyPromiseRunnable final : public Runnable
+{
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<Promise> mPromise;
+
+public:
+ GetReadyPromiseRunnable(nsPIDOMWindowInner* aWindow, Promise* aPromise)
+ : mWindow(aWindow), mPromise(aPromise)
+ {}
+
+ NS_IMETHOD
+ Run() override
+ {
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return NS_OK;
+ }
+
+ nsIDocument* doc = mWindow->GetExtantDoc();
+ if (!doc) {
+ mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> docURI = doc->GetDocumentURI();
+ if (!docURI) {
+ mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return NS_OK;
+ }
+
+ if (!swm->CheckReadyPromise(mWindow, docURI, mPromise)) {
+ swm->StorePendingReadyPromise(mWindow, docURI, mPromise);
+ }
+
+ return NS_OK;
+ }
+};
+
+NS_IMETHODIMP
+ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes,
+ const nsACString& aScope,
+ uint32_t aDataLength,
+ uint8_t* aDataBytes,
+ uint8_t optional_argc)
+{
+ if (optional_argc == 2) {
+ nsTArray<uint8_t> data;
+ if (!data.InsertElementsAt(0, aDataBytes, aDataLength, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return SendPushEvent(aOriginAttributes, aScope, EmptyString(), Some(data));
+ }
+ MOZ_ASSERT(optional_argc == 0);
+ return SendPushEvent(aOriginAttributes, aScope, EmptyString(), Nothing());
+}
+
+nsresult
+ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes,
+ const nsACString& aScope,
+ const nsAString& aMessageId,
+ const Maybe<nsTArray<uint8_t>>& aData)
+{
+ PrincipalOriginAttributes attrs;
+ if (!attrs.PopulateFromSuffix(aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ ServiceWorkerInfo* serviceWorker = GetActiveWorkerInfoForScope(attrs, aScope);
+ if (NS_WARN_IF(!serviceWorker)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ GetRegistration(serviceWorker->GetPrincipal(), aScope);
+ MOZ_DIAGNOSTIC_ASSERT(registration);
+
+ return serviceWorker->WorkerPrivate()->SendPushEvent(aMessageId, aData,
+ registration);
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::SendPushSubscriptionChangeEvent(const nsACString& aOriginAttributes,
+ const nsACString& aScope)
+{
+ PrincipalOriginAttributes attrs;
+ if (!attrs.PopulateFromSuffix(aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ ServiceWorkerInfo* info = GetActiveWorkerInfoForScope(attrs, aScope);
+ if (!info) {
+ return NS_ERROR_FAILURE;
+ }
+ return info->WorkerPrivate()->SendPushSubscriptionChangeEvent();
+}
+
+nsresult
+ServiceWorkerManager::SendNotificationEvent(const nsAString& aEventName,
+ const nsACString& aOriginSuffix,
+ const nsACString& aScope,
+ const nsAString& aID,
+ const nsAString& aTitle,
+ const nsAString& aDir,
+ const nsAString& aLang,
+ const nsAString& aBody,
+ const nsAString& aTag,
+ const nsAString& aIcon,
+ const nsAString& aData,
+ const nsAString& aBehavior)
+{
+ PrincipalOriginAttributes attrs;
+ if (!attrs.PopulateFromSuffix(aOriginSuffix)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ ServiceWorkerInfo* info = GetActiveWorkerInfoForScope(attrs, aScope);
+ if (!info) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ServiceWorkerPrivate* workerPrivate = info->WorkerPrivate();
+ return workerPrivate->SendNotificationEvent(aEventName, aID, aTitle, aDir,
+ aLang, aBody, aTag,
+ aIcon, aData, aBehavior,
+ NS_ConvertUTF8toUTF16(aScope));
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::SendNotificationClickEvent(const nsACString& aOriginSuffix,
+ const nsACString& aScope,
+ const nsAString& aID,
+ const nsAString& aTitle,
+ const nsAString& aDir,
+ const nsAString& aLang,
+ const nsAString& aBody,
+ const nsAString& aTag,
+ const nsAString& aIcon,
+ const nsAString& aData,
+ const nsAString& aBehavior)
+{
+ return SendNotificationEvent(NS_LITERAL_STRING(NOTIFICATION_CLICK_EVENT_NAME),
+ aOriginSuffix, aScope, aID, aTitle, aDir, aLang,
+ aBody, aTag, aIcon, aData, aBehavior);
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::SendNotificationCloseEvent(const nsACString& aOriginSuffix,
+ const nsACString& aScope,
+ const nsAString& aID,
+ const nsAString& aTitle,
+ const nsAString& aDir,
+ const nsAString& aLang,
+ const nsAString& aBody,
+ const nsAString& aTag,
+ const nsAString& aIcon,
+ const nsAString& aData,
+ const nsAString& aBehavior)
+{
+ return SendNotificationEvent(NS_LITERAL_STRING(NOTIFICATION_CLOSE_EVENT_NAME),
+ aOriginSuffix, aScope, aID, aTitle, aDir, aLang,
+ aBody, aTag, aIcon, aData, aBehavior);
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::GetReadyPromise(mozIDOMWindow* aWindow,
+ nsISupports** aPromise)
+{
+ AssertIsOnMainThread();
+
+ if (NS_WARN_IF(!aWindow)) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ auto* window = nsPIDOMWindowInner::From(aWindow);
+
+ nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Don't allow service workers to register when the *document* is chrome for
+ // now.
+ MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(doc->NodePrincipal()));
+
+ MOZ_ASSERT(!mPendingReadyPromises.Contains(window));
+
+ nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(sgo, result);
+ if (result.Failed()) {
+ return result.StealNSResult();
+ }
+
+ nsCOMPtr<nsIRunnable> runnable =
+ new GetReadyPromiseRunnable(window, promise);
+ promise.forget(aPromise);
+ return NS_DispatchToCurrentThread(runnable);
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::RemoveReadyPromise(mozIDOMWindow* aWindow)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aWindow);
+
+ if (!aWindow) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mPendingReadyPromises.Remove(aWindow);
+ return NS_OK;
+}
+
+void
+ServiceWorkerManager::StorePendingReadyPromise(nsPIDOMWindowInner* aWindow,
+ nsIURI* aURI,
+ Promise* aPromise)
+{
+ PendingReadyPromise* data;
+
+ // We should not have 2 pending promises for the same window.
+ MOZ_ASSERT(!mPendingReadyPromises.Get(aWindow, &data));
+
+ data = new PendingReadyPromise(aURI, aPromise);
+ mPendingReadyPromises.Put(aWindow, data);
+}
+
+void
+ServiceWorkerManager::CheckPendingReadyPromises()
+{
+ for (auto iter = mPendingReadyPromises.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(iter.Key());
+ MOZ_ASSERT(window);
+
+ nsAutoPtr<PendingReadyPromise>& pendingReadyPromise = iter.Data();
+ if (CheckReadyPromise(window, pendingReadyPromise->mURI,
+ pendingReadyPromise->mPromise)) {
+ iter.Remove();
+ }
+ }
+}
+
+bool
+ServiceWorkerManager::CheckReadyPromise(nsPIDOMWindowInner* aWindow,
+ nsIURI* aURI, Promise* aPromise)
+{
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aURI);
+
+ nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
+ MOZ_ASSERT(doc);
+
+ nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+ MOZ_ASSERT(principal);
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ GetServiceWorkerRegistrationInfo(principal, aURI);
+
+ if (registration && registration->GetActive()) {
+ NS_ConvertUTF8toUTF16 scope(registration->mScope);
+ RefPtr<ServiceWorkerRegistration> swr =
+ aWindow->GetServiceWorkerRegistration(scope);
+ aPromise->MaybeResolve(swr);
+ return true;
+ }
+
+ return false;
+}
+
+ServiceWorkerInfo*
+ServiceWorkerManager::GetActiveWorkerInfoForScope(const PrincipalOriginAttributes& aOriginAttributes,
+ const nsACString& aScope)
+{
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIURI> scopeURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, nullptr);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateCodebasePrincipal(scopeURI, aOriginAttributes);
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ GetServiceWorkerRegistrationInfo(principal, scopeURI);
+ if (!registration) {
+ return nullptr;
+ }
+
+ return registration->GetActive();
+}
+
+ServiceWorkerInfo*
+ServiceWorkerManager::GetActiveWorkerInfoForDocument(nsIDocument* aDocument)
+{
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration;
+ GetDocumentRegistration(aDocument, getter_AddRefs(registration));
+
+ if (!registration) {
+ return nullptr;
+ }
+
+ return registration->GetActive();
+}
+
+namespace {
+
+class UnregisterJobCallback final : public ServiceWorkerJob::Callback
+{
+ nsCOMPtr<nsIServiceWorkerUnregisterCallback> mCallback;
+
+ ~UnregisterJobCallback()
+ {
+ }
+
+public:
+ explicit UnregisterJobCallback(nsIServiceWorkerUnregisterCallback* aCallback)
+ : mCallback(aCallback)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mCallback);
+ }
+
+ void
+ JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aJob);
+
+ if (aStatus.Failed()) {
+ mCallback->UnregisterFailed();
+ return;
+ }
+
+ MOZ_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Unregister);
+ RefPtr<ServiceWorkerUnregisterJob> unregisterJob =
+ static_cast<ServiceWorkerUnregisterJob*>(aJob);
+ mCallback->UnregisterSucceeded(unregisterJob->GetResult());
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(UnregisterJobCallback)
+};
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+ServiceWorkerManager::Unregister(nsIPrincipal* aPrincipal,
+ nsIServiceWorkerUnregisterCallback* aCallback,
+ const nsAString& aScope)
+{
+ AssertIsOnMainThread();
+
+ if (!aPrincipal) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+
+// This is not accessible by content, and callers should always ensure scope is
+// a correct URI, so this is wrapped in DEBUG
+#ifdef DEBUG
+ nsCOMPtr<nsIURI> scopeURI;
+ rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+#endif
+
+ nsAutoCString scopeKey;
+ rv = PrincipalToScopeKey(aPrincipal, scopeKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ NS_ConvertUTF16toUTF8 scope(aScope);
+ RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey, scope);
+
+ RefPtr<ServiceWorkerUnregisterJob> job =
+ new ServiceWorkerUnregisterJob(aPrincipal, scope, true /* send to parent */);
+
+ if (aCallback) {
+ RefPtr<UnregisterJobCallback> cb = new UnregisterJobCallback(aCallback);
+ job->AppendResultCallback(cb);
+ }
+
+ queue->ScheduleJob(job);
+ return NS_OK;
+}
+
+nsresult
+ServiceWorkerManager::NotifyUnregister(nsIPrincipal* aPrincipal,
+ const nsAString& aScope)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+
+ nsresult rv;
+
+// This is not accessible by content, and callers should always ensure scope is
+// a correct URI, so this is wrapped in DEBUG
+#ifdef DEBUG
+ nsCOMPtr<nsIURI> scopeURI;
+ rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+#endif
+
+ nsAutoCString scopeKey;
+ rv = PrincipalToScopeKey(aPrincipal, scopeKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ NS_ConvertUTF16toUTF8 scope(aScope);
+ RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey, scope);
+
+ RefPtr<ServiceWorkerUnregisterJob> job =
+ new ServiceWorkerUnregisterJob(aPrincipal, scope,
+ false /* send to parent */);
+
+ queue->ScheduleJob(job);
+ return NS_OK;
+}
+
+void
+ServiceWorkerManager::WorkerIsIdle(ServiceWorkerInfo* aWorker)
+{
+ AssertIsOnMainThread();
+ MOZ_DIAGNOSTIC_ASSERT(aWorker);
+
+ RefPtr<ServiceWorkerRegistrationInfo> reg =
+ GetRegistration(aWorker->GetPrincipal(), aWorker->Scope());
+ if (!reg) {
+ return;
+ }
+
+ if (reg->GetActive() != aWorker) {
+ return;
+ }
+
+ if (!reg->IsControllingDocuments() && reg->mPendingUninstall) {
+ RemoveRegistration(reg);
+ return;
+ }
+
+ reg->TryToActivateAsync();
+}
+
+already_AddRefed<ServiceWorkerJobQueue>
+ServiceWorkerManager::GetOrCreateJobQueue(const nsACString& aKey,
+ const nsACString& aScope)
+{
+ MOZ_ASSERT(!aKey.IsEmpty());
+ ServiceWorkerManager::RegistrationDataPerPrincipal* data;
+ if (!mRegistrationInfos.Get(aKey, &data)) {
+ data = new RegistrationDataPerPrincipal();
+ mRegistrationInfos.Put(aKey, data);
+ }
+
+ RefPtr<ServiceWorkerJobQueue> queue;
+ if (!data->mJobQueues.Get(aScope, getter_AddRefs(queue))) {
+ RefPtr<ServiceWorkerJobQueue> newQueue = new ServiceWorkerJobQueue();
+ queue = newQueue;
+ data->mJobQueues.Put(aScope, newQueue.forget());
+ }
+
+ return queue.forget();
+}
+
+/* static */
+already_AddRefed<ServiceWorkerManager>
+ServiceWorkerManager::GetInstance()
+{
+ // Note: We don't simply check gInstance for null-ness here, since otherwise
+ // this can resurrect the ServiceWorkerManager pretty late during shutdown.
+ static bool firstTime = true;
+ if (firstTime) {
+ RefPtr<ServiceWorkerRegistrar> swr;
+
+ // Don't create the ServiceWorkerManager until the ServiceWorkerRegistrar is
+ // initialized.
+ if (XRE_IsParentProcess()) {
+ swr = ServiceWorkerRegistrar::Get();
+ if (!swr) {
+ return nullptr;
+ }
+ }
+
+ firstTime = false;
+
+ AssertIsOnMainThread();
+
+ gInstance = new ServiceWorkerManager();
+ gInstance->Init(swr);
+ ClearOnShutdown(&gInstance);
+ }
+ RefPtr<ServiceWorkerManager> copy = gInstance.get();
+ return copy.forget();
+}
+
+void
+ServiceWorkerManager::FinishFetch(ServiceWorkerRegistrationInfo* aRegistration)
+{
+}
+
+void
+ServiceWorkerManager::ReportToAllClients(const nsCString& aScope,
+ const nsString& aMessage,
+ const nsString& aFilename,
+ const nsString& aLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aFlags)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv;
+
+ if (!aFilename.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(uri), aFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ }
+
+ AutoTArray<uint64_t, 16> windows;
+
+ // Report errors to every controlled document.
+ for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
+ ServiceWorkerRegistrationInfo* reg = iter.UserData();
+ MOZ_ASSERT(reg);
+ if (!reg->mScope.Equals(aScope)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(iter.Key());
+ if (!doc || !doc->IsCurrentActiveDocument() || !doc->GetWindow()) {
+ continue;
+ }
+
+ windows.AppendElement(doc->InnerWindowID());
+
+ nsContentUtils::ReportToConsoleNonLocalized(aMessage,
+ aFlags,
+ NS_LITERAL_CSTRING("Service Workers"),
+ doc,
+ uri,
+ aLine,
+ aLineNumber,
+ aColumnNumber,
+ nsContentUtils::eOMIT_LOCATION);
+ }
+
+ // Report to any documents that have called .register() for this scope. They
+ // may not be controlled, but will still want to see error reports.
+ WeakDocumentList* regList = mRegisteringDocuments.Get(aScope);
+ if (regList) {
+ for (int32_t i = regList->Length() - 1; i >= 0; --i) {
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(regList->ElementAt(i));
+ if (!doc) {
+ regList->RemoveElementAt(i);
+ continue;
+ }
+
+ if (!doc->IsCurrentActiveDocument()) {
+ continue;
+ }
+
+ uint64_t innerWindowId = doc->InnerWindowID();
+ if (windows.Contains(innerWindowId)) {
+ continue;
+ }
+
+ windows.AppendElement(innerWindowId);
+
+ nsContentUtils::ReportToConsoleNonLocalized(aMessage,
+ aFlags,
+ NS_LITERAL_CSTRING("Service Workers"),
+ doc,
+ uri,
+ aLine,
+ aLineNumber,
+ aColumnNumber,
+ nsContentUtils::eOMIT_LOCATION);
+ }
+
+ if (regList->IsEmpty()) {
+ regList = nullptr;
+ nsAutoPtr<WeakDocumentList> doomed;
+ mRegisteringDocuments.RemoveAndForget(aScope, doomed);
+ }
+ }
+
+ InterceptionList* intList = mNavigationInterceptions.Get(aScope);
+ if (intList) {
+ nsIConsoleService* consoleService = nullptr;
+ for (uint32_t i = 0; i < intList->Length(); ++i) {
+ nsCOMPtr<nsIInterceptedChannel> channel = intList->ElementAt(i);
+
+ nsCOMPtr<nsIChannel> inner;
+ rv = channel->GetChannel(getter_AddRefs(inner));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ uint64_t innerWindowId = nsContentUtils::GetInnerWindowID(inner);
+ if (innerWindowId == 0 || windows.Contains(innerWindowId)) {
+ continue;
+ }
+
+ windows.AppendElement(innerWindowId);
+
+ // Unfortunately the nsContentUtils helpers don't provide a convenient
+ // way to log to a window ID without a document. Use console service
+ // directly.
+ nsCOMPtr<nsIScriptError> errorObject =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ rv = errorObject->InitWithWindowID(aMessage,
+ aFilename,
+ aLine,
+ aLineNumber,
+ aColumnNumber,
+ aFlags,
+ NS_LITERAL_CSTRING("Service Workers"),
+ innerWindowId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ if (!consoleService) {
+ rv = CallGetService(NS_CONSOLESERVICE_CONTRACTID, &consoleService);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ }
+
+ consoleService->LogMessage(errorObject);
+ }
+ }
+
+ // If there are no documents to report to, at least report something to the
+ // browser console.
+ if (windows.IsEmpty()) {
+ nsContentUtils::ReportToConsoleNonLocalized(aMessage,
+ aFlags,
+ NS_LITERAL_CSTRING("Service Workers"),
+ nullptr, // document
+ uri,
+ aLine,
+ aLineNumber,
+ aColumnNumber,
+ nsContentUtils::eOMIT_LOCATION);
+ return;
+ }
+}
+
+/* static */
+void
+ServiceWorkerManager::LocalizeAndReportToAllClients(
+ const nsCString& aScope,
+ const char* aStringKey,
+ const nsTArray<nsString>& aParamArray,
+ uint32_t aFlags,
+ const nsString& aFilename,
+ const nsString& aLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber)
+{
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ return;
+ }
+
+ nsresult rv;
+ nsXPIDLString message;
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ aStringKey, aParamArray, message);
+ if (NS_SUCCEEDED(rv)) {
+ swm->ReportToAllClients(aScope, message,
+ aFilename, aLine, aLineNumber, aColumnNumber,
+ aFlags);
+ } else {
+ NS_WARNING("Failed to format and therefore report localized error.");
+ }
+}
+
+void
+ServiceWorkerManager::FlushReportsToAllClients(const nsACString& aScope,
+ nsIConsoleReportCollector* aReporter)
+{
+ AutoTArray<uint64_t, 16> windows;
+
+ // Report errors to every controlled document.
+ for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
+ ServiceWorkerRegistrationInfo* reg = iter.UserData();
+ MOZ_ASSERT(reg);
+ if (!reg->mScope.Equals(aScope)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(iter.Key());
+ if (!doc || !doc->IsCurrentActiveDocument() || !doc->GetWindow()) {
+ continue;
+ }
+
+ windows.AppendElement(doc->InnerWindowID());
+
+ aReporter->FlushConsoleReports(doc,
+ nsIConsoleReportCollector::ReportAction::Save);
+ }
+
+ // Report to any documents that have called .register() for this scope. They
+ // may not be controlled, but will still want to see error reports.
+ WeakDocumentList* regList = mRegisteringDocuments.Get(aScope);
+ if (regList) {
+ for (int32_t i = regList->Length() - 1; i >= 0; --i) {
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(regList->ElementAt(i));
+ if (!doc) {
+ regList->RemoveElementAt(i);
+ continue;
+ }
+
+ if (!doc->IsCurrentActiveDocument()) {
+ continue;
+ }
+
+ uint64_t innerWindowId = doc->InnerWindowID();
+ if (windows.Contains(innerWindowId)) {
+ continue;
+ }
+
+ windows.AppendElement(innerWindowId);
+
+ aReporter->FlushConsoleReports(doc,
+ nsIConsoleReportCollector::ReportAction::Save);
+ }
+
+ if (regList->IsEmpty()) {
+ regList = nullptr;
+ nsAutoPtr<WeakDocumentList> doomed;
+ mRegisteringDocuments.RemoveAndForget(aScope, doomed);
+ }
+ }
+
+ nsresult rv;
+ InterceptionList* intList = mNavigationInterceptions.Get(aScope);
+ if (intList) {
+ for (uint32_t i = 0; i < intList->Length(); ++i) {
+ nsCOMPtr<nsIInterceptedChannel> channel = intList->ElementAt(i);
+
+ nsCOMPtr<nsIChannel> inner;
+ rv = channel->GetChannel(getter_AddRefs(inner));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ uint64_t innerWindowId = nsContentUtils::GetInnerWindowID(inner);
+ if (innerWindowId == 0 || windows.Contains(innerWindowId)) {
+ continue;
+ }
+
+ windows.AppendElement(innerWindowId);
+
+ aReporter->FlushReportsByWindowId(innerWindowId,
+ nsIConsoleReportCollector::ReportAction::Save);
+ }
+ }
+
+ // If there are no documents to report to, at least report something to the
+ // browser console.
+ if (windows.IsEmpty()) {
+ aReporter->FlushConsoleReports((nsIDocument*)nullptr);
+ return;
+ }
+
+ aReporter->ClearConsoleReports();
+}
+
+void
+ServiceWorkerManager::HandleError(JSContext* aCx,
+ nsIPrincipal* aPrincipal,
+ const nsCString& aScope,
+ const nsString& aWorkerURL,
+ const nsString& aMessage,
+ const nsString& aFilename,
+ const nsString& aLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aFlags,
+ JSExnType aExnType)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+
+ nsAutoCString scopeKey;
+ nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ ServiceWorkerManager::RegistrationDataPerPrincipal* data;
+ if (NS_WARN_IF(!mRegistrationInfos.Get(scopeKey, &data))) {
+ return;
+ }
+
+ // Always report any uncaught exceptions or errors to the console of
+ // each client.
+ ReportToAllClients(aScope, aMessage, aFilename, aLine, aLineNumber,
+ aColumnNumber, aFlags);
+}
+
+void
+ServiceWorkerManager::LoadRegistration(
+ const ServiceWorkerRegistrationData& aRegistration)
+{
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIPrincipal> principal =
+ PrincipalInfoToPrincipal(aRegistration.principal());
+ if (!principal) {
+ return;
+ }
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ GetRegistration(principal, aRegistration.scope());
+ if (!registration) {
+ registration = CreateNewRegistration(aRegistration.scope(), principal);
+ } else {
+ // If active worker script matches our expectations for a "current worker",
+ // then we are done.
+ if (registration->GetActive() &&
+ registration->GetActive()->ScriptSpec() == aRegistration.currentWorkerURL()) {
+ // No needs for updates.
+ return;
+ }
+ }
+
+ const nsCString& currentWorkerURL = aRegistration.currentWorkerURL();
+ if (!currentWorkerURL.IsEmpty()) {
+ registration->SetActive(
+ new ServiceWorkerInfo(registration->mPrincipal, registration->mScope,
+ currentWorkerURL, aRegistration.cacheName()));
+ registration->GetActive()->SetActivateStateUncheckedWithoutEvent(ServiceWorkerState::Activated);
+ }
+}
+
+void
+ServiceWorkerManager::LoadRegistrations(
+ const nsTArray<ServiceWorkerRegistrationData>& aRegistrations)
+{
+ AssertIsOnMainThread();
+
+ for (uint32_t i = 0, len = aRegistrations.Length(); i < len; ++i) {
+ LoadRegistration(aRegistrations[i]);
+ }
+}
+
+void
+ServiceWorkerManager::ActorFailed()
+{
+ MOZ_DIAGNOSTIC_ASSERT(!mActor);
+ MaybeStartShutdown();
+}
+
+void
+ServiceWorkerManager::ActorCreated(mozilla::ipc::PBackgroundChild* aActor)
+{
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(!mActor);
+
+ if (mShuttingDown) {
+ MOZ_DIAGNOSTIC_ASSERT(mPendingOperations.IsEmpty());
+ return;
+ }
+
+ PServiceWorkerManagerChild* actor =
+ aActor->SendPServiceWorkerManagerConstructor();
+ if (!actor) {
+ ActorFailed();
+ return;
+ }
+
+ mActor = static_cast<ServiceWorkerManagerChild*>(actor);
+
+ // Flush the pending requests.
+ for (uint32_t i = 0, len = mPendingOperations.Length(); i < len; ++i) {
+ MOZ_ASSERT(mPendingOperations[i]);
+ nsresult rv = NS_DispatchToCurrentThread(mPendingOperations[i].forget());
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch a runnable.");
+ }
+ }
+
+ mPendingOperations.Clear();
+}
+
+void
+ServiceWorkerManager::StoreRegistration(
+ nsIPrincipal* aPrincipal,
+ ServiceWorkerRegistrationInfo* aRegistration)
+{
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aRegistration);
+
+ if (mShuttingDown) {
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mActor);
+ if (!mActor) {
+ return;
+ }
+
+ ServiceWorkerRegistrationData data;
+ nsresult rv = PopulateRegistrationData(aPrincipal, aRegistration, data);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ PrincipalInfo principalInfo;
+ if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(aPrincipal,
+ &principalInfo)))) {
+ return;
+ }
+
+ mActor->SendRegister(data);
+}
+
+already_AddRefed<ServiceWorkerRegistrationInfo>
+ServiceWorkerManager::GetServiceWorkerRegistrationInfo(nsPIDOMWindowInner* aWindow)
+{
+ MOZ_ASSERT(aWindow);
+ nsCOMPtr<nsIDocument> document = aWindow->GetExtantDoc();
+ return GetServiceWorkerRegistrationInfo(document);
+}
+
+already_AddRefed<ServiceWorkerRegistrationInfo>
+ServiceWorkerManager::GetServiceWorkerRegistrationInfo(nsIDocument* aDoc)
+{
+ MOZ_ASSERT(aDoc);
+ nsCOMPtr<nsIURI> documentURI = aDoc->GetDocumentURI();
+ nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
+ return GetServiceWorkerRegistrationInfo(principal, documentURI);
+}
+
+already_AddRefed<ServiceWorkerRegistrationInfo>
+ServiceWorkerManager::GetServiceWorkerRegistrationInfo(nsIPrincipal* aPrincipal,
+ nsIURI* aURI)
+{
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aURI);
+
+ //XXXnsm Temporary fix until Bug 1171432 is fixed.
+ if (NS_WARN_IF(BasePrincipal::Cast(aPrincipal)->AppId() == nsIScriptSecurityManager::UNKNOWN_APP_ID)) {
+ return nullptr;
+ }
+
+ nsAutoCString scopeKey;
+ nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return GetServiceWorkerRegistrationInfo(scopeKey, aURI);
+}
+
+already_AddRefed<ServiceWorkerRegistrationInfo>
+ServiceWorkerManager::GetServiceWorkerRegistrationInfo(const nsACString& aScopeKey,
+ nsIURI* aURI)
+{
+ MOZ_ASSERT(aURI);
+
+ nsAutoCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ nsAutoCString scope;
+ RegistrationDataPerPrincipal* data;
+ if (!FindScopeForPath(aScopeKey, spec, &data, scope)) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(data);
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration;
+ data->mInfos.Get(scope, getter_AddRefs(registration));
+ // ordered scopes and registrations better be in sync.
+ MOZ_ASSERT(registration);
+
+#ifdef DEBUG
+ nsAutoCString origin;
+ rv = registration->mPrincipal->GetOrigin(origin);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(origin.Equals(aScopeKey));
+#endif
+
+ if (registration->mPendingUninstall) {
+ return nullptr;
+ }
+ return registration.forget();
+}
+
+/* static */ nsresult
+ServiceWorkerManager::PrincipalToScopeKey(nsIPrincipal* aPrincipal,
+ nsACString& aKey)
+{
+ MOZ_ASSERT(aPrincipal);
+
+ if (!BasePrincipal::Cast(aPrincipal)->IsCodebasePrincipal()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = aPrincipal->GetOrigin(aKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+/* static */ void
+ServiceWorkerManager::AddScopeAndRegistration(const nsACString& aScope,
+ ServiceWorkerRegistrationInfo* aInfo)
+{
+ MOZ_ASSERT(aInfo);
+ MOZ_ASSERT(aInfo->mPrincipal);
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ // browser shutdown
+ return;
+ }
+
+ nsAutoCString scopeKey;
+ nsresult rv = swm->PrincipalToScopeKey(aInfo->mPrincipal, scopeKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ MOZ_ASSERT(!scopeKey.IsEmpty());
+
+ RegistrationDataPerPrincipal* data;
+ if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
+ data = new RegistrationDataPerPrincipal();
+ swm->mRegistrationInfos.Put(scopeKey, data);
+ }
+
+ for (uint32_t i = 0; i < data->mOrderedScopes.Length(); ++i) {
+ const nsCString& current = data->mOrderedScopes[i];
+
+ // Perfect match!
+ if (aScope.Equals(current)) {
+ data->mInfos.Put(aScope, aInfo);
+ swm->NotifyListenersOnRegister(aInfo);
+ return;
+ }
+
+ // Sort by length, with longest match first.
+ // /foo/bar should be before /foo/
+ // Similarly /foo/b is between the two.
+ if (StringBeginsWith(aScope, current)) {
+ data->mOrderedScopes.InsertElementAt(i, aScope);
+ data->mInfos.Put(aScope, aInfo);
+ swm->NotifyListenersOnRegister(aInfo);
+ return;
+ }
+ }
+
+ data->mOrderedScopes.AppendElement(aScope);
+ data->mInfos.Put(aScope, aInfo);
+ swm->NotifyListenersOnRegister(aInfo);
+}
+
+/* static */ bool
+ServiceWorkerManager::FindScopeForPath(const nsACString& aScopeKey,
+ const nsACString& aPath,
+ RegistrationDataPerPrincipal** aData,
+ nsACString& aMatch)
+{
+ MOZ_ASSERT(aData);
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+
+ if (!swm || !swm->mRegistrationInfos.Get(aScopeKey, aData)) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < (*aData)->mOrderedScopes.Length(); ++i) {
+ const nsCString& current = (*aData)->mOrderedScopes[i];
+ if (StringBeginsWith(aPath, current)) {
+ aMatch = current;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* static */ bool
+ServiceWorkerManager::HasScope(nsIPrincipal* aPrincipal,
+ const nsACString& aScope)
+{
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ return false;
+ }
+
+ nsAutoCString scopeKey;
+ nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ RegistrationDataPerPrincipal* data;
+ if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
+ return false;
+ }
+
+ return data->mOrderedScopes.Contains(aScope);
+}
+
+/* static */ void
+ServiceWorkerManager::RemoveScopeAndRegistration(ServiceWorkerRegistrationInfo* aRegistration)
+{
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ return;
+ }
+
+ nsAutoCString scopeKey;
+ nsresult rv = swm->PrincipalToScopeKey(aRegistration->mPrincipal, scopeKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ RegistrationDataPerPrincipal* data;
+ if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
+ return;
+ }
+
+ nsCOMPtr<nsITimer> timer = data->mUpdateTimers.Get(aRegistration->mScope);
+ if (timer) {
+ timer->Cancel();
+ data->mUpdateTimers.Remove(aRegistration->mScope);
+ }
+
+ // The registration should generally only be removed if there are no controlled
+ // documents, but mControlledDocuments can contain references to potentially
+ // controlled docs. This happens when the service worker is not active yet.
+ // We must purge these references since we are evicting the registration.
+ for (auto iter = swm->mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
+ ServiceWorkerRegistrationInfo* reg = iter.UserData();
+ MOZ_ASSERT(reg);
+ if (reg->mScope.Equals(aRegistration->mScope)) {
+ iter.Remove();
+ }
+ }
+
+ RefPtr<ServiceWorkerRegistrationInfo> info;
+ data->mInfos.Get(aRegistration->mScope, getter_AddRefs(info));
+
+ data->mInfos.Remove(aRegistration->mScope);
+ data->mOrderedScopes.RemoveElement(aRegistration->mScope);
+ swm->NotifyListenersOnUnregister(info);
+
+ swm->MaybeRemoveRegistrationInfo(scopeKey);
+ swm->NotifyServiceWorkerRegistrationRemoved(aRegistration);
+}
+
+void
+ServiceWorkerManager::MaybeRemoveRegistrationInfo(const nsACString& aScopeKey)
+{
+ RegistrationDataPerPrincipal* data;
+ if (!mRegistrationInfos.Get(aScopeKey, &data)) {
+ return;
+ }
+
+ if (data->mOrderedScopes.IsEmpty() && data->mJobQueues.Count() == 0) {
+ mRegistrationInfos.Remove(aScopeKey);
+ }
+}
+
+void
+ServiceWorkerManager::MaybeStartControlling(nsIDocument* aDoc,
+ const nsAString& aDocumentId)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aDoc);
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ GetServiceWorkerRegistrationInfo(aDoc);
+ if (registration) {
+ MOZ_ASSERT(!mControlledDocuments.Contains(aDoc));
+ StartControllingADocument(registration, aDoc, aDocumentId);
+ }
+}
+
+void
+ServiceWorkerManager::MaybeStopControlling(nsIDocument* aDoc)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aDoc);
+ RefPtr<ServiceWorkerRegistrationInfo> registration;
+ mControlledDocuments.Remove(aDoc, getter_AddRefs(registration));
+ // A document which was uncontrolled does not maintain that state itself, so
+ // it will always call MaybeStopControlling() even if there isn't an
+ // associated registration. So this check is required.
+ if (registration) {
+ StopControllingADocument(registration);
+ }
+}
+
+void
+ServiceWorkerManager::MaybeCheckNavigationUpdate(nsIDocument* aDoc)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aDoc);
+ // We perform these success path navigation update steps when the
+ // document tells us its more or less done loading. This avoids
+ // slowing down page load and also lets pages consistently get
+ // updatefound events when they fire.
+ //
+ // 9.8.20 If respondWithEntered is false, then:
+ // 9.8.22 Else: (respondWith was entered and succeeded)
+ // If request is a non-subresource request, then: Invoke Soft Update
+ // algorithm.
+ RefPtr<ServiceWorkerRegistrationInfo> registration;
+ mControlledDocuments.Get(aDoc, getter_AddRefs(registration));
+ if (registration) {
+ registration->MaybeScheduleUpdate();
+ }
+}
+
+void
+ServiceWorkerManager::StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
+ nsIDocument* aDoc,
+ const nsAString& aDocumentId)
+{
+ MOZ_ASSERT(aRegistration);
+ MOZ_ASSERT(aDoc);
+
+ aRegistration->StartControllingADocument();
+ mControlledDocuments.Put(aDoc, aRegistration);
+ if (!aDocumentId.IsEmpty()) {
+ aDoc->SetId(aDocumentId);
+ }
+ Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
+}
+
+void
+ServiceWorkerManager::StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration)
+{
+ aRegistration->StopControllingADocument();
+ if (aRegistration->IsControllingDocuments() || !aRegistration->IsIdle()) {
+ return;
+ }
+
+ if (aRegistration->mPendingUninstall) {
+ RemoveRegistration(aRegistration);
+ return;
+ }
+
+ // We use to aggressively terminate the worker at this point, but it
+ // caused problems. There are more uses for a service worker than actively
+ // controlled documents. We need to let the worker naturally terminate
+ // in case its handling push events, message events, etc.
+ aRegistration->TryToActivateAsync();
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::GetScopeForUrl(nsIPrincipal* aPrincipal,
+ const nsAString& aUrl, nsAString& aScope)
+{
+ MOZ_ASSERT(aPrincipal);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aUrl, nullptr, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<ServiceWorkerRegistrationInfo> r =
+ GetServiceWorkerRegistrationInfo(aPrincipal, uri);
+ if (!r) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aScope = NS_ConvertUTF8toUTF16(r->mScope);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::AddRegistrationEventListener(const nsAString& aScope,
+ ServiceWorkerRegistrationListener* aListener)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aListener);
+#ifdef DEBUG
+ // Ensure a registration is only listening for it's own scope.
+ nsAutoString regScope;
+ aListener->GetScope(regScope);
+ MOZ_ASSERT(!regScope.IsEmpty());
+ MOZ_ASSERT(aScope.Equals(regScope));
+#endif
+
+ MOZ_ASSERT(!mServiceWorkerRegistrationListeners.Contains(aListener));
+ mServiceWorkerRegistrationListeners.AppendElement(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::RemoveRegistrationEventListener(const nsAString& aScope,
+ ServiceWorkerRegistrationListener* aListener)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aListener);
+#ifdef DEBUG
+ // Ensure a registration is unregistering for it's own scope.
+ nsAutoString regScope;
+ aListener->GetScope(regScope);
+ MOZ_ASSERT(!regScope.IsEmpty());
+ MOZ_ASSERT(aScope.Equals(regScope));
+#endif
+
+ MOZ_ASSERT(mServiceWorkerRegistrationListeners.Contains(aListener));
+ mServiceWorkerRegistrationListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+void
+ServiceWorkerManager::FireUpdateFoundOnServiceWorkerRegistrations(
+ ServiceWorkerRegistrationInfo* aRegistration)
+{
+ AssertIsOnMainThread();
+
+ nsTObserverArray<ServiceWorkerRegistrationListener*>::ForwardIterator it(mServiceWorkerRegistrationListeners);
+ while (it.HasMore()) {
+ RefPtr<ServiceWorkerRegistrationListener> target = it.GetNext();
+ nsAutoString regScope;
+ target->GetScope(regScope);
+ MOZ_ASSERT(!regScope.IsEmpty());
+
+ NS_ConvertUTF16toUTF8 utf8Scope(regScope);
+ if (utf8Scope.Equals(aRegistration->mScope)) {
+ target->UpdateFound();
+ }
+ }
+}
+
+/*
+ * This is used for installing, waiting and active.
+ */
+nsresult
+ServiceWorkerManager::GetServiceWorkerForScope(nsPIDOMWindowInner* aWindow,
+ const nsAString& aScope,
+ WhichServiceWorker aWhichWorker,
+ nsISupports** aServiceWorker)
+{
+ AssertIsOnMainThread();
+
+ if (NS_WARN_IF(!aWindow)) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
+ MOZ_ASSERT(doc);
+
+ ///////////////////////////////////////////
+ // Security check
+ nsAutoCString scope = NS_ConvertUTF16toUTF8(aScope);
+ nsCOMPtr<nsIURI> scopeURI;
+ // We pass nullptr as the base URI since scopes obtained from
+ // ServiceWorkerRegistrations MUST be fully qualified URIs.
+ nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), scope, nullptr, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsCOMPtr<nsIPrincipal> documentPrincipal = doc->NodePrincipal();
+ rv = documentPrincipal->CheckMayLoad(scopeURI, true /* report */,
+ false /* allowIfInheritsPrinciple */);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ ////////////////////////////////////////////
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ GetRegistration(documentPrincipal, scope);
+ if (NS_WARN_IF(!registration)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<ServiceWorkerInfo> info;
+ if (aWhichWorker == WhichServiceWorker::INSTALLING_WORKER) {
+ info = registration->GetInstalling();
+ } else if (aWhichWorker == WhichServiceWorker::WAITING_WORKER) {
+ info = registration->GetWaiting();
+ } else if (aWhichWorker == WhichServiceWorker::ACTIVE_WORKER) {
+ info = registration->GetActive();
+ } else {
+ MOZ_CRASH("Invalid worker type");
+ }
+
+ if (NS_WARN_IF(!info)) {
+ return NS_ERROR_DOM_NOT_FOUND_ERR;
+ }
+
+ RefPtr<ServiceWorker> serviceWorker = info->GetOrCreateInstance(aWindow);
+
+ serviceWorker->SetState(info->State());
+ serviceWorker.forget(aServiceWorker);
+ return NS_OK;
+}
+
+namespace {
+
+class ContinueDispatchFetchEventRunnable : public Runnable
+{
+ RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
+ nsCOMPtr<nsIInterceptedChannel> mChannel;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsString mDocumentId;
+ bool mIsReload;
+public:
+ ContinueDispatchFetchEventRunnable(ServiceWorkerPrivate* aServiceWorkerPrivate,
+ nsIInterceptedChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ const nsAString& aDocumentId,
+ bool aIsReload)
+ : mServiceWorkerPrivate(aServiceWorkerPrivate)
+ , mChannel(aChannel)
+ , mLoadGroup(aLoadGroup)
+ , mDocumentId(aDocumentId)
+ , mIsReload(aIsReload)
+ {
+ MOZ_ASSERT(aServiceWorkerPrivate);
+ MOZ_ASSERT(aChannel);
+ }
+
+ void
+ HandleError()
+ {
+ AssertIsOnMainThread();
+ NS_WARNING("Unexpected error while dispatching fetch event!");
+ DebugOnly<nsresult> rv = mChannel->ResetInterception();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to resume intercepted network request");
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = mChannel->GetChannel(getter_AddRefs(channel));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ HandleError();
+ return NS_OK;
+ }
+
+ // The channel might have encountered an unexpected error while ensuring
+ // the upload stream is cloneable. Check here and reset the interception
+ // if that happens.
+ nsresult status;
+ rv = channel->GetStatus(&status);
+ if (NS_WARN_IF(NS_FAILED(rv) || NS_FAILED(status))) {
+ HandleError();
+ return NS_OK;
+ }
+
+ rv = mServiceWorkerPrivate->SendFetchEvent(mChannel, mLoadGroup,
+ mDocumentId, mIsReload);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ HandleError();
+ }
+
+ return NS_OK;
+ }
+};
+
+} // anonymous namespace
+
+void
+ServiceWorkerManager::DispatchFetchEvent(const PrincipalOriginAttributes& aOriginAttributes,
+ nsIDocument* aDoc,
+ const nsAString& aDocumentIdForTopLevelNavigation,
+ nsIInterceptedChannel* aChannel,
+ bool aIsReload,
+ bool aIsSubresourceLoad,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aChannel);
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerInfo> serviceWorker;
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsAutoString documentId;
+
+ if (aIsSubresourceLoad) {
+ MOZ_ASSERT(aDoc);
+
+ serviceWorker = GetActiveWorkerInfoForDocument(aDoc);
+ if (!serviceWorker) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ loadGroup = aDoc->GetDocumentLoadGroup();
+ nsresult rv = aDoc->GetOrCreateId(documentId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ } else {
+ nsCOMPtr<nsIChannel> internalChannel;
+ aRv = aChannel->GetChannel(getter_AddRefs(internalChannel));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ internalChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ // TODO: Use aDocumentIdForTopLevelNavigation for potentialClientId, pending
+ // the spec change.
+
+ nsCOMPtr<nsIURI> uri;
+ aRv = aChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ // non-subresource request means the URI contains the principal
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateCodebasePrincipal(uri, aOriginAttributes);
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ GetServiceWorkerRegistrationInfo(principal, uri);
+ if (!registration) {
+ NS_WARNING("No registration found when dispatching the fetch event");
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // While we only enter this method if IsAvailable() previously saw
+ // an active worker, it is possible for that worker to be removed
+ // before we get to this point. Therefore we must handle a nullptr
+ // active worker here.
+ serviceWorker = registration->GetActive();
+ if (!serviceWorker) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ AddNavigationInterception(serviceWorker->Scope(), aChannel);
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(serviceWorker);
+
+ nsCOMPtr<nsIRunnable> continueRunnable =
+ new ContinueDispatchFetchEventRunnable(serviceWorker->WorkerPrivate(),
+ aChannel, loadGroup,
+ documentId, aIsReload);
+
+ nsCOMPtr<nsIChannel> innerChannel;
+ aRv = aChannel->GetChannel(getter_AddRefs(innerChannel));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(innerChannel);
+
+ // If there is no upload stream, then continue immediately
+ if (!uploadChannel) {
+ MOZ_ALWAYS_SUCCEEDS(continueRunnable->Run());
+ return;
+ }
+ // Otherwise, ensure the upload stream can be cloned directly. This may
+ // require some async copying, so provide a callback.
+ aRv = uploadChannel->EnsureUploadStreamIsCloneable(continueRunnable);
+}
+
+bool
+ServiceWorkerManager::IsAvailable(nsIPrincipal* aPrincipal,
+ nsIURI* aURI)
+{
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aURI);
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ GetServiceWorkerRegistrationInfo(aPrincipal, aURI);
+ return registration && registration->GetActive();
+}
+
+bool
+ServiceWorkerManager::IsControlled(nsIDocument* aDoc, ErrorResult& aRv)
+{
+ MOZ_ASSERT(aDoc);
+
+ if (nsContentUtils::IsInPrivateBrowsing(aDoc)) {
+ // Handle the case where a service worker was previously registered in
+ // a non-private window (bug 1255621).
+ return false;
+ }
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration;
+ nsresult rv = GetDocumentRegistration(aDoc, getter_AddRefs(registration));
+ if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE)) {
+ // It's OK to ignore the case where we don't have a registration.
+ aRv.Throw(rv);
+ return false;
+ }
+
+ return !!registration;
+}
+
+nsresult
+ServiceWorkerManager::GetDocumentRegistration(nsIDocument* aDoc,
+ ServiceWorkerRegistrationInfo** aRegistrationInfo)
+{
+ RefPtr<ServiceWorkerRegistrationInfo> registration;
+ if (!mControlledDocuments.Get(aDoc, getter_AddRefs(registration))) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // If the document is controlled, the current worker MUST be non-null.
+ if (!registration->GetActive()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ registration.forget(aRegistrationInfo);
+ return NS_OK;
+}
+
+/*
+ * The .controller is for the registration associated with the document when
+ * the document was loaded.
+ */
+NS_IMETHODIMP
+ServiceWorkerManager::GetDocumentController(nsPIDOMWindowInner* aWindow,
+ nsISupports** aServiceWorker)
+{
+ if (NS_WARN_IF(!aWindow)) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
+ if (!doc) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration;
+ nsresult rv = GetDocumentRegistration(doc, getter_AddRefs(registration));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(registration->GetActive());
+ RefPtr<ServiceWorker> serviceWorker =
+ registration->GetActive()->GetOrCreateInstance(aWindow);
+
+ serviceWorker.forget(aServiceWorker);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::GetInstalling(nsPIDOMWindowInner* aWindow,
+ const nsAString& aScope,
+ nsISupports** aServiceWorker)
+{
+ return GetServiceWorkerForScope(aWindow, aScope,
+ WhichServiceWorker::INSTALLING_WORKER,
+ aServiceWorker);
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::GetWaiting(nsPIDOMWindowInner* aWindow,
+ const nsAString& aScope,
+ nsISupports** aServiceWorker)
+{
+ return GetServiceWorkerForScope(aWindow, aScope,
+ WhichServiceWorker::WAITING_WORKER,
+ aServiceWorker);
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::GetActive(nsPIDOMWindowInner* aWindow,
+ const nsAString& aScope,
+ nsISupports** aServiceWorker)
+{
+ return GetServiceWorkerForScope(aWindow, aScope,
+ WhichServiceWorker::ACTIVE_WORKER,
+ aServiceWorker);
+}
+
+void
+ServiceWorkerManager::InvalidateServiceWorkerRegistrationWorker(ServiceWorkerRegistrationInfo* aRegistration,
+ WhichServiceWorker aWhichOnes)
+{
+ AssertIsOnMainThread();
+ nsTObserverArray<ServiceWorkerRegistrationListener*>::ForwardIterator it(mServiceWorkerRegistrationListeners);
+ while (it.HasMore()) {
+ RefPtr<ServiceWorkerRegistrationListener> target = it.GetNext();
+ nsAutoString regScope;
+ target->GetScope(regScope);
+ MOZ_ASSERT(!regScope.IsEmpty());
+
+ NS_ConvertUTF16toUTF8 utf8Scope(regScope);
+
+ if (utf8Scope.Equals(aRegistration->mScope)) {
+ target->InvalidateWorkers(aWhichOnes);
+ }
+ }
+}
+
+void
+ServiceWorkerManager::NotifyServiceWorkerRegistrationRemoved(ServiceWorkerRegistrationInfo* aRegistration)
+{
+ AssertIsOnMainThread();
+ nsTObserverArray<ServiceWorkerRegistrationListener*>::ForwardIterator it(mServiceWorkerRegistrationListeners);
+ while (it.HasMore()) {
+ RefPtr<ServiceWorkerRegistrationListener> target = it.GetNext();
+ nsAutoString regScope;
+ target->GetScope(regScope);
+ MOZ_ASSERT(!regScope.IsEmpty());
+
+ NS_ConvertUTF16toUTF8 utf8Scope(regScope);
+
+ if (utf8Scope.Equals(aRegistration->mScope)) {
+ target->RegistrationRemoved();
+ }
+ }
+}
+
+void
+ServiceWorkerManager::SoftUpdate(const PrincipalOriginAttributes& aOriginAttributes,
+ const nsACString& aScope)
+{
+ AssertIsOnMainThread();
+
+ if (mShuttingDown) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> scopeURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateCodebasePrincipal(scopeURI, aOriginAttributes);
+ if (NS_WARN_IF(!principal)) {
+ return;
+ }
+
+ nsAutoCString scopeKey;
+ rv = PrincipalToScopeKey(principal, scopeKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ GetRegistration(scopeKey, aScope);
+ if (NS_WARN_IF(!registration)) {
+ return;
+ }
+
+ // "If registration's uninstalling flag is set, abort these steps."
+ if (registration->mPendingUninstall) {
+ return;
+ }
+
+ // "If registration's installing worker is not null, abort these steps."
+ if (registration->GetInstalling()) {
+ return;
+ }
+
+ // "Let newestWorker be the result of running Get Newest Worker algorithm
+ // passing registration as its argument.
+ // If newestWorker is null, abort these steps."
+ RefPtr<ServiceWorkerInfo> newest = registration->Newest();
+ if (!newest) {
+ return;
+ }
+
+ // "If the registration queue for registration is empty, invoke Update algorithm,
+ // or its equivalent, with client, registration as its argument."
+ // TODO(catalinb): We don't implement the force bypass cache flag.
+ // See: https://github.com/slightlyoff/ServiceWorker/issues/759
+ RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey,
+ aScope);
+
+ RefPtr<ServiceWorkerUpdateJob> job =
+ new ServiceWorkerUpdateJob(principal, registration->mScope,
+ newest->ScriptSpec(), nullptr);
+ queue->ScheduleJob(job);
+}
+
+namespace {
+
+class UpdateJobCallback final : public ServiceWorkerJob::Callback
+{
+ RefPtr<ServiceWorkerUpdateFinishCallback> mCallback;
+
+ ~UpdateJobCallback()
+ {
+ }
+
+public:
+ explicit UpdateJobCallback(ServiceWorkerUpdateFinishCallback* aCallback)
+ : mCallback(aCallback)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mCallback);
+ }
+
+ void
+ JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aJob);
+
+ if (aStatus.Failed()) {
+ mCallback->UpdateFailed(aStatus);
+ return;
+ }
+
+ MOZ_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Update);
+ RefPtr<ServiceWorkerUpdateJob> updateJob =
+ static_cast<ServiceWorkerUpdateJob*>(aJob);
+ RefPtr<ServiceWorkerRegistrationInfo> reg = updateJob->GetRegistration();
+ mCallback->UpdateSucceeded(reg);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(UpdateJobCallback)
+};
+} // anonymous namespace
+
+void
+ServiceWorkerManager::Update(nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ ServiceWorkerUpdateFinishCallback* aCallback)
+{
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aCallback);
+
+ nsAutoCString scopeKey;
+ nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ GetRegistration(scopeKey, aScope);
+ if (NS_WARN_IF(!registration)) {
+ return;
+ }
+
+ // "Let newestWorker be the result of running Get Newest Worker algorithm
+ // passing registration as its argument.
+ // If newestWorker is null, return a promise rejected with "InvalidStateError"
+ RefPtr<ServiceWorkerInfo> newest = registration->Newest();
+ if (!newest) {
+ ErrorResult error(NS_ERROR_DOM_INVALID_STATE_ERR);
+ aCallback->UpdateFailed(error);
+
+ // In case the callback does not consume the exception
+ error.SuppressException();
+
+ return;
+ }
+
+ RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey, aScope);
+
+ // "Invoke Update algorithm, or its equivalent, with client, registration as
+ // its argument."
+ RefPtr<ServiceWorkerUpdateJob> job =
+ new ServiceWorkerUpdateJob(aPrincipal, registration->mScope,
+ newest->ScriptSpec(), nullptr);
+
+ RefPtr<UpdateJobCallback> cb = new UpdateJobCallback(aCallback);
+ job->AppendResultCallback(cb);
+
+ queue->ScheduleJob(job);
+}
+
+namespace {
+
+static void
+FireControllerChangeOnDocument(nsIDocument* aDocument)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aDocument);
+
+ nsCOMPtr<nsPIDOMWindowInner> w = aDocument->GetInnerWindow();
+ if (!w) {
+ NS_WARNING("Failed to dispatch controllerchange event");
+ return;
+ }
+
+ auto* window = nsGlobalWindow::Cast(w.get());
+ ErrorResult result;
+ dom::Navigator* navigator = window->GetNavigator(result);
+ if (NS_WARN_IF(result.Failed())) {
+ result.SuppressException();
+ return;
+ }
+
+ RefPtr<ServiceWorkerContainer> container = navigator->ServiceWorker();
+ container->ControllerChanged(result);
+ if (result.Failed()) {
+ NS_WARNING("Failed to dispatch controllerchange event");
+ }
+}
+
+} // anonymous namespace
+
+UniquePtr<ServiceWorkerClientInfo>
+ServiceWorkerManager::GetClient(nsIPrincipal* aPrincipal,
+ const nsAString& aClientId,
+ ErrorResult& aRv)
+{
+ UniquePtr<ServiceWorkerClientInfo> clientInfo;
+ nsCOMPtr<nsISupportsInterfacePointer> ifptr =
+ do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID);
+ if (NS_WARN_IF(!ifptr)) {
+ return clientInfo;
+ }
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return clientInfo;
+ }
+
+ nsresult rv = obs->NotifyObservers(ifptr, "service-worker-get-client",
+ PromiseFlatString(aClientId).get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return clientInfo;
+ }
+
+ nsCOMPtr<nsISupports> ptr;
+ ifptr->GetData(getter_AddRefs(ptr));
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(ptr);
+ if (NS_WARN_IF(!doc)) {
+ return clientInfo;
+ }
+
+ bool equals = false;
+ aPrincipal->Equals(doc->NodePrincipal(), &equals);
+ if (!equals) {
+ return clientInfo;
+ }
+
+ if (!IsFromAuthenticatedOrigin(doc)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return clientInfo;
+ }
+
+ clientInfo.reset(new ServiceWorkerClientInfo(doc));
+ return clientInfo;
+}
+
+void
+ServiceWorkerManager::GetAllClients(nsIPrincipal* aPrincipal,
+ const nsCString& aScope,
+ bool aIncludeUncontrolled,
+ nsTArray<ServiceWorkerClientInfo>& aDocuments)
+{
+ MOZ_ASSERT(aPrincipal);
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ GetRegistration(aPrincipal, aScope);
+
+ if (!registration) {
+ // The registration was removed, leave the array empty.
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsresult rv = obs->EnumerateObservers("service-worker-get-client",
+ getter_AddRefs(enumerator));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ auto ProcessDocument = [&aDocuments](nsIPrincipal* aPrincipal, nsIDocument* aDoc) {
+ if (!aDoc || !aDoc->GetWindow()) {
+ return;
+ }
+
+ bool equals = false;
+ aPrincipal->Equals(aDoc->NodePrincipal(), &equals);
+ if (!equals) {
+ return;
+ }
+
+ // Treat http windows with devtools opened as secure if the correct devtools
+ // setting is enabled.
+ if (!aDoc->GetWindow()->GetServiceWorkersTestingEnabled() &&
+ !Preferences::GetBool("dom.serviceWorkers.testing.enabled") &&
+ !IsFromAuthenticatedOrigin(aDoc)) {
+ return;
+ }
+
+ ServiceWorkerClientInfo clientInfo(aDoc);
+ aDocuments.AppendElement(aDoc);
+ };
+
+ // Since it's not simple to check whether a document is in
+ // mControlledDocuments, we take different code paths depending on whether we
+ // need to look at all documents. The common parts of the two loops are
+ // factored out into the ProcessDocument lambda.
+ if (aIncludeUncontrolled) {
+ bool loop = true;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&loop)) && loop) {
+ nsCOMPtr<nsISupports> ptr;
+ rv = enumerator->GetNext(getter_AddRefs(ptr));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(ptr);
+ ProcessDocument(aPrincipal, doc);
+ }
+ } else {
+ for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
+ ServiceWorkerRegistrationInfo* thisRegistration = iter.UserData();
+ MOZ_ASSERT(thisRegistration);
+ if (!registration->mScope.Equals(thisRegistration->mScope)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(iter.Key());
+
+ // All controlled documents must have an outer window.
+ MOZ_ASSERT(doc->GetWindow());
+
+ ProcessDocument(aPrincipal, doc);
+ }
+ }
+}
+
+void
+ServiceWorkerManager::MaybeClaimClient(nsIDocument* aDocument,
+ ServiceWorkerRegistrationInfo* aWorkerRegistration)
+{
+ MOZ_ASSERT(aWorkerRegistration);
+ MOZ_ASSERT(aWorkerRegistration->GetActive());
+
+ // Same origin check
+ if (!aWorkerRegistration->mPrincipal->Equals(aDocument->NodePrincipal())) {
+ return;
+ }
+
+ // The registration that should be controlling the client
+ RefPtr<ServiceWorkerRegistrationInfo> matchingRegistration =
+ GetServiceWorkerRegistrationInfo(aDocument);
+
+ // The registration currently controlling the client
+ RefPtr<ServiceWorkerRegistrationInfo> controllingRegistration;
+ GetDocumentRegistration(aDocument, getter_AddRefs(controllingRegistration));
+
+ if (aWorkerRegistration != matchingRegistration ||
+ aWorkerRegistration == controllingRegistration) {
+ return;
+ }
+
+ if (controllingRegistration) {
+ StopControllingADocument(controllingRegistration);
+ }
+
+ StartControllingADocument(aWorkerRegistration, aDocument, NS_LITERAL_STRING(""));
+ FireControllerChangeOnDocument(aDocument);
+}
+
+nsresult
+ServiceWorkerManager::ClaimClients(nsIPrincipal* aPrincipal,
+ const nsCString& aScope, uint64_t aId)
+{
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ GetRegistration(aPrincipal, aScope);
+
+ if (!registration || !registration->GetActive() ||
+ !(registration->GetActive()->ID() == aId)) {
+ // The worker is not active.
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsresult rv = obs->EnumerateObservers("service-worker-get-client",
+ getter_AddRefs(enumerator));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool loop = true;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&loop)) && loop) {
+ nsCOMPtr<nsISupports> ptr;
+ rv = enumerator->GetNext(getter_AddRefs(ptr));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(ptr);
+ MaybeClaimClient(doc, registration);
+ }
+
+ return NS_OK;
+}
+
+void
+ServiceWorkerManager::SetSkipWaitingFlag(nsIPrincipal* aPrincipal,
+ const nsCString& aScope,
+ uint64_t aServiceWorkerID)
+{
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ GetRegistration(aPrincipal, aScope);
+ if (NS_WARN_IF(!registration)) {
+ return;
+ }
+
+ RefPtr<ServiceWorkerInfo> worker =
+ registration->GetServiceWorkerInfoById(aServiceWorkerID);
+
+ if (NS_WARN_IF(!worker)) {
+ return;
+ }
+
+ worker->SetSkipWaitingFlag();
+
+ if (worker->State() == ServiceWorkerState::Installed) {
+ registration->TryToActivateAsync();
+ }
+}
+
+void
+ServiceWorkerManager::FireControllerChange(ServiceWorkerRegistrationInfo* aRegistration)
+{
+ AssertIsOnMainThread();
+ for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
+ if (iter.UserData() != aRegistration) {
+ continue;
+ }
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(iter.Key());
+ if (NS_WARN_IF(!doc)) {
+ continue;
+ }
+
+ FireControllerChangeOnDocument(doc);
+ }
+}
+
+already_AddRefed<ServiceWorkerRegistrationInfo>
+ServiceWorkerManager::GetRegistration(nsIPrincipal* aPrincipal,
+ const nsACString& aScope) const
+{
+ MOZ_ASSERT(aPrincipal);
+
+ nsAutoCString scopeKey;
+ nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ return GetRegistration(scopeKey, aScope);
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::GetRegistrationByPrincipal(nsIPrincipal* aPrincipal,
+ const nsAString& aScope,
+ nsIServiceWorkerRegistrationInfo** aInfo)
+{
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aInfo);
+
+ nsCOMPtr<nsIURI> scopeURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, nullptr);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<ServiceWorkerRegistrationInfo> info =
+ GetServiceWorkerRegistrationInfo(aPrincipal, scopeURI);
+ if (!info) {
+ return NS_ERROR_FAILURE;
+ }
+ info.forget(aInfo);
+
+ return NS_OK;
+}
+
+already_AddRefed<ServiceWorkerRegistrationInfo>
+ServiceWorkerManager::GetRegistration(const nsACString& aScopeKey,
+ const nsACString& aScope) const
+{
+ RefPtr<ServiceWorkerRegistrationInfo> reg;
+
+ RegistrationDataPerPrincipal* data;
+ if (!mRegistrationInfos.Get(aScopeKey, &data)) {
+ return reg.forget();
+ }
+
+ data->mInfos.Get(aScope, getter_AddRefs(reg));
+ return reg.forget();
+}
+
+already_AddRefed<ServiceWorkerRegistrationInfo>
+ServiceWorkerManager::CreateNewRegistration(const nsCString& aScope,
+ nsIPrincipal* aPrincipal)
+{
+#ifdef DEBUG
+ AssertIsOnMainThread();
+ nsCOMPtr<nsIURI> scopeURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, nullptr);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ RefPtr<ServiceWorkerRegistrationInfo> tmp =
+ GetRegistration(aPrincipal, aScope);
+ MOZ_ASSERT(!tmp);
+#endif
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ new ServiceWorkerRegistrationInfo(aScope, aPrincipal);
+ // From now on ownership of registration is with
+ // mServiceWorkerRegistrationInfos.
+ AddScopeAndRegistration(aScope, registration);
+ return registration.forget();
+}
+
+void
+ServiceWorkerManager::MaybeRemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration)
+{
+ MOZ_ASSERT(aRegistration);
+ RefPtr<ServiceWorkerInfo> newest = aRegistration->Newest();
+ if (!newest && HasScope(aRegistration->mPrincipal, aRegistration->mScope)) {
+ RemoveRegistration(aRegistration);
+ }
+}
+
+void
+ServiceWorkerManager::RemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration)
+{
+ // Note, we do not need to call mActor->SendUnregister() here. There are a few
+ // ways we can get here:
+ // 1) Through a normal unregister which calls SendUnregister() in the unregister
+ // job Start() method.
+ // 2) Through origin storage being purged. These result in ForceUnregister()
+ // starting unregister jobs which in turn call SendUnregister().
+ // 3) Through the failure to install a new service worker. Since we don't store
+ // the registration until install succeeds, we do not need to call
+ // SendUnregister here.
+ // Assert these conditions by testing for pending uninstall (cases 1 and 2) or
+ // null workers (case 3).
+#ifdef DEBUG
+ RefPtr<ServiceWorkerInfo> newest = aRegistration->Newest();
+ MOZ_ASSERT(aRegistration->mPendingUninstall || !newest);
+#endif
+
+ MOZ_ASSERT(HasScope(aRegistration->mPrincipal, aRegistration->mScope));
+
+ // When a registration is removed, we must clear its contents since the DOM
+ // object may be held by content script.
+ aRegistration->Clear();
+
+ RemoveScopeAndRegistration(aRegistration);
+}
+
+namespace {
+/**
+ * See toolkit/modules/sessionstore/Utils.jsm function hasRootDomain().
+ *
+ * Returns true if the |url| passed in is part of the given root |domain|.
+ * For example, if |url| is "www.mozilla.org", and we pass in |domain| as
+ * "mozilla.org", this will return true. It would return false the other way
+ * around.
+ */
+bool
+HasRootDomain(nsIURI* aURI, const nsACString& aDomain)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aURI);
+
+ nsAutoCString host;
+ nsresult rv = aURI->GetHost(host);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ nsACString::const_iterator start, end;
+ host.BeginReading(start);
+ host.EndReading(end);
+ if (!FindInReadable(aDomain, start, end)) {
+ return false;
+ }
+
+ if (host.Equals(aDomain)) {
+ return true;
+ }
+
+ // Beginning of the string matches, can't look at the previous char.
+ if (start.get() == host.BeginReading()) {
+ // Equals failed so this is fine.
+ return false;
+ }
+
+ char prevChar = *(--start);
+ return prevChar == '.';
+}
+
+} // namespace
+
+NS_IMETHODIMP
+ServiceWorkerManager::GetAllRegistrations(nsIArray** aResult)
+{
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ if (!array) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (auto it1 = mRegistrationInfos.Iter(); !it1.Done(); it1.Next()) {
+ for (auto it2 = it1.UserData()->mInfos.Iter(); !it2.Done(); it2.Next()) {
+ ServiceWorkerRegistrationInfo* reg = it2.UserData();
+ MOZ_ASSERT(reg);
+
+ if (reg->mPendingUninstall) {
+ continue;
+ }
+
+ array->AppendElement(reg, false);
+ }
+ }
+
+ array.forget(aResult);
+ return NS_OK;
+}
+
+// MUST ONLY BE CALLED FROM Remove(), RemoveAll() and RemoveAllRegistrations()!
+void
+ServiceWorkerManager::ForceUnregister(RegistrationDataPerPrincipal* aRegistrationData,
+ ServiceWorkerRegistrationInfo* aRegistration)
+{
+ MOZ_ASSERT(aRegistrationData);
+ MOZ_ASSERT(aRegistration);
+
+ RefPtr<ServiceWorkerJobQueue> queue;
+ aRegistrationData->mJobQueues.Get(aRegistration->mScope, getter_AddRefs(queue));
+ if (queue) {
+ queue->CancelAll();
+ }
+
+ nsCOMPtr<nsITimer> timer =
+ aRegistrationData->mUpdateTimers.Get(aRegistration->mScope);
+ if (timer) {
+ timer->Cancel();
+ aRegistrationData->mUpdateTimers.Remove(aRegistration->mScope);
+ }
+
+ // Since Unregister is async, it is ok to call it in an enumeration.
+ Unregister(aRegistration->mPrincipal, nullptr, NS_ConvertUTF8toUTF16(aRegistration->mScope));
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::RemoveAndPropagate(const nsACString& aHost)
+{
+ Remove(aHost);
+ PropagateRemove(aHost);
+ return NS_OK;
+}
+
+void
+ServiceWorkerManager::Remove(const nsACString& aHost)
+{
+ AssertIsOnMainThread();
+
+ // We need to postpone this operation in case we don't have an actor because
+ // this is needed by the ForceUnregister.
+ if (!mActor) {
+ RefPtr<nsIRunnable> runnable = new RemoveRunnable(aHost);
+ AppendPendingOperation(runnable);
+ return;
+ }
+
+ for (auto it1 = mRegistrationInfos.Iter(); !it1.Done(); it1.Next()) {
+ ServiceWorkerManager::RegistrationDataPerPrincipal* data = it1.UserData();
+ for (auto it2 = data->mInfos.Iter(); !it2.Done(); it2.Next()) {
+ ServiceWorkerRegistrationInfo* reg = it2.UserData();
+ nsCOMPtr<nsIURI> scopeURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), it2.Key(),
+ nullptr, nullptr);
+ // This way subdomains are also cleared.
+ if (NS_SUCCEEDED(rv) && HasRootDomain(scopeURI, aHost)) {
+ ForceUnregister(data, reg);
+ }
+ }
+ }
+}
+
+void
+ServiceWorkerManager::PropagateRemove(const nsACString& aHost)
+{
+ AssertIsOnMainThread();
+
+ if (!mActor) {
+ RefPtr<nsIRunnable> runnable = new PropagateRemoveRunnable(aHost);
+ AppendPendingOperation(runnable);
+ return;
+ }
+
+ mActor->SendPropagateRemove(nsCString(aHost));
+}
+
+void
+ServiceWorkerManager::RemoveAll()
+{
+ AssertIsOnMainThread();
+
+ for (auto it1 = mRegistrationInfos.Iter(); !it1.Done(); it1.Next()) {
+ ServiceWorkerManager::RegistrationDataPerPrincipal* data = it1.UserData();
+ for (auto it2 = data->mInfos.Iter(); !it2.Done(); it2.Next()) {
+ ServiceWorkerRegistrationInfo* reg = it2.UserData();
+ ForceUnregister(data, reg);
+ }
+ }
+}
+
+void
+ServiceWorkerManager::PropagateRemoveAll()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!mActor) {
+ RefPtr<nsIRunnable> runnable = new PropagateRemoveAllRunnable();
+ AppendPendingOperation(runnable);
+ return;
+ }
+
+ mActor->SendPropagateRemoveAll();
+}
+
+void
+ServiceWorkerManager::RemoveAllRegistrations(OriginAttributesPattern* aPattern)
+{
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(aPattern);
+
+ for (auto it1 = mRegistrationInfos.Iter(); !it1.Done(); it1.Next()) {
+ ServiceWorkerManager::RegistrationDataPerPrincipal* data = it1.UserData();
+
+ // We can use iteration because ForceUnregister (and Unregister) are
+ // async. Otherwise doing some R/W operations on an hashtable during
+ // iteration will crash.
+ for (auto it2 = data->mInfos.Iter(); !it2.Done(); it2.Next()) {
+ ServiceWorkerRegistrationInfo* reg = it2.UserData();
+
+ MOZ_ASSERT(reg);
+ MOZ_ASSERT(reg->mPrincipal);
+
+ bool matches =
+ aPattern->Matches(BasePrincipal::Cast(reg->mPrincipal)->OriginAttributesRef());
+ if (!matches) {
+ continue;
+ }
+
+ ForceUnregister(data, reg);
+ }
+ }
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::AddListener(nsIServiceWorkerManagerListener* aListener)
+{
+ AssertIsOnMainThread();
+
+ if (!aListener || mListeners.Contains(aListener)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mListeners.AppendElement(aListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::RemoveListener(nsIServiceWorkerManagerListener* aListener)
+{
+ AssertIsOnMainThread();
+
+ if (!aListener || !mListeners.Contains(aListener)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mListeners.RemoveElement(aListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::ShouldReportToWindow(mozIDOMWindowProxy* aWindow,
+ const nsACString& aScope,
+ bool* aResult)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aResult);
+
+ *aResult = false;
+
+ // Get the inner window ID to compare to our document windows below.
+ nsCOMPtr<nsPIDOMWindowOuter> targetWin = nsPIDOMWindowOuter::From(aWindow);
+ if (NS_WARN_IF(!targetWin)) {
+ return NS_OK;
+ }
+
+ targetWin = targetWin->GetScriptableTop();
+ uint64_t winId = targetWin->WindowID();
+
+ // Check our weak registering document references first. This way we clear
+ // out as many dead weak references as possible when this method is called.
+ WeakDocumentList* list = mRegisteringDocuments.Get(aScope);
+ if (list) {
+ for (int32_t i = list->Length() - 1; i >= 0; --i) {
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(list->ElementAt(i));
+ if (!doc) {
+ list->RemoveElementAt(i);
+ continue;
+ }
+
+ if (!doc->IsCurrentActiveDocument()) {
+ continue;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
+ if (!win) {
+ continue;
+ }
+
+ win = win->GetScriptableTop();
+
+ // Match. We should report to this window.
+ if (win && winId == win->WindowID()) {
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+
+ if (list->IsEmpty()) {
+ list = nullptr;
+ nsAutoPtr<WeakDocumentList> doomed;
+ mRegisteringDocuments.RemoveAndForget(aScope, doomed);
+ }
+ }
+
+ // Examine any windows performing a navigation that we are currently
+ // intercepting.
+ InterceptionList* intList = mNavigationInterceptions.Get(aScope);
+ if (intList) {
+ for (uint32_t i = 0; i < intList->Length(); ++i) {
+ nsCOMPtr<nsIInterceptedChannel> channel = intList->ElementAt(i);
+
+ nsCOMPtr<nsIChannel> inner;
+ nsresult rv = channel->GetChannel(getter_AddRefs(inner));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ uint64_t id = nsContentUtils::GetInnerWindowID(inner);
+ if (id == 0) {
+ continue;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> win = nsGlobalWindow::GetInnerWindowWithId(id)->AsInner();
+ if (!win) {
+ continue;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> outer = win->GetScriptableTop();
+
+ // Match. We should report to this window.
+ if (outer && winId == outer->WindowID()) {
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+ }
+
+ // Next examine controlled documents to see if the windows match.
+ for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
+ ServiceWorkerRegistrationInfo* reg = iter.UserData();
+ MOZ_ASSERT(reg);
+ if (!reg->mScope.Equals(aScope)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(iter.Key());
+ if (!doc || !doc->IsCurrentActiveDocument()) {
+ continue;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
+ if (!win) {
+ continue;
+ }
+
+ win = win->GetScriptableTop();
+
+ // Match. We should report to this window.
+ if (win && winId == win->WindowID()) {
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+
+ // No match. We should not report to this window.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ if (strcmp(aTopic, PURGE_SESSION_HISTORY) == 0) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ RemoveAll();
+ PropagateRemoveAll();
+ return NS_OK;
+ }
+
+ if (strcmp(aTopic, PURGE_DOMAIN_DATA) == 0) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ nsAutoString domain(aData);
+ RemoveAndPropagate(NS_ConvertUTF16toUTF8(domain));
+ return NS_OK;
+ }
+
+ if (strcmp(aTopic, CLEAR_ORIGIN_DATA) == 0) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ OriginAttributesPattern pattern;
+ MOZ_ALWAYS_TRUE(pattern.Init(nsAutoString(aData)));
+
+ RemoveAllRegistrations(&pattern);
+ return NS_OK;
+ }
+
+ if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ MaybeStartShutdown();
+ return NS_OK;
+ }
+
+ MOZ_CRASH("Received message we aren't supposed to be registered for!");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::PropagateSoftUpdate(JS::Handle<JS::Value> aOriginAttributes,
+ const nsAString& aScope,
+ JSContext* aCx)
+{
+ AssertIsOnMainThread();
+
+ PrincipalOriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PropagateSoftUpdate(attrs, aScope);
+ return NS_OK;
+}
+
+void
+ServiceWorkerManager::PropagateSoftUpdate(const PrincipalOriginAttributes& aOriginAttributes,
+ const nsAString& aScope)
+{
+ AssertIsOnMainThread();
+
+ if (!mActor) {
+ RefPtr<nsIRunnable> runnable =
+ new PropagateSoftUpdateRunnable(aOriginAttributes, aScope);
+ AppendPendingOperation(runnable);
+ return;
+ }
+
+ mActor->SendPropagateSoftUpdate(aOriginAttributes, nsString(aScope));
+}
+
+NS_IMETHODIMP
+ServiceWorkerManager::PropagateUnregister(nsIPrincipal* aPrincipal,
+ nsIServiceWorkerUnregisterCallback* aCallback,
+ const nsAString& aScope)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+
+ if (!mActor) {
+ RefPtr<nsIRunnable> runnable =
+ new PropagateUnregisterRunnable(aPrincipal, aCallback, aScope);
+ AppendPendingOperation(runnable);
+ return NS_OK;
+ }
+
+ PrincipalInfo principalInfo;
+ if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(aPrincipal,
+ &principalInfo)))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mActor->SendPropagateUnregister(principalInfo, nsString(aScope));
+
+ nsresult rv = Unregister(aPrincipal, aCallback, aScope);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+ServiceWorkerManager::NotifyListenersOnRegister(
+ nsIServiceWorkerRegistrationInfo* aInfo)
+{
+ nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> listeners(mListeners);
+ for (size_t index = 0; index < listeners.Length(); ++index) {
+ listeners[index]->OnRegister(aInfo);
+ }
+}
+
+void
+ServiceWorkerManager::NotifyListenersOnUnregister(
+ nsIServiceWorkerRegistrationInfo* aInfo)
+{
+ nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> listeners(mListeners);
+ for (size_t index = 0; index < listeners.Length(); ++index) {
+ listeners[index]->OnUnregister(aInfo);
+ }
+}
+
+void
+ServiceWorkerManager::AddRegisteringDocument(const nsACString& aScope,
+ nsIDocument* aDoc)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!aScope.IsEmpty());
+ MOZ_ASSERT(aDoc);
+
+ WeakDocumentList* list = mRegisteringDocuments.LookupOrAdd(aScope);
+ MOZ_ASSERT(list);
+
+ for (int32_t i = list->Length() - 1; i >= 0; --i) {
+ nsCOMPtr<nsIDocument> existing = do_QueryReferent(list->ElementAt(i));
+ if (!existing) {
+ list->RemoveElementAt(i);
+ continue;
+ }
+ if (existing == aDoc) {
+ return;
+ }
+ }
+
+ list->AppendElement(do_GetWeakReference(aDoc));
+}
+
+class ServiceWorkerManager::InterceptionReleaseHandle final : public nsISupports
+{
+ const nsCString mScope;
+
+ // Weak reference to channel is safe, because the channel holds a
+ // reference to this object. Also, the pointer is only used for
+ // comparison purposes.
+ nsIInterceptedChannel* mChannel;
+
+ ~InterceptionReleaseHandle()
+ {
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->RemoveNavigationInterception(mScope, mChannel);
+ }
+ }
+
+public:
+ InterceptionReleaseHandle(const nsACString& aScope,
+ nsIInterceptedChannel* aChannel)
+ : mScope(aScope)
+ , mChannel(aChannel)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!aScope.IsEmpty());
+ MOZ_ASSERT(mChannel);
+ }
+
+ NS_DECL_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS0(ServiceWorkerManager::InterceptionReleaseHandle);
+
+void
+ServiceWorkerManager::AddNavigationInterception(const nsACString& aScope,
+ nsIInterceptedChannel* aChannel)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!aScope.IsEmpty());
+ MOZ_ASSERT(aChannel);
+
+ InterceptionList* list =
+ mNavigationInterceptions.LookupOrAdd(aScope);
+ MOZ_ASSERT(list);
+ MOZ_ASSERT(!list->Contains(aChannel));
+
+ nsCOMPtr<nsISupports> releaseHandle =
+ new InterceptionReleaseHandle(aScope, aChannel);
+ aChannel->SetReleaseHandle(releaseHandle);
+
+ list->AppendElement(aChannel);
+}
+
+void
+ServiceWorkerManager::RemoveNavigationInterception(const nsACString& aScope,
+ nsIInterceptedChannel* aChannel)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aChannel);
+ InterceptionList* list =
+ mNavigationInterceptions.Get(aScope);
+ if (list) {
+ MOZ_ALWAYS_TRUE(list->RemoveElement(aChannel));
+ MOZ_ASSERT(!list->Contains(aChannel));
+ if (list->IsEmpty()) {
+ list = nullptr;
+ nsAutoPtr<InterceptionList> doomed;
+ mNavigationInterceptions.RemoveAndForget(aScope, doomed);
+ }
+ }
+}
+
+class UpdateTimerCallback final : public nsITimerCallback
+{
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ const nsCString mScope;
+
+ ~UpdateTimerCallback()
+ {
+ }
+
+public:
+ UpdateTimerCallback(nsIPrincipal* aPrincipal, const nsACString& aScope)
+ : mPrincipal(aPrincipal)
+ , mScope(aScope)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mPrincipal);
+ MOZ_ASSERT(!mScope.IsEmpty());
+ }
+
+ NS_IMETHOD
+ Notify(nsITimer* aTimer) override
+ {
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ // shutting down, do nothing
+ return NS_OK;
+ }
+
+ swm->UpdateTimerFired(mPrincipal, mScope);
+ return NS_OK;
+ }
+
+ NS_DECL_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS(UpdateTimerCallback, nsITimerCallback)
+
+bool
+ServiceWorkerManager::MayHaveActiveServiceWorkerInstance(ContentParent* aContent,
+ nsIPrincipal* aPrincipal)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+
+ if (mShuttingDown) {
+ return false;
+ }
+
+ nsAutoCString scopeKey;
+ nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ RegistrationDataPerPrincipal* data;
+ if (!mRegistrationInfos.Get(scopeKey, &data)) {
+ return false;
+ }
+
+ return true;
+}
+
+void
+ServiceWorkerManager::ScheduleUpdateTimer(nsIPrincipal* aPrincipal,
+ const nsACString& aScope)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(!aScope.IsEmpty());
+
+ if (mShuttingDown) {
+ return;
+ }
+
+ nsAutoCString scopeKey;
+ nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ RegistrationDataPerPrincipal* data;
+ if (!mRegistrationInfos.Get(scopeKey, &data)) {
+ return;
+ }
+
+ nsCOMPtr<nsITimer> timer = data->mUpdateTimers.Get(aScope);
+ if (timer) {
+ // There is already a timer scheduled. In this case just use the original
+ // schedule time. We don't want to push it out to a later time since that
+ // could allow updates to be starved forever if events are continuously
+ // fired.
+ return;
+ }
+
+ timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsCOMPtr<nsITimerCallback> callback = new UpdateTimerCallback(aPrincipal,
+ aScope);
+
+ const uint32_t UPDATE_DELAY_MS = 1000;
+
+ rv = timer->InitWithCallback(callback, UPDATE_DELAY_MS,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ data->mUpdateTimers.Put(aScope, timer);
+}
+
+void
+ServiceWorkerManager::UpdateTimerFired(nsIPrincipal* aPrincipal,
+ const nsACString& aScope)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(!aScope.IsEmpty());
+
+ if (mShuttingDown) {
+ return;
+ }
+
+ // First cleanup the timer.
+ nsAutoCString scopeKey;
+ nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ RegistrationDataPerPrincipal* data;
+ if (!mRegistrationInfos.Get(scopeKey, &data)) {
+ return;
+ }
+
+ nsCOMPtr<nsITimer> timer = data->mUpdateTimers.Get(aScope);
+ if (timer) {
+ timer->Cancel();
+ data->mUpdateTimers.Remove(aScope);
+ }
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration;
+ data->mInfos.Get(aScope, getter_AddRefs(registration));
+ if (!registration) {
+ return;
+ }
+
+ if (!registration->CheckAndClearIfUpdateNeeded()) {
+ return;
+ }
+
+ PrincipalOriginAttributes attrs =
+ BasePrincipal::Cast(aPrincipal)->OriginAttributesRef();
+
+ SoftUpdate(attrs, aScope);
+}
+
+void
+ServiceWorkerManager::MaybeSendUnregister(nsIPrincipal* aPrincipal,
+ const nsACString& aScope)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(!aScope.IsEmpty());
+
+ if (!mActor) {
+ return;
+ }
+
+ PrincipalInfo principalInfo;
+ nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ Unused << mActor->SendUnregister(principalInfo, NS_ConvertUTF8toUTF16(aScope));
+}
+
+END_WORKERS_NAMESPACE
diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
new file mode 100644
index 000000000..f99bc4248
--- /dev/null
+++ b/dom/workers/ServiceWorkerManager.h
@@ -0,0 +1,521 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_serviceworkermanager_h
+#define mozilla_dom_workers_serviceworkermanager_h
+
+#include "nsIServiceWorkerManager.h"
+#include "nsCOMPtr.h"
+
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ServiceWorkerCommon.h"
+#include "mozilla/dom/ServiceWorkerRegistrar.h"
+#include "mozilla/dom/ServiceWorkerRegistrarTypes.h"
+#include "mozilla/dom/workers/ServiceWorkerRegistrationInfo.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsTObserverArray.h"
+
+class mozIApplicationClearPrivateDataParams;
+class nsIConsoleReportCollector;
+
+namespace mozilla {
+
+class PrincipalOriginAttributes;
+
+namespace dom {
+
+class ServiceWorkerRegistrar;
+class ServiceWorkerRegistrationListener;
+
+namespace workers {
+
+class ServiceWorkerClientInfo;
+class ServiceWorkerInfo;
+class ServiceWorkerJobQueue;
+class ServiceWorkerManagerChild;
+class ServiceWorkerPrivate;
+
+class ServiceWorkerUpdateFinishCallback
+{
+protected:
+ virtual ~ServiceWorkerUpdateFinishCallback()
+ {}
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(ServiceWorkerUpdateFinishCallback)
+
+ virtual
+ void UpdateSucceeded(ServiceWorkerRegistrationInfo* aInfo) = 0;
+
+ virtual
+ void UpdateFailed(ErrorResult& aStatus) = 0;
+};
+
+#define NS_SERVICEWORKERMANAGER_IMPL_IID \
+{ /* f4f8755a-69ca-46e8-a65d-775745535990 */ \
+ 0xf4f8755a, \
+ 0x69ca, \
+ 0x46e8, \
+ { 0xa6, 0x5d, 0x77, 0x57, 0x45, 0x53, 0x59, 0x90 } \
+}
+
+/*
+ * The ServiceWorkerManager is a per-process global that deals with the
+ * installation, querying and event dispatch of ServiceWorkers for all the
+ * origins in the process.
+ */
+class ServiceWorkerManager final
+ : public nsIServiceWorkerManager
+ , public nsIIPCBackgroundChildCreateCallback
+ , public nsIObserver
+{
+ friend class GetReadyPromiseRunnable;
+ friend class GetRegistrationsRunnable;
+ friend class GetRegistrationRunnable;
+ friend class ServiceWorkerJob;
+ friend class ServiceWorkerRegistrationInfo;
+ friend class ServiceWorkerUnregisterJob;
+ friend class ServiceWorkerUpdateJob;
+ friend class UpdateTimerCallback;
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISERVICEWORKERMANAGER
+ NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
+ NS_DECL_NSIOBSERVER
+
+ struct RegistrationDataPerPrincipal;
+ nsClassHashtable<nsCStringHashKey, RegistrationDataPerPrincipal> mRegistrationInfos;
+
+ nsTObserverArray<ServiceWorkerRegistrationListener*> mServiceWorkerRegistrationListeners;
+
+ nsRefPtrHashtable<nsISupportsHashKey, ServiceWorkerRegistrationInfo> mControlledDocuments;
+
+ // Track all documents that have attempted to register a service worker for a
+ // given scope.
+ typedef nsTArray<nsCOMPtr<nsIWeakReference>> WeakDocumentList;
+ nsClassHashtable<nsCStringHashKey, WeakDocumentList> mRegisteringDocuments;
+
+ // Track all intercepted navigation channels for a given scope. Channels are
+ // placed in the appropriate list before dispatch the FetchEvent to the worker
+ // thread and removed once FetchEvent processing dispatches back to the main
+ // thread.
+ //
+ // Note: Its safe to use weak references here because a RAII-style callback
+ // is registered with the channel before its added to this list. We
+ // are guaranteed the callback will fire before and remove the ref
+ // from this list before the channel is destroyed.
+ typedef nsTArray<nsIInterceptedChannel*> InterceptionList;
+ nsClassHashtable<nsCStringHashKey, InterceptionList> mNavigationInterceptions;
+
+ bool
+ IsAvailable(nsIPrincipal* aPrincipal, nsIURI* aURI);
+
+ bool
+ IsControlled(nsIDocument* aDocument, ErrorResult& aRv);
+
+ // Return true if the given content process could potentially be executing
+ // service worker code with the given principal. At the current time, this
+ // just means that we have any registration for the origin, regardless of
+ // scope. This is a very weak guarantee but is the best we can do when push
+ // notifications can currently spin up a service worker in content processes
+ // without our involvement in the parent process.
+ //
+ // In the future when there is only a single ServiceWorkerManager in the
+ // parent process that is entirely in control of spawning and running service
+ // worker code, we will be able to authoritatively indicate whether there is
+ // an activate service worker in the given content process. At that time we
+ // will rename this method HasActiveServiceWorkerInstance and provide
+ // semantics that ensure this method returns true until the worker is known to
+ // have shut down in order to allow the caller to induce a crash for security
+ // reasons without having to worry about shutdown races with the worker.
+ bool
+ MayHaveActiveServiceWorkerInstance(ContentParent* aContent,
+ nsIPrincipal* aPrincipal);
+
+ void
+ DispatchFetchEvent(const PrincipalOriginAttributes& aOriginAttributes,
+ nsIDocument* aDoc,
+ const nsAString& aDocumentIdForTopLevelNavigation,
+ nsIInterceptedChannel* aChannel,
+ bool aIsReload,
+ bool aIsSubresourceLoad,
+ ErrorResult& aRv);
+
+ void
+ Update(nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ ServiceWorkerUpdateFinishCallback* aCallback);
+
+ void
+ SoftUpdate(const PrincipalOriginAttributes& aOriginAttributes,
+ const nsACString& aScope);
+
+ void
+ PropagateSoftUpdate(const PrincipalOriginAttributes& aOriginAttributes,
+ const nsAString& aScope);
+
+ void
+ PropagateRemove(const nsACString& aHost);
+
+ void
+ Remove(const nsACString& aHost);
+
+ void
+ PropagateRemoveAll();
+
+ void
+ RemoveAll();
+
+ already_AddRefed<ServiceWorkerRegistrationInfo>
+ GetRegistration(nsIPrincipal* aPrincipal, const nsACString& aScope) const;
+
+ already_AddRefed<ServiceWorkerRegistrationInfo>
+ CreateNewRegistration(const nsCString& aScope, nsIPrincipal* aPrincipal);
+
+ void
+ RemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration);
+
+ void StoreRegistration(nsIPrincipal* aPrincipal,
+ ServiceWorkerRegistrationInfo* aRegistration);
+
+ void
+ FinishFetch(ServiceWorkerRegistrationInfo* aRegistration);
+
+ /**
+ * Report an error for the given scope to any window we think might be
+ * interested, failing over to the Browser Console if we couldn't find any.
+ *
+ * Error messages should be localized, so you probably want to call
+ * LocalizeAndReportToAllClients instead, which in turn calls us after
+ * localizing the error.
+ */
+ void
+ ReportToAllClients(const nsCString& aScope,
+ const nsString& aMessage,
+ const nsString& aFilename,
+ const nsString& aLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aFlags);
+
+ /**
+ * Report a localized error for the given scope to any window we think might
+ * be interested.
+ *
+ * Note that this method takes an nsTArray<nsString> for the parameters, not
+ * bare chart16_t*[]. You can use a std::initializer_list constructor inline
+ * so that argument might look like: nsTArray<nsString> { some_nsString,
+ * PromiseFlatString(some_nsSubString_aka_nsAString),
+ * NS_ConvertUTF8toUTF16(some_nsCString_or_nsCSubString),
+ * NS_LITERAL_STRING("some literal") }. If you have anything else, like a
+ * number, you can use an nsAutoString with AppendInt/friends.
+ *
+ * @param [aFlags]
+ * The nsIScriptError flag, one of errorFlag (0x0), warningFlag (0x1),
+ * infoFlag (0x8). We default to error if omitted because usually we're
+ * logging exceptional and/or obvious breakage.
+ */
+ static void
+ LocalizeAndReportToAllClients(const nsCString& aScope,
+ const char* aStringKey,
+ const nsTArray<nsString>& aParamArray,
+ uint32_t aFlags = 0x0,
+ const nsString& aFilename = EmptyString(),
+ const nsString& aLine = EmptyString(),
+ uint32_t aLineNumber = 0,
+ uint32_t aColumnNumber = 0);
+
+ void
+ FlushReportsToAllClients(const nsACString& aScope,
+ nsIConsoleReportCollector* aReporter);
+
+ // Always consumes the error by reporting to consoles of all controlled
+ // documents.
+ void
+ HandleError(JSContext* aCx,
+ nsIPrincipal* aPrincipal,
+ const nsCString& aScope,
+ const nsString& aWorkerURL,
+ const nsString& aMessage,
+ const nsString& aFilename,
+ const nsString& aLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aFlags,
+ JSExnType aExnType);
+
+ UniquePtr<ServiceWorkerClientInfo>
+ GetClient(nsIPrincipal* aPrincipal,
+ const nsAString& aClientId,
+ ErrorResult& aRv);
+
+ void
+ GetAllClients(nsIPrincipal* aPrincipal,
+ const nsCString& aScope,
+ bool aIncludeUncontrolled,
+ nsTArray<ServiceWorkerClientInfo>& aDocuments);
+
+ void
+ MaybeClaimClient(nsIDocument* aDocument,
+ ServiceWorkerRegistrationInfo* aWorkerRegistration);
+
+ nsresult
+ ClaimClients(nsIPrincipal* aPrincipal, const nsCString& aScope, uint64_t aId);
+
+ void
+ SetSkipWaitingFlag(nsIPrincipal* aPrincipal, const nsCString& aScope,
+ uint64_t aServiceWorkerID);
+
+ static already_AddRefed<ServiceWorkerManager>
+ GetInstance();
+
+ void
+ LoadRegistration(const ServiceWorkerRegistrationData& aRegistration);
+
+ void
+ LoadRegistrations(const nsTArray<ServiceWorkerRegistrationData>& aRegistrations);
+
+ // Used by remove() and removeAll() when clearing history.
+ // MUST ONLY BE CALLED FROM UnregisterIfMatchesHost!
+ void
+ ForceUnregister(RegistrationDataPerPrincipal* aRegistrationData,
+ ServiceWorkerRegistrationInfo* aRegistration);
+
+ NS_IMETHOD
+ AddRegistrationEventListener(const nsAString& aScope,
+ ServiceWorkerRegistrationListener* aListener);
+
+ NS_IMETHOD
+ RemoveRegistrationEventListener(const nsAString& aScope,
+ ServiceWorkerRegistrationListener* aListener);
+
+ void
+ MaybeCheckNavigationUpdate(nsIDocument* aDoc);
+
+ nsresult
+ SendPushEvent(const nsACString& aOriginAttributes,
+ const nsACString& aScope,
+ const nsAString& aMessageId,
+ const Maybe<nsTArray<uint8_t>>& aData);
+
+ nsresult
+ NotifyUnregister(nsIPrincipal* aPrincipal, const nsAString& aScope);
+
+ void
+ WorkerIsIdle(ServiceWorkerInfo* aWorker);
+
+private:
+ ServiceWorkerManager();
+ ~ServiceWorkerManager();
+
+ void
+ Init(ServiceWorkerRegistrar* aRegistrar);
+
+ void
+ MaybeStartShutdown();
+
+ already_AddRefed<ServiceWorkerJobQueue>
+ GetOrCreateJobQueue(const nsACString& aOriginSuffix,
+ const nsACString& aScope);
+
+ void
+ MaybeRemoveRegistrationInfo(const nsACString& aScopeKey);
+
+ already_AddRefed<ServiceWorkerRegistrationInfo>
+ GetRegistration(const nsACString& aScopeKey,
+ const nsACString& aScope) const;
+
+ void
+ AbortCurrentUpdate(ServiceWorkerRegistrationInfo* aRegistration);
+
+ nsresult
+ Update(ServiceWorkerRegistrationInfo* aRegistration);
+
+ nsresult
+ GetDocumentRegistration(nsIDocument* aDoc,
+ ServiceWorkerRegistrationInfo** aRegistrationInfo);
+
+ nsresult
+ GetServiceWorkerForScope(nsPIDOMWindowInner* aWindow,
+ const nsAString& aScope,
+ WhichServiceWorker aWhichWorker,
+ nsISupports** aServiceWorker);
+
+ ServiceWorkerInfo*
+ GetActiveWorkerInfoForScope(const PrincipalOriginAttributes& aOriginAttributes,
+ const nsACString& aScope);
+
+ ServiceWorkerInfo*
+ GetActiveWorkerInfoForDocument(nsIDocument* aDocument);
+
+ void
+ InvalidateServiceWorkerRegistrationWorker(ServiceWorkerRegistrationInfo* aRegistration,
+ WhichServiceWorker aWhichOnes);
+
+ void
+ NotifyServiceWorkerRegistrationRemoved(ServiceWorkerRegistrationInfo* aRegistration);
+
+ void
+ StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
+ nsIDocument* aDoc,
+ const nsAString& aDocumentId);
+
+ void
+ StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration);
+
+ already_AddRefed<ServiceWorkerRegistrationInfo>
+ GetServiceWorkerRegistrationInfo(nsPIDOMWindowInner* aWindow);
+
+ already_AddRefed<ServiceWorkerRegistrationInfo>
+ GetServiceWorkerRegistrationInfo(nsIDocument* aDoc);
+
+ already_AddRefed<ServiceWorkerRegistrationInfo>
+ GetServiceWorkerRegistrationInfo(nsIPrincipal* aPrincipal, nsIURI* aURI);
+
+ already_AddRefed<ServiceWorkerRegistrationInfo>
+ GetServiceWorkerRegistrationInfo(const nsACString& aScopeKey,
+ nsIURI* aURI);
+
+ // This method generates a key using appId and isInElementBrowser from the
+ // principal. We don't use the origin because it can change during the
+ // loading.
+ static nsresult
+ PrincipalToScopeKey(nsIPrincipal* aPrincipal, nsACString& aKey);
+
+ static void
+ AddScopeAndRegistration(const nsACString& aScope,
+ ServiceWorkerRegistrationInfo* aRegistation);
+
+ static bool
+ FindScopeForPath(const nsACString& aScopeKey,
+ const nsACString& aPath,
+ RegistrationDataPerPrincipal** aData, nsACString& aMatch);
+
+ static bool
+ HasScope(nsIPrincipal* aPrincipal, const nsACString& aScope);
+
+ static void
+ RemoveScopeAndRegistration(ServiceWorkerRegistrationInfo* aRegistration);
+
+ void
+ QueueFireEventOnServiceWorkerRegistrations(ServiceWorkerRegistrationInfo* aRegistration,
+ const nsAString& aName);
+
+ void
+ FireUpdateFoundOnServiceWorkerRegistrations(ServiceWorkerRegistrationInfo* aRegistration);
+
+ void
+ FireControllerChange(ServiceWorkerRegistrationInfo* aRegistration);
+
+ void
+ StorePendingReadyPromise(nsPIDOMWindowInner* aWindow, nsIURI* aURI,
+ Promise* aPromise);
+
+ void
+ CheckPendingReadyPromises();
+
+ bool
+ CheckReadyPromise(nsPIDOMWindowInner* aWindow, nsIURI* aURI,
+ Promise* aPromise);
+
+ struct PendingReadyPromise final
+ {
+ PendingReadyPromise(nsIURI* aURI, Promise* aPromise)
+ : mURI(aURI), mPromise(aPromise)
+ {}
+
+ nsCOMPtr<nsIURI> mURI;
+ RefPtr<Promise> mPromise;
+ };
+
+ void AppendPendingOperation(nsIRunnable* aRunnable);
+
+ bool HasBackgroundActor() const
+ {
+ return !!mActor;
+ }
+
+ nsClassHashtable<nsISupportsHashKey, PendingReadyPromise> mPendingReadyPromises;
+
+ void
+ MaybeRemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration);
+
+ // Removes all service worker registrations that matches the given pattern.
+ void
+ RemoveAllRegistrations(OriginAttributesPattern* aPattern);
+
+ RefPtr<ServiceWorkerManagerChild> mActor;
+
+ nsTArray<nsCOMPtr<nsIRunnable>> mPendingOperations;
+
+ bool mShuttingDown;
+
+ nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> mListeners;
+
+ void
+ NotifyListenersOnRegister(nsIServiceWorkerRegistrationInfo* aRegistration);
+
+ void
+ NotifyListenersOnUnregister(nsIServiceWorkerRegistrationInfo* aRegistration);
+
+ void
+ AddRegisteringDocument(const nsACString& aScope, nsIDocument* aDoc);
+
+ class InterceptionReleaseHandle;
+
+ void
+ AddNavigationInterception(const nsACString& aScope,
+ nsIInterceptedChannel* aChannel);
+
+ void
+ RemoveNavigationInterception(const nsACString& aScope,
+ nsIInterceptedChannel* aChannel);
+
+ void
+ ScheduleUpdateTimer(nsIPrincipal* aPrincipal, const nsACString& aScope);
+
+ void
+ UpdateTimerFired(nsIPrincipal* aPrincipal, const nsACString& aScope);
+
+ void
+ MaybeSendUnregister(nsIPrincipal* aPrincipal, const nsACString& aScope);
+
+ nsresult
+ SendNotificationEvent(const nsAString& aEventName,
+ const nsACString& aOriginSuffix,
+ const nsACString& aScope,
+ const nsAString& aID,
+ const nsAString& aTitle,
+ const nsAString& aDir,
+ const nsAString& aLang,
+ const nsAString& aBody,
+ const nsAString& aTag,
+ const nsAString& aIcon,
+ const nsAString& aData,
+ const nsAString& aBehavior);
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkermanager_h
diff --git a/dom/workers/ServiceWorkerManagerChild.cpp b/dom/workers/ServiceWorkerManagerChild.cpp
new file mode 100644
index 000000000..47a39d1c2
--- /dev/null
+++ b/dom/workers/ServiceWorkerManagerChild.cpp
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerManagerChild.h"
+#include "ServiceWorkerManager.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+namespace workers {
+
+bool
+ServiceWorkerManagerChild::RecvNotifyRegister(
+ const ServiceWorkerRegistrationData& aData)
+{
+ if (mShuttingDown) {
+ return true;
+ }
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->LoadRegistration(aData);
+ }
+
+ return true;
+}
+
+bool
+ServiceWorkerManagerChild::RecvNotifySoftUpdate(
+ const PrincipalOriginAttributes& aOriginAttributes,
+ const nsString& aScope)
+{
+ if (mShuttingDown) {
+ return true;
+ }
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->SoftUpdate(aOriginAttributes, NS_ConvertUTF16toUTF8(aScope));
+ }
+
+ return true;
+}
+
+bool
+ServiceWorkerManagerChild::RecvNotifyUnregister(const PrincipalInfo& aPrincipalInfo,
+ const nsString& aScope)
+{
+ if (mShuttingDown) {
+ return true;
+ }
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ // browser shutdown
+ return true;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(aPrincipalInfo);
+ if (NS_WARN_IF(!principal)) {
+ return true;
+ }
+
+ nsresult rv = swm->NotifyUnregister(principal, aScope);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ return true;
+}
+
+bool
+ServiceWorkerManagerChild::RecvNotifyRemove(const nsCString& aHost)
+{
+ if (mShuttingDown) {
+ return true;
+ }
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->Remove(aHost);
+ }
+
+ return true;
+}
+
+bool
+ServiceWorkerManagerChild::RecvNotifyRemoveAll()
+{
+ if (mShuttingDown) {
+ return true;
+ }
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->RemoveAll();
+ }
+
+ return true;
+}
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/ServiceWorkerManagerChild.h b/dom/workers/ServiceWorkerManagerChild.h
new file mode 100644
index 000000000..d32f3ed10
--- /dev/null
+++ b/dom/workers/ServiceWorkerManagerChild.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ServiceWorkerManagerChild_h
+#define mozilla_dom_ServiceWorkerManagerChild_h
+
+#include "mozilla/dom/PServiceWorkerManagerChild.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+
+namespace mozilla {
+
+class PrincipalOriginAttributes;
+
+namespace ipc {
+class BackgroundChildImpl;
+} // namespace ipc
+
+namespace dom {
+namespace workers {
+
+class ServiceWorkerManagerChild final : public PServiceWorkerManagerChild
+{
+ friend class mozilla::ipc::BackgroundChildImpl;
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(ServiceWorkerManagerChild)
+
+ void ManagerShuttingDown()
+ {
+ mShuttingDown = true;
+ }
+
+ virtual bool RecvNotifyRegister(const ServiceWorkerRegistrationData& aData)
+ override;
+
+ virtual bool RecvNotifySoftUpdate(const PrincipalOriginAttributes& aOriginAttributes,
+ const nsString& aScope) override;
+
+ virtual bool RecvNotifyUnregister(const PrincipalInfo& aPrincipalInfo,
+ const nsString& aScope) override;
+
+ virtual bool RecvNotifyRemove(const nsCString& aHost) override;
+
+ virtual bool RecvNotifyRemoveAll() override;
+
+private:
+ ServiceWorkerManagerChild()
+ : mShuttingDown(false)
+ {}
+
+ ~ServiceWorkerManagerChild() {}
+
+ bool mShuttingDown;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ServiceWorkerManagerChild_h
diff --git a/dom/workers/ServiceWorkerManagerParent.cpp b/dom/workers/ServiceWorkerManagerParent.cpp
new file mode 100644
index 000000000..bd9afad7a
--- /dev/null
+++ b/dom/workers/ServiceWorkerManagerParent.cpp
@@ -0,0 +1,330 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerManagerParent.h"
+#include "ServiceWorkerManagerService.h"
+#include "mozilla/AppProcessChecker.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ServiceWorkerRegistrar.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/Unused.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+namespace workers {
+
+namespace {
+
+uint64_t sServiceWorkerManagerParentID = 0;
+
+class RegisterServiceWorkerCallback final : public Runnable
+{
+public:
+ RegisterServiceWorkerCallback(const ServiceWorkerRegistrationData& aData,
+ uint64_t aParentID)
+ : mData(aData)
+ , mParentID(aParentID)
+ {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<dom::ServiceWorkerRegistrar> service =
+ dom::ServiceWorkerRegistrar::Get();
+
+ // Shutdown during the process of trying to update the registrar. Give
+ // up on this modification.
+ if (!service) {
+ return NS_OK;
+ }
+
+ service->RegisterServiceWorker(mData);
+
+ RefPtr<ServiceWorkerManagerService> managerService =
+ ServiceWorkerManagerService::Get();
+ if (managerService) {
+ managerService->PropagateRegistration(mParentID, mData);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ ServiceWorkerRegistrationData mData;
+ const uint64_t mParentID;
+};
+
+class UnregisterServiceWorkerCallback final : public Runnable
+{
+public:
+ UnregisterServiceWorkerCallback(const PrincipalInfo& aPrincipalInfo,
+ const nsString& aScope,
+ uint64_t aParentID)
+ : mPrincipalInfo(aPrincipalInfo)
+ , mScope(aScope)
+ , mParentID(aParentID)
+ {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<dom::ServiceWorkerRegistrar> service =
+ dom::ServiceWorkerRegistrar::Get();
+
+ // Shutdown during the process of trying to update the registrar. Give
+ // up on this modification.
+ if (!service) {
+ return NS_OK;
+ }
+
+ service->UnregisterServiceWorker(mPrincipalInfo,
+ NS_ConvertUTF16toUTF8(mScope));
+
+ RefPtr<ServiceWorkerManagerService> managerService =
+ ServiceWorkerManagerService::Get();
+ if (managerService) {
+ managerService->PropagateUnregister(mParentID, mPrincipalInfo,
+ mScope);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ const PrincipalInfo mPrincipalInfo;
+ nsString mScope;
+ uint64_t mParentID;
+};
+
+class CheckPrincipalWithCallbackRunnable final : public Runnable
+{
+public:
+ CheckPrincipalWithCallbackRunnable(already_AddRefed<ContentParent> aParent,
+ const PrincipalInfo& aPrincipalInfo,
+ Runnable* aCallback)
+ : mContentParent(aParent)
+ , mPrincipalInfo(aPrincipalInfo)
+ , mCallback(aCallback)
+ , mBackgroundThread(NS_GetCurrentThread())
+ {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ MOZ_ASSERT(mContentParent);
+ MOZ_ASSERT(mCallback);
+ MOZ_ASSERT(mBackgroundThread);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(mPrincipalInfo);
+ AssertAppPrincipal(mContentParent, principal);
+ mContentParent = nullptr;
+
+ mBackgroundThread->Dispatch(this, NS_DISPATCH_NORMAL);
+ return NS_OK;
+ }
+
+ AssertIsOnBackgroundThread();
+ mCallback->Run();
+ mCallback = nullptr;
+
+ return NS_OK;
+ }
+
+private:
+ RefPtr<ContentParent> mContentParent;
+ PrincipalInfo mPrincipalInfo;
+ RefPtr<Runnable> mCallback;
+ nsCOMPtr<nsIThread> mBackgroundThread;
+};
+
+} // namespace
+
+ServiceWorkerManagerParent::ServiceWorkerManagerParent()
+ : mService(ServiceWorkerManagerService::GetOrCreate())
+ , mID(++sServiceWorkerManagerParentID)
+{
+ AssertIsOnBackgroundThread();
+ mService->RegisterActor(this);
+}
+
+ServiceWorkerManagerParent::~ServiceWorkerManagerParent()
+{
+ AssertIsOnBackgroundThread();
+}
+
+bool
+ServiceWorkerManagerParent::RecvRegister(
+ const ServiceWorkerRegistrationData& aData)
+{
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ // Basic validation.
+ if (aData.scope().IsEmpty() ||
+ aData.principal().type() == PrincipalInfo::TNullPrincipalInfo ||
+ aData.principal().type() == PrincipalInfo::TSystemPrincipalInfo) {
+ return false;
+ }
+
+ RefPtr<RegisterServiceWorkerCallback> callback =
+ new RegisterServiceWorkerCallback(aData, mID);
+
+ RefPtr<ContentParent> parent =
+ BackgroundParent::GetContentParent(Manager());
+
+ // If the ContentParent is null we are dealing with a same-process actor.
+ if (!parent) {
+ callback->Run();
+ return true;
+ }
+
+ RefPtr<CheckPrincipalWithCallbackRunnable> runnable =
+ new CheckPrincipalWithCallbackRunnable(parent.forget(), aData.principal(),
+ callback);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+
+ return true;
+}
+
+bool
+ServiceWorkerManagerParent::RecvUnregister(const PrincipalInfo& aPrincipalInfo,
+ const nsString& aScope)
+{
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ // Basic validation.
+ if (aScope.IsEmpty() ||
+ aPrincipalInfo.type() == PrincipalInfo::TNullPrincipalInfo ||
+ aPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
+ return false;
+ }
+
+ RefPtr<UnregisterServiceWorkerCallback> callback =
+ new UnregisterServiceWorkerCallback(aPrincipalInfo, aScope, mID);
+
+ RefPtr<ContentParent> parent =
+ BackgroundParent::GetContentParent(Manager());
+
+ // If the ContentParent is null we are dealing with a same-process actor.
+ if (!parent) {
+ callback->Run();
+ return true;
+ }
+
+ RefPtr<CheckPrincipalWithCallbackRunnable> runnable =
+ new CheckPrincipalWithCallbackRunnable(parent.forget(), aPrincipalInfo,
+ callback);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+
+ return true;
+}
+
+bool
+ServiceWorkerManagerParent::RecvPropagateSoftUpdate(const PrincipalOriginAttributes& aOriginAttributes,
+ const nsString& aScope)
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!mService)) {
+ return false;
+ }
+
+ mService->PropagateSoftUpdate(mID, aOriginAttributes, aScope);
+ return true;
+}
+
+bool
+ServiceWorkerManagerParent::RecvPropagateUnregister(const PrincipalInfo& aPrincipalInfo,
+ const nsString& aScope)
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!mService)) {
+ return false;
+ }
+
+ mService->PropagateUnregister(mID, aPrincipalInfo, aScope);
+ return true;
+}
+
+bool
+ServiceWorkerManagerParent::RecvPropagateRemove(const nsCString& aHost)
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!mService)) {
+ return false;
+ }
+
+ mService->PropagateRemove(mID, aHost);
+ return true;
+}
+
+bool
+ServiceWorkerManagerParent::RecvPropagateRemoveAll()
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!mService)) {
+ return false;
+ }
+
+ mService->PropagateRemoveAll(mID);
+ return true;
+}
+
+bool
+ServiceWorkerManagerParent::RecvShutdown()
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(!mService)) {
+ return false;
+ }
+
+ mService->UnregisterActor(this);
+ mService = nullptr;
+
+ Unused << Send__delete__(this);
+ return true;
+}
+
+void
+ServiceWorkerManagerParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnBackgroundThread();
+
+ if (mService) {
+ // This object is about to be released and with it, also mService will be
+ // released too.
+ mService->UnregisterActor(this);
+ }
+}
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/ServiceWorkerManagerParent.h b/dom/workers/ServiceWorkerManagerParent.h
new file mode 100644
index 000000000..83a0a97aa
--- /dev/null
+++ b/dom/workers/ServiceWorkerManagerParent.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ServiceWorkerManagerParent_h
+#define mozilla_dom_ServiceWorkerManagerParent_h
+
+#include "mozilla/dom/PServiceWorkerManagerParent.h"
+
+namespace mozilla {
+
+class PrincipalOriginAttributes;
+
+namespace ipc {
+class BackgroundParentImpl;
+} // namespace ipc
+
+namespace dom {
+namespace workers {
+
+class ServiceWorkerManagerService;
+
+class ServiceWorkerManagerParent final : public PServiceWorkerManagerParent
+{
+ friend class mozilla::ipc::BackgroundParentImpl;
+
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ServiceWorkerManagerParent)
+
+ uint64_t ID() const
+ {
+ return mID;
+ }
+
+private:
+ ServiceWorkerManagerParent();
+ ~ServiceWorkerManagerParent();
+
+ virtual bool RecvRegister(
+ const ServiceWorkerRegistrationData& aData) override;
+
+ virtual bool RecvUnregister(const PrincipalInfo& aPrincipalInfo,
+ const nsString& aScope) override;
+
+ virtual bool RecvPropagateSoftUpdate(const PrincipalOriginAttributes& aOriginAttributes,
+ const nsString& aScope) override;
+
+ virtual bool RecvPropagateUnregister(const PrincipalInfo& aPrincipalInfo,
+ const nsString& aScope) override;
+
+ virtual bool RecvPropagateRemove(const nsCString& aHost) override;
+
+ virtual bool RecvPropagateRemoveAll() override;
+
+ virtual bool RecvShutdown() override;
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ RefPtr<ServiceWorkerManagerService> mService;
+
+ // We use this ID in the Service in order to avoid the sending of messages to
+ // ourself.
+ uint64_t mID;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ServiceWorkerManagerParent_h
diff --git a/dom/workers/ServiceWorkerManagerService.cpp b/dom/workers/ServiceWorkerManagerService.cpp
new file mode 100644
index 000000000..983bd77db
--- /dev/null
+++ b/dom/workers/ServiceWorkerManagerService.cpp
@@ -0,0 +1,237 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerManagerService.h"
+#include "ServiceWorkerManagerParent.h"
+#include "ServiceWorkerRegistrar.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/Unused.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+namespace workers {
+
+namespace {
+
+ServiceWorkerManagerService* sInstance = nullptr;
+
+} // namespace
+
+ServiceWorkerManagerService::ServiceWorkerManagerService()
+{
+ AssertIsOnBackgroundThread();
+
+ // sInstance is a raw ServiceWorkerManagerService*.
+ MOZ_ASSERT(!sInstance);
+ sInstance = this;
+}
+
+ServiceWorkerManagerService::~ServiceWorkerManagerService()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(sInstance == this);
+ MOZ_ASSERT(mAgents.Count() == 0);
+
+ sInstance = nullptr;
+}
+
+/* static */ already_AddRefed<ServiceWorkerManagerService>
+ServiceWorkerManagerService::Get()
+{
+ AssertIsOnBackgroundThread();
+
+ RefPtr<ServiceWorkerManagerService> instance = sInstance;
+ return instance.forget();
+}
+
+/* static */ already_AddRefed<ServiceWorkerManagerService>
+ServiceWorkerManagerService::GetOrCreate()
+{
+ AssertIsOnBackgroundThread();
+
+ RefPtr<ServiceWorkerManagerService> instance = sInstance;
+ if (!instance) {
+ instance = new ServiceWorkerManagerService();
+ }
+ return instance.forget();
+}
+
+void
+ServiceWorkerManagerService::RegisterActor(ServiceWorkerManagerParent* aParent)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParent);
+ MOZ_ASSERT(!mAgents.Contains(aParent));
+
+ mAgents.PutEntry(aParent);
+}
+
+void
+ServiceWorkerManagerService::UnregisterActor(ServiceWorkerManagerParent* aParent)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParent);
+ MOZ_ASSERT(mAgents.Contains(aParent));
+
+ mAgents.RemoveEntry(aParent);
+}
+
+void
+ServiceWorkerManagerService::PropagateRegistration(
+ uint64_t aParentID,
+ ServiceWorkerRegistrationData& aData)
+{
+ AssertIsOnBackgroundThread();
+
+ DebugOnly<bool> parentFound = false;
+ for (auto iter = mAgents.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ServiceWorkerManagerParent> parent = iter.Get()->GetKey();
+ MOZ_ASSERT(parent);
+
+ if (parent->ID() != aParentID) {
+ Unused << parent->SendNotifyRegister(aData);
+#ifdef DEBUG
+ } else {
+ parentFound = true;
+#endif
+ }
+ }
+
+#ifdef DEBUG
+ MOZ_ASSERT(parentFound);
+#endif
+}
+
+void
+ServiceWorkerManagerService::PropagateSoftUpdate(
+ uint64_t aParentID,
+ const PrincipalOriginAttributes& aOriginAttributes,
+ const nsAString& aScope)
+{
+ AssertIsOnBackgroundThread();
+
+ DebugOnly<bool> parentFound = false;
+ for (auto iter = mAgents.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ServiceWorkerManagerParent> parent = iter.Get()->GetKey();
+ MOZ_ASSERT(parent);
+
+ nsString scope(aScope);
+ Unused << parent->SendNotifySoftUpdate(aOriginAttributes,
+ scope);
+
+#ifdef DEBUG
+ if (parent->ID() == aParentID) {
+ parentFound = true;
+ }
+#endif
+ }
+
+#ifdef DEBUG
+ MOZ_ASSERT(parentFound);
+#endif
+}
+
+void
+ServiceWorkerManagerService::PropagateUnregister(
+ uint64_t aParentID,
+ const PrincipalInfo& aPrincipalInfo,
+ const nsAString& aScope)
+{
+ AssertIsOnBackgroundThread();
+
+ RefPtr<dom::ServiceWorkerRegistrar> service =
+ dom::ServiceWorkerRegistrar::Get();
+ MOZ_ASSERT(service);
+
+ // It's possible that we don't have any ServiceWorkerManager managing this
+ // scope but we still need to unregister it from the ServiceWorkerRegistrar.
+ service->UnregisterServiceWorker(aPrincipalInfo,
+ NS_ConvertUTF16toUTF8(aScope));
+
+ DebugOnly<bool> parentFound = false;
+ for (auto iter = mAgents.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ServiceWorkerManagerParent> parent = iter.Get()->GetKey();
+ MOZ_ASSERT(parent);
+
+ if (parent->ID() != aParentID) {
+ nsString scope(aScope);
+ Unused << parent->SendNotifyUnregister(aPrincipalInfo, scope);
+#ifdef DEBUG
+ } else {
+ parentFound = true;
+#endif
+ }
+ }
+
+#ifdef DEBUG
+ MOZ_ASSERT(parentFound);
+#endif
+}
+
+void
+ServiceWorkerManagerService::PropagateRemove(uint64_t aParentID,
+ const nsACString& aHost)
+{
+ AssertIsOnBackgroundThread();
+
+ DebugOnly<bool> parentFound = false;
+ for (auto iter = mAgents.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ServiceWorkerManagerParent> parent = iter.Get()->GetKey();
+ MOZ_ASSERT(parent);
+
+ if (parent->ID() != aParentID) {
+ nsCString host(aHost);
+ Unused << parent->SendNotifyRemove(host);
+#ifdef DEBUG
+ } else {
+ parentFound = true;
+#endif
+ }
+ }
+
+#ifdef DEBUG
+ MOZ_ASSERT(parentFound);
+#endif
+}
+
+void
+ServiceWorkerManagerService::PropagateRemoveAll(uint64_t aParentID)
+{
+ AssertIsOnBackgroundThread();
+
+ RefPtr<dom::ServiceWorkerRegistrar> service =
+ dom::ServiceWorkerRegistrar::Get();
+ MOZ_ASSERT(service);
+
+ service->RemoveAll();
+
+ DebugOnly<bool> parentFound = false;
+ for (auto iter = mAgents.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ServiceWorkerManagerParent> parent = iter.Get()->GetKey();
+ MOZ_ASSERT(parent);
+
+ if (parent->ID() != aParentID) {
+ Unused << parent->SendNotifyRemoveAll();
+#ifdef DEBUG
+ } else {
+ parentFound = true;
+#endif
+ }
+ }
+
+#ifdef DEBUG
+ MOZ_ASSERT(parentFound);
+#endif
+}
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/ServiceWorkerManagerService.h b/dom/workers/ServiceWorkerManagerService.h
new file mode 100644
index 000000000..3f3f760e4
--- /dev/null
+++ b/dom/workers/ServiceWorkerManagerService.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ServiceWorkerManagerService_h
+#define mozilla_dom_ServiceWorkerManagerService_h
+
+#include "nsISupportsImpl.h"
+#include "nsHashKeys.h"
+#include "nsTHashtable.h"
+
+namespace mozilla {
+
+class PrincipalOriginAttributes;
+
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+class ServiceWorkerRegistrationData;
+
+namespace workers {
+
+class ServiceWorkerManagerParent;
+
+class ServiceWorkerManagerService final
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(ServiceWorkerManagerService)
+
+ static already_AddRefed<ServiceWorkerManagerService> Get();
+ static already_AddRefed<ServiceWorkerManagerService> GetOrCreate();
+
+ void RegisterActor(ServiceWorkerManagerParent* aParent);
+ void UnregisterActor(ServiceWorkerManagerParent* aParent);
+
+ void PropagateRegistration(uint64_t aParentID,
+ ServiceWorkerRegistrationData& aData);
+
+ void PropagateSoftUpdate(uint64_t aParentID,
+ const PrincipalOriginAttributes& aOriginAttributes,
+ const nsAString& aScope);
+
+ void PropagateUnregister(uint64_t aParentID,
+ const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+ const nsAString& aScope);
+
+ void PropagateRemove(uint64_t aParentID, const nsACString& aHost);
+
+ void PropagateRemoveAll(uint64_t aParentID);
+
+private:
+ ServiceWorkerManagerService();
+ ~ServiceWorkerManagerService();
+
+ nsTHashtable<nsPtrHashKey<ServiceWorkerManagerParent>> mAgents;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ServiceWorkerManagerService_h
diff --git a/dom/workers/ServiceWorkerPrivate.cpp b/dom/workers/ServiceWorkerPrivate.cpp
new file mode 100644
index 000000000..eaa548f95
--- /dev/null
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -0,0 +1,2088 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerPrivate.h"
+
+#include "ServiceWorkerManager.h"
+#include "nsContentUtils.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIPushErrorReporter.h"
+#include "nsISupportsImpl.h"
+#include "nsIUploadChannel2.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "nsQueryObject.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "WorkerRunnable.h"
+#include "WorkerScope.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/FetchUtil.h"
+#include "mozilla/dom/IndexedDatabaseManager.h"
+#include "mozilla/dom/InternalHeaders.h"
+#include "mozilla/dom/NotificationEvent.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/PushEventBinding.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/Unused.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+BEGIN_WORKERS_NAMESPACE
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerPrivate)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ServiceWorkerPrivate)
+NS_IMPL_CYCLE_COLLECTION(ServiceWorkerPrivate, mSupportsArray)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerPrivate)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+NS_INTERFACE_MAP_END
+
+// Tracks the "dom.disable_open_click_delay" preference. Modified on main
+// thread, read on worker threads.
+// It is updated every time a "notificationclick" event is dispatched. While
+// this is done without synchronization, at the worst, the thread will just get
+// an older value within which a popup is allowed to be displayed, which will
+// still be a valid value since it was set prior to dispatching the runnable.
+Atomic<uint32_t> gDOMDisableOpenClickDelay(0);
+
+// Used to keep track of pending waitUntil as well as in-flight extendable events.
+// When the last token is released, we attempt to terminate the worker.
+class KeepAliveToken final : public nsISupports
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit KeepAliveToken(ServiceWorkerPrivate* aPrivate)
+ : mPrivate(aPrivate)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrivate);
+ mPrivate->AddToken();
+ }
+
+private:
+ ~KeepAliveToken()
+ {
+ AssertIsOnMainThread();
+ mPrivate->ReleaseToken();
+ }
+
+ RefPtr<ServiceWorkerPrivate> mPrivate;
+};
+
+NS_IMPL_ISUPPORTS0(KeepAliveToken)
+
+ServiceWorkerPrivate::ServiceWorkerPrivate(ServiceWorkerInfo* aInfo)
+ : mInfo(aInfo)
+ , mDebuggerCount(0)
+ , mTokenCount(0)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aInfo);
+
+ mIdleWorkerTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ MOZ_ASSERT(mIdleWorkerTimer);
+}
+
+ServiceWorkerPrivate::~ServiceWorkerPrivate()
+{
+ MOZ_ASSERT(!mWorkerPrivate);
+ MOZ_ASSERT(!mTokenCount);
+ MOZ_ASSERT(!mInfo);
+ MOZ_ASSERT(mSupportsArray.IsEmpty());
+
+ mIdleWorkerTimer->Cancel();
+}
+
+namespace {
+
+class MessageWaitUntilHandler final : public PromiseNativeHandler
+{
+ nsMainThreadPtrHandle<nsISupports> mKeepAliveToken;
+
+ ~MessageWaitUntilHandler()
+ {
+ }
+
+public:
+ explicit MessageWaitUntilHandler(const nsMainThreadPtrHandle<nsISupports>& aKeepAliveToken)
+ : mKeepAliveToken(aKeepAliveToken)
+ {
+ MOZ_ASSERT(mKeepAliveToken);
+ }
+
+ void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ mKeepAliveToken = nullptr;
+ }
+
+ void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ mKeepAliveToken = nullptr;
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS0(MessageWaitUntilHandler)
+
+} // anonymous namespace
+
+nsresult
+ServiceWorkerPrivate::SendMessageEvent(JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ UniquePtr<ServiceWorkerClientInfo>&& aClientInfo)
+{
+ ErrorResult rv(SpawnWorkerIfNeeded(MessageEvent, nullptr));
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ nsMainThreadPtrHandle<nsISupports> token(
+ new nsMainThreadPtrHolder<nsISupports>(CreateEventKeepAliveToken()));
+
+ RefPtr<PromiseNativeHandler> handler = new MessageWaitUntilHandler(token);
+
+ mWorkerPrivate->PostMessageToServiceWorker(aCx, aMessage, aTransferable,
+ Move(aClientInfo), handler,
+ rv);
+ return rv.StealNSResult();
+}
+
+namespace {
+
+class CheckScriptEvaluationWithCallback final : public WorkerRunnable
+{
+ nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;
+ RefPtr<LifeCycleEventCallback> mCallback;
+#ifdef DEBUG
+ bool mDone;
+#endif
+
+public:
+ CheckScriptEvaluationWithCallback(WorkerPrivate* aWorkerPrivate,
+ KeepAliveToken* aKeepAliveToken,
+ LifeCycleEventCallback* aCallback)
+ : WorkerRunnable(aWorkerPrivate)
+ , mKeepAliveToken(new nsMainThreadPtrHolder<KeepAliveToken>(aKeepAliveToken))
+ , mCallback(aCallback)
+#ifdef DEBUG
+ , mDone(false)
+#endif
+ {
+ AssertIsOnMainThread();
+ }
+
+ ~CheckScriptEvaluationWithCallback()
+ {
+ MOZ_ASSERT(mDone);
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ Done(aWorkerPrivate->WorkerScriptExecutedSuccessfully());
+
+ return true;
+ }
+
+ nsresult
+ Cancel() override
+ {
+ Done(false);
+ return WorkerRunnable::Cancel();
+ }
+
+private:
+ void
+ Done(bool aResult)
+ {
+#ifdef DEBUG
+ mDone = true;
+#endif
+ mCallback->SetResult(aResult);
+ MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(mCallback));
+ }
+};
+
+} // anonymous namespace
+
+nsresult
+ServiceWorkerPrivate::CheckScriptEvaluation(LifeCycleEventCallback* aCallback)
+{
+ nsresult rv = SpawnWorkerIfNeeded(LifeCycleEvent, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
+ RefPtr<WorkerRunnable> r = new CheckScriptEvaluationWithCallback(mWorkerPrivate,
+ token,
+ aCallback);
+ if (NS_WARN_IF(!r->Dispatch())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+namespace {
+
+// Holds the worker alive until the waitUntil promise is resolved or
+// rejected.
+class KeepAliveHandler final
+{
+ // Use an internal class to listen for the promise resolve/reject
+ // callbacks. This class also registers a feature so that it can
+ // preemptively cleanup if the service worker is timed out and
+ // terminated.
+ class InternalHandler final : public PromiseNativeHandler
+ , public WorkerHolder
+ {
+ nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;
+
+ // Worker thread only
+ WorkerPrivate* mWorkerPrivate;
+ RefPtr<Promise> mPromise;
+ bool mWorkerHolderAdded;
+
+ ~InternalHandler()
+ {
+ MaybeCleanup();
+ }
+
+ bool
+ UseWorkerHolder()
+ {
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(!mWorkerHolderAdded);
+ mWorkerHolderAdded = HoldWorker(mWorkerPrivate, Terminating);
+ return mWorkerHolderAdded;
+ }
+
+ void
+ MaybeCleanup()
+ {
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ if (!mPromise) {
+ return;
+ }
+ if (mWorkerHolderAdded) {
+ ReleaseWorker();
+ }
+ mPromise = nullptr;
+ mKeepAliveToken = nullptr;
+ }
+
+ void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MaybeCleanup();
+ }
+
+ void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MaybeCleanup();
+ }
+
+ bool
+ Notify(Status aStatus) override
+ {
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ if (aStatus < Terminating) {
+ return true;
+ }
+ MaybeCleanup();
+ return true;
+ }
+
+ InternalHandler(const nsMainThreadPtrHandle<KeepAliveToken>& aKeepAliveToken,
+ WorkerPrivate* aWorkerPrivate,
+ Promise* aPromise)
+ : mKeepAliveToken(aKeepAliveToken)
+ , mWorkerPrivate(aWorkerPrivate)
+ , mPromise(aPromise)
+ , mWorkerHolderAdded(false)
+ {
+ MOZ_ASSERT(mKeepAliveToken);
+ MOZ_ASSERT(mWorkerPrivate);
+ MOZ_ASSERT(mPromise);
+ }
+
+ public:
+ static already_AddRefed<InternalHandler>
+ Create(const nsMainThreadPtrHandle<KeepAliveToken>& aKeepAliveToken,
+ WorkerPrivate* aWorkerPrivate,
+ Promise* aPromise)
+ {
+ RefPtr<InternalHandler> ref = new InternalHandler(aKeepAliveToken,
+ aWorkerPrivate,
+ aPromise);
+
+ if (NS_WARN_IF(!ref->UseWorkerHolder())) {
+ return nullptr;
+ }
+
+ return ref.forget();
+ }
+
+ NS_DECL_ISUPPORTS
+ };
+
+ // This is really just a wrapper class to keep the InternalHandler
+ // private. We don't want any code to accidentally call
+ // Promise::AppendNativeHandler() without also referencing the promise.
+ // Therefore we force all code through the static CreateAndAttachToPromise()
+ // and use the private InternalHandler object.
+ KeepAliveHandler() = delete;
+ ~KeepAliveHandler() = delete;
+
+public:
+ // Create a private handler object and attach it to the given Promise.
+ // This will also create a strong ref to the Promise in a ref cycle. The
+ // ref cycle is broken when the Promise is fulfilled or the worker thread
+ // is Terminated.
+ static void
+ CreateAndAttachToPromise(const nsMainThreadPtrHandle<KeepAliveToken>& aKeepAliveToken,
+ Promise* aPromise)
+ {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ workerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(aKeepAliveToken);
+ MOZ_ASSERT(aPromise);
+
+ // This creates a strong ref to the promise.
+ RefPtr<InternalHandler> handler = InternalHandler::Create(aKeepAliveToken,
+ workerPrivate,
+ aPromise);
+ if (NS_WARN_IF(!handler)) {
+ return;
+ }
+
+ // This then creates a strong ref cycle between the promise and the
+ // handler. The cycle is broken when the Promise is fulfilled or
+ // the worker thread is Terminated.
+ aPromise->AppendNativeHandler(handler);
+ }
+};
+
+NS_IMPL_ISUPPORTS0(KeepAliveHandler::InternalHandler)
+
+class RegistrationUpdateRunnable : public Runnable
+{
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
+ const bool mNeedTimeCheck;
+
+public:
+ RegistrationUpdateRunnable(nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
+ bool aNeedTimeCheck)
+ : mRegistration(aRegistration)
+ , mNeedTimeCheck(aNeedTimeCheck)
+ {
+ MOZ_DIAGNOSTIC_ASSERT(mRegistration);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ if (mNeedTimeCheck) {
+ mRegistration->MaybeScheduleTimeCheckAndUpdate();
+ } else {
+ mRegistration->MaybeScheduleUpdate();
+ }
+ return NS_OK;
+ }
+};
+
+class ExtendableEventWorkerRunnable : public WorkerRunnable
+{
+protected:
+ nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;
+
+public:
+ ExtendableEventWorkerRunnable(WorkerPrivate* aWorkerPrivate,
+ KeepAliveToken* aKeepAliveToken)
+ : WorkerRunnable(aWorkerPrivate)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aKeepAliveToken);
+
+ mKeepAliveToken =
+ new nsMainThreadPtrHolder<KeepAliveToken>(aKeepAliveToken);
+ }
+
+ bool
+ DispatchExtendableEventOnWorkerScope(JSContext* aCx,
+ WorkerGlobalScope* aWorkerScope,
+ ExtendableEvent* aEvent,
+ PromiseNativeHandler* aPromiseHandler)
+ {
+ MOZ_ASSERT(aWorkerScope);
+ MOZ_ASSERT(aEvent);
+ nsCOMPtr<nsIGlobalObject> sgo = aWorkerScope;
+ WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
+
+ ErrorResult result;
+ result = aWorkerScope->DispatchDOMEvent(nullptr, aEvent, nullptr, nullptr);
+ if (NS_WARN_IF(result.Failed()) || internalEvent->mFlags.mExceptionWasRaised) {
+ result.SuppressException();
+ return false;
+ }
+
+ RefPtr<Promise> waitUntilPromise = aEvent->GetPromise();
+ if (!waitUntilPromise) {
+ waitUntilPromise =
+ Promise::Resolve(sgo, aCx, JS::UndefinedHandleValue, result);
+ MOZ_RELEASE_ASSERT(!result.Failed());
+ }
+
+ MOZ_ASSERT(waitUntilPromise);
+
+ // Make sure to append the caller's promise handler before attaching
+ // our keep alive handler. This can avoid terminating the worker
+ // before a success result is delivered to the caller in cases where
+ // the idle timeout has been set to zero. This low timeout value is
+ // sometimes set in tests.
+ if (aPromiseHandler) {
+ waitUntilPromise->AppendNativeHandler(aPromiseHandler);
+ }
+
+ KeepAliveHandler::CreateAndAttachToPromise(mKeepAliveToken,
+ waitUntilPromise);
+
+ return true;
+ }
+};
+
+// Handle functional event
+// 9.9.7 If the time difference in seconds calculated by the current time minus
+// registration's last update check time is greater than 86400, invoke Soft Update
+// algorithm.
+class ExtendableFunctionalEventWorkerRunnable : public ExtendableEventWorkerRunnable
+{
+protected:
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
+public:
+ ExtendableFunctionalEventWorkerRunnable(WorkerPrivate* aWorkerPrivate,
+ KeepAliveToken* aKeepAliveToken,
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
+ : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
+ , mRegistration(aRegistration)
+ {
+ MOZ_DIAGNOSTIC_ASSERT(aRegistration);
+ }
+
+ void
+ PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
+ {
+ // Sub-class PreRun() or WorkerRun() methods could clear our mRegistration.
+ if (mRegistration) {
+ nsCOMPtr<nsIRunnable> runnable =
+ new RegistrationUpdateRunnable(mRegistration, true /* time check */);
+ aWorkerPrivate->DispatchToMainThread(runnable.forget());
+ }
+
+ ExtendableEventWorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
+ }
+};
+
+/*
+ * Fires 'install' event on the ServiceWorkerGlobalScope. Modifies busy count
+ * since it fires the event. This is ok since there can't be nested
+ * ServiceWorkers, so the parent thread -> worker thread requirement for
+ * runnables is satisfied.
+ */
+class LifecycleEventWorkerRunnable : public ExtendableEventWorkerRunnable
+{
+ nsString mEventName;
+ RefPtr<LifeCycleEventCallback> mCallback;
+
+public:
+ LifecycleEventWorkerRunnable(WorkerPrivate* aWorkerPrivate,
+ KeepAliveToken* aToken,
+ const nsAString& aEventName,
+ LifeCycleEventCallback* aCallback)
+ : ExtendableEventWorkerRunnable(aWorkerPrivate, aToken)
+ , mEventName(aEventName)
+ , mCallback(aCallback)
+ {
+ AssertIsOnMainThread();
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ return DispatchLifecycleEvent(aCx, aWorkerPrivate);
+ }
+
+ nsresult
+ Cancel() override
+ {
+ mCallback->SetResult(false);
+ MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(mCallback));
+
+ return WorkerRunnable::Cancel();
+ }
+
+private:
+ bool
+ DispatchLifecycleEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
+
+};
+
+/*
+ * Used to handle ExtendableEvent::waitUntil() and catch abnormal worker
+ * termination during the execution of life cycle events. It is responsible
+ * with advancing the job queue for install/activate tasks.
+ */
+class LifeCycleEventWatcher final : public PromiseNativeHandler,
+ public WorkerHolder
+{
+ WorkerPrivate* mWorkerPrivate;
+ RefPtr<LifeCycleEventCallback> mCallback;
+ bool mDone;
+
+ ~LifeCycleEventWatcher()
+ {
+ if (mDone) {
+ return;
+ }
+
+ MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate);
+ // XXXcatalinb: If all the promises passed to waitUntil go out of scope,
+ // the resulting Promise.all will be cycle collected and it will drop its
+ // native handlers (including this object). Instead of waiting for a timeout
+ // we report the failure now.
+ ReportResult(false);
+ }
+
+public:
+ NS_DECL_ISUPPORTS
+
+ LifeCycleEventWatcher(WorkerPrivate* aWorkerPrivate,
+ LifeCycleEventCallback* aCallback)
+ : mWorkerPrivate(aWorkerPrivate)
+ , mCallback(aCallback)
+ , mDone(false)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ bool
+ Init()
+ {
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ // We need to listen for worker termination in case the event handler
+ // never completes or never resolves the waitUntil promise. There are
+ // two possible scenarios:
+ // 1. The keepAlive token expires and the worker is terminated, in which
+ // case the registration/update promise will be rejected
+ // 2. A new service worker is registered which will terminate the current
+ // installing worker.
+ if (NS_WARN_IF(!HoldWorker(mWorkerPrivate, Terminating))) {
+ NS_WARNING("LifeCycleEventWatcher failed to add feature.");
+ ReportResult(false);
+ return false;
+ }
+
+ return true;
+ }
+
+ bool
+ Notify(Status aStatus) override
+ {
+ if (aStatus < Terminating) {
+ return true;
+ }
+
+ MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate);
+ ReportResult(false);
+
+ return true;
+ }
+
+ void
+ ReportResult(bool aResult)
+ {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mDone) {
+ return;
+ }
+ mDone = true;
+
+ mCallback->SetResult(aResult);
+ nsresult rv = mWorkerPrivate->DispatchToMainThread(mCallback);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ NS_RUNTIMEABORT("Failed to dispatch life cycle event handler.");
+ }
+
+ ReleaseWorker();
+ }
+
+ void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ ReportResult(true);
+ }
+
+ void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ ReportResult(false);
+
+ // Note, all WaitUntil() rejections are reported to client consoles
+ // by the WaitUntilHandler in ServiceWorkerEvents. This ensures that
+ // errors in non-lifecycle events like FetchEvent and PushEvent are
+ // reported properly.
+ }
+};
+
+NS_IMPL_ISUPPORTS0(LifeCycleEventWatcher)
+
+bool
+LifecycleEventWorkerRunnable::DispatchLifecycleEvent(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate)
+{
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
+
+ RefPtr<ExtendableEvent> event;
+ RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
+
+ if (mEventName.EqualsASCII("install") || mEventName.EqualsASCII("activate")) {
+ ExtendableEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ event = ExtendableEvent::Constructor(target, mEventName, init);
+ } else {
+ MOZ_CRASH("Unexpected lifecycle event");
+ }
+
+ event->SetTrusted(true);
+
+ // It is important to initialize the watcher before actually dispatching
+ // the event in order to catch worker termination while the event handler
+ // is still executing. This can happen with infinite loops, for example.
+ RefPtr<LifeCycleEventWatcher> watcher =
+ new LifeCycleEventWatcher(aWorkerPrivate, mCallback);
+
+ if (!watcher->Init()) {
+ return true;
+ }
+
+ if (!DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(),
+ event, watcher)) {
+ watcher->ReportResult(false);
+ }
+
+ return true;
+}
+
+} // anonymous namespace
+
+nsresult
+ServiceWorkerPrivate::SendLifeCycleEvent(const nsAString& aEventType,
+ LifeCycleEventCallback* aCallback,
+ nsIRunnable* aLoadFailure)
+{
+ nsresult rv = SpawnWorkerIfNeeded(LifeCycleEvent, aLoadFailure);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
+ RefPtr<WorkerRunnable> r = new LifecycleEventWorkerRunnable(mWorkerPrivate,
+ token,
+ aEventType,
+ aCallback);
+ if (NS_WARN_IF(!r->Dispatch())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+namespace {
+
+class PushErrorReporter final : public PromiseNativeHandler
+{
+ WorkerPrivate* mWorkerPrivate;
+ nsString mMessageId;
+
+ ~PushErrorReporter()
+ {
+ }
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ PushErrorReporter(WorkerPrivate* aWorkerPrivate,
+ const nsAString& aMessageId)
+ : mWorkerPrivate(aWorkerPrivate)
+ , mMessageId(aMessageId)
+ {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ mWorkerPrivate = nullptr;
+ // Do nothing; we only use this to report errors to the Push service.
+ }
+
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ Report(nsIPushErrorReporter::DELIVERY_UNHANDLED_REJECTION);
+ }
+
+ void Report(uint16_t aReason = nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR)
+ {
+ WorkerPrivate* workerPrivate = mWorkerPrivate;
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ mWorkerPrivate = nullptr;
+
+ if (NS_WARN_IF(aReason > nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR) ||
+ mMessageId.IsEmpty()) {
+ return;
+ }
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod<uint16_t>(this,
+ &PushErrorReporter::ReportOnMainThread, aReason);
+ MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+ workerPrivate->DispatchToMainThread(runnable.forget())));
+ }
+
+ void ReportOnMainThread(uint16_t aReason)
+ {
+ AssertIsOnMainThread();
+ nsCOMPtr<nsIPushErrorReporter> reporter =
+ do_GetService("@mozilla.org/push/Service;1");
+ if (reporter) {
+ nsresult rv = reporter->ReportDeliveryError(mMessageId, aReason);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+ }
+};
+
+NS_IMPL_ISUPPORTS0(PushErrorReporter)
+
+class SendPushEventRunnable final : public ExtendableFunctionalEventWorkerRunnable
+{
+ nsString mMessageId;
+ Maybe<nsTArray<uint8_t>> mData;
+
+public:
+ SendPushEventRunnable(WorkerPrivate* aWorkerPrivate,
+ KeepAliveToken* aKeepAliveToken,
+ const nsAString& aMessageId,
+ const Maybe<nsTArray<uint8_t>>& aData,
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> aRegistration)
+ : ExtendableFunctionalEventWorkerRunnable(
+ aWorkerPrivate, aKeepAliveToken, aRegistration)
+ , mMessageId(aMessageId)
+ , mData(aData)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
+
+ RefPtr<PushErrorReporter> errorReporter =
+ new PushErrorReporter(aWorkerPrivate, mMessageId);
+
+ PushEventInit pei;
+ if (mData) {
+ const nsTArray<uint8_t>& bytes = mData.ref();
+ JSObject* data = Uint8Array::Create(aCx, bytes.Length(), bytes.Elements());
+ if (!data) {
+ errorReporter->Report();
+ return false;
+ }
+ pei.mData.Construct().SetAsArrayBufferView().Init(data);
+ }
+ pei.mBubbles = false;
+ pei.mCancelable = false;
+
+ ErrorResult result;
+ RefPtr<PushEvent> event =
+ PushEvent::Constructor(globalObj, NS_LITERAL_STRING("push"), pei, result);
+ if (NS_WARN_IF(result.Failed())) {
+ result.SuppressException();
+ errorReporter->Report();
+ return false;
+ }
+ event->SetTrusted(true);
+
+ if (!DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(),
+ event, errorReporter)) {
+ errorReporter->Report(nsIPushErrorReporter::DELIVERY_UNCAUGHT_EXCEPTION);
+ }
+
+ return true;
+ }
+};
+
+class SendPushSubscriptionChangeEventRunnable final : public ExtendableEventWorkerRunnable
+{
+
+public:
+ explicit SendPushSubscriptionChangeEventRunnable(
+ WorkerPrivate* aWorkerPrivate, KeepAliveToken* aKeepAliveToken)
+ : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+
+ RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
+
+ ExtendableEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+
+ RefPtr<ExtendableEvent> event =
+ ExtendableEvent::Constructor(target,
+ NS_LITERAL_STRING("pushsubscriptionchange"),
+ init);
+
+ event->SetTrusted(true);
+
+ DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(),
+ event, nullptr);
+
+ return true;
+ }
+};
+
+} // anonymous namespace
+
+nsresult
+ServiceWorkerPrivate::SendPushEvent(const nsAString& aMessageId,
+ const Maybe<nsTArray<uint8_t>>& aData,
+ ServiceWorkerRegistrationInfo* aRegistration)
+{
+ nsresult rv = SpawnWorkerIfNeeded(PushEvent, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
+
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
+ new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(aRegistration, false));
+
+ RefPtr<WorkerRunnable> r = new SendPushEventRunnable(mWorkerPrivate,
+ token,
+ aMessageId,
+ aData,
+ regInfo);
+
+ if (mInfo->State() == ServiceWorkerState::Activating) {
+ mPendingFunctionalEvents.AppendElement(r.forget());
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated);
+
+ if (NS_WARN_IF(!r->Dispatch())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ServiceWorkerPrivate::SendPushSubscriptionChangeEvent()
+{
+ nsresult rv = SpawnWorkerIfNeeded(PushSubscriptionChangeEvent, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
+ RefPtr<WorkerRunnable> r =
+ new SendPushSubscriptionChangeEventRunnable(mWorkerPrivate, token);
+ if (NS_WARN_IF(!r->Dispatch())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+namespace {
+
+static void
+DummyNotificationTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ // Nothing.
+}
+
+class AllowWindowInteractionHandler;
+
+class ClearWindowAllowedRunnable final : public WorkerRunnable
+{
+public:
+ ClearWindowAllowedRunnable(WorkerPrivate* aWorkerPrivate,
+ AllowWindowInteractionHandler* aHandler)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ , mHandler(aHandler)
+ { }
+
+private:
+ bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ // WorkerRunnable asserts that the dispatch is from parent thread if
+ // the busy count modification is WorkerThreadUnchangedBusyCount.
+ // Since this runnable will be dispatched from the timer thread, we override
+ // PreDispatch and PostDispatch to skip the check.
+ return true;
+ }
+
+ void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ // Silence bad assertions.
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
+
+ nsresult
+ Cancel() override
+ {
+ // Always ensure the handler is released on the worker thread, even if we
+ // are cancelled.
+ mHandler = nullptr;
+ return WorkerRunnable::Cancel();
+ }
+
+ RefPtr<AllowWindowInteractionHandler> mHandler;
+};
+
+class AllowWindowInteractionHandler final : public PromiseNativeHandler
+{
+ friend class ClearWindowAllowedRunnable;
+ nsCOMPtr<nsITimer> mTimer;
+
+ ~AllowWindowInteractionHandler()
+ {
+ }
+
+ void
+ ClearWindowAllowed(WorkerPrivate* aWorkerPrivate)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (!mTimer) {
+ return;
+ }
+
+ // XXXcatalinb: This *might* be executed after the global was unrooted, in
+ // which case GlobalScope() will return null. Making the check here just
+ // to be safe.
+ WorkerGlobalScope* globalScope = aWorkerPrivate->GlobalScope();
+ if (!globalScope) {
+ return;
+ }
+
+ globalScope->ConsumeWindowInteraction();
+ mTimer->Cancel();
+ mTimer = nullptr;
+ MOZ_ALWAYS_TRUE(aWorkerPrivate->ModifyBusyCountFromWorker(false));
+ }
+
+ void
+ StartClearWindowTimer(WorkerPrivate* aWorkerPrivate)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(!mTimer);
+
+ nsresult rv;
+ nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ RefPtr<ClearWindowAllowedRunnable> r =
+ new ClearWindowAllowedRunnable(aWorkerPrivate, this);
+
+ RefPtr<TimerThreadEventTarget> target =
+ new TimerThreadEventTarget(aWorkerPrivate, r);
+
+ rv = timer->SetTarget(target);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ // The important stuff that *has* to be reversed.
+ if (NS_WARN_IF(!aWorkerPrivate->ModifyBusyCountFromWorker(true))) {
+ return;
+ }
+ aWorkerPrivate->GlobalScope()->AllowWindowInteraction();
+ timer.swap(mTimer);
+
+ // We swap first and then initialize the timer so that even if initializing
+ // fails, we still clean the busy count and interaction count correctly.
+ // The timer can't be initialized before modifying the busy count since the
+ // timer thread could run and call the timeout but the worker may
+ // already be terminating and modifying the busy count could fail.
+ rv = mTimer->InitWithFuncCallback(DummyNotificationTimerCallback, nullptr,
+ gDOMDisableOpenClickDelay,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ClearWindowAllowed(aWorkerPrivate);
+ return;
+ }
+ }
+
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit AllowWindowInteractionHandler(WorkerPrivate* aWorkerPrivate)
+ {
+ StartClearWindowTimer(aWorkerPrivate);
+ }
+
+ void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+ ClearWindowAllowed(workerPrivate);
+ }
+
+ void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+ ClearWindowAllowed(workerPrivate);
+ }
+};
+
+NS_IMPL_ISUPPORTS0(AllowWindowInteractionHandler)
+
+bool
+ClearWindowAllowedRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+{
+ mHandler->ClearWindowAllowed(aWorkerPrivate);
+ mHandler = nullptr;
+ return true;
+}
+
+class SendNotificationEventRunnable final : public ExtendableEventWorkerRunnable
+{
+ const nsString mEventName;
+ const nsString mID;
+ const nsString mTitle;
+ const nsString mDir;
+ const nsString mLang;
+ const nsString mBody;
+ const nsString mTag;
+ const nsString mIcon;
+ const nsString mData;
+ const nsString mBehavior;
+ const nsString mScope;
+
+public:
+ SendNotificationEventRunnable(WorkerPrivate* aWorkerPrivate,
+ KeepAliveToken* aKeepAliveToken,
+ const nsAString& aEventName,
+ const nsAString& aID,
+ const nsAString& aTitle,
+ const nsAString& aDir,
+ const nsAString& aLang,
+ const nsAString& aBody,
+ const nsAString& aTag,
+ const nsAString& aIcon,
+ const nsAString& aData,
+ const nsAString& aBehavior,
+ const nsAString& aScope)
+ : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
+ , mEventName(aEventName)
+ , mID(aID)
+ , mTitle(aTitle)
+ , mDir(aDir)
+ , mLang(aLang)
+ , mBody(aBody)
+ , mTag(aTag)
+ , mIcon(aIcon)
+ , mData(aData)
+ , mBehavior(aBehavior)
+ , mScope(aScope)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+
+ RefPtr<EventTarget> target = do_QueryObject(aWorkerPrivate->GlobalScope());
+
+ ErrorResult result;
+ RefPtr<Notification> notification =
+ Notification::ConstructFromFields(aWorkerPrivate->GlobalScope(), mID,
+ mTitle, mDir, mLang, mBody, mTag, mIcon,
+ mData, mScope, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return false;
+ }
+
+ NotificationEventInit nei;
+ nei.mNotification = notification;
+ nei.mBubbles = false;
+ nei.mCancelable = false;
+
+ RefPtr<NotificationEvent> event =
+ NotificationEvent::Constructor(target, mEventName,
+ nei, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return false;
+ }
+
+ event->SetTrusted(true);
+ aWorkerPrivate->GlobalScope()->AllowWindowInteraction();
+ RefPtr<AllowWindowInteractionHandler> allowWindowInteraction =
+ new AllowWindowInteractionHandler(aWorkerPrivate);
+ if (!DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(),
+ event, allowWindowInteraction)) {
+ allowWindowInteraction->RejectedCallback(aCx, JS::UndefinedHandleValue);
+ }
+ aWorkerPrivate->GlobalScope()->ConsumeWindowInteraction();
+
+ return true;
+ }
+};
+
+} // namespace anonymous
+
+nsresult
+ServiceWorkerPrivate::SendNotificationEvent(const nsAString& aEventName,
+ const nsAString& aID,
+ const nsAString& aTitle,
+ const nsAString& aDir,
+ const nsAString& aLang,
+ const nsAString& aBody,
+ const nsAString& aTag,
+ const nsAString& aIcon,
+ const nsAString& aData,
+ const nsAString& aBehavior,
+ const nsAString& aScope)
+{
+ WakeUpReason why;
+ if (aEventName.EqualsLiteral(NOTIFICATION_CLICK_EVENT_NAME)) {
+ why = NotificationClickEvent;
+ gDOMDisableOpenClickDelay = Preferences::GetInt("dom.disable_open_click_delay");
+ } else if (aEventName.EqualsLiteral(NOTIFICATION_CLOSE_EVENT_NAME)) {
+ why = NotificationCloseEvent;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Invalid notification event name");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = SpawnWorkerIfNeeded(why, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
+
+ RefPtr<WorkerRunnable> r =
+ new SendNotificationEventRunnable(mWorkerPrivate, token,
+ aEventName, aID, aTitle, aDir, aLang,
+ aBody, aTag, aIcon, aData, aBehavior,
+ aScope);
+ if (NS_WARN_IF(!r->Dispatch())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+namespace {
+
+// Inheriting ExtendableEventWorkerRunnable so that the worker is not terminated
+// while handling the fetch event, though that's very unlikely.
+class FetchEventRunnable : public ExtendableFunctionalEventWorkerRunnable
+ , public nsIHttpHeaderVisitor {
+ nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
+ const nsCString mScriptSpec;
+ nsTArray<nsCString> mHeaderNames;
+ nsTArray<nsCString> mHeaderValues;
+ nsCString mSpec;
+ nsCString mFragment;
+ nsCString mMethod;
+ nsString mClientId;
+ bool mIsReload;
+ RequestCache mCacheMode;
+ RequestMode mRequestMode;
+ RequestRedirect mRequestRedirect;
+ RequestCredentials mRequestCredentials;
+ nsContentPolicyType mContentPolicyType;
+ nsCOMPtr<nsIInputStream> mUploadStream;
+ nsCString mReferrer;
+ ReferrerPolicy mReferrerPolicy;
+ nsString mIntegrity;
+public:
+ FetchEventRunnable(WorkerPrivate* aWorkerPrivate,
+ KeepAliveToken* aKeepAliveToken,
+ nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
+ // CSP checks might require the worker script spec
+ // later on.
+ const nsACString& aScriptSpec,
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
+ const nsAString& aDocumentId,
+ bool aIsReload)
+ : ExtendableFunctionalEventWorkerRunnable(
+ aWorkerPrivate, aKeepAliveToken, aRegistration)
+ , mInterceptedChannel(aChannel)
+ , mScriptSpec(aScriptSpec)
+ , mClientId(aDocumentId)
+ , mIsReload(aIsReload)
+ , mCacheMode(RequestCache::Default)
+ , mRequestMode(RequestMode::No_cors)
+ , mRequestRedirect(RequestRedirect::Follow)
+ // By default we set it to same-origin since normal HTTP fetches always
+ // send credentials to same-origin websites unless explicitly forbidden.
+ , mRequestCredentials(RequestCredentials::Same_origin)
+ , mContentPolicyType(nsIContentPolicy::TYPE_INVALID)
+ , mReferrer(kFETCH_CLIENT_REFERRER_STR)
+ , mReferrerPolicy(ReferrerPolicy::_empty)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD
+ VisitHeader(const nsACString& aHeader, const nsACString& aValue) override
+ {
+ mHeaderNames.AppendElement(aHeader);
+ mHeaderValues.AppendElement(aValue);
+ return NS_OK;
+ }
+
+ nsresult
+ Init()
+ {
+ AssertIsOnMainThread();
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = mInterceptedChannel->GetChannel(getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = mInterceptedChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Normally we rely on the Request constructor to strip the fragment, but
+ // when creating the FetchEvent we bypass the constructor. So strip the
+ // fragment manually here instead. We can't do it later when we create
+ // the Request because that code executes off the main thread.
+ nsCOMPtr<nsIURI> uriNoFragment;
+ rv = uri->CloneIgnoringRef(getter_AddRefs(uriNoFragment));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = uriNoFragment->GetSpec(mSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = uri->GetRef(mFragment);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t loadFlags;
+ rv = channel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ rv = channel->GetLoadInfo(getter_AddRefs(loadInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mContentPolicyType = loadInfo->InternalContentPolicyType();
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
+ MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?");
+
+ nsAutoCString referrer;
+ // Ignore the return value since the Referer header may not exist.
+ httpChannel->GetRequestHeader(NS_LITERAL_CSTRING("Referer"), referrer);
+ if (!referrer.IsEmpty()) {
+ mReferrer = referrer;
+ }
+
+ uint32_t referrerPolicy = 0;
+ rv = httpChannel->GetReferrerPolicy(&referrerPolicy);
+ NS_ENSURE_SUCCESS(rv, rv);
+ switch (referrerPolicy) {
+ case nsIHttpChannel::REFERRER_POLICY_NO_REFERRER:
+ mReferrerPolicy = ReferrerPolicy::No_referrer;
+ break;
+ case nsIHttpChannel::REFERRER_POLICY_ORIGIN:
+ mReferrerPolicy = ReferrerPolicy::Origin;
+ break;
+ case nsIHttpChannel::REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE:
+ mReferrerPolicy = ReferrerPolicy::No_referrer_when_downgrade;
+ break;
+ case nsIHttpChannel::REFERRER_POLICY_ORIGIN_WHEN_XORIGIN:
+ mReferrerPolicy = ReferrerPolicy::Origin_when_cross_origin;
+ break;
+ case nsIHttpChannel::REFERRER_POLICY_UNSAFE_URL:
+ mReferrerPolicy = ReferrerPolicy::Unsafe_url;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid Referrer Policy enum value?");
+ break;
+ }
+
+ rv = httpChannel->GetRequestMethod(mMethod);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(httpChannel);
+ NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE);
+
+ mRequestMode = InternalRequest::MapChannelToRequestMode(channel);
+
+ // This is safe due to static_asserts in ServiceWorkerManager.cpp.
+ uint32_t redirectMode;
+ internalChannel->GetRedirectMode(&redirectMode);
+ mRequestRedirect = static_cast<RequestRedirect>(redirectMode);
+
+ // This is safe due to static_asserts in ServiceWorkerManager.cpp.
+ uint32_t cacheMode;
+ internalChannel->GetFetchCacheMode(&cacheMode);
+ mCacheMode = static_cast<RequestCache>(cacheMode);
+
+ internalChannel->GetIntegrityMetadata(mIntegrity);
+
+ mRequestCredentials = InternalRequest::MapChannelToRequestCredentials(channel);
+
+ rv = httpChannel->VisitNonDefaultRequestHeaders(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
+ if (uploadChannel) {
+ MOZ_ASSERT(!mUploadStream);
+ bool bodyHasHeaders = false;
+ rv = uploadChannel->GetUploadStreamHasHeaders(&bodyHasHeaders);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIInputStream> uploadStream;
+ rv = uploadChannel->CloneUploadStream(getter_AddRefs(uploadStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (bodyHasHeaders) {
+ HandleBodyWithHeaders(uploadStream);
+ } else {
+ mUploadStream = uploadStream;
+ }
+ }
+
+ return NS_OK;
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ return DispatchFetchEvent(aCx, aWorkerPrivate);
+ }
+
+ nsresult
+ Cancel() override
+ {
+ nsCOMPtr<nsIRunnable> runnable = new ResumeRequest(mInterceptedChannel);
+ if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable))) {
+ NS_WARNING("Failed to resume channel on FetchEventRunnable::Cancel()!\n");
+ }
+ WorkerRunnable::Cancel();
+ return NS_OK;
+ }
+
+private:
+ ~FetchEventRunnable() {}
+
+ class ResumeRequest final : public Runnable {
+ nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
+ public:
+ explicit ResumeRequest(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel)
+ : mChannel(aChannel)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ AssertIsOnMainThread();
+ nsresult rv = mChannel->ResetInterception();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to resume intercepted network request");
+ return rv;
+ }
+ };
+
+ bool
+ DispatchFetchEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+ {
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
+ GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
+
+ RefPtr<InternalHeaders> internalHeaders = new InternalHeaders(HeadersGuardEnum::Request);
+ MOZ_ASSERT(mHeaderNames.Length() == mHeaderValues.Length());
+ for (uint32_t i = 0; i < mHeaderNames.Length(); i++) {
+ ErrorResult result;
+ internalHeaders->Set(mHeaderNames[i], mHeaderValues[i], result);
+ if (NS_WARN_IF(result.Failed())) {
+ result.SuppressException();
+ return false;
+ }
+ }
+
+ ErrorResult result;
+ internalHeaders->SetGuard(HeadersGuardEnum::Immutable, result);
+ if (NS_WARN_IF(result.Failed())) {
+ result.SuppressException();
+ return false;
+ }
+ RefPtr<InternalRequest> internalReq = new InternalRequest(mSpec,
+ mFragment,
+ mMethod,
+ internalHeaders.forget(),
+ mCacheMode,
+ mRequestMode,
+ mRequestRedirect,
+ mRequestCredentials,
+ NS_ConvertUTF8toUTF16(mReferrer),
+ mReferrerPolicy,
+ mContentPolicyType,
+ mIntegrity);
+ internalReq->SetBody(mUploadStream);
+ // For Telemetry, note that this Request object was created by a Fetch event.
+ internalReq->SetCreatedByFetchEvent();
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(globalObj.GetAsSupports());
+ if (NS_WARN_IF(!global)) {
+ return false;
+ }
+ RefPtr<Request> request = new Request(global, internalReq);
+
+ MOZ_ASSERT_IF(internalReq->IsNavigationRequest(),
+ request->Redirect() == RequestRedirect::Manual);
+
+ RootedDictionary<FetchEventInit> init(aCx);
+ init.mRequest = request;
+ init.mBubbles = false;
+ init.mCancelable = true;
+ if (!mClientId.IsEmpty()) {
+ init.mClientId = mClientId;
+ }
+ init.mIsReload = mIsReload;
+ RefPtr<FetchEvent> event =
+ FetchEvent::Constructor(globalObj, NS_LITERAL_STRING("fetch"), init, result);
+ if (NS_WARN_IF(result.Failed())) {
+ result.SuppressException();
+ return false;
+ }
+
+ event->PostInit(mInterceptedChannel, mRegistration, mScriptSpec);
+ event->SetTrusted(true);
+
+ RefPtr<EventTarget> target = do_QueryObject(aWorkerPrivate->GlobalScope());
+ nsresult rv2 = target->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv2)) || !event->WaitToRespond()) {
+ nsCOMPtr<nsIRunnable> runnable;
+ if (event->DefaultPrevented(aCx)) {
+ event->ReportCanceled();
+ } else if (event->WidgetEventPtr()->mFlags.mExceptionWasRaised) {
+ // Exception logged via the WorkerPrivate ErrorReporter
+ } else {
+ runnable = new ResumeRequest(mInterceptedChannel);
+ }
+
+ if (!runnable) {
+ runnable = new CancelChannelRunnable(mInterceptedChannel,
+ mRegistration,
+ NS_ERROR_INTERCEPTION_FAILED);
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(runnable.forget()));
+ }
+
+ RefPtr<Promise> waitUntilPromise = event->GetPromise();
+ if (waitUntilPromise) {
+ KeepAliveHandler::CreateAndAttachToPromise(mKeepAliveToken,
+ waitUntilPromise);
+ }
+
+ return true;
+ }
+
+ nsresult
+ HandleBodyWithHeaders(nsIInputStream* aUploadStream)
+ {
+ // We are dealing with an nsMIMEInputStream which uses string input streams
+ // under the hood, so all of the data is available synchronously.
+ bool nonBlocking = false;
+ nsresult rv = aUploadStream->IsNonBlocking(&nonBlocking);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (NS_WARN_IF(!nonBlocking)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsAutoCString body;
+ rv = NS_ConsumeStream(aUploadStream, UINT32_MAX, body);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Extract the headers in the beginning of the buffer
+ nsAutoCString::const_iterator begin, end;
+ body.BeginReading(begin);
+ body.EndReading(end);
+ const nsAutoCString::const_iterator body_end = end;
+ nsAutoCString headerName, headerValue;
+ bool emptyHeader = false;
+ while (FetchUtil::ExtractHeader(begin, end, headerName,
+ headerValue, &emptyHeader) &&
+ !emptyHeader) {
+ mHeaderNames.AppendElement(headerName);
+ mHeaderValues.AppendElement(headerValue);
+ headerName.Truncate();
+ headerValue.Truncate();
+ }
+
+ // Replace the upload stream with one only containing the body text.
+ nsCOMPtr<nsIStringInputStream> strStream =
+ do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Skip past the "\r\n" that separates the headers and the body.
+ ++begin;
+ ++begin;
+ body.Assign(Substring(begin, body_end));
+ rv = strStream->SetData(body.BeginReading(), body.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ mUploadStream = strStream;
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable, nsIHttpHeaderVisitor)
+
+} // anonymous namespace
+
+nsresult
+ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ const nsAString& aDocumentId,
+ bool aIsReload)
+{
+ AssertIsOnMainThread();
+
+ // if the ServiceWorker script fails to load for some reason, just resume
+ // the original channel.
+ nsCOMPtr<nsIRunnable> failRunnable =
+ NewRunnableMethod(aChannel, &nsIInterceptedChannel::ResetInterception);
+
+ nsresult rv = SpawnWorkerIfNeeded(FetchEvent, failRunnable, aLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMainThreadPtrHandle<nsIInterceptedChannel> handle(
+ new nsMainThreadPtrHolder<nsIInterceptedChannel>(aChannel, false));
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (NS_WARN_IF(!mInfo || !swm)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ swm->GetRegistration(mInfo->GetPrincipal(), mInfo->Scope());
+
+ // Its possible the registration is removed between starting the interception
+ // and actually dispatching the fetch event. In these cases we simply
+ // want to restart the original network request. Since this is a normal
+ // condition we handle the reset here instead of returning an error which
+ // would in turn trigger a console report.
+ if (!registration) {
+ aChannel->ResetInterception();
+ return NS_OK;
+ }
+
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
+ new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(registration, false));
+
+ RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
+
+ RefPtr<FetchEventRunnable> r =
+ new FetchEventRunnable(mWorkerPrivate, token, handle,
+ mInfo->ScriptSpec(), regInfo,
+ aDocumentId, aIsReload);
+ rv = r->Init();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mInfo->State() == ServiceWorkerState::Activating) {
+ mPendingFunctionalEvents.AppendElement(r.forget());
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated);
+
+ if (NS_WARN_IF(!r->Dispatch())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ServiceWorkerPrivate::SpawnWorkerIfNeeded(WakeUpReason aWhy,
+ nsIRunnable* aLoadFailedRunnable,
+ nsILoadGroup* aLoadGroup)
+{
+ AssertIsOnMainThread();
+
+ // XXXcatalinb: We need to have a separate load group that's linked to
+ // an existing tab child to pass security checks on b2g.
+ // This should be fixed in bug 1125961, but for now we enforce updating
+ // the overriden load group when intercepting a fetch.
+ MOZ_ASSERT_IF(aWhy == FetchEvent, aLoadGroup);
+
+ if (mWorkerPrivate) {
+ mWorkerPrivate->UpdateOverridenLoadGroup(aLoadGroup);
+ RenewKeepAliveToken(aWhy);
+
+ return NS_OK;
+ }
+
+ // Sanity check: mSupportsArray should be empty if we're about to
+ // spin up a new worker.
+ MOZ_ASSERT(mSupportsArray.IsEmpty());
+
+ if (NS_WARN_IF(!mInfo)) {
+ NS_WARNING("Trying to wake up a dead service worker.");
+ return NS_ERROR_FAILURE;
+ }
+
+ // TODO(catalinb): Bug 1192138 - Add telemetry for service worker wake-ups.
+
+ // Ensure that the IndexedDatabaseManager is initialized
+ Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate());
+
+ WorkerLoadInfo info;
+ nsresult rv = NS_NewURI(getter_AddRefs(info.mBaseURI), mInfo->ScriptSpec(),
+ nullptr, nullptr);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ info.mResolvedScriptURI = info.mBaseURI;
+ MOZ_ASSERT(!mInfo->CacheName().IsEmpty());
+ info.mServiceWorkerCacheName = mInfo->CacheName();
+ info.mServiceWorkerID = mInfo->ID();
+ info.mLoadGroup = aLoadGroup;
+ info.mLoadFailedAsyncRunnable = aLoadFailedRunnable;
+
+ rv = info.mBaseURI->GetHost(info.mDomain);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ info.mPrincipal = mInfo->GetPrincipal();
+
+ nsContentUtils::StorageAccess access =
+ nsContentUtils::StorageAllowedForPrincipal(info.mPrincipal);
+ info.mStorageAllowed = access > nsContentUtils::StorageAccess::ePrivateBrowsing;
+ info.mOriginAttributes = mInfo->GetOriginAttributes();
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ rv = info.mPrincipal->GetCsp(getter_AddRefs(csp));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ info.mCSP = csp;
+ if (info.mCSP) {
+ rv = info.mCSP->GetAllowsEval(&info.mReportCSPViolations,
+ &info.mEvalAllowed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ info.mEvalAllowed = true;
+ info.mReportCSPViolations = false;
+ }
+
+ WorkerPrivate::OverrideLoadInfoLoadGroup(info);
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ ErrorResult error;
+ NS_ConvertUTF8toUTF16 scriptSpec(mInfo->ScriptSpec());
+
+ mWorkerPrivate = WorkerPrivate::Constructor(jsapi.cx(),
+ scriptSpec,
+ false, WorkerTypeService,
+ mInfo->Scope(), &info, error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ RenewKeepAliveToken(aWhy);
+
+ return NS_OK;
+}
+
+void
+ServiceWorkerPrivate::StoreISupports(nsISupports* aSupports)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mWorkerPrivate);
+ MOZ_ASSERT(!mSupportsArray.Contains(aSupports));
+
+ mSupportsArray.AppendElement(aSupports);
+}
+
+void
+ServiceWorkerPrivate::RemoveISupports(nsISupports* aSupports)
+{
+ AssertIsOnMainThread();
+ mSupportsArray.RemoveElement(aSupports);
+}
+
+void
+ServiceWorkerPrivate::TerminateWorker()
+{
+ AssertIsOnMainThread();
+
+ mIdleWorkerTimer->Cancel();
+ mIdleKeepAliveToken = nullptr;
+ if (mWorkerPrivate) {
+ if (Preferences::GetBool("dom.serviceWorkers.testing.enabled")) {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(this, "service-worker-shutdown", nullptr);
+ }
+ }
+
+ Unused << NS_WARN_IF(!mWorkerPrivate->Terminate());
+ mWorkerPrivate = nullptr;
+ mSupportsArray.Clear();
+
+ // Any pending events are never going to fire on this worker. Cancel
+ // them so that intercepted channels can be reset and other resources
+ // cleaned up.
+ nsTArray<RefPtr<WorkerRunnable>> pendingEvents;
+ mPendingFunctionalEvents.SwapElements(pendingEvents);
+ for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
+ pendingEvents[i]->Cancel();
+ }
+ }
+}
+
+void
+ServiceWorkerPrivate::NoteDeadServiceWorkerInfo()
+{
+ AssertIsOnMainThread();
+ mInfo = nullptr;
+ TerminateWorker();
+}
+
+void
+ServiceWorkerPrivate::Activated()
+{
+ AssertIsOnMainThread();
+
+ // If we had to queue up events due to the worker activating, that means
+ // the worker must be currently running. We should be called synchronously
+ // when the worker becomes activated.
+ MOZ_ASSERT_IF(!mPendingFunctionalEvents.IsEmpty(), mWorkerPrivate);
+
+ nsTArray<RefPtr<WorkerRunnable>> pendingEvents;
+ mPendingFunctionalEvents.SwapElements(pendingEvents);
+
+ for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
+ RefPtr<WorkerRunnable> r = pendingEvents[i].forget();
+ if (NS_WARN_IF(!r->Dispatch())) {
+ NS_WARNING("Failed to dispatch pending functional event!");
+ }
+ }
+}
+
+nsresult
+ServiceWorkerPrivate::GetDebugger(nsIWorkerDebugger** aResult)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aResult);
+
+ if (!mDebuggerCount) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mWorkerPrivate);
+
+ nsCOMPtr<nsIWorkerDebugger> debugger = do_QueryInterface(mWorkerPrivate->Debugger());
+ debugger.forget(aResult);
+
+ return NS_OK;
+}
+
+nsresult
+ServiceWorkerPrivate::AttachDebugger()
+{
+ AssertIsOnMainThread();
+
+ // When the first debugger attaches to a worker, we spawn a worker if needed,
+ // and cancel the idle timeout. The idle timeout should not be reset until
+ // the last debugger detached from the worker.
+ if (!mDebuggerCount) {
+ nsresult rv = SpawnWorkerIfNeeded(AttachEvent, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIdleWorkerTimer->Cancel();
+ }
+
+ ++mDebuggerCount;
+
+ return NS_OK;
+}
+
+nsresult
+ServiceWorkerPrivate::DetachDebugger()
+{
+ AssertIsOnMainThread();
+
+ if (!mDebuggerCount) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ --mDebuggerCount;
+
+ // When the last debugger detaches from a worker, we either reset the idle
+ // timeout, or terminate the worker if there are no more active tokens.
+ if (!mDebuggerCount) {
+ if (mTokenCount) {
+ ResetIdleTimeout();
+ } else {
+ TerminateWorker();
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+ServiceWorkerPrivate::IsIdle() const
+{
+ AssertIsOnMainThread();
+ return mTokenCount == 0 || (mTokenCount == 1 && mIdleKeepAliveToken);
+}
+
+namespace {
+
+class ServiceWorkerPrivateTimerCallback final : public nsITimerCallback
+{
+public:
+ typedef void (ServiceWorkerPrivate::*Method)(nsITimer*);
+
+ ServiceWorkerPrivateTimerCallback(ServiceWorkerPrivate* aServiceWorkerPrivate,
+ Method aMethod)
+ : mServiceWorkerPrivate(aServiceWorkerPrivate)
+ , mMethod(aMethod)
+ {
+ }
+
+ NS_IMETHOD
+ Notify(nsITimer* aTimer) override
+ {
+ (mServiceWorkerPrivate->*mMethod)(aTimer);
+ mServiceWorkerPrivate = nullptr;
+ return NS_OK;
+ }
+
+private:
+ ~ServiceWorkerPrivateTimerCallback() = default;
+
+ RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
+ Method mMethod;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS(ServiceWorkerPrivateTimerCallback, nsITimerCallback);
+
+} // anonymous namespace
+
+void
+ServiceWorkerPrivate::NoteIdleWorkerCallback(nsITimer* aTimer)
+{
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(aTimer == mIdleWorkerTimer, "Invalid timer!");
+
+ // Release ServiceWorkerPrivate's token, since the grace period has ended.
+ mIdleKeepAliveToken = nullptr;
+
+ if (mWorkerPrivate) {
+ // If we still have a workerPrivate at this point it means there are pending
+ // waitUntil promises. Wait a bit more until we forcibly terminate the
+ // worker.
+ uint32_t timeout = Preferences::GetInt("dom.serviceWorkers.idle_extended_timeout");
+ nsCOMPtr<nsITimerCallback> cb = new ServiceWorkerPrivateTimerCallback(
+ this, &ServiceWorkerPrivate::TerminateWorkerCallback);
+ DebugOnly<nsresult> rv =
+ mIdleWorkerTimer->InitWithCallback(cb, timeout, nsITimer::TYPE_ONE_SHOT);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+void
+ServiceWorkerPrivate::TerminateWorkerCallback(nsITimer* aTimer)
+{
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(aTimer == this->mIdleWorkerTimer, "Invalid timer!");
+
+ // mInfo must be non-null at this point because NoteDeadServiceWorkerInfo
+ // which zeroes it calls TerminateWorker which cancels our timer which will
+ // ensure we don't get invoked even if the nsTimerEvent is in the event queue.
+ ServiceWorkerManager::LocalizeAndReportToAllClients(
+ mInfo->Scope(),
+ "ServiceWorkerGraceTimeoutTermination",
+ nsTArray<nsString> { NS_ConvertUTF8toUTF16(mInfo->Scope()) });
+
+ TerminateWorker();
+}
+
+void
+ServiceWorkerPrivate::RenewKeepAliveToken(WakeUpReason aWhy)
+{
+ // We should have an active worker if we're renewing the keep alive token.
+ MOZ_ASSERT(mWorkerPrivate);
+
+ // If there is at least one debugger attached to the worker, the idle worker
+ // timeout was canceled when the first debugger attached to the worker. It
+ // should not be reset until the last debugger detaches from the worker.
+ if (!mDebuggerCount) {
+ ResetIdleTimeout();
+ }
+
+ if (!mIdleKeepAliveToken) {
+ mIdleKeepAliveToken = new KeepAliveToken(this);
+ }
+}
+
+void
+ServiceWorkerPrivate::ResetIdleTimeout()
+{
+ uint32_t timeout = Preferences::GetInt("dom.serviceWorkers.idle_timeout");
+ nsCOMPtr<nsITimerCallback> cb = new ServiceWorkerPrivateTimerCallback(
+ this, &ServiceWorkerPrivate::NoteIdleWorkerCallback);
+ DebugOnly<nsresult> rv =
+ mIdleWorkerTimer->InitWithCallback(cb, timeout, nsITimer::TYPE_ONE_SHOT);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+void
+ServiceWorkerPrivate::AddToken()
+{
+ AssertIsOnMainThread();
+ ++mTokenCount;
+}
+
+void
+ServiceWorkerPrivate::ReleaseToken()
+{
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(mTokenCount > 0);
+ --mTokenCount;
+ if (!mTokenCount) {
+ TerminateWorker();
+ }
+
+ // mInfo can be nullptr here if NoteDeadServiceWorkerInfo() is called while
+ // the KeepAliveToken is being proxy released as a runnable.
+ else if (mInfo && IsIdle()) {
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->WorkerIsIdle(mInfo);
+ }
+ }
+}
+
+already_AddRefed<KeepAliveToken>
+ServiceWorkerPrivate::CreateEventKeepAliveToken()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mWorkerPrivate);
+ MOZ_ASSERT(mIdleKeepAliveToken);
+ RefPtr<KeepAliveToken> ref = new KeepAliveToken(this);
+ return ref.forget();
+}
+
+void
+ServiceWorkerPrivate::AddPendingWindow(Runnable* aPendingWindow)
+{
+ AssertIsOnMainThread();
+ pendingWindows.AppendElement(aPendingWindow);
+}
+
+nsresult
+ServiceWorkerPrivate::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+{
+ AssertIsOnMainThread();
+
+ nsCString topic(aTopic);
+ if (!topic.Equals(NS_LITERAL_CSTRING("BrowserChrome:Ready"))) {
+ MOZ_ASSERT(false, "Unexpected topic.");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ NS_ENSURE_STATE(os);
+ os->RemoveObserver(static_cast<nsIObserver*>(this), "BrowserChrome:Ready");
+
+ size_t len = pendingWindows.Length();
+ for (int i = len-1; i >= 0; i--) {
+ RefPtr<Runnable> runnable = pendingWindows[i];
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+ pendingWindows.RemoveElementAt(i);
+ }
+
+ return NS_OK;
+}
+
+END_WORKERS_NAMESPACE
diff --git a/dom/workers/ServiceWorkerPrivate.h b/dom/workers/ServiceWorkerPrivate.h
new file mode 100644
index 000000000..8d59ea1d0
--- /dev/null
+++ b/dom/workers/ServiceWorkerPrivate.h
@@ -0,0 +1,236 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_serviceworkerprivate_h
+#define mozilla_dom_workers_serviceworkerprivate_h
+
+#include "nsCOMPtr.h"
+
+#include "WorkerPrivate.h"
+
+#define NOTIFICATION_CLICK_EVENT_NAME "notificationclick"
+#define NOTIFICATION_CLOSE_EVENT_NAME "notificationclose"
+
+class nsIInterceptedChannel;
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+class ServiceWorkerInfo;
+class ServiceWorkerRegistrationInfo;
+class KeepAliveToken;
+
+class LifeCycleEventCallback : public Runnable
+{
+public:
+ // Called on the worker thread.
+ virtual void
+ SetResult(bool aResult) = 0;
+};
+
+// ServiceWorkerPrivate is a wrapper for managing the on-demand aspect of
+// service workers. It handles all event dispatching to the worker and ensures
+// the worker thread is running when needed.
+//
+// Lifetime management: To spin up the worker thread we own a |WorkerPrivate|
+// object which can be cancelled if no events are received for a certain
+// amount of time. The worker is kept alive by holding a |KeepAliveToken|
+// reference.
+//
+// Extendable events hold tokens for the duration of their handler execution
+// and until their waitUntil promise is resolved, while ServiceWorkerPrivate
+// will hold a token for |dom.serviceWorkers.idle_timeout| seconds after each
+// new event.
+//
+// Note: All timer events must be handled on the main thread because the
+// worker may block indefinitely the worker thread (e. g. infinite loop in the
+// script).
+//
+// There are 3 cases where we may ignore keep alive tokens:
+// 1. When ServiceWorkerPrivate's token expired, if there are still waitUntil
+// handlers holding tokens, we wait another |dom.serviceWorkers.idle_extended_timeout|
+// seconds before forcibly terminating the worker.
+// 2. If the worker stopped controlling documents and it is not handling push
+// events.
+// 3. The content process is shutting down.
+//
+// Adding an API function for a new event requires calling |SpawnWorkerIfNeeded|
+// with an appropriate reason before any runnable is dispatched to the worker.
+// If the event is extendable then the runnable should inherit
+// ExtendableEventWorkerRunnable.
+class ServiceWorkerPrivate final : public nsIObserver
+{
+ friend class KeepAliveToken;
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(ServiceWorkerPrivate)
+ NS_DECL_NSIOBSERVER
+
+ explicit ServiceWorkerPrivate(ServiceWorkerInfo* aInfo);
+
+ nsresult
+ SendMessageEvent(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ UniquePtr<ServiceWorkerClientInfo>&& aClientInfo);
+
+ // This is used to validate the worker script and continue the installation
+ // process.
+ nsresult
+ CheckScriptEvaluation(LifeCycleEventCallback* aCallback);
+
+ nsresult
+ SendLifeCycleEvent(const nsAString& aEventType,
+ LifeCycleEventCallback* aCallback,
+ nsIRunnable* aLoadFailure);
+
+ nsresult
+ SendPushEvent(const nsAString& aMessageId,
+ const Maybe<nsTArray<uint8_t>>& aData,
+ ServiceWorkerRegistrationInfo* aRegistration);
+
+ nsresult
+ SendPushSubscriptionChangeEvent();
+
+ nsresult
+ SendNotificationEvent(const nsAString& aEventName,
+ const nsAString& aID,
+ const nsAString& aTitle,
+ const nsAString& aDir,
+ const nsAString& aLang,
+ const nsAString& aBody,
+ const nsAString& aTag,
+ const nsAString& aIcon,
+ const nsAString& aData,
+ const nsAString& aBehavior,
+ const nsAString& aScope);
+
+ nsresult
+ SendFetchEvent(nsIInterceptedChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ const nsAString& aDocumentId,
+ bool aIsReload);
+
+ void
+ StoreISupports(nsISupports* aSupports);
+
+ void
+ RemoveISupports(nsISupports* aSupports);
+
+ // This will terminate the current running worker thread and drop the
+ // workerPrivate reference.
+ // Called by ServiceWorkerInfo when [[Clear Registration]] is invoked
+ // or whenever the spec mandates that we terminate the worker.
+ // This is a no-op if the worker has already been stopped.
+ void
+ TerminateWorker();
+
+ void
+ NoteDeadServiceWorkerInfo();
+
+ void
+ NoteStoppedControllingDocuments();
+
+ void
+ Activated();
+
+ nsresult
+ GetDebugger(nsIWorkerDebugger** aResult);
+
+ nsresult
+ AttachDebugger();
+
+ nsresult
+ DetachDebugger();
+
+ bool
+ IsIdle() const;
+
+ void
+ AddPendingWindow(Runnable* aPendingWindow);
+
+private:
+ enum WakeUpReason {
+ FetchEvent = 0,
+ PushEvent,
+ PushSubscriptionChangeEvent,
+ MessageEvent,
+ NotificationClickEvent,
+ NotificationCloseEvent,
+ LifeCycleEvent,
+ AttachEvent
+ };
+
+ // Timer callbacks
+ void
+ NoteIdleWorkerCallback(nsITimer* aTimer);
+
+ void
+ TerminateWorkerCallback(nsITimer* aTimer);
+
+ void
+ RenewKeepAliveToken(WakeUpReason aWhy);
+
+ void
+ ResetIdleTimeout();
+
+ void
+ AddToken();
+
+ void
+ ReleaseToken();
+
+ // |aLoadFailedRunnable| is a runnable dispatched to the main thread
+ // if the script loader failed for some reason, but can be null.
+ nsresult
+ SpawnWorkerIfNeeded(WakeUpReason aWhy,
+ nsIRunnable* aLoadFailedRunnable,
+ nsILoadGroup* aLoadGroup = nullptr);
+
+ ~ServiceWorkerPrivate();
+
+ already_AddRefed<KeepAliveToken>
+ CreateEventKeepAliveToken();
+
+ // The info object owns us. It is possible to outlive it for a brief period
+ // of time if there are pending waitUntil promises, in which case it
+ // will be null and |SpawnWorkerIfNeeded| will always fail.
+ ServiceWorkerInfo* MOZ_NON_OWNING_REF mInfo;
+
+ // The WorkerPrivate object can only be closed by this class or by the
+ // RuntimeService class if gecko is shutting down. Closing the worker
+ // multiple times is OK, since the second attempt will be a no-op.
+ RefPtr<WorkerPrivate> mWorkerPrivate;
+
+ nsCOMPtr<nsITimer> mIdleWorkerTimer;
+
+ // We keep a token for |dom.serviceWorkers.idle_timeout| seconds to give the
+ // worker a grace period after each event.
+ RefPtr<KeepAliveToken> mIdleKeepAliveToken;
+
+ uint64_t mDebuggerCount;
+
+ uint64_t mTokenCount;
+
+ // Meant for keeping objects alive while handling requests from the worker
+ // on the main thread. Access to this array is provided through
+ // |StoreISupports| and |RemoveISupports|. Note that the array is also
+ // cleared whenever the worker is terminated.
+ nsTArray<nsCOMPtr<nsISupports>> mSupportsArray;
+
+ // Array of function event worker runnables that are pending due to
+ // the worker activating. Main thread only.
+ nsTArray<RefPtr<WorkerRunnable>> mPendingFunctionalEvents;
+
+ nsTArray<Runnable*> pendingWindows;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkerprivate_h
diff --git a/dom/workers/ServiceWorkerRegisterJob.cpp b/dom/workers/ServiceWorkerRegisterJob.cpp
new file mode 100644
index 000000000..8f771e762
--- /dev/null
+++ b/dom/workers/ServiceWorkerRegisterJob.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerRegisterJob.h"
+
+#include "Workers.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ const nsACString& aScriptSpec,
+ nsILoadGroup* aLoadGroup)
+ : ServiceWorkerUpdateJob(Type::Register, aPrincipal, aScope, aScriptSpec,
+ aLoadGroup)
+{
+}
+
+void
+ServiceWorkerRegisterJob::AsyncExecute()
+{
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (Canceled() || !swm) {
+ FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
+ return;
+ }
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ swm->GetRegistration(mPrincipal, mScope);
+
+ if (registration) {
+ // If we are resurrecting an uninstalling registration, then persist
+ // it to disk again. We preemptively removed it earlier during
+ // unregister so that closing the window by shutting down the browser
+ // results in the registration being gone on restart.
+ if (registration->mPendingUninstall) {
+ swm->StoreRegistration(mPrincipal, registration);
+ }
+ registration->mPendingUninstall = false;
+ RefPtr<ServiceWorkerInfo> newest = registration->Newest();
+ if (newest && mScriptSpec.Equals(newest->ScriptSpec())) {
+ SetRegistration(registration);
+ Finish(NS_OK);
+ return;
+ }
+ } else {
+ registration = swm->CreateNewRegistration(mScope, mPrincipal);
+ }
+
+ SetRegistration(registration);
+ Update();
+}
+
+ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob()
+{
+}
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/ServiceWorkerRegisterJob.h b/dom/workers/ServiceWorkerRegisterJob.h
new file mode 100644
index 000000000..a459e25b6
--- /dev/null
+++ b/dom/workers/ServiceWorkerRegisterJob.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_serviceworkerregisterjob_h
+#define mozilla_dom_workers_serviceworkerregisterjob_h
+
+#include "ServiceWorkerUpdateJob.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+// The register job. This implements the steps in the spec Register algorithm,
+// but then uses ServiceWorkerUpdateJob to implement the Update and Install
+// spec algorithms.
+class ServiceWorkerRegisterJob final : public ServiceWorkerUpdateJob
+{
+public:
+ ServiceWorkerRegisterJob(nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ const nsACString& aScriptSpec,
+ nsILoadGroup* aLoadGroup);
+
+private:
+ // Implement the Register algorithm steps and then call the parent class
+ // Update() to complete the job execution.
+ virtual void
+ AsyncExecute() override;
+
+ virtual ~ServiceWorkerRegisterJob();
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkerregisterjob_h
diff --git a/dom/workers/ServiceWorkerRegistrar.cpp b/dom/workers/ServiceWorkerRegistrar.cpp
new file mode 100644
index 000000000..a4757ea54
--- /dev/null
+++ b/dom/workers/ServiceWorkerRegistrar.cpp
@@ -0,0 +1,884 @@
+
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerRegistrar.h"
+#include "mozilla/dom/ServiceWorkerRegistrarTypes.h"
+
+#include "nsIEventTarget.h"
+#include "nsIInputStream.h"
+#include "nsILineInputStream.h"
+#include "nsIObserverService.h"
+#include "nsIOutputStream.h"
+#include "nsISafeOutputStream.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsContentUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+static const char* gSupportedRegistrarVersions[] = {
+ SERVICEWORKERREGISTRAR_VERSION,
+ "3",
+ "2"
+};
+
+StaticRefPtr<ServiceWorkerRegistrar> gServiceWorkerRegistrar;
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(ServiceWorkerRegistrar,
+ nsIObserver)
+
+void
+ServiceWorkerRegistrar::Initialize()
+{
+ MOZ_ASSERT(!gServiceWorkerRegistrar);
+
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ gServiceWorkerRegistrar = new ServiceWorkerRegistrar();
+ ClearOnShutdown(&gServiceWorkerRegistrar);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ DebugOnly<nsresult> rv = obs->AddObserver(gServiceWorkerRegistrar,
+ "profile-after-change", false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = obs->AddObserver(gServiceWorkerRegistrar, "profile-before-change",
+ false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+/* static */ already_AddRefed<ServiceWorkerRegistrar>
+ServiceWorkerRegistrar::Get()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ MOZ_ASSERT(gServiceWorkerRegistrar);
+ RefPtr<ServiceWorkerRegistrar> service = gServiceWorkerRegistrar.get();
+ return service.forget();
+}
+
+ServiceWorkerRegistrar::ServiceWorkerRegistrar()
+ : mMonitor("ServiceWorkerRegistrar.mMonitor")
+ , mDataLoaded(false)
+ , mShuttingDown(false)
+ , mShutdownCompleteFlag(nullptr)
+ , mRunnableCounter(0)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+ServiceWorkerRegistrar::~ServiceWorkerRegistrar()
+{
+ MOZ_ASSERT(!mRunnableCounter);
+}
+
+void
+ServiceWorkerRegistrar::GetRegistrations(
+ nsTArray<ServiceWorkerRegistrationData>& aValues)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aValues.IsEmpty());
+
+ MonitorAutoLock lock(mMonitor);
+
+ // If we don't have the profile directory, profile is not started yet (and
+ // probably we are in a utest).
+ if (!mProfileDir) {
+ return;
+ }
+
+ // We care just about the first execution because this can be blocked by
+ // loading data from disk.
+ static bool firstTime = true;
+ TimeStamp startTime;
+
+ if (firstTime) {
+ startTime = TimeStamp::NowLoRes();
+ }
+
+ // Waiting for data loaded.
+ mMonitor.AssertCurrentThreadOwns();
+ while (!mDataLoaded) {
+ mMonitor.Wait();
+ }
+
+ aValues.AppendElements(mData);
+
+ if (firstTime) {
+ firstTime = false;
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::SERVICE_WORKER_REGISTRATION_LOADING,
+ startTime);
+ }
+}
+
+namespace {
+
+bool Equivalent(const ServiceWorkerRegistrationData& aLeft,
+ const ServiceWorkerRegistrationData& aRight)
+{
+ MOZ_ASSERT(aLeft.principal().type() ==
+ mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
+ MOZ_ASSERT(aRight.principal().type() ==
+ mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
+
+ const auto& leftPrincipal = aLeft.principal().get_ContentPrincipalInfo();
+ const auto& rightPrincipal = aRight.principal().get_ContentPrincipalInfo();
+
+ // Only compare the attributes, not the spec part of the principal.
+ // The scope comparison above already covers the origin and codebase
+ // principals include the full path in their spec which is not what
+ // we want here.
+ return aLeft.scope() == aRight.scope() &&
+ leftPrincipal.attrs() == rightPrincipal.attrs();
+}
+
+} // anonymous namespace
+
+void
+ServiceWorkerRegistrar::RegisterServiceWorker(
+ const ServiceWorkerRegistrationData& aData)
+{
+ AssertIsOnBackgroundThread();
+
+ if (mShuttingDown) {
+ NS_WARNING("Failed to register a serviceWorker during shutting down.");
+ return;
+ }
+
+ {
+ MonitorAutoLock lock(mMonitor);
+ MOZ_ASSERT(mDataLoaded);
+ RegisterServiceWorkerInternal(aData);
+ }
+
+ ScheduleSaveData();
+}
+
+void
+ServiceWorkerRegistrar::UnregisterServiceWorker(
+ const PrincipalInfo& aPrincipalInfo,
+ const nsACString& aScope)
+{
+ AssertIsOnBackgroundThread();
+
+ if (mShuttingDown) {
+ NS_WARNING("Failed to unregister a serviceWorker during shutting down.");
+ return;
+ }
+
+ bool deleted = false;
+
+ {
+ MonitorAutoLock lock(mMonitor);
+ MOZ_ASSERT(mDataLoaded);
+
+ ServiceWorkerRegistrationData tmp;
+ tmp.principal() = aPrincipalInfo;
+ tmp.scope() = aScope;
+
+ for (uint32_t i = 0; i < mData.Length(); ++i) {
+ if (Equivalent(tmp, mData[i])) {
+ mData.RemoveElementAt(i);
+ deleted = true;
+ break;
+ }
+ }
+ }
+
+ if (deleted) {
+ ScheduleSaveData();
+ }
+}
+
+void
+ServiceWorkerRegistrar::RemoveAll()
+{
+ AssertIsOnBackgroundThread();
+
+ if (mShuttingDown) {
+ NS_WARNING("Failed to remove all the serviceWorkers during shutting down.");
+ return;
+ }
+
+ bool deleted = false;
+
+ {
+ MonitorAutoLock lock(mMonitor);
+ MOZ_ASSERT(mDataLoaded);
+
+ deleted = !mData.IsEmpty();
+ mData.Clear();
+ }
+
+ if (deleted) {
+ ScheduleSaveData();
+ }
+}
+
+void
+ServiceWorkerRegistrar::LoadData()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!mDataLoaded);
+
+ nsresult rv = ReadData();
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ DeleteData();
+ // Also if the reading failed we have to notify what is waiting for data.
+ }
+
+ MonitorAutoLock lock(mMonitor);
+ MOZ_ASSERT(!mDataLoaded);
+ mDataLoaded = true;
+ mMonitor.Notify();
+}
+
+nsresult
+ServiceWorkerRegistrar::ReadData()
+{
+ // We cannot assert about the correct thread because normally this method
+ // runs on a IO thread, but in gTests we call it from the main-thread.
+
+ nsCOMPtr<nsIFile> file;
+
+ {
+ MonitorAutoLock lock(mMonitor);
+
+ if (!mProfileDir) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = mProfileDir->Clone(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ nsresult rv = file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool exists;
+ rv = file->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!exists) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(stream);
+ MOZ_ASSERT(lineInputStream);
+
+ nsAutoCString version;
+ bool hasMoreLines;
+ rv = lineInputStream->ReadLine(version, &hasMoreLines);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!IsSupportedVersion(version)) {
+ nsContentUtils::LogMessageToConsole(nsPrintfCString(
+ "Unsupported service worker registrar version: %s", version.get()).get());
+ return NS_ERROR_FAILURE;
+ }
+
+ nsTArray<ServiceWorkerRegistrationData> tmpData;
+
+ bool overwrite = false;
+ bool dedupe = false;
+ while (hasMoreLines) {
+ ServiceWorkerRegistrationData* entry = tmpData.AppendElement();
+
+#define GET_LINE(x) \
+ rv = lineInputStream->ReadLine(x, &hasMoreLines); \
+ if (NS_WARN_IF(NS_FAILED(rv))) { \
+ return rv; \
+ } \
+ if (NS_WARN_IF(!hasMoreLines)) { \
+ return NS_ERROR_FAILURE; \
+ }
+
+ nsAutoCString line;
+ nsAutoCString unused;
+ if (version.EqualsLiteral(SERVICEWORKERREGISTRAR_VERSION)) {
+ nsAutoCString suffix;
+ GET_LINE(suffix);
+
+ PrincipalOriginAttributes attrs;
+ if (!attrs.PopulateFromSuffix(suffix)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ GET_LINE(entry->scope());
+
+ entry->principal() =
+ mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope());
+
+ GET_LINE(entry->currentWorkerURL());
+
+ nsAutoCString cacheName;
+ GET_LINE(cacheName);
+ CopyUTF8toUTF16(cacheName, entry->cacheName());
+ } else if (version.EqualsLiteral("3")) {
+ overwrite = true;
+ dedupe = true;
+
+ nsAutoCString suffix;
+ GET_LINE(suffix);
+
+ PrincipalOriginAttributes attrs;
+ if (!attrs.PopulateFromSuffix(suffix)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // principal spec is no longer used; we use scope directly instead
+ GET_LINE(unused);
+
+ GET_LINE(entry->scope());
+
+ entry->principal() =
+ mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope());
+
+ GET_LINE(entry->currentWorkerURL());
+
+ nsAutoCString cacheName;
+ GET_LINE(cacheName);
+ CopyUTF8toUTF16(cacheName, entry->cacheName());
+ } else if (version.EqualsLiteral("2")) {
+ overwrite = true;
+ dedupe = true;
+
+ nsAutoCString suffix;
+ GET_LINE(suffix);
+
+ PrincipalOriginAttributes attrs;
+ if (!attrs.PopulateFromSuffix(suffix)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // principal spec is no longer used; we use scope directly instead
+ GET_LINE(unused);
+
+ GET_LINE(entry->scope());
+
+ entry->principal() =
+ mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope());
+
+ // scriptSpec is no more used in latest version.
+ GET_LINE(unused);
+
+ GET_LINE(entry->currentWorkerURL());
+
+ nsAutoCString cacheName;
+ GET_LINE(cacheName);
+ CopyUTF8toUTF16(cacheName, entry->cacheName());
+
+ // waitingCacheName is no more used in latest version.
+ GET_LINE(unused);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Should never get here!");
+ }
+
+#undef GET_LINE
+
+ rv = lineInputStream->ReadLine(line, &hasMoreLines);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!line.EqualsLiteral(SERVICEWORKERREGISTRAR_TERMINATOR)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ stream->Close();
+
+ // Copy data over to mData.
+ for (uint32_t i = 0; i < tmpData.Length(); ++i) {
+ bool match = false;
+ if (dedupe) {
+ MOZ_ASSERT(overwrite);
+ // If this is an old profile, then we might need to deduplicate. In
+ // theory this can be removed in the future (Bug 1248449)
+ for (uint32_t j = 0; j < mData.Length(); ++j) {
+ // Use same comparison as RegisterServiceWorker. Scope contains
+ // basic origin information. Combine with any principal attributes.
+ if (Equivalent(tmpData[i], mData[j])) {
+ // Last match wins, just like legacy loading used to do in
+ // the ServiceWorkerManager.
+ mData[j] = tmpData[i];
+ // Dupe found, so overwrite file with reduced list.
+ match = true;
+ break;
+ }
+ }
+ } else {
+#ifdef DEBUG
+ // Otherwise assert no duplications in debug builds.
+ for (uint32_t j = 0; j < mData.Length(); ++j) {
+ MOZ_ASSERT(!Equivalent(tmpData[i], mData[j]));
+ }
+#endif
+ }
+ if (!match) {
+ mData.AppendElement(tmpData[i]);
+ }
+ }
+
+ // Overwrite previous version.
+ // Cannot call SaveData directly because gtest uses main-thread.
+ if (overwrite && NS_FAILED(WriteData())) {
+ NS_WARNING("Failed to write data for the ServiceWorker Registations.");
+ DeleteData();
+ }
+
+ return NS_OK;
+}
+
+void
+ServiceWorkerRegistrar::DeleteData()
+{
+ // We cannot assert about the correct thread because normally this method
+ // runs on a IO thread, but in gTests we call it from the main-thread.
+
+ nsCOMPtr<nsIFile> file;
+
+ {
+ MonitorAutoLock lock(mMonitor);
+ mData.Clear();
+
+ if (!mProfileDir) {
+ return;
+ }
+
+ nsresult rv = mProfileDir->Clone(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ }
+
+ nsresult rv = file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ rv = file->Remove(false);
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ return;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+}
+
+void
+ServiceWorkerRegistrar::RegisterServiceWorkerInternal(const ServiceWorkerRegistrationData& aData)
+{
+ bool found = false;
+ for (uint32_t i = 0, len = mData.Length(); i < len; ++i) {
+ if (Equivalent(aData, mData[i])) {
+ mData[i] = aData;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ mData.AppendElement(aData);
+ }
+}
+
+class ServiceWorkerRegistrarSaveDataRunnable final : public Runnable
+{
+public:
+ ServiceWorkerRegistrarSaveDataRunnable()
+ : mThread(do_GetCurrentThread())
+ {
+ AssertIsOnBackgroundThread();
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ RefPtr<ServiceWorkerRegistrar> service = ServiceWorkerRegistrar::Get();
+ MOZ_ASSERT(service);
+
+ service->SaveData();
+
+ RefPtr<Runnable> runnable =
+ NewRunnableMethod(service, &ServiceWorkerRegistrar::DataSaved);
+ nsresult rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+void
+ServiceWorkerRegistrar::ScheduleSaveData()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mShuttingDown);
+
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT(target, "Must have stream transport service");
+
+ RefPtr<Runnable> runnable =
+ new ServiceWorkerRegistrarSaveDataRunnable();
+ nsresult rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ ++mRunnableCounter;
+}
+
+void
+ServiceWorkerRegistrar::ShutdownCompleted()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MOZ_ASSERT(mShutdownCompleteFlag && !*mShutdownCompleteFlag);
+ *mShutdownCompleteFlag = true;
+}
+
+void
+ServiceWorkerRegistrar::SaveData()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsresult rv = WriteData();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to write data for the ServiceWorker Registations.");
+ DeleteData();
+ }
+}
+
+void
+ServiceWorkerRegistrar::DataSaved()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mRunnableCounter);
+
+ --mRunnableCounter;
+ MaybeScheduleShutdownCompleted();
+}
+
+void
+ServiceWorkerRegistrar::MaybeScheduleShutdownCompleted()
+{
+ AssertIsOnBackgroundThread();
+
+ if (mRunnableCounter || !mShuttingDown) {
+ return;
+ }
+
+ RefPtr<Runnable> runnable =
+ NewRunnableMethod(this, &ServiceWorkerRegistrar::ShutdownCompleted);
+ nsresult rv = NS_DispatchToMainThread(runnable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+}
+
+bool
+ServiceWorkerRegistrar::IsSupportedVersion(const nsACString& aVersion) const
+{
+ uint32_t numVersions = ArrayLength(gSupportedRegistrarVersions);
+ for (uint32_t i = 0; i < numVersions; i++) {
+ if (aVersion.EqualsASCII(gSupportedRegistrarVersions[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsresult
+ServiceWorkerRegistrar::WriteData()
+{
+ // We cannot assert about the correct thread because normally this method
+ // runs on a IO thread, but in gTests we call it from the main-thread.
+
+ nsCOMPtr<nsIFile> file;
+
+ {
+ MonitorAutoLock lock(mMonitor);
+
+ if (!mProfileDir) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = mProfileDir->Clone(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ nsresult rv = file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // We need a lock to take a snapshot of the data.
+ nsTArray<ServiceWorkerRegistrationData> data;
+ {
+ MonitorAutoLock lock(mMonitor);
+ data = mData;
+ }
+
+ nsCOMPtr<nsIOutputStream> stream;
+ rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), file);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString buffer;
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_VERSION);
+ buffer.Append('\n');
+
+ uint32_t count;
+ rv = stream->Write(buffer.Data(), buffer.Length(), &count);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (count != buffer.Length()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ for (uint32_t i = 0, len = data.Length(); i < len; ++i) {
+ const mozilla::ipc::PrincipalInfo& info = data[i].principal();
+
+ MOZ_ASSERT(info.type() == mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
+
+ const mozilla::ipc::ContentPrincipalInfo& cInfo =
+ info.get_ContentPrincipalInfo();
+
+ nsAutoCString suffix;
+ cInfo.attrs().CreateSuffix(suffix);
+
+ buffer.Truncate();
+ buffer.Append(suffix.get());
+ buffer.Append('\n');
+
+ buffer.Append(data[i].scope());
+ buffer.Append('\n');
+
+ buffer.Append(data[i].currentWorkerURL());
+ buffer.Append('\n');
+
+ buffer.Append(NS_ConvertUTF16toUTF8(data[i].cacheName()));
+ buffer.Append('\n');
+
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR);
+ buffer.Append('\n');
+
+ rv = stream->Write(buffer.Data(), buffer.Length(), &count);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (count != buffer.Length()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(stream);
+ MOZ_ASSERT(safeStream);
+
+ rv = safeStream->Finish();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+ServiceWorkerRegistrar::ProfileStarted()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MonitorAutoLock lock(mMonitor);
+ MOZ_DIAGNOSTIC_ASSERT(!mProfileDir);
+
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(mProfileDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT(target, "Must have stream transport service");
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod(this, &ServiceWorkerRegistrar::LoadData);
+ rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the LoadDataRunnable.");
+ }
+}
+
+void
+ServiceWorkerRegistrar::ProfileStopped()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MonitorAutoLock lock(mMonitor);
+
+ if (!mProfileDir) {
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(mProfileDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ }
+
+ // We must set the pointer before potentially entering the fast-path shutdown
+ // below.
+ bool completed = false;
+ mShutdownCompleteFlag = &completed;
+
+ PBackgroundChild* child = BackgroundChild::GetForCurrentThread();
+ if (!child) {
+ // Mutations to the ServiceWorkerRegistrar happen on the PBackground thread,
+ // issued by the ServiceWorkerManagerService, so the appropriate place to
+ // trigger shutdown is on that thread.
+ //
+ // However, it's quite possible that the PBackground thread was not brought
+ // into existence for xpcshell tests. We don't cause it to be created
+ // ourselves for any reason, for example.
+ //
+ // In this scenario, we know that:
+ // - We will receive exactly one call to ourself from BlockShutdown() and
+ // BlockShutdown() will be called (at most) once.
+ // - The only way our Shutdown() method gets called is via
+ // BackgroundParentImpl::RecvShutdownServiceWorkerRegistrar() being
+ // invoked, which only happens if we get to that send below here that we
+ // can't get to.
+ // - All Shutdown() does is set mShuttingDown=true (essential for
+ // invariants) and invoke MaybeScheduleShutdownCompleted().
+ // - Since there is no PBackground thread, mRunnableCounter must be 0
+ // because only ScheduleSaveData() increments it and it only runs on the
+ // background thread, so it cannot have run. And so we would expect
+ // MaybeScheduleShutdownCompleted() to schedule an invocation of
+ // ShutdownCompleted on the main thread.
+ //
+ // So it's appropriate for us to set mShuttingDown=true (as Shutdown would
+ // do) and directly invoke ShutdownCompleted() (as Shutdown would indirectly
+ // do via MaybeScheduleShutdownCompleted).
+ mShuttingDown = true;
+ ShutdownCompleted();
+ return;
+ }
+
+ child->SendShutdownServiceWorkerRegistrar();
+
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+ while (true) {
+ MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(thread));
+ if (completed) {
+ break;
+ }
+ }
+}
+
+void
+ServiceWorkerRegistrar::Shutdown()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mShuttingDown);
+
+ mShuttingDown = true;
+ MaybeScheduleShutdownCompleted();
+}
+
+NS_IMETHODIMP
+ServiceWorkerRegistrar::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!strcmp(aTopic, "profile-after-change")) {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ observerService->RemoveObserver(this, "profile-after-change");
+
+ // The profile is fully loaded, now we can proceed with the loading of data
+ // from disk.
+ ProfileStarted();
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "profile-before-change")) {
+ // Hygiene; gServiceWorkerRegistrar should still be keeping a reference
+ // alive well past this phase of shutdown, but it's bad form to drop your
+ // last potentially owning reference and then make a call that requires you
+ // to still be alive, especially when you spin a nested event loop.
+ RefPtr<ServiceWorkerRegistrar> kungFuDeathGrip(this);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ observerService->RemoveObserver(this, "profile-before-change");
+
+ // Shutting down, let's sync the data.
+ ProfileStopped();
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(false, "ServiceWorkerRegistrar got unexpected topic!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/ServiceWorkerRegistrar.h b/dom/workers/ServiceWorkerRegistrar.h
new file mode 100644
index 000000000..0c476ad9b
--- /dev/null
+++ b/dom/workers/ServiceWorkerRegistrar.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_ServiceWorkerRegistrar_h
+#define mozilla_dom_workers_ServiceWorkerRegistrar_h
+
+#include "mozilla/Monitor.h"
+#include "mozilla/Telemetry.h"
+#include "nsClassHashtable.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#define SERVICEWORKERREGISTRAR_FILE "serviceworker.txt"
+#define SERVICEWORKERREGISTRAR_VERSION "4"
+#define SERVICEWORKERREGISTRAR_TERMINATOR "#"
+#define SERVICEWORKERREGISTRAR_TRUE "true"
+#define SERVICEWORKERREGISTRAR_FALSE "false"
+
+class nsIFile;
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+class ServiceWorkerRegistrationData;
+
+class ServiceWorkerRegistrar : public nsIObserver
+{
+ friend class ServiceWorkerRegistrarSaveDataRunnable;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ static void Initialize();
+
+ void Shutdown();
+
+ void DataSaved();
+
+ static already_AddRefed<ServiceWorkerRegistrar> Get();
+
+ void GetRegistrations(nsTArray<ServiceWorkerRegistrationData>& aValues);
+
+ void RegisterServiceWorker(const ServiceWorkerRegistrationData& aData);
+ void UnregisterServiceWorker(const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+ const nsACString& aScope);
+ void RemoveAll();
+
+protected:
+ // These methods are protected because we test this class using gTest
+ // subclassing it.
+ void LoadData();
+ void SaveData();
+
+ nsresult ReadData();
+ nsresult WriteData();
+ void DeleteData();
+
+ void RegisterServiceWorkerInternal(const ServiceWorkerRegistrationData& aData);
+
+ ServiceWorkerRegistrar();
+ virtual ~ServiceWorkerRegistrar();
+
+private:
+ void ProfileStarted();
+ void ProfileStopped();
+
+ void ScheduleSaveData();
+ void ShutdownCompleted();
+ void MaybeScheduleShutdownCompleted();
+
+ bool IsSupportedVersion(const nsACString& aVersion) const;
+
+ mozilla::Monitor mMonitor;
+
+protected:
+ // protected by mMonitor.
+ nsCOMPtr<nsIFile> mProfileDir;
+ nsTArray<ServiceWorkerRegistrationData> mData;
+ bool mDataLoaded;
+
+ // PBackground thread only
+ bool mShuttingDown;
+ bool* mShutdownCompleteFlag;
+ uint32_t mRunnableCounter;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_ServiceWorkerRegistrar_h
diff --git a/dom/workers/ServiceWorkerRegistrarTypes.ipdlh b/dom/workers/ServiceWorkerRegistrarTypes.ipdlh
new file mode 100644
index 000000000..7754a19e6
--- /dev/null
+++ b/dom/workers/ServiceWorkerRegistrarTypes.ipdlh
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include PBackgroundSharedTypes;
+
+namespace mozilla {
+namespace dom {
+
+struct ServiceWorkerRegistrationData
+{
+ nsCString scope;
+ nsCString currentWorkerURL;
+
+ nsString cacheName;
+
+ PrincipalInfo principal;
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/ServiceWorkerRegistration.cpp b/dom/workers/ServiceWorkerRegistration.cpp
new file mode 100644
index 000000000..451bd2be9
--- /dev/null
+++ b/dom/workers/ServiceWorkerRegistration.cpp
@@ -0,0 +1,1327 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerRegistration.h"
+
+#include "ipc/ErrorIPCUtils.h"
+#include "mozilla/dom/Notification.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseWorkerProxy.h"
+#include "mozilla/dom/PushManagerBinding.h"
+#include "mozilla/dom/PushManager.h"
+#include "mozilla/dom/ServiceWorkerRegistrationBinding.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "ServiceWorker.h"
+#include "ServiceWorkerManager.h"
+
+#include "nsIDocument.h"
+#include "nsIServiceWorkerManager.h"
+#include "nsISupportsPrimitives.h"
+#include "nsPIDOMWindow.h"
+#include "nsContentUtils.h"
+
+#include "WorkerPrivate.h"
+#include "Workers.h"
+#include "WorkerScope.h"
+
+using namespace mozilla::dom::workers;
+
+namespace mozilla {
+namespace dom {
+
+/* static */ bool
+ServiceWorkerRegistration::Visible(JSContext* aCx, JSObject* aObj)
+{
+ if (NS_IsMainThread()) {
+ return Preferences::GetBool("dom.serviceWorkers.enabled", false);
+ }
+
+ // Otherwise check the pref via the work private helper
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+ if (!workerPrivate) {
+ return false;
+ }
+
+ return workerPrivate->ServiceWorkersEnabled();
+}
+
+/* static */ bool
+ServiceWorkerRegistration::NotificationAPIVisible(JSContext* aCx, JSObject* aObj)
+{
+ if (NS_IsMainThread()) {
+ return Preferences::GetBool("dom.webnotifications.serviceworker.enabled", false);
+ }
+
+ // Otherwise check the pref via the work private helper
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+ if (!workerPrivate) {
+ return false;
+ }
+
+ return workerPrivate->DOMServiceWorkerNotificationEnabled();
+}
+
+////////////////////////////////////////////////////
+// Main Thread implementation
+
+class ServiceWorkerRegistrationMainThread final : public ServiceWorkerRegistration,
+ public ServiceWorkerRegistrationListener
+{
+ friend nsPIDOMWindowInner;
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerRegistrationMainThread,
+ ServiceWorkerRegistration)
+
+ ServiceWorkerRegistrationMainThread(nsPIDOMWindowInner* aWindow,
+ const nsAString& aScope);
+
+ already_AddRefed<Promise>
+ Update(ErrorResult& aRv) override;
+
+ already_AddRefed<Promise>
+ Unregister(ErrorResult& aRv) override;
+
+ // Partial interface from Notification API.
+ already_AddRefed<Promise>
+ ShowNotification(JSContext* aCx,
+ const nsAString& aTitle,
+ const NotificationOptions& aOptions,
+ ErrorResult& aRv) override;
+
+ already_AddRefed<Promise>
+ GetNotifications(const GetNotificationOptions& aOptions,
+ ErrorResult& aRv) override;
+
+ already_AddRefed<ServiceWorker>
+ GetInstalling() override;
+
+ already_AddRefed<ServiceWorker>
+ GetWaiting() override;
+
+ already_AddRefed<ServiceWorker>
+ GetActive() override;
+
+ already_AddRefed<PushManager>
+ GetPushManager(JSContext* aCx, ErrorResult& aRv) override;
+
+ // DOMEventTargethelper
+ void DisconnectFromOwner() override
+ {
+ StopListeningForEvents();
+ ServiceWorkerRegistration::DisconnectFromOwner();
+ }
+
+ // ServiceWorkerRegistrationListener
+ void
+ UpdateFound() override;
+
+ void
+ InvalidateWorkers(WhichServiceWorker aWhichOnes) override;
+
+ void
+ RegistrationRemoved() override;
+
+ void
+ GetScope(nsAString& aScope) const override
+ {
+ aScope = mScope;
+ }
+
+private:
+ ~ServiceWorkerRegistrationMainThread();
+
+ already_AddRefed<ServiceWorker>
+ GetWorkerReference(WhichServiceWorker aWhichOne);
+
+ void
+ StartListeningForEvents();
+
+ void
+ StopListeningForEvents();
+
+ bool mListeningForEvents;
+
+ // The following properties are cached here to ensure JS equality is satisfied
+ // instead of acquiring a new worker instance from the ServiceWorkerManager
+ // for every access. A null value is considered a cache miss.
+ // These three may change to a new worker at any time.
+ RefPtr<ServiceWorker> mInstallingWorker;
+ RefPtr<ServiceWorker> mWaitingWorker;
+ RefPtr<ServiceWorker> mActiveWorker;
+
+ RefPtr<PushManager> mPushManager;
+};
+
+NS_IMPL_ADDREF_INHERITED(ServiceWorkerRegistrationMainThread, ServiceWorkerRegistration)
+NS_IMPL_RELEASE_INHERITED(ServiceWorkerRegistrationMainThread, ServiceWorkerRegistration)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServiceWorkerRegistrationMainThread)
+NS_INTERFACE_MAP_END_INHERITING(ServiceWorkerRegistration)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerRegistrationMainThread,
+ ServiceWorkerRegistration,
+ mPushManager,
+ mInstallingWorker, mWaitingWorker, mActiveWorker);
+
+ServiceWorkerRegistrationMainThread::ServiceWorkerRegistrationMainThread(nsPIDOMWindowInner* aWindow,
+ const nsAString& aScope)
+ : ServiceWorkerRegistration(aWindow, aScope)
+ , mListeningForEvents(false)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsInnerWindow());
+ StartListeningForEvents();
+}
+
+ServiceWorkerRegistrationMainThread::~ServiceWorkerRegistrationMainThread()
+{
+ StopListeningForEvents();
+ MOZ_ASSERT(!mListeningForEvents);
+}
+
+
+already_AddRefed<ServiceWorker>
+ServiceWorkerRegistrationMainThread::GetWorkerReference(WhichServiceWorker aWhichOne)
+{
+ nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
+ if (!window) {
+ return nullptr;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIServiceWorkerManager> swm =
+ do_GetService(SERVICEWORKERMANAGER_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsISupports> serviceWorker;
+ switch(aWhichOne) {
+ case WhichServiceWorker::INSTALLING_WORKER:
+ rv = swm->GetInstalling(window, mScope, getter_AddRefs(serviceWorker));
+ break;
+ case WhichServiceWorker::WAITING_WORKER:
+ rv = swm->GetWaiting(window, mScope, getter_AddRefs(serviceWorker));
+ break;
+ case WhichServiceWorker::ACTIVE_WORKER:
+ rv = swm->GetActive(window, mScope, getter_AddRefs(serviceWorker));
+ break;
+ default:
+ MOZ_CRASH("Invalid enum value");
+ }
+
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv) || rv == NS_ERROR_DOM_NOT_FOUND_ERR,
+ "Unexpected error getting service worker instance from "
+ "ServiceWorkerManager");
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ RefPtr<ServiceWorker> ref =
+ static_cast<ServiceWorker*>(serviceWorker.get());
+ return ref.forget();
+}
+
+// XXXnsm, maybe this can be optimized to only add when a event handler is
+// registered.
+void
+ServiceWorkerRegistrationMainThread::StartListeningForEvents()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mListeningForEvents);
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->AddRegistrationEventListener(mScope, this);
+ mListeningForEvents = true;
+ }
+}
+
+void
+ServiceWorkerRegistrationMainThread::StopListeningForEvents()
+{
+ AssertIsOnMainThread();
+ if (!mListeningForEvents) {
+ return;
+ }
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->RemoveRegistrationEventListener(mScope, this);
+ }
+ mListeningForEvents = false;
+}
+
+already_AddRefed<ServiceWorker>
+ServiceWorkerRegistrationMainThread::GetInstalling()
+{
+ AssertIsOnMainThread();
+ if (!mInstallingWorker) {
+ mInstallingWorker = GetWorkerReference(WhichServiceWorker::INSTALLING_WORKER);
+ }
+
+ RefPtr<ServiceWorker> ret = mInstallingWorker;
+ return ret.forget();
+}
+
+already_AddRefed<ServiceWorker>
+ServiceWorkerRegistrationMainThread::GetWaiting()
+{
+ AssertIsOnMainThread();
+ if (!mWaitingWorker) {
+ mWaitingWorker = GetWorkerReference(WhichServiceWorker::WAITING_WORKER);
+ }
+
+ RefPtr<ServiceWorker> ret = mWaitingWorker;
+ return ret.forget();
+}
+
+already_AddRefed<ServiceWorker>
+ServiceWorkerRegistrationMainThread::GetActive()
+{
+ AssertIsOnMainThread();
+ if (!mActiveWorker) {
+ mActiveWorker = GetWorkerReference(WhichServiceWorker::ACTIVE_WORKER);
+ }
+
+ RefPtr<ServiceWorker> ret = mActiveWorker;
+ return ret.forget();
+}
+
+void
+ServiceWorkerRegistrationMainThread::UpdateFound()
+{
+ DispatchTrustedEvent(NS_LITERAL_STRING("updatefound"));
+}
+
+void
+ServiceWorkerRegistrationMainThread::InvalidateWorkers(WhichServiceWorker aWhichOnes)
+{
+ AssertIsOnMainThread();
+ if (aWhichOnes & WhichServiceWorker::INSTALLING_WORKER) {
+ mInstallingWorker = nullptr;
+ }
+
+ if (aWhichOnes & WhichServiceWorker::WAITING_WORKER) {
+ mWaitingWorker = nullptr;
+ }
+
+ if (aWhichOnes & WhichServiceWorker::ACTIVE_WORKER) {
+ mActiveWorker = nullptr;
+ }
+
+}
+
+void
+ServiceWorkerRegistrationMainThread::RegistrationRemoved()
+{
+ // If the registration is being removed completely, remove it from the
+ // window registration hash table so that a new registration would get a new
+ // wrapper JS object.
+ if (nsCOMPtr<nsPIDOMWindowInner> window = GetOwner()) {
+ window->InvalidateServiceWorkerRegistration(mScope);
+ }
+}
+
+namespace {
+
+void
+UpdateInternal(nsIPrincipal* aPrincipal,
+ const nsAString& aScope,
+ ServiceWorkerUpdateFinishCallback* aCallback)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aCallback);
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ // browser shutdown
+ return;
+ }
+
+ swm->Update(aPrincipal, NS_ConvertUTF16toUTF8(aScope), aCallback);
+}
+
+class MainThreadUpdateCallback final : public ServiceWorkerUpdateFinishCallback
+{
+ RefPtr<Promise> mPromise;
+
+ ~MainThreadUpdateCallback()
+ { }
+
+public:
+ explicit MainThreadUpdateCallback(Promise* aPromise)
+ : mPromise(aPromise)
+ {
+ AssertIsOnMainThread();
+ }
+
+ void
+ UpdateSucceeded(ServiceWorkerRegistrationInfo* aRegistration) override
+ {
+ mPromise->MaybeResolveWithUndefined();
+ }
+
+ void
+ UpdateFailed(ErrorResult& aStatus) override
+ {
+ mPromise->MaybeReject(aStatus);
+ }
+};
+
+class UpdateResultRunnable final : public WorkerRunnable
+{
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ IPC::Message mSerializedErrorResult;
+
+ ~UpdateResultRunnable()
+ {}
+
+public:
+ UpdateResultRunnable(PromiseWorkerProxy* aPromiseProxy, ErrorResult& aStatus)
+ : WorkerRunnable(aPromiseProxy->GetWorkerPrivate())
+ , mPromiseProxy(aPromiseProxy)
+ {
+ // ErrorResult is not thread safe. Serialize it for transfer across
+ // threads.
+ IPC::WriteParam(&mSerializedErrorResult, aStatus);
+ aStatus.SuppressException();
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ // Deserialize the ErrorResult now that we are back in the worker
+ // thread.
+ ErrorResult status;
+ PickleIterator iter = PickleIterator(mSerializedErrorResult);
+ Unused << IPC::ReadParam(&mSerializedErrorResult, &iter, &status);
+
+ Promise* promise = mPromiseProxy->WorkerPromise();
+ if (status.Failed()) {
+ promise->MaybeReject(status);
+ } else {
+ promise->MaybeResolveWithUndefined();
+ }
+ status.SuppressException();
+ mPromiseProxy->CleanUp();
+ return true;
+ }
+};
+
+class WorkerThreadUpdateCallback final : public ServiceWorkerUpdateFinishCallback
+{
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+
+ ~WorkerThreadUpdateCallback()
+ {
+ }
+
+public:
+ explicit WorkerThreadUpdateCallback(PromiseWorkerProxy* aPromiseProxy)
+ : mPromiseProxy(aPromiseProxy)
+ {
+ AssertIsOnMainThread();
+ }
+
+ void
+ UpdateSucceeded(ServiceWorkerRegistrationInfo* aRegistration) override
+ {
+ ErrorResult rv(NS_OK);
+ Finish(rv);
+ }
+
+ void
+ UpdateFailed(ErrorResult& aStatus) override
+ {
+ Finish(aStatus);
+ }
+
+ void
+ Finish(ErrorResult& aStatus)
+ {
+ if (!mPromiseProxy) {
+ return;
+ }
+
+ RefPtr<PromiseWorkerProxy> proxy = mPromiseProxy.forget();
+
+ MutexAutoLock lock(proxy->Lock());
+ if (proxy->CleanedUp()) {
+ return;
+ }
+
+ RefPtr<UpdateResultRunnable> r =
+ new UpdateResultRunnable(proxy, aStatus);
+ r->Dispatch();
+ }
+};
+
+class UpdateRunnable final : public Runnable
+{
+public:
+ UpdateRunnable(PromiseWorkerProxy* aPromiseProxy,
+ const nsAString& aScope)
+ : mPromiseProxy(aPromiseProxy)
+ , mScope(aScope)
+ {}
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+ ErrorResult result;
+
+ nsCOMPtr<nsIPrincipal> principal;
+ // UpdateInternal may try to reject the promise synchronously leading
+ // to a deadlock.
+ {
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return NS_OK;
+ }
+
+ principal = mPromiseProxy->GetWorkerPrivate()->GetPrincipal();
+ }
+ MOZ_ASSERT(principal);
+
+ RefPtr<WorkerThreadUpdateCallback> cb =
+ new WorkerThreadUpdateCallback(mPromiseProxy);
+ UpdateInternal(principal, mScope, cb);
+ return NS_OK;
+ }
+
+private:
+ ~UpdateRunnable()
+ {}
+
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ const nsString mScope;
+};
+
+class UnregisterCallback final : public nsIServiceWorkerUnregisterCallback
+{
+ RefPtr<Promise> mPromise;
+
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit UnregisterCallback(Promise* aPromise)
+ : mPromise(aPromise)
+ {
+ MOZ_ASSERT(mPromise);
+ }
+
+ NS_IMETHOD
+ UnregisterSucceeded(bool aState) override
+ {
+ AssertIsOnMainThread();
+ mPromise->MaybeResolve(aState);
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ UnregisterFailed() override
+ {
+ AssertIsOnMainThread();
+
+ mPromise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return NS_OK;
+ }
+
+private:
+ ~UnregisterCallback()
+ { }
+};
+
+NS_IMPL_ISUPPORTS(UnregisterCallback, nsIServiceWorkerUnregisterCallback)
+
+class FulfillUnregisterPromiseRunnable final : public WorkerRunnable
+{
+ RefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
+ Maybe<bool> mState;
+public:
+ FulfillUnregisterPromiseRunnable(PromiseWorkerProxy* aProxy,
+ Maybe<bool> aState)
+ : WorkerRunnable(aProxy->GetWorkerPrivate())
+ , mPromiseWorkerProxy(aProxy)
+ , mState(aState)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mPromiseWorkerProxy);
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ RefPtr<Promise> promise = mPromiseWorkerProxy->WorkerPromise();
+ if (mState.isSome()) {
+ promise->MaybeResolve(mState.value());
+ } else {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ }
+
+ mPromiseWorkerProxy->CleanUp();
+ return true;
+ }
+};
+
+class WorkerUnregisterCallback final : public nsIServiceWorkerUnregisterCallback
+{
+ RefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit WorkerUnregisterCallback(PromiseWorkerProxy* aProxy)
+ : mPromiseWorkerProxy(aProxy)
+ {
+ MOZ_ASSERT(aProxy);
+ }
+
+ NS_IMETHOD
+ UnregisterSucceeded(bool aState) override
+ {
+ AssertIsOnMainThread();
+ Finish(Some(aState));
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ UnregisterFailed() override
+ {
+ AssertIsOnMainThread();
+ Finish(Nothing());
+ return NS_OK;
+ }
+
+private:
+ ~WorkerUnregisterCallback()
+ {}
+
+ void
+ Finish(Maybe<bool> aState)
+ {
+ AssertIsOnMainThread();
+ if (!mPromiseWorkerProxy) {
+ return;
+ }
+
+ RefPtr<PromiseWorkerProxy> proxy = mPromiseWorkerProxy.forget();
+ MutexAutoLock lock(proxy->Lock());
+ if (proxy->CleanedUp()) {
+ return;
+ }
+
+ RefPtr<WorkerRunnable> r =
+ new FulfillUnregisterPromiseRunnable(proxy, aState);
+
+ r->Dispatch();
+ }
+};
+
+NS_IMPL_ISUPPORTS(WorkerUnregisterCallback, nsIServiceWorkerUnregisterCallback);
+
+/*
+ * If the worker goes away, we still continue to unregister, but we don't try to
+ * resolve the worker Promise (which doesn't exist by that point).
+ */
+class StartUnregisterRunnable final : public Runnable
+{
+ RefPtr<PromiseWorkerProxy> mPromiseWorkerProxy;
+ const nsString mScope;
+
+public:
+ StartUnregisterRunnable(PromiseWorkerProxy* aProxy,
+ const nsAString& aScope)
+ : mPromiseWorkerProxy(aProxy)
+ , mScope(aScope)
+ {
+ MOZ_ASSERT(aProxy);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+
+ // XXXnsm: There is a rare chance of this failing if the worker gets
+ // destroyed. In that case, unregister() called from a SW is no longer
+ // guaranteed to run. We should fix this by having a main thread proxy
+ // maintain a strongref to ServiceWorkerRegistrationInfo and use its
+ // principal. Can that be trusted?
+ nsCOMPtr<nsIPrincipal> principal;
+ {
+ MutexAutoLock lock(mPromiseWorkerProxy->Lock());
+ if (mPromiseWorkerProxy->CleanedUp()) {
+ return NS_OK;
+ }
+
+ WorkerPrivate* worker = mPromiseWorkerProxy->GetWorkerPrivate();
+ MOZ_ASSERT(worker);
+ principal = worker->GetPrincipal();
+ }
+ MOZ_ASSERT(principal);
+
+ RefPtr<WorkerUnregisterCallback> cb =
+ new WorkerUnregisterCallback(mPromiseWorkerProxy);
+ nsCOMPtr<nsIServiceWorkerManager> swm =
+ mozilla::services::GetServiceWorkerManager();
+ nsresult rv = swm->Unregister(principal, cb, mScope);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ cb->UnregisterFailed();
+ }
+
+ return NS_OK;
+ }
+};
+} // namespace
+
+already_AddRefed<Promise>
+ServiceWorkerRegistrationMainThread::Update(ErrorResult& aRv)
+{
+ AssertIsOnMainThread();
+ nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(GetOwner());
+ if (!go) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = Promise::Create(go, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocument> doc = GetOwner()->GetExtantDoc();
+ MOZ_ASSERT(doc);
+
+ RefPtr<MainThreadUpdateCallback> cb =
+ new MainThreadUpdateCallback(promise);
+ UpdateInternal(doc->NodePrincipal(), mScope, cb);
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+ServiceWorkerRegistrationMainThread::Unregister(ErrorResult& aRv)
+{
+ AssertIsOnMainThread();
+ nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(GetOwner());
+ if (!go) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // Although the spec says that the same-origin checks should also be done
+ // asynchronously, we do them in sync because the Promise created by the
+ // WebIDL infrastructure due to a returned error will be resolved
+ // asynchronously. We aren't making any internal state changes in these
+ // checks, so ordering of multiple calls is not affected.
+ nsCOMPtr<nsIDocument> document = GetOwner()->GetExtantDoc();
+ if (!document) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> scopeURI;
+ nsCOMPtr<nsIURI> baseURI = document->GetBaseURI();
+ // "If the origin of scope is not client's origin..."
+ nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), mScope, nullptr, baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPrincipal> documentPrincipal = document->NodePrincipal();
+ rv = documentPrincipal->CheckMayLoad(scopeURI, true /* report */,
+ false /* allowIfInheritsPrinciple */);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ nsAutoCString uriSpec;
+ aRv = scopeURI->GetSpecIgnoringRef(uriSpec);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIServiceWorkerManager> swm =
+ mozilla::services::GetServiceWorkerManager();
+
+ RefPtr<Promise> promise = Promise::Create(go, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RefPtr<UnregisterCallback> cb = new UnregisterCallback(promise);
+
+ NS_ConvertUTF8toUTF16 scope(uriSpec);
+ aRv = swm->Unregister(documentPrincipal, cb, scope);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+// Notification API extension.
+already_AddRefed<Promise>
+ServiceWorkerRegistrationMainThread::ShowNotification(JSContext* aCx,
+ const nsAString& aTitle,
+ const NotificationOptions& aOptions,
+ ErrorResult& aRv)
+{
+ AssertIsOnMainThread();
+ nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
+ if (NS_WARN_IF(!window)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<ServiceWorker> worker = GetActive();
+ if (!worker) {
+ aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(mScope);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(window);
+ RefPtr<Promise> p =
+ Notification::ShowPersistentNotification(aCx, global, mScope, aTitle,
+ aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return p.forget();
+}
+
+already_AddRefed<Promise>
+ServiceWorkerRegistrationMainThread::GetNotifications(const GetNotificationOptions& aOptions, ErrorResult& aRv)
+{
+ AssertIsOnMainThread();
+ nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
+ if (NS_WARN_IF(!window)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+ return Notification::Get(window, aOptions, mScope, aRv);
+}
+
+already_AddRefed<PushManager>
+ServiceWorkerRegistrationMainThread::GetPushManager(JSContext* aCx,
+ ErrorResult& aRv)
+{
+ AssertIsOnMainThread();
+
+ if (!mPushManager) {
+ nsCOMPtr<nsIGlobalObject> globalObject = do_QueryInterface(GetOwner());
+
+ if (!globalObject) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ GlobalObject global(aCx, globalObject->GetGlobalJSObject());
+ mPushManager = PushManager::Constructor(global, mScope, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ RefPtr<PushManager> ret = mPushManager;
+ return ret.forget();
+}
+
+////////////////////////////////////////////////////
+// Worker Thread implementation
+
+class ServiceWorkerRegistrationWorkerThread final : public ServiceWorkerRegistration
+ , public WorkerHolder
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerRegistrationWorkerThread,
+ ServiceWorkerRegistration)
+
+ ServiceWorkerRegistrationWorkerThread(WorkerPrivate* aWorkerPrivate,
+ const nsAString& aScope);
+
+ already_AddRefed<Promise>
+ Update(ErrorResult& aRv) override;
+
+ already_AddRefed<Promise>
+ Unregister(ErrorResult& aRv) override;
+
+ // Partial interface from Notification API.
+ already_AddRefed<Promise>
+ ShowNotification(JSContext* aCx,
+ const nsAString& aTitle,
+ const NotificationOptions& aOptions,
+ ErrorResult& aRv) override;
+
+ already_AddRefed<Promise>
+ GetNotifications(const GetNotificationOptions& aOptions,
+ ErrorResult& aRv) override;
+
+ already_AddRefed<ServiceWorker>
+ GetInstalling() override;
+
+ already_AddRefed<ServiceWorker>
+ GetWaiting() override;
+
+ already_AddRefed<ServiceWorker>
+ GetActive() override;
+
+ void
+ GetScope(nsAString& aScope) const override
+ {
+ aScope = mScope;
+ }
+
+ bool
+ Notify(Status aStatus) override;
+
+ already_AddRefed<PushManager>
+ GetPushManager(JSContext* aCx, ErrorResult& aRv) override;
+
+private:
+ ~ServiceWorkerRegistrationWorkerThread();
+
+ void
+ InitListener();
+
+ void
+ ReleaseListener();
+
+ WorkerPrivate* mWorkerPrivate;
+ RefPtr<WorkerListener> mListener;
+
+ RefPtr<PushManager> mPushManager;
+};
+
+class WorkerListener final : public ServiceWorkerRegistrationListener
+{
+ // Accessed on the main thread.
+ WorkerPrivate* mWorkerPrivate;
+ nsString mScope;
+ bool mListeningForEvents;
+
+ // Accessed on the worker thread.
+ ServiceWorkerRegistrationWorkerThread* mRegistration;
+
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WorkerListener, override)
+
+ WorkerListener(WorkerPrivate* aWorkerPrivate,
+ ServiceWorkerRegistrationWorkerThread* aReg)
+ : mWorkerPrivate(aWorkerPrivate)
+ , mListeningForEvents(false)
+ , mRegistration(aReg)
+ {
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(mRegistration);
+ // Copy scope so we can return it on the main thread.
+ mRegistration->GetScope(mScope);
+ }
+
+ void
+ StartListeningForEvents()
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mListeningForEvents);
+ MOZ_ASSERT(mWorkerPrivate);
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ // FIXME(nsm): Maybe the function shouldn't take an explicit scope.
+ swm->AddRegistrationEventListener(mScope, this);
+ mListeningForEvents = true;
+ }
+ }
+
+ void
+ StopListeningForEvents()
+ {
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(mListeningForEvents);
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+
+ // We aren't going to need this anymore and we shouldn't hold on since the
+ // worker will go away soon.
+ mWorkerPrivate = nullptr;
+
+ if (swm) {
+ // FIXME(nsm): Maybe the function shouldn't take an explicit scope.
+ swm->RemoveRegistrationEventListener(mScope, this);
+ mListeningForEvents = false;
+ }
+ }
+
+ // ServiceWorkerRegistrationListener
+ void
+ UpdateFound() override;
+
+ void
+ InvalidateWorkers(WhichServiceWorker aWhichOnes) override
+ {
+ AssertIsOnMainThread();
+ // FIXME(nsm);
+ }
+
+ void
+ RegistrationRemoved() override
+ {
+ AssertIsOnMainThread();
+ }
+
+ void
+ GetScope(nsAString& aScope) const override
+ {
+ aScope = mScope;
+ }
+
+ ServiceWorkerRegistrationWorkerThread*
+ GetRegistration() const
+ {
+ if (mWorkerPrivate) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+ return mRegistration;
+ }
+
+ void
+ ClearRegistration()
+ {
+ if (mWorkerPrivate) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+ mRegistration = nullptr;
+ }
+
+private:
+ ~WorkerListener()
+ {
+ MOZ_ASSERT(!mListeningForEvents);
+ }
+};
+
+NS_IMPL_ADDREF_INHERITED(ServiceWorkerRegistrationWorkerThread, ServiceWorkerRegistration)
+NS_IMPL_RELEASE_INHERITED(ServiceWorkerRegistrationWorkerThread, ServiceWorkerRegistration)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServiceWorkerRegistrationWorkerThread)
+NS_INTERFACE_MAP_END_INHERITING(ServiceWorkerRegistration)
+
+// Expanded macros since we need special behaviour to release the proxy.
+NS_IMPL_CYCLE_COLLECTION_CLASS(ServiceWorkerRegistrationWorkerThread)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ServiceWorkerRegistrationWorkerThread,
+ ServiceWorkerRegistration)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPushManager)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ServiceWorkerRegistrationWorkerThread,
+ ServiceWorkerRegistration)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPushManager)
+ tmp->ReleaseListener();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+ServiceWorkerRegistrationWorkerThread::ServiceWorkerRegistrationWorkerThread(WorkerPrivate* aWorkerPrivate,
+ const nsAString& aScope)
+ : ServiceWorkerRegistration(nullptr, aScope)
+ , mWorkerPrivate(aWorkerPrivate)
+{
+ InitListener();
+}
+
+ServiceWorkerRegistrationWorkerThread::~ServiceWorkerRegistrationWorkerThread()
+{
+ ReleaseListener();
+ MOZ_ASSERT(!mListener);
+}
+
+already_AddRefed<workers::ServiceWorker>
+ServiceWorkerRegistrationWorkerThread::GetInstalling()
+{
+ // FIXME(nsm): Will be implemented after Bug 1113522.
+ return nullptr;
+}
+
+already_AddRefed<ServiceWorker>
+ServiceWorkerRegistrationWorkerThread::GetWaiting()
+{
+ // FIXME(nsm): Will be implemented after Bug 1113522.
+ return nullptr;
+}
+
+already_AddRefed<ServiceWorker>
+ServiceWorkerRegistrationWorkerThread::GetActive()
+{
+ // FIXME(nsm): Will be implemented after Bug 1113522.
+ return nullptr;
+}
+
+already_AddRefed<Promise>
+ServiceWorkerRegistrationWorkerThread::Update(ErrorResult& aRv)
+{
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ worker->AssertIsOnWorkerThread();
+
+ RefPtr<Promise> promise = Promise::Create(worker->GlobalScope(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Avoid infinite update loops by ignoring update() calls during top
+ // level script evaluation. See:
+ // https://github.com/slightlyoff/ServiceWorker/issues/800
+ if (worker->LoadScriptAsPartOfLoadingServiceWorkerScript()) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+
+ RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, promise);
+ if (!proxy) {
+ aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+ return nullptr;
+ }
+
+ RefPtr<UpdateRunnable> r = new UpdateRunnable(proxy, mScope);
+ MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(r.forget()));
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+ServiceWorkerRegistrationWorkerThread::Unregister(ErrorResult& aRv)
+{
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ worker->AssertIsOnWorkerThread();
+
+ if (!worker->IsServiceWorker()) {
+ // For other workers, the registration probably originated from
+ // getRegistration(), so we may have to validate origin etc. Let's do this
+ // this later.
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = Promise::Create(worker->GlobalScope(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, promise);
+ if (!proxy) {
+ aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+ return nullptr;
+ }
+
+ RefPtr<StartUnregisterRunnable> r = new StartUnregisterRunnable(proxy, mScope);
+ MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(r.forget()));
+
+ return promise.forget();
+}
+
+void
+ServiceWorkerRegistrationWorkerThread::InitListener()
+{
+ MOZ_ASSERT(!mListener);
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ worker->AssertIsOnWorkerThread();
+
+ mListener = new WorkerListener(worker, this);
+ if (!HoldWorker(worker, Closing)) {
+ mListener = nullptr;
+ NS_WARNING("Could not add feature");
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> r =
+ NewRunnableMethod(mListener, &WorkerListener::StartListeningForEvents);
+ MOZ_ALWAYS_SUCCEEDS(worker->DispatchToMainThread(r.forget()));
+}
+
+void
+ServiceWorkerRegistrationWorkerThread::ReleaseListener()
+{
+ if (!mListener) {
+ return;
+ }
+
+ // We can assert worker here, because:
+ // 1) We always HoldWorker, so if the worker has shutdown already, we'll
+ // have received Notify and removed it. If HoldWorker had failed,
+ // mListener will be null and we won't reach here.
+ // 2) Otherwise, worker is still around even if we are going away.
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ ReleaseWorker();
+
+ mListener->ClearRegistration();
+
+ nsCOMPtr<nsIRunnable> r =
+ NewRunnableMethod(mListener, &WorkerListener::StopListeningForEvents);
+ MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(r.forget()));
+
+ mListener = nullptr;
+ mWorkerPrivate = nullptr;
+}
+
+bool
+ServiceWorkerRegistrationWorkerThread::Notify(Status aStatus)
+{
+ ReleaseListener();
+ return true;
+}
+
+class FireUpdateFoundRunnable final : public WorkerRunnable
+{
+ RefPtr<WorkerListener> mListener;
+public:
+ FireUpdateFoundRunnable(WorkerPrivate* aWorkerPrivate,
+ WorkerListener* aListener)
+ : WorkerRunnable(aWorkerPrivate)
+ , mListener(aListener)
+ {
+ // Need this assertion for now since runnables which modify busy count can
+ // only be dispatched from parent thread to worker thread and we don't deal
+ // with nested workers. SW threads can't be nested.
+ MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ ServiceWorkerRegistrationWorkerThread* reg = mListener->GetRegistration();
+ if (reg) {
+ reg->DispatchTrustedEvent(NS_LITERAL_STRING("updatefound"));
+ }
+ return true;
+ }
+};
+
+void
+WorkerListener::UpdateFound()
+{
+ AssertIsOnMainThread();
+ if (mWorkerPrivate) {
+ RefPtr<FireUpdateFoundRunnable> r =
+ new FireUpdateFoundRunnable(mWorkerPrivate, this);
+ Unused << NS_WARN_IF(!r->Dispatch());
+ }
+}
+
+// Notification API extension.
+already_AddRefed<Promise>
+ServiceWorkerRegistrationWorkerThread::ShowNotification(JSContext* aCx,
+ const nsAString& aTitle,
+ const NotificationOptions& aOptions,
+ ErrorResult& aRv)
+{
+ // Until Bug 1131324 exposes ServiceWorkerContainer on workers,
+ // ShowPersistentNotification() checks for valid active worker while it is
+ // also verifying scope so that we block the worker on the main thread only
+ // once.
+ RefPtr<Promise> p =
+ Notification::ShowPersistentNotification(aCx, mWorkerPrivate->GlobalScope(),
+ mScope, aTitle, aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return p.forget();
+}
+
+already_AddRefed<Promise>
+ServiceWorkerRegistrationWorkerThread::GetNotifications(const GetNotificationOptions& aOptions,
+ ErrorResult& aRv)
+{
+ return Notification::WorkerGet(mWorkerPrivate, aOptions, mScope, aRv);
+}
+
+already_AddRefed<PushManager>
+ServiceWorkerRegistrationWorkerThread::GetPushManager(JSContext* aCx, ErrorResult& aRv)
+{
+ if (!mPushManager) {
+ mPushManager = new PushManager(mScope);
+ }
+
+ RefPtr<PushManager> ret = mPushManager;
+ return ret.forget();
+}
+
+////////////////////////////////////////////////////
+// Base class implementation
+
+NS_IMPL_ADDREF_INHERITED(ServiceWorkerRegistration, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(ServiceWorkerRegistration, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServiceWorkerRegistration)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+ServiceWorkerRegistration::ServiceWorkerRegistration(nsPIDOMWindowInner* aWindow,
+ const nsAString& aScope)
+ : DOMEventTargetHelper(aWindow)
+ , mScope(aScope)
+{}
+
+JSObject*
+ServiceWorkerRegistration::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto)
+{
+ return ServiceWorkerRegistrationBinding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */ already_AddRefed<ServiceWorkerRegistration>
+ServiceWorkerRegistration::CreateForMainThread(nsPIDOMWindowInner* aWindow,
+ const nsAString& aScope)
+{
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<ServiceWorkerRegistration> registration =
+ new ServiceWorkerRegistrationMainThread(aWindow, aScope);
+
+ return registration.forget();
+}
+
+/* static */ already_AddRefed<ServiceWorkerRegistration>
+ServiceWorkerRegistration::CreateForWorker(workers::WorkerPrivate* aWorkerPrivate,
+ const nsAString& aScope)
+{
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<ServiceWorkerRegistration> registration =
+ new ServiceWorkerRegistrationWorkerThread(aWorkerPrivate, aScope);
+
+ return registration.forget();
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/workers/ServiceWorkerRegistration.h b/dom/workers/ServiceWorkerRegistration.h
new file mode 100644
index 000000000..5b0847e7b
--- /dev/null
+++ b/dom/workers/ServiceWorkerRegistration.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ServiceWorkerRegistration_h
+#define mozilla_dom_ServiceWorkerRegistration_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/ServiceWorkerBinding.h"
+#include "mozilla/dom/ServiceWorkerCommon.h"
+#include "mozilla/dom/workers/bindings/WorkerHolder.h"
+#include "nsContentUtils.h" // Required for nsContentUtils::PushEnabled
+
+// Support for Notification API extension.
+#include "mozilla/dom/NotificationBinding.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+class PushManager;
+class WorkerListener;
+
+namespace workers {
+class ServiceWorker;
+class WorkerPrivate;
+} // namespace workers
+
+// Used by ServiceWorkerManager to notify ServiceWorkerRegistrations of
+// updatefound event and invalidating ServiceWorker instances.
+class ServiceWorkerRegistrationListener
+{
+public:
+ NS_IMETHOD_(MozExternalRefCountType) AddRef() = 0;
+ NS_IMETHOD_(MozExternalRefCountType) Release() = 0;
+
+ virtual void
+ UpdateFound() = 0;
+
+ virtual void
+ InvalidateWorkers(WhichServiceWorker aWhichOnes) = 0;
+
+ virtual void
+ RegistrationRemoved() = 0;
+
+ virtual void
+ GetScope(nsAString& aScope) const = 0;
+};
+
+class ServiceWorkerRegistration : public DOMEventTargetHelper
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ IMPL_EVENT_HANDLER(updatefound)
+
+ static bool
+ Visible(JSContext* aCx, JSObject* aObj);
+
+ static bool
+ NotificationAPIVisible(JSContext* aCx, JSObject* aObj);
+
+
+ static already_AddRefed<ServiceWorkerRegistration>
+ CreateForMainThread(nsPIDOMWindowInner* aWindow,
+ const nsAString& aScope);
+
+ static already_AddRefed<ServiceWorkerRegistration>
+ CreateForWorker(workers::WorkerPrivate* aWorkerPrivate,
+ const nsAString& aScope);
+
+ JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual already_AddRefed<workers::ServiceWorker>
+ GetInstalling() = 0;
+
+ virtual already_AddRefed<workers::ServiceWorker>
+ GetWaiting() = 0;
+
+ virtual already_AddRefed<workers::ServiceWorker>
+ GetActive() = 0;
+
+ virtual void
+ GetScope(nsAString& aScope) const = 0;
+
+ virtual already_AddRefed<Promise>
+ Update(ErrorResult& aRv) = 0;
+
+ virtual already_AddRefed<Promise>
+ Unregister(ErrorResult& aRv) = 0;
+
+ virtual already_AddRefed<PushManager>
+ GetPushManager(JSContext* aCx, ErrorResult& aRv) = 0;
+
+ virtual already_AddRefed<Promise>
+ ShowNotification(JSContext* aCx,
+ const nsAString& aTitle,
+ const NotificationOptions& aOptions,
+ ErrorResult& aRv) = 0;
+
+ virtual already_AddRefed<Promise>
+ GetNotifications(const GetNotificationOptions& aOptions,
+ ErrorResult& aRv) = 0;
+
+protected:
+ ServiceWorkerRegistration(nsPIDOMWindowInner* aWindow,
+ const nsAString& aScope);
+
+ virtual ~ServiceWorkerRegistration()
+ { }
+
+ const nsString mScope;
+};
+
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_ServiceWorkerRegistration_h */
diff --git a/dom/workers/ServiceWorkerRegistrationInfo.cpp b/dom/workers/ServiceWorkerRegistrationInfo.cpp
new file mode 100644
index 000000000..26ad74bda
--- /dev/null
+++ b/dom/workers/ServiceWorkerRegistrationInfo.cpp
@@ -0,0 +1,546 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerRegistrationInfo.h"
+
+BEGIN_WORKERS_NAMESPACE
+
+namespace {
+
+class ContinueActivateRunnable final : public LifeCycleEventCallback
+{
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
+ bool mSuccess;
+
+public:
+ explicit ContinueActivateRunnable(const nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
+ : mRegistration(aRegistration)
+ , mSuccess(false)
+ {
+ AssertIsOnMainThread();
+ }
+
+ void
+ SetResult(bool aResult) override
+ {
+ mSuccess = aResult;
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+ mRegistration->FinishActivate(mSuccess);
+ mRegistration = nullptr;
+ return NS_OK;
+ }
+};
+
+} // anonymous namespace
+
+void
+ServiceWorkerRegistrationInfo::Clear()
+{
+ if (mEvaluatingWorker) {
+ mEvaluatingWorker = nullptr;
+ }
+
+ if (mInstallingWorker) {
+ mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
+ mInstallingWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo();
+ mInstallingWorker = nullptr;
+ // FIXME(nsm): Abort any inflight requests from installing worker.
+ }
+
+ if (mWaitingWorker) {
+ mWaitingWorker->UpdateState(ServiceWorkerState::Redundant);
+ mWaitingWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo();
+ mWaitingWorker = nullptr;
+ }
+
+ if (mActiveWorker) {
+ mActiveWorker->UpdateState(ServiceWorkerState::Redundant);
+ mActiveWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo();
+ mActiveWorker = nullptr;
+ }
+
+ NotifyListenersOnChange(WhichServiceWorker::INSTALLING_WORKER |
+ WhichServiceWorker::WAITING_WORKER |
+ WhichServiceWorker::ACTIVE_WORKER);
+}
+
+ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(const nsACString& aScope,
+ nsIPrincipal* aPrincipal)
+ : mControlledDocumentsCounter(0)
+ , mUpdateState(NoUpdate)
+ , mLastUpdateCheckTime(0)
+ , mScope(aScope)
+ , mPrincipal(aPrincipal)
+ , mPendingUninstall(false)
+{}
+
+ServiceWorkerRegistrationInfo::~ServiceWorkerRegistrationInfo()
+{
+ if (IsControllingDocuments()) {
+ NS_WARNING("ServiceWorkerRegistrationInfo is still controlling documents. This can be a bug or a leak in ServiceWorker API or in any other API that takes the document alive.");
+ }
+}
+
+NS_IMPL_ISUPPORTS(ServiceWorkerRegistrationInfo, nsIServiceWorkerRegistrationInfo)
+
+NS_IMETHODIMP
+ServiceWorkerRegistrationInfo::GetPrincipal(nsIPrincipal** aPrincipal)
+{
+ AssertIsOnMainThread();
+ NS_ADDREF(*aPrincipal = mPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerRegistrationInfo::GetScope(nsAString& aScope)
+{
+ AssertIsOnMainThread();
+ CopyUTF8toUTF16(mScope, aScope);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerRegistrationInfo::GetScriptSpec(nsAString& aScriptSpec)
+{
+ AssertIsOnMainThread();
+ RefPtr<ServiceWorkerInfo> newest = Newest();
+ if (newest) {
+ CopyUTF8toUTF16(newest->ScriptSpec(), aScriptSpec);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerRegistrationInfo::GetInstallingWorker(nsIServiceWorkerInfo **aResult)
+{
+ AssertIsOnMainThread();
+ nsCOMPtr<nsIServiceWorkerInfo> info = do_QueryInterface(mInstallingWorker);
+ info.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerRegistrationInfo::GetWaitingWorker(nsIServiceWorkerInfo **aResult)
+{
+ AssertIsOnMainThread();
+ nsCOMPtr<nsIServiceWorkerInfo> info = do_QueryInterface(mWaitingWorker);
+ info.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerRegistrationInfo::GetActiveWorker(nsIServiceWorkerInfo **aResult)
+{
+ AssertIsOnMainThread();
+ nsCOMPtr<nsIServiceWorkerInfo> info = do_QueryInterface(mActiveWorker);
+ info.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerRegistrationInfo::GetWorkerByID(uint64_t aID, nsIServiceWorkerInfo **aResult)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aResult);
+
+ RefPtr<ServiceWorkerInfo> info = GetServiceWorkerInfoById(aID);
+ // It is ok to return null for a missing service worker info.
+ info.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerRegistrationInfo::AddListener(
+ nsIServiceWorkerRegistrationInfoListener *aListener)
+{
+ AssertIsOnMainThread();
+
+ if (!aListener || mListeners.Contains(aListener)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mListeners.AppendElement(aListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerRegistrationInfo::RemoveListener(
+ nsIServiceWorkerRegistrationInfoListener *aListener)
+{
+ AssertIsOnMainThread();
+
+ if (!aListener || !mListeners.Contains(aListener)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mListeners.RemoveElement(aListener);
+
+ return NS_OK;
+}
+
+already_AddRefed<ServiceWorkerInfo>
+ServiceWorkerRegistrationInfo::GetServiceWorkerInfoById(uint64_t aId)
+{
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerInfo> serviceWorker;
+ if (mEvaluatingWorker && mEvaluatingWorker->ID() == aId) {
+ serviceWorker = mEvaluatingWorker;
+ } else if (mInstallingWorker && mInstallingWorker->ID() == aId) {
+ serviceWorker = mInstallingWorker;
+ } else if (mWaitingWorker && mWaitingWorker->ID() == aId) {
+ serviceWorker = mWaitingWorker;
+ } else if (mActiveWorker && mActiveWorker->ID() == aId) {
+ serviceWorker = mActiveWorker;
+ }
+
+ return serviceWorker.forget();
+}
+
+void
+ServiceWorkerRegistrationInfo::TryToActivateAsync()
+{
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_DispatchToMainThread(NewRunnableMethod(this,
+ &ServiceWorkerRegistrationInfo::TryToActivate)));
+}
+
+/*
+ * TryToActivate should not be called directly, use TryToActivateAsync instead.
+ */
+void
+ServiceWorkerRegistrationInfo::TryToActivate()
+{
+ AssertIsOnMainThread();
+ bool controlling = IsControllingDocuments();
+ bool skipWaiting = mWaitingWorker && mWaitingWorker->SkipWaitingFlag();
+ bool idle = IsIdle();
+ if (idle && (!controlling || skipWaiting)) {
+ Activate();
+ }
+}
+
+void
+ServiceWorkerRegistrationInfo::Activate()
+{
+ if (!mWaitingWorker) {
+ return;
+ }
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ // browser shutdown began during async activation step
+ return;
+ }
+
+ TransitionWaitingToActive();
+
+ // FIXME(nsm): Unlink appcache if there is one.
+
+ swm->CheckPendingReadyPromises();
+
+ // "Queue a task to fire a simple event named controllerchange..."
+ nsCOMPtr<nsIRunnable> controllerChangeRunnable =
+ NewRunnableMethod<RefPtr<ServiceWorkerRegistrationInfo>>(
+ swm, &ServiceWorkerManager::FireControllerChange, this);
+ NS_DispatchToMainThread(controllerChangeRunnable);
+
+ nsCOMPtr<nsIRunnable> failRunnable =
+ NewRunnableMethod<bool>(this,
+ &ServiceWorkerRegistrationInfo::FinishActivate,
+ false /* success */);
+
+ nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> handle(
+ new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(this));
+ RefPtr<LifeCycleEventCallback> callback = new ContinueActivateRunnable(handle);
+
+ ServiceWorkerPrivate* workerPrivate = mActiveWorker->WorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ nsresult rv = workerPrivate->SendLifeCycleEvent(NS_LITERAL_STRING("activate"),
+ callback, failRunnable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(failRunnable));
+ return;
+ }
+}
+
+void
+ServiceWorkerRegistrationInfo::FinishActivate(bool aSuccess)
+{
+ if (mPendingUninstall || !mActiveWorker ||
+ mActiveWorker->State() != ServiceWorkerState::Activating) {
+ return;
+ }
+
+ // Activation never fails, so aSuccess is ignored.
+ mActiveWorker->UpdateState(ServiceWorkerState::Activated);
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ // browser shutdown started during async activation completion step
+ return;
+ }
+ swm->StoreRegistration(mPrincipal, this);
+}
+
+void
+ServiceWorkerRegistrationInfo::RefreshLastUpdateCheckTime()
+{
+ AssertIsOnMainThread();
+ mLastUpdateCheckTime = PR_IntervalNow() / PR_MSEC_PER_SEC;
+}
+
+bool
+ServiceWorkerRegistrationInfo::IsLastUpdateCheckTimeOverOneDay() const
+{
+ AssertIsOnMainThread();
+
+ // For testing.
+ if (Preferences::GetBool("dom.serviceWorkers.testUpdateOverOneDay")) {
+ return true;
+ }
+
+ const uint64_t kSecondsPerDay = 86400;
+ const uint64_t now = PR_IntervalNow() / PR_MSEC_PER_SEC;
+
+ if ((now - mLastUpdateCheckTime) > kSecondsPerDay) {
+ return true;
+ }
+ return false;
+}
+
+void
+ServiceWorkerRegistrationInfo::NotifyListenersOnChange(WhichServiceWorker aChangedWorkers)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aChangedWorkers & (WhichServiceWorker::INSTALLING_WORKER |
+ WhichServiceWorker::WAITING_WORKER |
+ WhichServiceWorker::ACTIVE_WORKER));
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ // browser shutdown started
+ return;
+ }
+
+ swm->InvalidateServiceWorkerRegistrationWorker(this, aChangedWorkers);
+
+ nsTArray<nsCOMPtr<nsIServiceWorkerRegistrationInfoListener>> listeners(mListeners);
+ for (size_t index = 0; index < listeners.Length(); ++index) {
+ listeners[index]->OnChange();
+ }
+}
+
+void
+ServiceWorkerRegistrationInfo::MaybeScheduleTimeCheckAndUpdate()
+{
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ // shutting down, do nothing
+ return;
+ }
+
+ if (mUpdateState == NoUpdate) {
+ mUpdateState = NeedTimeCheckAndUpdate;
+ }
+
+ swm->ScheduleUpdateTimer(mPrincipal, mScope);
+}
+
+void
+ServiceWorkerRegistrationInfo::MaybeScheduleUpdate()
+{
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ // shutting down, do nothing
+ return;
+ }
+
+ mUpdateState = NeedUpdate;
+
+ swm->ScheduleUpdateTimer(mPrincipal, mScope);
+}
+
+bool
+ServiceWorkerRegistrationInfo::CheckAndClearIfUpdateNeeded()
+{
+ AssertIsOnMainThread();
+
+ bool result = mUpdateState == NeedUpdate ||
+ (mUpdateState == NeedTimeCheckAndUpdate &&
+ IsLastUpdateCheckTimeOverOneDay());
+
+ mUpdateState = NoUpdate;
+
+ return result;
+}
+
+ServiceWorkerInfo*
+ServiceWorkerRegistrationInfo::GetEvaluating() const
+{
+ AssertIsOnMainThread();
+ return mEvaluatingWorker;
+}
+
+ServiceWorkerInfo*
+ServiceWorkerRegistrationInfo::GetInstalling() const
+{
+ AssertIsOnMainThread();
+ return mInstallingWorker;
+}
+
+ServiceWorkerInfo*
+ServiceWorkerRegistrationInfo::GetWaiting() const
+{
+ AssertIsOnMainThread();
+ return mWaitingWorker;
+}
+
+ServiceWorkerInfo*
+ServiceWorkerRegistrationInfo::GetActive() const
+{
+ AssertIsOnMainThread();
+ return mActiveWorker;
+}
+
+void
+ServiceWorkerRegistrationInfo::SetEvaluating(ServiceWorkerInfo* aServiceWorker)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aServiceWorker);
+ MOZ_ASSERT(!mEvaluatingWorker);
+ MOZ_ASSERT(!mInstallingWorker);
+ MOZ_ASSERT(mWaitingWorker != aServiceWorker);
+ MOZ_ASSERT(mActiveWorker != aServiceWorker);
+
+ mEvaluatingWorker = aServiceWorker;
+}
+
+void
+ServiceWorkerRegistrationInfo::ClearEvaluating()
+{
+ AssertIsOnMainThread();
+
+ if (!mEvaluatingWorker) {
+ return;
+ }
+
+ mEvaluatingWorker->UpdateState(ServiceWorkerState::Redundant);
+ mEvaluatingWorker = nullptr;
+}
+
+void
+ServiceWorkerRegistrationInfo::ClearInstalling()
+{
+ AssertIsOnMainThread();
+
+ if (!mInstallingWorker) {
+ return;
+ }
+
+ mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
+ mInstallingWorker = nullptr;
+ NotifyListenersOnChange(WhichServiceWorker::INSTALLING_WORKER);
+}
+
+void
+ServiceWorkerRegistrationInfo::TransitionEvaluatingToInstalling()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mEvaluatingWorker);
+ MOZ_ASSERT(!mInstallingWorker);
+
+ mInstallingWorker = mEvaluatingWorker.forget();
+ mInstallingWorker->UpdateState(ServiceWorkerState::Installing);
+ NotifyListenersOnChange(WhichServiceWorker::INSTALLING_WORKER);
+}
+
+void
+ServiceWorkerRegistrationInfo::TransitionInstallingToWaiting()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mInstallingWorker);
+
+ if (mWaitingWorker) {
+ MOZ_ASSERT(mInstallingWorker->CacheName() != mWaitingWorker->CacheName());
+ mWaitingWorker->UpdateState(ServiceWorkerState::Redundant);
+ }
+
+ mWaitingWorker = mInstallingWorker.forget();
+ mWaitingWorker->UpdateState(ServiceWorkerState::Installed);
+ NotifyListenersOnChange(WhichServiceWorker::INSTALLING_WORKER |
+ WhichServiceWorker::WAITING_WORKER);
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ // browser shutdown began
+ return;
+ }
+ swm->StoreRegistration(mPrincipal, this);
+}
+
+void
+ServiceWorkerRegistrationInfo::SetActive(ServiceWorkerInfo* aServiceWorker)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aServiceWorker);
+
+ // TODO: Assert installing, waiting, and active are nullptr once the SWM
+ // moves to the parent process. After that happens this code will
+ // only run for browser initialization and not for cross-process
+ // overrides.
+ MOZ_ASSERT(mInstallingWorker != aServiceWorker);
+ MOZ_ASSERT(mWaitingWorker != aServiceWorker);
+ MOZ_ASSERT(mActiveWorker != aServiceWorker);
+
+ if (mActiveWorker) {
+ MOZ_ASSERT(aServiceWorker->CacheName() != mActiveWorker->CacheName());
+ mActiveWorker->UpdateState(ServiceWorkerState::Redundant);
+ }
+
+ // The active worker is being overriden due to initial load or
+ // another process activating a worker. Move straight to the
+ // Activated state.
+ mActiveWorker = aServiceWorker;
+ mActiveWorker->SetActivateStateUncheckedWithoutEvent(ServiceWorkerState::Activated);
+ NotifyListenersOnChange(WhichServiceWorker::ACTIVE_WORKER);
+}
+
+void
+ServiceWorkerRegistrationInfo::TransitionWaitingToActive()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mWaitingWorker);
+
+ if (mActiveWorker) {
+ MOZ_ASSERT(mWaitingWorker->CacheName() != mActiveWorker->CacheName());
+ mActiveWorker->UpdateState(ServiceWorkerState::Redundant);
+ }
+
+ // We are transitioning from waiting to active normally, so go to
+ // the activating state.
+ mActiveWorker = mWaitingWorker.forget();
+ mActiveWorker->UpdateState(ServiceWorkerState::Activating);
+ NotifyListenersOnChange(WhichServiceWorker::WAITING_WORKER |
+ WhichServiceWorker::ACTIVE_WORKER);
+}
+
+bool
+ServiceWorkerRegistrationInfo::IsIdle() const
+{
+ return !mActiveWorker || mActiveWorker->WorkerPrivate()->IsIdle();
+}
+
+END_WORKERS_NAMESPACE
diff --git a/dom/workers/ServiceWorkerRegistrationInfo.h b/dom/workers/ServiceWorkerRegistrationInfo.h
new file mode 100644
index 000000000..d2d217be0
--- /dev/null
+++ b/dom/workers/ServiceWorkerRegistrationInfo.h
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_serviceworkerregistrationinfo_h
+#define mozilla_dom_workers_serviceworkerregistrationinfo_h
+
+#include "mozilla/dom/workers/ServiceWorkerInfo.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+class ServiceWorkerRegistrationInfo final
+ : public nsIServiceWorkerRegistrationInfo
+{
+ uint32_t mControlledDocumentsCounter;
+
+ enum
+ {
+ NoUpdate,
+ NeedTimeCheckAndUpdate,
+ NeedUpdate
+ } mUpdateState;
+
+ uint64_t mLastUpdateCheckTime;
+
+ RefPtr<ServiceWorkerInfo> mEvaluatingWorker;
+ RefPtr<ServiceWorkerInfo> mActiveWorker;
+ RefPtr<ServiceWorkerInfo> mWaitingWorker;
+ RefPtr<ServiceWorkerInfo> mInstallingWorker;
+
+ virtual ~ServiceWorkerRegistrationInfo();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISERVICEWORKERREGISTRATIONINFO
+
+ const nsCString mScope;
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+
+ nsTArray<nsCOMPtr<nsIServiceWorkerRegistrationInfoListener>> mListeners;
+
+ // When unregister() is called on a registration, it is not immediately
+ // removed since documents may be controlled. It is marked as
+ // pendingUninstall and when all controlling documents go away, removed.
+ bool mPendingUninstall;
+
+ ServiceWorkerRegistrationInfo(const nsACString& aScope,
+ nsIPrincipal* aPrincipal);
+
+ already_AddRefed<ServiceWorkerInfo>
+ Newest() const
+ {
+ RefPtr<ServiceWorkerInfo> newest;
+ if (mInstallingWorker) {
+ newest = mInstallingWorker;
+ } else if (mWaitingWorker) {
+ newest = mWaitingWorker;
+ } else {
+ newest = mActiveWorker;
+ }
+
+ return newest.forget();
+ }
+
+ already_AddRefed<ServiceWorkerInfo>
+ GetServiceWorkerInfoById(uint64_t aId);
+
+ void
+ StartControllingADocument()
+ {
+ ++mControlledDocumentsCounter;
+ }
+
+ void
+ StopControllingADocument()
+ {
+ MOZ_ASSERT(mControlledDocumentsCounter);
+ --mControlledDocumentsCounter;
+ }
+
+ bool
+ IsControllingDocuments() const
+ {
+ return mActiveWorker && mControlledDocumentsCounter;
+ }
+
+ void
+ Clear();
+
+ void
+ TryToActivateAsync();
+
+ void
+ TryToActivate();
+
+ void
+ Activate();
+
+ void
+ FinishActivate(bool aSuccess);
+
+ void
+ RefreshLastUpdateCheckTime();
+
+ bool
+ IsLastUpdateCheckTimeOverOneDay() const;
+
+ void
+ NotifyListenersOnChange(WhichServiceWorker aChangedWorkers);
+
+ void
+ MaybeScheduleTimeCheckAndUpdate();
+
+ void
+ MaybeScheduleUpdate();
+
+ bool
+ CheckAndClearIfUpdateNeeded();
+
+ ServiceWorkerInfo*
+ GetEvaluating() const;
+
+ ServiceWorkerInfo*
+ GetInstalling() const;
+
+ ServiceWorkerInfo*
+ GetWaiting() const;
+
+ ServiceWorkerInfo*
+ GetActive() const;
+
+ // Set the given worker as the evaluating service worker. The worker
+ // state is not changed.
+ void
+ SetEvaluating(ServiceWorkerInfo* aServiceWorker);
+
+ // Remove an existing evaluating worker, if present. The worker will
+ // be transitioned to the Redundant state.
+ void
+ ClearEvaluating();
+
+ // Remove an existing installing worker, if present. The worker will
+ // be transitioned to the Redundant state.
+ void
+ ClearInstalling();
+
+ // Transition the current evaluating worker to be the installing worker. The
+ // worker's state is update to Installing.
+ void
+ TransitionEvaluatingToInstalling();
+
+ // Transition the current installing worker to be the waiting worker. The
+ // worker's state is updated to Installed.
+ void
+ TransitionInstallingToWaiting();
+
+ // Override the current active worker. This is used during browser
+ // initialization to load persisted workers. Its also used to propagate
+ // active workers across child processes in e10s. This second use will
+ // go away once the ServiceWorkerManager moves to the parent process.
+ // The worker is transitioned to the Activated state.
+ void
+ SetActive(ServiceWorkerInfo* aServiceWorker);
+
+ // Transition the current waiting worker to be the new active worker. The
+ // worker is updated to the Activating state.
+ void
+ TransitionWaitingToActive();
+
+ // Determine if the registration is actively performing work.
+ bool
+ IsIdle() const;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkerregistrationinfo_h
diff --git a/dom/workers/ServiceWorkerScriptCache.cpp b/dom/workers/ServiceWorkerScriptCache.cpp
new file mode 100644
index 000000000..f44bb673c
--- /dev/null
+++ b/dom/workers/ServiceWorkerScriptCache.cpp
@@ -0,0 +1,1060 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerScriptCache.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/CacheBinding.h"
+#include "mozilla/dom/cache/CacheStorage.h"
+#include "mozilla/dom/cache/Cache.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseWorkerProxy.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIStreamLoader.h"
+#include "nsIThreadRetargetableRequest.h"
+
+#include "nsIInputStreamPump.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptError.h"
+#include "nsContentUtils.h"
+#include "nsNetUtil.h"
+#include "nsScriptLoader.h"
+#include "ServiceWorkerManager.h"
+#include "Workers.h"
+#include "nsStringStream.h"
+
+using mozilla::dom::cache::Cache;
+using mozilla::dom::cache::CacheStorage;
+
+BEGIN_WORKERS_NAMESPACE
+
+namespace serviceWorkerScriptCache {
+
+namespace {
+
+// XXX A sandbox nsIGlobalObject does not preserve its reflector, so |aSandbox|
+// must be kept alive as long as the CacheStorage if you want to ensure that
+// the CacheStorage will continue to work. Failures will manifest as errors
+// like "JavaScript error: , line 0: TypeError: The expression cannot be
+// converted to return the specified type."
+already_AddRefed<CacheStorage>
+CreateCacheStorage(JSContext* aCx, nsIPrincipal* aPrincipal, ErrorResult& aRv,
+ JS::MutableHandle<JSObject*> aSandbox)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+
+ nsIXPConnect* xpc = nsContentUtils::XPConnect();
+ MOZ_ASSERT(xpc, "This should never be null!");
+ aRv = xpc->CreateSandbox(aCx, aPrincipal, aSandbox.address());
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> sandboxGlobalObject = xpc::NativeGlobal(aSandbox);
+ if (!sandboxGlobalObject) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // We assume private browsing is not enabled here. The ScriptLoader
+ // explicitly fails for private browsing so there should never be
+ // a service worker running in private browsing mode. Therefore if
+ // we are purging scripts or running a comparison algorithm we cannot
+ // be in private browing.
+ //
+ // Also, bypass the CacheStorage trusted origin checks. The ServiceWorker
+ // has validated the origin prior to this point. All the information
+ // to revalidate is not available now.
+ return CacheStorage::CreateOnMainThread(cache::CHROME_ONLY_NAMESPACE,
+ sandboxGlobalObject, aPrincipal,
+ false /* private browsing */,
+ true /* force trusted origin */,
+ aRv);
+}
+
+class CompareManager;
+
+// This class downloads a URL from the network and then it calls
+// NetworkFinished() in the CompareManager.
+class CompareNetwork final : public nsIStreamLoaderObserver,
+ public nsIRequestObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLOADEROBSERVER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ explicit CompareNetwork(CompareManager* aManager)
+ : mManager(aManager)
+ {
+ MOZ_ASSERT(aManager);
+ AssertIsOnMainThread();
+ }
+
+ nsresult
+ Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL, nsILoadGroup* aLoadGroup);
+
+ void
+ Abort()
+ {
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(mChannel);
+ mChannel->Cancel(NS_BINDING_ABORTED);
+ mChannel = nullptr;
+ }
+
+ const nsString& Buffer() const
+ {
+ AssertIsOnMainThread();
+ return mBuffer;
+ }
+
+private:
+ ~CompareNetwork()
+ {
+ AssertIsOnMainThread();
+ }
+
+ RefPtr<CompareManager> mManager;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsString mBuffer;
+};
+
+NS_IMPL_ISUPPORTS(CompareNetwork, nsIStreamLoaderObserver,
+ nsIRequestObserver)
+
+// This class gets a cached Response from the CacheStorage and then it calls
+// CacheFinished() in the CompareManager.
+class CompareCache final : public PromiseNativeHandler
+ , public nsIStreamLoaderObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+ explicit CompareCache(CompareManager* aManager)
+ : mManager(aManager)
+ , mState(WaitingForCache)
+ , mAborted(false)
+ {
+ MOZ_ASSERT(aManager);
+ AssertIsOnMainThread();
+ }
+
+ nsresult
+ Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
+ const nsAString& aCacheName);
+
+ void
+ Abort()
+ {
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(!mAborted);
+ mAborted = true;
+
+ if (mPump) {
+ mPump->Cancel(NS_BINDING_ABORTED);
+ mPump = nullptr;
+ }
+ }
+
+ // This class manages 2 promises: 1 is to retrieve cache object, and 2 is for
+ // the value from the cache. For this reason we have mState to know what
+ // reject/resolve callback we are handling.
+
+ virtual void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ AssertIsOnMainThread();
+
+ if (mAborted) {
+ return;
+ }
+
+ if (mState == WaitingForCache) {
+ ManageCacheResult(aCx, aValue);
+ return;
+ }
+
+ MOZ_ASSERT(mState == WaitingForValue);
+ ManageValueResult(aCx, aValue);
+ }
+
+ virtual void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+ const nsString& Buffer() const
+ {
+ AssertIsOnMainThread();
+ return mBuffer;
+ }
+
+ const nsString& URL() const
+ {
+ AssertIsOnMainThread();
+ return mURL;
+ }
+
+private:
+ ~CompareCache()
+ {
+ AssertIsOnMainThread();
+ }
+
+ void
+ ManageCacheResult(JSContext* aCx, JS::Handle<JS::Value> aValue);
+
+ void
+ ManageValueResult(JSContext* aCx, JS::Handle<JS::Value> aValue);
+
+ RefPtr<CompareManager> mManager;
+ nsCOMPtr<nsIInputStreamPump> mPump;
+
+ nsString mURL;
+ nsString mBuffer;
+
+ enum {
+ WaitingForCache,
+ WaitingForValue
+ } mState;
+
+ bool mAborted;
+};
+
+NS_IMPL_ISUPPORTS(CompareCache, nsIStreamLoaderObserver)
+
+class CompareManager final : public PromiseNativeHandler
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit CompareManager(ServiceWorkerRegistrationInfo* aRegistration,
+ CompareCallback* aCallback)
+ : mRegistration(aRegistration)
+ , mCallback(aCallback)
+ , mState(WaitingForOpen)
+ , mNetworkFinished(false)
+ , mCacheFinished(false)
+ , mInCache(false)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aRegistration);
+ }
+
+ nsresult
+ Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
+ const nsAString& aCacheName, nsILoadGroup* aLoadGroup)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+
+ mURL = aURL;
+
+ // Always create a CacheStorage since we want to write the network entry to
+ // the cache even if there isn't an existing one.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ ErrorResult result;
+ mSandbox.init(jsapi.cx());
+ mCacheStorage = CreateCacheStorage(jsapi.cx(), aPrincipal, result, &mSandbox);
+ if (NS_WARN_IF(result.Failed())) {
+ MOZ_ASSERT(!result.IsErrorWithMessage());
+ Cleanup();
+ return result.StealNSResult();
+ }
+
+ mCN = new CompareNetwork(this);
+ nsresult rv = mCN->Initialize(aPrincipal, aURL, aLoadGroup);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Cleanup();
+ return rv;
+ }
+
+ if (!aCacheName.IsEmpty()) {
+ mCC = new CompareCache(this);
+ rv = mCC->Initialize(aPrincipal, aURL, aCacheName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mCN->Abort();
+ Cleanup();
+ return rv;
+ }
+ }
+
+ return NS_OK;
+ }
+
+ const nsString&
+ URL() const
+ {
+ AssertIsOnMainThread();
+ return mURL;
+ }
+
+ void
+ SetMaxScope(const nsACString& aMaxScope)
+ {
+ MOZ_ASSERT(!mNetworkFinished);
+ mMaxScope = aMaxScope;
+ }
+
+ already_AddRefed<ServiceWorkerRegistrationInfo>
+ GetRegistration()
+ {
+ RefPtr<ServiceWorkerRegistrationInfo> copy = mRegistration.get();
+ return copy.forget();
+ }
+
+ void
+ NetworkFinished(nsresult aStatus)
+ {
+ AssertIsOnMainThread();
+
+ mNetworkFinished = true;
+
+ if (NS_WARN_IF(NS_FAILED(aStatus))) {
+ if (mCC) {
+ mCC->Abort();
+ }
+
+ ComparisonFinished(aStatus, false);
+ return;
+ }
+
+ MaybeCompare();
+ }
+
+ void
+ CacheFinished(nsresult aStatus, bool aInCache)
+ {
+ AssertIsOnMainThread();
+
+ mCacheFinished = true;
+ mInCache = aInCache;
+
+ if (NS_WARN_IF(NS_FAILED(aStatus))) {
+ if (mCN) {
+ mCN->Abort();
+ }
+
+ ComparisonFinished(aStatus, false);
+ return;
+ }
+
+ MaybeCompare();
+ }
+
+ void
+ MaybeCompare()
+ {
+ AssertIsOnMainThread();
+
+ if (!mNetworkFinished || (mCC && !mCacheFinished)) {
+ return;
+ }
+
+ if (!mCC || !mInCache) {
+ ComparisonFinished(NS_OK, false);
+ return;
+ }
+
+ ComparisonFinished(NS_OK, mCC->Buffer().Equals(mCN->Buffer()));
+ }
+
+ // This class manages 2 promises: 1 is to retrieve Cache object, and 2 is to
+ // Put the value in the cache. For this reason we have mState to know what
+ // callback we are handling.
+ void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mCallback);
+
+ if (mState == WaitingForOpen) {
+ if (NS_WARN_IF(!aValue.isObject())) {
+ Fail(NS_ERROR_FAILURE);
+ return;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+ if (NS_WARN_IF(!obj)) {
+ Fail(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<Cache> cache;
+ nsresult rv = UNWRAP_OBJECT(Cache, obj, cache);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ WriteToCache(cache);
+ return;
+ }
+
+ MOZ_ASSERT(mState == WaitingForPut);
+ mCallback->ComparisonResult(NS_OK, false /* aIsEqual */,
+ mNewCacheName, mMaxScope);
+ Cleanup();
+ }
+
+ void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ AssertIsOnMainThread();
+ if (mState == WaitingForOpen) {
+ NS_WARNING("Could not open cache.");
+ } else {
+ NS_WARNING("Could not write to cache.");
+ }
+ Fail(NS_ERROR_FAILURE);
+ }
+
+ CacheStorage*
+ CacheStorage_()
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mCacheStorage);
+ return mCacheStorage;
+ }
+
+ void
+ InitChannelInfo(nsIChannel* aChannel)
+ {
+ mChannelInfo.InitFromChannel(aChannel);
+ }
+
+ nsresult
+ SetPrincipalInfo(nsIChannel* aChannel)
+ {
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ NS_ASSERTION(ssm, "Should never be null!");
+
+ nsCOMPtr<nsIPrincipal> channelPrincipal;
+ nsresult rv = ssm->GetChannelResultPrincipal(aChannel, getter_AddRefs(channelPrincipal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ UniquePtr<mozilla::ipc::PrincipalInfo> principalInfo(new mozilla::ipc::PrincipalInfo());
+ rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mPrincipalInfo = Move(principalInfo);
+ return NS_OK;
+ }
+
+private:
+ ~CompareManager()
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mCC);
+ MOZ_ASSERT(!mCN);
+ }
+
+ void
+ Fail(nsresult aStatus)
+ {
+ AssertIsOnMainThread();
+ mCallback->ComparisonResult(aStatus, false /* aIsEqual */,
+ EmptyString(), EmptyCString());
+ Cleanup();
+ }
+
+ void
+ Cleanup()
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mCallback);
+ mCallback = nullptr;
+ mCN = nullptr;
+ mCC = nullptr;
+ }
+
+ void
+ ComparisonFinished(nsresult aStatus, bool aIsEqual)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mCallback);
+
+ if (NS_WARN_IF(NS_FAILED(aStatus))) {
+ Fail(aStatus);
+ return;
+ }
+
+ if (aIsEqual) {
+ mCallback->ComparisonResult(aStatus, aIsEqual, EmptyString(), mMaxScope);
+ Cleanup();
+ return;
+ }
+
+ // Write to Cache so ScriptLoader reads succeed.
+ WriteNetworkBufferToNewCache();
+ }
+
+ void
+ WriteNetworkBufferToNewCache()
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mCN);
+ MOZ_ASSERT(mCacheStorage);
+ MOZ_ASSERT(mNewCacheName.IsEmpty());
+
+ ErrorResult result;
+ result = serviceWorkerScriptCache::GenerateCacheName(mNewCacheName);
+ if (NS_WARN_IF(result.Failed())) {
+ MOZ_ASSERT(!result.IsErrorWithMessage());
+ Fail(result.StealNSResult());
+ return;
+ }
+
+ RefPtr<Promise> cacheOpenPromise = mCacheStorage->Open(mNewCacheName, result);
+ if (NS_WARN_IF(result.Failed())) {
+ MOZ_ASSERT(!result.IsErrorWithMessage());
+ Fail(result.StealNSResult());
+ return;
+ }
+
+ cacheOpenPromise->AppendNativeHandler(this);
+ }
+
+ void
+ WriteToCache(Cache* aCache)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aCache);
+ MOZ_ASSERT(mState == WaitingForOpen);
+
+ ErrorResult result;
+ nsCOMPtr<nsIInputStream> body;
+ result = NS_NewCStringInputStream(getter_AddRefs(body),
+ NS_ConvertUTF16toUTF8(mCN->Buffer()));
+ if (NS_WARN_IF(result.Failed())) {
+ MOZ_ASSERT(!result.IsErrorWithMessage());
+ Fail(result.StealNSResult());
+ return;
+ }
+
+ RefPtr<InternalResponse> ir =
+ new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
+ ir->SetBody(body, mCN->Buffer().Length());
+
+ ir->InitChannelInfo(mChannelInfo);
+ if (mPrincipalInfo) {
+ ir->SetPrincipalInfo(Move(mPrincipalInfo));
+ }
+
+ RefPtr<Response> response = new Response(aCache->GetGlobalObject(), ir);
+
+ RequestOrUSVString request;
+ request.SetAsUSVString().Rebind(URL().Data(), URL().Length());
+
+ // For now we have to wait until the Put Promise is fulfilled before we can
+ // continue since Cache does not yet support starting a read that is being
+ // written to.
+ RefPtr<Promise> cachePromise = aCache->Put(request, *response, result);
+ if (NS_WARN_IF(result.Failed())) {
+ MOZ_ASSERT(!result.IsErrorWithMessage());
+ Fail(result.StealNSResult());
+ return;
+ }
+
+ mState = WaitingForPut;
+ cachePromise->AppendNativeHandler(this);
+ }
+
+ RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
+ RefPtr<CompareCallback> mCallback;
+ JS::PersistentRooted<JSObject*> mSandbox;
+ RefPtr<CacheStorage> mCacheStorage;
+
+ RefPtr<CompareNetwork> mCN;
+ RefPtr<CompareCache> mCC;
+
+ nsString mURL;
+ // Only used if the network script has changed and needs to be cached.
+ nsString mNewCacheName;
+
+ ChannelInfo mChannelInfo;
+
+ UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
+
+ nsCString mMaxScope;
+
+ enum {
+ WaitingForOpen,
+ WaitingForPut
+ } mState;
+
+ bool mNetworkFinished;
+ bool mCacheFinished;
+ bool mInCache;
+};
+
+NS_IMPL_ISUPPORTS0(CompareManager)
+
+nsresult
+CompareNetwork::Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL, nsILoadGroup* aLoadGroup)
+{
+ MOZ_ASSERT(aPrincipal);
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsLoadFlags flags = nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ mManager->GetRegistration();
+ if (registration->IsLastUpdateCheckTimeOverOneDay()) {
+ flags |= nsIRequest::LOAD_BYPASS_CACHE;
+ }
+
+ // Note that because there is no "serviceworker" RequestContext type, we can
+ // use the TYPE_INTERNAL_SCRIPT content policy types when loading a service
+ // worker.
+ rv = NS_NewChannel(getter_AddRefs(mChannel),
+ uri, aPrincipal,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER,
+ loadGroup,
+ nullptr, // aCallbacks
+ flags);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (httpChannel) {
+ // Spec says no redirects allowed for SW scripts.
+ httpChannel->SetRedirectionLimit(0);
+
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Service-Worker"),
+ NS_LITERAL_CSTRING("script"),
+ /* merge */ false);
+ }
+
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(getter_AddRefs(loader), this, this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mChannel->AsyncOpen2(loader);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompareNetwork::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+ AssertIsOnMainThread();
+
+ // If no channel, Abort() has been called.
+ if (!mChannel) {
+ return NS_OK;
+ }
+
+#ifdef DEBUG
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ MOZ_ASSERT(channel == mChannel);
+#endif
+
+ mManager->InitChannelInfo(mChannel);
+ nsresult rv = mManager->SetPrincipalInfo(mChannel);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompareNetwork::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
+ nsresult aStatusCode)
+{
+ // Nothing to do here!
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompareNetwork::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
+ nsresult aStatus, uint32_t aLen,
+ const uint8_t* aString)
+{
+ AssertIsOnMainThread();
+
+ // If no channel, Abort() has been called.
+ if (!mChannel) {
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(aStatus))) {
+ if (aStatus == NS_ERROR_REDIRECT_LOOP) {
+ mManager->NetworkFinished(NS_ERROR_DOM_SECURITY_ERR);
+ } else {
+ mManager->NetworkFinished(aStatus);
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIRequest> request;
+ nsresult rv = aLoader->GetRequest(getter_AddRefs(request));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mManager->NetworkFinished(rv);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
+ MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?");
+
+ bool requestSucceeded;
+ rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mManager->NetworkFinished(rv);
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(!requestSucceeded)) {
+ // Get the stringified numeric status code, not statusText which could be
+ // something misleading like OK for a 404.
+ uint32_t status = 0;
+ httpChannel->GetResponseStatus(&status); // don't care if this fails, use 0.
+ nsAutoString statusAsText;
+ statusAsText.AppendInt(status);
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration = mManager->GetRegistration();
+ ServiceWorkerManager::LocalizeAndReportToAllClients(
+ registration->mScope, "ServiceWorkerRegisterNetworkError",
+ nsTArray<nsString> { NS_ConvertUTF8toUTF16(registration->mScope),
+ statusAsText, mManager->URL() });
+ mManager->NetworkFinished(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ nsAutoCString maxScope;
+ // Note: we explicitly don't check for the return value here, because the
+ // absence of the header is not an error condition.
+ Unused << httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Service-Worker-Allowed"),
+ maxScope);
+
+ mManager->SetMaxScope(maxScope);
+
+ bool isFromCache = false;
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(httpChannel));
+ if (cacheChannel) {
+ cacheChannel->IsFromCache(&isFromCache);
+ }
+
+ // [9.2 Update]4.13, If response's cache state is not "local",
+ // set registration's last update check time to the current time
+ if (!isFromCache) {
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ mManager->GetRegistration();
+ registration->RefreshLastUpdateCheckTime();
+ }
+
+ nsAutoCString mimeType;
+ rv = httpChannel->GetContentType(mimeType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // We should only end up here if !mResponseHead in the channel. If headers
+ // were received but no content type was specified, we'll be given
+ // UNKNOWN_CONTENT_TYPE "application/x-unknown-content-type" and so fall
+ // into the next case with its better error message.
+ mManager->NetworkFinished(NS_ERROR_DOM_SECURITY_ERR);
+ return rv;
+ }
+
+ if (!mimeType.LowerCaseEqualsLiteral("text/javascript") &&
+ !mimeType.LowerCaseEqualsLiteral("application/x-javascript") &&
+ !mimeType.LowerCaseEqualsLiteral("application/javascript")) {
+ RefPtr<ServiceWorkerRegistrationInfo> registration = mManager->GetRegistration();
+ ServiceWorkerManager::LocalizeAndReportToAllClients(
+ registration->mScope, "ServiceWorkerRegisterMimeTypeError",
+ nsTArray<nsString> { NS_ConvertUTF8toUTF16(registration->mScope),
+ NS_ConvertUTF8toUTF16(mimeType), mManager->URL() });
+ mManager->NetworkFinished(NS_ERROR_DOM_SECURITY_ERR);
+ return rv;
+ }
+
+ char16_t* buffer = nullptr;
+ size_t len = 0;
+
+ rv = nsScriptLoader::ConvertToUTF16(httpChannel, aString, aLen,
+ NS_LITERAL_STRING("UTF-8"), nullptr,
+ buffer, len);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mManager->NetworkFinished(rv);
+ return rv;
+ }
+
+ mBuffer.Adopt(buffer, len);
+
+ mManager->NetworkFinished(NS_OK);
+ return NS_OK;
+}
+
+nsresult
+CompareCache::Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
+ const nsAString& aCacheName)
+{
+ MOZ_ASSERT(aPrincipal);
+ AssertIsOnMainThread();
+
+ mURL = aURL;
+
+ ErrorResult rv;
+
+ RefPtr<Promise> promise = mManager->CacheStorage_()->Open(aCacheName, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ MOZ_ASSERT(!rv.IsErrorWithMessage());
+ return rv.StealNSResult();
+ }
+
+ promise->AppendNativeHandler(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompareCache::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
+ nsresult aStatus, uint32_t aLen,
+ const uint8_t* aString)
+{
+ AssertIsOnMainThread();
+
+ if (mAborted) {
+ return aStatus;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(aStatus))) {
+ mManager->CacheFinished(aStatus, false);
+ return aStatus;
+ }
+
+ char16_t* buffer = nullptr;
+ size_t len = 0;
+
+ nsresult rv = nsScriptLoader::ConvertToUTF16(nullptr, aString, aLen,
+ NS_LITERAL_STRING("UTF-8"),
+ nullptr, buffer, len);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mManager->CacheFinished(rv, false);
+ return rv;
+ }
+
+ mBuffer.Adopt(buffer, len);
+
+ mManager->CacheFinished(NS_OK, true);
+ return NS_OK;
+}
+
+void
+CompareCache::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+ AssertIsOnMainThread();
+
+ if (mAborted) {
+ return;
+ }
+
+ mManager->CacheFinished(NS_ERROR_FAILURE, false);
+}
+
+void
+CompareCache::ManageCacheResult(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+ AssertIsOnMainThread();
+
+ if (NS_WARN_IF(!aValue.isObject())) {
+ mManager->CacheFinished(NS_ERROR_FAILURE, false);
+ return;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+
+ Cache* cache = nullptr;
+ nsresult rv = UNWRAP_OBJECT(Cache, &obj, cache);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mManager->CacheFinished(rv, false);
+ return;
+ }
+
+ RequestOrUSVString request;
+ request.SetAsUSVString().Rebind(mURL.Data(), mURL.Length());
+ ErrorResult error;
+ CacheQueryOptions params;
+ RefPtr<Promise> promise = cache->Match(request, params, error);
+ if (NS_WARN_IF(error.Failed())) {
+ mManager->CacheFinished(error.StealNSResult(), false);
+ return;
+ }
+
+ promise->AppendNativeHandler(this);
+ mState = WaitingForValue;
+}
+
+void
+CompareCache::ManageValueResult(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+ AssertIsOnMainThread();
+
+ // The cache returns undefined if the object is not stored.
+ if (aValue.isUndefined()) {
+ mManager->CacheFinished(NS_OK, false);
+ return;
+ }
+
+ MOZ_ASSERT(aValue.isObject());
+
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+
+ Response* response = nullptr;
+ nsresult rv = UNWRAP_OBJECT(Response, &obj, response);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mManager->CacheFinished(rv, false);
+ return;
+ }
+
+ MOZ_ASSERT(response->Ok());
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ response->GetBody(getter_AddRefs(inputStream));
+ MOZ_ASSERT(inputStream);
+
+ MOZ_ASSERT(!mPump);
+ rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inputStream);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mManager->CacheFinished(rv, false);
+ return;
+ }
+
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mManager->CacheFinished(rv, false);
+ return;
+ }
+
+ rv = mPump->AsyncRead(loader, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mPump = nullptr;
+ mManager->CacheFinished(rv, false);
+ return;
+ }
+
+ nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(mPump);
+ if (rr) {
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ rv = rr->RetargetDeliveryTo(sts);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mPump = nullptr;
+ mManager->CacheFinished(rv, false);
+ return;
+ }
+ }
+}
+
+} // namespace
+
+nsresult
+PurgeCache(nsIPrincipal* aPrincipal, const nsAString& aCacheName)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+
+ if (aCacheName.IsEmpty()) {
+ return NS_OK;
+ }
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ ErrorResult rv;
+ JS::Rooted<JSObject*> sandboxObject(jsapi.cx());
+ RefPtr<CacheStorage> cacheStorage = CreateCacheStorage(jsapi.cx(), aPrincipal, rv, &sandboxObject);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ // We use the ServiceWorker scope as key for the cacheStorage.
+ RefPtr<Promise> promise =
+ cacheStorage->Delete(aCacheName, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ // We don't actually care about the result of the delete operation.
+ return NS_OK;
+}
+
+nsresult
+GenerateCacheName(nsAString& aName)
+{
+ nsresult rv;
+ nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
+ do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsID id;
+ rv = uuidGenerator->GenerateUUIDInPlace(&id);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ char chars[NSID_LENGTH];
+ id.ToProvidedString(chars);
+
+ // NSID_LENGTH counts the null terminator.
+ aName.AssignASCII(chars, NSID_LENGTH - 1);
+
+ return NS_OK;
+}
+
+nsresult
+Compare(ServiceWorkerRegistrationInfo* aRegistration,
+ nsIPrincipal* aPrincipal, const nsAString& aCacheName,
+ const nsAString& aURL, CompareCallback* aCallback,
+ nsILoadGroup* aLoadGroup)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aRegistration);
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(!aURL.IsEmpty());
+ MOZ_ASSERT(aCallback);
+
+ RefPtr<CompareManager> cm = new CompareManager(aRegistration, aCallback);
+
+ nsresult rv = cm->Initialize(aPrincipal, aURL, aCacheName, aLoadGroup);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+} // namespace serviceWorkerScriptCache
+
+END_WORKERS_NAMESPACE
diff --git a/dom/workers/ServiceWorkerScriptCache.h b/dom/workers/ServiceWorkerScriptCache.h
new file mode 100644
index 000000000..113ed0983
--- /dev/null
+++ b/dom/workers/ServiceWorkerScriptCache.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_ServiceWorkerScriptCache_h
+#define mozilla_dom_workers_ServiceWorkerScriptCache_h
+
+#include "nsString.h"
+
+class nsILoadGroup;
+class nsIPrincipal;
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+class ServiceWorkerRegistrationInfo;
+
+namespace serviceWorkerScriptCache {
+
+nsresult
+PurgeCache(nsIPrincipal* aPrincipal, const nsAString& aCacheName);
+
+nsresult
+GenerateCacheName(nsAString& aName);
+
+class CompareCallback
+{
+public:
+ /*
+ * If there is an error, ignore aInCacheAndEqual and aNewCacheName.
+ * On success, if the cached result and network result matched,
+ * aInCacheAndEqual will be true and no new cache name is passed, otherwise
+ * use the new cache name to load the ServiceWorker.
+ */
+ virtual void
+ ComparisonResult(nsresult aStatus,
+ bool aInCacheAndEqual,
+ const nsAString& aNewCacheName,
+ const nsACString& aMaxScope) = 0;
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef() = 0;
+ NS_IMETHOD_(MozExternalRefCountType) Release() = 0;
+};
+
+nsresult
+Compare(ServiceWorkerRegistrationInfo* aRegistration,
+ nsIPrincipal* aPrincipal, const nsAString& aCacheName,
+ const nsAString& aURL, CompareCallback* aCallback, nsILoadGroup* aLoadGroup);
+
+} // namespace serviceWorkerScriptCache
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_ServiceWorkerScriptCache_h
diff --git a/dom/workers/ServiceWorkerUnregisterJob.cpp b/dom/workers/ServiceWorkerUnregisterJob.cpp
new file mode 100644
index 000000000..8fd76b63d
--- /dev/null
+++ b/dom/workers/ServiceWorkerUnregisterJob.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerUnregisterJob.h"
+
+#include "nsIPushService.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+class ServiceWorkerUnregisterJob::PushUnsubscribeCallback final :
+ public nsIUnsubscribeResultCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit PushUnsubscribeCallback(ServiceWorkerUnregisterJob* aJob)
+ : mJob(aJob)
+ {
+ AssertIsOnMainThread();
+ }
+
+ NS_IMETHOD
+ OnUnsubscribe(nsresult aStatus, bool) override
+ {
+ // Warn if unsubscribing fails, but don't prevent the worker from
+ // unregistering.
+ Unused << NS_WARN_IF(NS_FAILED(aStatus));
+ mJob->Unregister();
+ return NS_OK;
+ }
+
+private:
+ ~PushUnsubscribeCallback()
+ {
+ }
+
+ RefPtr<ServiceWorkerUnregisterJob> mJob;
+};
+
+NS_IMPL_ISUPPORTS(ServiceWorkerUnregisterJob::PushUnsubscribeCallback,
+ nsIUnsubscribeResultCallback)
+
+ServiceWorkerUnregisterJob::ServiceWorkerUnregisterJob(nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ bool aSendToParent)
+ : ServiceWorkerJob(Type::Unregister, aPrincipal, aScope, EmptyCString())
+ , mResult(false)
+ , mSendToParent(aSendToParent)
+{
+}
+
+bool
+ServiceWorkerUnregisterJob::GetResult() const
+{
+ AssertIsOnMainThread();
+ return mResult;
+}
+
+ServiceWorkerUnregisterJob::~ServiceWorkerUnregisterJob()
+{
+}
+
+void
+ServiceWorkerUnregisterJob::AsyncExecute()
+{
+ AssertIsOnMainThread();
+
+ if (Canceled()) {
+ Finish(NS_ERROR_DOM_ABORT_ERR);
+ return;
+ }
+
+ // Push API, section 5: "When a service worker registration is unregistered,
+ // any associated push subscription must be deactivated." To ensure the
+ // service worker registration isn't cleared as we're unregistering, we
+ // unsubscribe first.
+ nsCOMPtr<nsIPushService> pushService =
+ do_GetService("@mozilla.org/push/Service;1");
+ if (NS_WARN_IF(!pushService)) {
+ Unregister();
+ return;
+ }
+ nsCOMPtr<nsIUnsubscribeResultCallback> unsubscribeCallback =
+ new PushUnsubscribeCallback(this);
+ nsresult rv = pushService->Unsubscribe(NS_ConvertUTF8toUTF16(mScope),
+ mPrincipal, unsubscribeCallback);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Unregister();
+ }
+}
+
+void
+ServiceWorkerUnregisterJob::Unregister()
+{
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (Canceled() || !swm) {
+ Finish(NS_ERROR_DOM_ABORT_ERR);
+ return;
+ }
+
+ // Step 1 of the Unregister algorithm requires checking that the
+ // client origin matches the scope's origin. We perform this in
+ // registration->update() method directly since we don't have that
+ // client information available here.
+
+ // "Let registration be the result of running [[Get Registration]]
+ // algorithm passing scope as the argument."
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ swm->GetRegistration(mPrincipal, mScope);
+ if (!registration) {
+ // "If registration is null, then, resolve promise with false."
+ Finish(NS_OK);
+ return;
+ }
+
+ // Note, we send the message to remove the registration from disk now even
+ // though we may only set the mPendingUninstall flag below. This is
+ // necessary to ensure the registration is removed if the controlled
+ // clients are closed by shutting down the browser. If the registration
+ // is resurrected by clearing mPendingUninstall then it should be saved
+ // to disk again.
+ if (mSendToParent && !registration->mPendingUninstall) {
+ swm->MaybeSendUnregister(mPrincipal, mScope);
+ }
+
+ // "Set registration's uninstalling flag."
+ registration->mPendingUninstall = true;
+
+ // "Resolve promise with true"
+ mResult = true;
+ InvokeResultCallbacks(NS_OK);
+
+ // "If no service worker client is using registration..."
+ if (!registration->IsControllingDocuments() && registration->IsIdle()) {
+ // "Invoke [[Clear Registration]]..."
+ swm->RemoveRegistration(registration);
+ }
+
+ Finish(NS_OK);
+}
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/ServiceWorkerUnregisterJob.h b/dom/workers/ServiceWorkerUnregisterJob.h
new file mode 100644
index 000000000..976f15a2c
--- /dev/null
+++ b/dom/workers/ServiceWorkerUnregisterJob.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_serviceworkerunregisterjob_h
+#define mozilla_dom_workers_serviceworkerunregisterjob_h
+
+#include "ServiceWorkerJob.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+class ServiceWorkerUnregisterJob final : public ServiceWorkerJob
+{
+public:
+ ServiceWorkerUnregisterJob(nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ bool aSendToParent);
+
+ bool
+ GetResult() const;
+
+private:
+ class PushUnsubscribeCallback;
+
+ virtual ~ServiceWorkerUnregisterJob();
+
+ virtual void
+ AsyncExecute() override;
+
+ void
+ Unregister();
+
+ bool mResult;
+ bool mSendToParent;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkerunregisterjob_h
diff --git a/dom/workers/ServiceWorkerUpdateJob.cpp b/dom/workers/ServiceWorkerUpdateJob.cpp
new file mode 100644
index 000000000..614fe4de5
--- /dev/null
+++ b/dom/workers/ServiceWorkerUpdateJob.cpp
@@ -0,0 +1,552 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerUpdateJob.h"
+
+#include "nsIScriptError.h"
+#include "nsIURL.h"
+#include "ServiceWorkerScriptCache.h"
+#include "Workers.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+namespace {
+
+/**
+ * The spec mandates slightly different behaviors for computing the scope
+ * prefix string in case a Service-Worker-Allowed header is specified versus
+ * when it's not available.
+ *
+ * With the header:
+ * "Set maxScopeString to "/" concatenated with the strings in maxScope's
+ * path (including empty strings), separated from each other by "/"."
+ * Without the header:
+ * "Set maxScopeString to "/" concatenated with the strings, except the last
+ * string that denotes the script's file name, in registration's registering
+ * script url's path (including empty strings), separated from each other by
+ * "/"."
+ *
+ * In simpler terms, if the header is not present, we should only use the
+ * "directory" part of the pathname, and otherwise the entire pathname should be
+ * used. ScopeStringPrefixMode allows the caller to specify the desired
+ * behavior.
+ */
+enum ScopeStringPrefixMode {
+ eUseDirectory,
+ eUsePath
+};
+
+nsresult
+GetRequiredScopeStringPrefix(nsIURI* aScriptURI, nsACString& aPrefix,
+ ScopeStringPrefixMode aPrefixMode)
+{
+ nsresult rv = aScriptURI->GetPrePath(aPrefix);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (aPrefixMode == eUseDirectory) {
+ nsCOMPtr<nsIURL> scriptURL(do_QueryInterface(aScriptURI));
+ if (NS_WARN_IF(!scriptURL)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString dir;
+ rv = scriptURL->GetDirectory(dir);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aPrefix.Append(dir);
+ } else if (aPrefixMode == eUsePath) {
+ nsAutoCString path;
+ rv = aScriptURI->GetPath(path);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aPrefix.Append(path);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Invalid value for aPrefixMode");
+ }
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+class ServiceWorkerUpdateJob::CompareCallback final : public serviceWorkerScriptCache::CompareCallback
+{
+ RefPtr<ServiceWorkerUpdateJob> mJob;
+
+ ~CompareCallback()
+ {
+ }
+
+public:
+ explicit CompareCallback(ServiceWorkerUpdateJob* aJob)
+ : mJob(aJob)
+ {
+ MOZ_ASSERT(mJob);
+ }
+
+ virtual void
+ ComparisonResult(nsresult aStatus,
+ bool aInCacheAndEqual,
+ const nsAString& aNewCacheName,
+ const nsACString& aMaxScope) override
+ {
+ mJob->ComparisonResult(aStatus, aInCacheAndEqual, aNewCacheName, aMaxScope);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(ServiceWorkerUpdateJob::CompareCallback, override)
+};
+
+class ServiceWorkerUpdateJob::ContinueUpdateRunnable final : public LifeCycleEventCallback
+{
+ nsMainThreadPtrHandle<ServiceWorkerUpdateJob> mJob;
+ bool mSuccess;
+
+public:
+ explicit ContinueUpdateRunnable(const nsMainThreadPtrHandle<ServiceWorkerUpdateJob>& aJob)
+ : mJob(aJob)
+ , mSuccess(false)
+ {
+ AssertIsOnMainThread();
+ }
+
+ void
+ SetResult(bool aResult) override
+ {
+ mSuccess = aResult;
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+ mJob->ContinueUpdateAfterScriptEval(mSuccess);
+ mJob = nullptr;
+ return NS_OK;
+ }
+};
+
+class ServiceWorkerUpdateJob::ContinueInstallRunnable final : public LifeCycleEventCallback
+{
+ nsMainThreadPtrHandle<ServiceWorkerUpdateJob> mJob;
+ bool mSuccess;
+
+public:
+ explicit ContinueInstallRunnable(const nsMainThreadPtrHandle<ServiceWorkerUpdateJob>& aJob)
+ : mJob(aJob)
+ , mSuccess(false)
+ {
+ AssertIsOnMainThread();
+ }
+
+ void
+ SetResult(bool aResult) override
+ {
+ mSuccess = aResult;
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+ mJob->ContinueAfterInstallEvent(mSuccess);
+ mJob = nullptr;
+ return NS_OK;
+ }
+};
+
+ServiceWorkerUpdateJob::ServiceWorkerUpdateJob(nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ const nsACString& aScriptSpec,
+ nsILoadGroup* aLoadGroup)
+ : ServiceWorkerJob(Type::Update, aPrincipal, aScope, aScriptSpec)
+ , mLoadGroup(aLoadGroup)
+{
+}
+
+already_AddRefed<ServiceWorkerRegistrationInfo>
+ServiceWorkerUpdateJob::GetRegistration() const
+{
+ AssertIsOnMainThread();
+ RefPtr<ServiceWorkerRegistrationInfo> ref = mRegistration;
+ return ref.forget();
+}
+
+ServiceWorkerUpdateJob::ServiceWorkerUpdateJob(Type aType,
+ nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ const nsACString& aScriptSpec,
+ nsILoadGroup* aLoadGroup)
+ : ServiceWorkerJob(aType, aPrincipal, aScope, aScriptSpec)
+ , mLoadGroup(aLoadGroup)
+{
+}
+
+ServiceWorkerUpdateJob::~ServiceWorkerUpdateJob()
+{
+}
+
+void
+ServiceWorkerUpdateJob::FailUpdateJob(ErrorResult& aRv)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aRv.Failed());
+
+ // Cleanup after a failed installation. This essentially implements
+ // step 12 of the Install algorithm.
+ //
+ // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#installation-algorithm
+ //
+ // The spec currently only runs this after an install event fails,
+ // but we must handle many more internal errors. So we check for
+ // cleanup on every non-successful exit.
+ if (mRegistration) {
+ mRegistration->ClearEvaluating();
+ mRegistration->ClearInstalling();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->MaybeRemoveRegistration(mRegistration);
+ }
+ }
+
+ mRegistration = nullptr;
+
+ Finish(aRv);
+}
+
+void
+ServiceWorkerUpdateJob::FailUpdateJob(nsresult aRv)
+{
+ ErrorResult rv(aRv);
+ FailUpdateJob(rv);
+}
+
+void
+ServiceWorkerUpdateJob::AsyncExecute()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(GetType() == Type::Update);
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (Canceled() || !swm) {
+ FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
+ return;
+ }
+
+ // Begin step 1 of the Update algorithm.
+ //
+ // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#update-algorithm
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ swm->GetRegistration(mPrincipal, mScope);
+
+ if (!registration || registration->mPendingUninstall) {
+ ErrorResult rv;
+ rv.ThrowTypeError<MSG_SW_UPDATE_BAD_REGISTRATION>(NS_ConvertUTF8toUTF16(mScope),
+ NS_LITERAL_STRING("uninstalled"));
+ FailUpdateJob(rv);
+ return;
+ }
+
+ // If a Register job with a new script executed ahead of us in the job queue,
+ // then our update for the old script no longer makes sense. Simply abort
+ // in this case.
+ RefPtr<ServiceWorkerInfo> newest = registration->Newest();
+ if (newest && !mScriptSpec.Equals(newest->ScriptSpec())) {
+ ErrorResult rv;
+ rv.ThrowTypeError<MSG_SW_UPDATE_BAD_REGISTRATION>(NS_ConvertUTF8toUTF16(mScope),
+ NS_LITERAL_STRING("changed"));
+ FailUpdateJob(rv);
+ return;
+ }
+
+ SetRegistration(registration);
+ Update();
+}
+
+void
+ServiceWorkerUpdateJob::SetRegistration(ServiceWorkerRegistrationInfo* aRegistration)
+{
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(!mRegistration);
+ MOZ_ASSERT(aRegistration);
+ mRegistration = aRegistration;
+}
+
+void
+ServiceWorkerUpdateJob::Update()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!Canceled());
+
+ // SetRegistration() must be called before Update().
+ MOZ_ASSERT(mRegistration);
+ MOZ_ASSERT(!mRegistration->GetInstalling());
+
+ // Begin the script download and comparison steps starting at step 5
+ // of the Update algorithm.
+
+ RefPtr<ServiceWorkerInfo> workerInfo = mRegistration->Newest();
+ nsAutoString cacheName;
+
+ // If the script has not changed, we need to perform a byte-for-byte
+ // comparison.
+ if (workerInfo && workerInfo->ScriptSpec().Equals(mScriptSpec)) {
+ cacheName = workerInfo->CacheName();
+ }
+
+ RefPtr<CompareCallback> callback = new CompareCallback(this);
+
+ nsresult rv =
+ serviceWorkerScriptCache::Compare(mRegistration, mPrincipal, cacheName,
+ NS_ConvertUTF8toUTF16(mScriptSpec),
+ callback, mLoadGroup);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailUpdateJob(rv);
+ return;
+ }
+}
+
+void
+ServiceWorkerUpdateJob::ComparisonResult(nsresult aStatus,
+ bool aInCacheAndEqual,
+ const nsAString& aNewCacheName,
+ const nsACString& aMaxScope)
+{
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (NS_WARN_IF(Canceled() || !swm)) {
+ FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
+ return;
+ }
+
+ // Handle failure of the download or comparison. This is part of Update
+ // step 5 as "If the algorithm asynchronously completes with null, then:".
+ if (NS_WARN_IF(NS_FAILED(aStatus))) {
+ FailUpdateJob(aStatus);
+ return;
+ }
+
+ // The spec validates the response before performing the byte-for-byte check.
+ // Here we perform the comparison in another module and then validate the
+ // script URL and scope. Make sure to do this validation before accepting
+ // an byte-for-byte match since the service-worker-allowed header might have
+ // changed since the last time it was installed.
+
+ // This is step 2 the "validate response" section of Update algorithm step 5.
+ // Step 1 is performed in the serviceWorkerScriptCache code.
+
+ nsCOMPtr<nsIURI> scriptURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(scriptURI), mScriptSpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIURI> maxScopeURI;
+ if (!aMaxScope.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(maxScopeURI), aMaxScope,
+ nullptr, scriptURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+ }
+
+ nsAutoCString defaultAllowedPrefix;
+ rv = GetRequiredScopeStringPrefix(scriptURI, defaultAllowedPrefix,
+ eUseDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsAutoCString maxPrefix(defaultAllowedPrefix);
+ if (maxScopeURI) {
+ rv = GetRequiredScopeStringPrefix(maxScopeURI, maxPrefix, eUsePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+ }
+
+ if (!StringBeginsWith(mRegistration->mScope, maxPrefix)) {
+ nsXPIDLString message;
+ NS_ConvertUTF8toUTF16 reportScope(mRegistration->mScope);
+ NS_ConvertUTF8toUTF16 reportMaxPrefix(maxPrefix);
+ const char16_t* params[] = { reportScope.get(), reportMaxPrefix.get() };
+
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "ServiceWorkerScopePathMismatch",
+ params, message);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to format localized string");
+ swm->ReportToAllClients(mScope,
+ message,
+ EmptyString(),
+ EmptyString(), 0, 0,
+ nsIScriptError::errorFlag);
+ FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ // The response has been validated, so now we can consider if its a
+ // byte-for-byte match. This is step 6 of the Update algorithm.
+ if (aInCacheAndEqual) {
+ Finish(NS_OK);
+ return;
+ }
+
+ Telemetry::Accumulate(Telemetry::SERVICE_WORKER_UPDATED, 1);
+
+ // Begin step 7 of the Update algorithm to evaluate the new script.
+
+ RefPtr<ServiceWorkerInfo> sw =
+ new ServiceWorkerInfo(mRegistration->mPrincipal,
+ mRegistration->mScope,
+ mScriptSpec, aNewCacheName);
+
+ mRegistration->SetEvaluating(sw);
+
+ nsMainThreadPtrHandle<ServiceWorkerUpdateJob> handle(
+ new nsMainThreadPtrHolder<ServiceWorkerUpdateJob>(this));
+ RefPtr<LifeCycleEventCallback> callback = new ContinueUpdateRunnable(handle);
+
+ ServiceWorkerPrivate* workerPrivate = sw->WorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ rv = workerPrivate->CheckScriptEvaluation(callback);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
+ return;
+ }
+}
+
+void
+ServiceWorkerUpdateJob::ContinueUpdateAfterScriptEval(bool aScriptEvaluationResult)
+{
+ AssertIsOnMainThread();
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (Canceled() || !swm) {
+ FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
+ return;
+ }
+
+ // Step 7.5 of the Update algorithm verifying that the script evaluated
+ // successfully.
+
+ if (NS_WARN_IF(!aScriptEvaluationResult)) {
+ ErrorResult error;
+
+ NS_ConvertUTF8toUTF16 scriptSpec(mScriptSpec);
+ NS_ConvertUTF8toUTF16 scope(mRegistration->mScope);
+ error.ThrowTypeError<MSG_SW_SCRIPT_THREW>(scriptSpec, scope);
+ FailUpdateJob(error);
+ return;
+ }
+
+ Install(swm);
+}
+
+void
+ServiceWorkerUpdateJob::Install(ServiceWorkerManager* aSWM)
+{
+ AssertIsOnMainThread();
+ MOZ_DIAGNOSTIC_ASSERT(!Canceled());
+ MOZ_DIAGNOSTIC_ASSERT(aSWM);
+
+ MOZ_ASSERT(!mRegistration->GetInstalling());
+
+ // Begin step 2 of the Install algorithm.
+ //
+ // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#installation-algorithm
+
+ mRegistration->TransitionEvaluatingToInstalling();
+
+ // Step 6 of the Install algorithm resolving the job promise.
+ InvokeResultCallbacks(NS_OK);
+
+ // The job promise cannot be rejected after this point, but the job can
+ // still fail; e.g. if the install event handler throws, etc.
+
+ // fire the updatefound event
+ nsCOMPtr<nsIRunnable> upr =
+ NewRunnableMethod<RefPtr<ServiceWorkerRegistrationInfo>>(
+ aSWM,
+ &ServiceWorkerManager::FireUpdateFoundOnServiceWorkerRegistrations,
+ mRegistration);
+ NS_DispatchToMainThread(upr);
+
+ // Call ContinueAfterInstallEvent(false) on main thread if the SW
+ // script fails to load.
+ nsCOMPtr<nsIRunnable> failRunnable = NewRunnableMethod<bool>
+ (this, &ServiceWorkerUpdateJob::ContinueAfterInstallEvent, false);
+
+ nsMainThreadPtrHandle<ServiceWorkerUpdateJob> handle(
+ new nsMainThreadPtrHolder<ServiceWorkerUpdateJob>(this));
+ RefPtr<LifeCycleEventCallback> callback = new ContinueInstallRunnable(handle);
+
+ // Send the install event to the worker thread
+ ServiceWorkerPrivate* workerPrivate =
+ mRegistration->GetInstalling()->WorkerPrivate();
+ nsresult rv = workerPrivate->SendLifeCycleEvent(NS_LITERAL_STRING("install"),
+ callback, failRunnable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ContinueAfterInstallEvent(false /* aSuccess */);
+ }
+}
+
+void
+ServiceWorkerUpdateJob::ContinueAfterInstallEvent(bool aInstallEventSuccess)
+{
+ if (Canceled()) {
+ return FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ // If we haven't been canceled we should have a registration. There appears
+ // to be a path where it gets cleared before we call into here. Assert
+ // to try to catch this condition, but don't crash in release.
+ MOZ_DIAGNOSTIC_ASSERT(mRegistration);
+ if (!mRegistration) {
+ return FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ // Continue executing the Install algorithm at step 12.
+
+ // "If installFailed is true"
+ if (NS_WARN_IF(!aInstallEventSuccess)) {
+ // The installing worker is cleaned up by FailUpdateJob().
+ FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mRegistration->GetInstalling());
+ mRegistration->TransitionInstallingToWaiting();
+
+ Finish(NS_OK);
+
+ // Step 20 calls for explicitly waiting for queued event tasks to fire. Instead,
+ // we simply queue a runnable to execute Activate. This ensures the events are
+ // flushed from the queue before proceeding.
+
+ // Step 22 of the Install algorithm. Activate is executed after the completion
+ // of this job. The controlling client and skipWaiting checks are performed
+ // in TryToActivate().
+ mRegistration->TryToActivateAsync();
+}
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/ServiceWorkerUpdateJob.h b/dom/workers/ServiceWorkerUpdateJob.h
new file mode 100644
index 000000000..77adb2212
--- /dev/null
+++ b/dom/workers/ServiceWorkerUpdateJob.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_serviceworkerupdatejob_h
+#define mozilla_dom_workers_serviceworkerupdatejob_h
+
+#include "ServiceWorkerJob.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+class ServiceWorkerManager;
+
+// A job class that performs the Update and Install algorithms from the
+// service worker spec. This class is designed to be inherited and customized
+// as a different job type. This is necessary because the register job
+// performs largely the same operations as the update job, but has a few
+// different starting steps.
+class ServiceWorkerUpdateJob : public ServiceWorkerJob
+{
+public:
+ // Construct an update job to be used only for updates.
+ ServiceWorkerUpdateJob(nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ const nsACString& aScriptSpec,
+ nsILoadGroup* aLoadGroup);
+
+ already_AddRefed<ServiceWorkerRegistrationInfo>
+ GetRegistration() const;
+
+protected:
+ // Construct an update job that is overriden as another job type.
+ ServiceWorkerUpdateJob(Type aType,
+ nsIPrincipal* aPrincipal,
+ const nsACString& aScope,
+ const nsACString& aScriptSpec,
+ nsILoadGroup* aLoadGroup);
+
+ virtual ~ServiceWorkerUpdateJob();
+
+ // FailUpdateJob() must be called if an update job needs Finish() with
+ // an error.
+ void
+ FailUpdateJob(ErrorResult& aRv);
+
+ void
+ FailUpdateJob(nsresult aRv);
+
+ // The entry point when the update job is being used directly. Job
+ // types overriding this class should override this method to
+ // customize behavior.
+ virtual void
+ AsyncExecute() override;
+
+ // Set the registration to be operated on by Update() or to be immediately
+ // returned as a result of the job. This must be called before Update().
+ void
+ SetRegistration(ServiceWorkerRegistrationInfo* aRegistration);
+
+ // Execute the bulk of the update job logic using the registration defined
+ // by a previous SetRegistration() call. This can be called by the overriden
+ // AsyncExecute() to complete the job. The Update() method will always call
+ // Finish(). This method corresponds to the spec Update algorithm.
+ void
+ Update();
+
+private:
+ class CompareCallback;
+ class ContinueUpdateRunnable;
+ class ContinueInstallRunnable;
+
+ // Utility method called after a script is loaded and compared to
+ // our current cached script.
+ void
+ ComparisonResult(nsresult aStatus,
+ bool aInCacheAndEqual,
+ const nsAString& aNewCacheName,
+ const nsACString& aMaxScope);
+
+ // Utility method called after evaluating the worker script.
+ void
+ ContinueUpdateAfterScriptEval(bool aScriptEvaluationResult);
+
+ // Utility method corresponding to the spec Install algorithm.
+ void
+ Install(ServiceWorkerManager* aSWM);
+
+ // Utility method called after the install event is handled.
+ void
+ ContinueAfterInstallEvent(bool aInstallEventSuccess);
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkerupdatejob_h
diff --git a/dom/workers/ServiceWorkerWindowClient.cpp b/dom/workers/ServiceWorkerWindowClient.cpp
new file mode 100644
index 000000000..2ce0603cf
--- /dev/null
+++ b/dom/workers/ServiceWorkerWindowClient.cpp
@@ -0,0 +1,558 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "ServiceWorkerWindowClient.h"
+
+#include "js/Value.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/dom/ClientBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseWorkerProxy.h"
+#include "mozilla/UniquePtr.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsIDocument.h"
+#include "nsIGlobalObject.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+#include "ServiceWorker.h"
+#include "ServiceWorkerInfo.h"
+#include "ServiceWorkerManager.h"
+#include "WorkerPrivate.h"
+#include "WorkerScope.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::workers;
+
+using mozilla::UniquePtr;
+
+JSObject*
+ServiceWorkerWindowClient::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return WindowClientBinding::Wrap(aCx, this, aGivenProto);
+}
+
+namespace {
+
+class ResolveOrRejectPromiseRunnable final : public WorkerRunnable
+{
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ UniquePtr<ServiceWorkerClientInfo> mClientInfo;
+ nsresult mRv;
+
+public:
+ // Passing a null clientInfo will resolve the promise with a null value.
+ ResolveOrRejectPromiseRunnable(
+ WorkerPrivate* aWorkerPrivate, PromiseWorkerProxy* aPromiseProxy,
+ UniquePtr<ServiceWorkerClientInfo>&& aClientInfo)
+ : WorkerRunnable(aWorkerPrivate)
+ , mPromiseProxy(aPromiseProxy)
+ , mClientInfo(Move(aClientInfo))
+ , mRv(NS_OK)
+ {
+ AssertIsOnMainThread();
+ }
+
+ // Reject the promise with passed nsresult.
+ ResolveOrRejectPromiseRunnable(WorkerPrivate* aWorkerPrivate,
+ PromiseWorkerProxy* aPromiseProxy,
+ nsresult aRv)
+ : WorkerRunnable(aWorkerPrivate)
+ , mPromiseProxy(aPromiseProxy)
+ , mClientInfo(nullptr)
+ , mRv(aRv)
+ {
+ MOZ_ASSERT(NS_FAILED(aRv));
+ AssertIsOnMainThread();
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<Promise> promise = mPromiseProxy->WorkerPromise();
+ MOZ_ASSERT(promise);
+
+ if (NS_WARN_IF(NS_FAILED(mRv))) {
+ promise->MaybeReject(mRv);
+ } else if (mClientInfo) {
+ RefPtr<ServiceWorkerWindowClient> client =
+ new ServiceWorkerWindowClient(promise->GetParentObject(), *mClientInfo);
+ promise->MaybeResolve(client);
+ } else {
+ promise->MaybeResolve(JS::NullHandleValue);
+ }
+
+ // Release the reference on the worker thread.
+ mPromiseProxy->CleanUp();
+
+ return true;
+ }
+};
+
+class ClientFocusRunnable final : public Runnable
+{
+ uint64_t mWindowId;
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+
+public:
+ ClientFocusRunnable(uint64_t aWindowId, PromiseWorkerProxy* aPromiseProxy)
+ : mWindowId(aWindowId)
+ , mPromiseProxy(aPromiseProxy)
+ {
+ MOZ_ASSERT(mPromiseProxy);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+ nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId);
+ UniquePtr<ServiceWorkerClientInfo> clientInfo;
+
+ if (window) {
+ nsCOMPtr<nsIDocument> doc = window->GetDocument();
+ if (doc) {
+ nsContentUtils::DispatchFocusChromeEvent(window->GetOuterWindow());
+ clientInfo.reset(new ServiceWorkerClientInfo(doc));
+ }
+ }
+
+ DispatchResult(Move(clientInfo));
+ return NS_OK;
+ }
+
+private:
+ void
+ DispatchResult(UniquePtr<ServiceWorkerClientInfo>&& aClientInfo)
+ {
+ AssertIsOnMainThread();
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return;
+ }
+
+ RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable;
+ if (aClientInfo) {
+ resolveRunnable = new ResolveOrRejectPromiseRunnable(
+ mPromiseProxy->GetWorkerPrivate(), mPromiseProxy, Move(aClientInfo));
+ } else {
+ resolveRunnable = new ResolveOrRejectPromiseRunnable(
+ mPromiseProxy->GetWorkerPrivate(), mPromiseProxy,
+ NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ }
+
+ resolveRunnable->Dispatch();
+ }
+};
+
+} // namespace
+
+already_AddRefed<Promise>
+ServiceWorkerWindowClient::Focus(ErrorResult& aRv) const
+{
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ workerPrivate->AssertIsOnWorkerThread();
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ MOZ_ASSERT(global);
+
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
+ RefPtr<PromiseWorkerProxy> promiseProxy =
+ PromiseWorkerProxy::Create(workerPrivate, promise);
+ if (promiseProxy) {
+ RefPtr<ClientFocusRunnable> r = new ClientFocusRunnable(mWindowId,
+ promiseProxy);
+ MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(r.forget()));
+ } else {
+ promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ } else {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+
+ return promise.forget();
+}
+
+class WebProgressListener final : public nsIWebProgressListener,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(WebProgressListener,
+ nsIWebProgressListener)
+
+ WebProgressListener(PromiseWorkerProxy* aPromiseProxy,
+ ServiceWorkerPrivate* aServiceWorkerPrivate,
+ nsPIDOMWindowOuter* aWindow, nsIURI* aBaseURI)
+ : mPromiseProxy(aPromiseProxy)
+ , mServiceWorkerPrivate(aServiceWorkerPrivate)
+ , mWindow(aWindow)
+ , mBaseURI(aBaseURI)
+ {
+ MOZ_ASSERT(aPromiseProxy);
+ MOZ_ASSERT(aServiceWorkerPrivate);
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsOuterWindow());
+ MOZ_ASSERT(aBaseURI);
+ AssertIsOnMainThread();
+
+ mServiceWorkerPrivate->StoreISupports(static_cast<nsIWebProgressListener*>(this));
+ }
+
+ NS_IMETHOD
+ OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aStateFlags, nsresult aStatus) override
+ {
+ if (!(aStateFlags & STATE_IS_DOCUMENT) ||
+ !(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) {
+ return NS_OK;
+ }
+
+ // This is safe because our caller holds a strong ref.
+ mServiceWorkerPrivate->RemoveISupports(static_cast<nsIWebProgressListener*>(this));
+ aWebProgress->RemoveProgressListener(this);
+
+ WorkerPrivate* workerPrivate;
+
+ {
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return NS_OK;
+ }
+
+ workerPrivate = mPromiseProxy->GetWorkerPrivate();
+ }
+
+ nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
+
+ RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable;
+ UniquePtr<ServiceWorkerClientInfo> clientInfo;
+ if (!doc) {
+ resolveRunnable = new ResolveOrRejectPromiseRunnable(
+ workerPrivate, mPromiseProxy, NS_ERROR_TYPE_ERR);
+ resolveRunnable->Dispatch();
+
+ return NS_OK;
+ }
+
+ // Check same origin.
+ nsCOMPtr<nsIScriptSecurityManager> securityManager =
+ nsContentUtils::GetSecurityManager();
+ nsresult rv = securityManager->CheckSameOriginURI(doc->GetOriginalURI(),
+ mBaseURI, false);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsContentUtils::DispatchFocusChromeEvent(mWindow->GetOuterWindow());
+ clientInfo.reset(new ServiceWorkerClientInfo(doc));
+ }
+
+ resolveRunnable = new ResolveOrRejectPromiseRunnable(
+ workerPrivate, mPromiseProxy, Move(clientInfo));
+ resolveRunnable->Dispatch();
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) override
+ {
+ MOZ_CRASH("Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsIURI* aLocation, uint32_t aFlags) override
+ {
+ MOZ_CRASH("Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsresult aStatus, const char16_t* aMessage) override
+ {
+ MOZ_CRASH("Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aState) override
+ {
+ MOZ_CRASH("Unexpected notification.");
+ return NS_OK;
+ }
+
+private:
+ ~WebProgressListener() {}
+
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
+ nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+ nsCOMPtr<nsIURI> mBaseURI;
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebProgressListener)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebProgressListener)
+NS_IMPL_CYCLE_COLLECTION(WebProgressListener, mPromiseProxy,
+ mServiceWorkerPrivate, mWindow)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebProgressListener)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+class ClientNavigateRunnable final : public Runnable
+{
+ uint64_t mWindowId;
+ nsString mUrl;
+ nsCString mBaseUrl;
+ nsString mScope;
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ MOZ_INIT_OUTSIDE_CTOR WorkerPrivate* mWorkerPrivate;
+
+public:
+ ClientNavigateRunnable(uint64_t aWindowId, const nsAString& aUrl,
+ const nsAString& aScope,
+ PromiseWorkerProxy* aPromiseProxy)
+ : mWindowId(aWindowId)
+ , mUrl(aUrl)
+ , mScope(aScope)
+ , mPromiseProxy(aPromiseProxy)
+ , mWorkerPrivate(nullptr)
+ {
+ MOZ_ASSERT(aPromiseProxy);
+ MOZ_ASSERT(aPromiseProxy->GetWorkerPrivate());
+ aPromiseProxy->GetWorkerPrivate()->AssertIsOnWorkerThread();
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIPrincipal> principal;
+
+ {
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return NS_OK;
+ }
+
+ mWorkerPrivate = mPromiseProxy->GetWorkerPrivate();
+ WorkerPrivate::LocationInfo& info = mWorkerPrivate->GetLocationInfo();
+ mBaseUrl = info.mHref;
+ principal = mWorkerPrivate->GetPrincipal();
+ MOZ_DIAGNOSTIC_ASSERT(principal);
+ }
+
+ nsCOMPtr<nsIURI> baseUrl;
+ nsCOMPtr<nsIURI> url;
+ nsresult rv = ParseUrl(getter_AddRefs(baseUrl), getter_AddRefs(url));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return RejectPromise(NS_ERROR_TYPE_ERR);
+ }
+
+ rv = principal->CheckMayLoad(url, true, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return RejectPromise(rv);
+ }
+
+ nsGlobalWindow* window;
+ rv = Navigate(url, principal, &window);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return RejectPromise(rv);
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
+ if (NS_WARN_IF(!webProgress)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (!swm) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<ServiceWorkerRegistrationInfo> registration =
+ swm->GetRegistration(principal, NS_ConvertUTF16toUTF8(mScope));
+ if (NS_WARN_IF(!registration)) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<ServiceWorkerInfo> serviceWorkerInfo =
+ registration->GetServiceWorkerInfoById(mWorkerPrivate->ServiceWorkerID());
+ if (NS_WARN_IF(!serviceWorkerInfo)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIWebProgressListener> listener =
+ new WebProgressListener(mPromiseProxy, serviceWorkerInfo->WorkerPrivate(),
+ window->GetOuterWindow(), baseUrl);
+
+ rv = webProgress->AddProgressListener(
+ listener, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return RejectPromise(rv);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ nsresult
+ RejectPromise(nsresult aRv)
+ {
+ MOZ_ASSERT(mWorkerPrivate);
+ RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable =
+ new ResolveOrRejectPromiseRunnable(mWorkerPrivate, mPromiseProxy, aRv);
+
+ resolveRunnable->Dispatch();
+ return NS_OK;
+ }
+
+ nsresult
+ ResolvePromise(UniquePtr<ServiceWorkerClientInfo>&& aClientInfo)
+ {
+ MOZ_ASSERT(mWorkerPrivate);
+ RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable =
+ new ResolveOrRejectPromiseRunnable(mWorkerPrivate, mPromiseProxy,
+ Move(aClientInfo));
+
+ resolveRunnable->Dispatch();
+ return NS_OK;
+ }
+
+ nsresult
+ ParseUrl(nsIURI** aBaseUrl, nsIURI** aUrl)
+ {
+ MOZ_ASSERT(aBaseUrl);
+ MOZ_ASSERT(aUrl);
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIURI> baseUrl;
+ nsresult rv = NS_NewURI(getter_AddRefs(baseUrl), mBaseUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> url;
+ rv = NS_NewURI(getter_AddRefs(url), mUrl, nullptr, baseUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ baseUrl.forget(aBaseUrl);
+ url.forget(aUrl);
+
+ return NS_OK;
+ }
+
+ nsresult
+ Navigate(nsIURI* aUrl, nsIPrincipal* aPrincipal, nsGlobalWindow** aWindow)
+ {
+ MOZ_ASSERT(aWindow);
+
+ nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId);
+ if (NS_WARN_IF(!window)) {
+ return NS_ERROR_TYPE_ERR;
+ }
+
+ nsCOMPtr<nsIDocument> doc = window->GetDocument();
+ if (NS_WARN_IF(!doc)) {
+ return NS_ERROR_TYPE_ERR;
+ }
+
+ if (NS_WARN_IF(!doc->IsActive())) {
+ return NS_ERROR_TYPE_ERR;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ if (NS_WARN_IF(!docShell)) {
+ return NS_ERROR_TYPE_ERR;
+ }
+
+ nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+ nsresult rv = docShell->CreateLoadInfo(getter_AddRefs(loadInfo));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ loadInfo->SetTriggeringPrincipal(aPrincipal);
+ loadInfo->SetReferrer(doc->GetOriginalURI());
+ loadInfo->SetReferrerPolicy(doc->GetReferrerPolicy());
+ loadInfo->SetLoadType(nsIDocShellLoadInfo::loadStopContentAndReplace);
+ loadInfo->SetSourceDocShell(docShell);
+ rv =
+ docShell->LoadURI(aUrl, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, true);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aWindow = window;
+ return NS_OK;
+ }
+};
+
+already_AddRefed<Promise>
+ServiceWorkerWindowClient::Navigate(const nsAString& aUrl, ErrorResult& aRv)
+{
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ workerPrivate->AssertIsOnWorkerThread();
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ MOZ_ASSERT(global);
+
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (aUrl.EqualsLiteral("about:blank")) {
+ promise->MaybeReject(NS_ERROR_TYPE_ERR);
+ return promise.forget();
+ }
+
+ ServiceWorkerGlobalScope* globalScope =
+ static_cast<ServiceWorkerGlobalScope*>(workerPrivate->GlobalScope());
+ nsString scope;
+ globalScope->GetScope(scope);
+
+ RefPtr<PromiseWorkerProxy> promiseProxy =
+ PromiseWorkerProxy::Create(workerPrivate, promise);
+ if (promiseProxy) {
+ RefPtr<ClientNavigateRunnable> r =
+ new ClientNavigateRunnable(mWindowId, aUrl, scope, promiseProxy);
+ MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(r.forget()));
+ } else {
+ promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ return promise.forget();
+}
diff --git a/dom/workers/ServiceWorkerWindowClient.h b/dom/workers/ServiceWorkerWindowClient.h
new file mode 100644
index 000000000..0e2441294
--- /dev/null
+++ b/dom/workers/ServiceWorkerWindowClient.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef mozilla_dom_workers_serviceworkerwindowclient_h
+#define mozilla_dom_workers_serviceworkerwindowclient_h
+
+#include "ServiceWorkerClient.h"
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+
+namespace workers {
+
+class ServiceWorkerWindowClient final : public ServiceWorkerClient
+{
+public:
+ ServiceWorkerWindowClient(nsISupports* aOwner,
+ const ServiceWorkerClientInfo& aClientInfo)
+ : ServiceWorkerClient(aOwner, aClientInfo),
+ mVisibilityState(aClientInfo.mVisibilityState),
+ mFocused(aClientInfo.mFocused)
+ {
+ }
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ mozilla::dom::VisibilityState
+ VisibilityState() const
+ {
+ return mVisibilityState;
+ }
+
+ bool
+ Focused() const
+ {
+ return mFocused;
+ }
+
+ already_AddRefed<Promise>
+ Focus(ErrorResult& aRv) const;
+
+ already_AddRefed<Promise>
+ Navigate(const nsAString& aUrl, ErrorResult& aRv);
+
+private:
+ ~ServiceWorkerWindowClient()
+ { }
+
+ mozilla::dom::VisibilityState mVisibilityState;
+ bool mFocused;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkerwindowclient_h
diff --git a/dom/workers/SharedWorker.cpp b/dom/workers/SharedWorker.cpp
new file mode 100644
index 000000000..b0eed2def
--- /dev/null
+++ b/dom/workers/SharedWorker.cpp
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SharedWorker.h"
+
+#include "nsPIDOMWindow.h"
+
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/SharedWorkerBinding.h"
+#include "mozilla/Telemetry.h"
+#include "nsContentUtils.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIDOMEvent.h"
+
+#include "RuntimeService.h"
+#include "WorkerPrivate.h"
+
+using mozilla::dom::Optional;
+using mozilla::dom::Sequence;
+using mozilla::dom::MessagePort;
+using namespace mozilla;
+
+USING_WORKERS_NAMESPACE
+
+SharedWorker::SharedWorker(nsPIDOMWindowInner* aWindow,
+ WorkerPrivate* aWorkerPrivate,
+ MessagePort* aMessagePort)
+ : DOMEventTargetHelper(aWindow)
+ , mWorkerPrivate(aWorkerPrivate)
+ , mMessagePort(aMessagePort)
+ , mFrozen(false)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aMessagePort);
+}
+
+SharedWorker::~SharedWorker()
+{
+ AssertIsOnMainThread();
+}
+
+// static
+already_AddRefed<SharedWorker>
+SharedWorker::Constructor(const GlobalObject& aGlobal, JSContext* aCx,
+ const nsAString& aScriptURL,
+ const mozilla::dom::Optional<nsAString>& aName,
+ ErrorResult& aRv)
+{
+ AssertIsOnMainThread();
+
+ RuntimeService* rts = RuntimeService::GetOrCreateService();
+ if (!rts) {
+ aRv = NS_ERROR_NOT_AVAILABLE;
+ return nullptr;
+ }
+
+ nsCString name;
+ if (aName.WasPassed()) {
+ name = NS_ConvertUTF16toUTF8(aName.Value());
+ }
+
+ RefPtr<SharedWorker> sharedWorker;
+ nsresult rv = rts->CreateSharedWorker(aGlobal, aScriptURL, name,
+ getter_AddRefs(sharedWorker));
+ if (NS_FAILED(rv)) {
+ aRv = rv;
+ return nullptr;
+ }
+
+ Telemetry::Accumulate(Telemetry::SHARED_WORKER_COUNT, 1);
+
+ return sharedWorker.forget();
+}
+
+MessagePort*
+SharedWorker::Port()
+{
+ AssertIsOnMainThread();
+ return mMessagePort;
+}
+
+void
+SharedWorker::Freeze()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!IsFrozen());
+
+ mFrozen = true;
+}
+
+void
+SharedWorker::Thaw()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(IsFrozen());
+
+ mFrozen = false;
+
+ if (!mFrozenEvents.IsEmpty()) {
+ nsTArray<nsCOMPtr<nsIDOMEvent>> events;
+ mFrozenEvents.SwapElements(events);
+
+ for (uint32_t index = 0; index < events.Length(); index++) {
+ nsCOMPtr<nsIDOMEvent>& event = events[index];
+ MOZ_ASSERT(event);
+
+ nsCOMPtr<nsIDOMEventTarget> target;
+ if (NS_SUCCEEDED(event->GetTarget(getter_AddRefs(target)))) {
+ bool ignored;
+ if (NS_FAILED(target->DispatchEvent(event, &ignored))) {
+ NS_WARNING("Failed to dispatch event!");
+ }
+ } else {
+ NS_WARNING("Failed to get target!");
+ }
+ }
+ }
+}
+
+void
+SharedWorker::QueueEvent(nsIDOMEvent* aEvent)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aEvent);
+ MOZ_ASSERT(IsFrozen());
+
+ mFrozenEvents.AppendElement(aEvent);
+}
+
+void
+SharedWorker::Close()
+{
+ AssertIsOnMainThread();
+
+ if (mMessagePort) {
+ mMessagePort->Close();
+ }
+}
+
+void
+SharedWorker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mWorkerPrivate);
+ MOZ_ASSERT(mMessagePort);
+
+ mMessagePort->PostMessage(aCx, aMessage, aTransferable, aRv);
+}
+
+NS_IMPL_ADDREF_INHERITED(SharedWorker, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(SharedWorker, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SharedWorker)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(SharedWorker)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SharedWorker,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessagePort)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrozenEvents)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SharedWorker,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessagePort)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrozenEvents)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+JSObject*
+SharedWorker::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ AssertIsOnMainThread();
+
+ return SharedWorkerBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsresult
+SharedWorker::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ AssertIsOnMainThread();
+
+ if (IsFrozen()) {
+ nsCOMPtr<nsIDOMEvent> event = aVisitor.mDOMEvent;
+ if (!event) {
+ event = EventDispatcher::CreateEvent(aVisitor.mEvent->mOriginalTarget,
+ aVisitor.mPresContext,
+ aVisitor.mEvent, EmptyString());
+ }
+
+ QueueEvent(event);
+
+ aVisitor.mCanHandle = false;
+ aVisitor.mParentTarget = nullptr;
+ return NS_OK;
+ }
+
+ return DOMEventTargetHelper::PreHandleEvent(aVisitor);
+}
diff --git a/dom/workers/SharedWorker.h b/dom/workers/SharedWorker.h
new file mode 100644
index 000000000..220436e4d
--- /dev/null
+++ b/dom/workers/SharedWorker.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_sharedworker_h__
+#define mozilla_dom_workers_sharedworker_h__
+
+#include "Workers.h"
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/DOMEventTargetHelper.h"
+
+class nsIDOMEvent;
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+class EventChainPreVisitor;
+
+namespace dom {
+class MessagePort;
+}
+} // namespace mozilla
+
+BEGIN_WORKERS_NAMESPACE
+
+class RuntimeService;
+class WorkerPrivate;
+
+class SharedWorker final : public DOMEventTargetHelper
+{
+ friend class RuntimeService;
+
+ typedef mozilla::ErrorResult ErrorResult;
+ typedef mozilla::dom::GlobalObject GlobalObject;
+
+ RefPtr<WorkerPrivate> mWorkerPrivate;
+ RefPtr<MessagePort> mMessagePort;
+ nsTArray<nsCOMPtr<nsIDOMEvent>> mFrozenEvents;
+ bool mFrozen;
+
+public:
+ static already_AddRefed<SharedWorker>
+ Constructor(const GlobalObject& aGlobal, JSContext* aCx,
+ const nsAString& aScriptURL, const Optional<nsAString>& aName,
+ ErrorResult& aRv);
+
+ MessagePort*
+ Port();
+
+ bool
+ IsFrozen() const
+ {
+ return mFrozen;
+ }
+
+ void
+ Freeze();
+
+ void
+ Thaw();
+
+ void
+ QueueEvent(nsIDOMEvent* aEvent);
+
+ void
+ Close();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SharedWorker, DOMEventTargetHelper)
+
+ IMPL_EVENT_HANDLER(error)
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual nsresult
+ PreHandleEvent(EventChainPreVisitor& aVisitor) override;
+
+ WorkerPrivate*
+ GetWorkerPrivate() const
+ {
+ return mWorkerPrivate;
+ }
+
+private:
+ // This class can only be created from the RuntimeService.
+ SharedWorker(nsPIDOMWindowInner* aWindow,
+ WorkerPrivate* aWorkerPrivate,
+ MessagePort* aMessagePort);
+
+ // This class is reference-counted and will be destroyed from Release().
+ ~SharedWorker();
+
+ // Only called by MessagePort.
+ void
+ PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv);
+};
+
+END_WORKERS_NAMESPACE
+
+#endif // mozilla_dom_workers_sharedworker_h__
diff --git a/dom/workers/WorkerDebuggerManager.cpp b/dom/workers/WorkerDebuggerManager.cpp
new file mode 100644
index 000000000..dfd7e5acc
--- /dev/null
+++ b/dom/workers/WorkerDebuggerManager.cpp
@@ -0,0 +1,361 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WorkerDebuggerManager.h"
+
+#include "nsISimpleEnumerator.h"
+
+#include "mozilla/ClearOnShutdown.h"
+
+#include "WorkerPrivate.h"
+
+USING_WORKERS_NAMESPACE
+
+namespace {
+
+class RegisterDebuggerMainThreadRunnable final : public mozilla::Runnable
+{
+ WorkerPrivate* mWorkerPrivate;
+ bool mNotifyListeners;
+
+public:
+ RegisterDebuggerMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
+ bool aNotifyListeners)
+ : mWorkerPrivate(aWorkerPrivate),
+ mNotifyListeners(aNotifyListeners)
+ { }
+
+private:
+ ~RegisterDebuggerMainThreadRunnable()
+ { }
+
+ NS_IMETHOD
+ Run() override
+ {
+ WorkerDebuggerManager* manager = WorkerDebuggerManager::Get();
+ MOZ_ASSERT(manager);
+
+ manager->RegisterDebuggerMainThread(mWorkerPrivate, mNotifyListeners);
+ return NS_OK;
+ }
+};
+
+class UnregisterDebuggerMainThreadRunnable final : public mozilla::Runnable
+{
+ WorkerPrivate* mWorkerPrivate;
+
+public:
+ explicit UnregisterDebuggerMainThreadRunnable(WorkerPrivate* aWorkerPrivate)
+ : mWorkerPrivate(aWorkerPrivate)
+ { }
+
+private:
+ ~UnregisterDebuggerMainThreadRunnable()
+ { }
+
+ NS_IMETHOD
+ Run() override
+ {
+ WorkerDebuggerManager* manager = WorkerDebuggerManager::Get();
+ MOZ_ASSERT(manager);
+
+ manager->UnregisterDebuggerMainThread(mWorkerPrivate);
+ return NS_OK;
+ }
+};
+
+// Does not hold an owning reference.
+static WorkerDebuggerManager* gWorkerDebuggerManager;
+
+} /* anonymous namespace */
+
+BEGIN_WORKERS_NAMESPACE
+
+class WorkerDebuggerEnumerator final : public nsISimpleEnumerator
+{
+ nsTArray<RefPtr<WorkerDebugger>> mDebuggers;
+ uint32_t mIndex;
+
+public:
+ explicit WorkerDebuggerEnumerator(
+ const nsTArray<RefPtr<WorkerDebugger>>& aDebuggers)
+ : mDebuggers(aDebuggers), mIndex(0)
+ {
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISIMPLEENUMERATOR
+
+private:
+ ~WorkerDebuggerEnumerator() {}
+};
+
+NS_IMPL_ISUPPORTS(WorkerDebuggerEnumerator, nsISimpleEnumerator);
+
+NS_IMETHODIMP
+WorkerDebuggerEnumerator::HasMoreElements(bool* aResult)
+{
+ *aResult = mIndex < mDebuggers.Length();
+ return NS_OK;
+};
+
+NS_IMETHODIMP
+WorkerDebuggerEnumerator::GetNext(nsISupports** aResult)
+{
+ if (mIndex == mDebuggers.Length()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mDebuggers.ElementAt(mIndex++).forget(aResult);
+ return NS_OK;
+};
+
+WorkerDebuggerManager::WorkerDebuggerManager()
+: mMutex("WorkerDebuggerManager::mMutex")
+{
+ AssertIsOnMainThread();
+}
+
+WorkerDebuggerManager::~WorkerDebuggerManager()
+{
+ AssertIsOnMainThread();
+}
+
+// static
+already_AddRefed<WorkerDebuggerManager>
+WorkerDebuggerManager::GetInstance()
+{
+ RefPtr<WorkerDebuggerManager> manager = WorkerDebuggerManager::GetOrCreate();
+ return manager.forget();
+}
+
+// static
+WorkerDebuggerManager*
+WorkerDebuggerManager::GetOrCreate()
+{
+ AssertIsOnMainThread();
+
+ if (!gWorkerDebuggerManager) {
+ // The observer service now owns us until shutdown.
+ gWorkerDebuggerManager = new WorkerDebuggerManager();
+ if (NS_FAILED(gWorkerDebuggerManager->Init())) {
+ NS_WARNING("Failed to initialize worker debugger manager!");
+ gWorkerDebuggerManager = nullptr;
+ return nullptr;
+ }
+ }
+
+ return gWorkerDebuggerManager;
+}
+
+WorkerDebuggerManager*
+WorkerDebuggerManager::Get()
+{
+ MOZ_ASSERT(gWorkerDebuggerManager);
+ return gWorkerDebuggerManager;
+}
+
+NS_IMPL_ISUPPORTS(WorkerDebuggerManager, nsIObserver, nsIWorkerDebuggerManager);
+
+NS_IMETHODIMP
+WorkerDebuggerManager::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ Shutdown();
+ return NS_OK;
+ }
+
+ NS_NOTREACHED("Unknown observer topic!");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebuggerManager::GetWorkerDebuggerEnumerator(
+ nsISimpleEnumerator** aResult)
+{
+ AssertIsOnMainThread();
+
+ RefPtr<WorkerDebuggerEnumerator> enumerator =
+ new WorkerDebuggerEnumerator(mDebuggers);
+ enumerator.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebuggerManager::AddListener(nsIWorkerDebuggerManagerListener* aListener)
+{
+ AssertIsOnMainThread();
+
+ MutexAutoLock lock(mMutex);
+
+ if (mListeners.Contains(aListener)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mListeners.AppendElement(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebuggerManager::RemoveListener(
+ nsIWorkerDebuggerManagerListener* aListener)
+{
+ AssertIsOnMainThread();
+
+ MutexAutoLock lock(mMutex);
+
+ if (!mListeners.Contains(aListener)) {
+ return NS_OK;
+ }
+
+ mListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+nsresult
+WorkerDebuggerManager::Init()
+{
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
+
+ nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+void
+WorkerDebuggerManager::Shutdown()
+{
+ AssertIsOnMainThread();
+
+ MutexAutoLock lock(mMutex);
+
+ mListeners.Clear();
+}
+
+void
+WorkerDebuggerManager::RegisterDebugger(WorkerPrivate* aWorkerPrivate)
+{
+ aWorkerPrivate->AssertIsOnParentThread();
+
+ if (NS_IsMainThread()) {
+ // When the parent thread is the main thread, it will always block until all
+ // register liseners have been called, since it cannot continue until the
+ // call to RegisterDebuggerMainThread returns.
+ //
+ // In this case, it is always safe to notify all listeners on the main
+ // thread, even if there were no listeners at the time this method was
+ // called, so we can always pass true for the value of aNotifyListeners.
+ // This avoids having to lock mMutex to check whether mListeners is empty.
+ RegisterDebuggerMainThread(aWorkerPrivate, true);
+ } else {
+ // We guarantee that if any register listeners are called, the worker does
+ // not start running until all register listeners have been called. To
+ // guarantee this, the parent thread should block until all register
+ // listeners have been called.
+ //
+ // However, to avoid overhead when the debugger is not being used, the
+ // parent thread will only block if there were any listeners at the time
+ // this method was called. As a result, we should not notify any listeners
+ // on the main thread if there were no listeners at the time this method was
+ // called, because the parent will not be blocking in that case.
+ bool hasListeners = false;
+ {
+ MutexAutoLock lock(mMutex);
+
+ hasListeners = !mListeners.IsEmpty();
+ }
+
+ nsCOMPtr<nsIRunnable> runnable =
+ new RegisterDebuggerMainThreadRunnable(aWorkerPrivate, hasListeners);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL));
+
+ if (hasListeners) {
+ aWorkerPrivate->WaitForIsDebuggerRegistered(true);
+ }
+ }
+}
+
+void
+WorkerDebuggerManager::UnregisterDebugger(WorkerPrivate* aWorkerPrivate)
+{
+ aWorkerPrivate->AssertIsOnParentThread();
+
+ if (NS_IsMainThread()) {
+ UnregisterDebuggerMainThread(aWorkerPrivate);
+ } else {
+ nsCOMPtr<nsIRunnable> runnable =
+ new UnregisterDebuggerMainThreadRunnable(aWorkerPrivate);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL));
+
+ aWorkerPrivate->WaitForIsDebuggerRegistered(false);
+ }
+}
+
+void
+WorkerDebuggerManager::RegisterDebuggerMainThread(WorkerPrivate* aWorkerPrivate,
+ bool aNotifyListeners)
+{
+ AssertIsOnMainThread();
+
+ RefPtr<WorkerDebugger> debugger = new WorkerDebugger(aWorkerPrivate);
+ mDebuggers.AppendElement(debugger);
+
+ aWorkerPrivate->SetDebugger(debugger);
+
+ if (aNotifyListeners) {
+ nsTArray<nsCOMPtr<nsIWorkerDebuggerManagerListener>> listeners;
+ {
+ MutexAutoLock lock(mMutex);
+
+ listeners = mListeners;
+ }
+
+ for (size_t index = 0; index < listeners.Length(); ++index) {
+ listeners[index]->OnRegister(debugger);
+ }
+ }
+
+ aWorkerPrivate->SetIsDebuggerRegistered(true);
+}
+
+void
+WorkerDebuggerManager::UnregisterDebuggerMainThread(
+ WorkerPrivate* aWorkerPrivate)
+{
+ AssertIsOnMainThread();
+
+ // There is nothing to do here if the debugger was never succesfully
+ // registered. We need to check this on the main thread because the worker
+ // does not wait for the registration to complete if there were no listeners
+ // installed when it started.
+ if (!aWorkerPrivate->IsDebuggerRegistered()) {
+ return;
+ }
+
+ RefPtr<WorkerDebugger> debugger = aWorkerPrivate->Debugger();
+ mDebuggers.RemoveElement(debugger);
+
+ aWorkerPrivate->SetDebugger(nullptr);
+
+ nsTArray<nsCOMPtr<nsIWorkerDebuggerManagerListener>> listeners;
+ {
+ MutexAutoLock lock(mMutex);
+
+ listeners = mListeners;
+ }
+
+ for (size_t index = 0; index < listeners.Length(); ++index) {
+ listeners[index]->OnUnregister(debugger);
+ }
+
+ debugger->Close();
+ aWorkerPrivate->SetIsDebuggerRegistered(false);
+}
+
+END_WORKERS_NAMESPACE
diff --git a/dom/workers/WorkerDebuggerManager.h b/dom/workers/WorkerDebuggerManager.h
new file mode 100644
index 000000000..6c4c943b1
--- /dev/null
+++ b/dom/workers/WorkerDebuggerManager.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_workerdebuggermanager_h
+#define mozilla_dom_workers_workerdebuggermanager_h
+
+#include "Workers.h"
+
+#include "nsIObserver.h"
+#include "nsIWorkerDebuggerManager.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+#define WORKERDEBUGGERMANAGER_CID \
+ { 0x62ec8731, 0x55ad, 0x4246, \
+ { 0xb2, 0xea, 0xf2, 0x6c, 0x1f, 0xe1, 0x9d, 0x2d } }
+#define WORKERDEBUGGERMANAGER_CONTRACTID \
+ "@mozilla.org/dom/workers/workerdebuggermanager;1"
+
+BEGIN_WORKERS_NAMESPACE
+
+class WorkerDebugger;
+
+class WorkerDebuggerManager final : public nsIObserver,
+ public nsIWorkerDebuggerManager
+{
+ Mutex mMutex;
+
+ // Protected by mMutex.
+ nsTArray<nsCOMPtr<nsIWorkerDebuggerManagerListener>> mListeners;
+
+ // Only touched on the main thread.
+ nsTArray<RefPtr<WorkerDebugger>> mDebuggers;
+
+public:
+ static already_AddRefed<WorkerDebuggerManager>
+ GetInstance();
+
+ static WorkerDebuggerManager*
+ GetOrCreate();
+
+ static WorkerDebuggerManager*
+ Get();
+
+ WorkerDebuggerManager();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIWORKERDEBUGGERMANAGER
+
+ nsresult
+ Init();
+
+ void
+ Shutdown();
+
+ void
+ RegisterDebugger(WorkerPrivate* aWorkerPrivate);
+
+ void
+ RegisterDebuggerMainThread(WorkerPrivate* aWorkerPrivate,
+ bool aNotifyListeners);
+
+ void
+ UnregisterDebugger(WorkerPrivate* aWorkerPrivate);
+
+ void
+ UnregisterDebuggerMainThread(WorkerPrivate* aWorkerPrivate);
+
+private:
+ virtual ~WorkerDebuggerManager();
+};
+
+inline nsresult
+RegisterWorkerDebugger(WorkerPrivate* aWorkerPrivate)
+{
+ WorkerDebuggerManager* manager;
+
+ if (NS_IsMainThread()) {
+ manager = WorkerDebuggerManager::GetOrCreate();
+ if (!manager) {
+ NS_WARNING("Failed to create worker debugger manager!");
+ return NS_ERROR_FAILURE;
+ }
+ }
+ else {
+ manager = WorkerDebuggerManager::Get();
+ }
+
+ manager->RegisterDebugger(aWorkerPrivate);
+ return NS_OK;
+}
+
+inline nsresult
+UnregisterWorkerDebugger(WorkerPrivate* aWorkerPrivate)
+{
+ WorkerDebuggerManager* manager;
+
+ if (NS_IsMainThread()) {
+ manager = WorkerDebuggerManager::GetOrCreate();
+ if (!manager) {
+ NS_WARNING("Failed to create worker debugger manager!");
+ return NS_ERROR_FAILURE;
+ }
+ }
+ else {
+ manager = WorkerDebuggerManager::Get();
+ }
+
+ manager->UnregisterDebugger(aWorkerPrivate);
+ return NS_OK;
+}
+
+END_WORKERS_NAMESPACE
+
+#endif // mozilla_dom_workers_workerdebuggermanager_h
diff --git a/dom/workers/WorkerHolder.cpp b/dom/workers/WorkerHolder.cpp
new file mode 100644
index 000000000..6383ae5a1
--- /dev/null
+++ b/dom/workers/WorkerHolder.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WorkerHolder.h"
+#include "WorkerPrivate.h"
+
+BEGIN_WORKERS_NAMESPACE
+
+WorkerHolder::WorkerHolder()
+ : mWorkerPrivate(nullptr)
+{
+}
+
+WorkerHolder::~WorkerHolder()
+{
+ NS_ASSERT_OWNINGTHREAD(WorkerHolder);
+ ReleaseWorkerInternal();
+ MOZ_ASSERT(mWorkerPrivate == nullptr);
+}
+
+bool
+WorkerHolder::HoldWorker(WorkerPrivate* aWorkerPrivate, Status aFailStatus)
+{
+ NS_ASSERT_OWNINGTHREAD(WorkerHolder);
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (!aWorkerPrivate->AddHolder(this, aFailStatus)) {
+ return false;
+ }
+
+ mWorkerPrivate = aWorkerPrivate;
+ return true;
+}
+
+void
+WorkerHolder::ReleaseWorker()
+{
+ NS_ASSERT_OWNINGTHREAD(WorkerHolder);
+ MOZ_ASSERT(mWorkerPrivate);
+
+ ReleaseWorkerInternal();
+}
+
+void
+WorkerHolder::ReleaseWorkerInternal()
+{
+ NS_ASSERT_OWNINGTHREAD(WorkerHolder);
+
+ if (mWorkerPrivate) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ mWorkerPrivate->RemoveHolder(this);
+ mWorkerPrivate = nullptr;
+ }
+}
+
+END_WORKERS_NAMESPACE
diff --git a/dom/workers/WorkerHolder.h b/dom/workers/WorkerHolder.h
new file mode 100644
index 000000000..d0eb22f29
--- /dev/null
+++ b/dom/workers/WorkerHolder.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_WorkerHolder_h
+#define mozilla_dom_workers_WorkerHolder_h
+
+#include "mozilla/dom/workers/Workers.h"
+
+BEGIN_WORKERS_NAMESPACE
+
+/**
+ * Use this chart to help figure out behavior during each of the closing
+ * statuses. Details below.
+ *
+ * +==============================================================+
+ * | Closing Statuses |
+ * +=============+=============+=================+================+
+ * | status | clear queue | abort execution | close handler |
+ * +=============+=============+=================+================+
+ * | Closing | yes | no | no timeout |
+ * +-------------+-------------+-----------------+----------------+
+ * | Terminating | yes | yes | no timeout |
+ * +-------------+-------------+-----------------+----------------+
+ * | Canceling | yes | yes | short duration |
+ * +-------------+-------------+-----------------+----------------+
+ * | Killing | yes | yes | doesn't run |
+ * +-------------+-------------+-----------------+----------------+
+ */
+
+#ifdef Status
+/* Xlib headers insist on this for some reason... Nuke it because
+ it'll override our member name */
+#undef Status
+#endif
+enum Status
+{
+ // Not yet scheduled.
+ Pending = 0,
+
+ // This status means that the close handler has not yet been scheduled.
+ Running,
+
+ // Inner script called close() on the worker global scope. Setting this
+ // status causes the worker to clear its queue of events but does not abort
+ // the currently running script. The close handler is also scheduled with
+ // no expiration time.
+ Closing,
+
+ // Outer script called terminate() on the worker or the worker object was
+ // garbage collected in its outer script. Setting this status causes the
+ // worker to abort immediately, clear its queue of events, and schedules the
+ // close handler with no expiration time.
+ Terminating,
+
+ // Either the user navigated away from the owning page or the owning page fell
+ // out of bfcache. Setting this status causes the worker to abort immediately
+ // and schedules the close handler with a short expiration time. Since the
+ // page has gone away the worker may not post any messages.
+ Canceling,
+
+ // The application is shutting down. Setting this status causes the worker to
+ // abort immediately and the close handler is never scheduled.
+ Killing,
+
+ // The close handler has run and the worker is effectively dead.
+ Dead
+};
+
+class WorkerHolder
+{
+public:
+ NS_DECL_OWNINGTHREAD
+
+ WorkerHolder();
+ virtual ~WorkerHolder();
+
+ bool HoldWorker(WorkerPrivate* aWorkerPrivate, Status aFailStatus);
+ void ReleaseWorker();
+
+ virtual bool Notify(Status aStatus) = 0;
+
+protected:
+ void ReleaseWorkerInternal();
+
+ WorkerPrivate* MOZ_NON_OWNING_REF mWorkerPrivate;
+
+private:
+ void AssertIsOwningThread() const;
+};
+
+END_WORKERS_NAMESPACE
+
+#endif /* mozilla_dom_workers_WorkerHolder_h */
diff --git a/dom/workers/WorkerInlines.h b/dom/workers/WorkerInlines.h
new file mode 100644
index 000000000..4fd7fd9a4
--- /dev/null
+++ b/dom/workers/WorkerInlines.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+BEGIN_WORKERS_NAMESPACE
+
+inline
+void
+SetJSPrivateSafeish(JSObject* aObj, PrivatizableBase* aBase)
+{
+ JS_SetPrivate(aObj, aBase);
+}
+
+template <class Derived>
+inline
+Derived*
+GetJSPrivateSafeish(JSObject* aObj)
+{
+ return static_cast<Derived*>(
+ static_cast<PrivatizableBase*>(JS_GetPrivate(aObj)));
+}
+
+END_WORKERS_NAMESPACE
diff --git a/dom/workers/WorkerLocation.cpp b/dom/workers/WorkerLocation.cpp
new file mode 100644
index 000000000..469bf84bf
--- /dev/null
+++ b/dom/workers/WorkerLocation.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/WorkerLocation.h"
+
+#include "mozilla/dom/WorkerLocationBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WorkerLocation)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WorkerLocation, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WorkerLocation, Release)
+
+/* static */ already_AddRefed<WorkerLocation>
+WorkerLocation::Create(workers::WorkerPrivate::LocationInfo& aInfo)
+{
+ RefPtr<WorkerLocation> location =
+ new WorkerLocation(NS_ConvertUTF8toUTF16(aInfo.mHref),
+ NS_ConvertUTF8toUTF16(aInfo.mProtocol),
+ NS_ConvertUTF8toUTF16(aInfo.mHost),
+ NS_ConvertUTF8toUTF16(aInfo.mHostname),
+ NS_ConvertUTF8toUTF16(aInfo.mPort),
+ NS_ConvertUTF8toUTF16(aInfo.mPathname),
+ NS_ConvertUTF8toUTF16(aInfo.mSearch),
+ NS_ConvertUTF8toUTF16(aInfo.mHash),
+ aInfo.mOrigin);
+
+ return location.forget();
+}
+
+JSObject*
+WorkerLocation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return WorkerLocationBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/WorkerLocation.h b/dom/workers/WorkerLocation.h
new file mode 100644
index 000000000..73137efff
--- /dev/null
+++ b/dom/workers/WorkerLocation.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_location_h__
+#define mozilla_dom_location_h__
+
+#include "Workers.h"
+#include "WorkerPrivate.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class WorkerLocation final : public nsWrapperCache
+{
+ nsString mHref;
+ nsString mProtocol;
+ nsString mHost;
+ nsString mHostname;
+ nsString mPort;
+ nsString mPathname;
+ nsString mSearch;
+ nsString mHash;
+ nsString mOrigin;
+
+ WorkerLocation(const nsAString& aHref,
+ const nsAString& aProtocol,
+ const nsAString& aHost,
+ const nsAString& aHostname,
+ const nsAString& aPort,
+ const nsAString& aPathname,
+ const nsAString& aSearch,
+ const nsAString& aHash,
+ const nsAString& aOrigin)
+ : mHref(aHref)
+ , mProtocol(aProtocol)
+ , mHost(aHost)
+ , mHostname(aHostname)
+ , mPort(aPort)
+ , mPathname(aPathname)
+ , mSearch(aSearch)
+ , mHash(aHash)
+ , mOrigin(aOrigin)
+ {
+ MOZ_COUNT_CTOR(WorkerLocation);
+ }
+
+ ~WorkerLocation()
+ {
+ MOZ_COUNT_DTOR(WorkerLocation);
+ }
+
+public:
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WorkerLocation)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WorkerLocation)
+
+ static already_AddRefed<WorkerLocation>
+ Create(workers::WorkerPrivate::LocationInfo& aInfo);
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsISupports* GetParentObject() const {
+ return nullptr;
+ }
+
+ void Stringify(nsString& aHref) const
+ {
+ aHref = mHref;
+ }
+ void GetHref(nsString& aHref) const
+ {
+ aHref = mHref;
+ }
+ void GetProtocol(nsString& aProtocol) const
+ {
+ aProtocol = mProtocol;
+ }
+ void GetHost(nsString& aHost) const
+ {
+ aHost = mHost;
+ }
+ void GetHostname(nsString& aHostname) const
+ {
+ aHostname = mHostname;
+ }
+ void GetPort(nsString& aPort) const
+ {
+ aPort = mPort;
+ }
+ void GetPathname(nsString& aPathname) const
+ {
+ aPathname = mPathname;
+ }
+ void GetSearch(nsString& aSearch) const
+ {
+ aSearch = mSearch;
+ }
+ void GetHash(nsString& aHash) const
+ {
+ aHash = mHash;
+ }
+ void GetOrigin(nsString& aOrigin) const
+ {
+ aOrigin = mOrigin;
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_location_h__
diff --git a/dom/workers/WorkerNavigator.cpp b/dom/workers/WorkerNavigator.cpp
new file mode 100644
index 000000000..682c7a22c
--- /dev/null
+++ b/dom/workers/WorkerNavigator.cpp
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseWorkerProxy.h"
+#include "mozilla/dom/StorageManager.h"
+#include "mozilla/dom/WorkerNavigator.h"
+#include "mozilla/dom/WorkerNavigatorBinding.h"
+
+#include "nsProxyRelease.h"
+#include "RuntimeService.h"
+
+#include "nsIDocument.h"
+
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "WorkerScope.h"
+
+#include "mozilla/dom/Navigator.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace mozilla::dom::workers;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WorkerNavigator, mStorageManager);
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WorkerNavigator, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WorkerNavigator, Release)
+
+/* static */ already_AddRefed<WorkerNavigator>
+WorkerNavigator::Create(bool aOnLine)
+{
+ RuntimeService* rts = RuntimeService::GetService();
+ MOZ_ASSERT(rts);
+
+ const RuntimeService::NavigatorProperties& properties =
+ rts->GetNavigatorProperties();
+
+ RefPtr<WorkerNavigator> navigator =
+ new WorkerNavigator(properties, aOnLine);
+
+ return navigator.forget();
+}
+
+JSObject*
+WorkerNavigator::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return WorkerNavigatorBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+WorkerNavigator::SetLanguages(const nsTArray<nsString>& aLanguages)
+{
+ WorkerNavigatorBinding::ClearCachedLanguagesValue(this);
+ mProperties.mLanguages = aLanguages;
+}
+
+void
+WorkerNavigator::GetAppName(nsString& aAppName) const
+{
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ if (!mProperties.mAppNameOverridden.IsEmpty() &&
+ !workerPrivate->UsesSystemPrincipal()) {
+ aAppName = mProperties.mAppNameOverridden;
+ } else {
+ aAppName = mProperties.mAppName;
+ }
+}
+
+void
+WorkerNavigator::GetAppVersion(nsString& aAppVersion) const
+{
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ if (!mProperties.mAppVersionOverridden.IsEmpty() &&
+ !workerPrivate->UsesSystemPrincipal()) {
+ aAppVersion = mProperties.mAppVersionOverridden;
+ } else {
+ aAppVersion = mProperties.mAppVersion;
+ }
+}
+
+void
+WorkerNavigator::GetPlatform(nsString& aPlatform) const
+{
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ if (!mProperties.mPlatformOverridden.IsEmpty() &&
+ !workerPrivate->UsesSystemPrincipal()) {
+ aPlatform = mProperties.mPlatformOverridden;
+ } else {
+ aPlatform = mProperties.mPlatform;
+ }
+}
+
+namespace {
+
+class GetUserAgentRunnable final : public WorkerMainThreadRunnable
+{
+ nsString& mUA;
+
+public:
+ GetUserAgentRunnable(WorkerPrivate* aWorkerPrivate, nsString& aUA)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ NS_LITERAL_CSTRING("UserAgent getter"))
+ , mUA(aUA)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ virtual bool MainThreadRun() override
+ {
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsPIDOMWindowInner> window = mWorkerPrivate->GetWindow();
+ nsCOMPtr<nsIURI> uri;
+ if (window && window->GetDocShell()) {
+ nsIDocument* doc = window->GetExtantDoc();
+ if (doc) {
+ doc->NodePrincipal()->GetURI(getter_AddRefs(uri));
+ }
+ }
+
+ bool isCallerChrome = mWorkerPrivate->UsesSystemPrincipal();
+ nsresult rv = dom::Navigator::GetUserAgent(window, uri,
+ isCallerChrome, mUA);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to retrieve user-agent from the worker thread.");
+ }
+
+ return true;
+ }
+};
+
+} // namespace
+
+void
+WorkerNavigator::GetUserAgent(nsString& aUserAgent, ErrorResult& aRv) const
+{
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ RefPtr<GetUserAgentRunnable> runnable =
+ new GetUserAgentRunnable(workerPrivate, aUserAgent);
+
+ runnable->Dispatch(aRv);
+}
+
+uint64_t
+WorkerNavigator::HardwareConcurrency() const
+{
+ RuntimeService* rts = RuntimeService::GetService();
+ MOZ_ASSERT(rts);
+
+ return rts->ClampedHardwareConcurrency();
+}
+
+StorageManager*
+WorkerNavigator::Storage()
+{
+ if (!mStorageManager) {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ RefPtr<nsIGlobalObject> global = workerPrivate->GlobalScope();
+ MOZ_ASSERT(global);
+
+ mStorageManager = new StorageManager(global);
+ }
+
+ return mStorageManager;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/WorkerNavigator.h b/dom/workers/WorkerNavigator.h
new file mode 100644
index 000000000..64338e46b
--- /dev/null
+++ b/dom/workers/WorkerNavigator.h
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workernavigator_h__
+#define mozilla_dom_workernavigator_h__
+
+#include "Workers.h"
+#include "RuntimeService.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/StorageManager.h"
+
+namespace mozilla {
+namespace dom {
+class Promise;
+class StorageManager;
+
+class WorkerNavigator final : public nsWrapperCache
+{
+ typedef struct workers::RuntimeService::NavigatorProperties NavigatorProperties;
+
+ NavigatorProperties mProperties;
+ RefPtr<StorageManager> mStorageManager;
+ bool mOnline;
+
+ WorkerNavigator(const NavigatorProperties& aProperties,
+ bool aOnline)
+ : mProperties(aProperties)
+ , mOnline(aOnline)
+ {
+ MOZ_COUNT_CTOR(WorkerNavigator);
+ }
+
+ ~WorkerNavigator()
+ {
+ MOZ_COUNT_DTOR(WorkerNavigator);
+ }
+
+public:
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WorkerNavigator)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WorkerNavigator)
+
+ static already_AddRefed<WorkerNavigator>
+ Create(bool aOnLine);
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsISupports* GetParentObject() const {
+ return nullptr;
+ }
+
+ void GetAppCodeName(nsString& aAppCodeName) const
+ {
+ aAppCodeName.AssignLiteral("Mozilla");
+ }
+ void GetAppName(nsString& aAppName) const;
+
+ void GetAppVersion(nsString& aAppVersion) const;
+
+ void GetPlatform(nsString& aPlatform) const;
+
+ void GetProduct(nsString& aProduct) const
+ {
+ aProduct.AssignLiteral("Gecko");
+ }
+
+ bool TaintEnabled() const
+ {
+ return false;
+ }
+
+ void GetLanguage(nsString& aLanguage) const
+ {
+ if (mProperties.mLanguages.Length() >= 1) {
+ aLanguage.Assign(mProperties.mLanguages[0]);
+ } else {
+ aLanguage.Truncate();
+ }
+ }
+
+ void GetLanguages(nsTArray<nsString>& aLanguages) const
+ {
+ aLanguages = mProperties.mLanguages;
+ }
+
+ void GetUserAgent(nsString& aUserAgent, ErrorResult& aRv) const;
+
+ bool OnLine() const
+ {
+ return mOnline;
+ }
+
+ // Worker thread only!
+ void SetOnLine(bool aOnline)
+ {
+ mOnline = aOnline;
+ }
+
+ void SetLanguages(const nsTArray<nsString>& aLanguages);
+
+ uint64_t HardwareConcurrency() const;
+
+ StorageManager* Storage();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workernavigator_h__
diff --git a/dom/workers/WorkerPrefs.h b/dom/workers/WorkerPrefs.h
new file mode 100644
index 000000000..c9b605a84
--- /dev/null
+++ b/dom/workers/WorkerPrefs.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This is the list of the preferences that are exposed to workers.
+// The format is as follows:
+//
+// WORKER_SIMPLE_PREF("foo.bar", FooBar, FOO_BAR, UpdaterFunction)
+//
+// * First argument is the name of the pref.
+// * The name of the getter function. This defines a FindName()
+// function that returns the value of the pref on WorkerPrivate.
+// * The macro version of the name. This defines a WORKERPREF_FOO_BAR
+// macro in Workers.h.
+// * The name of the function that updates the new value of a pref.
+//
+// WORKER_PREF("foo.bar", UpdaterFunction)
+//
+// * First argument is the name of the pref.
+// * The name of the function that updates the new value of a pref.
+
+#if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
+WORKER_SIMPLE_PREF("browser.dom.window.dump.enabled", DumpEnabled, DUMP)
+#endif
+WORKER_SIMPLE_PREF("canvas.imagebitmap_extensions.enabled", ImageBitmapExtensionsEnabled, IMAGEBITMAP_EXTENSIONS_ENABLED)
+WORKER_SIMPLE_PREF("dom.caches.enabled", DOMCachesEnabled, DOM_CACHES)
+WORKER_SIMPLE_PREF("dom.caches.testing.enabled", DOMCachesTestingEnabled, DOM_CACHES_TESTING)
+WORKER_SIMPLE_PREF("dom.performance.enable_user_timing_logging", PerformanceLoggingEnabled, PERFORMANCE_LOGGING_ENABLED)
+WORKER_SIMPLE_PREF("dom.webnotifications.enabled", DOMWorkerNotificationEnabled, DOM_WORKERNOTIFICATION)
+WORKER_SIMPLE_PREF("dom.webnotifications.serviceworker.enabled", DOMServiceWorkerNotificationEnabled, DOM_SERVICEWORKERNOTIFICATION)
+WORKER_SIMPLE_PREF("dom.webnotifications.requireinteraction.enabled", DOMWorkerNotificationRIEnabled, DOM_WORKERNOTIFICATIONRI)
+WORKER_SIMPLE_PREF("dom.serviceWorkers.enabled", ServiceWorkersEnabled, SERVICEWORKERS_ENABLED)
+WORKER_SIMPLE_PREF("dom.serviceWorkers.testing.enabled", ServiceWorkersTestingEnabled, SERVICEWORKERS_TESTING_ENABLED)
+WORKER_SIMPLE_PREF("dom.serviceWorkers.openWindow.enabled", OpenWindowEnabled, OPEN_WINDOW_ENABLED)
+WORKER_SIMPLE_PREF("dom.storageManager.enabled", StorageManagerEnabled, STORAGEMANAGER_ENABLED)
+WORKER_SIMPLE_PREF("dom.push.enabled", PushEnabled, PUSH_ENABLED)
+WORKER_SIMPLE_PREF("dom.requestcontext.enabled", RequestContextEnabled, REQUESTCONTEXT_ENABLED)
+WORKER_SIMPLE_PREF("gfx.offscreencanvas.enabled", OffscreenCanvasEnabled, OFFSCREENCANVAS_ENABLED)
+WORKER_SIMPLE_PREF("dom.webkitBlink.dirPicker.enabled", WebkitBlinkDirectoryPickerEnabled, DOM_WEBKITBLINK_DIRPICKER_WEBKITBLINK)
+WORKER_PREF("dom.workers.latestJSVersion", JSVersionChanged)
+WORKER_PREF("intl.accept_languages", PrefLanguagesChanged)
+WORKER_PREF("general.appname.override", AppNameOverrideChanged)
+WORKER_PREF("general.appversion.override", AppVersionOverrideChanged)
+WORKER_PREF("general.platform.override", PlatformOverrideChanged)
+#ifdef JS_GC_ZEAL
+WORKER_PREF("dom.workers.options.gcZeal", LoadGCZealOptions)
+#endif
diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
new file mode 100644
index 000000000..1df4e5551
--- /dev/null
+++ b/dom/workers/WorkerPrivate.cpp
@@ -0,0 +1,6752 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WorkerPrivate.h"
+
+#include "amIAddonManager.h"
+#include "nsIClassInfo.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIConsoleService.h"
+#include "nsIDOMDOMException.h"
+#include "nsIDOMEvent.h"
+#include "nsIDocument.h"
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIMemoryReporter.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIPermissionManager.h"
+#include "nsIScriptError.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIScriptTimeoutHandler.h"
+#include "nsITabChild.h"
+#include "nsITextToSubURI.h"
+#include "nsIThreadInternal.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIWorkerDebugger.h"
+#include "nsIXPConnect.h"
+#include "nsPIDOMWindow.h"
+#include "nsGlobalWindow.h"
+
+#include <algorithm>
+#include "ImageContainer.h"
+#include "jsfriendapi.h"
+#include "js/MemoryMetrics.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/Likely.h"
+#include "mozilla/LoadContext.h"
+#include "mozilla/Move.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Console.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/ErrorEvent.h"
+#include "mozilla/dom/ErrorEventBinding.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/ExtendableMessageEventBinding.h"
+#include "mozilla/dom/FunctionBinding.h"
+#include "mozilla/dom/IndexedDatabaseManager.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/MessagePortBinding.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PMessagePort.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseDebugging.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/SimpleGlobalObject.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/WorkerBinding.h"
+#include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
+#include "mozilla/dom/WorkerGlobalScopeBinding.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ThrottledEventQueue.h"
+#include "mozilla/TimelineConsumers.h"
+#include "mozilla/WorkerTimelineMarker.h"
+#include "nsAlgorithm.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollector.h"
+#include "nsError.h"
+#include "nsDOMJSUtils.h"
+#include "nsHostObjectProtocolHandler.h"
+#include "nsJSEnvironment.h"
+#include "nsJSUtils.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsProxyRelease.h"
+#include "nsQueryObject.h"
+#include "nsSandboxFlags.h"
+#include "nsUTF8Utils.h"
+#include "prthread.h"
+#include "xpcpublic.h"
+
+#ifdef ANDROID
+#include <android/log.h>
+#endif
+
+#ifdef DEBUG
+#include "nsThreadManager.h"
+#endif
+
+#include "Navigator.h"
+#include "Principal.h"
+#include "RuntimeService.h"
+#include "ScriptLoader.h"
+#include "ServiceWorkerEvents.h"
+#include "ServiceWorkerManager.h"
+#include "ServiceWorkerWindowClient.h"
+#include "SharedWorker.h"
+#include "WorkerDebuggerManager.h"
+#include "WorkerHolder.h"
+#include "WorkerNavigator.h"
+#include "WorkerRunnable.h"
+#include "WorkerScope.h"
+#include "WorkerThread.h"
+
+// JS_MaybeGC will run once every second during normal execution.
+#define PERIODIC_GC_TIMER_DELAY_SEC 1
+
+// A shrinking GC will run five seconds after the last event is processed.
+#define IDLE_GC_TIMER_DELAY_SEC 5
+
+#define PREF_WORKERS_ENABLED "dom.workers.enabled"
+
+static mozilla::LazyLogModule sWorkerPrivateLog("WorkerPrivate");
+static mozilla::LazyLogModule sWorkerTimeoutsLog("WorkerTimeouts");
+
+mozilla::LogModule*
+WorkerLog()
+{
+ return sWorkerPrivateLog;
+}
+
+mozilla::LogModule*
+TimeoutsLog()
+{
+ return sWorkerTimeoutsLog;
+}
+
+#define LOG(log, _args) MOZ_LOG(log, LogLevel::Debug, _args);
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+USING_WORKERS_NAMESPACE
+
+MOZ_DEFINE_MALLOC_SIZE_OF(JsWorkerMallocSizeOf)
+
+#ifdef DEBUG
+
+BEGIN_WORKERS_NAMESPACE
+
+void
+AssertIsOnMainThread()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+}
+
+END_WORKERS_NAMESPACE
+
+#endif
+
+namespace {
+
+#ifdef DEBUG
+
+const nsIID kDEBUGWorkerEventTargetIID = {
+ 0xccaba3fa, 0x5be2, 0x4de2, { 0xba, 0x87, 0x3b, 0x3b, 0x5b, 0x1d, 0x5, 0xfb }
+};
+
+#endif
+
+template <class T>
+class AutoPtrComparator
+{
+ typedef nsAutoPtr<T> A;
+ typedef T* B;
+
+public:
+ bool Equals(const A& a, const B& b) const {
+ return a && b ? *a == *b : !a && !b ? true : false;
+ }
+ bool LessThan(const A& a, const B& b) const {
+ return a && b ? *a < *b : b ? true : false;
+ }
+};
+
+template <class T>
+inline AutoPtrComparator<T>
+GetAutoPtrComparator(const nsTArray<nsAutoPtr<T> >&)
+{
+ return AutoPtrComparator<T>();
+}
+
+// Specialize this if there's some class that has multiple nsISupports bases.
+template <class T>
+struct ISupportsBaseInfo
+{
+ typedef T ISupportsBase;
+};
+
+template <template <class> class SmartPtr, class T>
+inline void
+SwapToISupportsArray(SmartPtr<T>& aSrc,
+ nsTArray<nsCOMPtr<nsISupports> >& aDest)
+{
+ nsCOMPtr<nsISupports>* dest = aDest.AppendElement();
+
+ T* raw = nullptr;
+ aSrc.swap(raw);
+
+ nsISupports* rawSupports =
+ static_cast<typename ISupportsBaseInfo<T>::ISupportsBase*>(raw);
+ dest->swap(rawSupports);
+}
+
+// This class is used to wrap any runnables that the worker receives via the
+// nsIEventTarget::Dispatch() method (either from NS_DispatchToCurrentThread or
+// from the worker's EventTarget).
+class ExternalRunnableWrapper final : public WorkerRunnable
+{
+ nsCOMPtr<nsIRunnable> mWrappedRunnable;
+
+public:
+ ExternalRunnableWrapper(WorkerPrivate* aWorkerPrivate,
+ nsIRunnable* aWrappedRunnable)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mWrappedRunnable(aWrappedRunnable)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aWrappedRunnable);
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+private:
+ ~ExternalRunnableWrapper()
+ { }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ nsresult rv = mWrappedRunnable->Run();
+ if (NS_FAILED(rv)) {
+ if (!JS_IsExceptionPending(aCx)) {
+ Throw(aCx, rv);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ nsresult
+ Cancel() override
+ {
+ nsresult rv;
+ nsCOMPtr<nsICancelableRunnable> cancelable =
+ do_QueryInterface(mWrappedRunnable);
+ MOZ_ASSERT(cancelable); // We checked this earlier!
+ rv = cancelable->Cancel();
+ nsresult rv2 = WorkerRunnable::Cancel();
+ return NS_FAILED(rv) ? rv : rv2;
+ }
+};
+
+struct WindowAction
+{
+ nsPIDOMWindowInner* mWindow;
+ bool mDefaultAction;
+
+ MOZ_IMPLICIT WindowAction(nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow), mDefaultAction(true)
+ { }
+
+ bool
+ operator==(const WindowAction& aOther) const
+ {
+ return mWindow == aOther.mWindow;
+ }
+};
+
+void
+LogErrorToConsole(const nsAString& aMessage,
+ const nsAString& aFilename,
+ const nsAString& aLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aFlags,
+ uint64_t aInnerWindowId)
+{
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIScriptError> scriptError =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
+ NS_WARNING_ASSERTION(scriptError, "Failed to create script error!");
+
+ if (scriptError) {
+ if (NS_FAILED(scriptError->InitWithWindowID(aMessage, aFilename, aLine,
+ aLineNumber, aColumnNumber,
+ aFlags, "Web Worker",
+ aInnerWindowId))) {
+ NS_WARNING("Failed to init script error!");
+ scriptError = nullptr;
+ }
+ }
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ NS_WARNING_ASSERTION(consoleService, "Failed to get console service!");
+
+ if (consoleService) {
+ if (scriptError) {
+ if (NS_SUCCEEDED(consoleService->LogMessage(scriptError))) {
+ return;
+ }
+ NS_WARNING("LogMessage failed!");
+ } else if (NS_SUCCEEDED(consoleService->LogStringMessage(
+ aMessage.BeginReading()))) {
+ return;
+ }
+ NS_WARNING("LogStringMessage failed!");
+ }
+
+ NS_ConvertUTF16toUTF8 msg(aMessage);
+ NS_ConvertUTF16toUTF8 filename(aFilename);
+
+ static const char kErrorString[] = "JS error in Web Worker: %s [%s:%u]";
+
+#ifdef ANDROID
+ __android_log_print(ANDROID_LOG_INFO, "Gecko", kErrorString, msg.get(),
+ filename.get(), aLineNumber);
+#endif
+
+ fprintf(stderr, kErrorString, msg.get(), filename.get(), aLineNumber);
+ fflush(stderr);
+}
+
+class MainThreadReleaseRunnable final : public Runnable
+{
+ nsTArray<nsCOMPtr<nsISupports>> mDoomed;
+ nsCOMPtr<nsILoadGroup> mLoadGroupToCancel;
+
+public:
+ MainThreadReleaseRunnable(nsTArray<nsCOMPtr<nsISupports>>& aDoomed,
+ nsCOMPtr<nsILoadGroup>& aLoadGroupToCancel)
+ {
+ mDoomed.SwapElements(aDoomed);
+ mLoadGroupToCancel.swap(aLoadGroupToCancel);
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD
+ Run() override
+ {
+ if (mLoadGroupToCancel) {
+ mLoadGroupToCancel->Cancel(NS_BINDING_ABORTED);
+ mLoadGroupToCancel = nullptr;
+ }
+
+ mDoomed.Clear();
+ return NS_OK;
+ }
+
+private:
+ ~MainThreadReleaseRunnable()
+ { }
+};
+
+class WorkerFinishedRunnable final : public WorkerControlRunnable
+{
+ WorkerPrivate* mFinishedWorker;
+
+public:
+ WorkerFinishedRunnable(WorkerPrivate* aWorkerPrivate,
+ WorkerPrivate* aFinishedWorker)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mFinishedWorker(aFinishedWorker)
+ { }
+
+private:
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ // Silence bad assertions.
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ // Silence bad assertions.
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ nsCOMPtr<nsILoadGroup> loadGroupToCancel;
+ mFinishedWorker->ForgetOverridenLoadGroup(loadGroupToCancel);
+
+ nsTArray<nsCOMPtr<nsISupports>> doomed;
+ mFinishedWorker->ForgetMainThreadObjects(doomed);
+
+ RefPtr<MainThreadReleaseRunnable> runnable =
+ new MainThreadReleaseRunnable(doomed, loadGroupToCancel);
+ if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable.forget()))) {
+ NS_WARNING("Failed to dispatch, going to leak!");
+ }
+
+ RuntimeService* runtime = RuntimeService::GetService();
+ NS_ASSERTION(runtime, "This should never be null!");
+
+ mFinishedWorker->DisableDebugger();
+
+ runtime->UnregisterWorker(mFinishedWorker);
+
+ mFinishedWorker->ClearSelfRef();
+ return true;
+ }
+};
+
+class TopLevelWorkerFinishedRunnable final : public Runnable
+{
+ WorkerPrivate* mFinishedWorker;
+
+public:
+ explicit TopLevelWorkerFinishedRunnable(WorkerPrivate* aFinishedWorker)
+ : mFinishedWorker(aFinishedWorker)
+ {
+ aFinishedWorker->AssertIsOnWorkerThread();
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+private:
+ ~TopLevelWorkerFinishedRunnable() {}
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+
+ RuntimeService* runtime = RuntimeService::GetService();
+ MOZ_ASSERT(runtime);
+
+ mFinishedWorker->DisableDebugger();
+
+ runtime->UnregisterWorker(mFinishedWorker);
+
+ nsCOMPtr<nsILoadGroup> loadGroupToCancel;
+ mFinishedWorker->ForgetOverridenLoadGroup(loadGroupToCancel);
+
+ nsTArray<nsCOMPtr<nsISupports> > doomed;
+ mFinishedWorker->ForgetMainThreadObjects(doomed);
+
+ RefPtr<MainThreadReleaseRunnable> runnable =
+ new MainThreadReleaseRunnable(doomed, loadGroupToCancel);
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ NS_WARNING("Failed to dispatch, going to leak!");
+ }
+
+ mFinishedWorker->ClearSelfRef();
+ return NS_OK;
+ }
+};
+
+class ModifyBusyCountRunnable final : public WorkerControlRunnable
+{
+ bool mIncrease;
+
+public:
+ ModifyBusyCountRunnable(WorkerPrivate* aWorkerPrivate, bool aIncrease)
+ : WorkerControlRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount),
+ mIncrease(aIncrease)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ return aWorkerPrivate->ModifyBusyCount(mIncrease);
+ }
+
+ virtual void
+ PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
+ override
+ {
+ if (mIncrease) {
+ WorkerControlRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
+ return;
+ }
+ // Don't do anything here as it's possible that aWorkerPrivate has been
+ // deleted.
+ }
+};
+
+class CompileScriptRunnable final : public WorkerRunnable
+{
+ nsString mScriptURL;
+
+public:
+ explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsAString& aScriptURL)
+ : WorkerRunnable(aWorkerPrivate),
+ mScriptURL(aScriptURL)
+ { }
+
+private:
+ // We can't implement PreRun effectively, because at the point when that would
+ // run we have not yet done our load so don't know things like our final
+ // principal and whatnot.
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ ErrorResult rv;
+ scriptloader::LoadMainScript(aWorkerPrivate, mScriptURL, WorkerScript, rv);
+ rv.WouldReportJSException();
+ // Explicitly ignore NS_BINDING_ABORTED on rv. Or more precisely, still
+ // return false and don't SetWorkerScriptExecutedSuccessfully() in that
+ // case, but don't throw anything on aCx. The idea is to not dispatch error
+ // events if our load is canceled with that error code.
+ if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) {
+ rv.SuppressException();
+ return false;
+ }
+
+ WorkerGlobalScope* globalScope = aWorkerPrivate->GlobalScope();
+ if (NS_WARN_IF(!globalScope)) {
+ // We never got as far as calling GetOrCreateGlobalScope, or it failed.
+ // We have no way to enter a compartment, hence no sane way to report this
+ // error. :(
+ rv.SuppressException();
+ return false;
+ }
+
+ // Make sure to propagate exceptions from rv onto aCx, so that they will get
+ // reported after we return. We do this for all failures on rv, because now
+ // we're using rv to track all the state we care about.
+ //
+ // This is a little dumb, but aCx is in the null compartment here because we
+ // set it up that way in our Run(), since we had not created the global at
+ // that point yet. So we need to enter the compartment of our global,
+ // because setting a pending exception on aCx involves wrapping into its
+ // current compartment. Luckily we have a global now.
+ JSAutoCompartment ac(aCx, globalScope->GetGlobalJSObject());
+ if (rv.MaybeSetPendingException(aCx)) {
+ return false;
+ }
+
+ aWorkerPrivate->SetWorkerScriptExecutedSuccessfully();
+ return true;
+ }
+};
+
+class CompileDebuggerScriptRunnable final : public WorkerDebuggerRunnable
+{
+ nsString mScriptURL;
+
+public:
+ CompileDebuggerScriptRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsAString& aScriptURL)
+ : WorkerDebuggerRunnable(aWorkerPrivate),
+ mScriptURL(aScriptURL)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ WorkerDebuggerGlobalScope* globalScope =
+ aWorkerPrivate->CreateDebuggerGlobalScope(aCx);
+ if (!globalScope) {
+ NS_WARNING("Failed to make global!");
+ return false;
+ }
+
+ JS::Rooted<JSObject*> global(aCx, globalScope->GetWrapper());
+
+ ErrorResult rv;
+ JSAutoCompartment ac(aCx, global);
+ scriptloader::LoadMainScript(aWorkerPrivate, mScriptURL,
+ DebuggerScript, rv);
+ rv.WouldReportJSException();
+ // Explicitly ignore NS_BINDING_ABORTED on rv. Or more precisely, still
+ // return false and don't SetWorkerScriptExecutedSuccessfully() in that
+ // case, but don't throw anything on aCx. The idea is to not dispatch error
+ // events if our load is canceled with that error code.
+ if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) {
+ rv.SuppressException();
+ return false;
+ }
+ // Make sure to propagate exceptions from rv onto aCx, so that they will get
+ // reported after we return. We do this for all failures on rv, because now
+ // we're using rv to track all the state we care about.
+ if (rv.MaybeSetPendingException(aCx)) {
+ return false;
+ }
+
+ return true;
+ }
+};
+
+class MessageEventRunnable final : public WorkerRunnable
+ , public StructuredCloneHolder
+{
+ // This is only used for messages dispatched to a service worker.
+ UniquePtr<ServiceWorkerClientInfo> mEventSource;
+
+ RefPtr<PromiseNativeHandler> mHandler;
+
+public:
+ MessageEventRunnable(WorkerPrivate* aWorkerPrivate,
+ TargetAndBusyBehavior aBehavior)
+ : WorkerRunnable(aWorkerPrivate, aBehavior)
+ , StructuredCloneHolder(CloningSupported, TransferringSupported,
+ StructuredCloneScope::SameProcessDifferentThread)
+ {
+ }
+
+ void
+ SetServiceWorkerData(UniquePtr<ServiceWorkerClientInfo>&& aSource,
+ PromiseNativeHandler* aHandler)
+ {
+ mEventSource = Move(aSource);
+ mHandler = aHandler;
+ }
+
+ bool
+ DispatchDOMEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ DOMEventTargetHelper* aTarget, bool aIsMainThread)
+ {
+ nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(aTarget->GetParentObject());
+
+ // For some workers without window, parent is null and we try to find it
+ // from the JS Context.
+ if (!parent) {
+ JS::Rooted<JSObject*> globalObject(aCx, JS::CurrentGlobalOrNull(aCx));
+ if (NS_WARN_IF(!globalObject)) {
+ return false;
+ }
+
+ parent = xpc::NativeGlobal(globalObject);
+ if (NS_WARN_IF(!parent)) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(parent);
+
+ JS::Rooted<JS::Value> messageData(aCx);
+ ErrorResult rv;
+
+ UniquePtr<AbstractTimelineMarker> start;
+ UniquePtr<AbstractTimelineMarker> end;
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ bool isTimelineRecording = timelines && !timelines->IsEmpty();
+
+ if (isTimelineRecording) {
+ start = MakeUnique<WorkerTimelineMarker>(aIsMainThread
+ ? ProfileTimelineWorkerOperationType::DeserializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::DeserializeDataOffMainThread,
+ MarkerTracingType::START);
+ }
+
+ Read(parent, aCx, &messageData, rv);
+
+ if (isTimelineRecording) {
+ end = MakeUnique<WorkerTimelineMarker>(aIsMainThread
+ ? ProfileTimelineWorkerOperationType::DeserializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::DeserializeDataOffMainThread,
+ MarkerTracingType::END);
+ timelines->AddMarkerForAllObservedDocShells(start);
+ timelines->AddMarkerForAllObservedDocShells(end);
+ }
+
+ if (NS_WARN_IF(rv.Failed())) {
+ xpc::Throw(aCx, rv.StealNSResult());
+ return false;
+ }
+
+ Sequence<OwningNonNull<MessagePort>> ports;
+ if (!TakeTransferredPortsAsSequence(ports)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDOMEvent> domEvent;
+ RefPtr<ExtendableMessageEvent> extendableEvent;
+ // For messages dispatched to service worker, use ExtendableMessageEvent
+ // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#extendablemessage-event-section
+ if (mEventSource) {
+ RefPtr<ServiceWorkerClient> client =
+ new ServiceWorkerWindowClient(aTarget, *mEventSource);
+
+ RootedDictionary<ExtendableMessageEventInit> init(aCx);
+
+ init.mBubbles = false;
+ init.mCancelable = false;
+
+ init.mData = messageData;
+ init.mPorts = ports;
+ init.mSource.SetValue().SetAsClient() = client;
+
+ ErrorResult rv;
+ extendableEvent = ExtendableMessageEvent::Constructor(
+ aTarget, NS_LITERAL_STRING("message"), init, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ domEvent = do_QueryObject(extendableEvent);
+ } else {
+ RefPtr<MessageEvent> event = new MessageEvent(aTarget, nullptr, nullptr);
+ event->InitMessageEvent(nullptr,
+ NS_LITERAL_STRING("message"),
+ false /* non-bubbling */,
+ false /* cancelable */,
+ messageData,
+ EmptyString(),
+ EmptyString(),
+ nullptr,
+ ports);
+ domEvent = do_QueryObject(event);
+ }
+
+ domEvent->SetTrusted(true);
+
+ nsEventStatus dummy = nsEventStatus_eIgnore;
+ aTarget->DispatchDOMEvent(nullptr, domEvent, nullptr, &dummy);
+
+ if (extendableEvent && mHandler) {
+ RefPtr<Promise> waitUntilPromise = extendableEvent->GetPromise();
+ if (!waitUntilPromise) {
+ waitUntilPromise = Promise::Resolve(parent, aCx,
+ JS::UndefinedHandleValue, rv);
+ MOZ_RELEASE_ASSERT(!rv.Failed());
+ }
+
+ MOZ_ASSERT(waitUntilPromise);
+
+ waitUntilPromise->AppendNativeHandler(mHandler);
+ }
+
+ return true;
+ }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ if (mBehavior == ParentThreadUnchangedBusyCount) {
+ // Don't fire this event if the JS object has been disconnected from the
+ // private object.
+ if (!aWorkerPrivate->IsAcceptingEvents()) {
+ return true;
+ }
+
+ if (aWorkerPrivate->IsFrozen() ||
+ aWorkerPrivate->IsParentWindowPaused()) {
+ MOZ_ASSERT(!IsDebuggerRunnable());
+ aWorkerPrivate->QueueRunnable(this);
+ return true;
+ }
+
+ aWorkerPrivate->AssertInnerWindowIsCorrect();
+
+ return DispatchDOMEvent(aCx, aWorkerPrivate, aWorkerPrivate,
+ !aWorkerPrivate->GetParent());
+ }
+
+ MOZ_ASSERT(aWorkerPrivate == GetWorkerPrivateFromContext(aCx));
+
+ return DispatchDOMEvent(aCx, aWorkerPrivate, aWorkerPrivate->GlobalScope(),
+ false);
+ }
+};
+
+class DebuggerMessageEventRunnable : public WorkerDebuggerRunnable {
+ nsString mMessage;
+
+public:
+ DebuggerMessageEventRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsAString& aMessage)
+ : WorkerDebuggerRunnable(aWorkerPrivate),
+ mMessage(aMessage)
+ {
+ }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ WorkerDebuggerGlobalScope* globalScope = aWorkerPrivate->DebuggerGlobalScope();
+ MOZ_ASSERT(globalScope);
+
+ JS::Rooted<JSString*> message(aCx, JS_NewUCStringCopyN(aCx, mMessage.get(),
+ mMessage.Length()));
+ if (!message) {
+ return false;
+ }
+ JS::Rooted<JS::Value> data(aCx, JS::StringValue(message));
+
+ RefPtr<MessageEvent> event = new MessageEvent(globalScope, nullptr,
+ nullptr);
+ event->InitMessageEvent(nullptr,
+ NS_LITERAL_STRING("message"),
+ false, // canBubble
+ true, // cancelable
+ data,
+ EmptyString(),
+ EmptyString(),
+ nullptr,
+ Sequence<OwningNonNull<MessagePort>>());
+ event->SetTrusted(true);
+
+ nsCOMPtr<nsIDOMEvent> domEvent = do_QueryObject(event);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ globalScope->DispatchDOMEvent(nullptr, domEvent, nullptr, &status);
+ return true;
+ }
+};
+
+class NotifyRunnable final : public WorkerControlRunnable
+{
+ Status mStatus;
+
+public:
+ NotifyRunnable(WorkerPrivate* aWorkerPrivate, Status aStatus)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mStatus(aStatus)
+ {
+ MOZ_ASSERT(aStatus == Closing || aStatus == Terminating ||
+ aStatus == Canceling || aStatus == Killing);
+ }
+
+private:
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->AssertIsOnParentThread();
+ return aWorkerPrivate->ModifyBusyCount(true);
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ aWorkerPrivate->AssertIsOnParentThread();
+ if (!aDispatchResult) {
+ // We couldn't dispatch to the worker, which means it's already dead.
+ // Undo the busy count modification.
+ aWorkerPrivate->ModifyBusyCount(false);
+ }
+ }
+
+ virtual void
+ PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
+ override
+ {
+ aWorkerPrivate->ModifyBusyCountFromWorker(false);
+ return;
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ bool ok = aWorkerPrivate->NotifyInternal(aCx, mStatus);
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+ return ok;
+ }
+};
+
+class CloseRunnable final : public WorkerControlRunnable
+{
+public:
+ explicit CloseRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ return aWorkerPrivate->Close();
+ }
+};
+
+class FreezeRunnable final : public WorkerControlRunnable
+{
+public:
+ explicit FreezeRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ return aWorkerPrivate->FreezeInternal();
+ }
+};
+
+class ThawRunnable final : public WorkerControlRunnable
+{
+public:
+ explicit ThawRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ return aWorkerPrivate->ThawInternal();
+ }
+};
+
+class ReportErrorToConsoleRunnable final : public WorkerRunnable
+{
+ const char* mMessage;
+
+public:
+ // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
+ static void
+ Report(WorkerPrivate* aWorkerPrivate, const char* aMessage)
+ {
+ if (aWorkerPrivate) {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ } else {
+ AssertIsOnMainThread();
+ }
+
+ // Now fire a runnable to do the same on the parent's thread if we can.
+ if (aWorkerPrivate) {
+ RefPtr<ReportErrorToConsoleRunnable> runnable =
+ new ReportErrorToConsoleRunnable(aWorkerPrivate, aMessage);
+ runnable->Dispatch();
+ return;
+ }
+
+ // Log a warning to the console.
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM"),
+ nullptr,
+ nsContentUtils::eDOM_PROPERTIES,
+ aMessage);
+ }
+
+private:
+ ReportErrorToConsoleRunnable(WorkerPrivate* aWorkerPrivate, const char* aMessage)
+ : WorkerRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount),
+ mMessage(aMessage)
+ { }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ // Dispatch may fail if the worker was canceled, no need to report that as
+ // an error, so don't call base class PostDispatch.
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ WorkerPrivate* parent = aWorkerPrivate->GetParent();
+ MOZ_ASSERT_IF(!parent, NS_IsMainThread());
+ Report(parent, mMessage);
+ return true;
+ }
+};
+
+class ReportErrorRunnable final : public WorkerRunnable
+{
+ nsString mMessage;
+ nsString mFilename;
+ nsString mLine;
+ uint32_t mLineNumber;
+ uint32_t mColumnNumber;
+ uint32_t mFlags;
+ uint32_t mErrorNumber;
+ JSExnType mExnType;
+ bool mMutedError;
+
+public:
+ // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
+ // aTarget is the worker object that we are going to fire an error at
+ // (if any).
+ static void
+ ReportError(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aFireAtScope, WorkerPrivate* aTarget,
+ const nsString& aMessage, const nsString& aFilename,
+ const nsString& aLine, uint32_t aLineNumber,
+ uint32_t aColumnNumber, uint32_t aFlags,
+ uint32_t aErrorNumber, JSExnType aExnType,
+ bool aMutedError, uint64_t aInnerWindowId,
+ JS::Handle<JS::Value> aException = JS::NullHandleValue)
+ {
+ if (aWorkerPrivate) {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ } else {
+ AssertIsOnMainThread();
+ }
+
+ // We should not fire error events for warnings but instead make sure that
+ // they show up in the error console.
+ if (!JSREPORT_IS_WARNING(aFlags)) {
+ // First fire an ErrorEvent at the worker.
+ RootedDictionary<ErrorEventInit> init(aCx);
+
+ if (aMutedError) {
+ init.mMessage.AssignLiteral("Script error.");
+ } else {
+ init.mMessage = aMessage;
+ init.mFilename = aFilename;
+ init.mLineno = aLineNumber;
+ init.mError = aException;
+ }
+
+ init.mCancelable = true;
+ init.mBubbles = false;
+
+ if (aTarget) {
+ RefPtr<ErrorEvent> event =
+ ErrorEvent::Constructor(aTarget, NS_LITERAL_STRING("error"), init);
+ event->SetTrusted(true);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ aTarget->DispatchDOMEvent(nullptr, event, nullptr, &status);
+
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+ }
+
+ // Now fire an event at the global object, but don't do that if the error
+ // code is too much recursion and this is the same script threw the error.
+ // XXXbz the interaction of this with worker errors seems kinda broken.
+ // An overrecursion in the debugger or debugger sandbox will get turned
+ // into an error event on our parent worker!
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks making this
+ // better.
+ if (aFireAtScope && (aTarget || aErrorNumber != JSMSG_OVER_RECURSED)) {
+ JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+ NS_ASSERTION(global, "This should never be null!");
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsIScriptGlobalObject* sgo;
+
+ if (aWorkerPrivate) {
+ WorkerGlobalScope* globalScope = nullptr;
+ UNWRAP_OBJECT(WorkerGlobalScope, &global, globalScope);
+
+ if (!globalScope) {
+ WorkerDebuggerGlobalScope* globalScope = nullptr;
+ UNWRAP_OBJECT(WorkerDebuggerGlobalScope, &global, globalScope);
+
+ MOZ_ASSERT_IF(globalScope, globalScope->GetWrapperPreserveColor() == global);
+ if (globalScope || IsDebuggerSandbox(global)) {
+ aWorkerPrivate->ReportErrorToDebugger(aFilename, aLineNumber,
+ aMessage);
+ return;
+ }
+
+ MOZ_ASSERT(SimpleGlobalObject::SimpleGlobalType(global) ==
+ SimpleGlobalObject::GlobalType::BindingDetail);
+ // XXXbz We should really log this to console, but unwinding out of
+ // this stuff without ending up firing any events is ... hard. Just
+ // return for now.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks
+ // making this better.
+ return;
+ }
+
+ MOZ_ASSERT(globalScope->GetWrapperPreserveColor() == global);
+ nsIDOMEventTarget* target = static_cast<nsIDOMEventTarget*>(globalScope);
+
+ RefPtr<ErrorEvent> event =
+ ErrorEvent::Constructor(aTarget, NS_LITERAL_STRING("error"), init);
+ event->SetTrusted(true);
+
+ if (NS_FAILED(EventDispatcher::DispatchDOMEvent(target, nullptr,
+ event, nullptr,
+ &status))) {
+ NS_WARNING("Failed to dispatch worker thread error event!");
+ status = nsEventStatus_eIgnore;
+ }
+ }
+ else if ((sgo = nsJSUtils::GetStaticScriptGlobal(global))) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_FAILED(sgo->HandleScriptError(init, &status))) {
+ NS_WARNING("Failed to dispatch main thread error event!");
+ status = nsEventStatus_eIgnore;
+ }
+ }
+
+ // Was preventDefault() called?
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+ }
+ }
+
+ // Now fire a runnable to do the same on the parent's thread if we can.
+ if (aWorkerPrivate) {
+ RefPtr<ReportErrorRunnable> runnable =
+ new ReportErrorRunnable(aWorkerPrivate, aMessage, aFilename, aLine,
+ aLineNumber, aColumnNumber, aFlags,
+ aErrorNumber, aExnType, aMutedError);
+ runnable->Dispatch();
+ return;
+ }
+
+ // Otherwise log an error to the error console.
+ LogErrorToConsole(aMessage, aFilename, aLine, aLineNumber, aColumnNumber,
+ aFlags, aInnerWindowId);
+ }
+
+private:
+ ReportErrorRunnable(WorkerPrivate* aWorkerPrivate, const nsString& aMessage,
+ const nsString& aFilename, const nsString& aLine,
+ uint32_t aLineNumber, uint32_t aColumnNumber,
+ uint32_t aFlags, uint32_t aErrorNumber,
+ JSExnType aExnType, bool aMutedError)
+ : WorkerRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount),
+ mMessage(aMessage), mFilename(aFilename), mLine(aLine),
+ mLineNumber(aLineNumber), mColumnNumber(aColumnNumber), mFlags(aFlags),
+ mErrorNumber(aErrorNumber), mExnType(aExnType), mMutedError(aMutedError)
+ { }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ // Dispatch may fail if the worker was canceled, no need to report that as
+ // an error, so don't call base class PostDispatch.
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ JS::Rooted<JSObject*> target(aCx, aWorkerPrivate->GetWrapper());
+
+ uint64_t innerWindowId;
+ bool fireAtScope = true;
+
+ bool workerIsAcceptingEvents = aWorkerPrivate->IsAcceptingEvents();
+
+ WorkerPrivate* parent = aWorkerPrivate->GetParent();
+ if (parent) {
+ innerWindowId = 0;
+ }
+ else {
+ AssertIsOnMainThread();
+
+ if (aWorkerPrivate->IsFrozen() ||
+ aWorkerPrivate->IsParentWindowPaused()) {
+ MOZ_ASSERT(!IsDebuggerRunnable());
+ aWorkerPrivate->QueueRunnable(this);
+ return true;
+ }
+
+ if (aWorkerPrivate->IsSharedWorker()) {
+ aWorkerPrivate->BroadcastErrorToSharedWorkers(aCx, mMessage, mFilename,
+ mLine, mLineNumber,
+ mColumnNumber, mFlags);
+ return true;
+ }
+
+ // Service workers do not have a main thread parent global, so normal
+ // worker error reporting will crash. Instead, pass the error to
+ // the ServiceWorkerManager to report on any controlled documents.
+ if (aWorkerPrivate->IsServiceWorker()) {
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->HandleError(aCx, aWorkerPrivate->GetPrincipal(),
+ aWorkerPrivate->WorkerName(),
+ aWorkerPrivate->ScriptURL(),
+ mMessage,
+ mFilename, mLine, mLineNumber,
+ mColumnNumber, mFlags, mExnType);
+ }
+ return true;
+ }
+
+ // The innerWindowId is only required if we are going to ReportError
+ // below, which is gated on this condition. The inner window correctness
+ // check is only going to succeed when the worker is accepting events.
+ if (workerIsAcceptingEvents) {
+ aWorkerPrivate->AssertInnerWindowIsCorrect();
+ innerWindowId = aWorkerPrivate->WindowID();
+ }
+ }
+
+ // Don't fire this event if the JS object has been disconnected from the
+ // private object.
+ if (!workerIsAcceptingEvents) {
+ return true;
+ }
+
+ ReportError(aCx, parent, fireAtScope, aWorkerPrivate, mMessage,
+ mFilename, mLine, mLineNumber, mColumnNumber, mFlags,
+ mErrorNumber, mExnType, mMutedError, innerWindowId);
+ return true;
+ }
+};
+
+class TimerRunnable final : public WorkerRunnable,
+ public nsITimerCallback
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit TimerRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ { }
+
+private:
+ ~TimerRunnable() {}
+
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ // Silence bad assertions.
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ // Silence bad assertions.
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ return aWorkerPrivate->RunExpiredTimeouts(aCx);
+ }
+
+ NS_IMETHOD
+ Notify(nsITimer* aTimer) override
+ {
+ return Run();
+ }
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(TimerRunnable, WorkerRunnable, nsITimerCallback)
+
+class DebuggerImmediateRunnable : public WorkerRunnable
+{
+ RefPtr<dom::Function> mHandler;
+
+public:
+ explicit DebuggerImmediateRunnable(WorkerPrivate* aWorkerPrivate,
+ dom::Function& aHandler)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mHandler(&aHandler)
+ { }
+
+private:
+ virtual bool
+ IsDebuggerRunnable() const override
+ {
+ return true;
+ }
+
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ // Silence bad assertions.
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ // Silence bad assertions.
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+ JS::Rooted<JS::Value> callable(aCx, JS::ObjectValue(*mHandler->Callable()));
+ JS::HandleValueArray args = JS::HandleValueArray::empty();
+ JS::Rooted<JS::Value> rval(aCx);
+ if (!JS_CallFunctionValue(aCx, global, callable, args, &rval)) {
+ // Just return false; WorkerRunnable::Run will report the exception.
+ return false;
+ }
+
+ return true;
+ }
+};
+
+void
+DummyCallback(nsITimer* aTimer, void* aClosure)
+{
+ // Nothing!
+}
+
+class UpdateContextOptionsRunnable final : public WorkerControlRunnable
+{
+ JS::ContextOptions mContextOptions;
+
+public:
+ UpdateContextOptionsRunnable(WorkerPrivate* aWorkerPrivate,
+ const JS::ContextOptions& aContextOptions)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mContextOptions(aContextOptions)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->UpdateContextOptionsInternal(aCx, mContextOptions);
+ return true;
+ }
+};
+
+class UpdatePreferenceRunnable final : public WorkerControlRunnable
+{
+ WorkerPreference mPref;
+ bool mValue;
+
+public:
+ UpdatePreferenceRunnable(WorkerPrivate* aWorkerPrivate,
+ WorkerPreference aPref,
+ bool aValue)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mPref(aPref),
+ mValue(aValue)
+ { }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->UpdatePreferenceInternal(mPref, mValue);
+ return true;
+ }
+};
+
+class UpdateLanguagesRunnable final : public WorkerRunnable
+{
+ nsTArray<nsString> mLanguages;
+
+public:
+ UpdateLanguagesRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsTArray<nsString>& aLanguages)
+ : WorkerRunnable(aWorkerPrivate),
+ mLanguages(aLanguages)
+ { }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->UpdateLanguagesInternal(mLanguages);
+ return true;
+ }
+};
+
+class UpdateJSWorkerMemoryParameterRunnable final :
+ public WorkerControlRunnable
+{
+ uint32_t mValue;
+ JSGCParamKey mKey;
+
+public:
+ UpdateJSWorkerMemoryParameterRunnable(WorkerPrivate* aWorkerPrivate,
+ JSGCParamKey aKey,
+ uint32_t aValue)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mValue(aValue), mKey(aKey)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->UpdateJSWorkerMemoryParameterInternal(aCx, mKey, mValue);
+ return true;
+ }
+};
+
+#ifdef JS_GC_ZEAL
+class UpdateGCZealRunnable final : public WorkerControlRunnable
+{
+ uint8_t mGCZeal;
+ uint32_t mFrequency;
+
+public:
+ UpdateGCZealRunnable(WorkerPrivate* aWorkerPrivate,
+ uint8_t aGCZeal,
+ uint32_t aFrequency)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mGCZeal(aGCZeal), mFrequency(aFrequency)
+ { }
+
+private:
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->UpdateGCZealInternal(aCx, mGCZeal, mFrequency);
+ return true;
+ }
+};
+#endif
+
+class GarbageCollectRunnable final : public WorkerControlRunnable
+{
+ bool mShrinking;
+ bool mCollectChildren;
+
+public:
+ GarbageCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aShrinking,
+ bool aCollectChildren)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mShrinking(aShrinking), mCollectChildren(aCollectChildren)
+ { }
+
+private:
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ // Silence bad assertions, this can be dispatched from either the main
+ // thread or the timer thread..
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ // Silence bad assertions, this can be dispatched from either the main
+ // thread or the timer thread..
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->GarbageCollectInternal(aCx, mShrinking, mCollectChildren);
+ return true;
+ }
+};
+
+class CycleCollectRunnable : public WorkerControlRunnable
+{
+ bool mCollectChildren;
+
+public:
+ CycleCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aCollectChildren)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mCollectChildren(aCollectChildren)
+ { }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+ {
+ aWorkerPrivate->CycleCollectInternal(mCollectChildren);
+ return true;
+ }
+};
+
+class OfflineStatusChangeRunnable : public WorkerRunnable
+{
+public:
+ OfflineStatusChangeRunnable(WorkerPrivate* aWorkerPrivate, bool aIsOffline)
+ : WorkerRunnable(aWorkerPrivate),
+ mIsOffline(aIsOffline)
+ {
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+ {
+ aWorkerPrivate->OfflineStatusChangeEventInternal(mIsOffline);
+ return true;
+ }
+
+private:
+ bool mIsOffline;
+};
+
+class MemoryPressureRunnable : public WorkerControlRunnable
+{
+public:
+ explicit MemoryPressureRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ {}
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+ {
+ aWorkerPrivate->MemoryPressureInternal();
+ return true;
+ }
+};
+
+#ifdef DEBUG
+static bool
+StartsWithExplicit(nsACString& s)
+{
+ return StringBeginsWith(s, NS_LITERAL_CSTRING("explicit/"));
+}
+#endif
+
+class MessagePortRunnable final : public WorkerRunnable
+{
+ MessagePortIdentifier mPortIdentifier;
+
+public:
+ MessagePortRunnable(WorkerPrivate* aWorkerPrivate, MessagePort* aPort)
+ : WorkerRunnable(aWorkerPrivate)
+ {
+ MOZ_ASSERT(aPort);
+ // In order to move the port from one thread to another one, we have to
+ // close and disentangle it. The output will be a MessagePortIdentifier that
+ // will be used to recreate a new MessagePort on the other thread.
+ aPort->CloneAndDisentangle(mPortIdentifier);
+ }
+
+private:
+ ~MessagePortRunnable()
+ { }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ return aWorkerPrivate->ConnectMessagePort(aCx, mPortIdentifier);
+ }
+
+ nsresult
+ Cancel() override
+ {
+ MessagePort::ForceClose(mPortIdentifier);
+ return WorkerRunnable::Cancel();
+ }
+};
+
+class DummyRunnable final
+ : public WorkerRunnable
+{
+public:
+ explicit
+ DummyRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+private:
+ ~DummyRunnable()
+ {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ MOZ_ASSERT_UNREACHABLE("Should never call Dispatch on this!");
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ MOZ_ASSERT_UNREACHABLE("Should never call Dispatch on this!");
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ // Do nothing.
+ return true;
+ }
+};
+
+PRThread*
+PRThreadFromThread(nsIThread* aThread)
+{
+ MOZ_ASSERT(aThread);
+
+ PRThread* result;
+ MOZ_ALWAYS_SUCCEEDS(aThread->GetPRThread(&result));
+ MOZ_ASSERT(result);
+
+ return result;
+}
+
+class SimpleWorkerHolder final : public WorkerHolder
+{
+public:
+ virtual bool Notify(Status aStatus) { return true; }
+};
+
+} /* anonymous namespace */
+
+NS_IMPL_ISUPPORTS_INHERITED0(MainThreadReleaseRunnable, Runnable)
+
+NS_IMPL_ISUPPORTS_INHERITED0(TopLevelWorkerFinishedRunnable, Runnable)
+
+TimerThreadEventTarget::TimerThreadEventTarget(WorkerPrivate* aWorkerPrivate,
+ WorkerRunnable* aWorkerRunnable)
+ : mWorkerPrivate(aWorkerPrivate), mWorkerRunnable(aWorkerRunnable)
+{
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aWorkerRunnable);
+}
+
+TimerThreadEventTarget::~TimerThreadEventTarget()
+{
+}
+
+NS_IMETHODIMP
+TimerThreadEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags)
+{
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+ return Dispatch(runnable.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+TimerThreadEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags)
+{
+ // This should only happen on the timer thread.
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aFlags == nsIEventTarget::DISPATCH_NORMAL);
+
+ RefPtr<TimerThreadEventTarget> kungFuDeathGrip = this;
+
+ // Run the runnable we're given now (should just call DummyCallback()),
+ // otherwise the timer thread will leak it... If we run this after
+ // dispatch running the event can race against resetting the timer.
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+ runnable->Run();
+
+ // This can fail if we're racing to terminate or cancel, should be handled
+ // by the terminate or cancel code.
+ mWorkerRunnable->Dispatch();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TimerThreadEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TimerThreadEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread)
+{
+ MOZ_ASSERT(aIsOnCurrentThread);
+
+ nsresult rv = mWorkerPrivate->IsOnCurrentThread(aIsOnCurrentThread);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(TimerThreadEventTarget, nsIEventTarget)
+
+WorkerLoadInfo::WorkerLoadInfo()
+ : mWindowID(UINT64_MAX)
+ , mServiceWorkerID(0)
+ , mReferrerPolicy(net::RP_Default)
+ , mFromWindow(false)
+ , mEvalAllowed(false)
+ , mReportCSPViolations(false)
+ , mXHRParamsAllowed(false)
+ , mPrincipalIsSystem(false)
+ , mStorageAllowed(false)
+ , mServiceWorkersTestingInWindow(false)
+{
+ MOZ_COUNT_CTOR(WorkerLoadInfo);
+}
+
+WorkerLoadInfo::~WorkerLoadInfo()
+{
+ MOZ_COUNT_DTOR(WorkerLoadInfo);
+}
+
+void
+WorkerLoadInfo::StealFrom(WorkerLoadInfo& aOther)
+{
+ MOZ_ASSERT(!mBaseURI);
+ aOther.mBaseURI.swap(mBaseURI);
+
+ MOZ_ASSERT(!mResolvedScriptURI);
+ aOther.mResolvedScriptURI.swap(mResolvedScriptURI);
+
+ MOZ_ASSERT(!mPrincipal);
+ aOther.mPrincipal.swap(mPrincipal);
+
+ MOZ_ASSERT(!mScriptContext);
+ aOther.mScriptContext.swap(mScriptContext);
+
+ MOZ_ASSERT(!mWindow);
+ aOther.mWindow.swap(mWindow);
+
+ MOZ_ASSERT(!mCSP);
+ aOther.mCSP.swap(mCSP);
+
+ MOZ_ASSERT(!mChannel);
+ aOther.mChannel.swap(mChannel);
+
+ MOZ_ASSERT(!mLoadGroup);
+ aOther.mLoadGroup.swap(mLoadGroup);
+
+ MOZ_ASSERT(!mLoadFailedAsyncRunnable);
+ aOther.mLoadFailedAsyncRunnable.swap(mLoadFailedAsyncRunnable);
+
+ MOZ_ASSERT(!mInterfaceRequestor);
+ aOther.mInterfaceRequestor.swap(mInterfaceRequestor);
+
+ MOZ_ASSERT(!mPrincipalInfo);
+ mPrincipalInfo = aOther.mPrincipalInfo.forget();
+
+ mDomain = aOther.mDomain;
+ mServiceWorkerCacheName = aOther.mServiceWorkerCacheName;
+ mWindowID = aOther.mWindowID;
+ mServiceWorkerID = aOther.mServiceWorkerID;
+ mReferrerPolicy = aOther.mReferrerPolicy;
+ mFromWindow = aOther.mFromWindow;
+ mEvalAllowed = aOther.mEvalAllowed;
+ mReportCSPViolations = aOther.mReportCSPViolations;
+ mXHRParamsAllowed = aOther.mXHRParamsAllowed;
+ mPrincipalIsSystem = aOther.mPrincipalIsSystem;
+ mStorageAllowed = aOther.mStorageAllowed;
+ mServiceWorkersTestingInWindow = aOther.mServiceWorkersTestingInWindow;
+ mOriginAttributes = aOther.mOriginAttributes;
+}
+
+template <class Derived>
+class WorkerPrivateParent<Derived>::EventTarget final
+ : public nsIEventTarget
+{
+ // This mutex protects mWorkerPrivate and must be acquired *before* the
+ // WorkerPrivate's mutex whenever they must both be held.
+ mozilla::Mutex mMutex;
+ WorkerPrivate* mWorkerPrivate;
+ nsIEventTarget* mWeakNestedEventTarget;
+ nsCOMPtr<nsIEventTarget> mNestedEventTarget;
+
+public:
+ explicit EventTarget(WorkerPrivate* aWorkerPrivate)
+ : mMutex("WorkerPrivateParent::EventTarget::mMutex"),
+ mWorkerPrivate(aWorkerPrivate), mWeakNestedEventTarget(nullptr)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ }
+
+ EventTarget(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aNestedEventTarget)
+ : mMutex("WorkerPrivateParent::EventTarget::mMutex"),
+ mWorkerPrivate(aWorkerPrivate), mWeakNestedEventTarget(aNestedEventTarget),
+ mNestedEventTarget(aNestedEventTarget)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aNestedEventTarget);
+ }
+
+ void
+ Disable()
+ {
+ nsCOMPtr<nsIEventTarget> nestedEventTarget;
+ {
+ MutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate = nullptr;
+ mNestedEventTarget.swap(nestedEventTarget);
+ }
+ }
+
+ nsIEventTarget*
+ GetWeakNestedEventTarget() const
+ {
+ MOZ_ASSERT(mWeakNestedEventTarget);
+ return mWeakNestedEventTarget;
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIEVENTTARGET
+
+private:
+ ~EventTarget()
+ { }
+};
+
+WorkerLoadInfo::
+InterfaceRequestor::InterfaceRequestor(nsIPrincipal* aPrincipal,
+ nsILoadGroup* aLoadGroup)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+
+ // Look for an existing LoadContext. This is optional and it's ok if
+ // we don't find one.
+ nsCOMPtr<nsILoadContext> baseContext;
+ if (aLoadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ callbacks->GetInterface(NS_GET_IID(nsILoadContext),
+ getter_AddRefs(baseContext));
+ }
+ mOuterRequestor = callbacks;
+ }
+
+ mLoadContext = new LoadContext(aPrincipal, baseContext);
+}
+
+void
+WorkerLoadInfo::
+InterfaceRequestor::MaybeAddTabChild(nsILoadGroup* aLoadGroup)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aLoadGroup) {
+ return;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (!callbacks) {
+ return;
+ }
+
+ nsCOMPtr<nsITabChild> tabChild;
+ callbacks->GetInterface(NS_GET_IID(nsITabChild), getter_AddRefs(tabChild));
+ if (!tabChild) {
+ return;
+ }
+
+ // Use weak references to the tab child. Holding a strong reference will
+ // not prevent an ActorDestroy() from being called on the TabChild.
+ // Therefore, we should let the TabChild destroy itself as soon as possible.
+ mTabChildList.AppendElement(do_GetWeakReference(tabChild));
+}
+
+NS_IMETHODIMP
+WorkerLoadInfo::
+InterfaceRequestor::GetInterface(const nsIID& aIID, void** aSink)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mLoadContext);
+
+ if (aIID.Equals(NS_GET_IID(nsILoadContext))) {
+ nsCOMPtr<nsILoadContext> ref = mLoadContext;
+ ref.forget(aSink);
+ return NS_OK;
+ }
+
+ // If we still have an active nsITabChild, then return it. Its possible,
+ // though, that all of the TabChild objects have been destroyed. In that
+ // case we return NS_NOINTERFACE.
+ if (aIID.Equals(NS_GET_IID(nsITabChild))) {
+ nsCOMPtr<nsITabChild> tabChild = GetAnyLiveTabChild();
+ if (!tabChild) {
+ return NS_NOINTERFACE;
+ }
+ tabChild.forget(aSink);
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) &&
+ mOuterRequestor) {
+ // If asked for the network intercept controller, ask the outer requestor,
+ // which could be the docshell.
+ return mOuterRequestor->GetInterface(aIID, aSink);
+ }
+
+ return NS_NOINTERFACE;
+}
+
+already_AddRefed<nsITabChild>
+WorkerLoadInfo::
+InterfaceRequestor::GetAnyLiveTabChild()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Search our list of known TabChild objects for one that still exists.
+ while (!mTabChildList.IsEmpty()) {
+ nsCOMPtr<nsITabChild> tabChild =
+ do_QueryReferent(mTabChildList.LastElement());
+
+ // Does this tab child still exist? If so, return it. We are done. If the
+ // PBrowser actor is no longer useful, don't bother returning this tab.
+ if (tabChild && !static_cast<TabChild*>(tabChild.get())->IsDestroyed()) {
+ return tabChild.forget();
+ }
+
+ // Otherwise remove the stale weak reference and check the next one
+ mTabChildList.RemoveElementAt(mTabChildList.Length() - 1);
+ }
+
+ return nullptr;
+}
+
+NS_IMPL_ADDREF(WorkerLoadInfo::InterfaceRequestor)
+NS_IMPL_RELEASE(WorkerLoadInfo::InterfaceRequestor)
+NS_IMPL_QUERY_INTERFACE(WorkerLoadInfo::InterfaceRequestor, nsIInterfaceRequestor)
+
+struct WorkerPrivate::TimeoutInfo
+{
+ TimeoutInfo()
+ : mId(0), mIsInterval(false), mCanceled(false)
+ {
+ MOZ_COUNT_CTOR(mozilla::dom::workers::WorkerPrivate::TimeoutInfo);
+ }
+
+ ~TimeoutInfo()
+ {
+ MOZ_COUNT_DTOR(mozilla::dom::workers::WorkerPrivate::TimeoutInfo);
+ }
+
+ bool operator==(const TimeoutInfo& aOther)
+ {
+ return mTargetTime == aOther.mTargetTime;
+ }
+
+ bool operator<(const TimeoutInfo& aOther)
+ {
+ return mTargetTime < aOther.mTargetTime;
+ }
+
+ nsCOMPtr<nsIScriptTimeoutHandler> mHandler;
+ mozilla::TimeStamp mTargetTime;
+ mozilla::TimeDuration mInterval;
+ int32_t mId;
+ bool mIsInterval;
+ bool mCanceled;
+};
+
+class WorkerJSContextStats final : public JS::RuntimeStats
+{
+ const nsCString mRtPath;
+
+public:
+ explicit WorkerJSContextStats(const nsACString& aRtPath)
+ : JS::RuntimeStats(JsWorkerMallocSizeOf), mRtPath(aRtPath)
+ { }
+
+ ~WorkerJSContextStats()
+ {
+ for (size_t i = 0; i != zoneStatsVector.length(); i++) {
+ delete static_cast<xpc::ZoneStatsExtras*>(zoneStatsVector[i].extra);
+ }
+
+ for (size_t i = 0; i != compartmentStatsVector.length(); i++) {
+ delete static_cast<xpc::CompartmentStatsExtras*>(compartmentStatsVector[i].extra);
+ }
+ }
+
+ const nsCString& Path() const
+ {
+ return mRtPath;
+ }
+
+ virtual void
+ initExtraZoneStats(JS::Zone* aZone,
+ JS::ZoneStats* aZoneStats)
+ override
+ {
+ MOZ_ASSERT(!aZoneStats->extra);
+
+ // ReportJSRuntimeExplicitTreeStats expects that
+ // aZoneStats->extra is a xpc::ZoneStatsExtras pointer.
+ xpc::ZoneStatsExtras* extras = new xpc::ZoneStatsExtras;
+ extras->pathPrefix = mRtPath;
+ extras->pathPrefix += nsPrintfCString("zone(0x%p)/", (void *)aZone);
+
+ MOZ_ASSERT(StartsWithExplicit(extras->pathPrefix));
+
+ aZoneStats->extra = extras;
+ }
+
+ virtual void
+ initExtraCompartmentStats(JSCompartment* aCompartment,
+ JS::CompartmentStats* aCompartmentStats)
+ override
+ {
+ MOZ_ASSERT(!aCompartmentStats->extra);
+
+ // ReportJSRuntimeExplicitTreeStats expects that
+ // aCompartmentStats->extra is a xpc::CompartmentStatsExtras pointer.
+ xpc::CompartmentStatsExtras* extras = new xpc::CompartmentStatsExtras;
+
+ // This is the |jsPathPrefix|. Each worker has exactly two compartments:
+ // one for atoms, and one for everything else.
+ extras->jsPathPrefix.Assign(mRtPath);
+ extras->jsPathPrefix += nsPrintfCString("zone(0x%p)/",
+ (void *)js::GetCompartmentZone(aCompartment));
+ extras->jsPathPrefix += js::IsAtomsCompartment(aCompartment)
+ ? NS_LITERAL_CSTRING("compartment(web-worker-atoms)/")
+ : NS_LITERAL_CSTRING("compartment(web-worker)/");
+
+ // This should never be used when reporting with workers (hence the "?!").
+ extras->domPathPrefix.AssignLiteral("explicit/workers/?!/");
+
+ MOZ_ASSERT(StartsWithExplicit(extras->jsPathPrefix));
+ MOZ_ASSERT(StartsWithExplicit(extras->domPathPrefix));
+
+ extras->location = nullptr;
+
+ aCompartmentStats->extra = extras;
+ }
+};
+
+class WorkerPrivate::MemoryReporter final : public nsIMemoryReporter
+{
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ friend class WorkerPrivate;
+
+ SharedMutex mMutex;
+ WorkerPrivate* mWorkerPrivate;
+ bool mAlreadyMappedToAddon;
+
+public:
+ explicit MemoryReporter(WorkerPrivate* aWorkerPrivate)
+ : mMutex(aWorkerPrivate->mMutex), mWorkerPrivate(aWorkerPrivate),
+ mAlreadyMappedToAddon(false)
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override;
+
+private:
+ class FinishCollectRunnable;
+
+ class CollectReportsRunnable final : public MainThreadWorkerControlRunnable
+ {
+ RefPtr<FinishCollectRunnable> mFinishCollectRunnable;
+ const bool mAnonymize;
+
+ public:
+ CollectReportsRunnable(
+ WorkerPrivate* aWorkerPrivate,
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aHandlerData,
+ bool aAnonymize,
+ const nsACString& aPath);
+
+ private:
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
+
+ ~CollectReportsRunnable()
+ {
+ if (NS_IsMainThread()) {
+ mFinishCollectRunnable->Run();
+ return;
+ }
+
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ MOZ_ALWAYS_SUCCEEDS(
+ workerPrivate->DispatchToMainThread(mFinishCollectRunnable.forget()));
+ }
+ };
+
+ class FinishCollectRunnable final : public Runnable
+ {
+ nsCOMPtr<nsIHandleReportCallback> mHandleReport;
+ nsCOMPtr<nsISupports> mHandlerData;
+ const bool mAnonymize;
+ bool mSuccess;
+
+ public:
+ WorkerJSContextStats mCxStats;
+
+ explicit FinishCollectRunnable(
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aHandlerData,
+ bool aAnonymize,
+ const nsACString& aPath);
+
+ NS_IMETHOD Run() override;
+
+ void SetSuccess(bool success)
+ {
+ mSuccess = success;
+ }
+
+ private:
+ ~FinishCollectRunnable()
+ {
+ // mHandleReport and mHandlerData are released on the main thread.
+ AssertIsOnMainThread();
+ }
+
+ FinishCollectRunnable(const FinishCollectRunnable&) = delete;
+ FinishCollectRunnable& operator=(const FinishCollectRunnable&) = delete;
+ FinishCollectRunnable& operator=(const FinishCollectRunnable&&) = delete;
+ };
+
+ ~MemoryReporter()
+ {
+ }
+
+ void
+ Disable()
+ {
+ // Called from WorkerPrivate::DisableMemoryReporter.
+ mMutex.AssertCurrentThreadOwns();
+
+ NS_ASSERTION(mWorkerPrivate, "Disabled more than once!");
+ mWorkerPrivate = nullptr;
+ }
+
+ // Only call this from the main thread and under mMutex lock.
+ void
+ TryToMapAddon(nsACString &path);
+};
+
+NS_IMPL_ISUPPORTS(WorkerPrivate::MemoryReporter, nsIMemoryReporter)
+
+NS_IMETHODIMP
+WorkerPrivate::MemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData,
+ bool aAnonymize)
+{
+ AssertIsOnMainThread();
+
+ RefPtr<CollectReportsRunnable> runnable;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (!mWorkerPrivate) {
+ // This will effectively report 0 memory.
+ nsCOMPtr<nsIMemoryReporterManager> manager =
+ do_GetService("@mozilla.org/memory-reporter-manager;1");
+ if (manager) {
+ manager->EndReport();
+ }
+ return NS_OK;
+ }
+
+ nsAutoCString path;
+ path.AppendLiteral("explicit/workers/workers(");
+ if (aAnonymize && !mWorkerPrivate->Domain().IsEmpty()) {
+ path.AppendLiteral("<anonymized-domain>)/worker(<anonymized-url>");
+ } else {
+ nsAutoCString escapedDomain(mWorkerPrivate->Domain());
+ if (escapedDomain.IsEmpty()) {
+ escapedDomain += "chrome";
+ } else {
+ escapedDomain.ReplaceChar('/', '\\');
+ }
+ path.Append(escapedDomain);
+ path.AppendLiteral(")/worker(");
+ NS_ConvertUTF16toUTF8 escapedURL(mWorkerPrivate->ScriptURL());
+ escapedURL.ReplaceChar('/', '\\');
+ path.Append(escapedURL);
+ }
+ path.AppendPrintf(", 0x%p)/", static_cast<void*>(mWorkerPrivate));
+
+ TryToMapAddon(path);
+
+ runnable =
+ new CollectReportsRunnable(mWorkerPrivate, aHandleReport, aData, aAnonymize, path);
+ }
+
+ if (!runnable->Dispatch()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+void
+WorkerPrivate::MemoryReporter::TryToMapAddon(nsACString &path)
+{
+ AssertIsOnMainThread();
+ mMutex.AssertCurrentThreadOwns();
+
+ if (mAlreadyMappedToAddon || !mWorkerPrivate) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> scriptURI;
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(scriptURI),
+ mWorkerPrivate->ScriptURL()))) {
+ return;
+ }
+
+ mAlreadyMappedToAddon = true;
+
+ if (!XRE_IsParentProcess()) {
+ // Only try to access the service from the main process.
+ return;
+ }
+
+ nsAutoCString addonId;
+ bool ok;
+ nsCOMPtr<amIAddonManager> addonManager =
+ do_GetService("@mozilla.org/addons/integration;1");
+
+ if (!addonManager ||
+ NS_FAILED(addonManager->MapURIToAddonID(scriptURI, addonId, &ok)) ||
+ !ok) {
+ return;
+ }
+
+ static const size_t explicitLength = strlen("explicit/");
+ addonId.Insert(NS_LITERAL_CSTRING("add-ons/"), 0);
+ addonId += "/";
+ path.Insert(addonId, explicitLength);
+}
+
+WorkerPrivate::MemoryReporter::CollectReportsRunnable::CollectReportsRunnable(
+ WorkerPrivate* aWorkerPrivate,
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aHandlerData,
+ bool aAnonymize,
+ const nsACString& aPath)
+ : MainThreadWorkerControlRunnable(aWorkerPrivate),
+ mFinishCollectRunnable(
+ new FinishCollectRunnable(aHandleReport, aHandlerData, aAnonymize, aPath)),
+ mAnonymize(aAnonymize)
+{ }
+
+bool
+WorkerPrivate::MemoryReporter::CollectReportsRunnable::WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate)
+{
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ mFinishCollectRunnable->SetSuccess(
+ aWorkerPrivate->CollectRuntimeStats(&mFinishCollectRunnable->mCxStats, mAnonymize));
+
+ return true;
+}
+
+WorkerPrivate::MemoryReporter::FinishCollectRunnable::FinishCollectRunnable(
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aHandlerData,
+ bool aAnonymize,
+ const nsACString& aPath)
+ : mHandleReport(aHandleReport),
+ mHandlerData(aHandlerData),
+ mAnonymize(aAnonymize),
+ mSuccess(false),
+ mCxStats(aPath)
+{ }
+
+NS_IMETHODIMP
+WorkerPrivate::MemoryReporter::FinishCollectRunnable::Run()
+{
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIMemoryReporterManager> manager =
+ do_GetService("@mozilla.org/memory-reporter-manager;1");
+
+ if (!manager)
+ return NS_OK;
+
+ if (mSuccess) {
+ xpc::ReportJSRuntimeExplicitTreeStats(mCxStats, mCxStats.Path(),
+ mHandleReport, mHandlerData,
+ mAnonymize);
+ }
+
+ manager->EndReport();
+
+ return NS_OK;
+}
+
+WorkerPrivate::SyncLoopInfo::SyncLoopInfo(EventTarget* aEventTarget)
+: mEventTarget(aEventTarget), mCompleted(false), mResult(false)
+#ifdef DEBUG
+ , mHasRun(false)
+#endif
+{
+}
+
+template <class Derived>
+nsIDocument*
+WorkerPrivateParent<Derived>::GetDocument() const
+{
+ AssertIsOnMainThread();
+ if (mLoadInfo.mWindow) {
+ return mLoadInfo.mWindow->GetExtantDoc();
+ }
+ // if we don't have a document, we should query the document
+ // from the parent in case of a nested worker
+ WorkerPrivate* parent = mParent;
+ while (parent) {
+ if (parent->mLoadInfo.mWindow) {
+ return parent->mLoadInfo.mWindow->GetExtantDoc();
+ }
+ parent = parent->GetParent();
+ }
+ // couldn't query a document, give up and return nullptr
+ return nullptr;
+}
+
+
+// Can't use NS_IMPL_CYCLE_COLLECTION_CLASS(WorkerPrivateParent) because of the
+// templates.
+template <class Derived>
+typename WorkerPrivateParent<Derived>::cycleCollection
+ WorkerPrivateParent<Derived>::_cycleCollectorGlobal =
+ WorkerPrivateParent<Derived>::cycleCollection();
+
+template <class Derived>
+WorkerPrivateParent<Derived>::WorkerPrivateParent(
+ WorkerPrivate* aParent,
+ const nsAString& aScriptURL,
+ bool aIsChromeWorker,
+ WorkerType aWorkerType,
+ const nsACString& aWorkerName,
+ WorkerLoadInfo& aLoadInfo)
+: mMutex("WorkerPrivateParent Mutex"),
+ mCondVar(mMutex, "WorkerPrivateParent CondVar"),
+ mParent(aParent), mScriptURL(aScriptURL),
+ mWorkerName(aWorkerName), mLoadingWorkerScript(false),
+ mBusyCount(0), mParentWindowPausedDepth(0), mParentStatus(Pending),
+ mParentFrozen(false), mIsChromeWorker(aIsChromeWorker),
+ mMainThreadObjectsForgotten(false), mIsSecureContext(false),
+ mWorkerType(aWorkerType),
+ mCreationTimeStamp(TimeStamp::Now()),
+ mCreationTimeHighRes((double)PR_Now() / PR_USEC_PER_MSEC)
+{
+ MOZ_ASSERT_IF(!IsDedicatedWorker(),
+ !aWorkerName.IsVoid() && NS_IsMainThread());
+ MOZ_ASSERT_IF(IsDedicatedWorker(), aWorkerName.IsEmpty());
+
+ if (aLoadInfo.mWindow) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aLoadInfo.mWindow->IsInnerWindow(),
+ "Should have inner window here!");
+ BindToOwner(aLoadInfo.mWindow);
+ }
+
+ mLoadInfo.StealFrom(aLoadInfo);
+
+ if (aParent) {
+ aParent->AssertIsOnWorkerThread();
+
+ // Note that this copies our parent's secure context state into mJSSettings.
+ aParent->CopyJSSettings(mJSSettings);
+
+ // And manually set our mIsSecureContext, though it's not really relevant to
+ // dedicated workers...
+ mIsSecureContext = aParent->IsSecureContext();
+ MOZ_ASSERT_IF(mIsChromeWorker, mIsSecureContext);
+
+ MOZ_ASSERT(IsDedicatedWorker());
+ mNowBaseTimeStamp = aParent->NowBaseTimeStamp();
+ mNowBaseTimeHighRes = aParent->NowBaseTime();
+
+ if (aParent->mParentFrozen) {
+ Freeze(nullptr);
+ }
+ }
+ else {
+ AssertIsOnMainThread();
+
+ RuntimeService::GetDefaultJSSettings(mJSSettings);
+
+ // Our secure context state depends on the kind of worker we have.
+ if (UsesSystemPrincipal() || IsServiceWorker()) {
+ mIsSecureContext = true;
+ } else if (mLoadInfo.mWindow) {
+ // Shared and dedicated workers both inherit the loading window's secure
+ // context state. Shared workers then prevent windows with a different
+ // secure context state from attaching to them.
+ mIsSecureContext = mLoadInfo.mWindow->IsSecureContext();
+ } else {
+ MOZ_ASSERT_UNREACHABLE("non-chrome worker that is not a service worker "
+ "that has no parent and no associated window");
+ }
+
+ if (mIsSecureContext) {
+ mJSSettings.chrome.compartmentOptions
+ .creationOptions().setSecureContext(true);
+ mJSSettings.content.compartmentOptions
+ .creationOptions().setSecureContext(true);
+ }
+
+ if (IsDedicatedWorker() && mLoadInfo.mWindow &&
+ mLoadInfo.mWindow->GetPerformance()) {
+ mNowBaseTimeStamp = mLoadInfo.mWindow->GetPerformance()->GetDOMTiming()->
+ GetNavigationStartTimeStamp();
+ mNowBaseTimeHighRes =
+ mLoadInfo.mWindow->GetPerformance()->GetDOMTiming()->
+ GetNavigationStartHighRes();
+ } else {
+ mNowBaseTimeStamp = CreationTimeStamp();
+ mNowBaseTimeHighRes = CreationTime();
+ }
+
+ // Our parent can get suspended after it initiates the async creation
+ // of a new worker thread. In this case suspend the new worker as well.
+ if (mLoadInfo.mWindow && mLoadInfo.mWindow->IsSuspended()) {
+ ParentWindowPaused();
+ }
+
+ if (mLoadInfo.mWindow && mLoadInfo.mWindow->IsFrozen()) {
+ Freeze(mLoadInfo.mWindow);
+ }
+ }
+}
+
+template <class Derived>
+WorkerPrivateParent<Derived>::~WorkerPrivateParent()
+{
+ DropJSObjects(this);
+}
+
+template <class Derived>
+JSObject*
+WorkerPrivateParent<Derived>::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ MOZ_ASSERT(!IsSharedWorker(),
+ "We should never wrap a WorkerPrivate for a SharedWorker");
+
+ AssertIsOnParentThread();
+
+ // XXXkhuey this should not need to be rooted, the analysis is dumb.
+ // See bug 980181.
+ JS::Rooted<JSObject*> wrapper(aCx,
+ WorkerBinding::Wrap(aCx, ParentAsWorkerPrivate(), aGivenProto));
+ if (wrapper) {
+ MOZ_ALWAYS_TRUE(TryPreserveWrapper(wrapper));
+ }
+
+ return wrapper;
+}
+
+template <class Derived>
+nsresult
+WorkerPrivateParent<Derived>::DispatchPrivate(already_AddRefed<WorkerRunnable> aRunnable,
+ nsIEventTarget* aSyncLoopTarget)
+{
+ // May be called on any thread!
+ RefPtr<WorkerRunnable> runnable(aRunnable);
+
+ WorkerPrivate* self = ParentAsWorkerPrivate();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT_IF(aSyncLoopTarget, self->mThread);
+
+ if (!self->mThread) {
+ if (ParentStatus() == Pending || self->mStatus == Pending) {
+ mPreStartRunnables.AppendElement(runnable);
+ return NS_OK;
+ }
+
+ NS_WARNING("Using a worker event target after the thread has already"
+ "been released!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (self->mStatus == Dead ||
+ (!aSyncLoopTarget && ParentStatus() > Running)) {
+ NS_WARNING("A runnable was posted to a worker that is already shutting "
+ "down!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv;
+ if (aSyncLoopTarget) {
+ rv = aSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ } else {
+ rv = self->mThread->DispatchAnyThread(WorkerThreadFriendKey(), runnable.forget());
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mCondVar.Notify();
+ }
+
+ return NS_OK;
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::EnableDebugger()
+{
+ AssertIsOnParentThread();
+
+ WorkerPrivate* self = ParentAsWorkerPrivate();
+
+ if (NS_FAILED(RegisterWorkerDebugger(self))) {
+ NS_WARNING("Failed to register worker debugger!");
+ return;
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::DisableDebugger()
+{
+ AssertIsOnParentThread();
+
+ WorkerPrivate* self = ParentAsWorkerPrivate();
+
+ if (NS_FAILED(UnregisterWorkerDebugger(self))) {
+ NS_WARNING("Failed to unregister worker debugger!");
+ }
+}
+
+template <class Derived>
+nsresult
+WorkerPrivateParent<Derived>::DispatchControlRunnable(
+ already_AddRefed<WorkerControlRunnable> aWorkerControlRunnable)
+{
+ // May be called on any thread!
+ RefPtr<WorkerControlRunnable> runnable(aWorkerControlRunnable);
+ MOZ_ASSERT(runnable);
+
+ WorkerPrivate* self = ParentAsWorkerPrivate();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (self->mStatus == Dead) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Transfer ownership to the control queue.
+ self->mControlQueue.Push(runnable.forget().take());
+
+ if (JSContext* cx = self->mJSContext) {
+ MOZ_ASSERT(self->mThread);
+ JS_RequestInterruptCallback(cx);
+ }
+
+ mCondVar.Notify();
+ }
+
+ return NS_OK;
+}
+
+template <class Derived>
+nsresult
+WorkerPrivateParent<Derived>::DispatchDebuggerRunnable(
+ already_AddRefed<WorkerRunnable> aDebuggerRunnable)
+{
+ // May be called on any thread!
+
+ RefPtr<WorkerRunnable> runnable(aDebuggerRunnable);
+
+ MOZ_ASSERT(runnable);
+
+ WorkerPrivate* self = ParentAsWorkerPrivate();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (self->mStatus == Dead) {
+ NS_WARNING("A debugger runnable was posted to a worker that is already "
+ "shutting down!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Transfer ownership to the debugger queue.
+ self->mDebuggerQueue.Push(runnable.forget().take());
+
+ mCondVar.Notify();
+ }
+
+ return NS_OK;
+}
+
+template <class Derived>
+already_AddRefed<WorkerRunnable>
+WorkerPrivateParent<Derived>::MaybeWrapAsWorkerRunnable(already_AddRefed<nsIRunnable> aRunnable)
+{
+ // May be called on any thread!
+
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+ MOZ_ASSERT(runnable);
+
+ RefPtr<WorkerRunnable> workerRunnable =
+ WorkerRunnable::FromRunnable(runnable);
+ if (workerRunnable) {
+ return workerRunnable.forget();
+ }
+
+ nsCOMPtr<nsICancelableRunnable> cancelable = do_QueryInterface(runnable);
+ if (!cancelable) {
+ MOZ_CRASH("All runnables destined for a worker thread must be cancelable!");
+ }
+
+ workerRunnable =
+ new ExternalRunnableWrapper(ParentAsWorkerPrivate(), runnable);
+ return workerRunnable.forget();
+}
+
+template <class Derived>
+already_AddRefed<nsIEventTarget>
+WorkerPrivateParent<Derived>::GetEventTarget()
+{
+ WorkerPrivate* self = ParentAsWorkerPrivate();
+
+ nsCOMPtr<nsIEventTarget> target;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (!mEventTarget &&
+ ParentStatus() <= Running &&
+ self->mStatus <= Running) {
+ mEventTarget = new EventTarget(self);
+ }
+
+ target = mEventTarget;
+ }
+
+ NS_WARNING_ASSERTION(
+ target,
+ "Requested event target for a worker that is already shutting down!");
+
+ return target.forget();
+}
+
+template <class Derived>
+bool
+WorkerPrivateParent<Derived>::Start()
+{
+ // May be called on any thread!
+ {
+ MutexAutoLock lock(mMutex);
+
+ NS_ASSERTION(mParentStatus != Running, "How can this be?!");
+
+ if (mParentStatus == Pending) {
+ mParentStatus = Running;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// aCx is null when called from the finalizer
+template <class Derived>
+bool
+WorkerPrivateParent<Derived>::NotifyPrivate(Status aStatus)
+{
+ AssertIsOnParentThread();
+
+ bool pending;
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mParentStatus >= aStatus) {
+ return true;
+ }
+
+ pending = mParentStatus == Pending;
+ mParentStatus = aStatus;
+ }
+
+ if (IsSharedWorker()) {
+ RuntimeService* runtime = RuntimeService::GetService();
+ MOZ_ASSERT(runtime);
+
+ runtime->ForgetSharedWorker(ParentAsWorkerPrivate());
+ }
+
+ if (pending) {
+ WorkerPrivate* self = ParentAsWorkerPrivate();
+
+#ifdef DEBUG
+ {
+ // Fake a thread here just so that our assertions don't go off for no
+ // reason.
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+
+ MOZ_ASSERT(!self->mPRThread);
+ self->mPRThread = PRThreadFromThread(currentThread);
+ MOZ_ASSERT(self->mPRThread);
+ }
+#endif
+
+ // Worker never got a chance to run, go ahead and delete it.
+ self->ScheduleDeletion(WorkerPrivate::WorkerNeverRan);
+ return true;
+ }
+
+ NS_ASSERTION(aStatus != Terminating || mQueuedRunnables.IsEmpty(),
+ "Shouldn't have anything queued!");
+
+ // Anything queued will be discarded.
+ mQueuedRunnables.Clear();
+
+ RefPtr<NotifyRunnable> runnable =
+ new NotifyRunnable(ParentAsWorkerPrivate(), aStatus);
+ return runnable->Dispatch();
+}
+
+template <class Derived>
+bool
+WorkerPrivateParent<Derived>::Freeze(nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnParentThread();
+
+ // Shared workers are only frozen if all of their owning documents are
+ // frozen. It can happen that mSharedWorkers is empty but this thread has
+ // not been unregistered yet.
+ if ((IsSharedWorker() || IsServiceWorker()) && !mSharedWorkers.IsEmpty()) {
+ AssertIsOnMainThread();
+
+ bool allFrozen = true;
+
+ for (uint32_t i = 0; i < mSharedWorkers.Length(); ++i) {
+ if (aWindow && mSharedWorkers[i]->GetOwner() == aWindow) {
+ // Calling Freeze() may change the refcount, ensure that the worker
+ // outlives this call.
+ RefPtr<SharedWorker> kungFuDeathGrip = mSharedWorkers[i];
+
+ kungFuDeathGrip->Freeze();
+ } else {
+ MOZ_ASSERT_IF(mSharedWorkers[i]->GetOwner() && aWindow,
+ !SameCOMIdentity(mSharedWorkers[i]->GetOwner(),
+ aWindow));
+ if (!mSharedWorkers[i]->IsFrozen()) {
+ allFrozen = false;
+ }
+ }
+ }
+
+ if (!allFrozen || mParentFrozen) {
+ return true;
+ }
+ }
+
+ mParentFrozen = true;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mParentStatus >= Terminating) {
+ return true;
+ }
+ }
+
+ DisableDebugger();
+
+ RefPtr<FreezeRunnable> runnable =
+ new FreezeRunnable(ParentAsWorkerPrivate());
+ if (!runnable->Dispatch()) {
+ return false;
+ }
+
+ return true;
+}
+
+template <class Derived>
+bool
+WorkerPrivateParent<Derived>::Thaw(nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnParentThread();
+
+ MOZ_ASSERT(mParentFrozen);
+
+ // Shared workers are resumed if any of their owning documents are thawed.
+ // It can happen that mSharedWorkers is empty but this thread has not been
+ // unregistered yet.
+ if ((IsSharedWorker() || IsServiceWorker()) && !mSharedWorkers.IsEmpty()) {
+ AssertIsOnMainThread();
+
+ bool anyRunning = false;
+
+ for (uint32_t i = 0; i < mSharedWorkers.Length(); ++i) {
+ if (aWindow && mSharedWorkers[i]->GetOwner() == aWindow) {
+ // Calling Thaw() may change the refcount, ensure that the worker
+ // outlives this call.
+ RefPtr<SharedWorker> kungFuDeathGrip = mSharedWorkers[i];
+
+ kungFuDeathGrip->Thaw();
+ anyRunning = true;
+ } else {
+ MOZ_ASSERT_IF(mSharedWorkers[i]->GetOwner() && aWindow,
+ !SameCOMIdentity(mSharedWorkers[i]->GetOwner(),
+ aWindow));
+ if (!mSharedWorkers[i]->IsFrozen()) {
+ anyRunning = true;
+ }
+ }
+ }
+
+ if (!anyRunning || !mParentFrozen) {
+ return true;
+ }
+ }
+
+ MOZ_ASSERT(mParentFrozen);
+
+ mParentFrozen = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mParentStatus >= Terminating) {
+ return true;
+ }
+ }
+
+ EnableDebugger();
+
+ // Execute queued runnables before waking up the worker, otherwise the worker
+ // could post new messages before we run those that have been queued.
+ if (!IsParentWindowPaused() && !mQueuedRunnables.IsEmpty()) {
+ MOZ_ASSERT(IsDedicatedWorker());
+
+ nsTArray<nsCOMPtr<nsIRunnable>> runnables;
+ mQueuedRunnables.SwapElements(runnables);
+
+ for (uint32_t index = 0; index < runnables.Length(); index++) {
+ runnables[index]->Run();
+ }
+ }
+
+ RefPtr<ThawRunnable> runnable =
+ new ThawRunnable(ParentAsWorkerPrivate());
+ if (!runnable->Dispatch()) {
+ return false;
+ }
+
+ return true;
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::ParentWindowPaused()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT_IF(IsDedicatedWorker(), mParentWindowPausedDepth == 0);
+ mParentWindowPausedDepth += 1;
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::ParentWindowResumed()
+{
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(mParentWindowPausedDepth > 0);
+ MOZ_ASSERT_IF(IsDedicatedWorker(), mParentWindowPausedDepth == 1);
+ mParentWindowPausedDepth -= 1;
+ if (mParentWindowPausedDepth > 0) {
+ return;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mParentStatus >= Terminating) {
+ return;
+ }
+ }
+
+ // Execute queued runnables before waking up, otherwise the worker could post
+ // new messages before we run those that have been queued.
+ if (!IsFrozen() && !mQueuedRunnables.IsEmpty()) {
+ MOZ_ASSERT(IsDedicatedWorker());
+
+ nsTArray<nsCOMPtr<nsIRunnable>> runnables;
+ mQueuedRunnables.SwapElements(runnables);
+
+ for (uint32_t index = 0; index < runnables.Length(); index++) {
+ runnables[index]->Run();
+ }
+ }
+}
+
+template <class Derived>
+bool
+WorkerPrivateParent<Derived>::Close()
+{
+ AssertIsOnParentThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mParentStatus < Closing) {
+ mParentStatus = Closing;
+ }
+ }
+
+ return true;
+}
+
+template <class Derived>
+bool
+WorkerPrivateParent<Derived>::ModifyBusyCount(bool aIncrease)
+{
+ AssertIsOnParentThread();
+
+ NS_ASSERTION(aIncrease || mBusyCount, "Mismatched busy count mods!");
+
+ if (aIncrease) {
+ mBusyCount++;
+ return true;
+ }
+
+ if (--mBusyCount == 0) {
+
+ bool shouldCancel;
+ {
+ MutexAutoLock lock(mMutex);
+ shouldCancel = mParentStatus == Terminating;
+ }
+
+ if (shouldCancel && !Cancel()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::ForgetOverridenLoadGroup(
+ nsCOMPtr<nsILoadGroup>& aLoadGroupOut)
+{
+ AssertIsOnParentThread();
+
+ // If we're not overriden, then do nothing here. Let the load group get
+ // handled in ForgetMainThreadObjects().
+ if (!mLoadInfo.mInterfaceRequestor) {
+ return;
+ }
+
+ mLoadInfo.mLoadGroup.swap(aLoadGroupOut);
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::ForgetMainThreadObjects(
+ nsTArray<nsCOMPtr<nsISupports> >& aDoomed)
+{
+ AssertIsOnParentThread();
+ MOZ_ASSERT(!mMainThreadObjectsForgotten);
+
+ static const uint32_t kDoomedCount = 10;
+
+ aDoomed.SetCapacity(kDoomedCount);
+
+ SwapToISupportsArray(mLoadInfo.mWindow, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mScriptContext, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mBaseURI, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mResolvedScriptURI, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mPrincipal, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mChannel, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mCSP, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mLoadGroup, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mLoadFailedAsyncRunnable, aDoomed);
+ SwapToISupportsArray(mLoadInfo.mInterfaceRequestor, aDoomed);
+ // Before adding anything here update kDoomedCount above!
+
+ MOZ_ASSERT(aDoomed.Length() == kDoomedCount);
+
+ mMainThreadObjectsForgotten = true;
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::PostMessageInternal(
+ JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ UniquePtr<ServiceWorkerClientInfo>&& aClientInfo,
+ PromiseNativeHandler* aHandler,
+ ErrorResult& aRv)
+{
+ AssertIsOnParentThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mParentStatus > Running) {
+ return;
+ }
+ }
+
+ JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
+ if (aTransferable.WasPassed()) {
+ const Sequence<JS::Value>& realTransferable = aTransferable.Value();
+
+ // The input sequence only comes from the generated bindings code, which
+ // ensures it is rooted.
+ JS::HandleValueArray elements =
+ JS::HandleValueArray::fromMarkedLocation(realTransferable.Length(),
+ realTransferable.Elements());
+
+ JSObject* array =
+ JS_NewArrayObject(aCx, elements);
+ if (!array) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ transferable.setObject(*array);
+ }
+
+ RefPtr<MessageEventRunnable> runnable =
+ new MessageEventRunnable(ParentAsWorkerPrivate(),
+ WorkerRunnable::WorkerThreadModifyBusyCount);
+
+ UniquePtr<AbstractTimelineMarker> start;
+ UniquePtr<AbstractTimelineMarker> end;
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ bool isTimelineRecording = timelines && !timelines->IsEmpty();
+
+ if (isTimelineRecording) {
+ start = MakeUnique<WorkerTimelineMarker>(NS_IsMainThread()
+ ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+ MarkerTracingType::START);
+ }
+
+ runnable->Write(aCx, aMessage, transferable, JS::CloneDataPolicy(), aRv);
+
+ if (isTimelineRecording) {
+ end = MakeUnique<WorkerTimelineMarker>(NS_IsMainThread()
+ ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+ MarkerTracingType::END);
+ timelines->AddMarkerForAllObservedDocShells(start);
+ timelines->AddMarkerForAllObservedDocShells(end);
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ runnable->SetServiceWorkerData(Move(aClientInfo), aHandler);
+
+ if (!runnable->Dispatch()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::PostMessage(
+ JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv)
+{
+ PostMessageInternal(aCx, aMessage, aTransferable, nullptr, nullptr, aRv);
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::PostMessageToServiceWorker(
+ JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ UniquePtr<ServiceWorkerClientInfo>&& aClientInfo,
+ PromiseNativeHandler* aHandler,
+ ErrorResult& aRv)
+{
+ AssertIsOnMainThread();
+ PostMessageInternal(aCx, aMessage, aTransferable, Move(aClientInfo),
+ aHandler, aRv);
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::UpdateContextOptions(
+ const JS::ContextOptions& aContextOptions)
+{
+ AssertIsOnParentThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+ mJSSettings.contextOptions = aContextOptions;
+ }
+
+ RefPtr<UpdateContextOptionsRunnable> runnable =
+ new UpdateContextOptionsRunnable(ParentAsWorkerPrivate(), aContextOptions);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to update worker context options!");
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::UpdatePreference(WorkerPreference aPref, bool aValue)
+{
+ AssertIsOnParentThread();
+ MOZ_ASSERT(aPref >= 0 && aPref < WORKERPREF_COUNT);
+
+ RefPtr<UpdatePreferenceRunnable> runnable =
+ new UpdatePreferenceRunnable(ParentAsWorkerPrivate(), aPref, aValue);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to update worker preferences!");
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::UpdateLanguages(const nsTArray<nsString>& aLanguages)
+{
+ AssertIsOnParentThread();
+
+ RefPtr<UpdateLanguagesRunnable> runnable =
+ new UpdateLanguagesRunnable(ParentAsWorkerPrivate(), aLanguages);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to update worker languages!");
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::UpdateJSWorkerMemoryParameter(JSGCParamKey aKey,
+ uint32_t aValue)
+{
+ AssertIsOnParentThread();
+
+ bool found = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+ found = mJSSettings.ApplyGCSetting(aKey, aValue);
+ }
+
+ if (found) {
+ RefPtr<UpdateJSWorkerMemoryParameterRunnable> runnable =
+ new UpdateJSWorkerMemoryParameterRunnable(ParentAsWorkerPrivate(), aKey,
+ aValue);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to update memory parameter!");
+ }
+ }
+}
+
+#ifdef JS_GC_ZEAL
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::UpdateGCZeal(uint8_t aGCZeal, uint32_t aFrequency)
+{
+ AssertIsOnParentThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+ mJSSettings.gcZeal = aGCZeal;
+ mJSSettings.gcZealFrequency = aFrequency;
+ }
+
+ RefPtr<UpdateGCZealRunnable> runnable =
+ new UpdateGCZealRunnable(ParentAsWorkerPrivate(), aGCZeal, aFrequency);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to update worker gczeal!");
+ }
+}
+#endif
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::GarbageCollect(bool aShrinking)
+{
+ AssertIsOnParentThread();
+
+ RefPtr<GarbageCollectRunnable> runnable =
+ new GarbageCollectRunnable(ParentAsWorkerPrivate(), aShrinking,
+ /* collectChildren = */ true);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to GC worker!");
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::CycleCollect(bool aDummy)
+{
+ AssertIsOnParentThread();
+
+ RefPtr<CycleCollectRunnable> runnable =
+ new CycleCollectRunnable(ParentAsWorkerPrivate(),
+ /* collectChildren = */ true);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to CC worker!");
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::OfflineStatusChangeEvent(bool aIsOffline)
+{
+ AssertIsOnParentThread();
+
+ RefPtr<OfflineStatusChangeRunnable> runnable =
+ new OfflineStatusChangeRunnable(ParentAsWorkerPrivate(), aIsOffline);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to dispatch offline status change event!");
+ }
+}
+
+void
+WorkerPrivate::OfflineStatusChangeEventInternal(bool aIsOffline)
+{
+ AssertIsOnWorkerThread();
+
+ // The worker is already in this state. No need to dispatch an event.
+ if (mOnLine == !aIsOffline) {
+ return;
+ }
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); ++index) {
+ mChildWorkers[index]->OfflineStatusChangeEvent(aIsOffline);
+ }
+
+ mOnLine = !aIsOffline;
+ WorkerGlobalScope* globalScope = GlobalScope();
+ RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
+ if (nav) {
+ nav->SetOnLine(mOnLine);
+ }
+
+ nsString eventType;
+ if (aIsOffline) {
+ eventType.AssignLiteral("offline");
+ } else {
+ eventType.AssignLiteral("online");
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(globalScope, nullptr, nullptr);
+
+ event->InitEvent(eventType, false, false);
+ event->SetTrusted(true);
+
+ globalScope->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::MemoryPressure(bool aDummy)
+{
+ AssertIsOnParentThread();
+
+ RefPtr<MemoryPressureRunnable> runnable =
+ new MemoryPressureRunnable(ParentAsWorkerPrivate());
+ Unused << NS_WARN_IF(!runnable->Dispatch());
+}
+
+template <class Derived>
+bool
+WorkerPrivateParent<Derived>::RegisterSharedWorker(SharedWorker* aSharedWorker,
+ MessagePort* aPort)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aSharedWorker);
+ MOZ_ASSERT(IsSharedWorker());
+ MOZ_ASSERT(!mSharedWorkers.Contains(aSharedWorker));
+
+ if (IsSharedWorker()) {
+ RefPtr<MessagePortRunnable> runnable =
+ new MessagePortRunnable(ParentAsWorkerPrivate(), aPort);
+ if (!runnable->Dispatch()) {
+ return false;
+ }
+ }
+
+ mSharedWorkers.AppendElement(aSharedWorker);
+
+ // If there were other SharedWorker objects attached to this worker then they
+ // may all have been frozen and this worker would need to be thawed.
+ if (mSharedWorkers.Length() > 1 && IsFrozen() && !Thaw(nullptr)) {
+ return false;
+ }
+
+ return true;
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::BroadcastErrorToSharedWorkers(
+ JSContext* aCx,
+ const nsAString& aMessage,
+ const nsAString& aFilename,
+ const nsAString& aLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aFlags)
+{
+ AssertIsOnMainThread();
+
+ if (JSREPORT_IS_WARNING(aFlags)) {
+ // Don't fire any events anywhere. Just log to console.
+ // XXXbz should we log to all the consoles of all the relevant windows?
+ LogErrorToConsole(aMessage, aFilename, aLine, aLineNumber, aColumnNumber,
+ aFlags, 0);
+ return;
+ }
+
+ AutoTArray<RefPtr<SharedWorker>, 10> sharedWorkers;
+ GetAllSharedWorkers(sharedWorkers);
+
+ if (sharedWorkers.IsEmpty()) {
+ return;
+ }
+
+ AutoTArray<WindowAction, 10> windowActions;
+ nsresult rv;
+
+ // First fire the error event at all SharedWorker objects. This may include
+ // multiple objects in a single window as well as objects in different
+ // windows.
+ for (size_t index = 0; index < sharedWorkers.Length(); index++) {
+ RefPtr<SharedWorker>& sharedWorker = sharedWorkers[index];
+
+ // May be null.
+ nsPIDOMWindowInner* window = sharedWorker->GetOwner();
+
+ RootedDictionary<ErrorEventInit> errorInit(aCx);
+ errorInit.mBubbles = false;
+ errorInit.mCancelable = true;
+ errorInit.mMessage = aMessage;
+ errorInit.mFilename = aFilename;
+ errorInit.mLineno = aLineNumber;
+ errorInit.mColno = aColumnNumber;
+
+ RefPtr<ErrorEvent> errorEvent =
+ ErrorEvent::Constructor(sharedWorker, NS_LITERAL_STRING("error"),
+ errorInit);
+ if (!errorEvent) {
+ ThrowAndReport(window, NS_ERROR_UNEXPECTED);
+ continue;
+ }
+
+ errorEvent->SetTrusted(true);
+
+ bool defaultActionEnabled;
+ nsresult rv = sharedWorker->DispatchEvent(errorEvent, &defaultActionEnabled);
+ if (NS_FAILED(rv)) {
+ ThrowAndReport(window, rv);
+ continue;
+ }
+
+ if (defaultActionEnabled) {
+ // Add the owning window to our list so that we will fire an error event
+ // at it later.
+ if (!windowActions.Contains(window)) {
+ windowActions.AppendElement(WindowAction(window));
+ }
+ } else {
+ size_t actionsIndex = windowActions.LastIndexOf(WindowAction(window));
+ if (actionsIndex != windowActions.NoIndex) {
+ // Any listener that calls preventDefault() will prevent the window from
+ // receiving the error event.
+ windowActions[actionsIndex].mDefaultAction = false;
+ }
+ }
+ }
+
+ // If there are no windows to consider further then we're done.
+ if (windowActions.IsEmpty()) {
+ return;
+ }
+
+ bool shouldLogErrorToConsole = true;
+
+ // Now fire error events at all the windows remaining.
+ for (uint32_t index = 0; index < windowActions.Length(); index++) {
+ WindowAction& windowAction = windowActions[index];
+
+ // If there is no window or the script already called preventDefault then
+ // skip this window.
+ if (!windowAction.mWindow || !windowAction.mDefaultAction) {
+ continue;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> sgo =
+ do_QueryInterface(windowAction.mWindow);
+ MOZ_ASSERT(sgo);
+
+ MOZ_ASSERT(NS_IsMainThread());
+ RootedDictionary<ErrorEventInit> init(aCx);
+ init.mLineno = aLineNumber;
+ init.mFilename = aFilename;
+ init.mMessage = aMessage;
+ init.mCancelable = true;
+ init.mBubbles = true;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ rv = sgo->HandleScriptError(init, &status);
+ if (NS_FAILED(rv)) {
+ ThrowAndReport(windowAction.mWindow, rv);
+ continue;
+ }
+
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ shouldLogErrorToConsole = false;
+ }
+ }
+
+ // Finally log a warning in the console if no window tried to prevent it.
+ if (shouldLogErrorToConsole) {
+ LogErrorToConsole(aMessage, aFilename, aLine, aLineNumber, aColumnNumber,
+ aFlags, 0);
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::GetAllSharedWorkers(
+ nsTArray<RefPtr<SharedWorker>>& aSharedWorkers)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(IsSharedWorker() || IsServiceWorker());
+
+ if (!aSharedWorkers.IsEmpty()) {
+ aSharedWorkers.Clear();
+ }
+
+ for (uint32_t i = 0; i < mSharedWorkers.Length(); ++i) {
+ aSharedWorkers.AppendElement(mSharedWorkers[i]);
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::CloseSharedWorkersForWindow(
+ nsPIDOMWindowInner* aWindow)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(IsSharedWorker() || IsServiceWorker());
+ MOZ_ASSERT(aWindow);
+
+ bool someRemoved = false;
+
+ for (uint32_t i = 0; i < mSharedWorkers.Length();) {
+ if (mSharedWorkers[i]->GetOwner() == aWindow) {
+ mSharedWorkers[i]->Close();
+ mSharedWorkers.RemoveElementAt(i);
+ someRemoved = true;
+ } else {
+ MOZ_ASSERT(!SameCOMIdentity(mSharedWorkers[i]->GetOwner(),
+ aWindow));
+ ++i;
+ }
+ }
+
+ if (!someRemoved) {
+ return;
+ }
+
+ // If there are still SharedWorker objects attached to this worker then they
+ // may all be frozen and this worker would need to be frozen. Otherwise,
+ // if that was the last SharedWorker then it's time to cancel this worker.
+
+ if (!mSharedWorkers.IsEmpty()) {
+ Freeze(nullptr);
+ } else {
+ Cancel();
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::CloseAllSharedWorkers()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(IsSharedWorker() || IsServiceWorker());
+
+ for (uint32_t i = 0; i < mSharedWorkers.Length(); ++i) {
+ mSharedWorkers[i]->Close();
+ }
+
+ mSharedWorkers.Clear();
+
+ Cancel();
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::WorkerScriptLoaded()
+{
+ AssertIsOnMainThread();
+
+ if (IsSharedWorker() || IsServiceWorker()) {
+ // No longer need to hold references to the window or document we came from.
+ mLoadInfo.mWindow = nullptr;
+ mLoadInfo.mScriptContext = nullptr;
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::SetBaseURI(nsIURI* aBaseURI)
+{
+ AssertIsOnMainThread();
+
+ if (!mLoadInfo.mBaseURI) {
+ NS_ASSERTION(GetParent(), "Shouldn't happen without a parent!");
+ mLoadInfo.mResolvedScriptURI = aBaseURI;
+ }
+
+ mLoadInfo.mBaseURI = aBaseURI;
+
+ if (NS_FAILED(aBaseURI->GetSpec(mLocationInfo.mHref))) {
+ mLocationInfo.mHref.Truncate();
+ }
+
+ mLocationInfo.mHostname.Truncate();
+ nsContentUtils::GetHostOrIPv6WithBrackets(aBaseURI, mLocationInfo.mHostname);
+
+ nsCOMPtr<nsIURL> url(do_QueryInterface(aBaseURI));
+ if (!url || NS_FAILED(url->GetFilePath(mLocationInfo.mPathname))) {
+ mLocationInfo.mPathname.Truncate();
+ }
+
+ nsCString temp;
+
+ if (url && NS_SUCCEEDED(url->GetQuery(temp)) && !temp.IsEmpty()) {
+ mLocationInfo.mSearch.Assign('?');
+ mLocationInfo.mSearch.Append(temp);
+ }
+
+ if (NS_SUCCEEDED(aBaseURI->GetRef(temp)) && !temp.IsEmpty()) {
+ nsCOMPtr<nsITextToSubURI> converter =
+ do_GetService(NS_ITEXTTOSUBURI_CONTRACTID);
+ if (converter && nsContentUtils::GettersDecodeURLHash()) {
+ nsCString charset;
+ nsAutoString unicodeRef;
+ if (NS_SUCCEEDED(aBaseURI->GetOriginCharset(charset)) &&
+ NS_SUCCEEDED(converter->UnEscapeURIForUI(charset, temp,
+ unicodeRef))) {
+ mLocationInfo.mHash.Assign('#');
+ mLocationInfo.mHash.Append(NS_ConvertUTF16toUTF8(unicodeRef));
+ }
+ }
+
+ if (mLocationInfo.mHash.IsEmpty()) {
+ mLocationInfo.mHash.Assign('#');
+ mLocationInfo.mHash.Append(temp);
+ }
+ }
+
+ if (NS_SUCCEEDED(aBaseURI->GetScheme(mLocationInfo.mProtocol))) {
+ mLocationInfo.mProtocol.Append(':');
+ }
+ else {
+ mLocationInfo.mProtocol.Truncate();
+ }
+
+ int32_t port;
+ if (NS_SUCCEEDED(aBaseURI->GetPort(&port)) && port != -1) {
+ mLocationInfo.mPort.AppendInt(port);
+
+ nsAutoCString host(mLocationInfo.mHostname);
+ host.Append(':');
+ host.Append(mLocationInfo.mPort);
+
+ mLocationInfo.mHost.Assign(host);
+ }
+ else {
+ mLocationInfo.mHost.Assign(mLocationInfo.mHostname);
+ }
+
+ nsContentUtils::GetUTFOrigin(aBaseURI, mLocationInfo.mOrigin);
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::SetPrincipal(nsIPrincipal* aPrincipal,
+ nsILoadGroup* aLoadGroup)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadGroup, aPrincipal));
+ MOZ_ASSERT(!mLoadInfo.mPrincipalInfo);
+
+ mLoadInfo.mPrincipal = aPrincipal;
+ mLoadInfo.mPrincipalIsSystem = nsContentUtils::IsSystemPrincipal(aPrincipal);
+
+ aPrincipal->GetCsp(getter_AddRefs(mLoadInfo.mCSP));
+
+ if (mLoadInfo.mCSP) {
+ mLoadInfo.mCSP->GetAllowsEval(&mLoadInfo.mReportCSPViolations,
+ &mLoadInfo.mEvalAllowed);
+ // Set ReferrerPolicy
+ bool hasReferrerPolicy = false;
+ uint32_t rp = mozilla::net::RP_Default;
+
+ nsresult rv = mLoadInfo.mCSP->GetReferrerPolicy(&rp, &hasReferrerPolicy);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (hasReferrerPolicy) {
+ mLoadInfo.mReferrerPolicy = static_cast<net::ReferrerPolicy>(rp);
+ }
+ } else {
+ mLoadInfo.mEvalAllowed = true;
+ mLoadInfo.mReportCSPViolations = false;
+ }
+
+ mLoadInfo.mLoadGroup = aLoadGroup;
+
+ mLoadInfo.mPrincipalInfo = new PrincipalInfo();
+ mLoadInfo.mOriginAttributes = nsContentUtils::GetOriginAttributes(aLoadGroup);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ PrincipalToPrincipalInfo(aPrincipal, mLoadInfo.mPrincipalInfo));
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::UpdateOverridenLoadGroup(nsILoadGroup* aBaseLoadGroup)
+{
+ AssertIsOnMainThread();
+
+ // The load group should have been overriden at init time.
+ mLoadInfo.mInterfaceRequestor->MaybeAddTabChild(aBaseLoadGroup);
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::FlushReportsToSharedWorkers(
+ nsIConsoleReportCollector* aReporter)
+{
+ AssertIsOnMainThread();
+
+ AutoTArray<RefPtr<SharedWorker>, 10> sharedWorkers;
+ AutoTArray<WindowAction, 10> windowActions;
+ GetAllSharedWorkers(sharedWorkers);
+
+ // First find out all the shared workers' window.
+ for (size_t index = 0; index < sharedWorkers.Length(); index++) {
+ RefPtr<SharedWorker>& sharedWorker = sharedWorkers[index];
+
+ // May be null.
+ nsPIDOMWindowInner* window = sharedWorker->GetOwner();
+
+ // Add the owning window to our list so that we will flush the reports later.
+ if (window && !windowActions.Contains(window)) {
+ windowActions.AppendElement(WindowAction(window));
+ }
+ }
+
+ bool reportErrorToBrowserConsole = true;
+
+ // Flush the reports.
+ for (uint32_t index = 0; index < windowActions.Length(); index++) {
+ WindowAction& windowAction = windowActions[index];
+
+ aReporter->FlushConsoleReports(windowAction.mWindow->GetExtantDoc(),
+ nsIConsoleReportCollector::ReportAction::Save);
+ reportErrorToBrowserConsole = false;
+ }
+
+ // Finally report to broswer console if there is no any window or shared
+ // worker.
+ if (reportErrorToBrowserConsole) {
+ aReporter->FlushConsoleReports((nsIDocument*)nullptr);
+ return;
+ }
+
+ aReporter->ClearConsoleReports();
+}
+
+template <class Derived>
+NS_IMPL_ADDREF_INHERITED(WorkerPrivateParent<Derived>, DOMEventTargetHelper)
+
+template <class Derived>
+NS_IMPL_RELEASE_INHERITED(WorkerPrivateParent<Derived>, DOMEventTargetHelper)
+
+template <class Derived>
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WorkerPrivateParent<Derived>)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+template <class Derived>
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WorkerPrivateParent<Derived>,
+ DOMEventTargetHelper)
+ tmp->AssertIsOnParentThread();
+
+ // The WorkerPrivate::mSelfRef has a reference to itself, which is really
+ // held by the worker thread. We traverse this reference if and only if our
+ // busy count is zero and we have not released the main thread reference.
+ // We do not unlink it. This allows the CC to break cycles involving the
+ // WorkerPrivate and begin shutting it down (which does happen in unlink) but
+ // ensures that the WorkerPrivate won't be deleted before we're done shutting
+ // down the thread.
+
+ if (!tmp->mBusyCount && !tmp->mMainThreadObjectsForgotten) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelfRef)
+ }
+
+ // The various strong references in LoadInfo are managed manually and cannot
+ // be cycle collected.
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+template <class Derived>
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WorkerPrivateParent<Derived>,
+ DOMEventTargetHelper)
+ tmp->Terminate();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+template <class Derived>
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WorkerPrivateParent<Derived>,
+ DOMEventTargetHelper)
+ tmp->AssertIsOnParentThread();
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+#ifdef DEBUG
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::AssertIsOnParentThread() const
+{
+ if (GetParent()) {
+ GetParent()->AssertIsOnWorkerThread();
+ }
+ else {
+ AssertIsOnMainThread();
+ }
+}
+
+template <class Derived>
+void
+WorkerPrivateParent<Derived>::AssertInnerWindowIsCorrect() const
+{
+ AssertIsOnParentThread();
+
+ // Only care about top level workers from windows.
+ if (mParent || !mLoadInfo.mWindow) {
+ return;
+ }
+
+ AssertIsOnMainThread();
+
+ nsPIDOMWindowOuter* outer = mLoadInfo.mWindow->GetOuterWindow();
+ NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mLoadInfo.mWindow,
+ "Inner window no longer correct!");
+}
+
+#endif
+
+class PostDebuggerMessageRunnable final : public Runnable
+{
+ WorkerDebugger *mDebugger;
+ nsString mMessage;
+
+public:
+ PostDebuggerMessageRunnable(WorkerDebugger* aDebugger,
+ const nsAString& aMessage)
+ : mDebugger(aDebugger),
+ mMessage(aMessage)
+ {
+ }
+
+private:
+ ~PostDebuggerMessageRunnable()
+ { }
+
+ NS_IMETHOD
+ Run() override
+ {
+ mDebugger->PostMessageToDebuggerOnMainThread(mMessage);
+
+ return NS_OK;
+ }
+};
+
+class ReportDebuggerErrorRunnable final : public Runnable
+{
+ WorkerDebugger *mDebugger;
+ nsString mFilename;
+ uint32_t mLineno;
+ nsString mMessage;
+
+public:
+ ReportDebuggerErrorRunnable(WorkerDebugger* aDebugger,
+ const nsAString& aFilename, uint32_t aLineno,
+ const nsAString& aMessage)
+ : mDebugger(aDebugger),
+ mFilename(aFilename),
+ mLineno(aLineno),
+ mMessage(aMessage)
+ {
+ }
+
+private:
+ ~ReportDebuggerErrorRunnable()
+ { }
+
+ NS_IMETHOD
+ Run() override
+ {
+ mDebugger->ReportErrorToDebuggerOnMainThread(mFilename, mLineno, mMessage);
+
+ return NS_OK;
+ }
+};
+
+WorkerDebugger::WorkerDebugger(WorkerPrivate* aWorkerPrivate)
+: mWorkerPrivate(aWorkerPrivate),
+ mIsInitialized(false)
+{
+ AssertIsOnMainThread();
+}
+
+WorkerDebugger::~WorkerDebugger()
+{
+ MOZ_ASSERT(!mWorkerPrivate);
+
+ if (!NS_IsMainThread()) {
+ for (size_t index = 0; index < mListeners.Length(); ++index) {
+ NS_ReleaseOnMainThread(mListeners[index].forget());
+ }
+ }
+}
+
+NS_IMPL_ISUPPORTS(WorkerDebugger, nsIWorkerDebugger)
+
+NS_IMETHODIMP
+WorkerDebugger::GetIsClosed(bool* aResult)
+{
+ AssertIsOnMainThread();
+
+ *aResult = !mWorkerPrivate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetIsChrome(bool* aResult)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *aResult = mWorkerPrivate->IsChromeWorker();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetIsInitialized(bool* aResult)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *aResult = mIsInitialized;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetParent(nsIWorkerDebugger** aResult)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ WorkerPrivate* parent = mWorkerPrivate->GetParent();
+ if (!parent) {
+ *aResult = nullptr;
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mWorkerPrivate->IsDedicatedWorker());
+
+ nsCOMPtr<nsIWorkerDebugger> debugger = parent->Debugger();
+ debugger.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetType(uint32_t* aResult)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *aResult = mWorkerPrivate->Type();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetUrl(nsAString& aResult)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ aResult = mWorkerPrivate->ScriptURL();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetWindow(mozIDOMWindow** aResult)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mWorkerPrivate->GetParent() || !mWorkerPrivate->IsDedicatedWorker()) {
+ *aResult = nullptr;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = mWorkerPrivate->GetWindow();
+ window.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetPrincipal(nsIPrincipal** aResult)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aResult);
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIPrincipal> prin = mWorkerPrivate->GetPrincipal();
+ prin.forget(aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::GetServiceWorkerID(uint32_t* aResult)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aResult);
+
+ if (!mWorkerPrivate || !mWorkerPrivate->IsServiceWorker()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *aResult = mWorkerPrivate->ServiceWorkerID();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::Initialize(const nsAString& aURL)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mIsInitialized) {
+ RefPtr<CompileDebuggerScriptRunnable> runnable =
+ new CompileDebuggerScriptRunnable(mWorkerPrivate, aURL);
+ if (!runnable->Dispatch()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIsInitialized = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::PostMessageMoz(const nsAString& aMessage)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate || !mIsInitialized) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<DebuggerMessageEventRunnable> runnable =
+ new DebuggerMessageEventRunnable(mWorkerPrivate, aMessage);
+ if (!runnable->Dispatch()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::AddListener(nsIWorkerDebuggerListener* aListener)
+{
+ AssertIsOnMainThread();
+
+ if (mListeners.Contains(aListener)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mListeners.AppendElement(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerDebugger::RemoveListener(nsIWorkerDebuggerListener* aListener)
+{
+ AssertIsOnMainThread();
+
+ if (!mListeners.Contains(aListener)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+void
+WorkerDebugger::Close()
+{
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate = nullptr;
+
+ nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> listeners(mListeners);
+ for (size_t index = 0; index < listeners.Length(); ++index) {
+ listeners[index]->OnClose();
+ }
+}
+
+void
+WorkerDebugger::PostMessageToDebugger(const nsAString& aMessage)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<PostDebuggerMessageRunnable> runnable =
+ new PostDebuggerMessageRunnable(this, aMessage);
+ if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable.forget()))) {
+ NS_WARNING("Failed to post message to debugger on main thread!");
+ }
+}
+
+void
+WorkerDebugger::PostMessageToDebuggerOnMainThread(const nsAString& aMessage)
+{
+ AssertIsOnMainThread();
+
+ nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> listeners(mListeners);
+ for (size_t index = 0; index < listeners.Length(); ++index) {
+ listeners[index]->OnMessage(aMessage);
+ }
+}
+
+void
+WorkerDebugger::ReportErrorToDebugger(const nsAString& aFilename,
+ uint32_t aLineno,
+ const nsAString& aMessage)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<ReportDebuggerErrorRunnable> runnable =
+ new ReportDebuggerErrorRunnable(this, aFilename, aLineno, aMessage);
+ if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable.forget()))) {
+ NS_WARNING("Failed to report error to debugger on main thread!");
+ }
+}
+
+void
+WorkerDebugger::ReportErrorToDebuggerOnMainThread(const nsAString& aFilename,
+ uint32_t aLineno,
+ const nsAString& aMessage)
+{
+ AssertIsOnMainThread();
+
+ nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> listeners(mListeners);
+ for (size_t index = 0; index < listeners.Length(); ++index) {
+ listeners[index]->OnError(aFilename, aLineno, aMessage);
+ }
+
+ LogErrorToConsole(aMessage, aFilename, nsString(), aLineno, 0, 0, 0);
+}
+
+WorkerPrivate::WorkerPrivate(WorkerPrivate* aParent,
+ const nsAString& aScriptURL,
+ bool aIsChromeWorker, WorkerType aWorkerType,
+ const nsACString& aWorkerName,
+ WorkerLoadInfo& aLoadInfo)
+ : WorkerPrivateParent<WorkerPrivate>(aParent, aScriptURL,
+ aIsChromeWorker, aWorkerType,
+ aWorkerName, aLoadInfo)
+ , mDebuggerRegistered(false)
+ , mDebugger(nullptr)
+ , mJSContext(nullptr)
+ , mPRThread(nullptr)
+ , mDebuggerEventLoopLevel(0)
+ , mMainThreadEventTarget(do_GetMainThread())
+ , mErrorHandlerRecursionCount(0)
+ , mNextTimeoutId(1)
+ , mStatus(Pending)
+ , mFrozen(false)
+ , mTimerRunning(false)
+ , mRunningExpiredTimeouts(false)
+ , mPendingEventQueueClearing(false)
+ , mCancelAllPendingRunnables(false)
+ , mPeriodicGCTimerRunning(false)
+ , mIdleGCTimerRunning(false)
+ , mWorkerScriptExecutedSuccessfully(false)
+ , mOnLine(false)
+{
+ MOZ_ASSERT_IF(!IsDedicatedWorker(), !aWorkerName.IsVoid());
+ MOZ_ASSERT_IF(IsDedicatedWorker(), aWorkerName.IsEmpty());
+
+ if (aParent) {
+ aParent->AssertIsOnWorkerThread();
+ aParent->GetAllPreferences(mPreferences);
+ mOnLine = aParent->OnLine();
+ }
+ else {
+ AssertIsOnMainThread();
+ RuntimeService::GetDefaultPreferences(mPreferences);
+ mOnLine = !NS_IsOffline();
+ }
+
+ nsCOMPtr<nsIEventTarget> target;
+
+ // A child worker just inherits the parent workers ThrottledEventQueue
+ // and main thread target for now. This is mainly due to the restriction
+ // that ThrottledEventQueue can only be created on the main thread at the
+ // moment.
+ if (aParent) {
+ mMainThreadThrottledEventQueue = aParent->mMainThreadThrottledEventQueue;
+ mMainThreadEventTarget = aParent->mMainThreadEventTarget;
+ return;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ target = GetWindow() ? GetWindow()->GetThrottledEventQueue() : nullptr;
+
+ if (!target) {
+ nsCOMPtr<nsIThread> mainThread;
+ NS_GetMainThread(getter_AddRefs(mainThread));
+ MOZ_DIAGNOSTIC_ASSERT(mainThread);
+ target = mainThread;
+ }
+
+ // Throttle events to the main thread using a ThrottledEventQueue specific to
+ // this worker thread. This may return nullptr during shutdown.
+ mMainThreadThrottledEventQueue = ThrottledEventQueue::Create(target);
+
+ // If we were able to creat the throttled event queue, then use it for
+ // dispatching our main thread runnables. Otherwise use our underlying
+ // base target.
+ if (mMainThreadThrottledEventQueue) {
+ mMainThreadEventTarget = mMainThreadThrottledEventQueue;
+ } else {
+ mMainThreadEventTarget = target.forget();
+ }
+}
+
+WorkerPrivate::~WorkerPrivate()
+{
+}
+
+// static
+already_AddRefed<WorkerPrivate>
+WorkerPrivate::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aScriptURL,
+ ErrorResult& aRv)
+{
+ return WorkerPrivate::Constructor(aGlobal, aScriptURL, false,
+ WorkerTypeDedicated, EmptyCString(),
+ nullptr, aRv);
+}
+
+// static
+bool
+WorkerPrivate::WorkerAvailable(JSContext* /* unused */, JSObject* /* unused */)
+{
+ // If we're already on a worker workers are clearly enabled.
+ if (!NS_IsMainThread()) {
+ return true;
+ }
+
+ // If our caller is chrome, workers are always available.
+ if (nsContentUtils::IsCallerChrome()) {
+ return true;
+ }
+
+ // Else check the pref.
+ return Preferences::GetBool(PREF_WORKERS_ENABLED);
+}
+
+// static
+already_AddRefed<ChromeWorkerPrivate>
+ChromeWorkerPrivate::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aScriptURL,
+ ErrorResult& aRv)
+{
+ return WorkerPrivate::Constructor(aGlobal, aScriptURL, true,
+ WorkerTypeDedicated, EmptyCString(),
+ nullptr, aRv)
+ .downcast<ChromeWorkerPrivate>();
+}
+
+// static
+bool
+ChromeWorkerPrivate::WorkerAvailable(JSContext* aCx, JSObject* /* unused */)
+{
+ // Chrome is always allowed to use workers, and content is never
+ // allowed to use ChromeWorker, so all we have to check is the
+ // caller. However, chrome workers apparently might not have a
+ // system principal, so we have to check for them manually.
+ if (NS_IsMainThread()) {
+ return nsContentUtils::IsCallerChrome();
+ }
+
+ return GetWorkerPrivateFromContext(aCx)->IsChromeWorker();
+}
+
+// static
+already_AddRefed<WorkerPrivate>
+WorkerPrivate::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aScriptURL,
+ bool aIsChromeWorker, WorkerType aWorkerType,
+ const nsACString& aWorkerName,
+ WorkerLoadInfo* aLoadInfo, ErrorResult& aRv)
+{
+ JSContext* cx = aGlobal.Context();
+ return Constructor(cx, aScriptURL, aIsChromeWorker, aWorkerType,
+ aWorkerName, aLoadInfo, aRv);
+}
+
+// static
+already_AddRefed<WorkerPrivate>
+WorkerPrivate::Constructor(JSContext* aCx,
+ const nsAString& aScriptURL,
+ bool aIsChromeWorker, WorkerType aWorkerType,
+ const nsACString& aWorkerName,
+ WorkerLoadInfo* aLoadInfo, ErrorResult& aRv)
+{
+ // If this is a sub-worker, we need to keep the parent worker alive until this
+ // one is registered.
+ UniquePtr<SimpleWorkerHolder> holder;
+
+ WorkerPrivate* parent = NS_IsMainThread() ?
+ nullptr :
+ GetCurrentThreadWorkerPrivate();
+ if (parent) {
+ parent->AssertIsOnWorkerThread();
+
+ holder.reset(new SimpleWorkerHolder());
+ if (!holder->HoldWorker(parent, Canceling)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+ } else {
+ AssertIsOnMainThread();
+ }
+
+ // Only service and shared workers can have names.
+ MOZ_ASSERT_IF(aWorkerType != WorkerTypeDedicated,
+ !aWorkerName.IsVoid());
+ MOZ_ASSERT_IF(aWorkerType == WorkerTypeDedicated,
+ aWorkerName.IsEmpty());
+
+ Maybe<WorkerLoadInfo> stackLoadInfo;
+ if (!aLoadInfo) {
+ stackLoadInfo.emplace();
+
+ nsresult rv = GetLoadInfo(aCx, nullptr, parent, aScriptURL,
+ aIsChromeWorker, InheritLoadGroup,
+ aWorkerType, stackLoadInfo.ptr());
+ aRv.MightThrowJSException();
+ if (NS_FAILED(rv)) {
+ scriptloader::ReportLoadError(aRv, rv, aScriptURL);
+ return nullptr;
+ }
+
+ aLoadInfo = stackLoadInfo.ptr();
+ }
+
+ // NB: This has to be done before creating the WorkerPrivate, because it will
+ // attempt to use static variables that are initialized in the RuntimeService
+ // constructor.
+ RuntimeService* runtimeService;
+
+ if (!parent) {
+ runtimeService = RuntimeService::GetOrCreateService();
+ if (!runtimeService) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ }
+ else {
+ runtimeService = RuntimeService::GetService();
+ }
+
+ MOZ_ASSERT(runtimeService);
+
+ RefPtr<WorkerPrivate> worker =
+ new WorkerPrivate(parent, aScriptURL, aIsChromeWorker,
+ aWorkerType, aWorkerName, *aLoadInfo);
+
+ // Gecko contexts always have an explicitly-set default locale (set by
+ // XPJSRuntime::Initialize for the main thread, set by
+ // WorkerThreadPrimaryRunnable::Run for workers just before running worker
+ // code), so this is never SpiderMonkey's builtin default locale.
+ JS::UniqueChars defaultLocale = JS_GetDefaultLocale(aCx);
+ if (NS_WARN_IF(!defaultLocale)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ worker->mDefaultLocale = Move(defaultLocale);
+
+ if (!runtimeService->RegisterWorker(worker)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ worker->EnableDebugger();
+
+ RefPtr<CompileScriptRunnable> compiler =
+ new CompileScriptRunnable(worker, aScriptURL);
+ if (!compiler->Dispatch()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ worker->mSelfRef = worker;
+
+ return worker.forget();
+}
+
+// static
+nsresult
+WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindowInner* aWindow,
+ WorkerPrivate* aParent, const nsAString& aScriptURL,
+ bool aIsChromeWorker,
+ LoadGroupBehavior aLoadGroupBehavior,
+ WorkerType aWorkerType,
+ WorkerLoadInfo* aLoadInfo)
+{
+ using namespace mozilla::dom::workers::scriptloader;
+
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT_IF(NS_IsMainThread(), aCx == nsContentUtils::GetCurrentJSContext());
+
+ if (aWindow) {
+ AssertIsOnMainThread();
+ }
+
+ WorkerLoadInfo loadInfo;
+ nsresult rv;
+
+ if (aParent) {
+ aParent->AssertIsOnWorkerThread();
+
+ // If the parent is going away give up now.
+ Status parentStatus;
+ {
+ MutexAutoLock lock(aParent->mMutex);
+ parentStatus = aParent->mStatus;
+ }
+
+ if (parentStatus > Running) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // StartAssignment() is used instead getter_AddRefs because, getter_AddRefs
+ // does QI in debug build and, if this worker runs in a child process,
+ // HttpChannelChild will crash because it's not thread-safe.
+ rv = ChannelFromScriptURLWorkerThread(aCx, aParent, aScriptURL,
+ loadInfo.mChannel.StartAssignment());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now that we've spun the loop there's no guarantee that our parent is
+ // still alive. We may have received control messages initiating shutdown.
+ {
+ MutexAutoLock lock(aParent->mMutex);
+ parentStatus = aParent->mStatus;
+ }
+
+ if (parentStatus > Running) {
+ NS_ReleaseOnMainThread(loadInfo.mChannel.forget());
+ return NS_ERROR_FAILURE;
+ }
+
+ loadInfo.mDomain = aParent->Domain();
+ loadInfo.mFromWindow = aParent->IsFromWindow();
+ loadInfo.mWindowID = aParent->WindowID();
+ loadInfo.mStorageAllowed = aParent->IsStorageAllowed();
+ loadInfo.mOriginAttributes = aParent->GetOriginAttributes();
+ loadInfo.mServiceWorkersTestingInWindow =
+ aParent->ServiceWorkersTestingInWindow();
+ } else {
+ AssertIsOnMainThread();
+
+ // Make sure that the IndexedDatabaseManager is set up
+ Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate());
+
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ MOZ_ASSERT(ssm);
+
+ bool isChrome = nsContentUtils::IsCallerChrome();
+
+ // First check to make sure the caller has permission to make a privileged
+ // worker if they called the ChromeWorker/ChromeSharedWorker constructor.
+ if (aIsChromeWorker && !isChrome) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // Chrome callers (whether creating a ChromeWorker or Worker) always get the
+ // system principal here as they're allowed to load anything. The script
+ // loader will refuse to run any script that does not also have the system
+ // principal.
+ if (isChrome) {
+ rv = ssm->GetSystemPrincipal(getter_AddRefs(loadInfo.mPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ loadInfo.mPrincipalIsSystem = true;
+ }
+
+ // See if we're being called from a window.
+ nsCOMPtr<nsPIDOMWindowInner> globalWindow = aWindow;
+ if (!globalWindow) {
+ nsCOMPtr<nsIScriptGlobalObject> scriptGlobal =
+ nsJSUtils::GetStaticScriptGlobal(JS::CurrentGlobalOrNull(aCx));
+ if (scriptGlobal) {
+ globalWindow = do_QueryInterface(scriptGlobal);
+ MOZ_ASSERT(globalWindow);
+ }
+ }
+
+ nsCOMPtr<nsIDocument> document;
+
+ if (globalWindow) {
+ // Only use the current inner window, and only use it if the caller can
+ // access it.
+ if (nsPIDOMWindowOuter* outerWindow = globalWindow->GetOuterWindow()) {
+ loadInfo.mWindow = outerWindow->GetCurrentInnerWindow();
+ // TODO: fix this for SharedWorkers with multiple documents (bug 1177935)
+ loadInfo.mServiceWorkersTestingInWindow =
+ outerWindow->GetServiceWorkersTestingEnabled();
+ }
+
+ if (!loadInfo.mWindow ||
+ (globalWindow != loadInfo.mWindow &&
+ !nsContentUtils::CanCallerAccess(loadInfo.mWindow))) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(loadInfo.mWindow);
+ MOZ_ASSERT(sgo);
+
+ loadInfo.mScriptContext = sgo->GetContext();
+ NS_ENSURE_TRUE(loadInfo.mScriptContext, NS_ERROR_FAILURE);
+
+ // If we're called from a window then we can dig out the principal and URI
+ // from the document.
+ document = loadInfo.mWindow->GetExtantDoc();
+ NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+
+ loadInfo.mBaseURI = document->GetDocBaseURI();
+ loadInfo.mLoadGroup = document->GetDocumentLoadGroup();
+
+ // Use the document's NodePrincipal as our principal if we're not being
+ // called from chrome.
+ if (!loadInfo.mPrincipal) {
+ loadInfo.mPrincipal = document->NodePrincipal();
+ NS_ENSURE_TRUE(loadInfo.mPrincipal, NS_ERROR_FAILURE);
+
+ // We use the document's base domain to limit the number of workers
+ // each domain can create. For sandboxed documents, we use the domain
+ // of their first non-sandboxed document, walking up until we find
+ // one. If we can't find one, we fall back to using the GUID of the
+ // null principal as the base domain.
+ if (document->GetSandboxFlags() & SANDBOXED_ORIGIN) {
+ nsCOMPtr<nsIDocument> tmpDoc = document;
+ do {
+ tmpDoc = tmpDoc->GetParentDocument();
+ } while (tmpDoc && tmpDoc->GetSandboxFlags() & SANDBOXED_ORIGIN);
+
+ if (tmpDoc) {
+ // There was an unsandboxed ancestor, yay!
+ nsCOMPtr<nsIPrincipal> tmpPrincipal = tmpDoc->NodePrincipal();
+ rv = tmpPrincipal->GetBaseDomain(loadInfo.mDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // No unsandboxed ancestor, use our GUID.
+ rv = loadInfo.mPrincipal->GetBaseDomain(loadInfo.mDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ // Document creating the worker is not sandboxed.
+ rv = loadInfo.mPrincipal->GetBaseDomain(loadInfo.mDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t perm;
+ rv = permMgr->TestPermissionFromPrincipal(loadInfo.mPrincipal, "systemXHR",
+ &perm);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ loadInfo.mXHRParamsAllowed = perm == nsIPermissionManager::ALLOW_ACTION;
+
+ loadInfo.mFromWindow = true;
+ loadInfo.mWindowID = globalWindow->WindowID();
+ nsContentUtils::StorageAccess access =
+ nsContentUtils::StorageAllowedForWindow(globalWindow);
+ loadInfo.mStorageAllowed = access > nsContentUtils::StorageAccess::eDeny;
+ loadInfo.mOriginAttributes = nsContentUtils::GetOriginAttributes(document);
+ } else {
+ // Not a window
+ MOZ_ASSERT(isChrome);
+
+ // We're being created outside of a window. Need to figure out the script
+ // that is creating us in order for us to use relative URIs later on.
+ JS::AutoFilename fileName;
+ if (JS::DescribeScriptedCaller(aCx, &fileName)) {
+ // In most cases, fileName is URI. In a few other cases
+ // (e.g. xpcshell), fileName is a file path. Ideally, we would
+ // prefer testing whether fileName parses as an URI and fallback
+ // to file path in case of error, but Windows file paths have
+ // the interesting property that they can be parsed as bogus
+ // URIs (e.g. C:/Windows/Tmp is interpreted as scheme "C",
+ // hostname "Windows", path "Tmp"), which defeats this algorithm.
+ // Therefore, we adopt the opposite convention.
+ nsCOMPtr<nsIFile> scriptFile =
+ do_CreateInstance("@mozilla.org/file/local;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = scriptFile->InitWithPath(NS_ConvertUTF8toUTF16(fileName.get()));
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_NewFileURI(getter_AddRefs(loadInfo.mBaseURI),
+ scriptFile);
+ }
+ if (NS_FAILED(rv)) {
+ // As expected, fileName is not a path, so proceed with
+ // a uri.
+ rv = NS_NewURI(getter_AddRefs(loadInfo.mBaseURI),
+ fileName.get());
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ loadInfo.mXHRParamsAllowed = true;
+ loadInfo.mFromWindow = false;
+ loadInfo.mWindowID = UINT64_MAX;
+ loadInfo.mStorageAllowed = true;
+ loadInfo.mOriginAttributes = PrincipalOriginAttributes();
+ }
+
+ MOZ_ASSERT(loadInfo.mPrincipal);
+ MOZ_ASSERT(isChrome || !loadInfo.mDomain.IsEmpty());
+
+ if (!loadInfo.mLoadGroup || aLoadGroupBehavior == OverrideLoadGroup) {
+ OverrideLoadInfoLoadGroup(loadInfo);
+ }
+ MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadInfo.mLoadGroup,
+ loadInfo.mPrincipal));
+
+ // Top level workers' main script use the document charset for the script
+ // uri encoding.
+ bool useDefaultEncoding = false;
+ rv = ChannelFromScriptURLMainThread(loadInfo.mPrincipal, loadInfo.mBaseURI,
+ document, loadInfo.mLoadGroup,
+ aScriptURL,
+ ContentPolicyType(aWorkerType),
+ useDefaultEncoding,
+ getter_AddRefs(loadInfo.mChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_GetFinalChannelURI(loadInfo.mChannel,
+ getter_AddRefs(loadInfo.mResolvedScriptURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aLoadInfo->StealFrom(loadInfo);
+ return NS_OK;
+}
+
+// static
+void
+WorkerPrivate::OverrideLoadInfoLoadGroup(WorkerLoadInfo& aLoadInfo)
+{
+ MOZ_ASSERT(!aLoadInfo.mInterfaceRequestor);
+
+ aLoadInfo.mInterfaceRequestor =
+ new WorkerLoadInfo::InterfaceRequestor(aLoadInfo.mPrincipal,
+ aLoadInfo.mLoadGroup);
+ aLoadInfo.mInterfaceRequestor->MaybeAddTabChild(aLoadInfo.mLoadGroup);
+
+ // NOTE: this defaults the load context to:
+ // - private browsing = false
+ // - content = true
+ // - use remote tabs = false
+ nsCOMPtr<nsILoadGroup> loadGroup =
+ do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+
+ nsresult rv =
+ loadGroup->SetNotificationCallbacks(aLoadInfo.mInterfaceRequestor);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+
+ aLoadInfo.mLoadGroup = loadGroup.forget();
+}
+
+void
+WorkerPrivate::DoRunLoop(JSContext* aCx)
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(mThread);
+
+ {
+ MutexAutoLock lock(mMutex);
+ mJSContext = aCx;
+
+ MOZ_ASSERT(mStatus == Pending);
+ mStatus = Running;
+ }
+
+ // Now that we've done that, we can go ahead and set up our AutoJSAPI. We
+ // can't before this point, because it can't find the right JSContext before
+ // then, since it gets it from our mJSContext.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ MOZ_ASSERT(jsapi.cx() == aCx);
+
+ EnableMemoryReporter();
+
+ InitializeGCTimers();
+
+ Maybe<JSAutoCompartment> workerCompartment;
+
+ for (;;) {
+ Status currentStatus, previousStatus;
+ bool debuggerRunnablesPending = false;
+ bool normalRunnablesPending = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+ previousStatus = mStatus;
+
+ while (mControlQueue.IsEmpty() &&
+ !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
+ !(normalRunnablesPending = NS_HasPendingEvents(mThread))) {
+ WaitForWorkerEvents();
+ }
+
+ auto result = ProcessAllControlRunnablesLocked();
+ if (result != ProcessAllControlRunnablesResult::Nothing) {
+ // NB: There's no JS on the stack here, so Abort vs MayContinue is
+ // irrelevant
+
+ // The state of the world may have changed, recheck it.
+ normalRunnablesPending = NS_HasPendingEvents(mThread);
+ // The debugger queue doesn't get cleared, so we can ignore that.
+ }
+
+ currentStatus = mStatus;
+ }
+
+ // if all holders are done then we can kill this thread.
+ if (currentStatus != Running && !HasActiveHolders()) {
+
+ // If we just changed status, we must schedule the current runnables.
+ if (previousStatus != Running && currentStatus != Killing) {
+ NotifyInternal(aCx, Killing);
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(mMutex);
+ currentStatus = mStatus;
+ }
+ MOZ_ASSERT(currentStatus == Killing);
+#else
+ currentStatus = Killing;
+#endif
+ }
+
+ // If we're supposed to die then we should exit the loop.
+ if (currentStatus == Killing) {
+ // Flush uncaught rejections immediately, without
+ // waiting for a next tick.
+ PromiseDebugging::FlushUncaughtRejections();
+
+ ShutdownGCTimers();
+
+ DisableMemoryReporter();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ mStatus = Dead;
+ mJSContext = nullptr;
+ }
+
+ // After mStatus is set to Dead there can be no more
+ // WorkerControlRunnables so no need to lock here.
+ if (!mControlQueue.IsEmpty()) {
+ WorkerControlRunnable* runnable;
+ while (mControlQueue.Pop(runnable)) {
+ runnable->Cancel();
+ runnable->Release();
+ }
+ }
+
+ // Unroot the globals
+ mScope = nullptr;
+ mDebuggerScope = nullptr;
+
+ return;
+ }
+ }
+
+ if (debuggerRunnablesPending || normalRunnablesPending) {
+ // Start the periodic GC timer if it is not already running.
+ SetGCTimerMode(PeriodicTimer);
+ }
+
+ if (debuggerRunnablesPending) {
+ WorkerRunnable* runnable;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ mDebuggerQueue.Pop(runnable);
+ debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
+ }
+
+ MOZ_ASSERT(runnable);
+ static_cast<nsIRunnable*>(runnable)->Run();
+ runnable->Release();
+
+ // Flush the promise queue.
+ Promise::PerformWorkerDebuggerMicroTaskCheckpoint();
+
+ if (debuggerRunnablesPending) {
+ WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope();
+ MOZ_ASSERT(globalScope);
+
+ // Now *might* be a good time to GC. Let the JS engine make the decision.
+ JSAutoCompartment ac(aCx, globalScope->GetGlobalJSObject());
+ JS_MaybeGC(aCx);
+ }
+ } else if (normalRunnablesPending) {
+ // Process a single runnable from the main queue.
+ NS_ProcessNextEvent(mThread, false);
+
+ normalRunnablesPending = NS_HasPendingEvents(mThread);
+ if (normalRunnablesPending && GlobalScope()) {
+ // Now *might* be a good time to GC. Let the JS engine make the decision.
+ JSAutoCompartment ac(aCx, GlobalScope()->GetGlobalJSObject());
+ JS_MaybeGC(aCx);
+ }
+ }
+
+ if (!debuggerRunnablesPending && !normalRunnablesPending) {
+ // Both the debugger event queue and the normal event queue has been
+ // exhausted, cancel the periodic GC timer and schedule the idle GC timer.
+ SetGCTimerMode(IdleTimer);
+ }
+
+ // If the worker thread is spamming the main thread faster than it can
+ // process the work, then pause the worker thread until the MT catches
+ // up.
+ if (mMainThreadThrottledEventQueue &&
+ mMainThreadThrottledEventQueue->Length() > 5000) {
+ mMainThreadThrottledEventQueue->AwaitIdle();
+ }
+ }
+
+ MOZ_CRASH("Shouldn't get here!");
+}
+
+void
+WorkerPrivate::OnProcessNextEvent()
+{
+ AssertIsOnWorkerThread();
+
+ uint32_t recursionDepth = CycleCollectedJSContext::Get()->RecursionDepth();
+ MOZ_ASSERT(recursionDepth);
+
+ // Normally we process control runnables in DoRunLoop or RunCurrentSyncLoop.
+ // However, it's possible that non-worker C++ could spin its own nested event
+ // loop, and in that case we must ensure that we continue to process control
+ // runnables here.
+ if (recursionDepth > 1 &&
+ mSyncLoopStack.Length() < recursionDepth - 1) {
+ Unused << ProcessAllControlRunnables();
+ // There's no running JS, and no state to revalidate, so we can ignore the
+ // return value.
+ }
+}
+
+void
+WorkerPrivate::AfterProcessNextEvent()
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(CycleCollectedJSContext::Get()->RecursionDepth());
+}
+
+void
+WorkerPrivate::MaybeDispatchLoadFailedRunnable()
+{
+ AssertIsOnWorkerThread();
+
+ nsCOMPtr<nsIRunnable> runnable = StealLoadFailedAsyncRunnable();
+ if (!runnable) {
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(DispatchToMainThread(runnable.forget()));
+}
+
+nsIEventTarget*
+WorkerPrivate::MainThreadEventTarget()
+{
+ return mMainThreadEventTarget;
+}
+
+nsresult
+WorkerPrivate::DispatchToMainThread(nsIRunnable* aRunnable, uint32_t aFlags)
+{
+ nsCOMPtr<nsIRunnable> r = aRunnable;
+ return DispatchToMainThread(r.forget(), aFlags);
+}
+
+nsresult
+WorkerPrivate::DispatchToMainThread(already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aFlags)
+{
+ return mMainThreadEventTarget->Dispatch(Move(aRunnable), aFlags);
+}
+
+void
+WorkerPrivate::InitializeGCTimers()
+{
+ AssertIsOnWorkerThread();
+
+ // We need a timer for GC. The basic plan is to run a non-shrinking GC
+ // periodically (PERIODIC_GC_TIMER_DELAY_SEC) while the worker is running.
+ // Once the worker goes idle we set a short (IDLE_GC_TIMER_DELAY_SEC) timer to
+ // run a shrinking GC. If the worker receives more messages then the short
+ // timer is canceled and the periodic timer resumes.
+ mGCTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ MOZ_ASSERT(mGCTimer);
+
+ RefPtr<GarbageCollectRunnable> runnable =
+ new GarbageCollectRunnable(this, false, false);
+ mPeriodicGCTimerTarget = new TimerThreadEventTarget(this, runnable);
+
+ runnable = new GarbageCollectRunnable(this, true, false);
+ mIdleGCTimerTarget = new TimerThreadEventTarget(this, runnable);
+
+ mPeriodicGCTimerRunning = false;
+ mIdleGCTimerRunning = false;
+}
+
+void
+WorkerPrivate::SetGCTimerMode(GCTimerMode aMode)
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(mGCTimer);
+ MOZ_ASSERT(mPeriodicGCTimerTarget);
+ MOZ_ASSERT(mIdleGCTimerTarget);
+
+ if ((aMode == PeriodicTimer && mPeriodicGCTimerRunning) ||
+ (aMode == IdleTimer && mIdleGCTimerRunning)) {
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(mGCTimer->Cancel());
+
+ mPeriodicGCTimerRunning = false;
+ mIdleGCTimerRunning = false;
+ LOG(WorkerLog(),
+ ("Worker %p canceled GC timer because %s\n", this,
+ aMode == PeriodicTimer ?
+ "periodic" :
+ aMode == IdleTimer ? "idle" : "none"));
+
+ if (aMode == NoTimer) {
+ return;
+ }
+
+ MOZ_ASSERT(aMode == PeriodicTimer || aMode == IdleTimer);
+
+ nsIEventTarget* target;
+ uint32_t delay;
+ int16_t type;
+
+ if (aMode == PeriodicTimer) {
+ target = mPeriodicGCTimerTarget;
+ delay = PERIODIC_GC_TIMER_DELAY_SEC * 1000;
+ type = nsITimer::TYPE_REPEATING_SLACK;
+ }
+ else {
+ target = mIdleGCTimerTarget;
+ delay = IDLE_GC_TIMER_DELAY_SEC * 1000;
+ type = nsITimer::TYPE_ONE_SHOT;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(mGCTimer->SetTarget(target));
+ MOZ_ALWAYS_SUCCEEDS(
+ mGCTimer->InitWithNamedFuncCallback(DummyCallback, nullptr, delay, type,
+ "dom::workers::DummyCallback(2)"));
+
+ if (aMode == PeriodicTimer) {
+ LOG(WorkerLog(), ("Worker %p scheduled periodic GC timer\n", this));
+ mPeriodicGCTimerRunning = true;
+ }
+ else {
+ LOG(WorkerLog(), ("Worker %p scheduled idle GC timer\n", this));
+ mIdleGCTimerRunning = true;
+ }
+}
+
+void
+WorkerPrivate::ShutdownGCTimers()
+{
+ AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(mGCTimer);
+
+ // Always make sure the timer is canceled.
+ MOZ_ALWAYS_SUCCEEDS(mGCTimer->Cancel());
+
+ LOG(WorkerLog(), ("Worker %p killed the GC timer\n", this));
+
+ mGCTimer = nullptr;
+ mPeriodicGCTimerTarget = nullptr;
+ mIdleGCTimerTarget = nullptr;
+ mPeriodicGCTimerRunning = false;
+ mIdleGCTimerRunning = false;
+}
+
+bool
+WorkerPrivate::InterruptCallback(JSContext* aCx)
+{
+ AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+
+ bool mayContinue = true;
+ bool scheduledIdleGC = false;
+
+ for (;;) {
+ // Run all control events now.
+ auto result = ProcessAllControlRunnables();
+ if (result == ProcessAllControlRunnablesResult::Abort) {
+ mayContinue = false;
+ }
+
+ bool mayFreeze = mFrozen;
+ if (mayFreeze) {
+ MutexAutoLock lock(mMutex);
+ mayFreeze = mStatus <= Running;
+ }
+
+ if (!mayContinue || !mayFreeze) {
+ break;
+ }
+
+ // Cancel the periodic GC timer here before freezing. The idle GC timer
+ // will clean everything up once it runs.
+ if (!scheduledIdleGC) {
+ SetGCTimerMode(IdleTimer);
+ scheduledIdleGC = true;
+ }
+
+ while ((mayContinue = MayContinueRunning())) {
+ MutexAutoLock lock(mMutex);
+ if (!mControlQueue.IsEmpty()) {
+ break;
+ }
+
+ WaitForWorkerEvents(PR_MillisecondsToInterval(UINT32_MAX));
+ }
+ }
+
+ if (!mayContinue) {
+ // We want only uncatchable exceptions here.
+ NS_ASSERTION(!JS_IsExceptionPending(aCx),
+ "Should not have an exception set here!");
+ return false;
+ }
+
+ // Make sure the periodic timer gets turned back on here.
+ SetGCTimerMode(PeriodicTimer);
+
+ return true;
+}
+
+nsresult
+WorkerPrivate::IsOnCurrentThread(bool* aIsOnCurrentThread)
+{
+ // May be called on any thread!
+
+ MOZ_ASSERT(aIsOnCurrentThread);
+ MOZ_ASSERT(mPRThread);
+
+ *aIsOnCurrentThread = PR_GetCurrentThread() == mPRThread;
+ return NS_OK;
+}
+
+void
+WorkerPrivate::ScheduleDeletion(WorkerRanOrNot aRanOrNot)
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(mChildWorkers.IsEmpty());
+ MOZ_ASSERT(mSyncLoopStack.IsEmpty());
+ MOZ_ASSERT(!mPendingEventQueueClearing);
+
+ ClearMainEventQueue(aRanOrNot);
+#ifdef DEBUG
+ if (WorkerRan == aRanOrNot) {
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+ MOZ_ASSERT(!NS_HasPendingEvents(currentThread));
+ }
+#endif
+
+ if (WorkerPrivate* parent = GetParent()) {
+ RefPtr<WorkerFinishedRunnable> runnable =
+ new WorkerFinishedRunnable(parent, this);
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to dispatch runnable!");
+ }
+ }
+ else {
+ RefPtr<TopLevelWorkerFinishedRunnable> runnable =
+ new TopLevelWorkerFinishedRunnable(this);
+ if (NS_FAILED(DispatchToMainThread(runnable.forget()))) {
+ NS_WARNING("Failed to dispatch runnable!");
+ }
+ }
+}
+
+bool
+WorkerPrivate::CollectRuntimeStats(JS::RuntimeStats* aRtStats,
+ bool aAnonymize)
+{
+ AssertIsOnWorkerThread();
+ NS_ASSERTION(aRtStats, "Null RuntimeStats!");
+ NS_ASSERTION(mJSContext, "This must never be null!");
+
+ return JS::CollectRuntimeStats(mJSContext, aRtStats, nullptr, aAnonymize);
+}
+
+void
+WorkerPrivate::EnableMemoryReporter()
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(!mMemoryReporter);
+
+ // No need to lock here since the main thread can't race until we've
+ // successfully registered the reporter.
+ mMemoryReporter = new MemoryReporter(this);
+
+ if (NS_FAILED(RegisterWeakAsyncMemoryReporter(mMemoryReporter))) {
+ NS_WARNING("Failed to register memory reporter!");
+ // No need to lock here since a failed registration means our memory
+ // reporter can't start running. Just clean up.
+ mMemoryReporter = nullptr;
+ }
+}
+
+void
+WorkerPrivate::DisableMemoryReporter()
+{
+ AssertIsOnWorkerThread();
+
+ RefPtr<MemoryReporter> memoryReporter;
+ {
+ // Mutex protectes MemoryReporter::mWorkerPrivate which is cleared by
+ // MemoryReporter::Disable() below.
+ MutexAutoLock lock(mMutex);
+
+ // There is nothing to do here if the memory reporter was never successfully
+ // registered.
+ if (!mMemoryReporter) {
+ return;
+ }
+
+ // We don't need this set any longer. Swap it out so that we can unregister
+ // below.
+ mMemoryReporter.swap(memoryReporter);
+
+ // Next disable the memory reporter so that the main thread stops trying to
+ // signal us.
+ memoryReporter->Disable();
+ }
+
+ // Finally unregister the memory reporter.
+ if (NS_FAILED(UnregisterWeakMemoryReporter(memoryReporter))) {
+ NS_WARNING("Failed to unregister memory reporter!");
+ }
+}
+
+void
+WorkerPrivate::WaitForWorkerEvents(PRIntervalTime aInterval)
+{
+ AssertIsOnWorkerThread();
+ mMutex.AssertCurrentThreadOwns();
+
+ // Wait for a worker event.
+ mCondVar.Wait(aInterval);
+}
+
+WorkerPrivate::ProcessAllControlRunnablesResult
+WorkerPrivate::ProcessAllControlRunnablesLocked()
+{
+ AssertIsOnWorkerThread();
+ mMutex.AssertCurrentThreadOwns();
+
+ auto result = ProcessAllControlRunnablesResult::Nothing;
+
+ for (;;) {
+ WorkerControlRunnable* event;
+ if (!mControlQueue.Pop(event)) {
+ break;
+ }
+
+ MutexAutoUnlock unlock(mMutex);
+
+ MOZ_ASSERT(event);
+ if (NS_FAILED(static_cast<nsIRunnable*>(event)->Run())) {
+ result = ProcessAllControlRunnablesResult::Abort;
+ }
+
+ if (result == ProcessAllControlRunnablesResult::Nothing) {
+ // We ran at least one thing.
+ result = ProcessAllControlRunnablesResult::MayContinue;
+ }
+ event->Release();
+ }
+
+ return result;
+}
+
+void
+WorkerPrivate::ClearMainEventQueue(WorkerRanOrNot aRanOrNot)
+{
+ AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(mSyncLoopStack.IsEmpty());
+ MOZ_ASSERT(!mCancelAllPendingRunnables);
+ mCancelAllPendingRunnables = true;
+
+ if (WorkerNeverRan == aRanOrNot) {
+ for (uint32_t count = mPreStartRunnables.Length(), index = 0;
+ index < count;
+ index++) {
+ RefPtr<WorkerRunnable> runnable = mPreStartRunnables[index].forget();
+ static_cast<nsIRunnable*>(runnable.get())->Run();
+ }
+ } else {
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+
+ NS_ProcessPendingEvents(currentThread);
+ }
+
+ MOZ_ASSERT(mCancelAllPendingRunnables);
+ mCancelAllPendingRunnables = false;
+}
+
+void
+WorkerPrivate::ClearDebuggerEventQueue()
+{
+ while (!mDebuggerQueue.IsEmpty()) {
+ WorkerRunnable* runnable;
+ mDebuggerQueue.Pop(runnable);
+ // It should be ok to simply release the runnable, without running it.
+ runnable->Release();
+ }
+}
+
+bool
+WorkerPrivate::FreezeInternal()
+{
+ AssertIsOnWorkerThread();
+
+ NS_ASSERTION(!mFrozen, "Already frozen!");
+
+ mFrozen = true;
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->Freeze(nullptr);
+ }
+
+ return true;
+}
+
+bool
+WorkerPrivate::ThawInternal()
+{
+ AssertIsOnWorkerThread();
+
+ NS_ASSERTION(mFrozen, "Not yet frozen!");
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->Thaw(nullptr);
+ }
+
+ mFrozen = false;
+ return true;
+}
+
+void
+WorkerPrivate::TraverseTimeouts(nsCycleCollectionTraversalCallback& cb)
+{
+ for (uint32_t i = 0; i < mTimeouts.Length(); ++i) {
+ TimeoutInfo* tmp = mTimeouts[i];
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHandler)
+ }
+}
+
+void
+WorkerPrivate::UnlinkTimeouts()
+{
+ mTimeouts.Clear();
+}
+
+bool
+WorkerPrivate::ModifyBusyCountFromWorker(bool aIncrease)
+{
+ AssertIsOnWorkerThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ // If we're in shutdown then the busy count is no longer being considered so
+ // just return now.
+ if (mStatus >= Killing) {
+ return true;
+ }
+ }
+
+ RefPtr<ModifyBusyCountRunnable> runnable =
+ new ModifyBusyCountRunnable(this, aIncrease);
+ return runnable->Dispatch();
+}
+
+bool
+WorkerPrivate::AddChildWorker(ParentType* aChildWorker)
+{
+ AssertIsOnWorkerThread();
+
+#ifdef DEBUG
+ {
+ Status currentStatus;
+ {
+ MutexAutoLock lock(mMutex);
+ currentStatus = mStatus;
+ }
+
+ MOZ_ASSERT(currentStatus == Running);
+ }
+#endif
+
+ NS_ASSERTION(!mChildWorkers.Contains(aChildWorker),
+ "Already know about this one!");
+ mChildWorkers.AppendElement(aChildWorker);
+
+ return mChildWorkers.Length() == 1 ?
+ ModifyBusyCountFromWorker(true) :
+ true;
+}
+
+void
+WorkerPrivate::RemoveChildWorker(ParentType* aChildWorker)
+{
+ AssertIsOnWorkerThread();
+
+ NS_ASSERTION(mChildWorkers.Contains(aChildWorker),
+ "Didn't know about this one!");
+ mChildWorkers.RemoveElement(aChildWorker);
+
+ if (mChildWorkers.IsEmpty() && !ModifyBusyCountFromWorker(false)) {
+ NS_WARNING("Failed to modify busy count!");
+ }
+}
+
+bool
+WorkerPrivate::AddHolder(WorkerHolder* aHolder, Status aFailStatus)
+{
+ AssertIsOnWorkerThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mStatus >= aFailStatus) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(!mHolders.Contains(aHolder), "Already know about this one!");
+
+ if (mHolders.IsEmpty() && !ModifyBusyCountFromWorker(true)) {
+ return false;
+ }
+
+ mHolders.AppendElement(aHolder);
+ return true;
+}
+
+void
+WorkerPrivate::RemoveHolder(WorkerHolder* aHolder)
+{
+ AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(mHolders.Contains(aHolder), "Didn't know about this one!");
+ mHolders.RemoveElement(aHolder);
+
+ if (mHolders.IsEmpty() && !ModifyBusyCountFromWorker(false)) {
+ NS_WARNING("Failed to modify busy count!");
+ }
+}
+
+void
+WorkerPrivate::NotifyHolders(JSContext* aCx, Status aStatus)
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+
+ NS_ASSERTION(aStatus > Running, "Bad status!");
+
+ if (aStatus >= Closing) {
+ CancelAllTimeouts();
+ }
+
+ nsTObserverArray<WorkerHolder*>::ForwardIterator iter(mHolders);
+ while (iter.HasMore()) {
+ WorkerHolder* holder = iter.GetNext();
+ if (!holder->Notify(aStatus)) {
+ NS_WARNING("Failed to notify holder!");
+ }
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+ }
+
+ AutoTArray<ParentType*, 10> children;
+ children.AppendElements(mChildWorkers);
+
+ for (uint32_t index = 0; index < children.Length(); index++) {
+ if (!children[index]->Notify(aStatus)) {
+ NS_WARNING("Failed to notify child worker!");
+ }
+ }
+}
+
+void
+WorkerPrivate::CancelAllTimeouts()
+{
+ AssertIsOnWorkerThread();
+
+ LOG(TimeoutsLog(), ("Worker %p CancelAllTimeouts.\n", this));
+
+ if (mTimerRunning) {
+ NS_ASSERTION(mTimer && mTimerRunnable, "Huh?!");
+ NS_ASSERTION(!mTimeouts.IsEmpty(), "Huh?!");
+
+ if (NS_FAILED(mTimer->Cancel())) {
+ NS_WARNING("Failed to cancel timer!");
+ }
+
+ for (uint32_t index = 0; index < mTimeouts.Length(); index++) {
+ mTimeouts[index]->mCanceled = true;
+ }
+
+ // If mRunningExpiredTimeouts, then the fact that they are all canceled now
+ // means that the currently executing RunExpiredTimeouts will deal with
+ // them. Otherwise, we need to clean them up ourselves.
+ if (!mRunningExpiredTimeouts) {
+ mTimeouts.Clear();
+ ModifyBusyCountFromWorker(false);
+ }
+
+ // Set mTimerRunning false even if mRunningExpiredTimeouts is true, so that
+ // if we get reentered under this same RunExpiredTimeouts call we don't
+ // assert above that !mTimeouts().IsEmpty(), because that's clearly false
+ // now.
+ mTimerRunning = false;
+ }
+#ifdef DEBUG
+ else if (!mRunningExpiredTimeouts) {
+ NS_ASSERTION(mTimeouts.IsEmpty(), "Huh?!");
+ }
+#endif
+
+ mTimer = nullptr;
+ mTimerRunnable = nullptr;
+}
+
+already_AddRefed<nsIEventTarget>
+WorkerPrivate::CreateNewSyncLoop()
+{
+ AssertIsOnWorkerThread();
+
+ nsCOMPtr<nsIThreadInternal> thread = do_QueryInterface(NS_GetCurrentThread());
+ MOZ_ASSERT(thread);
+
+ nsCOMPtr<nsIEventTarget> realEventTarget;
+ MOZ_ALWAYS_SUCCEEDS(thread->PushEventQueue(getter_AddRefs(realEventTarget)));
+
+ RefPtr<EventTarget> workerEventTarget =
+ new EventTarget(this, realEventTarget);
+
+ {
+ // Modifications must be protected by mMutex in DEBUG builds, see comment
+ // about mSyncLoopStack in WorkerPrivate.h.
+#ifdef DEBUG
+ MutexAutoLock lock(mMutex);
+#endif
+
+ mSyncLoopStack.AppendElement(new SyncLoopInfo(workerEventTarget));
+ }
+
+ return workerEventTarget.forget();
+}
+
+bool
+WorkerPrivate::RunCurrentSyncLoop()
+{
+ AssertIsOnWorkerThread();
+
+ JSContext* cx = GetJSContext();
+ MOZ_ASSERT(cx);
+
+ // This should not change between now and the time we finish running this sync
+ // loop.
+ uint32_t currentLoopIndex = mSyncLoopStack.Length() - 1;
+
+ SyncLoopInfo* loopInfo = mSyncLoopStack[currentLoopIndex];
+
+ MOZ_ASSERT(loopInfo);
+ MOZ_ASSERT(!loopInfo->mHasRun);
+ MOZ_ASSERT(!loopInfo->mCompleted);
+
+#ifdef DEBUG
+ loopInfo->mHasRun = true;
+#endif
+
+ while (!loopInfo->mCompleted) {
+ bool normalRunnablesPending = false;
+
+ // Don't block with the periodic GC timer running.
+ if (!NS_HasPendingEvents(mThread)) {
+ SetGCTimerMode(IdleTimer);
+ }
+
+ // Wait for something to do.
+ {
+ MutexAutoLock lock(mMutex);
+
+ for (;;) {
+ while (mControlQueue.IsEmpty() &&
+ !normalRunnablesPending &&
+ !(normalRunnablesPending = NS_HasPendingEvents(mThread))) {
+ WaitForWorkerEvents();
+ }
+
+ auto result = ProcessAllControlRunnablesLocked();
+ if (result != ProcessAllControlRunnablesResult::Nothing) {
+ // XXXkhuey how should we handle Abort here? See Bug 1003730.
+
+ // The state of the world may have changed. Recheck it.
+ normalRunnablesPending = NS_HasPendingEvents(mThread);
+
+ // NB: If we processed a NotifyRunnable, we might have run
+ // non-control runnables, one of which may have shut down the
+ // sync loop.
+ if (loopInfo->mCompleted) {
+ break;
+ }
+ }
+
+ // If we *didn't* run any control runnables, this should be unchanged.
+ MOZ_ASSERT(!loopInfo->mCompleted);
+
+ if (normalRunnablesPending) {
+ break;
+ }
+ }
+ }
+
+ if (normalRunnablesPending) {
+ // Make sure the periodic timer is running before we continue.
+ SetGCTimerMode(PeriodicTimer);
+
+ MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false));
+
+ // Now *might* be a good time to GC. Let the JS engine make the decision.
+ if (JS::CurrentGlobalOrNull(cx)) {
+ JS_MaybeGC(cx);
+ }
+ }
+ }
+
+ // Make sure that the stack didn't change underneath us.
+ MOZ_ASSERT(mSyncLoopStack[currentLoopIndex] == loopInfo);
+
+ return DestroySyncLoop(currentLoopIndex);
+}
+
+bool
+WorkerPrivate::DestroySyncLoop(uint32_t aLoopIndex, nsIThreadInternal* aThread)
+{
+ MOZ_ASSERT(!mSyncLoopStack.IsEmpty());
+ MOZ_ASSERT(mSyncLoopStack.Length() - 1 == aLoopIndex);
+
+ if (!aThread) {
+ aThread = mThread;
+ }
+
+ // We're about to delete the loop, stash its event target and result.
+ SyncLoopInfo* loopInfo = mSyncLoopStack[aLoopIndex];
+ nsIEventTarget* nestedEventTarget =
+ loopInfo->mEventTarget->GetWeakNestedEventTarget();
+ MOZ_ASSERT(nestedEventTarget);
+
+ bool result = loopInfo->mResult;
+
+ {
+ // Modifications must be protected by mMutex in DEBUG builds, see comment
+ // about mSyncLoopStack in WorkerPrivate.h.
+#ifdef DEBUG
+ MutexAutoLock lock(mMutex);
+#endif
+
+ // This will delete |loopInfo|!
+ mSyncLoopStack.RemoveElementAt(aLoopIndex);
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(aThread->PopEventQueue(nestedEventTarget));
+
+ if (mSyncLoopStack.IsEmpty() && mPendingEventQueueClearing) {
+ mPendingEventQueueClearing = false;
+ ClearMainEventQueue(WorkerRan);
+ }
+
+ return result;
+}
+
+void
+WorkerPrivate::StopSyncLoop(nsIEventTarget* aSyncLoopTarget, bool aResult)
+{
+ AssertIsOnWorkerThread();
+ AssertValidSyncLoop(aSyncLoopTarget);
+
+ MOZ_ASSERT(!mSyncLoopStack.IsEmpty());
+
+ for (uint32_t index = mSyncLoopStack.Length(); index > 0; index--) {
+ nsAutoPtr<SyncLoopInfo>& loopInfo = mSyncLoopStack[index - 1];
+ MOZ_ASSERT(loopInfo);
+ MOZ_ASSERT(loopInfo->mEventTarget);
+
+ if (loopInfo->mEventTarget == aSyncLoopTarget) {
+ // Can't assert |loop->mHasRun| here because dispatch failures can cause
+ // us to bail out early.
+ MOZ_ASSERT(!loopInfo->mCompleted);
+
+ loopInfo->mResult = aResult;
+ loopInfo->mCompleted = true;
+
+ loopInfo->mEventTarget->Disable();
+
+ return;
+ }
+
+ MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
+ }
+
+ MOZ_CRASH("Unknown sync loop!");
+}
+
+#ifdef DEBUG
+void
+WorkerPrivate::AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget)
+{
+ MOZ_ASSERT(aSyncLoopTarget);
+
+ EventTarget* workerTarget;
+ nsresult rv =
+ aSyncLoopTarget->QueryInterface(kDEBUGWorkerEventTargetIID,
+ reinterpret_cast<void**>(&workerTarget));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(workerTarget);
+
+ bool valid = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ for (uint32_t index = 0; index < mSyncLoopStack.Length(); index++) {
+ nsAutoPtr<SyncLoopInfo>& loopInfo = mSyncLoopStack[index];
+ MOZ_ASSERT(loopInfo);
+ MOZ_ASSERT(loopInfo->mEventTarget);
+
+ if (loopInfo->mEventTarget == aSyncLoopTarget) {
+ valid = true;
+ break;
+ }
+
+ MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
+ }
+ }
+
+ MOZ_ASSERT(valid);
+}
+#endif
+
+void
+WorkerPrivate::PostMessageToParentInternal(
+ JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv)
+{
+ AssertIsOnWorkerThread();
+
+ JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
+ if (aTransferable.WasPassed()) {
+ const Sequence<JS::Value>& realTransferable = aTransferable.Value();
+
+ // The input sequence only comes from the generated bindings code, which
+ // ensures it is rooted.
+ JS::HandleValueArray elements =
+ JS::HandleValueArray::fromMarkedLocation(realTransferable.Length(),
+ realTransferable.Elements());
+
+ JSObject* array = JS_NewArrayObject(aCx, elements);
+ if (!array) {
+ aRv = NS_ERROR_OUT_OF_MEMORY;
+ return;
+ }
+ transferable.setObject(*array);
+ }
+
+ RefPtr<MessageEventRunnable> runnable =
+ new MessageEventRunnable(this,
+ WorkerRunnable::ParentThreadUnchangedBusyCount);
+
+ UniquePtr<AbstractTimelineMarker> start;
+ UniquePtr<AbstractTimelineMarker> end;
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ bool isTimelineRecording = timelines && !timelines->IsEmpty();
+
+ if (isTimelineRecording) {
+ start = MakeUnique<WorkerTimelineMarker>(NS_IsMainThread()
+ ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+ MarkerTracingType::START);
+ }
+
+ runnable->Write(aCx, aMessage, transferable, JS::CloneDataPolicy(), aRv);
+
+ if (isTimelineRecording) {
+ end = MakeUnique<WorkerTimelineMarker>(NS_IsMainThread()
+ ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+ MarkerTracingType::END);
+ timelines->AddMarkerForAllObservedDocShells(start);
+ timelines->AddMarkerForAllObservedDocShells(end);
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (!runnable->Dispatch()) {
+ aRv = NS_ERROR_FAILURE;
+ }
+}
+
+void
+WorkerPrivate::EnterDebuggerEventLoop()
+{
+ AssertIsOnWorkerThread();
+
+ JSContext* cx = GetJSContext();
+ MOZ_ASSERT(cx);
+
+ uint32_t currentEventLoopLevel = ++mDebuggerEventLoopLevel;
+
+ while (currentEventLoopLevel <= mDebuggerEventLoopLevel) {
+ bool debuggerRunnablesPending = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
+ }
+
+ // Don't block with the periodic GC timer running.
+ if (!debuggerRunnablesPending) {
+ SetGCTimerMode(IdleTimer);
+ }
+
+ // Wait for something to do
+ {
+ MutexAutoLock lock(mMutex);
+
+ while (mControlQueue.IsEmpty() &&
+ !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty())) {
+ WaitForWorkerEvents();
+ }
+
+ ProcessAllControlRunnablesLocked();
+
+ // XXXkhuey should we abort JS on the stack here if we got Abort above?
+ }
+
+ if (debuggerRunnablesPending) {
+ // Start the periodic GC timer if it is not already running.
+ SetGCTimerMode(PeriodicTimer);
+
+ WorkerRunnable* runnable;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ mDebuggerQueue.Pop(runnable);
+ }
+
+ MOZ_ASSERT(runnable);
+ static_cast<nsIRunnable*>(runnable)->Run();
+ runnable->Release();
+
+ // Flush the promise queue.
+ Promise::PerformWorkerDebuggerMicroTaskCheckpoint();
+
+ // Now *might* be a good time to GC. Let the JS engine make the decision.
+ if (JS::CurrentGlobalOrNull(cx)) {
+ JS_MaybeGC(cx);
+ }
+ }
+ }
+}
+
+void
+WorkerPrivate::LeaveDebuggerEventLoop()
+{
+ AssertIsOnWorkerThread();
+
+ MutexAutoLock lock(mMutex);
+
+ if (mDebuggerEventLoopLevel > 0) {
+ --mDebuggerEventLoopLevel;
+ }
+}
+
+void
+WorkerPrivate::PostMessageToDebugger(const nsAString& aMessage)
+{
+ mDebugger->PostMessageToDebugger(aMessage);
+}
+
+void
+WorkerPrivate::SetDebuggerImmediate(dom::Function& aHandler, ErrorResult& aRv)
+{
+ AssertIsOnWorkerThread();
+
+ RefPtr<DebuggerImmediateRunnable> runnable =
+ new DebuggerImmediateRunnable(this, aHandler);
+ if (!runnable->Dispatch()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+void
+WorkerPrivate::ReportErrorToDebugger(const nsAString& aFilename,
+ uint32_t aLineno,
+ const nsAString& aMessage)
+{
+ mDebugger->ReportErrorToDebugger(aFilename, aLineno, aMessage);
+}
+
+bool
+WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus)
+{
+ AssertIsOnWorkerThread();
+
+ NS_ASSERTION(aStatus > Running && aStatus < Dead, "Bad status!");
+
+ RefPtr<EventTarget> eventTarget;
+
+ // Save the old status and set the new status.
+ Status previousStatus;
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mStatus >= aStatus) {
+ MOZ_ASSERT(!mEventTarget);
+ return true;
+ }
+
+ previousStatus = mStatus;
+ mStatus = aStatus;
+
+ mEventTarget.swap(eventTarget);
+ }
+
+ // Now that mStatus > Running, no-one can create a new WorkerEventTarget or
+ // WorkerCrossThreadDispatcher if we don't already have one.
+ if (eventTarget) {
+ // Since we'll no longer process events, make sure we no longer allow anyone
+ // to post them. We have to do this without mMutex held, since our mutex
+ // must be acquired *after* the WorkerEventTarget's mutex when they're both
+ // held.
+ eventTarget->Disable();
+ eventTarget = nullptr;
+ }
+
+ if (mCrossThreadDispatcher) {
+ // Since we'll no longer process events, make sure we no longer allow
+ // anyone to post them. We have to do this without mMutex held, since our
+ // mutex must be acquired *after* mCrossThreadDispatcher's mutex when
+ // they're both held.
+ mCrossThreadDispatcher->Forget();
+ mCrossThreadDispatcher = nullptr;
+ }
+
+ MOZ_ASSERT(previousStatus != Pending);
+
+ // Let all our holders know the new status.
+ NotifyHolders(aCx, aStatus);
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+
+ // If this is the first time our status has changed then we need to clear the
+ // main event queue.
+ if (previousStatus == Running) {
+ // NB: If we're in a sync loop, we can't clear the queue immediately,
+ // because this is the wrong queue. So we have to defer it until later.
+ if (!mSyncLoopStack.IsEmpty()) {
+ mPendingEventQueueClearing = true;
+ } else {
+ ClearMainEventQueue(WorkerRan);
+ }
+ }
+
+ // If the worker script never ran, or failed to compile, we don't need to do
+ // anything else.
+ if (!GlobalScope()) {
+ return true;
+ }
+
+ if (aStatus == Closing) {
+ // Notify parent to stop sending us messages and balance our busy count.
+ RefPtr<CloseRunnable> runnable = new CloseRunnable(this);
+ if (!runnable->Dispatch()) {
+ return false;
+ }
+
+ // Don't abort the script.
+ return true;
+ }
+
+ MOZ_ASSERT(aStatus == Terminating ||
+ aStatus == Canceling ||
+ aStatus == Killing);
+
+ // Always abort the script.
+ return false;
+}
+
+void
+WorkerPrivate::ReportError(JSContext* aCx, JS::ConstUTF8CharsZ aToStringResult,
+ JSErrorReport* aReport)
+{
+ AssertIsOnWorkerThread();
+
+ if (!MayContinueRunning() || mErrorHandlerRecursionCount == 2) {
+ return;
+ }
+
+ NS_ASSERTION(mErrorHandlerRecursionCount == 0 ||
+ mErrorHandlerRecursionCount == 1,
+ "Bad recursion logic!");
+
+ JS::Rooted<JS::Value> exn(aCx);
+ if (!JS_GetPendingException(aCx, &exn)) {
+ // Probably shouldn't actually happen? But let's go ahead and just use null
+ // for lack of anything better.
+ exn.setNull();
+ }
+ JS_ClearPendingException(aCx);
+
+ nsString message, filename, line;
+ uint32_t lineNumber, columnNumber, flags, errorNumber;
+ JSExnType exnType = JSEXN_ERR;
+ bool mutedError = aReport && aReport->isMuted;
+
+ if (aReport) {
+ // We want the same behavior here as xpc::ErrorReport::init here.
+ xpc::ErrorReport::ErrorReportToMessageString(aReport, message);
+
+ filename = NS_ConvertUTF8toUTF16(aReport->filename);
+ line.Assign(aReport->linebuf(), aReport->linebufLength());
+ lineNumber = aReport->lineno;
+ columnNumber = aReport->tokenOffset();
+ flags = aReport->flags;
+ errorNumber = aReport->errorNumber;
+ MOZ_ASSERT(aReport->exnType >= JSEXN_FIRST && aReport->exnType < JSEXN_LIMIT);
+ exnType = JSExnType(aReport->exnType);
+ }
+ else {
+ lineNumber = columnNumber = errorNumber = 0;
+ flags = nsIScriptError::errorFlag | nsIScriptError::exceptionFlag;
+ }
+
+ if (message.IsEmpty() && aToStringResult) {
+ nsDependentCString toStringResult(aToStringResult.c_str());
+ if (!AppendUTF8toUTF16(toStringResult, message, mozilla::fallible)) {
+ // Try again, with only a 1 KB string. Do this infallibly this time.
+ // If the user doesn't have 1 KB to spare we're done anyways.
+ uint32_t index = std::min(uint32_t(1024), toStringResult.Length());
+
+ // Drop the last code point that may be cropped.
+ index = RewindToPriorUTF8Codepoint(toStringResult.BeginReading(), index);
+
+ nsDependentCString truncatedToStringResult(aToStringResult.c_str(),
+ index);
+ AppendUTF8toUTF16(truncatedToStringResult, message);
+ }
+ }
+
+ mErrorHandlerRecursionCount++;
+
+ // Don't want to run the scope's error handler if this is a recursive error or
+ // if we ran out of memory.
+ bool fireAtScope = mErrorHandlerRecursionCount == 1 &&
+ errorNumber != JSMSG_OUT_OF_MEMORY &&
+ JS::CurrentGlobalOrNull(aCx);
+
+ ReportErrorRunnable::ReportError(aCx, this, fireAtScope, nullptr, message,
+ filename, line, lineNumber,
+ columnNumber, flags, errorNumber, exnType,
+ mutedError, 0, exn);
+
+ mErrorHandlerRecursionCount--;
+}
+
+// static
+void
+WorkerPrivate::ReportErrorToConsole(const char* aMessage)
+{
+ WorkerPrivate* wp = nullptr;
+ if (!NS_IsMainThread()) {
+ wp = GetCurrentThreadWorkerPrivate();
+ }
+
+ ReportErrorToConsoleRunnable::Report(wp, aMessage);
+}
+
+int32_t
+WorkerPrivate::SetTimeout(JSContext* aCx,
+ nsIScriptTimeoutHandler* aHandler,
+ int32_t aTimeout, bool aIsInterval,
+ ErrorResult& aRv)
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(aHandler);
+
+ const int32_t timerId = mNextTimeoutId++;
+
+ Status currentStatus;
+ {
+ MutexAutoLock lock(mMutex);
+ currentStatus = mStatus;
+ }
+
+ // If the worker is trying to call setTimeout/setInterval and the parent
+ // thread has initiated the close process then just silently fail.
+ if (currentStatus >= Closing) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return 0;
+ }
+
+ nsAutoPtr<TimeoutInfo> newInfo(new TimeoutInfo());
+ newInfo->mIsInterval = aIsInterval;
+ newInfo->mId = timerId;
+
+ if (MOZ_UNLIKELY(timerId == INT32_MAX)) {
+ NS_WARNING("Timeout ids overflowed!");
+ mNextTimeoutId = 1;
+ }
+
+ newInfo->mHandler = aHandler;
+
+ // See if any of the optional arguments were passed.
+ aTimeout = std::max(0, aTimeout);
+ newInfo->mInterval = TimeDuration::FromMilliseconds(aTimeout);
+
+ newInfo->mTargetTime = TimeStamp::Now() + newInfo->mInterval;
+
+ nsAutoPtr<TimeoutInfo>* insertedInfo =
+ mTimeouts.InsertElementSorted(newInfo.forget(), GetAutoPtrComparator(mTimeouts));
+
+ LOG(TimeoutsLog(), ("Worker %p has new timeout: delay=%d interval=%s\n",
+ this, aTimeout, aIsInterval ? "yes" : "no"));
+
+ // If the timeout we just made is set to fire next then we need to update the
+ // timer, unless we're currently running timeouts.
+ if (insertedInfo == mTimeouts.Elements() && !mRunningExpiredTimeouts) {
+ nsresult rv;
+
+ if (!mTimer) {
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return 0;
+ }
+
+ mTimerRunnable = new TimerRunnable(this);
+ }
+
+ if (!mTimerRunning) {
+ if (!ModifyBusyCountFromWorker(true)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return 0;
+ }
+ mTimerRunning = true;
+ }
+
+ if (!RescheduleTimeoutTimer(aCx)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return 0;
+ }
+ }
+
+ return timerId;
+}
+
+void
+WorkerPrivate::ClearTimeout(int32_t aId)
+{
+ AssertIsOnWorkerThread();
+
+ if (!mTimeouts.IsEmpty()) {
+ NS_ASSERTION(mTimerRunning, "Huh?!");
+
+ for (uint32_t index = 0; index < mTimeouts.Length(); index++) {
+ nsAutoPtr<TimeoutInfo>& info = mTimeouts[index];
+ if (info->mId == aId) {
+ info->mCanceled = true;
+ break;
+ }
+ }
+ }
+}
+
+bool
+WorkerPrivate::RunExpiredTimeouts(JSContext* aCx)
+{
+ AssertIsOnWorkerThread();
+
+ // We may be called recursively (e.g. close() inside a timeout) or we could
+ // have been canceled while this event was pending, bail out if there is
+ // nothing to do.
+ if (mRunningExpiredTimeouts || !mTimerRunning) {
+ return true;
+ }
+
+ NS_ASSERTION(mTimer && mTimerRunnable, "Must have a timer!");
+ NS_ASSERTION(!mTimeouts.IsEmpty(), "Should have some work to do!");
+
+ bool retval = true;
+
+ AutoPtrComparator<TimeoutInfo> comparator = GetAutoPtrComparator(mTimeouts);
+ JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+
+ // We want to make sure to run *something*, even if the timer fired a little
+ // early. Fudge the value of now to at least include the first timeout.
+ const TimeStamp actual_now = TimeStamp::Now();
+ const TimeStamp now = std::max(actual_now, mTimeouts[0]->mTargetTime);
+
+ if (now != actual_now) {
+ LOG(TimeoutsLog(), ("Worker %p fudged timeout by %f ms.\n", this,
+ (now - actual_now).ToMilliseconds()));
+ }
+
+ AutoTArray<TimeoutInfo*, 10> expiredTimeouts;
+ for (uint32_t index = 0; index < mTimeouts.Length(); index++) {
+ nsAutoPtr<TimeoutInfo>& info = mTimeouts[index];
+ if (info->mTargetTime > now) {
+ break;
+ }
+ expiredTimeouts.AppendElement(info);
+ }
+
+ // Guard against recursion.
+ mRunningExpiredTimeouts = true;
+
+ // Run expired timeouts.
+ for (uint32_t index = 0; index < expiredTimeouts.Length(); index++) {
+ TimeoutInfo*& info = expiredTimeouts[index];
+
+ if (info->mCanceled) {
+ continue;
+ }
+
+ LOG(TimeoutsLog(), ("Worker %p executing timeout with original delay %f ms.\n",
+ this, info->mInterval.ToMilliseconds()));
+
+ // Always check JS_IsExceptionPending if something fails, and if
+ // JS_IsExceptionPending returns false (i.e. uncatchable exception) then
+ // break out of the loop.
+ const char *reason;
+ if (info->mIsInterval) {
+ reason = "setInterval handler";
+ } else {
+ reason = "setTimeout handler";
+ }
+
+ RefPtr<Function> callback = info->mHandler->GetCallback();
+ if (!callback) {
+ // scope for the AutoEntryScript, so it comes off the stack before we do
+ // Promise::PerformMicroTaskCheckpoint.
+ AutoEntryScript aes(global, reason, false);
+
+ // Evaluate the timeout expression.
+ const nsAString& script = info->mHandler->GetHandlerText();
+
+ const char* filename = nullptr;
+ uint32_t lineNo = 0, dummyColumn = 0;
+ info->mHandler->GetLocation(&filename, &lineNo, &dummyColumn);
+
+ JS::CompileOptions options(aes.cx());
+ options.setFileAndLine(filename, lineNo).setNoScriptRval(true);
+
+ JS::Rooted<JS::Value> unused(aes.cx());
+
+ if (!JS::Evaluate(aes.cx(), options, script.BeginReading(),
+ script.Length(), &unused) &&
+ !JS_IsExceptionPending(aCx)) {
+ retval = false;
+ break;
+ }
+ } else {
+ ErrorResult rv;
+ JS::Rooted<JS::Value> ignoredVal(aCx);
+ callback->Call(GlobalScope(), info->mHandler->GetArgs(), &ignoredVal, rv,
+ reason);
+ if (rv.IsUncatchableException()) {
+ rv.SuppressException();
+ retval = false;
+ break;
+ }
+
+ rv.SuppressException();
+ }
+
+ // Since we might be processing more timeouts, go ahead and flush
+ // the promise queue now before we do that.
+ Promise::PerformWorkerMicroTaskCheckpoint();
+
+ NS_ASSERTION(mRunningExpiredTimeouts, "Someone changed this!");
+ }
+
+ // No longer possible to be called recursively.
+ mRunningExpiredTimeouts = false;
+
+ // Now remove canceled and expired timeouts from the main list.
+ // NB: The timeouts present in expiredTimeouts must have the same order
+ // with respect to each other in mTimeouts. That is, mTimeouts is just
+ // expiredTimeouts with extra elements inserted. There may be unexpired
+ // timeouts that have been inserted between the expired timeouts if the
+ // timeout event handler called setTimeout/setInterval.
+ for (uint32_t index = 0, expiredTimeoutIndex = 0,
+ expiredTimeoutLength = expiredTimeouts.Length();
+ index < mTimeouts.Length(); ) {
+ nsAutoPtr<TimeoutInfo>& info = mTimeouts[index];
+ if ((expiredTimeoutIndex < expiredTimeoutLength &&
+ info == expiredTimeouts[expiredTimeoutIndex] &&
+ ++expiredTimeoutIndex) ||
+ info->mCanceled) {
+ if (info->mIsInterval && !info->mCanceled) {
+ // Reschedule intervals.
+ info->mTargetTime = info->mTargetTime + info->mInterval;
+ // Don't resort the list here, we'll do that at the end.
+ ++index;
+ }
+ else {
+ mTimeouts.RemoveElement(info);
+ }
+ }
+ else {
+ // If info did not match the current entry in expiredTimeouts, it
+ // shouldn't be there at all.
+ NS_ASSERTION(!expiredTimeouts.Contains(info),
+ "Our timeouts are out of order!");
+ ++index;
+ }
+ }
+
+ mTimeouts.Sort(comparator);
+
+ // Either signal the parent that we're no longer using timeouts or reschedule
+ // the timer.
+ if (mTimeouts.IsEmpty()) {
+ if (!ModifyBusyCountFromWorker(false)) {
+ retval = false;
+ }
+ mTimerRunning = false;
+ }
+ else if (retval && !RescheduleTimeoutTimer(aCx)) {
+ retval = false;
+ }
+
+ return retval;
+}
+
+bool
+WorkerPrivate::RescheduleTimeoutTimer(JSContext* aCx)
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(!mRunningExpiredTimeouts);
+ NS_ASSERTION(!mTimeouts.IsEmpty(), "Should have some timeouts!");
+ NS_ASSERTION(mTimer && mTimerRunnable, "Should have a timer!");
+
+ // NB: This is important! The timer may have already fired, e.g. if a timeout
+ // callback itself calls setTimeout for a short duration and then takes longer
+ // than that to finish executing. If that has happened, it's very important
+ // that we don't execute the event that is now pending in our event queue, or
+ // our code in RunExpiredTimeouts to "fudge" the timeout value will unleash an
+ // early timeout when we execute the event we're about to queue.
+ mTimer->Cancel();
+
+ double delta =
+ (mTimeouts[0]->mTargetTime - TimeStamp::Now()).ToMilliseconds();
+ uint32_t delay = delta > 0 ? std::min(delta, double(UINT32_MAX)) : 0;
+
+ LOG(TimeoutsLog(), ("Worker %p scheduled timer for %d ms, %d pending timeouts\n",
+ this, delay, mTimeouts.Length()));
+
+ nsresult rv = mTimer->InitWithCallback(mTimerRunnable, delay, nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ JS_ReportErrorASCII(aCx, "Failed to start timer!");
+ return false;
+ }
+
+ return true;
+}
+
+void
+WorkerPrivate::UpdateContextOptionsInternal(
+ JSContext* aCx,
+ const JS::ContextOptions& aContextOptions)
+{
+ AssertIsOnWorkerThread();
+
+ JS::ContextOptionsRef(aCx) = aContextOptions;
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->UpdateContextOptions(aContextOptions);
+ }
+}
+
+void
+WorkerPrivate::UpdateLanguagesInternal(const nsTArray<nsString>& aLanguages)
+{
+ WorkerGlobalScope* globalScope = GlobalScope();
+ if (globalScope) {
+ RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
+ if (nav) {
+ nav->SetLanguages(aLanguages);
+ }
+ }
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->UpdateLanguages(aLanguages);
+ }
+}
+
+void
+WorkerPrivate::UpdatePreferenceInternal(WorkerPreference aPref, bool aValue)
+{
+ AssertIsOnWorkerThread();
+ MOZ_ASSERT(aPref >= 0 && aPref < WORKERPREF_COUNT);
+
+ mPreferences[aPref] = aValue;
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->UpdatePreference(aPref, aValue);
+ }
+}
+
+void
+WorkerPrivate::UpdateJSWorkerMemoryParameterInternal(JSContext* aCx,
+ JSGCParamKey aKey,
+ uint32_t aValue)
+{
+ AssertIsOnWorkerThread();
+
+ // XXX aValue might be 0 here (telling us to unset a previous value for child
+ // workers). Calling JS_SetGCParameter with a value of 0 isn't actually
+ // supported though. We really need some way to revert to a default value
+ // here.
+ if (aValue) {
+ JS_SetGCParameter(aCx, aKey, aValue);
+ }
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->UpdateJSWorkerMemoryParameter(aKey, aValue);
+ }
+}
+
+#ifdef JS_GC_ZEAL
+void
+WorkerPrivate::UpdateGCZealInternal(JSContext* aCx, uint8_t aGCZeal,
+ uint32_t aFrequency)
+{
+ AssertIsOnWorkerThread();
+
+ JS_SetGCZeal(aCx, aGCZeal, aFrequency);
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->UpdateGCZeal(aGCZeal, aFrequency);
+ }
+}
+#endif
+
+void
+WorkerPrivate::GarbageCollectInternal(JSContext* aCx, bool aShrinking,
+ bool aCollectChildren)
+{
+ AssertIsOnWorkerThread();
+
+ if (!GlobalScope()) {
+ // We haven't compiled anything yet. Just bail out.
+ return;
+ }
+
+ if (aShrinking || aCollectChildren) {
+ JS::PrepareForFullGC(aCx);
+
+ if (aShrinking) {
+ JS::GCForReason(aCx, GC_SHRINK, JS::gcreason::DOM_WORKER);
+
+ if (!aCollectChildren) {
+ LOG(WorkerLog(), ("Worker %p collected idle garbage\n", this));
+ }
+ }
+ else {
+ JS::GCForReason(aCx, GC_NORMAL, JS::gcreason::DOM_WORKER);
+ LOG(WorkerLog(), ("Worker %p collected garbage\n", this));
+ }
+ }
+ else {
+ JS_MaybeGC(aCx);
+ LOG(WorkerLog(), ("Worker %p collected periodic garbage\n", this));
+ }
+
+ if (aCollectChildren) {
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->GarbageCollect(aShrinking);
+ }
+ }
+}
+
+void
+WorkerPrivate::CycleCollectInternal(bool aCollectChildren)
+{
+ AssertIsOnWorkerThread();
+
+ nsCycleCollector_collect(nullptr);
+
+ if (aCollectChildren) {
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->CycleCollect(/* dummy = */ false);
+ }
+ }
+}
+
+void
+WorkerPrivate::MemoryPressureInternal()
+{
+ AssertIsOnWorkerThread();
+
+ RefPtr<Console> console = mScope ? mScope->GetConsoleIfExists() : nullptr;
+ if (console) {
+ console->ClearStorage();
+ }
+
+ console = mDebuggerScope ? mDebuggerScope->GetConsoleIfExists() : nullptr;
+ if (console) {
+ console->ClearStorage();
+ }
+
+ for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+ mChildWorkers[index]->MemoryPressure(false);
+ }
+}
+
+void
+WorkerPrivate::SetThread(WorkerThread* aThread)
+{
+ if (aThread) {
+#ifdef DEBUG
+ {
+ bool isOnCurrentThread;
+ MOZ_ASSERT(NS_SUCCEEDED(aThread->IsOnCurrentThread(&isOnCurrentThread)));
+ MOZ_ASSERT(isOnCurrentThread);
+ }
+#endif
+
+ MOZ_ASSERT(!mPRThread);
+ mPRThread = PRThreadFromThread(aThread);
+ MOZ_ASSERT(mPRThread);
+ }
+ else {
+ MOZ_ASSERT(mPRThread);
+ }
+
+ const WorkerThreadFriendKey friendKey;
+
+ RefPtr<WorkerThread> doomedThread;
+
+ { // Scope so that |doomedThread| is released without holding the lock.
+ MutexAutoLock lock(mMutex);
+
+ if (aThread) {
+ MOZ_ASSERT(!mThread);
+ MOZ_ASSERT(mStatus == Pending);
+
+ mThread = aThread;
+ mThread->SetWorker(friendKey, this);
+
+ if (!mPreStartRunnables.IsEmpty()) {
+ for (uint32_t index = 0; index < mPreStartRunnables.Length(); index++) {
+ MOZ_ALWAYS_SUCCEEDS(
+ mThread->DispatchAnyThread(friendKey, mPreStartRunnables[index].forget()));
+ }
+ mPreStartRunnables.Clear();
+ }
+ }
+ else {
+ MOZ_ASSERT(mThread);
+
+ mThread->SetWorker(friendKey, nullptr);
+
+ mThread.swap(doomedThread);
+ }
+ }
+}
+
+WorkerCrossThreadDispatcher*
+WorkerPrivate::GetCrossThreadDispatcher()
+{
+ MutexAutoLock lock(mMutex);
+
+ if (!mCrossThreadDispatcher && mStatus <= Running) {
+ mCrossThreadDispatcher = new WorkerCrossThreadDispatcher(this);
+ }
+
+ return mCrossThreadDispatcher;
+}
+
+void
+WorkerPrivate::BeginCTypesCall()
+{
+ AssertIsOnWorkerThread();
+
+ // Don't try to GC while we're blocked in a ctypes call.
+ SetGCTimerMode(NoTimer);
+}
+
+void
+WorkerPrivate::EndCTypesCall()
+{
+ AssertIsOnWorkerThread();
+
+ // Make sure the periodic timer is running before we start running JS again.
+ SetGCTimerMode(PeriodicTimer);
+}
+
+bool
+WorkerPrivate::ConnectMessagePort(JSContext* aCx,
+ MessagePortIdentifier& aIdentifier)
+{
+ AssertIsOnWorkerThread();
+
+ WorkerGlobalScope* globalScope = GlobalScope();
+
+ JS::Rooted<JSObject*> jsGlobal(aCx, globalScope->GetWrapper());
+ MOZ_ASSERT(jsGlobal);
+
+ // This MessagePortIdentifier is used to create a new port, still connected
+ // with the other one, but in the worker thread.
+ ErrorResult rv;
+ RefPtr<MessagePort> port = MessagePort::Create(globalScope, aIdentifier, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ GlobalObject globalObject(aCx, jsGlobal);
+ if (globalObject.Failed()) {
+ return false;
+ }
+
+ RootedDictionary<MessageEventInit> init(aCx);
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mSource.SetValue().SetAsMessagePort() = port;
+ if (!init.mPorts.AppendElement(port.forget(), fallible)) {
+ return false;
+ }
+
+ RefPtr<MessageEvent> event =
+ MessageEvent::Constructor(globalObject,
+ NS_LITERAL_STRING("connect"), init, rv);
+
+ event->SetTrusted(true);
+
+ nsCOMPtr<nsIDOMEvent> domEvent = do_QueryObject(event);
+
+ nsEventStatus dummy = nsEventStatus_eIgnore;
+ globalScope->DispatchDOMEvent(nullptr, domEvent, nullptr, &dummy);
+
+ return true;
+}
+
+WorkerGlobalScope*
+WorkerPrivate::GetOrCreateGlobalScope(JSContext* aCx)
+{
+ AssertIsOnWorkerThread();
+
+ if (!mScope) {
+ RefPtr<WorkerGlobalScope> globalScope;
+ if (IsSharedWorker()) {
+ globalScope = new SharedWorkerGlobalScope(this, WorkerName());
+ } else if (IsServiceWorker()) {
+ globalScope = new ServiceWorkerGlobalScope(this, WorkerName());
+ } else {
+ globalScope = new DedicatedWorkerGlobalScope(this);
+ }
+
+ JS::Rooted<JSObject*> global(aCx);
+ NS_ENSURE_TRUE(globalScope->WrapGlobalObject(aCx, &global), nullptr);
+
+ JSAutoCompartment ac(aCx, global);
+
+ // RegisterBindings() can spin a nested event loop so we have to set mScope
+ // before calling it, and we have to make sure to unset mScope if it fails.
+ mScope = Move(globalScope);
+
+ if (!RegisterBindings(aCx, global)) {
+ mScope = nullptr;
+ return nullptr;
+ }
+
+ JS_FireOnNewGlobalObject(aCx, global);
+ }
+
+ return mScope;
+}
+
+WorkerDebuggerGlobalScope*
+WorkerPrivate::CreateDebuggerGlobalScope(JSContext* aCx)
+{
+ AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(!mDebuggerScope);
+
+ RefPtr<WorkerDebuggerGlobalScope> globalScope =
+ new WorkerDebuggerGlobalScope(this);
+
+ JS::Rooted<JSObject*> global(aCx);
+ NS_ENSURE_TRUE(globalScope->WrapGlobalObject(aCx, &global), nullptr);
+
+ JSAutoCompartment ac(aCx, global);
+
+ // RegisterDebuggerBindings() can spin a nested event loop so we have to set
+ // mDebuggerScope before calling it, and we have to make sure to unset
+ // mDebuggerScope if it fails.
+ mDebuggerScope = Move(globalScope);
+
+ if (!RegisterDebuggerBindings(aCx, global)) {
+ mDebuggerScope = nullptr;
+ return nullptr;
+ }
+
+ JS_FireOnNewGlobalObject(aCx, global);
+
+ return mDebuggerScope;
+}
+
+#ifdef DEBUG
+
+void
+WorkerPrivate::AssertIsOnWorkerThread() const
+{
+ // This is much more complicated than it needs to be but we can't use mThread
+ // because it must be protected by mMutex and sometimes this method is called
+ // when mMutex is already locked. This method should always work.
+ MOZ_ASSERT(mPRThread,
+ "AssertIsOnWorkerThread() called before a thread was assigned!");
+
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv =
+ nsThreadManager::get().GetThreadFromPRThread(mPRThread,
+ getter_AddRefs(thread));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(thread);
+
+ bool current;
+ rv = thread->IsOnCurrentThread(&current);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(current, "Wrong thread!");
+}
+
+#endif // DEBUG
+
+NS_IMPL_ISUPPORTS_INHERITED0(ExternalRunnableWrapper, WorkerRunnable)
+
+template <class Derived>
+NS_IMPL_ADDREF(WorkerPrivateParent<Derived>::EventTarget)
+
+template <class Derived>
+NS_IMPL_RELEASE(WorkerPrivateParent<Derived>::EventTarget)
+
+template <class Derived>
+NS_INTERFACE_MAP_BEGIN(WorkerPrivateParent<Derived>::EventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+#ifdef DEBUG
+ // kDEBUGWorkerEventTargetIID is special in that it does not AddRef its
+ // result.
+ if (aIID.Equals(kDEBUGWorkerEventTargetIID)) {
+ *aInstancePtr = this;
+ return NS_OK;
+ }
+ else
+#endif
+NS_INTERFACE_MAP_END
+
+template <class Derived>
+NS_IMETHODIMP
+WorkerPrivateParent<Derived>::
+EventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags)
+{
+ nsCOMPtr<nsIRunnable> event(aRunnable);
+ return Dispatch(event.forget(), aFlags);
+}
+
+template <class Derived>
+NS_IMETHODIMP
+WorkerPrivateParent<Derived>::
+EventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags)
+{
+ // May be called on any thread!
+ nsCOMPtr<nsIRunnable> event(aRunnable);
+
+ // Workers only support asynchronous dispatch for now.
+ if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<WorkerRunnable> workerRunnable;
+
+ MutexAutoLock lock(mMutex);
+
+ if (!mWorkerPrivate) {
+ NS_WARNING("A runnable was posted to a worker that is already shutting "
+ "down!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (event) {
+ workerRunnable = mWorkerPrivate->MaybeWrapAsWorkerRunnable(event.forget());
+ }
+
+ nsresult rv =
+ mWorkerPrivate->DispatchPrivate(workerRunnable.forget(), mNestedEventTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+template <class Derived>
+NS_IMETHODIMP
+WorkerPrivateParent<Derived>::
+EventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+template <class Derived>
+NS_IMETHODIMP
+WorkerPrivateParent<Derived>::
+EventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread)
+{
+ // May be called on any thread!
+
+ MOZ_ASSERT(aIsOnCurrentThread);
+
+ MutexAutoLock lock(mMutex);
+
+ if (!mWorkerPrivate) {
+ NS_WARNING("A worker's event target was used after the worker has !");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = mWorkerPrivate->IsOnCurrentThread(aIsOnCurrentThread);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+BEGIN_WORKERS_NAMESPACE
+
+WorkerCrossThreadDispatcher*
+GetWorkerCrossThreadDispatcher(JSContext* aCx, const JS::Value& aWorker)
+{
+ if (!aWorker.isObject()) {
+ return nullptr;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, &aWorker.toObject());
+ WorkerPrivate* w = nullptr;
+ UNWRAP_OBJECT(Worker, &obj, w);
+ MOZ_ASSERT(w);
+ return w->GetCrossThreadDispatcher();
+}
+
+// Force instantiation.
+template class WorkerPrivateParent<WorkerPrivate>;
+
+END_WORKERS_NAMESPACE
diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h
new file mode 100644
index 000000000..ad906b054
--- /dev/null
+++ b/dom/workers/WorkerPrivate.h
@@ -0,0 +1,1594 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_workerprivate_h__
+#define mozilla_dom_workers_workerprivate_h__
+
+#include "Workers.h"
+
+#include "js/CharacterEncoding.h"
+#include "nsIContentPolicy.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsILoadGroup.h"
+#include "nsIWorkerDebugger.h"
+#include "nsPIDOMWindow.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/Move.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsAutoPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "nsRefPtrHashtable.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsTObserverArray.h"
+
+#include "Queue.h"
+#include "WorkerHolder.h"
+
+#ifdef XP_WIN
+#undef PostMessage
+#endif
+
+class nsIChannel;
+class nsIConsoleReportCollector;
+class nsIDocument;
+class nsIEventTarget;
+class nsIPrincipal;
+class nsIScriptContext;
+class nsIScriptTimeoutHandler;
+class nsISerializable;
+class nsIThread;
+class nsIThreadInternal;
+class nsITimer;
+class nsIURI;
+template<class T> class nsMainThreadPtrHandle;
+
+namespace JS {
+struct RuntimeStats;
+} // namespace JS
+
+namespace mozilla {
+class ThrottledEventQueue;
+namespace dom {
+class Function;
+class MessagePort;
+class MessagePortIdentifier;
+class PromiseNativeHandler;
+class StructuredCloneHolder;
+class WorkerDebuggerGlobalScope;
+class WorkerGlobalScope;
+} // namespace dom
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+} // namespace mozilla
+
+struct PRThread;
+
+class ReportDebuggerErrorRunnable;
+class PostDebuggerMessageRunnable;
+
+BEGIN_WORKERS_NAMESPACE
+
+class AutoSyncLoopHolder;
+class SharedWorker;
+class ServiceWorkerClientInfo;
+class WorkerControlRunnable;
+class WorkerDebugger;
+class WorkerPrivate;
+class WorkerRunnable;
+class WorkerThread;
+
+// SharedMutex is a small wrapper around an (internal) reference-counted Mutex
+// object. It exists to avoid changing a lot of code to use Mutex* instead of
+// Mutex&.
+class SharedMutex
+{
+ typedef mozilla::Mutex Mutex;
+
+ class RefCountedMutex final : public Mutex
+ {
+ public:
+ explicit RefCountedMutex(const char* aName)
+ : Mutex(aName)
+ { }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefCountedMutex)
+
+ private:
+ ~RefCountedMutex()
+ { }
+ };
+
+ RefPtr<RefCountedMutex> mMutex;
+
+public:
+ explicit SharedMutex(const char* aName)
+ : mMutex(new RefCountedMutex(aName))
+ { }
+
+ SharedMutex(SharedMutex& aOther)
+ : mMutex(aOther.mMutex)
+ { }
+
+ operator Mutex&()
+ {
+ return *mMutex;
+ }
+
+ operator const Mutex&() const
+ {
+ return *mMutex;
+ }
+
+ void
+ AssertCurrentThreadOwns() const
+ {
+ mMutex->AssertCurrentThreadOwns();
+ }
+};
+
+template <class Derived>
+class WorkerPrivateParent : public DOMEventTargetHelper
+{
+protected:
+ class EventTarget;
+ friend class EventTarget;
+
+ typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
+
+public:
+ struct LocationInfo
+ {
+ nsCString mHref;
+ nsCString mProtocol;
+ nsCString mHost;
+ nsCString mHostname;
+ nsCString mPort;
+ nsCString mPathname;
+ nsCString mSearch;
+ nsCString mHash;
+ nsString mOrigin;
+ };
+
+protected:
+ typedef mozilla::ErrorResult ErrorResult;
+
+ SharedMutex mMutex;
+ mozilla::CondVar mCondVar;
+
+ // Protected by mMutex.
+ RefPtr<EventTarget> mEventTarget;
+ nsTArray<RefPtr<WorkerRunnable>> mPreStartRunnables;
+
+private:
+ WorkerPrivate* mParent;
+ nsString mScriptURL;
+ // This is the worker name for shared workers or the worker scope
+ // for service workers.
+ nsCString mWorkerName;
+ LocationInfo mLocationInfo;
+ // The lifetime of these objects within LoadInfo is managed explicitly;
+ // they do not need to be cycle collected.
+ WorkerLoadInfo mLoadInfo;
+
+ Atomic<bool> mLoadingWorkerScript;
+
+ // Only used for top level workers.
+ nsTArray<nsCOMPtr<nsIRunnable>> mQueuedRunnables;
+
+ // Protected by mMutex.
+ JSSettings mJSSettings;
+
+ // Only touched on the parent thread (currently this is always the main
+ // thread as SharedWorkers are always top-level).
+ nsTArray<RefPtr<SharedWorker>> mSharedWorkers;
+
+ uint64_t mBusyCount;
+ // SharedWorkers may have multiple windows paused, so this must be
+ // a count instead of just a boolean.
+ uint32_t mParentWindowPausedDepth;
+ Status mParentStatus;
+ bool mParentFrozen;
+ bool mIsChromeWorker;
+ bool mMainThreadObjectsForgotten;
+ // mIsSecureContext is set once in our constructor; after that it can be read
+ // from various threads. We could make this const if we were OK with setting
+ // it in the initializer list via calling some function that takes all sorts
+ // of state (loadinfo, worker type, parent).
+ //
+ // It's a bit unfortunate that we have to have an out-of-band boolean for
+ // this, but we need access to this state from the parent thread, and we can't
+ // use our global object's secure state there.
+ bool mIsSecureContext;
+ WorkerType mWorkerType;
+ TimeStamp mCreationTimeStamp;
+ DOMHighResTimeStamp mCreationTimeHighRes;
+ TimeStamp mNowBaseTimeStamp;
+ DOMHighResTimeStamp mNowBaseTimeHighRes;
+
+protected:
+ // The worker is owned by its thread, which is represented here. This is set
+ // in Construct() and emptied by WorkerFinishedRunnable, and conditionally
+ // traversed by the cycle collector if the busy count is zero.
+ RefPtr<WorkerPrivate> mSelfRef;
+
+ WorkerPrivateParent(WorkerPrivate* aParent,
+ const nsAString& aScriptURL, bool aIsChromeWorker,
+ WorkerType aWorkerType,
+ const nsACString& aSharedWorkerName,
+ WorkerLoadInfo& aLoadInfo);
+
+ ~WorkerPrivateParent();
+
+private:
+ Derived*
+ ParentAsWorkerPrivate() const
+ {
+ return static_cast<Derived*>(const_cast<WorkerPrivateParent*>(this));
+ }
+
+ bool
+ NotifyPrivate(Status aStatus);
+
+ bool
+ TerminatePrivate()
+ {
+ return NotifyPrivate(Terminating);
+ }
+
+ void
+ PostMessageInternal(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ UniquePtr<ServiceWorkerClientInfo>&& aClientInfo,
+ PromiseNativeHandler* aHandler,
+ ErrorResult& aRv);
+
+ nsresult
+ DispatchPrivate(already_AddRefed<WorkerRunnable> aRunnable, nsIEventTarget* aSyncLoopTarget);
+
+public:
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(WorkerPrivateParent,
+ DOMEventTargetHelper)
+
+ void
+ EnableDebugger();
+
+ void
+ DisableDebugger();
+
+ void
+ ClearSelfRef()
+ {
+ AssertIsOnParentThread();
+ MOZ_ASSERT(mSelfRef);
+ mSelfRef = nullptr;
+ }
+
+ nsresult
+ Dispatch(already_AddRefed<WorkerRunnable> aRunnable)
+ {
+ return DispatchPrivate(Move(aRunnable), nullptr);
+ }
+
+ nsresult
+ DispatchControlRunnable(already_AddRefed<WorkerControlRunnable> aWorkerControlRunnable);
+
+ nsresult
+ DispatchDebuggerRunnable(already_AddRefed<WorkerRunnable> aDebuggerRunnable);
+
+ already_AddRefed<WorkerRunnable>
+ MaybeWrapAsWorkerRunnable(already_AddRefed<nsIRunnable> aRunnable);
+
+ already_AddRefed<nsIEventTarget>
+ GetEventTarget();
+
+ // May be called on any thread...
+ bool
+ Start();
+
+ // Called on the parent thread.
+ bool
+ Notify(Status aStatus)
+ {
+ return NotifyPrivate(aStatus);
+ }
+
+ bool
+ Cancel()
+ {
+ return Notify(Canceling);
+ }
+
+ bool
+ Kill()
+ {
+ return Notify(Killing);
+ }
+
+ // We can assume that an nsPIDOMWindow will be available for Freeze, Thaw
+ // as these are only used for globals going in and out of the bfcache.
+ //
+ // XXXbz: This is a bald-faced lie given the uses in RegisterSharedWorker and
+ // CloseSharedWorkersForWindow, which pass null for aWindow to Thaw and Freeze
+ // respectively. See bug 1251722.
+ bool
+ Freeze(nsPIDOMWindowInner* aWindow);
+
+ bool
+ Thaw(nsPIDOMWindowInner* aWindow);
+
+ // When we debug a worker, we want to disconnect the window and the worker
+ // communication. This happens calling this method.
+ // Note: this method doesn't suspend the worker! Use Freeze/Thaw instead.
+ void
+ ParentWindowPaused();
+
+ void
+ ParentWindowResumed();
+
+ bool
+ Terminate()
+ {
+ AssertIsOnParentThread();
+ return TerminatePrivate();
+ }
+
+ bool
+ Close();
+
+ bool
+ ModifyBusyCount(bool aIncrease);
+
+ void
+ ForgetOverridenLoadGroup(nsCOMPtr<nsILoadGroup>& aLoadGroupOut);
+
+ void
+ ForgetMainThreadObjects(nsTArray<nsCOMPtr<nsISupports> >& aDoomed);
+
+ void
+ PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv);
+
+ void
+ PostMessageToServiceWorker(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ UniquePtr<ServiceWorkerClientInfo>&& aClientInfo,
+ PromiseNativeHandler* aHandler,
+ ErrorResult& aRv);
+
+ void
+ UpdateContextOptions(const JS::ContextOptions& aContextOptions);
+
+ void
+ UpdateLanguages(const nsTArray<nsString>& aLanguages);
+
+ void
+ UpdatePreference(WorkerPreference aPref, bool aValue);
+
+ void
+ UpdateJSWorkerMemoryParameter(JSGCParamKey key, uint32_t value);
+
+#ifdef JS_GC_ZEAL
+ void
+ UpdateGCZeal(uint8_t aGCZeal, uint32_t aFrequency);
+#endif
+
+ void
+ GarbageCollect(bool aShrinking);
+
+ void
+ CycleCollect(bool aDummy);
+
+ void
+ OfflineStatusChangeEvent(bool aIsOffline);
+
+ void
+ MemoryPressure(bool aDummy);
+
+ bool
+ RegisterSharedWorker(SharedWorker* aSharedWorker, MessagePort* aPort);
+
+ void
+ BroadcastErrorToSharedWorkers(JSContext* aCx,
+ const nsAString& aMessage,
+ const nsAString& aFilename,
+ const nsAString& aLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aFlags);
+
+ void
+ WorkerScriptLoaded();
+
+ void
+ QueueRunnable(nsIRunnable* aRunnable)
+ {
+ AssertIsOnParentThread();
+ mQueuedRunnables.AppendElement(aRunnable);
+ }
+
+ WorkerPrivate*
+ GetParent() const
+ {
+ return mParent;
+ }
+
+ bool
+ IsFrozen() const
+ {
+ AssertIsOnParentThread();
+ return mParentFrozen;
+ }
+
+ bool
+ IsParentWindowPaused() const
+ {
+ AssertIsOnParentThread();
+ return mParentWindowPausedDepth > 0;
+ }
+
+ bool
+ IsAcceptingEvents()
+ {
+ AssertIsOnParentThread();
+
+ MutexAutoLock lock(mMutex);
+ return mParentStatus < Terminating;
+ }
+
+ Status
+ ParentStatus() const
+ {
+ mMutex.AssertCurrentThreadOwns();
+ return mParentStatus;
+ }
+
+ nsIScriptContext*
+ GetScriptContext() const
+ {
+ AssertIsOnMainThread();
+ return mLoadInfo.mScriptContext;
+ }
+
+ const nsString&
+ ScriptURL() const
+ {
+ return mScriptURL;
+ }
+
+ const nsCString&
+ Domain() const
+ {
+ return mLoadInfo.mDomain;
+ }
+
+ bool
+ IsFromWindow() const
+ {
+ return mLoadInfo.mFromWindow;
+ }
+
+ uint64_t
+ WindowID() const
+ {
+ return mLoadInfo.mWindowID;
+ }
+
+ uint64_t
+ ServiceWorkerID() const
+ {
+ return mLoadInfo.mServiceWorkerID;
+ }
+
+ nsIURI*
+ GetBaseURI() const
+ {
+ AssertIsOnMainThread();
+ return mLoadInfo.mBaseURI;
+ }
+
+ void
+ SetBaseURI(nsIURI* aBaseURI);
+
+ nsIURI*
+ GetResolvedScriptURI() const
+ {
+ AssertIsOnMainThread();
+ return mLoadInfo.mResolvedScriptURI;
+ }
+
+ const nsString&
+ ServiceWorkerCacheName() const
+ {
+ MOZ_ASSERT(IsServiceWorker());
+ AssertIsOnMainThread();
+ return mLoadInfo.mServiceWorkerCacheName;
+ }
+
+ const ChannelInfo&
+ GetChannelInfo() const
+ {
+ return mLoadInfo.mChannelInfo;
+ }
+
+ void
+ SetChannelInfo(const ChannelInfo& aChannelInfo)
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mLoadInfo.mChannelInfo.IsInitialized());
+ MOZ_ASSERT(aChannelInfo.IsInitialized());
+ mLoadInfo.mChannelInfo = aChannelInfo;
+ }
+
+ void
+ InitChannelInfo(nsIChannel* aChannel)
+ {
+ mLoadInfo.mChannelInfo.InitFromChannel(aChannel);
+ }
+
+ void
+ InitChannelInfo(const ChannelInfo& aChannelInfo)
+ {
+ mLoadInfo.mChannelInfo = aChannelInfo;
+ }
+
+ // This is used to handle importScripts(). When the worker is first loaded
+ // and executed, it happens in a sync loop. At this point it sets
+ // mLoadingWorkerScript to true. importScripts() calls that occur during the
+ // execution run in nested sync loops and so this continues to return true,
+ // leading to these scripts being cached offline.
+ // mLoadingWorkerScript is set to false when the top level loop ends.
+ // importScripts() in function calls or event handlers are always fetched
+ // from the network.
+ bool
+ LoadScriptAsPartOfLoadingServiceWorkerScript()
+ {
+ MOZ_ASSERT(IsServiceWorker());
+ return mLoadingWorkerScript;
+ }
+
+ void
+ SetLoadingWorkerScript(bool aLoadingWorkerScript)
+ {
+ // any thread
+ MOZ_ASSERT(IsServiceWorker());
+ mLoadingWorkerScript = aLoadingWorkerScript;
+ }
+
+ TimeStamp CreationTimeStamp() const
+ {
+ return mCreationTimeStamp;
+ }
+
+ DOMHighResTimeStamp CreationTime() const
+ {
+ return mCreationTimeHighRes;
+ }
+
+ TimeStamp NowBaseTimeStamp() const
+ {
+ return mNowBaseTimeStamp;
+ }
+
+ DOMHighResTimeStamp NowBaseTime() const
+ {
+ return mNowBaseTimeHighRes;
+ }
+
+ nsIPrincipal*
+ GetPrincipal() const
+ {
+ AssertIsOnMainThread();
+ return mLoadInfo.mPrincipal;
+ }
+
+ nsILoadGroup*
+ GetLoadGroup() const
+ {
+ AssertIsOnMainThread();
+ return mLoadInfo.mLoadGroup;
+ }
+
+ // This method allows the principal to be retrieved off the main thread.
+ // Principals are main-thread objects so the caller must ensure that all
+ // access occurs on the main thread.
+ nsIPrincipal*
+ GetPrincipalDontAssertMainThread() const
+ {
+ return mLoadInfo.mPrincipal;
+ }
+
+ void
+ SetPrincipal(nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup);
+
+ bool
+ UsesSystemPrincipal() const
+ {
+ return mLoadInfo.mPrincipalIsSystem;
+ }
+
+ const PrincipalInfo&
+ GetPrincipalInfo() const
+ {
+ return *mLoadInfo.mPrincipalInfo;
+ }
+
+ already_AddRefed<nsIChannel>
+ ForgetWorkerChannel()
+ {
+ AssertIsOnMainThread();
+ return mLoadInfo.mChannel.forget();
+ }
+
+ nsIDocument* GetDocument() const;
+
+ nsPIDOMWindowInner*
+ GetWindow()
+ {
+ AssertIsOnMainThread();
+ return mLoadInfo.mWindow;
+ }
+
+ nsIContentSecurityPolicy*
+ GetCSP() const
+ {
+ AssertIsOnMainThread();
+ return mLoadInfo.mCSP;
+ }
+
+ void
+ SetCSP(nsIContentSecurityPolicy* aCSP)
+ {
+ AssertIsOnMainThread();
+ mLoadInfo.mCSP = aCSP;
+ }
+
+ net::ReferrerPolicy
+ GetReferrerPolicy() const
+ {
+ return mLoadInfo.mReferrerPolicy;
+ }
+
+ void
+ SetReferrerPolicy(net::ReferrerPolicy aReferrerPolicy)
+ {
+ mLoadInfo.mReferrerPolicy = aReferrerPolicy;
+ }
+
+ bool
+ IsEvalAllowed() const
+ {
+ return mLoadInfo.mEvalAllowed;
+ }
+
+ void
+ SetEvalAllowed(bool aEvalAllowed)
+ {
+ mLoadInfo.mEvalAllowed = aEvalAllowed;
+ }
+
+ bool
+ GetReportCSPViolations() const
+ {
+ return mLoadInfo.mReportCSPViolations;
+ }
+
+ void
+ SetReportCSPViolations(bool aReport)
+ {
+ mLoadInfo.mReportCSPViolations = aReport;
+ }
+
+ bool
+ XHRParamsAllowed() const
+ {
+ return mLoadInfo.mXHRParamsAllowed;
+ }
+
+ void
+ SetXHRParamsAllowed(bool aAllowed)
+ {
+ mLoadInfo.mXHRParamsAllowed = aAllowed;
+ }
+
+ LocationInfo&
+ GetLocationInfo()
+ {
+ return mLocationInfo;
+ }
+
+ void
+ CopyJSSettings(JSSettings& aSettings)
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+ aSettings = mJSSettings;
+ }
+
+ void
+ CopyJSCompartmentOptions(JS::CompartmentOptions& aOptions)
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+ aOptions = IsChromeWorker() ? mJSSettings.chrome.compartmentOptions
+ : mJSSettings.content.compartmentOptions;
+ }
+
+ // The ability to be a chrome worker is orthogonal to the type of
+ // worker [Dedicated|Shared|Service].
+ bool
+ IsChromeWorker() const
+ {
+ return mIsChromeWorker;
+ }
+
+ WorkerType
+ Type() const
+ {
+ return mWorkerType;
+ }
+
+ bool
+ IsDedicatedWorker() const
+ {
+ return mWorkerType == WorkerTypeDedicated;
+ }
+
+ bool
+ IsSharedWorker() const
+ {
+ return mWorkerType == WorkerTypeShared;
+ }
+
+ bool
+ IsServiceWorker() const
+ {
+ return mWorkerType == WorkerTypeService;
+ }
+
+ nsContentPolicyType
+ ContentPolicyType() const
+ {
+ return ContentPolicyType(mWorkerType);
+ }
+
+ static nsContentPolicyType
+ ContentPolicyType(WorkerType aWorkerType)
+ {
+ switch (aWorkerType) {
+ case WorkerTypeDedicated:
+ return nsIContentPolicy::TYPE_INTERNAL_WORKER;
+ case WorkerTypeShared:
+ return nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER;
+ case WorkerTypeService:
+ return nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid worker type");
+ return nsIContentPolicy::TYPE_INVALID;
+ }
+ }
+
+ const nsCString&
+ WorkerName() const
+ {
+ MOZ_ASSERT(IsServiceWorker() || IsSharedWorker());
+ return mWorkerName;
+ }
+
+ bool
+ IsStorageAllowed() const
+ {
+ return mLoadInfo.mStorageAllowed;
+ }
+
+ const PrincipalOriginAttributes&
+ GetOriginAttributes() const
+ {
+ return mLoadInfo.mOriginAttributes;
+ }
+
+ // Determine if the SW testing per-window flag is set by devtools
+ bool
+ ServiceWorkersTestingInWindow() const
+ {
+ return mLoadInfo.mServiceWorkersTestingInWindow;
+ }
+
+ void
+ GetAllSharedWorkers(nsTArray<RefPtr<SharedWorker>>& aSharedWorkers);
+
+ void
+ CloseSharedWorkersForWindow(nsPIDOMWindowInner* aWindow);
+
+ void
+ CloseAllSharedWorkers();
+
+ void
+ UpdateOverridenLoadGroup(nsILoadGroup* aBaseLoadGroup);
+
+ already_AddRefed<nsIRunnable>
+ StealLoadFailedAsyncRunnable()
+ {
+ return mLoadInfo.mLoadFailedAsyncRunnable.forget();
+ }
+
+ void
+ FlushReportsToSharedWorkers(nsIConsoleReportCollector* aReporter);
+
+ IMPL_EVENT_HANDLER(message)
+ IMPL_EVENT_HANDLER(error)
+
+ // Check whether this worker is a secure context. For use from the parent
+ // thread only; the canonical "is secure context" boolean is stored on the
+ // compartment of the worker global. The only reason we don't
+ // AssertIsOnParentThread() here is so we can assert that this value matches
+ // the one on the compartment, which has to be done from the worker thread.
+ bool IsSecureContext() const
+ {
+ return mIsSecureContext;
+ }
+
+#ifdef DEBUG
+ void
+ AssertIsOnParentThread() const;
+
+ void
+ AssertInnerWindowIsCorrect() const;
+#else
+ void
+ AssertIsOnParentThread() const
+ { }
+
+ void
+ AssertInnerWindowIsCorrect() const
+ { }
+#endif
+};
+
+class WorkerDebugger : public nsIWorkerDebugger {
+ friend class ::ReportDebuggerErrorRunnable;
+ friend class ::PostDebuggerMessageRunnable;
+
+ WorkerPrivate* mWorkerPrivate;
+ bool mIsInitialized;
+ nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> mListeners;
+
+public:
+ explicit WorkerDebugger(WorkerPrivate* aWorkerPrivate);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWORKERDEBUGGER
+
+ void
+ AssertIsOnParentThread();
+
+ void
+ Close();
+
+ void
+ PostMessageToDebugger(const nsAString& aMessage);
+
+ void
+ ReportErrorToDebugger(const nsAString& aFilename, uint32_t aLineno,
+ const nsAString& aMessage);
+
+private:
+ virtual
+ ~WorkerDebugger();
+
+ void
+ PostMessageToDebuggerOnMainThread(const nsAString& aMessage);
+
+ void
+ ReportErrorToDebuggerOnMainThread(const nsAString& aFilename,
+ uint32_t aLineno,
+ const nsAString& aMessage);
+};
+
+class WorkerPrivate : public WorkerPrivateParent<WorkerPrivate>
+{
+ friend class WorkerHolder;
+ friend class WorkerPrivateParent<WorkerPrivate>;
+ typedef WorkerPrivateParent<WorkerPrivate> ParentType;
+ friend class AutoSyncLoopHolder;
+
+ struct TimeoutInfo;
+
+ class MemoryReporter;
+ friend class MemoryReporter;
+
+ friend class WorkerThread;
+
+ enum GCTimerMode
+ {
+ PeriodicTimer = 0,
+ IdleTimer,
+ NoTimer
+ };
+
+ bool mDebuggerRegistered;
+ WorkerDebugger* mDebugger;
+
+ Queue<WorkerControlRunnable*, 4> mControlQueue;
+ Queue<WorkerRunnable*, 4> mDebuggerQueue;
+
+ // Touched on multiple threads, protected with mMutex.
+ JSContext* mJSContext;
+ RefPtr<WorkerCrossThreadDispatcher> mCrossThreadDispatcher;
+ nsTArray<nsCOMPtr<nsIRunnable>> mUndispatchedRunnablesForSyncLoop;
+ RefPtr<WorkerThread> mThread;
+ PRThread* mPRThread;
+
+ // Things touched on worker thread only.
+ RefPtr<WorkerGlobalScope> mScope;
+ RefPtr<WorkerDebuggerGlobalScope> mDebuggerScope;
+ nsTArray<ParentType*> mChildWorkers;
+ nsTObserverArray<WorkerHolder*> mHolders;
+ nsTArray<nsAutoPtr<TimeoutInfo>> mTimeouts;
+ uint32_t mDebuggerEventLoopLevel;
+ RefPtr<ThrottledEventQueue> mMainThreadThrottledEventQueue;
+ nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
+
+ struct SyncLoopInfo
+ {
+ explicit SyncLoopInfo(EventTarget* aEventTarget);
+
+ RefPtr<EventTarget> mEventTarget;
+ bool mCompleted;
+ bool mResult;
+#ifdef DEBUG
+ bool mHasRun;
+#endif
+ };
+
+ // This is only modified on the worker thread, but in DEBUG builds
+ // AssertValidSyncLoop function iterates it on other threads. Therefore
+ // modifications are done with mMutex held *only* in DEBUG builds.
+ nsTArray<nsAutoPtr<SyncLoopInfo>> mSyncLoopStack;
+
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsITimerCallback> mTimerRunnable;
+
+ nsCOMPtr<nsITimer> mGCTimer;
+ nsCOMPtr<nsIEventTarget> mPeriodicGCTimerTarget;
+ nsCOMPtr<nsIEventTarget> mIdleGCTimerTarget;
+
+ RefPtr<MemoryReporter> mMemoryReporter;
+
+ // fired on the main thread if the worker script fails to load
+ nsCOMPtr<nsIRunnable> mLoadFailedRunnable;
+
+ JS::UniqueChars mDefaultLocale; // nulled during worker JSContext init
+ TimeStamp mKillTime;
+ uint32_t mErrorHandlerRecursionCount;
+ uint32_t mNextTimeoutId;
+ Status mStatus;
+ bool mFrozen;
+ bool mTimerRunning;
+ bool mRunningExpiredTimeouts;
+ bool mPendingEventQueueClearing;
+ bool mCancelAllPendingRunnables;
+ bool mPeriodicGCTimerRunning;
+ bool mIdleGCTimerRunning;
+ bool mWorkerScriptExecutedSuccessfully;
+ bool mPreferences[WORKERPREF_COUNT];
+ bool mOnLine;
+
+protected:
+ ~WorkerPrivate();
+
+public:
+ static already_AddRefed<WorkerPrivate>
+ Constructor(const GlobalObject& aGlobal, const nsAString& aScriptURL,
+ ErrorResult& aRv);
+
+ static already_AddRefed<WorkerPrivate>
+ Constructor(const GlobalObject& aGlobal, const nsAString& aScriptURL,
+ bool aIsChromeWorker, WorkerType aWorkerType,
+ const nsACString& aSharedWorkerName,
+ WorkerLoadInfo* aLoadInfo, ErrorResult& aRv);
+
+ static already_AddRefed<WorkerPrivate>
+ Constructor(JSContext* aCx, const nsAString& aScriptURL, bool aIsChromeWorker,
+ WorkerType aWorkerType, const nsACString& aSharedWorkerName,
+ WorkerLoadInfo* aLoadInfo, ErrorResult& aRv);
+
+ static bool
+ WorkerAvailable(JSContext* /* unused */, JSObject* /* unused */);
+
+ enum LoadGroupBehavior
+ {
+ InheritLoadGroup,
+ OverrideLoadGroup
+ };
+
+ static nsresult
+ GetLoadInfo(JSContext* aCx, nsPIDOMWindowInner* aWindow,
+ WorkerPrivate* aParent,
+ const nsAString& aScriptURL, bool aIsChromeWorker,
+ LoadGroupBehavior aLoadGroupBehavior, WorkerType aWorkerType,
+ WorkerLoadInfo* aLoadInfo);
+
+ static void
+ OverrideLoadInfoLoadGroup(WorkerLoadInfo& aLoadInfo);
+
+ bool
+ IsDebuggerRegistered()
+ {
+ AssertIsOnMainThread();
+
+ // No need to lock here since this is only ever modified by the same thread.
+ return mDebuggerRegistered;
+ }
+
+ void
+ SetIsDebuggerRegistered(bool aDebuggerRegistered)
+ {
+ AssertIsOnMainThread();
+
+ MutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(mDebuggerRegistered != aDebuggerRegistered);
+ mDebuggerRegistered = aDebuggerRegistered;
+
+ mCondVar.Notify();
+ }
+
+ void
+ WaitForIsDebuggerRegistered(bool aDebuggerRegistered)
+ {
+ AssertIsOnParentThread();
+
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ MutexAutoLock lock(mMutex);
+
+ while (mDebuggerRegistered != aDebuggerRegistered) {
+ mCondVar.Wait();
+ }
+ }
+
+ WorkerDebugger*
+ Debugger() const
+ {
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(mDebugger);
+ return mDebugger;
+ }
+
+ void
+ SetDebugger(WorkerDebugger* aDebugger)
+ {
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(mDebugger != aDebugger);
+ mDebugger = aDebugger;
+ }
+
+ JS::UniqueChars
+ AdoptDefaultLocale()
+ {
+ MOZ_ASSERT(mDefaultLocale,
+ "the default locale must have been successfully set for anyone "
+ "to be trying to adopt it");
+ return Move(mDefaultLocale);
+ }
+
+ void
+ DoRunLoop(JSContext* aCx);
+
+ bool
+ InterruptCallback(JSContext* aCx);
+
+ nsresult
+ IsOnCurrentThread(bool* aIsOnCurrentThread);
+
+ bool
+ CloseInternal(JSContext* aCx)
+ {
+ AssertIsOnWorkerThread();
+ return NotifyInternal(aCx, Closing);
+ }
+
+ bool
+ FreezeInternal();
+
+ bool
+ ThawInternal();
+
+ void
+ TraverseTimeouts(nsCycleCollectionTraversalCallback& aCallback);
+
+ void
+ UnlinkTimeouts();
+
+ bool
+ ModifyBusyCountFromWorker(bool aIncrease);
+
+ bool
+ AddChildWorker(ParentType* aChildWorker);
+
+ void
+ RemoveChildWorker(ParentType* aChildWorker);
+
+ void
+ PostMessageToParent(JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv)
+ {
+ PostMessageToParentInternal(aCx, aMessage, aTransferable, aRv);
+ }
+
+ void
+ PostMessageToParentMessagePort(
+ JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv);
+
+ void
+ EnterDebuggerEventLoop();
+
+ void
+ LeaveDebuggerEventLoop();
+
+ void
+ PostMessageToDebugger(const nsAString& aMessage);
+
+ void
+ SetDebuggerImmediate(Function& aHandler, ErrorResult& aRv);
+
+ void
+ ReportErrorToDebugger(const nsAString& aFilename, uint32_t aLineno,
+ const nsAString& aMessage);
+
+ bool
+ NotifyInternal(JSContext* aCx, Status aStatus);
+
+ void
+ ReportError(JSContext* aCx, JS::ConstUTF8CharsZ aToStringResult,
+ JSErrorReport* aReport);
+
+ static void
+ ReportErrorToConsole(const char* aMessage);
+
+ int32_t
+ SetTimeout(JSContext* aCx, nsIScriptTimeoutHandler* aHandler,
+ int32_t aTimeout, bool aIsInterval,
+ ErrorResult& aRv);
+
+ void
+ ClearTimeout(int32_t aId);
+
+ bool
+ RunExpiredTimeouts(JSContext* aCx);
+
+ bool
+ RescheduleTimeoutTimer(JSContext* aCx);
+
+ void
+ UpdateContextOptionsInternal(JSContext* aCx, const JS::ContextOptions& aContextOptions);
+
+ void
+ UpdateLanguagesInternal(const nsTArray<nsString>& aLanguages);
+
+ void
+ UpdatePreferenceInternal(WorkerPreference aPref, bool aValue);
+
+ void
+ UpdateJSWorkerMemoryParameterInternal(JSContext* aCx, JSGCParamKey key, uint32_t aValue);
+
+ enum WorkerRanOrNot {
+ WorkerNeverRan = 0,
+ WorkerRan
+ };
+
+ void
+ ScheduleDeletion(WorkerRanOrNot aRanOrNot);
+
+ bool
+ CollectRuntimeStats(JS::RuntimeStats* aRtStats, bool aAnonymize);
+
+#ifdef JS_GC_ZEAL
+ void
+ UpdateGCZealInternal(JSContext* aCx, uint8_t aGCZeal, uint32_t aFrequency);
+#endif
+
+ void
+ GarbageCollectInternal(JSContext* aCx, bool aShrinking,
+ bool aCollectChildren);
+
+ void
+ CycleCollectInternal(bool aCollectChildren);
+
+ void
+ OfflineStatusChangeEventInternal(bool aIsOffline);
+
+ void
+ MemoryPressureInternal();
+
+ JSContext*
+ GetJSContext() const
+ {
+ AssertIsOnWorkerThread();
+ return mJSContext;
+ }
+
+ WorkerGlobalScope*
+ GlobalScope() const
+ {
+ AssertIsOnWorkerThread();
+ return mScope;
+ }
+
+ WorkerDebuggerGlobalScope*
+ DebuggerGlobalScope() const
+ {
+ AssertIsOnWorkerThread();
+ return mDebuggerScope;
+ }
+
+ void
+ SetThread(WorkerThread* aThread);
+
+ void
+ AssertIsOnWorkerThread() const
+#ifdef DEBUG
+ ;
+#else
+ { }
+#endif
+
+ WorkerCrossThreadDispatcher*
+ GetCrossThreadDispatcher();
+
+ // This may block!
+ void
+ BeginCTypesCall();
+
+ // This may block!
+ void
+ EndCTypesCall();
+
+ void
+ BeginCTypesCallback()
+ {
+ // If a callback is beginning then we need to do the exact same thing as
+ // when a ctypes call ends.
+ EndCTypesCall();
+ }
+
+ void
+ EndCTypesCallback()
+ {
+ // If a callback is ending then we need to do the exact same thing as
+ // when a ctypes call begins.
+ BeginCTypesCall();
+ }
+
+ bool
+ ConnectMessagePort(JSContext* aCx, MessagePortIdentifier& aIdentifier);
+
+ WorkerGlobalScope*
+ GetOrCreateGlobalScope(JSContext* aCx);
+
+ WorkerDebuggerGlobalScope*
+ CreateDebuggerGlobalScope(JSContext* aCx);
+
+ bool
+ RegisterBindings(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
+
+ bool
+ RegisterDebuggerBindings(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
+
+#define WORKER_SIMPLE_PREF(name, getter, NAME) \
+ bool \
+ getter() const \
+ { \
+ AssertIsOnWorkerThread(); \
+ return mPreferences[WORKERPREF_##NAME]; \
+ }
+#define WORKER_PREF(name, callback)
+#include "WorkerPrefs.h"
+#undef WORKER_SIMPLE_PREF
+#undef WORKER_PREF
+
+ bool
+ OnLine() const
+ {
+ AssertIsOnWorkerThread();
+ return mOnLine;
+ }
+
+ void
+ StopSyncLoop(nsIEventTarget* aSyncLoopTarget, bool aResult);
+
+ bool
+ AllPendingRunnablesShouldBeCanceled() const
+ {
+ return mCancelAllPendingRunnables;
+ }
+
+ void
+ ClearMainEventQueue(WorkerRanOrNot aRanOrNot);
+
+ void
+ ClearDebuggerEventQueue();
+
+ void
+ OnProcessNextEvent();
+
+ void
+ AfterProcessNextEvent();
+
+ void
+ AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget)
+#ifdef DEBUG
+ ;
+#else
+ { }
+#endif
+
+ void
+ SetWorkerScriptExecutedSuccessfully()
+ {
+ AssertIsOnWorkerThread();
+ // Should only be called once!
+ MOZ_ASSERT(!mWorkerScriptExecutedSuccessfully);
+ mWorkerScriptExecutedSuccessfully = true;
+ }
+
+ // Only valid after CompileScriptRunnable has finished running!
+ bool
+ WorkerScriptExecutedSuccessfully() const
+ {
+ AssertIsOnWorkerThread();
+ return mWorkerScriptExecutedSuccessfully;
+ }
+
+ void
+ MaybeDispatchLoadFailedRunnable();
+
+ // Get the event target to use when dispatching to the main thread
+ // from this Worker thread. This may be the main thread itself or
+ // a ThrottledEventQueue to the main thread.
+ nsIEventTarget*
+ MainThreadEventTarget();
+
+ nsresult
+ DispatchToMainThread(nsIRunnable* aRunnable,
+ uint32_t aFlags = NS_DISPATCH_NORMAL);
+
+ nsresult
+ DispatchToMainThread(already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aFlags = NS_DISPATCH_NORMAL);
+
+private:
+ WorkerPrivate(WorkerPrivate* aParent,
+ const nsAString& aScriptURL, bool aIsChromeWorker,
+ WorkerType aWorkerType, const nsACString& aSharedWorkerName,
+ WorkerLoadInfo& aLoadInfo);
+
+ bool
+ MayContinueRunning()
+ {
+ AssertIsOnWorkerThread();
+
+ Status status;
+ {
+ MutexAutoLock lock(mMutex);
+ status = mStatus;
+ }
+
+ if (status < Terminating) {
+ return true;
+ }
+
+ return false;
+ }
+
+ void
+ CancelAllTimeouts();
+
+ enum class ProcessAllControlRunnablesResult
+ {
+ // We did not process anything.
+ Nothing,
+ // We did process something, states may have changed, but we can keep
+ // executing script.
+ MayContinue,
+ // We did process something, and should not continue executing script.
+ Abort
+ };
+
+ ProcessAllControlRunnablesResult
+ ProcessAllControlRunnables()
+ {
+ MutexAutoLock lock(mMutex);
+ return ProcessAllControlRunnablesLocked();
+ }
+
+ ProcessAllControlRunnablesResult
+ ProcessAllControlRunnablesLocked();
+
+ void
+ EnableMemoryReporter();
+
+ void
+ DisableMemoryReporter();
+
+ void
+ WaitForWorkerEvents(PRIntervalTime interval = PR_INTERVAL_NO_TIMEOUT);
+
+ void
+ PostMessageToParentInternal(JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv);
+
+ void
+ GetAllPreferences(bool aPreferences[WORKERPREF_COUNT]) const
+ {
+ AssertIsOnWorkerThread();
+ memcpy(aPreferences, mPreferences, WORKERPREF_COUNT * sizeof(bool));
+ }
+
+ already_AddRefed<nsIEventTarget>
+ CreateNewSyncLoop();
+
+ bool
+ RunCurrentSyncLoop();
+
+ bool
+ DestroySyncLoop(uint32_t aLoopIndex, nsIThreadInternal* aThread = nullptr);
+
+ void
+ InitializeGCTimers();
+
+ void
+ SetGCTimerMode(GCTimerMode aMode);
+
+ void
+ ShutdownGCTimers();
+
+ bool
+ AddHolder(WorkerHolder* aHolder, Status aFailStatus);
+
+ void
+ RemoveHolder(WorkerHolder* aHolder);
+
+ void
+ NotifyHolders(JSContext* aCx, Status aStatus);
+
+ bool
+ HasActiveHolders()
+ {
+ return !(mChildWorkers.IsEmpty() && mTimeouts.IsEmpty() &&
+ mHolders.IsEmpty());
+ }
+};
+
+// This class is only used to trick the DOM bindings. We never create
+// instances of it, and static_casting to it is fine since it doesn't add
+// anything to WorkerPrivate.
+class ChromeWorkerPrivate : public WorkerPrivate
+{
+public:
+ static already_AddRefed<ChromeWorkerPrivate>
+ Constructor(const GlobalObject& aGlobal, const nsAString& aScriptURL,
+ ErrorResult& rv);
+
+ static bool
+ WorkerAvailable(JSContext* aCx, JSObject* /* unused */);
+
+private:
+ ChromeWorkerPrivate() = delete;
+ ChromeWorkerPrivate(const ChromeWorkerPrivate& aRHS) = delete;
+ ChromeWorkerPrivate& operator =(const ChromeWorkerPrivate& aRHS) = delete;
+};
+
+WorkerPrivate*
+GetWorkerPrivateFromContext(JSContext* aCx);
+
+WorkerPrivate*
+GetCurrentThreadWorkerPrivate();
+
+bool
+IsCurrentThreadRunningChromeWorker();
+
+JSContext*
+GetCurrentThreadJSContext();
+
+JSObject*
+GetCurrentThreadWorkerGlobal();
+
+class AutoSyncLoopHolder
+{
+ WorkerPrivate* mWorkerPrivate;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ uint32_t mIndex;
+
+public:
+ explicit AutoSyncLoopHolder(WorkerPrivate* aWorkerPrivate)
+ : mWorkerPrivate(aWorkerPrivate)
+ , mTarget(aWorkerPrivate->CreateNewSyncLoop())
+ , mIndex(aWorkerPrivate->mSyncLoopStack.Length() - 1)
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ ~AutoSyncLoopHolder()
+ {
+ if (mWorkerPrivate) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ mWorkerPrivate->StopSyncLoop(mTarget, false);
+ mWorkerPrivate->DestroySyncLoop(mIndex);
+ }
+ }
+
+ bool
+ Run()
+ {
+ WorkerPrivate* workerPrivate = mWorkerPrivate;
+ mWorkerPrivate = nullptr;
+
+ workerPrivate->AssertIsOnWorkerThread();
+
+ return workerPrivate->RunCurrentSyncLoop();
+ }
+
+ nsIEventTarget*
+ EventTarget() const
+ {
+ return mTarget;
+ }
+};
+
+class TimerThreadEventTarget final : public nsIEventTarget
+{
+ ~TimerThreadEventTarget();
+
+ WorkerPrivate* mWorkerPrivate;
+ RefPtr<WorkerRunnable> mWorkerRunnable;
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ TimerThreadEventTarget(WorkerPrivate* aWorkerPrivate,
+ WorkerRunnable* aWorkerRunnable);
+
+protected:
+ NS_IMETHOD
+ DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) override;
+
+
+ NS_IMETHOD
+ Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) override;
+
+ NS_IMETHOD
+ DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) override;
+
+ NS_IMETHOD
+ IsOnCurrentThread(bool* aIsOnCurrentThread) override;
+};
+
+END_WORKERS_NAMESPACE
+
+#endif /* mozilla_dom_workers_workerprivate_h__ */
diff --git a/dom/workers/WorkerRunnable.cpp b/dom/workers/WorkerRunnable.cpp
new file mode 100644
index 000000000..1e16d7254
--- /dev/null
+++ b/dom/workers/WorkerRunnable.cpp
@@ -0,0 +1,777 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WorkerRunnable.h"
+
+#include "nsGlobalWindow.h"
+#include "nsIEventTarget.h"
+#include "nsIGlobalObject.h"
+#include "nsIRunnable.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/Telemetry.h"
+
+#include "js/RootingAPI.h"
+#include "js/Value.h"
+
+#include "WorkerPrivate.h"
+#include "WorkerScope.h"
+
+USING_WORKERS_NAMESPACE
+
+namespace {
+
+const nsIID kWorkerRunnableIID = {
+ 0x320cc0b5, 0xef12, 0x4084, { 0x88, 0x6e, 0xca, 0x6a, 0x81, 0xe4, 0x1d, 0x68 }
+};
+
+} // namespace
+
+#ifdef DEBUG
+WorkerRunnable::WorkerRunnable(WorkerPrivate* aWorkerPrivate,
+ TargetAndBusyBehavior aBehavior)
+: mWorkerPrivate(aWorkerPrivate), mBehavior(aBehavior), mCanceled(0),
+ mCallingCancelWithinRun(false)
+{
+ MOZ_ASSERT(aWorkerPrivate);
+}
+#endif
+
+bool
+WorkerRunnable::IsDebuggerRunnable() const
+{
+ return false;
+}
+
+nsIGlobalObject*
+WorkerRunnable::DefaultGlobalObject() const
+{
+ if (IsDebuggerRunnable()) {
+ return mWorkerPrivate->DebuggerGlobalScope();
+ } else {
+ return mWorkerPrivate->GlobalScope();
+ }
+}
+
+bool
+WorkerRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate)
+{
+#ifdef DEBUG
+ MOZ_ASSERT(aWorkerPrivate);
+
+ switch (mBehavior) {
+ case ParentThreadUnchangedBusyCount:
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ break;
+
+ case WorkerThreadModifyBusyCount:
+ case WorkerThreadUnchangedBusyCount:
+ aWorkerPrivate->AssertIsOnParentThread();
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown behavior!");
+ }
+#endif
+
+ if (mBehavior == WorkerThreadModifyBusyCount) {
+ return aWorkerPrivate->ModifyBusyCount(true);
+ }
+
+ return true;
+}
+
+bool
+WorkerRunnable::Dispatch()
+{
+ bool ok = PreDispatch(mWorkerPrivate);
+ if (ok) {
+ ok = DispatchInternal();
+ }
+ PostDispatch(mWorkerPrivate, ok);
+ return ok;
+}
+
+bool
+WorkerRunnable::DispatchInternal()
+{
+ RefPtr<WorkerRunnable> runnable(this);
+
+ if (mBehavior == WorkerThreadModifyBusyCount ||
+ mBehavior == WorkerThreadUnchangedBusyCount) {
+ if (IsDebuggerRunnable()) {
+ return NS_SUCCEEDED(mWorkerPrivate->DispatchDebuggerRunnable(runnable.forget()));
+ } else {
+ return NS_SUCCEEDED(mWorkerPrivate->Dispatch(runnable.forget()));
+ }
+ }
+
+ MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount);
+
+ if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) {
+ return NS_SUCCEEDED(parent->Dispatch(runnable.forget()));
+ }
+
+ return NS_SUCCEEDED(mWorkerPrivate->DispatchToMainThread(runnable.forget()));
+}
+
+void
+WorkerRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult)
+{
+ MOZ_ASSERT(aWorkerPrivate);
+
+#ifdef DEBUG
+ switch (mBehavior) {
+ case ParentThreadUnchangedBusyCount:
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ break;
+
+ case WorkerThreadModifyBusyCount:
+ aWorkerPrivate->AssertIsOnParentThread();
+ break;
+
+ case WorkerThreadUnchangedBusyCount:
+ aWorkerPrivate->AssertIsOnParentThread();
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown behavior!");
+ }
+#endif
+
+ if (!aDispatchResult) {
+ if (mBehavior == WorkerThreadModifyBusyCount) {
+ aWorkerPrivate->ModifyBusyCount(false);
+ }
+ }
+}
+
+bool
+WorkerRunnable::PreRun(WorkerPrivate* aWorkerPrivate)
+{
+ return true;
+}
+
+void
+WorkerRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult)
+{
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aWorkerPrivate);
+
+#ifdef DEBUG
+ switch (mBehavior) {
+ case ParentThreadUnchangedBusyCount:
+ aWorkerPrivate->AssertIsOnParentThread();
+ break;
+
+ case WorkerThreadModifyBusyCount:
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ break;
+
+ case WorkerThreadUnchangedBusyCount:
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown behavior!");
+ }
+#endif
+
+ if (mBehavior == WorkerThreadModifyBusyCount) {
+ aWorkerPrivate->ModifyBusyCountFromWorker(false);
+ }
+}
+
+// static
+WorkerRunnable*
+WorkerRunnable::FromRunnable(nsIRunnable* aRunnable)
+{
+ MOZ_ASSERT(aRunnable);
+
+ WorkerRunnable* runnable;
+ nsresult rv = aRunnable->QueryInterface(kWorkerRunnableIID,
+ reinterpret_cast<void**>(&runnable));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(runnable);
+ return runnable;
+}
+
+NS_IMPL_ADDREF(WorkerRunnable)
+NS_IMPL_RELEASE(WorkerRunnable)
+
+NS_INTERFACE_MAP_BEGIN(WorkerRunnable)
+ NS_INTERFACE_MAP_ENTRY(nsIRunnable)
+ NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRunnable)
+ // kWorkerRunnableIID is special in that it does not AddRef its result.
+ if (aIID.Equals(kWorkerRunnableIID)) {
+ *aInstancePtr = this;
+ return NS_OK;
+ }
+ else
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+WorkerRunnable::Run()
+{
+ bool targetIsWorkerThread = mBehavior == WorkerThreadModifyBusyCount ||
+ mBehavior == WorkerThreadUnchangedBusyCount;
+
+#ifdef DEBUG
+ MOZ_ASSERT_IF(mCallingCancelWithinRun, targetIsWorkerThread);
+ if (targetIsWorkerThread) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+ else {
+ MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount);
+ mWorkerPrivate->AssertIsOnParentThread();
+ }
+#endif
+
+ if (IsCanceled() && !mCallingCancelWithinRun) {
+ return NS_OK;
+ }
+
+ if (targetIsWorkerThread &&
+ mWorkerPrivate->AllPendingRunnablesShouldBeCanceled() &&
+ !IsCanceled() && !mCallingCancelWithinRun) {
+
+ // Prevent recursion.
+ mCallingCancelWithinRun = true;
+
+ Cancel();
+
+ MOZ_ASSERT(mCallingCancelWithinRun);
+ mCallingCancelWithinRun = false;
+
+ MOZ_ASSERT(IsCanceled(), "Subclass Cancel() didn't set IsCanceled()!");
+
+ if (mBehavior == WorkerThreadModifyBusyCount) {
+ mWorkerPrivate->ModifyBusyCountFromWorker(false);
+ }
+
+ return NS_OK;
+ }
+
+ bool result = PreRun(mWorkerPrivate);
+ if (!result) {
+ MOZ_ASSERT(targetIsWorkerThread,
+ "The only PreRun implementation that can fail is "
+ "ScriptExecutorRunnable");
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(!JS_IsExceptionPending(mWorkerPrivate->GetJSContext()));
+ // We can't enter a useful compartment on the JSContext here; just pass it
+ // in as-is.
+ PostRun(mWorkerPrivate->GetJSContext(), mWorkerPrivate, false);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Track down the appropriate global, if any, to use for the AutoEntryScript.
+ nsCOMPtr<nsIGlobalObject> globalObject;
+ bool isMainThread = !targetIsWorkerThread && !mWorkerPrivate->GetParent();
+ MOZ_ASSERT(isMainThread == NS_IsMainThread());
+ RefPtr<WorkerPrivate> kungFuDeathGrip;
+ if (targetIsWorkerThread) {
+ JSContext* cx = GetCurrentThreadJSContext();
+ if (NS_WARN_IF(!cx)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JSObject* global = JS::CurrentGlobalOrNull(cx);
+ if (global) {
+ globalObject = xpc::NativeGlobal(global);
+ } else {
+ globalObject = DefaultGlobalObject();
+ }
+
+ // We may still not have a globalObject here: in the case of
+ // CompileScriptRunnable, we don't actually create the global object until
+ // we have the script data, which happens in a syncloop under
+ // CompileScriptRunnable::WorkerRun, so we can't assert that it got created
+ // in the PreRun call above.
+ } else {
+ kungFuDeathGrip = mWorkerPrivate;
+ if (isMainThread) {
+ globalObject = nsGlobalWindow::Cast(mWorkerPrivate->GetWindow());
+ } else {
+ globalObject = mWorkerPrivate->GetParent()->GlobalScope();
+ }
+ }
+
+ // We might run script as part of WorkerRun, so we need an AutoEntryScript.
+ // This is part of the HTML spec for workers at:
+ // http://www.whatwg.org/specs/web-apps/current-work/#run-a-worker
+ // If we don't have a globalObject we have to use an AutoJSAPI instead, but
+ // this is OK as we won't be running script in these circumstances.
+ Maybe<mozilla::dom::AutoJSAPI> maybeJSAPI;
+ Maybe<mozilla::dom::AutoEntryScript> aes;
+ JSContext* cx;
+ AutoJSAPI* jsapi;
+ if (globalObject) {
+ aes.emplace(globalObject, "Worker runnable", isMainThread);
+ jsapi = aes.ptr();
+ cx = aes->cx();
+ } else {
+ maybeJSAPI.emplace();
+ maybeJSAPI->Init();
+ jsapi = maybeJSAPI.ptr();
+ cx = jsapi->cx();
+ }
+
+ // Note that we can't assert anything about mWorkerPrivate->GetWrapper()
+ // existing, since it may in fact have been GCed (and we may be one of the
+ // runnables cleaning up the worker as a result).
+
+ // If we are on the parent thread and that thread is not the main thread,
+ // then we must be a dedicated worker (because there are no
+ // Shared/ServiceWorkers whose parent is itself a worker) and then we
+ // definitely have a globalObject. If it _is_ the main thread, globalObject
+ // can be null for workers started from JSMs or other non-window contexts,
+ // sadly.
+ MOZ_ASSERT_IF(!targetIsWorkerThread && !isMainThread,
+ mWorkerPrivate->IsDedicatedWorker() && globalObject);
+
+ // If we're on the parent thread we might be in a null compartment in the
+ // situation described above when globalObject is null. Make sure to enter
+ // the compartment of the worker's reflector if there is one. There might
+ // not be one if we're just starting to compile the script for this worker.
+ Maybe<JSAutoCompartment> ac;
+ if (!targetIsWorkerThread && mWorkerPrivate->GetWrapper()) {
+ // If we're on the parent thread and have a reflector and a globalObject,
+ // then the compartments of cx, globalObject, and the worker's reflector
+ // should all match.
+ MOZ_ASSERT_IF(globalObject,
+ js::GetObjectCompartment(mWorkerPrivate->GetWrapper()) ==
+ js::GetContextCompartment(cx));
+ MOZ_ASSERT_IF(globalObject,
+ js::GetObjectCompartment(mWorkerPrivate->GetWrapper()) ==
+ js::GetObjectCompartment(globalObject->GetGlobalJSObject()));
+
+ // If we're on the parent thread and have a reflector, then our
+ // JSContext had better be either in the null compartment (and hence
+ // have no globalObject) or in the compartment of our reflector.
+ MOZ_ASSERT(!js::GetContextCompartment(cx) ||
+ js::GetObjectCompartment(mWorkerPrivate->GetWrapper()) ==
+ js::GetContextCompartment(cx),
+ "Must either be in the null compartment or in our reflector "
+ "compartment");
+
+ ac.emplace(cx, mWorkerPrivate->GetWrapper());
+ }
+
+ MOZ_ASSERT(!jsapi->HasException());
+ result = WorkerRun(cx, mWorkerPrivate);
+ MOZ_ASSERT_IF(result, !jsapi->HasException());
+ jsapi->ReportException();
+
+ // We can't even assert that this didn't create our global, since in the case
+ // of CompileScriptRunnable it _does_.
+
+ // It would be nice to avoid passing a JSContext to PostRun, but in the case
+ // of ScriptExecutorRunnable we need to know the current compartment on the
+ // JSContext (the one we set up based on the global returned from PreRun) so
+ // that we can sanely do exception reporting. In particular, we want to make
+ // sure that we do our JS_SetPendingException while still in that compartment,
+ // because otherwise we might end up trying to create a cross-compartment
+ // wrapper when we try to move the JS exception from our runnable's
+ // ErrorResult to the JSContext, and that's not desirable in this case.
+ //
+ // We _could_ skip passing a JSContext here and then in
+ // ScriptExecutorRunnable::PostRun end up grabbing it from the WorkerPrivate
+ // and looking at its current compartment. But that seems like slightly weird
+ // action-at-a-distance...
+ //
+ // In any case, we do NOT try to change the compartment on the JSContext at
+ // this point; in the one case in which we could do that
+ // (CompileScriptRunnable) it actually doesn't matter which compartment we're
+ // in for PostRun.
+ PostRun(cx, mWorkerPrivate, result);
+ MOZ_ASSERT(!jsapi->HasException());
+
+ return result ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
+WorkerRunnable::Cancel()
+{
+ uint32_t canceledCount = ++mCanceled;
+
+ MOZ_ASSERT(canceledCount, "Cancel() overflow!");
+
+ // The docs say that Cancel() should not be called more than once and that we
+ // should throw NS_ERROR_UNEXPECTED if it is.
+ return (canceledCount == 1) ? NS_OK : NS_ERROR_UNEXPECTED;
+}
+
+void
+WorkerDebuggerRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult)
+{
+}
+
+WorkerSyncRunnable::WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+ nsIEventTarget* aSyncLoopTarget)
+: WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mSyncLoopTarget(aSyncLoopTarget)
+{
+#ifdef DEBUG
+ if (mSyncLoopTarget) {
+ mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
+ }
+#endif
+}
+
+WorkerSyncRunnable::WorkerSyncRunnable(
+ WorkerPrivate* aWorkerPrivate,
+ already_AddRefed<nsIEventTarget>&& aSyncLoopTarget)
+: WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+ mSyncLoopTarget(aSyncLoopTarget)
+{
+#ifdef DEBUG
+ if (mSyncLoopTarget) {
+ mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
+ }
+#endif
+}
+
+WorkerSyncRunnable::~WorkerSyncRunnable()
+{
+}
+
+bool
+WorkerSyncRunnable::DispatchInternal()
+{
+ if (mSyncLoopTarget) {
+ RefPtr<WorkerSyncRunnable> runnable(this);
+ return NS_SUCCEEDED(mSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL));
+ }
+
+ return WorkerRunnable::DispatchInternal();
+}
+
+void
+MainThreadWorkerSyncRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult)
+{
+}
+
+MainThreadStopSyncLoopRunnable::MainThreadStopSyncLoopRunnable(
+ WorkerPrivate* aWorkerPrivate,
+ already_AddRefed<nsIEventTarget>&& aSyncLoopTarget,
+ bool aResult)
+: WorkerSyncRunnable(aWorkerPrivate, Move(aSyncLoopTarget)), mResult(aResult)
+{
+ AssertIsOnMainThread();
+#ifdef DEBUG
+ mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
+#endif
+}
+
+nsresult
+MainThreadStopSyncLoopRunnable::Cancel()
+{
+ nsresult rv = Run();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Run() failed");
+
+ nsresult rv2 = WorkerSyncRunnable::Cancel();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv2), "Cancel() failed");
+
+ return NS_FAILED(rv) ? rv : rv2;
+}
+
+bool
+MainThreadStopSyncLoopRunnable::WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate)
+{
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(mSyncLoopTarget);
+
+ nsCOMPtr<nsIEventTarget> syncLoopTarget;
+ mSyncLoopTarget.swap(syncLoopTarget);
+
+ aWorkerPrivate->StopSyncLoop(syncLoopTarget, mResult);
+ return true;
+}
+
+bool
+MainThreadStopSyncLoopRunnable::DispatchInternal()
+{
+ MOZ_ASSERT(mSyncLoopTarget);
+
+ RefPtr<MainThreadStopSyncLoopRunnable> runnable(this);
+ return NS_SUCCEEDED(mSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL));
+}
+
+void
+MainThreadStopSyncLoopRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult)
+{
+}
+
+#ifdef DEBUG
+WorkerControlRunnable::WorkerControlRunnable(WorkerPrivate* aWorkerPrivate,
+ TargetAndBusyBehavior aBehavior)
+: WorkerRunnable(aWorkerPrivate, aBehavior)
+{
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aBehavior == ParentThreadUnchangedBusyCount ||
+ aBehavior == WorkerThreadUnchangedBusyCount,
+ "WorkerControlRunnables should not modify the busy count");
+}
+#endif
+
+nsresult
+WorkerControlRunnable::Cancel()
+{
+ if (NS_FAILED(Run())) {
+ NS_WARNING("WorkerControlRunnable::Run() failed.");
+ }
+
+ return WorkerRunnable::Cancel();
+}
+
+bool
+WorkerControlRunnable::DispatchInternal()
+{
+ RefPtr<WorkerControlRunnable> runnable(this);
+
+ if (mBehavior == WorkerThreadUnchangedBusyCount) {
+ return NS_SUCCEEDED(mWorkerPrivate->DispatchControlRunnable(runnable.forget()));
+ }
+
+ if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) {
+ return NS_SUCCEEDED(parent->DispatchControlRunnable(runnable.forget()));
+ }
+
+ return NS_SUCCEEDED(mWorkerPrivate->DispatchToMainThread(runnable.forget()));
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(WorkerControlRunnable, WorkerRunnable)
+
+WorkerMainThreadRunnable::WorkerMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsACString& aTelemetryKey)
+: mWorkerPrivate(aWorkerPrivate)
+, mTelemetryKey(aTelemetryKey)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+}
+
+void
+WorkerMainThreadRunnable::Dispatch(ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ TimeStamp startTime = TimeStamp::NowLoRes();
+
+ AutoSyncLoopHolder syncLoop(mWorkerPrivate);
+
+ mSyncLoopTarget = syncLoop.EventTarget();
+
+ DebugOnly<nsresult> rv = mWorkerPrivate->DispatchToMainThread(this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv),
+ "Should only fail after xpcom-shutdown-threads and we're gone by then");
+
+ if (!syncLoop.Run()) {
+ aRv.ThrowUncatchableException();
+ }
+
+ Telemetry::Accumulate(Telemetry::SYNC_WORKER_OPERATION, mTelemetryKey,
+ static_cast<uint32_t>((TimeStamp::NowLoRes() - startTime)
+ .ToMilliseconds()));
+ Unused << startTime; // Shut the compiler up.
+}
+
+NS_IMETHODIMP
+WorkerMainThreadRunnable::Run()
+{
+ AssertIsOnMainThread();
+
+ bool runResult = MainThreadRun();
+
+ RefPtr<MainThreadStopSyncLoopRunnable> response =
+ new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
+ mSyncLoopTarget.forget(),
+ runResult);
+
+ MOZ_ALWAYS_TRUE(response->Dispatch());
+
+ return NS_OK;
+}
+
+WorkerCheckAPIExposureOnMainThreadRunnable::WorkerCheckAPIExposureOnMainThreadRunnable(WorkerPrivate* aWorkerPrivate):
+ WorkerMainThreadRunnable(aWorkerPrivate,
+ NS_LITERAL_CSTRING("WorkerCheckAPIExposureOnMainThread"))
+{}
+
+WorkerCheckAPIExposureOnMainThreadRunnable::~WorkerCheckAPIExposureOnMainThreadRunnable()
+{}
+
+bool
+WorkerCheckAPIExposureOnMainThreadRunnable::Dispatch()
+{
+ ErrorResult rv;
+ WorkerMainThreadRunnable::Dispatch(rv);
+ bool ok = !rv.Failed();
+ rv.SuppressException();
+ return ok;
+}
+
+bool
+WorkerSameThreadRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate)
+{
+ // We don't call WorkerRunnable::PreDispatch, because we're using
+ // WorkerThreadModifyBusyCount for mBehavior, and WorkerRunnable will assert
+ // that PreDispatch is on the parent thread in that case.
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ return true;
+}
+
+void
+WorkerSameThreadRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult)
+{
+ // We don't call WorkerRunnable::PostDispatch, because we're using
+ // WorkerThreadModifyBusyCount for mBehavior, and WorkerRunnable will assert
+ // that PostDispatch is on the parent thread in that case.
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ if (aDispatchResult) {
+ DebugOnly<bool> willIncrement = aWorkerPrivate->ModifyBusyCountFromWorker(true);
+ // Should never fail since if this thread is still running, so should the
+ // parent and it should be able to process a control runnable.
+ MOZ_ASSERT(willIncrement);
+ }
+}
+
+WorkerProxyToMainThreadRunnable::WorkerProxyToMainThreadRunnable(WorkerPrivate* aWorkerPrivate)
+ : mWorkerPrivate(aWorkerPrivate)
+{
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+}
+
+WorkerProxyToMainThreadRunnable::~WorkerProxyToMainThreadRunnable()
+{}
+
+bool
+WorkerProxyToMainThreadRunnable::Dispatch()
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (NS_WARN_IF(!HoldWorker())) {
+ RunBackOnWorkerThread();
+ return false;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(mWorkerPrivate->DispatchToMainThread(this)))) {
+ ReleaseWorker();
+ RunBackOnWorkerThread();
+ return false;
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+WorkerProxyToMainThreadRunnable::Run()
+{
+ AssertIsOnMainThread();
+ RunOnMainThread();
+ PostDispatchOnMainThread();
+ return NS_OK;
+}
+
+void
+WorkerProxyToMainThreadRunnable::PostDispatchOnMainThread()
+{
+ class ReleaseRunnable final : public MainThreadWorkerControlRunnable
+ {
+ RefPtr<WorkerProxyToMainThreadRunnable> mRunnable;
+
+ public:
+ ReleaseRunnable(WorkerPrivate* aWorkerPrivate,
+ WorkerProxyToMainThreadRunnable* aRunnable)
+ : MainThreadWorkerControlRunnable(aWorkerPrivate)
+ , mRunnable(aRunnable)
+ {
+ MOZ_ASSERT(aRunnable);
+ }
+
+ // We must call RunBackOnWorkerThread() also if the runnable is canceled.
+ nsresult
+ Cancel() override
+ {
+ WorkerRun(nullptr, mWorkerPrivate);
+ return MainThreadWorkerControlRunnable::Cancel();
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, workers::WorkerPrivate* aWorkerPrivate) override
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mRunnable) {
+ mRunnable->RunBackOnWorkerThread();
+
+ // Let's release the worker thread.
+ mRunnable->ReleaseWorker();
+ mRunnable = nullptr;
+ }
+
+ return true;
+ }
+
+ private:
+ ~ReleaseRunnable()
+ {}
+ };
+
+ RefPtr<WorkerControlRunnable> runnable =
+ new ReleaseRunnable(mWorkerPrivate, this);
+ Unused << NS_WARN_IF(!runnable->Dispatch());
+}
+
+bool
+WorkerProxyToMainThreadRunnable::HoldWorker()
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(!mWorkerHolder);
+
+ class SimpleWorkerHolder final : public WorkerHolder
+ {
+ public:
+ bool Notify(Status aStatus) override
+ {
+ // We don't care about the notification. We just want to keep the
+ // mWorkerPrivate alive.
+ return true;
+ }
+ };
+
+ UniquePtr<WorkerHolder> workerHolder(new SimpleWorkerHolder());
+ if (NS_WARN_IF(!workerHolder->HoldWorker(mWorkerPrivate, Canceling))) {
+ return false;
+ }
+
+ mWorkerHolder = Move(workerHolder);
+ return true;
+}
+
+void
+WorkerProxyToMainThreadRunnable::ReleaseWorker()
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(mWorkerHolder);
+ mWorkerHolder = nullptr;
+}
diff --git a/dom/workers/WorkerRunnable.h b/dom/workers/WorkerRunnable.h
new file mode 100644
index 000000000..c65060f44
--- /dev/null
+++ b/dom/workers/WorkerRunnable.h
@@ -0,0 +1,509 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_workerrunnable_h__
+#define mozilla_dom_workers_workerrunnable_h__
+
+#include "Workers.h"
+
+#include "nsICancelableRunnable.h"
+
+#include "mozilla/Atomics.h"
+#include "nsISupportsImpl.h"
+#include "nsThreadUtils.h" /* nsRunnable */
+
+struct JSContext;
+class nsIEventTarget;
+
+namespace mozilla {
+class ErrorResult;
+} // namespace mozilla
+
+BEGIN_WORKERS_NAMESPACE
+
+class WorkerPrivate;
+
+// Use this runnable to communicate from the worker to its parent or vice-versa.
+// The busy count must be taken into consideration and declared at construction
+// time.
+class WorkerRunnable : public nsIRunnable,
+ public nsICancelableRunnable
+{
+public:
+ enum TargetAndBusyBehavior {
+ // Target the main thread for top-level workers, otherwise target the
+ // WorkerThread of the worker's parent. No change to the busy count.
+ ParentThreadUnchangedBusyCount,
+
+ // Target the thread where the worker event loop runs. The busy count will
+ // be incremented before dispatching and decremented (asynchronously) after
+ // running.
+ WorkerThreadModifyBusyCount,
+
+ // Target the thread where the worker event loop runs. The busy count will
+ // not be modified in any way. Besides worker-internal runnables this is
+ // almost always the wrong choice.
+ WorkerThreadUnchangedBusyCount
+ };
+
+protected:
+ // The WorkerPrivate that this runnable is associated with.
+ WorkerPrivate* mWorkerPrivate;
+
+ // See above.
+ TargetAndBusyBehavior mBehavior;
+
+ // It's unclear whether or not Cancel() is supposed to work when called on any
+ // thread. To be safe we're using an atomic but it's likely overkill.
+ Atomic<uint32_t> mCanceled;
+
+private:
+ // Whether or not Cancel() is currently being called from inside the Run()
+ // method. Avoids infinite recursion when a subclass calls Run() from inside
+ // Cancel(). Only checked and modified on the target thread.
+ bool mCallingCancelWithinRun;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // If you override Cancel() then you'll need to either call the base class
+ // Cancel() method or override IsCanceled() so that the Run() method bails out
+ // appropriately.
+ nsresult
+ Cancel() override;
+
+ // The return value is true if and only if both PreDispatch and
+ // DispatchInternal return true.
+ bool
+ Dispatch();
+
+ // See above note about Cancel().
+ virtual bool
+ IsCanceled() const
+ {
+ return mCanceled != 0;
+ }
+
+ static WorkerRunnable*
+ FromRunnable(nsIRunnable* aRunnable);
+
+protected:
+ WorkerRunnable(WorkerPrivate* aWorkerPrivate,
+ TargetAndBusyBehavior aBehavior = WorkerThreadModifyBusyCount)
+#ifdef DEBUG
+ ;
+#else
+ : mWorkerPrivate(aWorkerPrivate), mBehavior(aBehavior), mCanceled(0),
+ mCallingCancelWithinRun(false)
+ { }
+#endif
+
+ // This class is reference counted.
+ virtual ~WorkerRunnable()
+ { }
+
+ // Returns true if this runnable should be dispatched to the debugger queue,
+ // and false otherwise.
+ virtual bool
+ IsDebuggerRunnable() const;
+
+ nsIGlobalObject*
+ DefaultGlobalObject() const;
+
+ // By default asserts that Dispatch() is being called on the right thread
+ // (ParentThread if |mTarget| is WorkerThread, or WorkerThread otherwise).
+ // Also increments the busy count of |mWorkerPrivate| if targeting the
+ // WorkerThread.
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate);
+
+ // By default asserts that Dispatch() is being called on the right thread
+ // (ParentThread if |mTarget| is WorkerThread, or WorkerThread otherwise).
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult);
+
+ // May be implemented by subclasses if desired if they need to do some sort of
+ // setup before we try to set up our JSContext and compartment for real.
+ // Typically the only thing that should go in here is creation of the worker's
+ // global.
+ //
+ // If false is returned, WorkerRun will not be called at all. PostRun will
+ // still be called, with false passed for aRunResult.
+ virtual bool
+ PreRun(WorkerPrivate* aWorkerPrivate);
+
+ // Must be implemented by subclasses. Called on the target thread. The return
+ // value will be passed to PostRun(). The JSContext passed in here comes from
+ // an AutoJSAPI (or AutoEntryScript) that we set up on the stack. If
+ // mBehavior is ParentThreadUnchangedBusyCount, it is in the compartment of
+ // mWorkerPrivate's reflector (i.e. the worker object in the parent thread),
+ // unless that reflector is null, in which case it's in the compartment of the
+ // parent global (which is the compartment reflector would have been in), or
+ // in the null compartment if there is no parent global. For other mBehavior
+ // values, we're running on the worker thread and aCx is in whatever
+ // compartment GetCurrentThreadJSContext() was in when nsIRunnable::Run() got
+ // called. This is actually important for cases when a runnable spins a
+ // syncloop and wants everything that happens during the syncloop to happen in
+ // the compartment that runnable set up (which may, for example, be a debugger
+ // sandbox compartment!). If aCx wasn't in a compartment to start with, aCx
+ // will be in either the debugger global's compartment or the worker's
+ // global's compartment depending on whether IsDebuggerRunnable() is true.
+ //
+ // Immediately after WorkerRun returns, the caller will assert that either it
+ // returns false or there is no exception pending on aCx. Then it will report
+ // any pending exceptions on aCx.
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) = 0;
+
+ // By default asserts that Run() (and WorkerRun()) were called on the correct
+ // thread. Also sends an asynchronous message to the ParentThread if the
+ // busy count was previously modified in PreDispatch().
+ //
+ // The aCx passed here is the same one as was passed to WorkerRun and is
+ // still in the same compartment. PostRun implementations must NOT leave an
+ // exception on the JSContext and must not run script, because the incoming
+ // JSContext may be in the null compartment.
+ virtual void
+ PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult);
+
+ virtual bool
+ DispatchInternal();
+
+ // Calling Run() directly is not supported. Just call Dispatch() and
+ // WorkerRun() will be called on the correct thread automatically.
+ NS_DECL_NSIRUNNABLE
+};
+
+// This runnable is used to send a message to a worker debugger.
+class WorkerDebuggerRunnable : public WorkerRunnable
+{
+protected:
+ explicit WorkerDebuggerRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ {
+ }
+
+ virtual ~WorkerDebuggerRunnable()
+ { }
+
+private:
+ virtual bool
+ IsDebuggerRunnable() const override
+ {
+ return true;
+ }
+
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override final
+ {
+ AssertIsOnMainThread();
+
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override;
+};
+
+// This runnable is used to send a message directly to a worker's sync loop.
+class WorkerSyncRunnable : public WorkerRunnable
+{
+protected:
+ nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
+
+ // Passing null for aSyncLoopTarget is allowed and will result in the behavior
+ // of a normal WorkerRunnable.
+ WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+ nsIEventTarget* aSyncLoopTarget);
+
+ WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+ already_AddRefed<nsIEventTarget>&& aSyncLoopTarget);
+
+ virtual ~WorkerSyncRunnable();
+
+ virtual bool
+ DispatchInternal() override;
+};
+
+// This runnable is identical to WorkerSyncRunnable except it is meant to be
+// created on and dispatched from the main thread only. Its WorkerRun/PostRun
+// will run on the worker thread.
+class MainThreadWorkerSyncRunnable : public WorkerSyncRunnable
+{
+protected:
+ // Passing null for aSyncLoopTarget is allowed and will result in the behavior
+ // of a normal WorkerRunnable.
+ MainThreadWorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+ nsIEventTarget* aSyncLoopTarget)
+ : WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget)
+ {
+ AssertIsOnMainThread();
+ }
+
+ MainThreadWorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
+ already_AddRefed<nsIEventTarget>&& aSyncLoopTarget)
+ : WorkerSyncRunnable(aWorkerPrivate, Move(aSyncLoopTarget))
+ {
+ AssertIsOnMainThread();
+ }
+
+ virtual ~MainThreadWorkerSyncRunnable()
+ { }
+
+private:
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ AssertIsOnMainThread();
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override;
+};
+
+// This runnable is processed as soon as it is received by the worker,
+// potentially running before previously queued runnables and perhaps even with
+// other JS code executing on the stack. These runnables must not alter the
+// state of the JS runtime and should only twiddle state values. The busy count
+// is never modified.
+class WorkerControlRunnable : public WorkerRunnable
+{
+ friend class WorkerPrivate;
+
+protected:
+ WorkerControlRunnable(WorkerPrivate* aWorkerPrivate,
+ TargetAndBusyBehavior aBehavior = WorkerThreadModifyBusyCount)
+#ifdef DEBUG
+ ;
+#else
+ : WorkerRunnable(aWorkerPrivate, aBehavior)
+ { }
+#endif
+
+ virtual ~WorkerControlRunnable()
+ { }
+
+ nsresult
+ Cancel() override;
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+private:
+ virtual bool
+ DispatchInternal() override;
+
+ // Should only be called by WorkerPrivate::DoRunLoop.
+ using WorkerRunnable::Cancel;
+};
+
+// A convenience class for WorkerRunnables that are originated on the main
+// thread.
+class MainThreadWorkerRunnable : public WorkerRunnable
+{
+protected:
+ explicit MainThreadWorkerRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ {
+ AssertIsOnMainThread();
+ }
+
+ virtual ~MainThreadWorkerRunnable()
+ {}
+
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ AssertIsOnMainThread();
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override
+ {
+ AssertIsOnMainThread();
+ }
+};
+
+// A convenience class for WorkerControlRunnables that originate on the main
+// thread.
+class MainThreadWorkerControlRunnable : public WorkerControlRunnable
+{
+protected:
+ explicit MainThreadWorkerControlRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ { }
+
+ virtual ~MainThreadWorkerControlRunnable()
+ { }
+
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ AssertIsOnMainThread();
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {
+ AssertIsOnMainThread();
+ }
+};
+
+// A WorkerRunnable that should be dispatched from the worker to itself for
+// async tasks. This will increment the busy count PostDispatch() (only if
+// dispatch was successful) and decrement it in PostRun().
+//
+// Async tasks will almost always want to use this since
+// a WorkerSameThreadRunnable keeps the Worker from being GCed.
+class WorkerSameThreadRunnable : public WorkerRunnable
+{
+protected:
+ explicit WorkerSameThreadRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
+ { }
+
+ virtual ~WorkerSameThreadRunnable()
+ { }
+
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override;
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override;
+
+ // We just delegate PostRun to WorkerRunnable, since it does exactly
+ // what we want.
+};
+
+// Base class for the runnable objects, which makes a synchronous call to
+// dispatch the tasks from the worker thread to the main thread.
+//
+// Note that the derived class must override MainThreadRun.
+class WorkerMainThreadRunnable : public Runnable
+{
+protected:
+ WorkerPrivate* mWorkerPrivate;
+ nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
+ const nsCString mTelemetryKey;
+
+ explicit WorkerMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsACString& aTelemetryKey);
+ ~WorkerMainThreadRunnable() {}
+
+ virtual bool MainThreadRun() = 0;
+
+public:
+ // Dispatch the runnable to the main thread. If dispatch to main thread
+ // fails, or if the worker is shut down while dispatching, an error will be
+ // reported on aRv. In that case the error MUST be propagated out to script.
+ void Dispatch(ErrorResult& aRv);
+
+private:
+ NS_IMETHOD Run() override;
+};
+
+// This runnable is an helper class for dispatching something from a worker
+// thread to the main-thread and back to the worker-thread. During this
+// operation, this class will keep the worker alive.
+class WorkerProxyToMainThreadRunnable : public Runnable
+{
+protected:
+ explicit WorkerProxyToMainThreadRunnable(WorkerPrivate* aWorkerPrivate);
+
+ virtual ~WorkerProxyToMainThreadRunnable();
+
+ // First this method is called on the main-thread.
+ virtual void RunOnMainThread() = 0;
+
+ // After this second method is called on the worker-thread.
+ virtual void RunBackOnWorkerThread() = 0;
+
+public:
+ bool Dispatch();
+
+private:
+ NS_IMETHOD Run() override;
+
+ void PostDispatchOnMainThread();
+
+ bool HoldWorker();
+ void ReleaseWorker();
+
+protected:
+ WorkerPrivate* mWorkerPrivate;
+ UniquePtr<WorkerHolder> mWorkerHolder;
+};
+
+// Class for checking API exposure. This totally violates the "MUST" in the
+// comments on WorkerMainThreadRunnable::Dispatch, because API exposure checks
+// can't throw. Maybe we should change it so they _could_ throw. But for now
+// we are bad people and should be ashamed of ourselves. Let's hope none of
+// them happen while a worker is shutting down.
+//
+// Do NOT copy what this class is doing elsewhere. Just don't.
+class WorkerCheckAPIExposureOnMainThreadRunnable
+ : public WorkerMainThreadRunnable
+{
+public:
+ explicit
+ WorkerCheckAPIExposureOnMainThreadRunnable(WorkerPrivate* aWorkerPrivate);
+ virtual
+ ~WorkerCheckAPIExposureOnMainThreadRunnable();
+
+ // Returns whether the dispatch succeeded. If this returns false, the API
+ // should not be exposed.
+ bool Dispatch();
+};
+
+// This runnable is used to stop a sync loop and it's meant to be used on the
+// main-thread only. As sync loops keep the busy count incremented as long as
+// they run this runnable does not modify the busy count
+// in any way.
+class MainThreadStopSyncLoopRunnable : public WorkerSyncRunnable
+{
+ bool mResult;
+
+public:
+ // Passing null for aSyncLoopTarget is not allowed.
+ MainThreadStopSyncLoopRunnable(
+ WorkerPrivate* aWorkerPrivate,
+ already_AddRefed<nsIEventTarget>&& aSyncLoopTarget,
+ bool aResult);
+
+ // By default StopSyncLoopRunnables cannot be canceled since they could leave
+ // a sync loop spinning forever.
+ nsresult
+ Cancel() override;
+
+protected:
+ virtual ~MainThreadStopSyncLoopRunnable()
+ { }
+
+private:
+ virtual bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override final
+ {
+ AssertIsOnMainThread();
+ return true;
+ }
+
+ virtual void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override;
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
+
+ virtual bool
+ DispatchInternal() override final;
+};
+
+END_WORKERS_NAMESPACE
+
+#endif // mozilla_dom_workers_workerrunnable_h__
diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp
new file mode 100644
index 000000000..d9a987b62
--- /dev/null
+++ b/dom/workers/WorkerScope.cpp
@@ -0,0 +1,978 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WorkerScope.h"
+
+#include "jsapi.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/Console.h"
+#include "mozilla/dom/DedicatedWorkerGlobalScopeBinding.h"
+#include "mozilla/dom/Fetch.h"
+#include "mozilla/dom/FunctionBinding.h"
+#include "mozilla/dom/IDBFactory.h"
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/ImageBitmapBinding.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseWorkerProxy.h"
+#include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
+#include "mozilla/dom/SharedWorkerGlobalScopeBinding.h"
+#include "mozilla/dom/SimpleGlobalObject.h"
+#include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
+#include "mozilla/dom/WorkerGlobalScopeBinding.h"
+#include "mozilla/dom/WorkerLocation.h"
+#include "mozilla/dom/WorkerNavigator.h"
+#include "mozilla/dom/cache/CacheStorage.h"
+#include "mozilla/Services.h"
+#include "nsServiceManagerUtils.h"
+
+#include "nsIDocument.h"
+#include "nsIServiceWorkerManager.h"
+#include "nsIScriptTimeoutHandler.h"
+
+#ifdef ANDROID
+#include <android/log.h>
+#endif
+
+#include "Crypto.h"
+#include "Principal.h"
+#include "RuntimeService.h"
+#include "ScriptLoader.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "ServiceWorkerClients.h"
+#include "ServiceWorkerManager.h"
+#include "ServiceWorkerRegistration.h"
+
+#ifdef XP_WIN
+#undef PostMessage
+#endif
+
+extern already_AddRefed<nsIScriptTimeoutHandler>
+NS_CreateJSTimeoutHandler(JSContext* aCx,
+ mozilla::dom::workers::WorkerPrivate* aWorkerPrivate,
+ mozilla::dom::Function& aFunction,
+ const mozilla::dom::Sequence<JS::Value>& aArguments,
+ mozilla::ErrorResult& aError);
+
+extern already_AddRefed<nsIScriptTimeoutHandler>
+NS_CreateJSTimeoutHandler(JSContext* aCx,
+ mozilla::dom::workers::WorkerPrivate* aWorkerPrivate,
+ const nsAString& aExpression);
+
+using namespace mozilla;
+using namespace mozilla::dom;
+USING_WORKERS_NAMESPACE
+
+using mozilla::dom::cache::CacheStorage;
+using mozilla::ipc::PrincipalInfo;
+
+WorkerGlobalScope::WorkerGlobalScope(WorkerPrivate* aWorkerPrivate)
+: mWindowInteractionsAllowed(0)
+, mWorkerPrivate(aWorkerPrivate)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+}
+
+WorkerGlobalScope::~WorkerGlobalScope()
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(WorkerGlobalScope)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WorkerGlobalScope,
+ DOMEventTargetHelper)
+ tmp->mWorkerPrivate->AssertIsOnWorkerThread();
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrypto)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocation)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheStorage)
+ tmp->TraverseHostObjectURIs(cb);
+ tmp->mWorkerPrivate->TraverseTimeouts(cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WorkerGlobalScope,
+ DOMEventTargetHelper)
+ tmp->mWorkerPrivate->AssertIsOnWorkerThread();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheStorage)
+ tmp->UnlinkHostObjectURIs();
+ tmp->mWorkerPrivate->UnlinkTimeouts();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WorkerGlobalScope,
+ DOMEventTargetHelper)
+ tmp->mWorkerPrivate->AssertIsOnWorkerThread();
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_ADDREF_INHERITED(WorkerGlobalScope, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(WorkerGlobalScope, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerGlobalScope)
+ NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+JSObject*
+WorkerGlobalScope::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ MOZ_CRASH("We should never get here!");
+}
+
+Console*
+WorkerGlobalScope::GetConsole(ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (!mConsole) {
+ mConsole = Console::Create(nullptr, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ return mConsole;
+}
+
+Crypto*
+WorkerGlobalScope::GetCrypto(ErrorResult& aError)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (!mCrypto) {
+ mCrypto = new Crypto();
+ mCrypto->Init(this);
+ }
+
+ return mCrypto;
+}
+
+already_AddRefed<CacheStorage>
+WorkerGlobalScope::GetCaches(ErrorResult& aRv)
+{
+ if (!mCacheStorage) {
+ MOZ_ASSERT(mWorkerPrivate);
+ mCacheStorage = CacheStorage::CreateOnWorker(cache::DEFAULT_NAMESPACE, this,
+ mWorkerPrivate, aRv);
+ }
+
+ RefPtr<CacheStorage> ref = mCacheStorage;
+ return ref.forget();
+}
+
+bool
+WorkerGlobalScope::IsSecureContext() const
+{
+ bool globalSecure =
+ JS_GetIsSecureContext(js::GetObjectCompartment(GetWrapperPreserveColor()));
+ MOZ_ASSERT(globalSecure == mWorkerPrivate->IsSecureContext());
+ return globalSecure;
+}
+
+already_AddRefed<WorkerLocation>
+WorkerGlobalScope::Location()
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (!mLocation) {
+ WorkerPrivate::LocationInfo& info = mWorkerPrivate->GetLocationInfo();
+
+ mLocation = WorkerLocation::Create(info);
+ MOZ_ASSERT(mLocation);
+ }
+
+ RefPtr<WorkerLocation> location = mLocation;
+ return location.forget();
+}
+
+already_AddRefed<WorkerNavigator>
+WorkerGlobalScope::Navigator()
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (!mNavigator) {
+ mNavigator = WorkerNavigator::Create(mWorkerPrivate->OnLine());
+ MOZ_ASSERT(mNavigator);
+ }
+
+ RefPtr<WorkerNavigator> navigator = mNavigator;
+ return navigator.forget();
+}
+
+already_AddRefed<WorkerNavigator>
+WorkerGlobalScope::GetExistingNavigator() const
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<WorkerNavigator> navigator = mNavigator;
+ return navigator.forget();
+}
+
+void
+WorkerGlobalScope::Close(JSContext* aCx, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mWorkerPrivate->IsServiceWorker()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ } else {
+ mWorkerPrivate->CloseInternal(aCx);
+ }
+}
+
+OnErrorEventHandlerNonNull*
+WorkerGlobalScope::GetOnerror()
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ EventListenerManager* elm = GetExistingListenerManager();
+ return elm ? elm->GetOnErrorEventHandler() : nullptr;
+}
+
+void
+WorkerGlobalScope::SetOnerror(OnErrorEventHandlerNonNull* aHandler)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ EventListenerManager* elm = GetOrCreateListenerManager();
+ if (elm) {
+ elm->SetEventHandler(aHandler);
+ }
+}
+
+void
+WorkerGlobalScope::ImportScripts(const Sequence<nsString>& aScriptURLs,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ scriptloader::Load(mWorkerPrivate, aScriptURLs, WorkerScript, aRv);
+}
+
+int32_t
+WorkerGlobalScope::SetTimeout(JSContext* aCx,
+ Function& aHandler,
+ const int32_t aTimeout,
+ const Sequence<JS::Value>& aArguments,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ nsCOMPtr<nsIScriptTimeoutHandler> handler =
+ NS_CreateJSTimeoutHandler(aCx, mWorkerPrivate, aHandler, aArguments, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return 0;
+ }
+
+ return mWorkerPrivate->SetTimeout(aCx, handler, aTimeout, false, aRv);
+}
+
+int32_t
+WorkerGlobalScope::SetTimeout(JSContext* aCx,
+ const nsAString& aHandler,
+ const int32_t aTimeout,
+ const Sequence<JS::Value>& /* unused */,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ nsCOMPtr<nsIScriptTimeoutHandler> handler =
+ NS_CreateJSTimeoutHandler(aCx, mWorkerPrivate, aHandler);
+ return mWorkerPrivate->SetTimeout(aCx, handler, aTimeout, false, aRv);
+}
+
+void
+WorkerGlobalScope::ClearTimeout(int32_t aHandle)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ mWorkerPrivate->ClearTimeout(aHandle);
+}
+
+int32_t
+WorkerGlobalScope::SetInterval(JSContext* aCx,
+ Function& aHandler,
+ const Optional<int32_t>& aTimeout,
+ const Sequence<JS::Value>& aArguments,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ bool isInterval = aTimeout.WasPassed();
+ int32_t timeout = aTimeout.WasPassed() ? aTimeout.Value() : 0;
+
+ nsCOMPtr<nsIScriptTimeoutHandler> handler =
+ NS_CreateJSTimeoutHandler(aCx, mWorkerPrivate, aHandler, aArguments, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return 0;
+ }
+
+ return mWorkerPrivate->SetTimeout(aCx, handler, timeout, isInterval, aRv);
+}
+
+int32_t
+WorkerGlobalScope::SetInterval(JSContext* aCx,
+ const nsAString& aHandler,
+ const Optional<int32_t>& aTimeout,
+ const Sequence<JS::Value>& /* unused */,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ Sequence<JS::Value> dummy;
+
+ bool isInterval = aTimeout.WasPassed();
+ int32_t timeout = aTimeout.WasPassed() ? aTimeout.Value() : 0;
+
+ nsCOMPtr<nsIScriptTimeoutHandler> handler =
+ NS_CreateJSTimeoutHandler(aCx, mWorkerPrivate, aHandler);
+ return mWorkerPrivate->SetTimeout(aCx, handler, timeout, isInterval, aRv);
+}
+
+void
+WorkerGlobalScope::ClearInterval(int32_t aHandle)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ mWorkerPrivate->ClearTimeout(aHandle);
+}
+
+void
+WorkerGlobalScope::Atob(const nsAString& aAtob, nsAString& aOutput, ErrorResult& aRv) const
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ aRv = nsContentUtils::Atob(aAtob, aOutput);
+}
+
+void
+WorkerGlobalScope::Btoa(const nsAString& aBtoa, nsAString& aOutput, ErrorResult& aRv) const
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ aRv = nsContentUtils::Btoa(aBtoa, aOutput);
+}
+
+void
+WorkerGlobalScope::Dump(const Optional<nsAString>& aString) const
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (!aString.WasPassed()) {
+ return;
+ }
+
+#if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
+ if (!mWorkerPrivate->DumpEnabled()) {
+ return;
+ }
+#endif
+
+ NS_ConvertUTF16toUTF8 str(aString.Value());
+
+ MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug, ("[Worker.Dump] %s", str.get()));
+#ifdef ANDROID
+ __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", str.get());
+#endif
+ fputs(str.get(), stdout);
+ fflush(stdout);
+}
+
+Performance*
+WorkerGlobalScope::GetPerformance()
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (!mPerformance) {
+ mPerformance = Performance::CreateForWorker(mWorkerPrivate);
+ }
+
+ return mPerformance;
+}
+
+already_AddRefed<Promise>
+WorkerGlobalScope::Fetch(const RequestOrUSVString& aInput,
+ const RequestInit& aInit, ErrorResult& aRv)
+{
+ return FetchRequest(this, aInput, aInit, aRv);
+}
+
+already_AddRefed<IDBFactory>
+WorkerGlobalScope::GetIndexedDB(ErrorResult& aErrorResult)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<IDBFactory> indexedDB = mIndexedDB;
+
+ if (!indexedDB) {
+ if (!mWorkerPrivate->IsStorageAllowed()) {
+ NS_WARNING("IndexedDB is not allowed in this worker!");
+ aErrorResult = NS_ERROR_DOM_SECURITY_ERR;
+ return nullptr;
+ }
+
+ JSContext* cx = mWorkerPrivate->GetJSContext();
+ MOZ_ASSERT(cx);
+
+ JS::Rooted<JSObject*> owningObject(cx, GetGlobalJSObject());
+ MOZ_ASSERT(owningObject);
+
+ const PrincipalInfo& principalInfo = mWorkerPrivate->GetPrincipalInfo();
+
+ nsresult rv =
+ IDBFactory::CreateForWorker(cx,
+ owningObject,
+ principalInfo,
+ mWorkerPrivate->WindowID(),
+ getter_AddRefs(indexedDB));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aErrorResult = rv;
+ return nullptr;
+ }
+
+ mIndexedDB = indexedDB;
+ }
+
+ return indexedDB.forget();
+}
+
+already_AddRefed<Promise>
+WorkerGlobalScope::CreateImageBitmap(const ImageBitmapSource& aImage,
+ ErrorResult& aRv)
+{
+ if (aImage.IsArrayBuffer() || aImage.IsArrayBufferView()) {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return nullptr;
+ }
+
+ return ImageBitmap::Create(this, aImage, Nothing(), aRv);
+}
+
+already_AddRefed<Promise>
+WorkerGlobalScope::CreateImageBitmap(const ImageBitmapSource& aImage,
+ int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh,
+ ErrorResult& aRv)
+{
+ if (aImage.IsArrayBuffer() || aImage.IsArrayBufferView()) {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return nullptr;
+ }
+
+ return ImageBitmap::Create(this, aImage, Some(gfx::IntRect(aSx, aSy, aSw, aSh)), aRv);
+}
+
+already_AddRefed<mozilla::dom::Promise>
+WorkerGlobalScope::CreateImageBitmap(const ImageBitmapSource& aImage,
+ int32_t aOffset, int32_t aLength,
+ ImageBitmapFormat aFormat,
+ const Sequence<ChannelPixelLayout>& aLayout,
+ ErrorResult& aRv)
+{
+ JSContext* cx = GetCurrentThreadJSContext();
+ MOZ_ASSERT(cx);
+
+ if (!ImageBitmap::ExtensionsEnabled(cx, nullptr)) {
+ aRv.Throw(NS_ERROR_TYPE_ERR);
+ return nullptr;
+ }
+
+ if (aImage.IsArrayBuffer() || aImage.IsArrayBufferView()) {
+ return ImageBitmap::Create(this, aImage, aOffset, aLength, aFormat, aLayout,
+ aRv);
+ } else {
+ aRv.Throw(NS_ERROR_TYPE_ERR);
+ return nullptr;
+ }
+}
+
+DedicatedWorkerGlobalScope::DedicatedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate)
+: WorkerGlobalScope(aWorkerPrivate)
+{
+}
+
+bool
+DedicatedWorkerGlobalScope::WrapGlobalObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aReflector)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(!mWorkerPrivate->IsSharedWorker());
+
+ JS::CompartmentOptions options;
+ mWorkerPrivate->CopyJSCompartmentOptions(options);
+
+ const bool usesSystemPrincipal = mWorkerPrivate->UsesSystemPrincipal();
+
+ // Note that xpc::ShouldDiscardSystemSource() and
+ // xpc::ExtraWarningsForSystemJS() read prefs that are cached on the main
+ // thread. This is benignly racey.
+ const bool discardSource = usesSystemPrincipal &&
+ xpc::ShouldDiscardSystemSource();
+ const bool extraWarnings = usesSystemPrincipal &&
+ xpc::ExtraWarningsForSystemJS();
+
+ JS::CompartmentBehaviors& behaviors = options.behaviors();
+ behaviors.setDiscardSource(discardSource)
+ .extraWarningsOverride().set(extraWarnings);
+
+ const bool sharedMemoryEnabled = xpc::SharedMemoryEnabled();
+
+ JS::CompartmentCreationOptions& creationOptions = options.creationOptions();
+ creationOptions.setSharedMemoryAndAtomicsEnabled(sharedMemoryEnabled);
+
+ return DedicatedWorkerGlobalScopeBinding::Wrap(aCx, this, this,
+ options,
+ GetWorkerPrincipal(),
+ true, aReflector);
+}
+
+void
+DedicatedWorkerGlobalScope::PostMessage(JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ mWorkerPrivate->PostMessageToParent(aCx, aMessage, aTransferable, aRv);
+}
+
+SharedWorkerGlobalScope::SharedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate,
+ const nsCString& aName)
+: WorkerGlobalScope(aWorkerPrivate), mName(aName)
+{
+}
+
+bool
+SharedWorkerGlobalScope::WrapGlobalObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aReflector)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(mWorkerPrivate->IsSharedWorker());
+
+ JS::CompartmentOptions options;
+ mWorkerPrivate->CopyJSCompartmentOptions(options);
+
+ return SharedWorkerGlobalScopeBinding::Wrap(aCx, this, this, options,
+ GetWorkerPrincipal(),
+ true, aReflector);
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope,
+ mClients, mRegistration)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServiceWorkerGlobalScope)
+NS_INTERFACE_MAP_END_INHERITING(WorkerGlobalScope)
+
+NS_IMPL_ADDREF_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope)
+NS_IMPL_RELEASE_INHERITED(ServiceWorkerGlobalScope, WorkerGlobalScope)
+
+ServiceWorkerGlobalScope::ServiceWorkerGlobalScope(WorkerPrivate* aWorkerPrivate,
+ const nsACString& aScope)
+ : WorkerGlobalScope(aWorkerPrivate),
+ mScope(NS_ConvertUTF8toUTF16(aScope))
+{
+}
+
+ServiceWorkerGlobalScope::~ServiceWorkerGlobalScope()
+{
+}
+
+bool
+ServiceWorkerGlobalScope::WrapGlobalObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aReflector)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(mWorkerPrivate->IsServiceWorker());
+
+ JS::CompartmentOptions options;
+ mWorkerPrivate->CopyJSCompartmentOptions(options);
+
+ return ServiceWorkerGlobalScopeBinding::Wrap(aCx, this, this, options,
+ GetWorkerPrincipal(),
+ true, aReflector);
+}
+
+ServiceWorkerClients*
+ServiceWorkerGlobalScope::Clients()
+{
+ if (!mClients) {
+ mClients = new ServiceWorkerClients(this);
+ }
+
+ return mClients;
+}
+
+ServiceWorkerRegistration*
+ServiceWorkerGlobalScope::Registration()
+{
+ if (!mRegistration) {
+ mRegistration =
+ ServiceWorkerRegistration::CreateForWorker(mWorkerPrivate, mScope);
+ }
+
+ return mRegistration;
+}
+
+namespace {
+
+class SkipWaitingResultRunnable final : public WorkerRunnable
+{
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+
+public:
+ SkipWaitingResultRunnable(WorkerPrivate* aWorkerPrivate,
+ PromiseWorkerProxy* aPromiseProxy)
+ : WorkerRunnable(aWorkerPrivate)
+ , mPromiseProxy(aPromiseProxy)
+ {
+ AssertIsOnMainThread();
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<Promise> promise = mPromiseProxy->WorkerPromise();
+ promise->MaybeResolveWithUndefined();
+
+ // Release the reference on the worker thread.
+ mPromiseProxy->CleanUp();
+
+ return true;
+ }
+};
+
+class WorkerScopeSkipWaitingRunnable final : public Runnable
+{
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ nsCString mScope;
+
+public:
+ WorkerScopeSkipWaitingRunnable(PromiseWorkerProxy* aPromiseProxy,
+ const nsCString& aScope)
+ : mPromiseProxy(aPromiseProxy)
+ , mScope(aScope)
+ {
+ MOZ_ASSERT(aPromiseProxy);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return NS_OK;
+ }
+
+ WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->SetSkipWaitingFlag(workerPrivate->GetPrincipal(), mScope,
+ workerPrivate->ServiceWorkerID());
+ }
+
+ RefPtr<SkipWaitingResultRunnable> runnable =
+ new SkipWaitingResultRunnable(workerPrivate, mPromiseProxy);
+
+ if (!runnable->Dispatch()) {
+ NS_WARNING("Failed to dispatch SkipWaitingResultRunnable to the worker.");
+ }
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+already_AddRefed<Promise>
+ServiceWorkerGlobalScope::SkipWaiting(ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(mWorkerPrivate->IsServiceWorker());
+
+ RefPtr<Promise> promise = Promise::Create(this, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RefPtr<PromiseWorkerProxy> promiseProxy =
+ PromiseWorkerProxy::Create(mWorkerPrivate, promise);
+ if (!promiseProxy) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+
+ RefPtr<WorkerScopeSkipWaitingRunnable> runnable =
+ new WorkerScopeSkipWaitingRunnable(promiseProxy,
+ NS_ConvertUTF16toUTF8(mScope));
+
+ MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(runnable.forget()));
+ return promise.forget();
+}
+
+bool
+ServiceWorkerGlobalScope::OpenWindowEnabled(JSContext* aCx, JSObject* aObj)
+{
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ worker->AssertIsOnWorkerThread();
+ return worker->OpenWindowEnabled();
+}
+
+WorkerDebuggerGlobalScope::WorkerDebuggerGlobalScope(
+ WorkerPrivate* aWorkerPrivate)
+: mWorkerPrivate(aWorkerPrivate)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+}
+
+WorkerDebuggerGlobalScope::~WorkerDebuggerGlobalScope()
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(WorkerDebuggerGlobalScope)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WorkerDebuggerGlobalScope,
+ DOMEventTargetHelper)
+ tmp->mWorkerPrivate->AssertIsOnWorkerThread();
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WorkerDebuggerGlobalScope,
+ DOMEventTargetHelper)
+ tmp->mWorkerPrivate->AssertIsOnWorkerThread();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WorkerDebuggerGlobalScope,
+ DOMEventTargetHelper)
+ tmp->mWorkerPrivate->AssertIsOnWorkerThread();
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_ADDREF_INHERITED(WorkerDebuggerGlobalScope, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(WorkerDebuggerGlobalScope, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerDebuggerGlobalScope)
+ NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+bool
+WorkerDebuggerGlobalScope::WrapGlobalObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aReflector)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ JS::CompartmentOptions options;
+ mWorkerPrivate->CopyJSCompartmentOptions(options);
+
+ return WorkerDebuggerGlobalScopeBinding::Wrap(aCx, this, this, options,
+ GetWorkerPrincipal(), true,
+ aReflector);
+}
+
+void
+WorkerDebuggerGlobalScope::GetGlobal(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aGlobal,
+ ErrorResult& aRv)
+{
+ WorkerGlobalScope* scope = mWorkerPrivate->GetOrCreateGlobalScope(aCx);
+ if (!scope) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+
+ aGlobal.set(scope->GetWrapper());
+}
+
+void
+WorkerDebuggerGlobalScope::CreateSandbox(JSContext* aCx, const nsAString& aName,
+ JS::Handle<JSObject*> aPrototype,
+ JS::MutableHandle<JSObject*> aResult,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ aResult.set(nullptr);
+
+ JS::Rooted<JS::Value> protoVal(aCx);
+ protoVal.setObjectOrNull(aPrototype);
+ JS::Rooted<JSObject*> sandbox(aCx,
+ SimpleGlobalObject::Create(SimpleGlobalObject::GlobalType::WorkerDebuggerSandbox,
+ protoVal));
+
+ if (!sandbox) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ if (!JS_WrapObject(aCx, &sandbox)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ aResult.set(sandbox);
+}
+
+void
+WorkerDebuggerGlobalScope::LoadSubScript(JSContext* aCx,
+ const nsAString& aURL,
+ const Optional<JS::Handle<JSObject*>>& aSandbox,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ Maybe<JSAutoCompartment> ac;
+ if (aSandbox.WasPassed()) {
+ JS::Rooted<JSObject*> sandbox(aCx, js::CheckedUnwrap(aSandbox.Value()));
+ if (!IsDebuggerSandbox(sandbox)) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ ac.emplace(aCx, sandbox);
+ }
+
+ nsTArray<nsString> urls;
+ urls.AppendElement(aURL);
+ scriptloader::Load(mWorkerPrivate, urls, DebuggerScript, aRv);
+}
+
+void
+WorkerDebuggerGlobalScope::EnterEventLoop()
+{
+ mWorkerPrivate->EnterDebuggerEventLoop();
+}
+
+void
+WorkerDebuggerGlobalScope::LeaveEventLoop()
+{
+ mWorkerPrivate->LeaveDebuggerEventLoop();
+}
+
+void
+WorkerDebuggerGlobalScope::PostMessage(const nsAString& aMessage)
+{
+ mWorkerPrivate->PostMessageToDebugger(aMessage);
+}
+
+void
+WorkerDebuggerGlobalScope::SetImmediate(Function& aHandler, ErrorResult& aRv)
+{
+ mWorkerPrivate->SetDebuggerImmediate(aHandler, aRv);
+}
+
+void
+WorkerDebuggerGlobalScope::ReportError(JSContext* aCx,
+ const nsAString& aMessage)
+{
+ JS::AutoFilename chars;
+ uint32_t lineno = 0;
+ JS::DescribeScriptedCaller(aCx, &chars, &lineno);
+ nsString filename(NS_ConvertUTF8toUTF16(chars.get()));
+ mWorkerPrivate->ReportErrorToDebugger(filename, lineno, aMessage);
+}
+
+void
+WorkerDebuggerGlobalScope::RetrieveConsoleEvents(JSContext* aCx,
+ nsTArray<JS::Value>& aEvents,
+ ErrorResult& aRv)
+{
+ WorkerGlobalScope* scope = mWorkerPrivate->GetOrCreateGlobalScope(aCx);
+ if (!scope) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<Console> console = scope->GetConsole(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ console->RetrieveConsoleEvents(aCx, aEvents, aRv);
+}
+
+void
+WorkerDebuggerGlobalScope::SetConsoleEventHandler(JSContext* aCx,
+ AnyCallback* aHandler,
+ ErrorResult& aRv)
+{
+ WorkerGlobalScope* scope = mWorkerPrivate->GetOrCreateGlobalScope(aCx);
+ if (!scope) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<Console> console = scope->GetConsole(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ console->SetConsoleEventHandler(aHandler);
+}
+
+Console*
+WorkerDebuggerGlobalScope::GetConsole(ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ // Debugger console has its own console object.
+ if (!mConsole) {
+ mConsole = Console::Create(nullptr, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ return mConsole;
+}
+
+void
+WorkerDebuggerGlobalScope::Dump(JSContext* aCx,
+ const Optional<nsAString>& aString) const
+{
+ WorkerGlobalScope* scope = mWorkerPrivate->GetOrCreateGlobalScope(aCx);
+ if (scope) {
+ scope->Dump(aString);
+ }
+}
+
+BEGIN_WORKERS_NAMESPACE
+
+bool
+IsWorkerGlobal(JSObject* object)
+{
+ return IS_INSTANCE_OF(WorkerGlobalScope, object);
+}
+
+bool
+IsDebuggerGlobal(JSObject* object)
+{
+ return IS_INSTANCE_OF(WorkerDebuggerGlobalScope, object);
+}
+
+bool
+IsDebuggerSandbox(JSObject* object)
+{
+ return SimpleGlobalObject::SimpleGlobalType(object) ==
+ SimpleGlobalObject::GlobalType::WorkerDebuggerSandbox;
+}
+
+bool
+GetterOnlyJSNative(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+{
+ JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, JSMSG_GETTER_ONLY);
+ return false;
+}
+
+END_WORKERS_NAMESPACE
diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h
new file mode 100644
index 000000000..53d0a578e
--- /dev/null
+++ b/dom/workers/WorkerScope.h
@@ -0,0 +1,389 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workerscope_h__
+#define mozilla_dom_workerscope_h__
+
+#include "Workers.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/Headers.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "nsWeakReference.h"
+#include "mozilla/dom/ImageBitmapSource.h"
+
+namespace mozilla {
+namespace dom {
+
+class AnyCallback;
+struct ChannelPixelLayout;
+class Console;
+class Crypto;
+class Function;
+class IDBFactory;
+enum class ImageBitmapFormat : uint32_t;
+class Performance;
+class Promise;
+class RequestOrUSVString;
+class ServiceWorkerRegistration;
+class WorkerLocation;
+class WorkerNavigator;
+
+namespace cache {
+
+class CacheStorage;
+
+} // namespace cache
+
+namespace workers {
+
+class ServiceWorkerClients;
+class WorkerPrivate;
+
+} // namespace workers
+
+class WorkerGlobalScope : public DOMEventTargetHelper,
+ public nsIGlobalObject,
+ public nsSupportsWeakReference
+{
+ typedef mozilla::dom::IDBFactory IDBFactory;
+
+ RefPtr<Console> mConsole;
+ RefPtr<Crypto> mCrypto;
+ RefPtr<WorkerLocation> mLocation;
+ RefPtr<WorkerNavigator> mNavigator;
+ RefPtr<Performance> mPerformance;
+ RefPtr<IDBFactory> mIndexedDB;
+ RefPtr<cache::CacheStorage> mCacheStorage;
+
+ uint32_t mWindowInteractionsAllowed;
+
+protected:
+ typedef mozilla::dom::workers::WorkerPrivate WorkerPrivate;
+ WorkerPrivate* mWorkerPrivate;
+
+ explicit WorkerGlobalScope(WorkerPrivate* aWorkerPrivate);
+ virtual ~WorkerGlobalScope();
+
+public:
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual bool
+ WrapGlobalObject(JSContext* aCx, JS::MutableHandle<JSObject*> aReflector) = 0;
+
+ virtual JSObject*
+ GetGlobalJSObject(void) override
+ {
+ return GetWrapper();
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(WorkerGlobalScope,
+ DOMEventTargetHelper)
+
+ WorkerGlobalScope*
+ Self()
+ {
+ return this;
+ }
+
+ Console*
+ GetConsole(ErrorResult& aRv);
+
+ Console*
+ GetConsoleIfExists() const
+ {
+ return mConsole;
+ }
+
+ Crypto*
+ GetCrypto(ErrorResult& aError);
+
+ already_AddRefed<WorkerLocation>
+ Location();
+
+ already_AddRefed<WorkerNavigator>
+ Navigator();
+
+ already_AddRefed<WorkerNavigator>
+ GetExistingNavigator() const;
+
+ void
+ Close(JSContext* aCx, ErrorResult& aRv);
+
+ OnErrorEventHandlerNonNull*
+ GetOnerror();
+ void
+ SetOnerror(OnErrorEventHandlerNonNull* aHandler);
+
+ void
+ ImportScripts(const Sequence<nsString>& aScriptURLs, ErrorResult& aRv);
+
+ int32_t
+ SetTimeout(JSContext* aCx, Function& aHandler, const int32_t aTimeout,
+ const Sequence<JS::Value>& aArguments, ErrorResult& aRv);
+ int32_t
+ SetTimeout(JSContext* aCx, const nsAString& aHandler, const int32_t aTimeout,
+ const Sequence<JS::Value>& /* unused */, ErrorResult& aRv);
+ void
+ ClearTimeout(int32_t aHandle);
+ int32_t
+ SetInterval(JSContext* aCx, Function& aHandler,
+ const Optional<int32_t>& aTimeout,
+ const Sequence<JS::Value>& aArguments, ErrorResult& aRv);
+ int32_t
+ SetInterval(JSContext* aCx, const nsAString& aHandler,
+ const Optional<int32_t>& aTimeout,
+ const Sequence<JS::Value>& /* unused */, ErrorResult& aRv);
+ void
+ ClearInterval(int32_t aHandle);
+
+ void
+ Atob(const nsAString& aAtob, nsAString& aOutput, ErrorResult& aRv) const;
+ void
+ Btoa(const nsAString& aBtoa, nsAString& aOutput, ErrorResult& aRv) const;
+
+ IMPL_EVENT_HANDLER(online)
+ IMPL_EVENT_HANDLER(offline)
+
+ void
+ Dump(const Optional<nsAString>& aString) const;
+
+ Performance* GetPerformance();
+
+ already_AddRefed<Promise>
+ Fetch(const RequestOrUSVString& aInput, const RequestInit& aInit, ErrorResult& aRv);
+
+ already_AddRefed<IDBFactory>
+ GetIndexedDB(ErrorResult& aErrorResult);
+
+ already_AddRefed<cache::CacheStorage>
+ GetCaches(ErrorResult& aRv);
+
+ bool IsSecureContext() const;
+
+ already_AddRefed<Promise>
+ CreateImageBitmap(const ImageBitmapSource& aImage, ErrorResult& aRv);
+
+ already_AddRefed<Promise>
+ CreateImageBitmap(const ImageBitmapSource& aImage,
+ int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh,
+ ErrorResult& aRv);
+
+ already_AddRefed<mozilla::dom::Promise>
+ CreateImageBitmap(const ImageBitmapSource& aImage,
+ int32_t aOffset, int32_t aLength,
+ mozilla::dom::ImageBitmapFormat aFormat,
+ const mozilla::dom::Sequence<mozilla::dom::ChannelPixelLayout>& aLayout,
+ mozilla::ErrorResult& aRv);
+
+ bool
+ WindowInteractionAllowed() const
+ {
+ return mWindowInteractionsAllowed > 0;
+ }
+
+ void
+ AllowWindowInteraction()
+ {
+ mWindowInteractionsAllowed++;
+ }
+
+ void
+ ConsumeWindowInteraction()
+ {
+ MOZ_ASSERT(mWindowInteractionsAllowed > 0);
+ mWindowInteractionsAllowed--;
+ }
+};
+
+class DedicatedWorkerGlobalScope final : public WorkerGlobalScope
+{
+ ~DedicatedWorkerGlobalScope() { }
+
+public:
+ explicit DedicatedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate);
+
+ virtual bool
+ WrapGlobalObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aReflector) override;
+
+ void
+ PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv);
+
+ IMPL_EVENT_HANDLER(message)
+};
+
+class SharedWorkerGlobalScope final : public WorkerGlobalScope
+{
+ const nsCString mName;
+
+ ~SharedWorkerGlobalScope() { }
+
+public:
+ SharedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate,
+ const nsCString& aName);
+
+ virtual bool
+ WrapGlobalObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aReflector) override;
+
+ void GetName(DOMString& aName) const
+ {
+ aName.AsAString() = NS_ConvertUTF8toUTF16(mName);
+ }
+
+ IMPL_EVENT_HANDLER(connect)
+};
+
+class ServiceWorkerGlobalScope final : public WorkerGlobalScope
+{
+ const nsString mScope;
+ RefPtr<workers::ServiceWorkerClients> mClients;
+ RefPtr<ServiceWorkerRegistration> mRegistration;
+
+ ~ServiceWorkerGlobalScope();
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerGlobalScope,
+ WorkerGlobalScope)
+ IMPL_EVENT_HANDLER(notificationclick)
+ IMPL_EVENT_HANDLER(notificationclose)
+
+ ServiceWorkerGlobalScope(WorkerPrivate* aWorkerPrivate, const nsACString& aScope);
+
+ virtual bool
+ WrapGlobalObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aReflector) override;
+
+ static bool
+ OpenWindowEnabled(JSContext* aCx, JSObject* aObj);
+
+ void
+ GetScope(nsString& aScope) const
+ {
+ aScope = mScope;
+ }
+
+ workers::ServiceWorkerClients*
+ Clients();
+
+ ServiceWorkerRegistration*
+ Registration();
+
+ already_AddRefed<Promise>
+ SkipWaiting(ErrorResult& aRv);
+
+ IMPL_EVENT_HANDLER(activate)
+ IMPL_EVENT_HANDLER(fetch)
+ IMPL_EVENT_HANDLER(install)
+ IMPL_EVENT_HANDLER(message)
+
+ IMPL_EVENT_HANDLER(push)
+ IMPL_EVENT_HANDLER(pushsubscriptionchange)
+
+};
+
+class WorkerDebuggerGlobalScope final : public DOMEventTargetHelper,
+ public nsIGlobalObject
+{
+ typedef mozilla::dom::workers::WorkerPrivate WorkerPrivate;
+
+ WorkerPrivate* mWorkerPrivate;
+ RefPtr<Console> mConsole;
+
+public:
+ explicit WorkerDebuggerGlobalScope(WorkerPrivate* aWorkerPrivate);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(WorkerDebuggerGlobalScope,
+ DOMEventTargetHelper)
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ MOZ_CRASH("Shouldn't get here!");
+ }
+
+ virtual bool
+ WrapGlobalObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aReflector);
+
+ virtual JSObject*
+ GetGlobalJSObject(void) override
+ {
+ return GetWrapper();
+ }
+
+ void
+ GetGlobal(JSContext* aCx, JS::MutableHandle<JSObject*> aGlobal,
+ ErrorResult& aRv);
+
+ void
+ CreateSandbox(JSContext* aCx, const nsAString& aName,
+ JS::Handle<JSObject*> aPrototype,
+ JS::MutableHandle<JSObject*> aResult,
+ ErrorResult& aRv);
+
+ void
+ LoadSubScript(JSContext* aCx, const nsAString& aURL,
+ const Optional<JS::Handle<JSObject*>>& aSandbox,
+ ErrorResult& aRv);
+
+ void
+ EnterEventLoop();
+
+ void
+ LeaveEventLoop();
+
+ void
+ PostMessage(const nsAString& aMessage);
+
+ IMPL_EVENT_HANDLER(message)
+
+ void
+ SetImmediate(Function& aHandler, ErrorResult& aRv);
+
+ void
+ ReportError(JSContext* aCx, const nsAString& aMessage);
+
+ void
+ RetrieveConsoleEvents(JSContext* aCx, nsTArray<JS::Value>& aEvents,
+ ErrorResult& aRv);
+
+ void
+ SetConsoleEventHandler(JSContext* aCx, AnyCallback* aHandler,
+ ErrorResult& aRv);
+
+ Console*
+ GetConsole(ErrorResult& aRv);
+
+ Console*
+ GetConsoleIfExists() const
+ {
+ return mConsole;
+ }
+
+ void
+ Dump(JSContext* aCx, const Optional<nsAString>& aString) const;
+
+private:
+ virtual ~WorkerDebuggerGlobalScope();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+inline nsISupports*
+ToSupports(mozilla::dom::WorkerGlobalScope* aScope)
+{
+ return static_cast<nsIDOMEventTarget*>(aScope);
+}
+
+#endif /* mozilla_dom_workerscope_h__ */
diff --git a/dom/workers/WorkerThread.cpp b/dom/workers/WorkerThread.cpp
new file mode 100644
index 000000000..7a7cb7ac3
--- /dev/null
+++ b/dom/workers/WorkerThread.cpp
@@ -0,0 +1,355 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WorkerThread.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "nsIThreadInternal.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+
+#ifdef DEBUG
+#include "nsThreadManager.h"
+#endif
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+using namespace mozilla::ipc;
+
+namespace {
+
+// The C stack size. We use the same stack size on all platforms for
+// consistency.
+const uint32_t kWorkerStackSize = 256 * sizeof(size_t) * 1024;
+
+} // namespace
+
+WorkerThreadFriendKey::WorkerThreadFriendKey()
+{
+ MOZ_COUNT_CTOR(WorkerThreadFriendKey);
+}
+
+WorkerThreadFriendKey::~WorkerThreadFriendKey()
+{
+ MOZ_COUNT_DTOR(WorkerThreadFriendKey);
+}
+
+class WorkerThread::Observer final
+ : public nsIThreadObserver
+{
+ WorkerPrivate* mWorkerPrivate;
+
+public:
+ explicit Observer(WorkerPrivate* aWorkerPrivate)
+ : mWorkerPrivate(aWorkerPrivate)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+private:
+ ~Observer()
+ {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ NS_DECL_NSITHREADOBSERVER
+};
+
+WorkerThread::WorkerThread()
+ : nsThread(nsThread::NOT_MAIN_THREAD, kWorkerStackSize)
+ , mWorkerPrivateCondVar(mLock, "WorkerThread::mWorkerPrivateCondVar")
+ , mWorkerPrivate(nullptr)
+ , mOtherThreadsDispatchingViaEventTarget(0)
+#ifdef DEBUG
+ , mAcceptingNonWorkerRunnables(true)
+#endif
+{
+}
+
+WorkerThread::~WorkerThread()
+{
+ MOZ_ASSERT(!mWorkerPrivate);
+ MOZ_ASSERT(!mOtherThreadsDispatchingViaEventTarget);
+ MOZ_ASSERT(mAcceptingNonWorkerRunnables);
+}
+
+// static
+already_AddRefed<WorkerThread>
+WorkerThread::Create(const WorkerThreadFriendKey& /* aKey */)
+{
+ RefPtr<WorkerThread> thread = new WorkerThread();
+ if (NS_FAILED(thread->Init())) {
+ NS_WARNING("Failed to create new thread!");
+ return nullptr;
+ }
+
+ return thread.forget();
+}
+
+void
+WorkerThread::SetWorker(const WorkerThreadFriendKey& /* aKey */,
+ WorkerPrivate* aWorkerPrivate)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == mThread);
+
+ if (aWorkerPrivate) {
+ {
+ MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(!mWorkerPrivate);
+ MOZ_ASSERT(mAcceptingNonWorkerRunnables);
+
+ mWorkerPrivate = aWorkerPrivate;
+#ifdef DEBUG
+ mAcceptingNonWorkerRunnables = false;
+#endif
+ }
+
+ mObserver = new Observer(aWorkerPrivate);
+ MOZ_ALWAYS_SUCCEEDS(AddObserver(mObserver));
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(RemoveObserver(mObserver));
+ mObserver = nullptr;
+
+ {
+ MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(mWorkerPrivate);
+ MOZ_ASSERT(!mAcceptingNonWorkerRunnables);
+ MOZ_ASSERT(!mOtherThreadsDispatchingViaEventTarget,
+ "XPCOM Dispatch hapenning at the same time our thread is "
+ "being unset! This should not be possible!");
+
+ while (mOtherThreadsDispatchingViaEventTarget) {
+ mWorkerPrivateCondVar.Wait();
+ }
+
+#ifdef DEBUG
+ mAcceptingNonWorkerRunnables = true;
+#endif
+ mWorkerPrivate = nullptr;
+ }
+ }
+}
+
+nsresult
+WorkerThread::DispatchPrimaryRunnable(const WorkerThreadFriendKey& /* aKey */,
+ already_AddRefed<nsIRunnable> aRunnable)
+{
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+
+#ifdef DEBUG
+ MOZ_ASSERT(PR_GetCurrentThread() != mThread);
+ MOZ_ASSERT(runnable);
+ {
+ MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(!mWorkerPrivate);
+ MOZ_ASSERT(mAcceptingNonWorkerRunnables);
+ }
+#endif
+
+ nsresult rv = nsThread::Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+WorkerThread::DispatchAnyThread(const WorkerThreadFriendKey& /* aKey */,
+ already_AddRefed<WorkerRunnable> aWorkerRunnable)
+{
+ // May be called on any thread!
+
+#ifdef DEBUG
+ {
+ const bool onWorkerThread = PR_GetCurrentThread() == mThread;
+ {
+ MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(mWorkerPrivate);
+ MOZ_ASSERT(!mAcceptingNonWorkerRunnables);
+
+ if (onWorkerThread) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+ }
+ }
+#endif
+ nsCOMPtr<nsIRunnable> runnable(aWorkerRunnable);
+
+ nsresult rv = nsThread::Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // We don't need to notify the worker's condition variable here because we're
+ // being called from worker-controlled code and it will make sure to wake up
+ // the worker thread if needed.
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(WorkerThread, nsThread)
+
+NS_IMETHODIMP
+WorkerThread::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags)
+{
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+ return Dispatch(runnable.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+WorkerThread::Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags)
+{
+ // May be called on any thread!
+ nsCOMPtr<nsIRunnable> runnable(aRunnable); // in case we exit early
+
+ // Workers only support asynchronous dispatch.
+ if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ const bool onWorkerThread = PR_GetCurrentThread() == mThread;
+
+#ifdef DEBUG
+ if (runnable && !onWorkerThread) {
+ nsCOMPtr<nsICancelableRunnable> cancelable = do_QueryInterface(runnable);
+
+ {
+ MutexAutoLock lock(mLock);
+
+ // Only enforce cancelable runnables after we've started the worker loop.
+ if (!mAcceptingNonWorkerRunnables) {
+ MOZ_ASSERT(cancelable,
+ "Only nsICancelableRunnable may be dispatched to a worker!");
+ }
+ }
+ }
+#endif
+
+ WorkerPrivate* workerPrivate = nullptr;
+ if (onWorkerThread) {
+ // No need to lock here because it is only modified on this thread.
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ workerPrivate = mWorkerPrivate;
+ } else {
+ MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(mOtherThreadsDispatchingViaEventTarget < UINT32_MAX);
+
+ if (mWorkerPrivate) {
+ workerPrivate = mWorkerPrivate;
+
+ // Incrementing this counter will make the worker thread sleep if it
+ // somehow tries to unset mWorkerPrivate while we're using it.
+ mOtherThreadsDispatchingViaEventTarget++;
+ }
+ }
+
+ nsresult rv;
+ if (runnable && onWorkerThread) {
+ RefPtr<WorkerRunnable> workerRunnable = workerPrivate->MaybeWrapAsWorkerRunnable(runnable.forget());
+ rv = nsThread::Dispatch(workerRunnable.forget(), NS_DISPATCH_NORMAL);
+ } else {
+ rv = nsThread::Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ }
+
+ if (!onWorkerThread && workerPrivate) {
+ // We need to wake the worker thread if we're not already on the right
+ // thread and the dispatch succeeded.
+ if (NS_SUCCEEDED(rv)) {
+ MutexAutoLock workerLock(workerPrivate->mMutex);
+
+ workerPrivate->mCondVar.Notify();
+ }
+
+ // Now unset our waiting flag.
+ {
+ MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(mOtherThreadsDispatchingViaEventTarget);
+
+ if (!--mOtherThreadsDispatchingViaEventTarget) {
+ mWorkerPrivateCondVar.Notify();
+ }
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerThread::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t
+WorkerThread::RecursionDepth(const WorkerThreadFriendKey& /* aKey */) const
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == mThread);
+
+ return mNestedEventLoopDepth;
+}
+
+NS_IMPL_ISUPPORTS(WorkerThread::Observer, nsIThreadObserver)
+
+NS_IMETHODIMP
+WorkerThread::Observer::OnDispatchedEvent(nsIThreadInternal* /* aThread */)
+{
+ MOZ_CRASH("OnDispatchedEvent() should never be called!");
+}
+
+NS_IMETHODIMP
+WorkerThread::Observer::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
+ bool aMayWait)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ // If the PBackground child is not created yet, then we must permit
+ // blocking event processing to support
+ // BackgroundChild::SynchronouslyCreateForCurrentThread(). If this occurs
+ // then we are spinning on the event queue at the start of
+ // PrimaryWorkerRunnable::Run() and don't want to process the event in
+ // mWorkerPrivate yet.
+ if (aMayWait) {
+ MOZ_ASSERT(CycleCollectedJSContext::Get()->RecursionDepth() == 2);
+ MOZ_ASSERT(!BackgroundChild::GetForCurrentThread());
+ return NS_OK;
+ }
+
+ mWorkerPrivate->OnProcessNextEvent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WorkerThread::Observer::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
+ bool /* aEventWasProcessed */)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ mWorkerPrivate->AfterProcessNextEvent();
+ return NS_OK;
+}
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/WorkerThread.h b/dom/workers/WorkerThread.h
new file mode 100644
index 000000000..f1287023d
--- /dev/null
+++ b/dom/workers/WorkerThread.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_WorkerThread_h__
+#define mozilla_dom_workers_WorkerThread_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/DebugOnly.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/RefPtr.h"
+#include "nsThread.h"
+
+class nsIRunnable;
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+class RuntimeService;
+class WorkerPrivate;
+template <class> class WorkerPrivateParent;
+class WorkerRunnable;
+
+// This class lets us restrict the public methods that can be called on
+// WorkerThread to RuntimeService and WorkerPrivate without letting them gain
+// full access to private methods (as would happen if they were simply friends).
+class WorkerThreadFriendKey
+{
+ friend class RuntimeService;
+ friend class WorkerPrivate;
+ friend class WorkerPrivateParent<WorkerPrivate>;
+
+ WorkerThreadFriendKey();
+ ~WorkerThreadFriendKey();
+};
+
+class WorkerThread final
+ : public nsThread
+{
+ class Observer;
+
+ CondVar mWorkerPrivateCondVar;
+
+ // Protected by nsThread::mLock.
+ WorkerPrivate* mWorkerPrivate;
+
+ // Only touched on the target thread.
+ RefPtr<Observer> mObserver;
+
+ // Protected by nsThread::mLock and waited on with mWorkerPrivateCondVar.
+ uint32_t mOtherThreadsDispatchingViaEventTarget;
+
+#ifdef DEBUG
+ // Protected by nsThread::mLock.
+ bool mAcceptingNonWorkerRunnables;
+#endif
+
+public:
+ static already_AddRefed<WorkerThread>
+ Create(const WorkerThreadFriendKey& aKey);
+
+ void
+ SetWorker(const WorkerThreadFriendKey& aKey, WorkerPrivate* aWorkerPrivate);
+
+ nsresult
+ DispatchPrimaryRunnable(const WorkerThreadFriendKey& aKey,
+ already_AddRefed<nsIRunnable> aRunnable);
+
+ nsresult
+ DispatchAnyThread(const WorkerThreadFriendKey& aKey,
+ already_AddRefed<WorkerRunnable> aWorkerRunnable);
+
+ uint32_t
+ RecursionDepth(const WorkerThreadFriendKey& aKey) const;
+
+ // Required for MinGW build #1336527 to handle compiler bug:
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79582
+ NS_IMETHOD
+ RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod> aIdlePeriod) override
+ {
+ return nsThread::RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod>(aIdlePeriod.take()));
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+private:
+ WorkerThread();
+ ~WorkerThread();
+
+ // This should only be called by consumers that have an
+ // nsIEventTarget/nsIThread pointer.
+ NS_IMETHOD
+ Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) override;
+
+ NS_IMETHOD
+ DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) override;
+
+ NS_IMETHOD
+ DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) override;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_WorkerThread_h__
diff --git a/dom/workers/Workers.h b/dom/workers/Workers.h
new file mode 100644
index 000000000..89e2ccfca
--- /dev/null
+++ b/dom/workers/Workers.h
@@ -0,0 +1,383 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_workers_h__
+#define mozilla_dom_workers_workers_h__
+
+#include "jsapi.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+#include <stdint.h>
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include "nsILoadContext.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "mozilla/dom/ChannelInfo.h"
+#include "mozilla/net/ReferrerPolicy.h"
+
+#define BEGIN_WORKERS_NAMESPACE \
+ namespace mozilla { namespace dom { namespace workers {
+#define END_WORKERS_NAMESPACE \
+ } /* namespace workers */ } /* namespace dom */ } /* namespace mozilla */
+#define USING_WORKERS_NAMESPACE \
+ using namespace mozilla::dom::workers;
+
+#define WORKERS_SHUTDOWN_TOPIC "web-workers-shutdown"
+
+class nsIContentSecurityPolicy;
+class nsIScriptContext;
+class nsIGlobalObject;
+class nsPIDOMWindowInner;
+class nsIPrincipal;
+class nsILoadGroup;
+class nsITabChild;
+class nsIChannel;
+class nsIRunnable;
+class nsIURI;
+
+namespace mozilla {
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+// If you change this, the corresponding list in nsIWorkerDebugger.idl needs to
+// be updated too.
+enum WorkerType
+{
+ WorkerTypeDedicated,
+ WorkerTypeShared,
+ WorkerTypeService
+};
+
+} // namespace dom
+} // namespace mozilla
+
+BEGIN_WORKERS_NAMESPACE
+
+class WorkerPrivate;
+
+struct PrivatizableBase
+{ };
+
+#ifdef DEBUG
+void
+AssertIsOnMainThread();
+#else
+inline void
+AssertIsOnMainThread()
+{ }
+#endif
+
+struct JSSettings
+{
+ enum {
+ // All the GC parameters that we support.
+ JSSettings_JSGC_MAX_BYTES = 0,
+ JSSettings_JSGC_MAX_MALLOC_BYTES,
+ JSSettings_JSGC_HIGH_FREQUENCY_TIME_LIMIT,
+ JSSettings_JSGC_LOW_FREQUENCY_HEAP_GROWTH,
+ JSSettings_JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN,
+ JSSettings_JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX,
+ JSSettings_JSGC_HIGH_FREQUENCY_LOW_LIMIT,
+ JSSettings_JSGC_HIGH_FREQUENCY_HIGH_LIMIT,
+ JSSettings_JSGC_ALLOCATION_THRESHOLD,
+ JSSettings_JSGC_SLICE_TIME_BUDGET,
+ JSSettings_JSGC_DYNAMIC_HEAP_GROWTH,
+ JSSettings_JSGC_DYNAMIC_MARK_SLICE,
+ JSSettings_JSGC_REFRESH_FRAME_SLICES,
+ // JSGC_MODE not supported
+
+ // This must be last so that we get an accurate count.
+ kGCSettingsArraySize
+ };
+
+ struct JSGCSetting
+ {
+ JSGCParamKey key;
+ uint32_t value;
+
+ JSGCSetting()
+ : key(static_cast<JSGCParamKey>(-1)), value(0)
+ { }
+
+ bool
+ IsSet() const
+ {
+ return key != static_cast<JSGCParamKey>(-1);
+ }
+
+ void
+ Unset()
+ {
+ key = static_cast<JSGCParamKey>(-1);
+ value = 0;
+ }
+ };
+
+ // There are several settings that we know we need so it makes sense to
+ // preallocate here.
+ typedef JSGCSetting JSGCSettingsArray[kGCSettingsArraySize];
+
+ // Settings that change based on chrome/content context.
+ struct JSContentChromeSettings
+ {
+ JS::CompartmentOptions compartmentOptions;
+ int32_t maxScriptRuntime;
+
+ JSContentChromeSettings()
+ : compartmentOptions(), maxScriptRuntime(0)
+ { }
+ };
+
+ JSContentChromeSettings chrome;
+ JSContentChromeSettings content;
+ JSGCSettingsArray gcSettings;
+ JS::ContextOptions contextOptions;
+
+#ifdef JS_GC_ZEAL
+ uint8_t gcZeal;
+ uint32_t gcZealFrequency;
+#endif
+
+ JSSettings()
+#ifdef JS_GC_ZEAL
+ : gcZeal(0), gcZealFrequency(0)
+#endif
+ {
+ for (uint32_t index = 0; index < ArrayLength(gcSettings); index++) {
+ new (gcSettings + index) JSGCSetting();
+ }
+ }
+
+ bool
+ ApplyGCSetting(JSGCParamKey aKey, uint32_t aValue)
+ {
+ JSSettings::JSGCSetting* firstEmptySetting = nullptr;
+ JSSettings::JSGCSetting* foundSetting = nullptr;
+
+ for (uint32_t index = 0; index < ArrayLength(gcSettings); index++) {
+ JSSettings::JSGCSetting& setting = gcSettings[index];
+ if (setting.key == aKey) {
+ foundSetting = &setting;
+ break;
+ }
+ if (!firstEmptySetting && !setting.IsSet()) {
+ firstEmptySetting = &setting;
+ }
+ }
+
+ if (aValue) {
+ if (!foundSetting) {
+ foundSetting = firstEmptySetting;
+ if (!foundSetting) {
+ NS_ERROR("Not enough space for this value!");
+ return false;
+ }
+ }
+ foundSetting->key = aKey;
+ foundSetting->value = aValue;
+ return true;
+ }
+
+ if (foundSetting) {
+ foundSetting->Unset();
+ return true;
+ }
+
+ return false;
+ }
+};
+
+enum WorkerPreference
+{
+#define WORKER_SIMPLE_PREF(name, getter, NAME) WORKERPREF_ ## NAME,
+#define WORKER_PREF(name, callback)
+#include "mozilla/dom/WorkerPrefs.h"
+#undef WORKER_SIMPLE_PREF
+#undef WORKER_PREF
+ WORKERPREF_COUNT
+};
+
+// Implemented in WorkerPrivate.cpp
+
+struct WorkerLoadInfo
+{
+ // All of these should be released in WorkerPrivateParent::ForgetMainThreadObjects.
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsCOMPtr<nsIURI> mResolvedScriptURI;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsIScriptContext> mScriptContext;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ nsCOMPtr<nsIContentSecurityPolicy> mCSP;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+
+ // mLoadFailedAsyncRunnable will execute on main thread if script loading
+ // fails during script loading. If script loading is never started due to
+ // a synchronous error, then the runnable is never executed. The runnable
+ // is guaranteed to be released on the main thread.
+ nsCOMPtr<nsIRunnable> mLoadFailedAsyncRunnable;
+
+ class InterfaceRequestor final : public nsIInterfaceRequestor
+ {
+ NS_DECL_ISUPPORTS
+
+ public:
+ InterfaceRequestor(nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup);
+ void MaybeAddTabChild(nsILoadGroup* aLoadGroup);
+ NS_IMETHOD GetInterface(const nsIID& aIID, void** aSink) override;
+
+ private:
+ ~InterfaceRequestor() { }
+
+ already_AddRefed<nsITabChild> GetAnyLiveTabChild();
+
+ nsCOMPtr<nsILoadContext> mLoadContext;
+ nsCOMPtr<nsIInterfaceRequestor> mOuterRequestor;
+
+ // Array of weak references to nsITabChild. We do not want to keep TabChild
+ // actors alive for long after their ActorDestroy() methods are called.
+ nsTArray<nsWeakPtr> mTabChildList;
+ };
+
+ // Only set if we have a custom overriden load group
+ RefPtr<InterfaceRequestor> mInterfaceRequestor;
+
+ nsAutoPtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
+ nsCString mDomain;
+
+ nsString mServiceWorkerCacheName;
+
+ ChannelInfo mChannelInfo;
+
+ uint64_t mWindowID;
+ uint64_t mServiceWorkerID;
+
+ net::ReferrerPolicy mReferrerPolicy;
+ bool mFromWindow;
+ bool mEvalAllowed;
+ bool mReportCSPViolations;
+ bool mXHRParamsAllowed;
+ bool mPrincipalIsSystem;
+ bool mStorageAllowed;
+ bool mServiceWorkersTestingInWindow;
+ PrincipalOriginAttributes mOriginAttributes;
+
+ WorkerLoadInfo();
+ ~WorkerLoadInfo();
+
+ void StealFrom(WorkerLoadInfo& aOther);
+};
+
+// All of these are implemented in RuntimeService.cpp
+
+void
+CancelWorkersForWindow(nsPIDOMWindowInner* aWindow);
+
+void
+FreezeWorkersForWindow(nsPIDOMWindowInner* aWindow);
+
+void
+ThawWorkersForWindow(nsPIDOMWindowInner* aWindow);
+
+void
+SuspendWorkersForWindow(nsPIDOMWindowInner* aWindow);
+
+void
+ResumeWorkersForWindow(nsPIDOMWindowInner* aWindow);
+
+// A class that can be used with WorkerCrossThreadDispatcher to run a
+// bit of C++ code on the worker thread.
+class WorkerTask
+{
+protected:
+ WorkerTask()
+ { }
+
+ virtual ~WorkerTask()
+ { }
+
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WorkerTask)
+
+ // The return value here has the same semantics as the return value
+ // of WorkerRunnable::WorkerRun.
+ virtual bool
+ RunTask(JSContext* aCx) = 0;
+};
+
+class WorkerCrossThreadDispatcher
+{
+ friend class WorkerPrivate;
+
+ // Must be acquired *before* the WorkerPrivate's mutex, when they're both
+ // held.
+ Mutex mMutex;
+ WorkerPrivate* mWorkerPrivate;
+
+private:
+ // Only created by WorkerPrivate.
+ explicit WorkerCrossThreadDispatcher(WorkerPrivate* aWorkerPrivate);
+
+ // Only called by WorkerPrivate.
+ void
+ Forget()
+ {
+ MutexAutoLock lock(mMutex);
+ mWorkerPrivate = nullptr;
+ }
+
+ ~WorkerCrossThreadDispatcher() {}
+
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WorkerCrossThreadDispatcher)
+
+ // Generically useful function for running a bit of C++ code on the worker
+ // thread.
+ bool
+ PostTask(WorkerTask* aTask);
+};
+
+WorkerCrossThreadDispatcher*
+GetWorkerCrossThreadDispatcher(JSContext* aCx, const JS::Value& aWorker);
+
+// Random unique constant to facilitate JSPrincipal debugging
+const uint32_t kJSPrincipalsDebugToken = 0x7e2df9d2;
+
+namespace exceptions {
+
+// Implemented in Exceptions.cpp
+void
+ThrowDOMExceptionForNSResult(JSContext* aCx, nsresult aNSResult);
+
+} // namespace exceptions
+
+bool
+IsWorkerGlobal(JSObject* global);
+
+bool
+IsDebuggerGlobal(JSObject* global);
+
+bool
+IsDebuggerSandbox(JSObject* object);
+
+// Throws the JSMSG_GETTER_ONLY exception. This shouldn't be used going
+// forward -- getter-only properties should just use JS_PSG for the setter
+// (implying no setter at all), which will not throw when set in non-strict
+// code but will in strict code. Old code should use this only for temporary
+// compatibility reasons.
+extern bool
+GetterOnlyJSNative(JSContext* aCx, unsigned aArgc, JS::Value* aVp);
+
+END_WORKERS_NAMESPACE
+
+#endif // mozilla_dom_workers_workers_h__
diff --git a/dom/workers/moz.build b/dom/workers/moz.build
new file mode 100644
index 000000000..4f4b52e4a
--- /dev/null
+++ b/dom/workers/moz.build
@@ -0,0 +1,131 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Public stuff.
+EXPORTS.mozilla.dom += [
+ 'FileReaderSync.h',
+ 'ServiceWorkerCommon.h',
+ 'ServiceWorkerContainer.h',
+ 'ServiceWorkerEvents.h',
+ 'ServiceWorkerRegistrar.h',
+ 'ServiceWorkerRegistration.h',
+ 'WorkerLocation.h',
+ 'WorkerNavigator.h',
+ 'WorkerPrefs.h',
+ 'WorkerPrivate.h',
+ 'WorkerRunnable.h',
+ 'WorkerScope.h',
+]
+
+EXPORTS.mozilla.dom.workers += [
+ 'RuntimeService.h',
+ 'ServiceWorkerInfo.h',
+ 'ServiceWorkerManager.h',
+ 'ServiceWorkerRegistrationInfo.h',
+ 'WorkerDebuggerManager.h',
+ 'Workers.h',
+]
+
+# Stuff needed for the bindings, not really public though.
+EXPORTS.mozilla.dom.workers.bindings += [
+ 'ServiceWorker.h',
+ 'ServiceWorkerClient.h',
+ 'ServiceWorkerClients.h',
+ 'ServiceWorkerWindowClient.h',
+ 'SharedWorker.h',
+ 'WorkerHolder.h',
+]
+
+XPIDL_MODULE = 'dom_workers'
+
+XPIDL_SOURCES += [
+ 'nsIWorkerDebugger.idl',
+ 'nsIWorkerDebuggerManager.idl',
+]
+
+UNIFIED_SOURCES += [
+ 'ChromeWorkerScope.cpp',
+ 'FileReaderSync.cpp',
+ 'Principal.cpp',
+ 'RegisterBindings.cpp',
+ 'RuntimeService.cpp',
+ 'ScriptLoader.cpp',
+ 'ServiceWorker.cpp',
+ 'ServiceWorkerClient.cpp',
+ 'ServiceWorkerClients.cpp',
+ 'ServiceWorkerContainer.cpp',
+ 'ServiceWorkerEvents.cpp',
+ 'ServiceWorkerInfo.cpp',
+ 'ServiceWorkerJob.cpp',
+ 'ServiceWorkerJobQueue.cpp',
+ 'ServiceWorkerManager.cpp',
+ 'ServiceWorkerManagerChild.cpp',
+ 'ServiceWorkerManagerParent.cpp',
+ 'ServiceWorkerManagerService.cpp',
+ 'ServiceWorkerPrivate.cpp',
+ 'ServiceWorkerRegisterJob.cpp',
+ 'ServiceWorkerRegistrar.cpp',
+ 'ServiceWorkerRegistration.cpp',
+ 'ServiceWorkerRegistrationInfo.cpp',
+ 'ServiceWorkerScriptCache.cpp',
+ 'ServiceWorkerUnregisterJob.cpp',
+ 'ServiceWorkerUpdateJob.cpp',
+ 'ServiceWorkerWindowClient.cpp',
+ 'SharedWorker.cpp',
+ 'WorkerDebuggerManager.cpp',
+ 'WorkerHolder.cpp',
+ 'WorkerLocation.cpp',
+ 'WorkerNavigator.cpp',
+ 'WorkerPrivate.cpp',
+ 'WorkerRunnable.cpp',
+ 'WorkerScope.cpp',
+ 'WorkerThread.cpp',
+]
+
+IPDL_SOURCES += [
+ 'PServiceWorkerManager.ipdl',
+ 'ServiceWorkerRegistrarTypes.ipdlh',
+]
+
+LOCAL_INCLUDES += [
+ '../base',
+ '../system',
+ '/dom/base',
+ '/xpcom/build',
+ '/xpcom/threads',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+TEST_DIRS += [
+ 'test/extensions/bootstrap',
+ 'test/extensions/traditional',
+]
+
+MOCHITEST_MANIFESTS += [
+ 'test/mochitest.ini',
+ 'test/serviceworkers/mochitest.ini',
+]
+
+MOCHITEST_CHROME_MANIFESTS += [
+ 'test/chrome.ini',
+ 'test/serviceworkers/chrome.ini'
+]
+
+BROWSER_CHROME_MANIFESTS += [
+ 'test/serviceworkers/browser.ini',
+]
+
+XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
+TEST_DIRS += ['test/gtest']
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/dom/workers/nsIWorkerDebugger.idl b/dom/workers/nsIWorkerDebugger.idl
new file mode 100644
index 000000000..f8144f3da
--- /dev/null
+++ b/dom/workers/nsIWorkerDebugger.idl
@@ -0,0 +1,50 @@
+#include "nsISupports.idl"
+
+interface mozIDOMWindow;
+interface nsIPrincipal;
+
+[scriptable, uuid(9cf3b48e-361d-486a-8917-55cf8d00bb41)]
+interface nsIWorkerDebuggerListener : nsISupports
+{
+ void onClose();
+
+ void onError(in DOMString filename, in unsigned long lineno,
+ in DOMString message);
+
+ void onMessage(in DOMString message);
+};
+
+[scriptable, builtinclass, uuid(22f93aa3-8a05-46be-87e0-fa93bf8a8eff)]
+interface nsIWorkerDebugger : nsISupports
+{
+ const unsigned long TYPE_DEDICATED = 0;
+ const unsigned long TYPE_SHARED = 1;
+ const unsigned long TYPE_SERVICE = 2;
+
+ readonly attribute bool isClosed;
+
+ readonly attribute bool isChrome;
+
+ readonly attribute bool isInitialized;
+
+ readonly attribute nsIWorkerDebugger parent;
+
+ readonly attribute unsigned long type;
+
+ readonly attribute DOMString url;
+
+ readonly attribute mozIDOMWindow window;
+
+ readonly attribute nsIPrincipal principal;
+
+ readonly attribute unsigned long serviceWorkerID;
+
+ void initialize(in DOMString url);
+
+ [binaryname(PostMessageMoz)]
+ void postMessage(in DOMString message);
+
+ void addListener(in nsIWorkerDebuggerListener listener);
+
+ void removeListener(in nsIWorkerDebuggerListener listener);
+};
diff --git a/dom/workers/nsIWorkerDebuggerManager.idl b/dom/workers/nsIWorkerDebuggerManager.idl
new file mode 100644
index 000000000..f7a0fb309
--- /dev/null
+++ b/dom/workers/nsIWorkerDebuggerManager.idl
@@ -0,0 +1,22 @@
+#include "nsISupports.idl"
+
+interface nsISimpleEnumerator;
+interface nsIWorkerDebugger;
+
+[scriptable, uuid(d2aa74ee-6b98-4d5d-8173-4e23422daf1e)]
+interface nsIWorkerDebuggerManagerListener : nsISupports
+{
+ void onRegister(in nsIWorkerDebugger debugger);
+
+ void onUnregister(in nsIWorkerDebugger debugger);
+};
+
+[scriptable, builtinclass, uuid(056d7918-dc86-452a-b4e6-86da3405f015)]
+interface nsIWorkerDebuggerManager : nsISupports
+{
+ nsISimpleEnumerator getWorkerDebuggerEnumerator();
+
+ void addListener(in nsIWorkerDebuggerManagerListener listener);
+
+ void removeListener(in nsIWorkerDebuggerManagerListener listener);
+};
diff --git a/dom/workers/test/404_server.sjs b/dom/workers/test/404_server.sjs
new file mode 100644
index 000000000..f5dff1abb
--- /dev/null
+++ b/dom/workers/test/404_server.sjs
@@ -0,0 +1,10 @@
+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 000000000..8cee6809e
--- /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 000000000..662bd3520
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.console_debugger.js
@@ -0,0 +1,41 @@
+"use strict"
+
+function ok(a, msg) {
+ postMessage(JSON.stringify({ type: 'status', what: !!a, msg: 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 000000000..2db43a3d7
--- /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 000000000..a85764bd9
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.initialize_childWorker.js
@@ -0,0 +1,6 @@
+"use strict";
+
+self.onmessage = function () {};
+
+debugger;
+postMessage("worker");
diff --git a/dom/workers/test/WorkerDebugger.initialize_debugger.js b/dom/workers/test/WorkerDebugger.initialize_debugger.js
new file mode 100644
index 000000000..f52e95b15
--- /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_worker.js b/dom/workers/test/WorkerDebugger.initialize_worker.js
new file mode 100644
index 000000000..5a24efd3a
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.initialize_worker.js
@@ -0,0 +1,9 @@
+"use strict";
+
+var worker = new Worker("WorkerDebugger.initialize_childWorker.js");
+worker.onmessage = function (event) {
+ postMessage("child:" + event.data);
+};
+
+debugger;
+postMessage("worker");
diff --git a/dom/workers/test/WorkerDebugger.postMessage_childWorker.js b/dom/workers/test/WorkerDebugger.postMessage_childWorker.js
new file mode 100644
index 000000000..8cee6809e
--- /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 000000000..4a231b7ab
--- /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 000000000..8ddf6cf86
--- /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 000000000..908c9f316
--- /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 000000000..f94d65062
--- /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 000000000..8cee6809e
--- /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 000000000..b76f45a4d
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js
@@ -0,0 +1,14 @@
+"use strict";
+
+function f() {
+ debugger;
+}
+
+self.onmessage = function (event) {
+ switch (event.data) {
+ case "ping":
+ debugger;
+ postMessage("pong");
+ break;
+ };
+};
diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js
new file mode 100644
index 000000000..dcd4dbecd
--- /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 000000000..c43738516
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js
@@ -0,0 +1,25 @@
+"use strict";
+
+function f() {
+ debugger;
+}
+
+var worker = new Worker("WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js");
+
+worker.onmessage = function (event) {
+ postMessage("child:" + event.data);
+};
+
+self.onmessage = function (event) {
+ var message = event.data;
+ if (message.indexOf(":") >= 0) {
+ worker.postMessage(message.split(":")[1]);
+ return;
+ }
+ switch (message) {
+ case "ping":
+ debugger;
+ postMessage("pong");
+ break;
+ };
+};
diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.reportError_childWorker.js b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_childWorker.js
new file mode 100644
index 000000000..823e7c477
--- /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 000000000..67ea08de5
--- /dev/null
+++ b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_debugger.js
@@ -0,0 +1,12 @@
+"use strict";
+
+this.onmessage = function (event) {
+ switch (event.data) {
+ case "report":
+ reportError("reported");
+ break;
+ case "throw":
+ throw new Error("thrown");
+ break;
+ }
+};
diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.reportError_worker.js b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_worker.js
new file mode 100644
index 000000000..67ccfc2ca
--- /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 000000000..b5075c70f
--- /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 000000000..5a72b0f24
--- /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 000000000..8cee6809e
--- /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 000000000..0737d17eb
--- /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 000000000..8cee6809e
--- /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_iframe1.html b/dom/workers/test/WorkerDebugger_frozen_iframe1.html
new file mode 100644
index 000000000..591923121
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_frozen_iframe1.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 () {
+ parent.postMessage("ready", "*");
+ };
+ </script>
+ </head>
+ <body>
+ This is page 1.
+ </body>
+<html>
diff --git a/dom/workers/test/WorkerDebugger_frozen_iframe2.html b/dom/workers/test/WorkerDebugger_frozen_iframe2.html
new file mode 100644
index 000000000..96d5c56eb
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_frozen_iframe2.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 () {
+ parent.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 000000000..371d2c064
--- /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 000000000..371d2c064
--- /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 000000000..7d7eaf532
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_promise_debugger.js
@@ -0,0 +1,30 @@
+"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 (event) {
+ if (event.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();
+ });
+ };
+ 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 000000000..a77737af5
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_promise_worker.js
@@ -0,0 +1,25 @@
+"use strict";
+
+self.onmessage = function (event) {
+ if (event.data !== "resolve") {
+ return;
+ }
+ // This then-handler should be executed inside the top-level event loop,
+ // within the context of the worker's global.
+ Promise.resolve().then(function () {
+ self.onmessage = function (event) {
+ if (event.data !== "pause") {
+ return;
+ }
+ // This then-handler should be executed inside the top-level event loop,
+ // within the context of the worker's global. Because the debugger
+ // statement here below enters a nested event loop, the then-handler
+ // should not be executed until the debugger statement returns.
+ Promise.resolve().then(function () {
+ postMessage("resumed");
+ });
+ debugger;
+ }
+ postMessage("resolved");
+ });
+};
diff --git a/dom/workers/test/WorkerDebugger_sharedWorker.js b/dom/workers/test/WorkerDebugger_sharedWorker.js
new file mode 100644
index 000000000..5ad97d4c5
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_sharedWorker.js
@@ -0,0 +1,11 @@
+"use strict";
+
+self.onconnect = function (event) {
+ event.ports[0].onmessage = function (event) {
+ switch (event.data) {
+ case "close":
+ close();
+ break;
+ }
+ };
+};
diff --git a/dom/workers/test/WorkerDebugger_suspended_debugger.js b/dom/workers/test/WorkerDebugger_suspended_debugger.js
new file mode 100644
index 000000000..2ed4e16c4
--- /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 000000000..c096be7e4
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_suspended_worker.js
@@ -0,0 +1,6 @@
+"use strict";
+
+self.onmessage = function () {
+ postMessage("worker");
+ debugger;
+};
diff --git a/dom/workers/test/WorkerDebugger_worker.js b/dom/workers/test/WorkerDebugger_worker.js
new file mode 100644
index 000000000..f301ac1e8
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger_worker.js
@@ -0,0 +1,8 @@
+"use strict";
+
+var worker = new Worker("WorkerDebugger_childWorker.js");
+self.onmessage = function (event) {
+ postMessage("child:" + event.data);
+};
+debugger;
+postMessage("worker");
diff --git a/dom/workers/test/WorkerTest.jsm b/dom/workers/test/WorkerTest.jsm
new file mode 100644
index 000000000..86431b7f8
--- /dev/null
+++ b/dom/workers/test/WorkerTest.jsm
@@ -0,0 +1,17 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+this.EXPORTED_SYMBOLS = [
+ "WorkerTest"
+];
+
+this.WorkerTest = {
+ go: function(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 000000000..7a1df54bd
--- /dev/null
+++ b/dom/workers/test/WorkerTest_badworker.js
@@ -0,0 +1,7 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+onmessage = function(event) {
+ throw "Shouldn't be able to read this!";
+}
diff --git a/dom/workers/test/WorkerTest_subworker.js b/dom/workers/test/WorkerTest_subworker.js
new file mode 100644
index 000000000..3e9242ed6
--- /dev/null
+++ b/dom/workers/test/WorkerTest_subworker.js
@@ -0,0 +1,43 @@
+/**
+ * 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.xul",
+ "WorkerTest_badworker.js");
+
+ let mochitestURL = event.data.replace("test_chromeWorkerJSM.xul",
+ "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.
+ let worker = new ChromeWorker(mochitestURL);
+ worker.onmessage = function(event) {
+ throw event.data;
+ };
+ worker.onerror = function(event) {
+ event.preventDefault();
+
+ // And we shouldn't be able to make a regular Worker to a non-chrome URL.
+ worker = new Worker(mochitestURL);
+ worker.onmessage = function(event) {
+ throw event.data;
+ };
+ worker.onerror = function(event) {
+ event.preventDefault();
+ postMessage("Done");
+ };
+ worker.postMessage("Hi");
+ };
+ worker.postMessage("Hi");
+};
diff --git a/dom/workers/test/WorkerTest_worker.js b/dom/workers/test/WorkerTest_worker.js
new file mode 100644
index 000000000..445408f8f
--- /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(event) {
+ postMessage(event.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 000000000..680ca904b
--- /dev/null
+++ b/dom/workers/test/atob_worker.js
@@ -0,0 +1,46 @@
+/**
+ * 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/browser.ini b/dom/workers/test/browser.ini
new file mode 100644
index 000000000..ae1e27d13
--- /dev/null
+++ b/dom/workers/test/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+support-files =
+ bug1047663_tab.html
+ bug1047663_worker.sjs
+ frame_script.js
+ head.js
+ !/dom/base/test/file_empty.html
+ !/dom/base/test/file_bug945152.jar
+
+[browser_bug1047663.js]
+[browser_bug1104623.js]
+run-if = buildapp == 'browser'
diff --git a/dom/workers/test/browser_bug1047663.js b/dom/workers/test/browser_bug1047663.js
new file mode 100644
index 000000000..8fa2c5358
--- /dev/null
+++ b/dom/workers/test/browser_bug1047663.js
@@ -0,0 +1,50 @@
+/* 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 TAB_URL = EXAMPLE_URL + "bug1047663_tab.html";
+const WORKER_URL = EXAMPLE_URL + "bug1047663_worker.sjs";
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ let tab = yield 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.
+ yield createWorkerInTab(tab, WORKER_URL);
+ let message = yield postMessageToWorkerInTab(tab, WORKER_URL, "ping");
+ is(message, "one");
+ yield 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.
+ yield createWorkerInTab(tab, WORKER_URL);
+ message = yield postMessageToWorkerInTab(tab, WORKER_URL, "ping");
+ is(message, "one");
+ yield terminateWorkerInTab(tab, WORKER_URL);
+
+ // Disable the cache in this tab. This should also disable the cache for all
+ // workers in this tab.
+ yield 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.
+ yield createWorkerInTab(tab, WORKER_URL);
+ message = yield postMessageToWorkerInTab(tab, WORKER_URL, "ping");
+ is(message, "two");
+ yield 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 000000000..64a8eeb9c
--- /dev/null
+++ b/dom/workers/test/browser_bug1104623.js
@@ -0,0 +1,53 @@
+/* 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 = gBrowser.addTab(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/bug1014466_data1.txt b/dom/workers/test/bug1014466_data1.txt
new file mode 100644
index 000000000..a32a4347a
--- /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 000000000..4d40154ce
--- /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 000000000..beb324f0f
--- /dev/null
+++ b/dom/workers/test/bug1014466_worker.js
@@ -0,0 +1,64 @@
+/**
+ * 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: 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(event) {
+ ok(false, "Got an error event: " + event);
+ 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 000000000..7f810d893
--- /dev/null
+++ b/dom/workers/test/bug1020226_frame.html
@@ -0,0 +1,20 @@
+<!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 000000000..868b1a2f2
--- /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 000000000..62ab9be7d
--- /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 000000000..a39bf4474
--- /dev/null
+++ b/dom/workers/test/bug1047663_worker.sjs
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"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");
+
+ // 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 000000000..a0fcd3f60
--- /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 000000000..d3df38870
--- /dev/null
+++ b/dom/workers/test/bug1062920_worker.js
@@ -0,0 +1,6 @@
+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_worker.js b/dom/workers/test/bug1063538_worker.js
new file mode 100644
index 000000000..dc53dd289
--- /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 gJar = "jar:http://example.org/tests/dom/base/test/file_bug945152.jar!/data_big.txt";
+var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+var progressFired = false;
+
+xhr.onloadend = function(e) {
+ postMessage({type: 'finish', progressFired: progressFired });
+ self.close();
+};
+
+xhr.onprogress = function(e) {
+ if (e.loaded > 0) {
+ progressFired = true;
+ xhr.abort();
+ }
+};
+
+onmessage = function(e) {
+ xhr.open("GET", gJar, true);
+ xhr.send();
+}
diff --git a/dom/workers/test/bug1104064_worker.js b/dom/workers/test/bug1104064_worker.js
new file mode 100644
index 000000000..5e627c86f
--- /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 000000000..988eea106
--- /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 000000000..cabd40686
--- /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 000000000..126b9c901
--- /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 000000000..a14ed2810
--- /dev/null
+++ b/dom/workers/test/bug998474_worker.js
@@ -0,0 +1,6 @@
+self.addEventListener("connect", function(e) {
+ var port = e.ports[0];
+ port.onmessage = function(e) {
+ port.postMessage(eval(e.data));
+ };
+});
diff --git a/dom/workers/test/chrome.ini b/dom/workers/test/chrome.ini
new file mode 100644
index 000000000..c01a20b2b
--- /dev/null
+++ b/dom/workers/test/chrome.ini
@@ -0,0 +1,86 @@
+[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_iframe1.html
+ WorkerDebugger_frozen_iframe2.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.js
+ 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
+ workersDisabled_worker.js
+
+[test_WorkerDebugger.initialize.xul]
+[test_WorkerDebugger.postMessage.xul]
+[test_WorkerDebugger.xul]
+[test_WorkerDebuggerGlobalScope.createSandbox.xul]
+[test_WorkerDebuggerGlobalScope.enterEventLoop.xul]
+[test_WorkerDebuggerGlobalScope.reportError.xul]
+skip-if = (os == 'linux') # Bug 1244697
+[test_WorkerDebuggerGlobalScope.setImmediate.xul]
+[test_WorkerDebuggerManager.xul]
+skip-if = (os == 'linux') # Bug 1244409
+[test_WorkerDebugger_console.xul]
+[test_WorkerDebugger_frozen.xul]
+[test_WorkerDebugger_promise.xul]
+[test_WorkerDebugger_suspended.xul]
+[test_chromeWorker.xul]
+[test_chromeWorkerJSM.xul]
+[test_extension.xul]
+[test_extensionBootstrap.xul]
+[test_file.xul]
+[test_fileBlobPosting.xul]
+[test_fileBlobSubWorker.xul]
+[test_filePosting.xul]
+[test_fileReadSlice.xul]
+[test_fileReaderSync.xul]
+[test_fileReaderSyncErrors.xul]
+[test_fileSlice.xul]
+[test_fileSubWorker.xul]
+[test_workersDisabled.xul]
+[test_bug1062920.xul]
+[test_sharedWorker_privateBrowsing.html]
diff --git a/dom/workers/test/chromeWorker_subworker.js b/dom/workers/test/chromeWorker_subworker.js
new file mode 100644
index 000000000..5ad1a9923
--- /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 000000000..5738a9ae5
--- /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(event) {
+ postMessage(event.data);
+ }
+ worker.postMessage(event.data);
+}
diff --git a/dom/workers/test/clearTimeouts_worker.js b/dom/workers/test/clearTimeouts_worker.js
new file mode 100644
index 000000000..b471515b3
--- /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 000000000..aaf104af1
--- /dev/null
+++ b/dom/workers/test/consoleReplaceable_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: '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 000000000..6b5f9d8a1
--- /dev/null
+++ b/dom/workers/test/console_worker.js
@@ -0,0 +1,109 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+onmessage = function(event) {
+ // TEST: does console exist?
+ postMessage({event: 'console exists', status: !!console, last : false});
+
+ postMessage({event: 'console is the same object', status: console === console, last: false});
+
+ postMessage({event: 'trace without function', status: true, last : false});
+
+ for (var i = 0; i < 10; ++i) {
+ console.log(i, i, i);
+ }
+
+ function trace1() {
+ function trace2() {
+ function trace3() {
+ console.trace("trace " + i);
+ }
+ trace3();
+ }
+ trace2();
+ }
+ trace1();
+
+ foobar585956c = function(a) {
+ console.trace();
+ return a+"c";
+ };
+
+ function foobar585956b(a) {
+ return foobar585956c(a+"b");
+ }
+
+ function foobar585956a(omg) {
+ return foobar585956b(omg + "a");
+ }
+
+ function foobar646025(omg) {
+ console.log(omg, "o", "d");
+ }
+
+ function startTimer(timer) {
+ console.time(timer);
+ }
+
+ function stopTimer(timer) {
+ console.timeEnd(timer);
+ }
+
+ function timeStamp(label) {
+ console.timeStamp(label);
+ }
+
+ function testGroups() {
+ console.groupCollapsed("a", "group");
+ console.group("b", "group");
+ console.groupEnd("b", "group");
+ }
+
+ 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(event) {
+ postMessage(event.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 000000000..7e092ec8c
--- /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": 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 000000000..6ad0d550f
--- /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 000000000..6d896bc46
--- /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 000000000..6d1f0f0a7
--- /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/779707.html b/dom/workers/test/crashtests/779707.html
new file mode 100644
index 000000000..97a8113da
--- /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 000000000..5f4667850
--- /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 000000000..a7518d3c2
--- /dev/null
+++ b/dom/workers/test/crashtests/crashtests.list
@@ -0,0 +1,5 @@
+load 779707.html
+load 943516.html
+load 1153636.html
+load 1158031.html
+load 1228456.html
diff --git a/dom/workers/test/csp_worker.js b/dom/workers/test/csp_worker.js
new file mode 100644
index 000000000..63b3adeaf
--- /dev/null
+++ b/dom/workers/test/csp_worker.js
@@ -0,0 +1,28 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+onmessage = function(event) {
+ if (event.data.do == "eval") {
+ var res;
+ try {
+ res = eval("40+2");
+ }
+ catch(ex) {
+ res = ex+"";
+ }
+ postMessage(res);
+ }
+ else if (event.data.do == "nest") {
+ var worker = new Worker(event.data.uri);
+ if (--event.data.level) {
+ worker.postMessage(event.data);
+ }
+ else {
+ worker.postMessage({ do: "eval" });
+ }
+ worker.onmessage = (e) => {
+ postMessage(e.data);
+ }
+ }
+}
diff --git a/dom/workers/test/csp_worker.js^headers^ b/dom/workers/test/csp_worker.js^headers^
new file mode 100644
index 000000000..7b835bf2a
--- /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 000000000..52e802ac7
--- /dev/null
+++ b/dom/workers/test/dom_worker_helper.js
@@ -0,0 +1,176 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+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() {
+ let e = wdm.getWorkerDebuggerEnumerator();
+ while (e.hasMoreElements()) {
+ let dbg = e.getNext().QueryInterface(Ci.nsIWorkerDebugger);
+ yield dbg;
+ }
+}
+
+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: function (dbg) {
+ dump("FAK " + dbg.url + "\n");
+ 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: function (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: function () {
+ ok(true, "Debugger should be closed.");
+ dbg.removeListener(this);
+ resolve();
+ }
+ });
+ });
+}
+
+function waitForDebuggerError(dbg) {
+ return new Promise(function (resolve) {
+ dbg.addListener({
+ onError: function (filename, lineno, message) {
+ dbg.removeListener(this);
+ resolve(new Error(message, filename, lineno));
+ }
+ });
+ });
+}
+
+function waitForDebuggerMessage(dbg, message) {
+ return new Promise(function (resolve) {
+ dbg.addListener({
+ onMessage: function (message1) {
+ if (message !== message1) {
+ return;
+ }
+ ok(true, "Should receive " + message + " message from debugger.");
+ dbg.removeListener(this);
+ resolve();
+ }
+ });
+ });
+}
+
+function waitForWindowMessage(window, message) {
+ return new Promise(function (resolve) {
+ let onmessage = function (event) {
+ if (event.data !== event.data) {
+ return;
+ }
+ window.removeEventListener("message", onmessage, false);
+ resolve();
+ };
+ window.addEventListener("message", onmessage, false);
+ });
+}
+
+function waitForWorkerMessage(worker, message) {
+ return new Promise(function (resolve) {
+ worker.addEventListener("message", function onmessage(event) {
+ if (event.data !== message) {
+ return;
+ }
+ ok(true, "Should receive " + message + " message from worker.");
+ worker.removeEventListener("message", onmessage);
+ resolve();
+ });
+ });
+}
+
+function waitForMultiple(promises) {
+ return new Promise(function (resolve) {
+ let values = [];
+ for (let i = 0; i < promises.length; ++i) {
+ let index = i;
+ promises[i].then(function (value) {
+ is(index + 1, values.length + 1,
+ "Promise " + (values.length + 1) + " out of " + promises.length +
+ " should be resolved.");
+ values.push(value);
+ if (values.length === promises.length) {
+ resolve(values);
+ }
+ });
+ }
+ });
+};
diff --git a/dom/workers/test/empty.html b/dom/workers/test/empty.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/dom/workers/test/empty.html
diff --git a/dom/workers/test/errorPropagation_iframe.html b/dom/workers/test/errorPropagation_iframe.html
new file mode 100644
index 000000000..c5f688487
--- /dev/null
+++ b/dom/workers/test/errorPropagation_iframe.html
@@ -0,0 +1,55 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <meta charset="utf-8">
+ <body>
+ <script type="text/javascript">
+ var worker;
+
+ function start(workerCount, messageCallback) {
+ var seenWindowError;
+ window.onerror = function(message, filename, lineno) {
+ if (!seenWindowError) {
+ seenWindowError = true;
+ messageCallback({
+ type: "window",
+ data: { message: message, filename: filename, lineno: lineno }
+ });
+ return true;
+ }
+ };
+
+ worker = new Worker("errorPropagation_worker.js");
+
+ worker.onmessage = function(event) {
+ messageCallback(event.data);
+ };
+
+ var seenWorkerError;
+ worker.onerror = function(event) {
+ if (!seenWorkerError) {
+ seenWorkerError = true;
+ messageCallback({
+ type: "worker",
+ data: {
+ message: event.message,
+ filename: event.filename,
+ lineno: event.lineno
+ }
+ });
+ event.preventDefault();
+ }
+ };
+
+ worker.postMessage(workerCount);
+ }
+
+ function stop() {
+ worker.terminate();
+ }
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/errorPropagation_worker.js b/dom/workers/test/errorPropagation_worker.js
new file mode 100644
index 000000000..84f5916e0
--- /dev/null
+++ b/dom/workers/test/errorPropagation_worker.js
@@ -0,0 +1,50 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+var seenScopeError;
+onerror = function(message, filename, lineno) {
+ if (!seenScopeError) {
+ seenScopeError = true;
+ postMessage({
+ type: "scope",
+ data: { message: message, filename: filename, lineno: lineno }
+ });
+ return true;
+ }
+};
+
+onmessage = function(event) {
+ var workerId = parseInt(event.data);
+
+ if (workerId > 1) {
+ var worker = new Worker("errorPropagation_worker.js");
+
+ worker.onmessage = function(event) {
+ postMessage(event.data);
+ };
+
+ var seenWorkerError;
+ worker.onerror = function(event) {
+ if (!seenWorkerError) {
+ seenWorkerError = true;
+ postMessage({
+ type: "worker",
+ data: {
+ message: event.message,
+ filename: event.filename,
+ lineno: event.lineno
+ }
+ });
+ event.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 000000000..1c372104c
--- /dev/null
+++ b/dom/workers/test/errorwarning_worker.js
@@ -0,0 +1,42 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function errorHandler() {
+ postMessage({ type: 'error' });
+}
+
+onmessage = function(event) {
+ if (event.data.errors) {
+ try {
+ // This is an error:
+ postMessage({ type: 'ignore', value: b.aaa });
+ } catch(e) {
+ errorHandler();
+ }
+ } else {
+ var a = {};
+ // This is a warning:
+ postMessage({ type: 'ignore', value: a.foo });
+ }
+
+ if (event.data.loop != 0) {
+ var worker = new Worker('errorwarning_worker.js');
+ worker.onerror = errorHandler;
+ worker.postMessage({ loop: event.data.loop - 1, errors: event.data.errors });
+
+ worker.onmessage = function(e) {
+ postMessage(e.data);
+ }
+
+ } else {
+ postMessage({ type: 'finish' });
+ }
+}
+
+onerror = errorHandler;
+onerror = onerror;
+if (!onerror || onerror != onerror) {
+ throw "onerror wasn't set properly";
+}
diff --git a/dom/workers/test/eventDispatch_worker.js b/dom/workers/test/eventDispatch_worker.js
new file mode 100644
index 000000000..915f60c93
--- /dev/null
+++ b/dom/workers/test/eventDispatch_worker.js
@@ -0,0 +1,67 @@
+/**
+ * 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/extensions/bootstrap/bootstrap.js b/dom/workers/test/extensions/bootstrap/bootstrap.js
new file mode 100644
index 000000000..acb0a204b
--- /dev/null
+++ b/dom/workers/test/extensions/bootstrap/bootstrap.js
@@ -0,0 +1,141 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function testForExpectedSymbols(stage, data) {
+ const expectedSymbols = [ "Worker", "ChromeWorker" ];
+ for (var symbol of expectedSymbols) {
+ Services.prefs.setBoolPref("workertest.bootstrap." + stage + "." + symbol,
+ symbol in this);
+ }
+}
+
+var gWorkerAndCallback = {
+ _ensureStarted: function() {
+ if (!this._worker) {
+ throw new Error("Not yet started!");
+ }
+ },
+
+ start: function(data) {
+ if (!this._worker) {
+ this._worker = new Worker("chrome://workerbootstrap/content/worker.js");
+ this._worker.onerror = function(event) {
+ Cu.reportError(event.message);
+ event.preventDefault();
+ };
+ }
+ },
+
+ stop: function() {
+ if (this._worker) {
+ this._worker.terminate();
+ delete this._worker;
+ }
+ },
+
+ set callback(val) {
+ this._ensureStarted();
+ var callback = val.QueryInterface(Ci.nsIObserver);
+ if (this._callback != callback) {
+ if (callback) {
+ this._worker.onmessage = function(event) {
+ callback.observe(this, event.type, event.data);
+ };
+ this._worker.onerror = function(event) {
+ callback.observe(this, event.type, event.message);
+ event.preventDefault();
+ };
+ }
+ else {
+ this._worker.onmessage = null;
+ this._worker.onerror = null;
+ }
+ this._callback = callback;
+ }
+ },
+
+ get callback() {
+ return this._callback;
+ },
+
+ postMessage: function(data) {
+ this._ensureStarted();
+ this._worker.postMessage(data);
+ },
+
+ terminate: function() {
+ this._ensureStarted();
+ this._worker.terminate();
+ delete this._callback;
+ }
+};
+
+function WorkerTestBootstrap() {
+}
+WorkerTestBootstrap.prototype = {
+ observe: function(subject, topic, data) {
+
+ gWorkerAndCallback.callback = subject;
+
+ switch (topic) {
+ case "postMessage":
+ gWorkerAndCallback.postMessage(data);
+ break;
+
+ case "terminate":
+ gWorkerAndCallback.terminate();
+ break;
+
+ default:
+ throw new Error("Unknown worker command");
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+};
+
+var gFactory = {
+ register: function() {
+ var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+ var classID = Components.ID("{36b5df0b-8dcf-4aa2-9c45-c51d871295f9}");
+ var description = "WorkerTestBootstrap";
+ var contractID = "@mozilla.org/test/workertestbootstrap;1";
+ var factory = XPCOMUtils._getFactory(WorkerTestBootstrap);
+
+ registrar.registerFactory(classID, description, contractID, factory);
+
+ this.unregister = function() {
+ registrar.unregisterFactory(classID, factory);
+ delete this.unregister;
+ };
+ }
+};
+
+function install(data, reason) {
+ testForExpectedSymbols("install");
+}
+
+function startup(data, reason) {
+ testForExpectedSymbols("startup");
+ gFactory.register();
+ gWorkerAndCallback.start(data);
+}
+
+function shutdown(data, reason) {
+ testForExpectedSymbols("shutdown");
+ gWorkerAndCallback.stop();
+ gFactory.unregister();
+}
+
+function uninstall(data, reason) {
+ testForExpectedSymbols("uninstall");
+}
diff --git a/dom/workers/test/extensions/bootstrap/install.rdf b/dom/workers/test/extensions/bootstrap/install.rdf
new file mode 100644
index 000000000..fdd9638cd
--- /dev/null
+++ b/dom/workers/test/extensions/bootstrap/install.rdf
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+ <em:name>WorkerTestBootstrap</em:name>
+ <em:description>Worker functions for use in testing.</em:description>
+ <em:creator>Mozilla</em:creator>
+ <em:version>2016.03.09</em:version>
+ <em:id>workerbootstrap-test@mozilla.org</em:id>
+ <em:type>2</em:type>
+ <em:bootstrap>true</em:bootstrap>
+ <em:targetApplication>
+ <Description>
+ <!-- Firefox -->
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>45.0</em:minVersion>
+ <em:maxVersion>*</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+ <em:targetApplication>
+ <Description>
+ <!-- Fennec -->
+ <em:id>{aa3c5121-dab2-40e2-81ca-7ea25febc110}</em:id>
+ <em:minVersion>45.0</em:minVersion>
+ <em:maxVersion>*</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+ </Description>
+</RDF>
diff --git a/dom/workers/test/extensions/bootstrap/jar.mn b/dom/workers/test/extensions/bootstrap/jar.mn
new file mode 100644
index 000000000..a9c625103
--- /dev/null
+++ b/dom/workers/test/extensions/bootstrap/jar.mn
@@ -0,0 +1,3 @@
+workerbootstrap.jar:
+% content workerbootstrap %content/
+ content/worker.js (worker.js)
diff --git a/dom/workers/test/extensions/bootstrap/moz.build b/dom/workers/test/extensions/bootstrap/moz.build
new file mode 100644
index 000000000..aec5c249c
--- /dev/null
+++ b/dom/workers/test/extensions/bootstrap/moz.build
@@ -0,0 +1,20 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPI_NAME = 'workerbootstrap'
+
+JAR_MANIFESTS += ['jar.mn']
+USE_EXTENSION_MANIFEST = True
+NO_JS_MANIFEST = True
+
+FINAL_TARGET_FILES += [
+ 'bootstrap.js',
+ 'install.rdf',
+]
+
+TEST_HARNESS_FILES.testing.mochitest.extensions += [
+ 'workerbootstrap-test@mozilla.org.xpi',
+]
diff --git a/dom/workers/test/extensions/bootstrap/worker.js b/dom/workers/test/extensions/bootstrap/worker.js
new file mode 100644
index 000000000..7346fc142
--- /dev/null
+++ b/dom/workers/test/extensions/bootstrap/worker.js
@@ -0,0 +1,7 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+onmessage = function(event) {
+ postMessage(event.data);
+}
diff --git a/dom/workers/test/extensions/bootstrap/workerbootstrap-test@mozilla.org.xpi b/dom/workers/test/extensions/bootstrap/workerbootstrap-test@mozilla.org.xpi
new file mode 100644
index 000000000..2dab975db
--- /dev/null
+++ b/dom/workers/test/extensions/bootstrap/workerbootstrap-test@mozilla.org.xpi
Binary files differ
diff --git a/dom/workers/test/extensions/moz.build b/dom/workers/test/extensions/moz.build
new file mode 100644
index 000000000..51cf80fa2
--- /dev/null
+++ b/dom/workers/test/extensions/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['bootstrap', 'traditional']
diff --git a/dom/workers/test/extensions/traditional/WorkerTest.js b/dom/workers/test/extensions/traditional/WorkerTest.js
new file mode 100644
index 000000000..5890c0d4c
--- /dev/null
+++ b/dom/workers/test/extensions/traditional/WorkerTest.js
@@ -0,0 +1,122 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var gWorkerAndCallback = {
+ _worker: null,
+ _callback: null,
+
+ _ensureStarted: function() {
+ if (!this._worker) {
+ throw new Error("Not yet started!");
+ }
+ },
+
+ start: function() {
+ if (!this._worker) {
+ var worker = new Worker("chrome://worker/content/worker.js");
+ worker.onerror = function(event) {
+ Cu.reportError(event.message);
+ event.preventDefault();
+ };
+
+ this._worker = worker;
+ }
+ },
+
+ stop: function() {
+ if (this._worker) {
+ try {
+ this.terminate();
+ }
+ catch(e) {
+ Cu.reportError(e);
+ }
+ this._worker = null;
+ }
+ },
+
+ set callback(val) {
+ this._ensureStarted();
+ if (val) {
+ var callback = val.QueryInterface(Ci.nsIWorkerTestCallback);
+ if (this.callback != callback) {
+ this._worker.onmessage = function(event) {
+ callback.onmessage(event.data);
+ };
+ this._worker.onerror = function(event) {
+ callback.onerror(event.message);
+ event.preventDefault();
+ };
+ this._callback = callback;
+ }
+ }
+ else {
+ this._worker.onmessage = null;
+ this._worker.onerror = null;
+ this._callback = null;
+ }
+ },
+
+ get callback() {
+ return this._callback;
+ },
+
+ postMessage: function(data) {
+ this._ensureStarted();
+ this._worker.postMessage(data);
+ },
+
+ terminate: function() {
+ this._ensureStarted();
+ this._worker.terminate();
+ this.callback = null;
+ }
+};
+
+function WorkerTest() {
+}
+WorkerTest.prototype = {
+ observe: function(subject, topic, data) {
+ switch(topic) {
+ case "profile-after-change":
+ gWorkerAndCallback.start();
+ Services.obs.addObserver(this, "profile-before-change", false);
+ break;
+ case "profile-before-change":
+ gWorkerAndCallback.stop();
+ break;
+ default:
+ Cu.reportError("Unknown topic: " + topic);
+ }
+ },
+
+ set callback(val) {
+ gWorkerAndCallback.callback = val;
+ },
+
+ get callback() {
+ return gWorkerAndCallback.callback;
+ },
+
+ postMessage: function(message) {
+ gWorkerAndCallback.postMessage(message);
+ },
+
+ terminate: function() {
+ gWorkerAndCallback.terminate();
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsIWorkerTest]),
+ classID: Components.ID("{3b52b935-551f-4606-ba4c-decc18b67bfd}")
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WorkerTest]);
diff --git a/dom/workers/test/extensions/traditional/WorkerTest.manifest b/dom/workers/test/extensions/traditional/WorkerTest.manifest
new file mode 100644
index 000000000..a5a32fb06
--- /dev/null
+++ b/dom/workers/test/extensions/traditional/WorkerTest.manifest
@@ -0,0 +1,3 @@
+component {3b52b935-551f-4606-ba4c-decc18b67bfd} WorkerTest.js
+contract @mozilla.org/test/workertest;1 {3b52b935-551f-4606-ba4c-decc18b67bfd}
+category profile-after-change WorkerTest @mozilla.org/test/workertest;1
diff --git a/dom/workers/test/extensions/traditional/install.rdf b/dom/workers/test/extensions/traditional/install.rdf
new file mode 100644
index 000000000..00fc0f441
--- /dev/null
+++ b/dom/workers/test/extensions/traditional/install.rdf
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+ <em:name>WorkerTest</em:name>
+ <em:description>Worker functions for use in testing.</em:description>
+ <em:creator>Mozilla</em:creator>
+ <em:version>2016.03.09</em:version>
+ <em:id>worker-test@mozilla.org</em:id>
+ <em:type>2</em:type>
+ <em:targetApplication>
+ <Description>
+ <!-- Firefox -->
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>45.0</em:minVersion>
+ <em:maxVersion>*</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+ <em:targetApplication>
+ <Description>
+ <!-- Fennec -->
+ <em:id>{aa3c5121-dab2-40e2-81ca-7ea25febc110}</em:id>
+ <em:minVersion>45.0</em:minVersion>
+ <em:maxVersion>*</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+ </Description>
+</RDF>
diff --git a/dom/workers/test/extensions/traditional/jar.mn b/dom/workers/test/extensions/traditional/jar.mn
new file mode 100644
index 000000000..421ee55a0
--- /dev/null
+++ b/dom/workers/test/extensions/traditional/jar.mn
@@ -0,0 +1,3 @@
+worker.jar:
+% content worker %content/
+ content/worker.js (worker.js)
diff --git a/dom/workers/test/extensions/traditional/moz.build b/dom/workers/test/extensions/traditional/moz.build
new file mode 100644
index 000000000..d0920420d
--- /dev/null
+++ b/dom/workers/test/extensions/traditional/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIWorkerTest.idl',
+]
+
+XPIDL_MODULE = 'WorkerTest'
+
+EXTRA_COMPONENTS += [
+ 'WorkerTest.js',
+ 'WorkerTest.manifest',
+]
+
+XPI_NAME = 'worker'
+
+JAR_MANIFESTS += ['jar.mn']
+USE_EXTENSION_MANIFEST = True
+NO_JS_MANIFEST = True
+
+FINAL_TARGET_FILES += [
+ 'install.rdf',
+]
+
+TEST_HARNESS_FILES.testing.mochitest.extensions += [
+ 'worker-test@mozilla.org.xpi',
+]
diff --git a/dom/workers/test/extensions/traditional/nsIWorkerTest.idl b/dom/workers/test/extensions/traditional/nsIWorkerTest.idl
new file mode 100644
index 000000000..32a952038
--- /dev/null
+++ b/dom/workers/test/extensions/traditional/nsIWorkerTest.idl
@@ -0,0 +1,23 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(10f8ebdf-1373-4640-9c34-53dee99f526f)]
+interface nsIWorkerTestCallback : nsISupports
+{
+ void onmessage(in DOMString data);
+ void onerror(in DOMString data);
+};
+
+[scriptable, uuid(887a0614-a0f0-4c0e-80e0-cf31e6d4e286)]
+interface nsIWorkerTest : nsISupports
+{
+ void postMessage(in DOMString data);
+ void terminate();
+
+ attribute nsIWorkerTestCallback callback;
+};
diff --git a/dom/workers/test/extensions/traditional/worker-test@mozilla.org.xpi b/dom/workers/test/extensions/traditional/worker-test@mozilla.org.xpi
new file mode 100644
index 000000000..8d2386894
--- /dev/null
+++ b/dom/workers/test/extensions/traditional/worker-test@mozilla.org.xpi
Binary files differ
diff --git a/dom/workers/test/extensions/traditional/worker.js b/dom/workers/test/extensions/traditional/worker.js
new file mode 100644
index 000000000..7346fc142
--- /dev/null
+++ b/dom/workers/test/extensions/traditional/worker.js
@@ -0,0 +1,7 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+onmessage = function(event) {
+ postMessage(event.data);
+}
diff --git a/dom/workers/test/fibonacci_worker.js b/dom/workers/test/fibonacci_worker.js
new file mode 100644
index 000000000..fa35385e7
--- /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(event) {
+ results.push(parseInt(event.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 000000000..2dc8cd12d
--- /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(event) {
+ postMessage(event.data);
+ }
+
+ worker.onerror = function(event) {
+ postMessage(undefined);
+ }
+};
diff --git a/dom/workers/test/fileBlob_worker.js b/dom/workers/test/fileBlob_worker.js
new file mode 100644
index 000000000..2f7a31714
--- /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 000000000..2a24b2a40
--- /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 000000000..2c10b7d76
--- /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 000000000..f79ecc6f0
--- /dev/null
+++ b/dom/workers/test/fileReaderSyncErrors_worker.js
@@ -0,0 +1,74 @@
+/**
+ * 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 000000000..4a37409d5
--- /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 000000000..d0c6364e2
--- /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 000000000..21fbc3454
--- /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(event) {
+ postMessage(event.data);
+ }
+
+ worker.onerror = function(event) {
+ 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 000000000..239968069
--- /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_worker.js b/dom/workers/test/file_worker.js
new file mode 100644
index 000000000..23233b8ac
--- /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.lastModifiedDate = file.lastModifiedDate;
+
+ postMessage(rtnObj);
+};
diff --git a/dom/workers/test/fileapi_chromeScript.js b/dom/workers/test/fileapi_chromeScript.js
new file mode 100644
index 000000000..614b556ed
--- /dev/null
+++ b/dom/workers/test/fileapi_chromeScript.js
@@ -0,0 +1,29 @@
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.importGlobalProperties(["File"]);
+
+var fileNum = 1;
+
+function createFileWithData(fileData) {
+ var willDelete = fileData === null;
+ var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
+ var testFile = dirSvc.get("ProfD", Ci.nsIFile);
+ testFile.append("fileAPItestfile" + fileNum);
+ 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);
+ if (willDelete) {
+ fileData = "some irrelevant test data\n";
+ }
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+ var domFile = File.createFromNsIFile(testFile);
+ if (willDelete) {
+ testFile.remove(/* recursive: */ false);
+ }
+ return domFile;
+}
+
+addMessageListener("files.open", function (message) {
+ sendAsyncMessage("files.opened", message.map(createFileWithData));
+});
diff --git a/dom/workers/test/foreign.js b/dom/workers/test/foreign.js
new file mode 100644
index 000000000..33c982fa8
--- /dev/null
+++ b/dom/workers/test/foreign.js
@@ -0,0 +1 @@
+response = "bad";
diff --git a/dom/workers/test/frame_script.js b/dom/workers/test/frame_script.js
new file mode 100644
index 000000000..ffc384416
--- /dev/null
+++ b/dom/workers/test/frame_script.js
@@ -0,0 +1,72 @@
+/* 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 { interfaces: Ci } = Components;
+
+let workers = {};
+
+let methods = {
+ /**
+ * Create a worker with the given `url` in this tab.
+ */
+ createWorker: function (url) {
+ dump("Frame script: creating worker with url '" + url + "'\n");
+
+ workers[url] = new content.Worker(url);
+ return Promise.resolve();
+ },
+
+ /**
+ * Terminate the worker with the given `url` in this tab.
+ */
+ terminateWorker: function (url) {
+ dump("Frame script: terminating worker with url '" + url + "'\n");
+
+ workers[url].terminate();
+ delete workers[url];
+ return Promise.resolve();
+ },
+
+ /**
+ * Post the given `message` to the worker with the given `url` in this tab.
+ */
+ postMessageToWorker: function (url, message) {
+ dump("Frame script: posting message to worker with url '" + url + "'\n");
+
+ let worker = workers[url];
+ worker.postMessage(message);
+ return new Promise(function (resolve) {
+ worker.onmessage = function (event) {
+ worker.onmessage = null;
+ resolve(event.data);
+ };
+ });
+ },
+
+ /**
+ * Disable the cache for this tab.
+ */
+ disableCache: function () {
+ docShell.defaultLoadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE
+ | Ci.nsIRequest.INHIBIT_CACHING;
+ }
+};
+
+addMessageListener("jsonrpc", function (event) {
+ let { id, method, params } = event.data;
+ Promise.resolve().then(function () {
+ return methods[method].apply(undefined, params);
+ }).then(function (result) {
+ sendAsyncMessage("jsonrpc", {
+ id: id,
+ result: result
+ });
+ }).catch(function (error) {
+ sendAsyncMessage("jsonrpc", {
+ id: id,
+ error: error.toString()
+ });
+ });
+});
diff --git a/dom/workers/test/gtest/TestReadWrite.cpp b/dom/workers/test/gtest/TestReadWrite.cpp
new file mode 100644
index 000000000..d59888e24
--- /dev/null
+++ b/dom/workers/test/gtest/TestReadWrite.cpp
@@ -0,0 +1,499 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/ServiceWorkerRegistrar.h"
+#include "mozilla/dom/ServiceWorkerRegistrarTypes.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIFile.h"
+#include "nsIOutputStream.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+class ServiceWorkerRegistrarTest : public ServiceWorkerRegistrar
+{
+public:
+ ServiceWorkerRegistrarTest()
+ {
+#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(mProfileDir));
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+#else
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(mProfileDir));
+#endif
+ MOZ_DIAGNOSTIC_ASSERT(mProfileDir);
+ }
+
+ nsresult TestReadData() { return ReadData(); }
+ nsresult TestWriteData() { return WriteData(); }
+ void TestDeleteData() { DeleteData(); }
+
+ void TestRegisterServiceWorker(const ServiceWorkerRegistrationData& aData)
+ {
+ RegisterServiceWorkerInternal(aData);
+ }
+
+ nsTArray<ServiceWorkerRegistrationData>& TestGetData() { return mData; }
+};
+
+already_AddRefed<nsIFile>
+GetFile()
+{
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE));
+ return file.forget();
+}
+
+bool
+CreateFile(const nsACString& aData)
+{
+ nsCOMPtr<nsIFile> file = GetFile();
+
+ nsCOMPtr<nsIOutputStream> stream;
+ nsresult rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), file);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ uint32_t count;
+ rv = stream->Write(aData.Data(), aData.Length(), &count);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ if (count != aData.Length()) {
+ return false;
+ }
+
+ return true;
+}
+
+TEST(ServiceWorkerRegistrar, TestNoFile)
+{
+ nsCOMPtr<nsIFile> file = GetFile();
+ ASSERT_TRUE(file) << "GetFile must return a nsIFIle";
+
+ bool exists;
+ nsresult rv = file->Exists(&exists);
+ ASSERT_EQ(NS_OK, rv) << "nsIFile::Exists cannot fail";
+
+ if (exists) {
+ rv = file->Remove(false);
+ ASSERT_EQ(NS_OK, rv) << "nsIFile::Remove cannot fail";
+ }
+
+ RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+ rv = swr->TestReadData();
+ ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+ const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
+ ASSERT_EQ((uint32_t)0, data.Length()) << "No data should be found in an empty file";
+}
+
+TEST(ServiceWorkerRegistrar, TestEmptyFile)
+{
+ ASSERT_TRUE(CreateFile(EmptyCString())) << "CreateFile should not fail";
+
+ RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+ nsresult rv = swr->TestReadData();
+ ASSERT_NE(NS_OK, rv) << "ReadData() should fail if the file is empty";
+
+ const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
+ ASSERT_EQ((uint32_t)0, data.Length()) << "No data should be found in an empty file";
+}
+
+TEST(ServiceWorkerRegistrar, TestRightVersionFile)
+{
+ ASSERT_TRUE(CreateFile(NS_LITERAL_CSTRING(SERVICEWORKERREGISTRAR_VERSION "\n"))) << "CreateFile should not fail";
+
+ RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+ nsresult rv = swr->TestReadData();
+ ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail when the version is correct";
+
+ const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
+ ASSERT_EQ((uint32_t)0, data.Length()) << "No data should be found in an empty file";
+}
+
+TEST(ServiceWorkerRegistrar, TestWrongVersionFile)
+{
+ ASSERT_TRUE(CreateFile(NS_LITERAL_CSTRING(SERVICEWORKERREGISTRAR_VERSION "bla\n"))) << "CreateFile should not fail";
+
+ RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+ nsresult rv = swr->TestReadData();
+ ASSERT_NE(NS_OK, rv) << "ReadData() should fail when the version is not correct";
+
+ const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
+ ASSERT_EQ((uint32_t)0, data.Length()) << "No data should be found in an empty file";
+}
+
+TEST(ServiceWorkerRegistrar, TestReadData)
+{
+ nsAutoCString buffer(SERVICEWORKERREGISTRAR_VERSION "\n");
+
+ buffer.Append("^appId=123&inBrowser=1\n");
+ buffer.Append("scope 0\ncurrentWorkerURL 0\ncacheName 0\n");
+ buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.Append("\n");
+ buffer.Append("scope 1\ncurrentWorkerURL 1\ncacheName 1\n");
+ buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ ASSERT_TRUE(CreateFile(buffer)) << "CreateFile should not fail";
+
+ RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+ nsresult rv = swr->TestReadData();
+ ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+ const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
+ ASSERT_EQ((uint32_t)2, data.Length()) << "2 entries should be found";
+
+ const mozilla::ipc::PrincipalInfo& info0 = data[0].principal();
+ ASSERT_EQ(info0.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
+ const mozilla::ipc::ContentPrincipalInfo& cInfo0 = data[0].principal();
+
+ nsAutoCString suffix0;
+ cInfo0.attrs().CreateSuffix(suffix0);
+
+ ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
+ ASSERT_STREQ("scope 0", cInfo0.spec().get());
+ ASSERT_STREQ("scope 0", data[0].scope().get());
+ ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
+ ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+
+ const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
+ ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
+ const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
+
+ nsAutoCString suffix1;
+ cInfo1.attrs().CreateSuffix(suffix1);
+
+ ASSERT_STREQ("", suffix1.get());
+ ASSERT_STREQ("scope 1", cInfo1.spec().get());
+ ASSERT_STREQ("scope 1", data[1].scope().get());
+ ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
+ ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
+}
+
+TEST(ServiceWorkerRegistrar, TestDeleteData)
+{
+ ASSERT_TRUE(CreateFile(NS_LITERAL_CSTRING("Foobar"))) << "CreateFile should not fail";
+
+ RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+ swr->TestDeleteData();
+
+ nsCOMPtr<nsIFile> file = GetFile();
+
+ bool exists;
+ nsresult rv = file->Exists(&exists);
+ ASSERT_EQ(NS_OK, rv) << "nsIFile::Exists cannot fail";
+
+ ASSERT_FALSE(exists) << "The file should not exist after a DeleteData().";
+}
+
+TEST(ServiceWorkerRegistrar, TestWriteData)
+{
+ {
+ RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+ for (int i = 0; i < 10; ++i) {
+ ServiceWorkerRegistrationData reg;
+
+ reg.scope() = nsPrintfCString("scope write %d", i);
+ reg.currentWorkerURL() = nsPrintfCString("currentWorkerURL write %d", i);
+ reg.cacheName() =
+ NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i));
+
+ nsAutoCString spec;
+ spec.AppendPrintf("spec write %d", i);
+ reg.principal() =
+ mozilla::ipc::ContentPrincipalInfo(mozilla::PrincipalOriginAttributes(i, i % 2),
+ mozilla::void_t(), spec);
+
+ swr->TestRegisterServiceWorker(reg);
+ }
+
+ nsresult rv = swr->TestWriteData();
+ ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail";
+ }
+
+ RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+ nsresult rv = swr->TestReadData();
+ ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+ const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
+ ASSERT_EQ((uint32_t)10, data.Length()) << "10 entries should be found";
+
+ for (int i = 0; i < 10; ++i) {
+ nsAutoCString test;
+
+ ASSERT_EQ(data[i].principal().type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
+ const mozilla::ipc::ContentPrincipalInfo& cInfo = data[i].principal();
+
+ mozilla::PrincipalOriginAttributes attrs(i, i % 2);
+ nsAutoCString suffix, expectSuffix;
+ attrs.CreateSuffix(expectSuffix);
+ cInfo.attrs().CreateSuffix(suffix);
+
+ ASSERT_STREQ(expectSuffix.get(), suffix.get());
+
+ test.AppendPrintf("scope write %d", i);
+ ASSERT_STREQ(test.get(), cInfo.spec().get());
+
+ test.Truncate();
+ test.AppendPrintf("scope write %d", i);
+ ASSERT_STREQ(test.get(), data[i].scope().get());
+
+ test.Truncate();
+ test.AppendPrintf("currentWorkerURL write %d", i);
+ ASSERT_STREQ(test.get(), data[i].currentWorkerURL().get());
+
+ test.Truncate();
+ test.AppendPrintf("cacheName write %d", i);
+ ASSERT_STREQ(test.get(), NS_ConvertUTF16toUTF8(data[i].cacheName()).get());
+ }
+}
+
+TEST(ServiceWorkerRegistrar, TestVersion2Migration)
+{
+ nsAutoCString buffer("2" "\n");
+
+ buffer.Append("^appId=123&inBrowser=1\n");
+ buffer.Append("spec 0\nscope 0\nscriptSpec 0\ncurrentWorkerURL 0\nactiveCache 0\nwaitingCache 0\n");
+ buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.Append("\n");
+ buffer.Append("spec 1\nscope 1\nscriptSpec 1\ncurrentWorkerURL 1\nactiveCache 1\nwaitingCache 1\n");
+ buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ ASSERT_TRUE(CreateFile(buffer)) << "CreateFile should not fail";
+
+ RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+ nsresult rv = swr->TestReadData();
+ ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+ const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
+ ASSERT_EQ((uint32_t)2, data.Length()) << "2 entries should be found";
+
+ const mozilla::ipc::PrincipalInfo& info0 = data[0].principal();
+ ASSERT_EQ(info0.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
+ const mozilla::ipc::ContentPrincipalInfo& cInfo0 = data[0].principal();
+
+ nsAutoCString suffix0;
+ cInfo0.attrs().CreateSuffix(suffix0);
+
+ ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
+ ASSERT_STREQ("scope 0", cInfo0.spec().get());
+ ASSERT_STREQ("scope 0", data[0].scope().get());
+ ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
+ ASSERT_STREQ("activeCache 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+
+ const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
+ ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
+ const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
+
+ nsAutoCString suffix1;
+ cInfo1.attrs().CreateSuffix(suffix1);
+
+ ASSERT_STREQ("", suffix1.get());
+ ASSERT_STREQ("scope 1", cInfo1.spec().get());
+ ASSERT_STREQ("scope 1", data[1].scope().get());
+ ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
+ ASSERT_STREQ("activeCache 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
+}
+
+TEST(ServiceWorkerRegistrar, TestVersion3Migration)
+{
+ nsAutoCString buffer("3" "\n");
+
+ buffer.Append("^appId=123&inBrowser=1\n");
+ buffer.Append("spec 0\nscope 0\ncurrentWorkerURL 0\ncacheName 0\n");
+ buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.Append("\n");
+ buffer.Append("spec 1\nscope 1\ncurrentWorkerURL 1\ncacheName 1\n");
+ buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ ASSERT_TRUE(CreateFile(buffer)) << "CreateFile should not fail";
+
+ RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+ nsresult rv = swr->TestReadData();
+ ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+ const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
+ ASSERT_EQ((uint32_t)2, data.Length()) << "2 entries should be found";
+
+ const mozilla::ipc::PrincipalInfo& info0 = data[0].principal();
+ ASSERT_EQ(info0.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
+ const mozilla::ipc::ContentPrincipalInfo& cInfo0 = data[0].principal();
+
+ nsAutoCString suffix0;
+ cInfo0.attrs().CreateSuffix(suffix0);
+
+ ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
+ ASSERT_STREQ("scope 0", cInfo0.spec().get());
+ ASSERT_STREQ("scope 0", data[0].scope().get());
+ ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
+ ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+
+ const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
+ ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
+ const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
+
+ nsAutoCString suffix1;
+ cInfo1.attrs().CreateSuffix(suffix1);
+
+ ASSERT_STREQ("", suffix1.get());
+ ASSERT_STREQ("scope 1", cInfo1.spec().get());
+ ASSERT_STREQ("scope 1", data[1].scope().get());
+ ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
+ ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
+}
+
+TEST(ServiceWorkerRegistrar, TestDedupeRead)
+{
+ nsAutoCString buffer("3" "\n");
+
+ // unique entries
+ buffer.Append("^appId=123&inBrowser=1\n");
+ buffer.Append("spec 0\nscope 0\ncurrentWorkerURL 0\ncacheName 0\n");
+ buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.Append("\n");
+ buffer.Append("spec 1\nscope 1\ncurrentWorkerURL 1\ncacheName 1\n");
+ buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ // dupe entries
+ buffer.Append("^appId=123&inBrowser=1\n");
+ buffer.Append("spec 1\nscope 0\ncurrentWorkerURL 0\ncacheName 0\n");
+ buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.Append("^appId=123&inBrowser=1\n");
+ buffer.Append("spec 2\nscope 0\ncurrentWorkerURL 0\ncacheName 0\n");
+ buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.Append("\n");
+ buffer.Append("spec 3\nscope 1\ncurrentWorkerURL 1\ncacheName 1\n");
+ buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ ASSERT_TRUE(CreateFile(buffer)) << "CreateFile should not fail";
+
+ RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+ nsresult rv = swr->TestReadData();
+ ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+ const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
+ ASSERT_EQ((uint32_t)2, data.Length()) << "2 entries should be found";
+
+ const mozilla::ipc::PrincipalInfo& info0 = data[0].principal();
+ ASSERT_EQ(info0.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
+ const mozilla::ipc::ContentPrincipalInfo& cInfo0 = data[0].principal();
+
+ nsAutoCString suffix0;
+ cInfo0.attrs().CreateSuffix(suffix0);
+
+ ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
+ ASSERT_STREQ("scope 0", cInfo0.spec().get());
+ ASSERT_STREQ("scope 0", data[0].scope().get());
+ ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
+ ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+
+ const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
+ ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
+ const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
+
+ nsAutoCString suffix1;
+ cInfo1.attrs().CreateSuffix(suffix1);
+
+ ASSERT_STREQ("", suffix1.get());
+ ASSERT_STREQ("scope 1", cInfo1.spec().get());
+ ASSERT_STREQ("scope 1", data[1].scope().get());
+ ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
+ ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
+}
+
+TEST(ServiceWorkerRegistrar, TestDedupeWrite)
+{
+ {
+ RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+ for (int i = 0; i < 10; ++i) {
+ ServiceWorkerRegistrationData reg;
+
+ reg.scope() = NS_LITERAL_CSTRING("scope write dedupe");
+ reg.currentWorkerURL() = nsPrintfCString("currentWorkerURL write %d", i);
+ reg.cacheName() =
+ NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i));
+
+ nsAutoCString spec;
+ spec.AppendPrintf("spec write dedupe/%d", i);
+ reg.principal() =
+ mozilla::ipc::ContentPrincipalInfo(mozilla::PrincipalOriginAttributes(0, false),
+ mozilla::void_t(), spec);
+
+ swr->TestRegisterServiceWorker(reg);
+ }
+
+ nsresult rv = swr->TestWriteData();
+ ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail";
+ }
+
+ RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+ nsresult rv = swr->TestReadData();
+ ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+ // Duplicate entries should be removed.
+ const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
+ ASSERT_EQ((uint32_t)1, data.Length()) << "1 entry should be found";
+
+ ASSERT_EQ(data[0].principal().type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
+ const mozilla::ipc::ContentPrincipalInfo& cInfo = data[0].principal();
+
+ mozilla::PrincipalOriginAttributes attrs(0, false);
+ nsAutoCString suffix, expectSuffix;
+ attrs.CreateSuffix(expectSuffix);
+ cInfo.attrs().CreateSuffix(suffix);
+
+ // Last entry passed to RegisterServiceWorkerInternal() should overwrite
+ // previous values. So expect "9" in values here.
+ ASSERT_STREQ(expectSuffix.get(), suffix.get());
+ ASSERT_STREQ("scope write dedupe", cInfo.spec().get());
+ ASSERT_STREQ("scope write dedupe", data[0].scope().get());
+ ASSERT_STREQ("currentWorkerURL write 9", data[0].currentWorkerURL().get());
+ ASSERT_STREQ("cacheName write 9",
+ NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ int rv = RUN_ALL_TESTS();
+ return rv;
+}
diff --git a/dom/workers/test/gtest/moz.build b/dom/workers/test/gtest/moz.build
new file mode 100644
index 000000000..5f1f185a9
--- /dev/null
+++ b/dom/workers/test/gtest/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES = [
+ 'TestReadWrite.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul-gtest'
diff --git a/dom/workers/test/head.js b/dom/workers/test/head.js
new file mode 100644
index 000000000..5f0c5c26e
--- /dev/null
+++ b/dom/workers/test/head.js
@@ -0,0 +1,91 @@
+/* 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 = "http://example.com/browser/dom/workers/test/";
+const FRAME_SCRIPT_URL = getRootDirectory(gTestPath) + "frame_script.js";
+
+/**
+ * Add a tab with given `url`, and load a frame script in it. Returns a promise
+ * that will be resolved when the tab finished loading.
+ */
+function addTab(url) {
+ let tab = gBrowser.addTab(TAB_URL);
+ gBrowser.selectedTab = tab;
+ let linkedBrowser = tab.linkedBrowser;
+ linkedBrowser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
+ return new Promise(function (resolve) {
+ linkedBrowser.addEventListener("load", function onload() {
+ linkedBrowser.removeEventListener("load", onload, true);
+ resolve(tab);
+ }, true);
+ });
+}
+
+/**
+ * Remove the given `tab`.
+ */
+function removeTab(tab) {
+ gBrowser.removeTab(tab);
+}
+
+let nextId = 0;
+
+/**
+ * Send a JSON RPC request to the frame script in the given `tab`, invoking the
+ * given `method` with the given `params`. Returns a promise that will be
+ * resolved with the result of the invocation.
+ */
+function jsonrpc(tab, method, params) {
+ let currentId = nextId++;
+ let messageManager = tab.linkedBrowser.messageManager;
+ messageManager.sendAsyncMessage("jsonrpc", {
+ id: currentId,
+ method: method,
+ params: params
+ });
+ return new Promise(function (resolve, reject) {
+ messageManager.addMessageListener("jsonrpc", function listener(event) {
+ let { id, result, error } = event.data;
+ if (id !== currentId) {
+ return;
+ }
+ messageManager.removeMessageListener("jsonrpc", listener);
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(result);
+ });
+ });
+}
+
+/**
+ * Create a worker with the given `url` in the given `tab`.
+ */
+function createWorkerInTab(tab, url) {
+ return jsonrpc(tab, "createWorker", [url]);
+}
+
+/**
+ * Terminate the worker with the given `url` in the given `tab`.
+ */
+function terminateWorkerInTab(tab, url) {
+ return jsonrpc(tab, "terminateWorker", [url]);
+}
+
+/**
+ * Post the given `message` to the worker with the given `url` in the given
+ * `tab`.
+ */
+function postMessageToWorkerInTab(tab, url, message) {
+ return jsonrpc(tab, "postMessageToWorker", [url, message]);
+}
+
+/**
+ * Disable the cache in the given `tab`.
+ */
+function disableCacheInTab(tab) {
+ return jsonrpc(tab, "disableCache", []);
+}
diff --git a/dom/workers/test/importForeignScripts_worker.js b/dom/workers/test/importForeignScripts_worker.js
new file mode 100644
index 000000000..5faa29c31
--- /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(e) {
+ if (e.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 000000000..ebf2d3b14
--- /dev/null
+++ b/dom/workers/test/importScripts_3rdParty_worker.js
@@ -0,0 +1,82 @@
+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 000000000..82933b091
--- /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: 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 000000000..7176ce838
--- /dev/null
+++ b/dom/workers/test/importScripts_worker.js
@@ -0,0 +1,64 @@
+/**
+ * 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/plain,const startResponse = 'started';";
+importScripts(url);
+
+onmessage = function(event) {
+ switch (event.data) {
+ case 'start':
+ importScripts("importScripts_worker_imported2.js");
+ importedScriptFunction2();
+ tryBadScripts();
+ postMessage(startResponse);
+ break;
+ case 'stop':
+ tryBadScripts();
+ postMessage('stopped');
+ break;
+ default:
+ throw new Error("Bad message: " + event.data);
+ break;
+ }
+}
+
+tryBadScripts();
diff --git a/dom/workers/test/importScripts_worker_imported1.js b/dom/workers/test/importScripts_worker_imported1.js
new file mode 100644
index 000000000..9c33588c4
--- /dev/null
+++ b/dom/workers/test/importScripts_worker_imported1.js
@@ -0,0 +1,10 @@
+/**
+ * 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 000000000..3aafb60be
--- /dev/null
+++ b/dom/workers/test/importScripts_worker_imported2.js
@@ -0,0 +1,10 @@
+/**
+ * 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 000000000..c54be3e5f
--- /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 000000000..82f8708c5
--- /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 000000000..a98255388
--- /dev/null
+++ b/dom/workers/test/instanceof_worker.js
@@ -0,0 +1,12 @@
+/**
+ * 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/json_worker.js b/dom/workers/test/json_worker.js
new file mode 100644
index 000000000..f35e14cf2
--- /dev/null
+++ b/dom/workers/test/json_worker.js
@@ -0,0 +1,338 @@
+/**
+ * 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 makeCrazyNested(obj, count) {
+ var innermostobj;
+ for (var i = 0; i < count; i++) {
+ obj.foo = { bar: 5 }
+ innermostobj = obj.foo;
+ obj = innermostobj;
+ }
+ return innermostobj;
+}
+
+var crazyNestedObject = {};
+makeCrazyNested(crazyNestedObject, 100);
+
+var crazyCyclicalObject = {};
+var innermost = makeCrazyNested(crazyCyclicalObject, 1000);
+innermost.baz = crazyCyclicalObject;
+
+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 (var index in typedArrayWithValues) {
+ typedArrayWithValues[index] = index;
+}
+
+var typedArrayWithFunBuffer = new Int8Array(4);
+for (var 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: crazyNestedObject,
+ jsonValue: JSON.stringify(crazyNestedObject)
+ },
+ {
+ type: "object",
+ value: crazyCyclicalObject,
+ },
+ {
+ 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: 238573459843702923492399923049
+ },
+ {
+ type: "number",
+ shouldEqual: true,
+ value: -238573459843702923492399923049
+ },
+ {
+ 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: function (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: function(a) { alert(b); } },
+ exception: true
+ },
+ {
+ type: "object",
+ value: xhr,
+ exception: true
+ },
+ {
+ type: "number",
+ value: xhr.readyState,
+ shouldEqual: true
+ },
+ {
+ type: "object",
+ value: { xhr: xhr },
+ exception: true
+ },
+ {
+ type: "object",
+ value: self,
+ exception: true
+ },
+ {
+ type: "object",
+ value: { p: ArrayBuffer.prototype },
+ exception: true
+ },
+ {
+ type: "string",
+ shouldEqual: true,
+ value: "testFinished"
+ }
+];
+
+for (var 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 (var 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/jsversion_worker.js b/dom/workers/test/jsversion_worker.js
new file mode 100644
index 000000000..c66b72977
--- /dev/null
+++ b/dom/workers/test/jsversion_worker.js
@@ -0,0 +1,14 @@
+onmessage = function(evt) {
+ if (evt.data != 0) {
+ var worker = new Worker('jsversion_worker.js');
+ worker.onmessage = function(evt) {
+ postMessage(evt.data);
+ }
+
+ worker.postMessage(evt.data - 1);
+ return;
+ }
+
+ let foo = 'bar';
+ postMessage(true);
+}
diff --git a/dom/workers/test/loadEncoding_worker.js b/dom/workers/test/loadEncoding_worker.js
new file mode 100644
index 000000000..5e4047844
--- /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 000000000..dd35ec8a3
--- /dev/null
+++ b/dom/workers/test/location_worker.js
@@ -0,0 +1,11 @@
+/**
+ * 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": string, "value": 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 000000000..f132dd8c1
--- /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/mochitest.ini b/dom/workers/test/mochitest.ini
new file mode 100644
index 000000000..d4848819c
--- /dev/null
+++ b/dom/workers/test/mochitest.ini
@@ -0,0 +1,227 @@
+[DEFAULT]
+support-files =
+ WorkerTest_badworker.js
+ atob_worker.js
+ bug978260_worker.js
+ bug1014466_data1.txt
+ bug1014466_data2.txt
+ bug1014466_worker.js
+ bug1020226_worker.js
+ bug1020226_frame.html
+ bug998474_worker.js
+ bug1063538_worker.js
+ clearTimeouts_worker.js
+ content_worker.js
+ console_worker.js
+ consoleReplaceable_worker.js
+ csp_worker.js
+ csp_worker.js^headers^
+ 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
+ jsversion_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_console.js
+ sharedWorker_sharedWorker.js
+ simpleThread_worker.js
+ suspend_iframe.html
+ suspend_worker.js
+ terminate_worker.js
+ test_csp.html^headers^
+ test_csp.js
+ referrer_worker.html
+ threadErrors_worker1.js
+ threadErrors_worker2.js
+ threadErrors_worker3.js
+ threadErrors_worker4.js
+ threadTimeouts_worker.js
+ throwingOnerror_worker.js
+ timeoutTracing_worker.js
+ transferable_worker.js
+ websocket_basic_worker.js
+ websocket_loadgroup_worker.js
+ websocket_worker1.js
+ websocket_worker2.js
+ websocket_worker3.js
+ websocket_worker4.js
+ websocket_worker5.js
+ websocket_helpers.js
+ workersDisabled_worker.js
+ test_worker_interfaces.js
+ worker_driver.js
+ worker_wrapper.js
+ bug1060621_worker.js
+ bug1062920_worker.js
+ webSocket_sharedWorker.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
+ websocket_https.html
+ websocket_https_worker.js
+ worker_fileReader.js
+ fileapi_chromeScript.js
+ importScripts_3rdParty_worker.js
+ worker_bug1278777.js
+ worker_setTimeoutWith0.js
+ worker_bug1301094.js
+ script_createFile.js
+ worker_suspended.js
+ window_suspended.html
+ !/dom/base/test/file_bug945152.jar
+ !/dom/base/test/file_websocket_basic_wsh.py
+ !/dom/base/test/file_websocket_hello_wsh.py
+ !/dom/base/test/file_websocket_http_resource.txt
+ !/dom/base/test/file_websocket_permessage_deflate_disabled_wsh.py
+ !/dom/base/test/file_websocket_permessage_deflate_params_wsh.py
+ !/dom/base/test/file_websocket_permessage_deflate_rejected_wsh.py
+ !/dom/base/test/file_websocket_permessage_deflate_wsh.py
+ !/dom/base/test/file_websocket_wsh.py
+ !/dom/base/test/websocket_helpers.js
+ !/dom/base/test/websocket_tests.js
+ !/dom/tests/mochitest/notification/MockServices.js
+ !/dom/tests/mochitest/notification/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
+
+[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]
+[test_bug1104064.html]
+[test_bug1132395.html]
+skip-if = true # bug 1176225
+[test_bug1132924.html]
+[test_chromeWorker.html]
+[test_clearTimeouts.html]
+[test_console.html]
+[test_consoleAndBlobs.html]
+[test_consoleReplaceable.html]
+[test_consoleSharedWorkers.html]
+[test_contentWorker.html]
+[test_csp.html]
+[test_dataURLWorker.html]
+[test_errorPropagation.html]
+[test_errorwarning.html]
+[test_eventDispatch.html]
+[test_fibonacci.html]
+[test_importScripts.html]
+[test_importScripts_mixedcontent.html]
+tags = mcb
+[test_instanceof.html]
+[test_json.html]
+[test_jsversion.html]
+[test_loadEncoding.html]
+[test_loadError.html]
+[test_location.html]
+[test_longThread.html]
+[test_multi_sharedWorker.html]
+[test_multi_sharedWorker_lifetimes.html]
+[test_navigator.html]
+[test_navigator_languages.html]
+[test_newError.html]
+[test_notification.html]
+[test_notification_child.html]
+[test_notification_permission.html]
+[test_onLine.html]
+[test_promise.html]
+[test_promise_resolved_with_string.html]
+[test_recursion.html]
+[test_recursiveOnerror.html]
+[test_resolveWorker.html]
+[test_resolveWorker-assignment.html]
+[test_rvals.html]
+[test_sharedWorker.html]
+[test_simpleThread.html]
+[test_suspend.html]
+[test_terminate.html]
+[test_threadErrors.html]
+[test_threadTimeouts.html]
+[test_throwingOnerror.html]
+[test_timeoutTracing.html]
+[test_transferable.html]
+[test_websocket1.html]
+skip-if = toolkit == 'android' #bug 982828
+[test_websocket2.html]
+skip-if = toolkit == 'android' #bug 982828
+[test_websocket3.html]
+skip-if = toolkit == 'android' #bug 982828
+[test_websocket4.html]
+skip-if = toolkit == 'android' #bug 982828
+[test_websocket5.html]
+skip-if = toolkit == 'android' #bug 982828
+[test_websocket_basic.html]
+skip-if = toolkit == 'android' #bug 982828
+[test_websocket_https.html]
+[test_websocket_loadgroup.html]
+skip-if = toolkit == 'android' #bug 982828
+[test_webSocket_sharedWorker.html]
+skip-if = toolkit == 'android' #bug 982828
+[test_worker_interfaces.html]
+[test_workersDisabled.html]
+[test_referrer.html]
+[test_referrer_header_worker.html]
+[test_importScripts_3rdparty.html]
+[test_sharedWorker_ports.html]
+[test_sharedWorker_lifetime.html]
+[test_fileReader.html]
+[test_navigator_workers_hardwareConcurrency.html]
+[test_bug1278777.html]
+[test_setTimeoutWith0.html]
+[test_bug1301094.html]
+[test_subworkers_suspended.html]
+[test_bug1317725.html]
diff --git a/dom/workers/test/multi_sharedWorker_frame.html b/dom/workers/test/multi_sharedWorker_frame.html
new file mode 100644
index 000000000..6c0dfe591
--- /dev/null
+++ b/dom/workers/test/multi_sharedWorker_frame.html
@@ -0,0 +1,52 @@
+<!--
+ 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;version=1.7">
+ "use strict";
+
+ function debug(message) {
+ if (typeof(message) != "string") {
+ throw new Error("debug() only accepts strings!");
+ }
+ parent.postMessage(message, "*");
+ }
+
+ let worker;
+
+ window.addEventListener("message", function(event) {
+ if (!worker) {
+ worker = new SharedWorker("multi_sharedWorker_sharedWorker.js",
+ "FrameWorker");
+ worker.onerror = function(event) {
+ debug("Worker error: " + event.message);
+ event.preventDefault();
+
+ let data = {
+ type: "error",
+ message: event.message,
+ filename: event.filename,
+ lineno: event.lineno,
+ isErrorEvent: event instanceof ErrorEvent
+ };
+ parent.postMessage(data, "*");
+ };
+
+ worker.port.onmessage = function(event) {
+ debug("Worker message: " + JSON.stringify(event.data));
+ parent.postMessage(event.data, "*");
+ };
+ }
+
+ debug("Posting message: " + JSON.stringify(event.data));
+ worker.port.postMessage(event.data);
+ });
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/multi_sharedWorker_sharedWorker.js b/dom/workers/test/multi_sharedWorker_sharedWorker.js
new file mode 100644
index 000000000..47a7ae04f
--- /dev/null
+++ b/dom/workers/test/multi_sharedWorker_sharedWorker.js
@@ -0,0 +1,72 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+if (self.name != "FrameWorker") {
+ throw new Error("Bad worker name: " + self.name);
+}
+
+var registeredPorts = [];
+var errorCount = 0;
+var storedData;
+
+self.onconnect = function(event) {
+ var port = event.ports[0];
+
+ if (registeredPorts.length) {
+ var data = {
+ type: "connect"
+ };
+
+ registeredPorts.forEach(function(registeredPort) {
+ registeredPort.postMessage(data);
+ });
+ }
+
+ port.onmessage = function(event) {
+ switch (event.data.command) {
+ case "start":
+ break;
+
+ case "error":
+ throw new Error("Expected");
+
+ case "store":
+ storedData = event.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: message,
+ filename: filename,
+ lineno: lineno
+ };
+
+ registeredPorts.forEach(function (registeredPort) {
+ registeredPort.postMessage(data);
+ });
+
+ // Prevent the error from propagating the first time only.
+ return true;
+ }
+};
diff --git a/dom/workers/test/navigator_languages_worker.js b/dom/workers/test/navigator_languages_worker.js
new file mode 100644
index 000000000..53aa4d39e
--- /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 000000000..63853aef1
--- /dev/null
+++ b/dom/workers/test/navigator_worker.js
@@ -0,0 +1,79 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// IMPORTANT: Do not change the list below without review from a DOM peer!
+var supportedProps = [
+ "appCodeName",
+ "appName",
+ "appVersion",
+ "platform",
+ "product",
+ "userAgent",
+ "onLine",
+ "language",
+ "languages",
+ "hardwareConcurrency",
+ { name: "storage", nightly: true },
+];
+
+self.onmessage = function(event) {
+ if (!event || !event.data) {
+ return;
+ }
+
+ startTest(event.data);
+};
+
+function startTest(channelData) {
+ // Prepare the interface map showing if a propery should exist in this build.
+ // For example, if interfaceMap[foo] = true means navigator.foo should exist.
+ var interfaceMap = {};
+
+ for (var prop of supportedProps) {
+ if (typeof(prop) === "string") {
+ interfaceMap[prop] = true;
+ continue;
+ }
+
+ if (prop.nightly === !channelData.isNightly ||
+ prop.release === !channelData.isRelease) {
+ 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 000000000..46e6226f7
--- /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 000000000..f69acaeda
--- /dev/null
+++ b/dom/workers/test/notification_permission_worker.js
@@ -0,0 +1,56 @@
+function info(message) {
+ dump("INFO: " + message + "\n");
+}
+
+function ok(test, message) {
+ postMessage({ type: 'ok', test: test, message: message });
+}
+
+function is(a, b, message) {
+ postMessage({ type: 'is', test1: a, test2: b, message: 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 (e) {
+ ok(false, "Test threw exception! " + nextTest + " " + e);
+ 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 000000000..cb55f8358
--- /dev/null
+++ b/dom/workers/test/notification_worker.js
@@ -0,0 +1,93 @@
+function ok(test, message) {
+ postMessage({ type: 'ok', test: test, message: message });
+}
+
+function is(a, b, message) {
+ postMessage({ type: 'is', test1: a, test2: b, message: 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 (e) {
+ ok(false, "Test threw exception! " + nextTest + " " + e);
+ 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 000000000..829bf43d3
--- /dev/null
+++ b/dom/workers/test/notification_worker_child-child.js
@@ -0,0 +1,92 @@
+function ok(test, message) {
+ postMessage({ type: 'ok', test: test, message: message });
+}
+
+function is(a, b, message) {
+ postMessage({ type: 'is', test1: a, test2: b, message: 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 (e) {
+ ok(false, "Test threw exception! " + nextTest + " " + e);
+ 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 000000000..bb28c520d
--- /dev/null
+++ b/dom/workers/test/notification_worker_child-parent.js
@@ -0,0 +1,26 @@
+function ok(test, message) {
+ postMessage({ type: 'ok', test: test, message: message });
+}
+
+function is(a, b, message) {
+ postMessage({ type: 'is', test1: a, test2: b, message: 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 000000000..5a302b15e
--- /dev/null
+++ b/dom/workers/test/onLine_worker.js
@@ -0,0 +1,65 @@
+/*
+ * 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 000000000..dc28fac45
--- /dev/null
+++ b/dom/workers/test/onLine_worker_child.js
@@ -0,0 +1,75 @@
+/*
+ * 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: test, message: 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 000000000..4800457d3
--- /dev/null
+++ b/dom/workers/test/onLine_worker_head.js
@@ -0,0 +1,43 @@
+/*
+ * 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: test, message: 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 000000000..5b0a8478b
--- /dev/null
+++ b/dom/workers/test/promise_worker.js
@@ -0,0 +1,856 @@
+function ok(a, msg) {
+ dump("OK: " + !!a + " => " + a + " " + msg + "\n");
+ postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
+}
+
+function todo(a, msg) {
+ dump("TODO: " + !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(resolve, reject) {
+ ok(true, "Nested promise is executed");
+ resolve(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(resolve, reject) {
+ ok(true, "Nested promise is executed");
+ resolve(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(r1, r2) {
+ ok(true, "Nested nested promise is executed");
+ r1(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 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 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: function(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 000000000..1a61ec9d1
--- /dev/null
+++ b/dom/workers/test/recursion_worker.js
@@ -0,0 +1,46 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This function should never run on a too much recursion error.
+onerror = function(event) {
+ postMessage(event.message);
+};
+
+// Pure JS recursion
+function recurse() {
+ recurse();
+}
+
+// JS -> C++ -> JS -> C++ recursion
+function recurse2() {
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ xhr.open("GET", "nonexistent.file");
+ }
+ xhr.open("GET", "nonexistent.file");
+}
+
+var messageCount = 0;
+onmessage = function(event) {
+ switch (++messageCount) {
+ case 2:
+ recurse2();
+
+ // An exception thrown from an event handler like xhr.onreadystatechange
+ // should not leave an exception pending in the code that generated the
+ // event.
+ postMessage("Done");
+ return;
+
+ case 1:
+ recurse();
+ throw "Exception should have prevented us from getting here!";
+
+ default:
+ throw "Weird number of messages: " + messageCount;
+ }
+
+ throw "Impossible to get here!";
+}
diff --git a/dom/workers/test/recursiveOnerror_worker.js b/dom/workers/test/recursiveOnerror_worker.js
new file mode 100644
index 000000000..e6656d68f
--- /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 000000000..1a5e24a59
--- /dev/null
+++ b/dom/workers/test/redirect_to_foreign.sjs
@@ -0,0 +1,4 @@
+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 000000000..dcfd1fe37
--- /dev/null
+++ b/dom/workers/test/referrer.sjs
@@ -0,0 +1,15 @@
+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.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 000000000..550fefb8a
--- /dev/null
+++ b/dom/workers/test/referrer_test_server.sjs
@@ -0,0 +1,101 @@
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+const SJS = "referrer_test_server.sjs?";
+const SHARED_KEY = SJS;
+
+var SAME_ORIGIN = "https://example.com/tests/dom/workers/test/" + SJS;
+var CROSS_ORIGIN = "https://test2.example.com/tests/dom/workers/test/" + SJS;
+var DOWNGRADE = "http://example.com/tests/dom/workers/test/" + SJS;
+
+function createUrl(aRequestType, aPolicy) {
+ var searchParams = new URLSearchParams();
+ searchParams.append("ACTION", "request-worker");
+ searchParams.append("Referrer-Policy", aPolicy);
+ searchParams.append("TYPE", aRequestType);
+
+ var url = SAME_ORIGIN;
+
+ if (aRequestType === "cross-origin") {
+ url = CROSS_ORIGIN;
+ } else if (aRequestType === "downgrade") {
+ url = DOWNGRADE;
+ }
+
+ return url + searchParams.toString();
+}
+function createWorker (aRequestType, aPolicy) {
+ return `
+ onmessage = function() {
+ fetch("${createUrl(aRequestType, aPolicy)}").then(function () {
+ postMessage(42);
+ close();
+ });
+ }
+ `;
+}
+
+function handleRequest(request, response) {
+ var params = new URLSearchParams(request.queryString);
+ var policy = params.get("Referrer-Policy");
+ var type = params.get("TYPE");
+ var action = params.get("ACTION");
+ response.setHeader("Content-Security-Policy", "default-src *", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+
+ if (policy) {
+ response.setHeader("Referrer-Policy", policy, false);
+ }
+
+ if (action === "test") {
+ response.setHeader("Content-Type", "text/javascript", false);
+ response.write(createWorker(type, policy));
+ return;
+ }
+
+ if (action === "resetState") {
+ setSharedState(SHARED_KEY, "{}");
+ response.write("");
+ return;
+ }
+
+ if (action === "get-test-results") {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(getSharedState(SHARED_KEY));
+ return;
+ }
+
+ if (action === "request-worker") {
+ var result = getSharedState(SHARED_KEY);
+ result = result ? JSON.parse(result) : {};
+ var referrerLevel = "none";
+ var test = {};
+
+ if (request.hasHeader("Referer")) {
+ var referrer = request.getHeader("Referer");
+ if (referrer.indexOf("referrer_test_server") > 0) {
+ referrerLevel = "full";
+ } else if (referrer.indexOf("https://example.com") == 0) {
+ referrerLevel = "origin";
+ } else {
+ // this is never supposed to happen
+ referrerLevel = "other-origin";
+ }
+ test.referrer = referrer;
+ } else {
+ test.referrer = "";
+ }
+
+ test.policy = referrerLevel;
+ test.expected = policy;
+
+ // test id equals type + "-" + policy
+ // Ex: same-origin-default
+ result[type + "-" + policy] = test;
+ setSharedState(SHARED_KEY, JSON.stringify(result));
+
+ response.write("'hello world'");
+ return;
+ }
+}
+
+
diff --git a/dom/workers/test/referrer_worker.html b/dom/workers/test/referrer_worker.html
new file mode 100644
index 000000000..5693fc340
--- /dev/null
+++ b/dom/workers/test/referrer_worker.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onload="tests.next();">
+<script type="text/javascript;version=1.7">
+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.close();
+ 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' : 'full',
+ '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' : 'full' }},
+
+ // 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]));
+ }
+ }
+
+ // complete. Be sure to yield so we don't call this twice.
+ yield 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 000000000..51f9ae723
--- /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 000000000..aee7df10e
--- /dev/null
+++ b/dom/workers/test/script_createFile.js
@@ -0,0 +1,15 @@
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.importGlobalProperties(["File", "Directory"]);
+
+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(Components.interfaces.nsIFile.FILE_TYPE, 0o600);
+
+ sendAsyncMessage("file.opened", {
+ data: File.createFromNsIFile(tmpFile)
+ });
+});
diff --git a/dom/workers/test/serviceworkers/activate_event_error_worker.js b/dom/workers/test/serviceworkers/activate_event_error_worker.js
new file mode 100644
index 000000000..a5f159d35
--- /dev/null
+++ b/dom/workers/test/serviceworkers/activate_event_error_worker.js
@@ -0,0 +1,4 @@
+// Worker that errors on receiving an activate event.
+onactivate = function(e) {
+ undefined.doSomething;
+}
diff --git a/dom/workers/test/serviceworkers/blocking_install_event_worker.js b/dom/workers/test/serviceworkers/blocking_install_event_worker.js
new file mode 100644
index 000000000..0bc6f7b7f
--- /dev/null
+++ b/dom/workers/test/serviceworkers/blocking_install_event_worker.js
@@ -0,0 +1,23 @@
+function postMessageToTest(msg) {
+ return clients.matchAll({ includeUncontrolled: true })
+ .then(list => {
+ for (var client of list) {
+ if (client.url.endsWith('test_install_event_gc.html')) {
+ client.postMessage(msg);
+ break;
+ }
+ }
+ });
+}
+
+addEventListener('install', evt => {
+ // This must be a simple promise to trigger the CC failure.
+ evt.waitUntil(new Promise(function() { }));
+ postMessageToTest({ type: 'INSTALL_EVENT' });
+});
+
+addEventListener('message', evt => {
+ if (evt.data.type === 'ping') {
+ postMessageToTest({ type: 'pong' });
+ }
+});
diff --git a/dom/workers/test/serviceworkers/browser.ini b/dom/workers/test/serviceworkers/browser.ini
new file mode 100644
index 000000000..c0aae30d6
--- /dev/null
+++ b/dom/workers/test/serviceworkers/browser.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+support-files =
+ browser_base_force_refresh.html
+ browser_cached_force_refresh.html
+ download/window.html
+ download/worker.js
+ force_refresh_browser_worker.js
+
+[browser_force_refresh.js]
+[browser_download.js]
diff --git a/dom/workers/test/serviceworkers/browser_base_force_refresh.html b/dom/workers/test/serviceworkers/browser_base_force_refresh.html
new file mode 100644
index 000000000..1b0d2defe
--- /dev/null
+++ b/dom/workers/test/serviceworkers/browser_base_force_refresh.html
@@ -0,0 +1,30 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+</head>
+<body>
+<script type="text/javascript">
+addEventListener('load', function(event) {
+ navigator.serviceWorker.register('force_refresh_browser_worker.js').then(function(swr) {
+ if (!swr) {
+ return;
+ }
+ var custom = new Event('base-register', { bubbles: true });
+ document.dispatchEvent(custom);
+ });
+
+ navigator.serviceWorker.ready.then(function() {
+ var custom = new Event('base-sw-ready', { bubbles: true });
+ document.dispatchEvent(custom);
+ });
+
+ var custom = new Event('base-load', { bubbles: true });
+ document.dispatchEvent(custom);
+});
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/browser_cached_force_refresh.html b/dom/workers/test/serviceworkers/browser_cached_force_refresh.html
new file mode 100644
index 000000000..33bd8cdaa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/browser_cached_force_refresh.html
@@ -0,0 +1,64 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+</head>
+<body>
+<script type="text/javascript">
+function ok(exp, msg) {
+ if (!exp) {
+ throw(msg);
+ }
+}
+
+function is(actual, expected, msg) {
+ if (actual !== expected) {
+ throw('got "' + actual + '", but expected "' + expected + '" - ' + msg);
+ }
+}
+
+function fail(err) {
+ var custom = new CustomEvent('cached-failure', {
+ bubbles: true,
+ detail: err
+ });
+ document.dispatchEvent(custom);
+}
+
+function getUncontrolledClients(sw) {
+ return new Promise(function(resolve, reject) {
+ navigator.serviceWorker.addEventListener('message', function onMsg(evt) {
+ if (evt.data.type === 'CLIENTS') {
+ navigator.serviceWorker.removeEventListener('message', onMsg);
+ resolve(evt.data.detail);
+ }
+ });
+ sw.postMessage({ type: 'GET_UNCONTROLLED_CLIENTS' })
+ });
+}
+
+addEventListener('load', function(event) {
+ if (!navigator.serviceWorker.controller) {
+ return fail(window.location.href + ' is not controlled!');
+ }
+
+ getUncontrolledClients(navigator.serviceWorker.controller)
+ .then(function(clientList) {
+ is(clientList.length, 1, 'should only have one client');
+ is(clientList[0].url, window.location.href,
+ 'client url should match current window');
+ is(clientList[0].frameType, 'top-level',
+ 'client should be a top-level window');
+ var custom = new Event('cached-load', { bubbles: true });
+ document.dispatchEvent(custom);
+ })
+ .catch(function(err) {
+ fail(err);
+ });
+});
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/browser_download.js b/dom/workers/test/serviceworkers/browser_download.js
new file mode 100644
index 000000000..bd4da10c9
--- /dev/null
+++ b/dom/workers/test/serviceworkers/browser_download.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import('resource://gre/modules/Services.jsm');
+var Downloads = Cu.import("resource://gre/modules/Downloads.jsm", {}).Downloads;
+var DownloadsCommon = Cu.import("resource:///modules/DownloadsCommon.jsm", {}).DownloadsCommon;
+Cu.import('resource://gre/modules/NetUtil.jsm');
+
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/",
+ "http://mochi.test:8888/")
+
+function getFile(aFilename) {
+ if (aFilename.startsWith('file:')) {
+ var url = NetUtil.newURI(aFilename).QueryInterface(Ci.nsIFileURL);
+ return url.file.clone();
+ }
+
+ var file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
+ file.initWithPath(aFilename);
+ return file;
+}
+
+function windowObserver(win, topic) {
+ if (topic !== 'domwindowopened') {
+ return;
+ }
+
+ win.addEventListener('load', function onLoadWindow() {
+ win.removeEventListener('load', onLoadWindow, false);
+ if (win.document.documentURI ===
+ 'chrome://mozapps/content/downloads/unknownContentType.xul') {
+ executeSoon(function() {
+ var button = win.document.documentElement.getButton('accept');
+ button.disabled = false;
+ win.document.documentElement.acceptDialog();
+ });
+ }
+ }, false);
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Services.ww.registerNotification(windowObserver);
+
+ SpecialPowers.pushPrefEnv({'set': [['dom.serviceWorkers.enabled', true],
+ ['dom.serviceWorkers.exemptFromPerDomainMax', true],
+ ['dom.serviceWorkers.testing.enabled', true]]},
+ function() {
+ var url = gTestRoot + 'download/window.html';
+ var tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+
+ Downloads.getList(Downloads.ALL).then(function(downloadList) {
+ var downloadListener;
+
+ function downloadVerifier(aDownload) {
+ if (aDownload.succeeded) {
+ var file = getFile(aDownload.target.path);
+ ok(file.exists(), 'download completed');
+ is(file.fileSize, 33, 'downloaded file has correct size');
+ file.remove(false);
+ DownloadsCommon.removeAndFinalizeDownload(aDownload);
+
+ downloadList.removeView(downloadListener);
+ gBrowser.removeTab(tab);
+ Services.ww.unregisterNotification(windowObserver);
+
+ executeSoon(finish);
+ }
+ }
+
+ downloadListener = {
+ onDownloadAdded: downloadVerifier,
+ onDownloadChanged: downloadVerifier
+ };
+
+ return downloadList.addView(downloadListener);
+ }).then(function() {
+ gBrowser.loadURI(url);
+ });
+ });
+}
diff --git a/dom/workers/test/serviceworkers/browser_force_refresh.js b/dom/workers/test/serviceworkers/browser_force_refresh.js
new file mode 100644
index 000000000..a2c9c871c
--- /dev/null
+++ b/dom/workers/test/serviceworkers/browser_force_refresh.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/",
+ "http://mochi.test:8888/")
+
+function refresh() {
+ EventUtils.synthesizeKey('R', { accelKey: true });
+}
+
+function forceRefresh() {
+ EventUtils.synthesizeKey('R', { accelKey: true, shiftKey: true });
+}
+
+function frameScript() {
+ function eventHandler(event) {
+ sendAsyncMessage("test:event", {type: event.type, detail: event.detail});
+ }
+
+ // These are tab-local, so no need to unregister them.
+ addEventListener('base-load', eventHandler, true, true);
+ addEventListener('base-register', eventHandler, true, true);
+ addEventListener('base-sw-ready', eventHandler, true, true);
+ addEventListener('cached-load', eventHandler, true, true);
+ addEventListener('cached-failure', eventHandler, true, true);
+}
+
+function test() {
+ waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({'set': [['dom.serviceWorkers.enabled', true],
+ ['dom.serviceWorkers.exemptFromPerDomainMax', true],
+ ['dom.serviceWorkers.testing.enabled', true],
+ ['dom.caches.enabled', true],
+ ['browser.cache.disk.enable', false],
+ ['browser.cache.memory.enable', false]]},
+ function() {
+ var url = gTestRoot + 'browser_base_force_refresh.html';
+ var tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+
+ tab.linkedBrowser.messageManager.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true);
+ gBrowser.loadURI(url);
+
+ function done() {
+ tab.linkedBrowser.messageManager.removeMessageListener("test:event", eventHandler);
+
+ gBrowser.removeTab(tab);
+ executeSoon(finish);
+ }
+
+ var maxCacheLoadCount = 3;
+ var cachedLoadCount = 0;
+ var baseLoadCount = 0;
+
+ function eventHandler(msg) {
+ if (msg.data.type === 'base-load') {
+ baseLoadCount += 1;
+ if (cachedLoadCount === maxCacheLoadCount) {
+ is(baseLoadCount, 2, 'cached load should occur before second base load');
+ return done();
+ }
+ if (baseLoadCount !== 1) {
+ ok(false, 'base load without cached load should only occur once');
+ return done();
+ }
+ } else if (msg.data.type === 'base-register') {
+ ok(!cachedLoadCount, 'cached load should not occur before base register');
+ is(baseLoadCount, 1, 'register should occur after first base load');
+ } else if (msg.data.type === 'base-sw-ready') {
+ ok(!cachedLoadCount, 'cached load should not occur before base ready');
+ is(baseLoadCount, 1, 'ready should occur after first base load');
+ refresh();
+ } else if (msg.data.type === 'cached-load') {
+ ok(cachedLoadCount < maxCacheLoadCount, 'cached load should not occur too many times');
+ is(baseLoadCount, 1, 'cache load occur after first base load');
+ cachedLoadCount += 1;
+ if (cachedLoadCount < maxCacheLoadCount) {
+ return refresh();
+ }
+ forceRefresh();
+ } else if (msg.data.type === 'cached-failure') {
+ ok(false, 'failure: ' + msg.data.detail);
+ done();
+ }
+
+ return;
+ }
+
+ tab.linkedBrowser.messageManager.addMessageListener("test:event", eventHandler);
+ });
+}
diff --git a/dom/workers/test/serviceworkers/bug1151916_driver.html b/dom/workers/test/serviceworkers/bug1151916_driver.html
new file mode 100644
index 000000000..e540ad239
--- /dev/null
+++ b/dom/workers/test/serviceworkers/bug1151916_driver.html
@@ -0,0 +1,53 @@
+<html>
+ <body>
+ <script language="javascript">
+ function fail(msg) {
+ window.parent.postMessage({ status: "failed", message: msg }, "*");
+ }
+
+ function success(msg) {
+ window.parent.postMessage({ status: "success", message: msg }, "*");
+ }
+
+ if (!window.parent) {
+ dump("This file must be embedded in an iframe!");
+ }
+
+ navigator.serviceWorker.getRegistration()
+ .then(function(reg) {
+ if (!reg) {
+ navigator.serviceWorker.ready.then(function(reg) {
+ if (reg.active.state == "activating") {
+ reg.active.onstatechange = function(e) {
+ reg.active.onstatechange = null;
+ if (reg.active.state == "activated") {
+ success("Registered and activated");
+ }
+ }
+ } else {
+ success("Registered and activated");
+ }
+ });
+ navigator.serviceWorker.register("bug1151916_worker.js",
+ { scope: "." });
+ } else {
+ // Simply force the sw to load a resource and touch self.caches.
+ if (!reg.active) {
+ fail("no-active-worker");
+ return;
+ }
+
+ fetch("madeup.txt").then(function(res) {
+ res.text().then(function(v) {
+ if (v == "Hi there") {
+ success("Loaded from cache");
+ } else {
+ fail("Response text did not match");
+ }
+ }, fail);
+ }, fail);
+ }
+ }, fail);
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/serviceworkers/bug1151916_worker.js b/dom/workers/test/serviceworkers/bug1151916_worker.js
new file mode 100644
index 000000000..06585e8e7
--- /dev/null
+++ b/dom/workers/test/serviceworkers/bug1151916_worker.js
@@ -0,0 +1,13 @@
+onactivate = function(e) {
+ e.waitUntil(self.caches.open("default-cache").then(function(cache) {
+ var response = new Response("Hi there");
+ return cache.put("madeup.txt", response);
+ }));
+}
+
+onfetch = function(e) {
+ if (e.request.url.match(/madeup.txt$/)) {
+ var p = self.caches.match("madeup.txt", { cacheName: "default-cache" });
+ e.respondWith(p);
+ }
+}
diff --git a/dom/workers/test/serviceworkers/bug1240436_worker.js b/dom/workers/test/serviceworkers/bug1240436_worker.js
new file mode 100644
index 000000000..5a588aedf
--- /dev/null
+++ b/dom/workers/test/serviceworkers/bug1240436_worker.js
@@ -0,0 +1,2 @@
+// a contains a ZERO WIDTH JOINER (0x200D)
+var a = "â€"; \ No newline at end of file
diff --git a/dom/workers/test/serviceworkers/chrome.ini b/dom/workers/test/serviceworkers/chrome.ini
new file mode 100644
index 000000000..e064e7fd0
--- /dev/null
+++ b/dom/workers/test/serviceworkers/chrome.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ chrome_helpers.js
+ empty.js
+ serviceworker.html
+ serviceworkerinfo_iframe.html
+ serviceworkermanager_iframe.html
+ serviceworkerregistrationinfo_iframe.html
+ worker.js
+ worker2.js
+
+[test_privateBrowsing.html]
+[test_serviceworkerinfo.xul]
+[test_serviceworkermanager.xul]
+[test_serviceworkerregistrationinfo.xul]
diff --git a/dom/workers/test/serviceworkers/chrome_helpers.js b/dom/workers/test/serviceworkers/chrome_helpers.js
new file mode 100644
index 000000000..a438333e2
--- /dev/null
+++ b/dom/workers/test/serviceworkers/chrome_helpers.js
@@ -0,0 +1,74 @@
+let { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Task.jsm");
+
+let swm = Cc["@mozilla.org/serviceworkers/manager;1"].
+ getService(Ci.nsIServiceWorkerManager);
+
+let EXAMPLE_URL = "https://example.com/chrome/dom/workers/test/serviceworkers/";
+
+function waitForIframeLoad(iframe) {
+ return new Promise(function (resolve) {
+ iframe.onload = resolve;
+ });
+}
+
+function waitForRegister(scope, callback) {
+ return new Promise(function (resolve) {
+ let listener = {
+ onRegister: function (registration) {
+ if (registration.scope !== scope) {
+ return;
+ }
+ swm.removeListener(listener);
+ resolve(callback ? callback(registration) : registration);
+ }
+ };
+ swm.addListener(listener);
+ });
+}
+
+function waitForUnregister(scope) {
+ return new Promise(function (resolve) {
+ let listener = {
+ onUnregister: function (registration) {
+ if (registration.scope !== scope) {
+ return;
+ }
+ swm.removeListener(listener);
+ resolve(registration);
+ }
+ };
+ swm.addListener(listener);
+ });
+}
+
+function waitForServiceWorkerRegistrationChange(registration, callback) {
+ return new Promise(function (resolve) {
+ let listener = {
+ onChange: function () {
+ registration.removeListener(listener);
+ if (callback) {
+ callback();
+ }
+ resolve(callback ? callback() : undefined);
+ }
+ };
+ registration.addListener(listener);
+ });
+}
+
+function waitForServiceWorkerShutdown() {
+ return new Promise(function (resolve) {
+ let observer = {
+ observe: function (subject, topic, data) {
+ if (topic !== "service-worker-shutdown") {
+ return;
+ }
+ SpecialPowers.removeObserver(observer, "service-worker-shutdown");
+ resolve();
+ }
+ };
+ SpecialPowers.addObserver(observer, "service-worker-shutdown", false);
+ });
+}
diff --git a/dom/workers/test/serviceworkers/claim_clients/client.html b/dom/workers/test/serviceworkers/claim_clients/client.html
new file mode 100644
index 000000000..eecfb294e
--- /dev/null
+++ b/dom/workers/test/serviceworkers/claim_clients/client.html
@@ -0,0 +1,44 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1130684 - claim client </title>
+ <script type="text/javascript" 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">
+
+ if (!parent) {
+ info("This page shouldn't be launched directly!");
+ }
+
+ window.onload = function() {
+ parent.postMessage("READY", "*");
+ }
+
+ navigator.serviceWorker.oncontrollerchange = function() {
+ parent.postMessage({
+ event: "controllerchange",
+ controller: (navigator.serviceWorker.controller !== null)
+ }, "*");
+ }
+
+ navigator.serviceWorker.onmessage = function(e) {
+ parent.postMessage({
+ event: "message",
+ data: e.data
+ }, "*");
+ }
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/claim_fetch_worker.js b/dom/workers/test/serviceworkers/claim_fetch_worker.js
new file mode 100644
index 000000000..ea62c37b8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/claim_fetch_worker.js
@@ -0,0 +1,12 @@
+onfetch = function(e) {
+ if (e.request.url.indexOf("service_worker_controlled") >= 0) {
+ // pass through
+ e.respondWith(fetch(e.request));
+ } else {
+ e.respondWith(new Response("Fetch was intercepted"));
+ }
+}
+
+onmessage = function(e) {
+ clients.claim();
+}
diff --git a/dom/workers/test/serviceworkers/claim_oninstall_worker.js b/dom/workers/test/serviceworkers/claim_oninstall_worker.js
new file mode 100644
index 000000000..269afa721
--- /dev/null
+++ b/dom/workers/test/serviceworkers/claim_oninstall_worker.js
@@ -0,0 +1,7 @@
+oninstall = function(e) {
+ var claimFailedPromise = new Promise(function(resolve, reject) {
+ clients.claim().then(reject, () => resolve());
+ });
+
+ e.waitUntil(claimFailedPromise);
+}
diff --git a/dom/workers/test/serviceworkers/claim_worker_1.js b/dom/workers/test/serviceworkers/claim_worker_1.js
new file mode 100644
index 000000000..e5f6392d3
--- /dev/null
+++ b/dom/workers/test/serviceworkers/claim_worker_1.js
@@ -0,0 +1,28 @@
+onactivate = function(e) {
+ var result = {
+ resolve_value: false,
+ match_count_before: -1,
+ match_count_after: -1,
+ message: "claim_worker_1"
+ };
+
+ self.clients.matchAll().then(function(matched) {
+ // should be 0
+ result.match_count_before = matched.length;
+ }).then(function() {
+ var claimPromise = self.clients.claim().then(function(ret) {
+ result.resolve_value = ret;
+ });
+
+ return claimPromise.then(self.clients.matchAll().then(function(matched) {
+ // should be 2
+ result.match_count_after = matched.length;
+ for (i = 0; i < matched.length; i++) {
+ matched[i].postMessage(result);
+ }
+ if (result.match_count_after !== 2) {
+ dump("ERROR: claim_worker_1 failed to capture clients.\n");
+ }
+ }));
+ });
+}
diff --git a/dom/workers/test/serviceworkers/claim_worker_2.js b/dom/workers/test/serviceworkers/claim_worker_2.js
new file mode 100644
index 000000000..be8281d34
--- /dev/null
+++ b/dom/workers/test/serviceworkers/claim_worker_2.js
@@ -0,0 +1,27 @@
+onactivate = function(e) {
+ var result = {
+ resolve_value: false,
+ match_count_before: -1,
+ match_count_after: -1,
+ message: "claim_worker_2"
+ };
+
+ self.clients.matchAll().then(function(matched) {
+ // should be 0
+ result.match_count_before = matched.length;
+ }).then(function() {
+ var claimPromise = self.clients.claim().then(function(ret) {
+ result.resolve_value = ret;
+ });
+
+ return claimPromise.then(self.clients.matchAll().then(function(matched) {
+ // should be 1
+ result.match_count_after = matched.length;
+ if (result.match_count_after === 1) {
+ matched[0].postMessage(result);
+ } else {
+ dump("ERROR: claim_worker_2 failed to capture clients.\n");
+ }
+ }));
+ });
+}
diff --git a/dom/workers/test/serviceworkers/close_test.js b/dom/workers/test/serviceworkers/close_test.js
new file mode 100644
index 000000000..6138d6421
--- /dev/null
+++ b/dom/workers/test/serviceworkers/close_test.js
@@ -0,0 +1,19 @@
+function ok(v, msg) {
+ client.postMessage({status: "ok", result: !!v, message: msg});
+}
+
+var client;
+onmessage = function(e) {
+ if (e.data.message == "start") {
+ self.clients.matchAll().then(function(clients) {
+ client = clients[0];
+ try {
+ close();
+ ok(false, "close() should throw");
+ } catch (e) {
+ ok(e.name === "InvalidAccessError", "close() should throw InvalidAccessError");
+ }
+ client.postMessage({status: "done"});
+ });
+ }
+}
diff --git a/dom/workers/test/serviceworkers/controller/index.html b/dom/workers/test/serviceworkers/controller/index.html
new file mode 100644
index 000000000..740e24f15
--- /dev/null
+++ b/dom/workers/test/serviceworkers/controller/index.html
@@ -0,0 +1,74 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 94048 - test install event.</title>
+ <script type="text/javascript" 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">
+
+ // Make sure to use good, unique messages, since the actual expression will not show up in test results.
+ function my_ok(result, msg) {
+ parent.postMessage({status: "ok", result: result, message: msg}, "*");
+ }
+
+ function finish() {
+ parent.postMessage({status: "done"}, "*");
+ }
+
+ navigator.serviceWorker.ready.then(function(swr) {
+ my_ok(swr.scope.match(/serviceworkers\/control$/),
+ "This page should be controlled by upper level registration");
+ my_ok(swr.installing == undefined,
+ "Upper level registration should not have a installing worker.");
+ if (navigator.serviceWorker.controller) {
+ // We are controlled.
+ // Register a new worker for this sub-scope. After that, controller should still be for upper level, but active should change to be this scope's.
+ navigator.serviceWorker.register("../worker2.js", { scope: "./" }).then(function(e) {
+ my_ok("installing" in e, "ServiceWorkerRegistration.installing exists.");
+ my_ok(e.installing instanceof ServiceWorker, "ServiceWorkerRegistration.installing is a ServiceWorker.");
+
+ my_ok("waiting" in e, "ServiceWorkerRegistration.waiting exists.");
+ my_ok("active" in e, "ServiceWorkerRegistration.active exists.");
+
+ my_ok(e.installing &&
+ e.installing.scriptURL.match(/worker2.js$/),
+ "Installing is serviceworker/controller");
+
+ my_ok("scope" in e, "ServiceWorkerRegistration.scope exists.");
+ my_ok(e.scope.match(/serviceworkers\/controller\/$/), "Scope is serviceworker/controller " + e.scope);
+
+ my_ok("unregister" in e, "ServiceWorkerRegistration.unregister exists.");
+
+ my_ok(navigator.serviceWorker.controller.scriptURL.match(/worker\.js$/),
+ "Controller is still worker.js");
+
+ e.unregister().then(function(result) {
+ my_ok(result, "Unregistering the SW should succeed");
+ finish();
+ }, function(e) {
+ dump("Error unregistering the SW: " + e + "\n");
+ });
+ });
+ } else {
+ my_ok(false, "Should've been controlled!");
+ finish();
+ }
+ }).catch(function(e) {
+ my_ok(false, "Some test threw an error " + e);
+ finish();
+ });
+</script>
+</pre>
+</body>
+</html>
+
+
diff --git a/dom/workers/test/serviceworkers/create_another_sharedWorker.html b/dom/workers/test/serviceworkers/create_another_sharedWorker.html
new file mode 100644
index 000000000..f49194fa5
--- /dev/null
+++ b/dom/workers/test/serviceworkers/create_another_sharedWorker.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<title>Shared workers: create antoehr sharedworekr client</title>
+<pre id=log>Hello World</pre>
+<script>
+ var worker = new SharedWorker('sharedWorker_fetch.js');
+</script>
diff --git a/dom/workers/test/serviceworkers/download/window.html b/dom/workers/test/serviceworkers/download/window.html
new file mode 100644
index 000000000..7d7893e0e
--- /dev/null
+++ b/dom/workers/test/serviceworkers/download/window.html
@@ -0,0 +1,46 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+</head>
+<body>
+<script type="text/javascript">
+
+function wait_until_controlled() {
+ return new Promise(function(resolve) {
+ if (navigator.serviceWorker.controller) {
+ return resolve();
+ }
+ navigator.serviceWorker.addEventListener('controllerchange', function onController() {
+ if (navigator.serviceWorker.controller) {
+ navigator.serviceWorker.removeEventListener('controllerchange', onController);
+ return resolve();
+ }
+ });
+ });
+}
+addEventListener('load', function(event) {
+ var registration;
+ navigator.serviceWorker.register('worker.js').then(function(swr) {
+ registration = swr;
+
+ // While the iframe below is a navigation, we still wait until we are
+ // controlled here. We want an active client to hold the service worker
+ // alive since it calls unregister() on itself.
+ return wait_until_controlled();
+
+ }).then(function() {
+ var frame = document.createElement('iframe');
+ document.body.appendChild(frame);
+ frame.src = 'fake_download';
+
+ // The service worker is unregistered in the fetch event. The window and
+ // frame are cleaned up from the browser chrome script.
+ });
+});
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/download/worker.js b/dom/workers/test/serviceworkers/download/worker.js
new file mode 100644
index 000000000..fe46d1a3b
--- /dev/null
+++ b/dom/workers/test/serviceworkers/download/worker.js
@@ -0,0 +1,30 @@
+addEventListener('install', function(evt) {
+ evt.waitUntil(self.skipWaiting());
+});
+
+addEventListener('activate', function(evt) {
+ // We claim the current clients in order to ensure that we have an
+ // active client when we call unregister in the fetch handler. Otherwise
+ // the unregister() can kill the current worker before returning a
+ // response.
+ evt.waitUntil(clients.claim());
+});
+
+addEventListener('fetch', function(evt) {
+ // This worker may live long enough to receive a fetch event from the next
+ // test. Just pass such requests through to the network.
+ if (evt.request.url.indexOf('fake_download') === -1) {
+ return;
+ }
+
+ // We should only get a single download fetch event. Automatically unregister.
+ evt.respondWith(registration.unregister().then(function() {
+ return new Response('service worker generated download', {
+ headers: {
+ 'Content-Disposition': 'attachment; filename="fake_download.bin"',
+ // fake encoding header that should have no effect
+ 'Content-Encoding': 'gzip',
+ }
+ });
+ }));
+});
diff --git a/dom/workers/test/serviceworkers/empty.js b/dom/workers/test/serviceworkers/empty.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/dom/workers/test/serviceworkers/empty.js
diff --git a/dom/workers/test/serviceworkers/error_reporting_helpers.js b/dom/workers/test/serviceworkers/error_reporting_helpers.js
new file mode 100644
index 000000000..fbc4ca6fc
--- /dev/null
+++ b/dom/workers/test/serviceworkers/error_reporting_helpers.js
@@ -0,0 +1,68 @@
+"use strict";
+
+/**
+ * Helpers for use in tests that want to verify that localized error messages
+ * are logged during the test. Because most of our errors (ex:
+ * ServiceWorkerManager) generate nsIScriptError instances with flattened
+ * strings (the interpolated arguments aren't kept around), we load the string
+ * bundle and use it to derive the exact string message we expect for the given
+ * payload.
+ **/
+
+let stringBundleService =
+ SpecialPowers.Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(SpecialPowers.Ci.nsIStringBundleService);
+let localizer =
+ stringBundleService.createBundle("chrome://global/locale/dom/dom.properties");
+
+/**
+ * Start monitoring the console for the given localized error message string(s)
+ * with the given arguments to be logged. Call before running code that will
+ * generate the console message. Pair with a call to
+ * `wait_for_expected_message` invoked after the message should have been
+ * generated.
+ *
+ * Multiple error messages can be expected, just repeat the msgId and args
+ * argument pair as needed.
+ *
+ * @param {String} msgId
+ * The localization message identifier used in the properties file.
+ * @param {String[]} args
+ * The list of formatting arguments we expect the error to be generated with.
+ * @return {Object} Promise/handle to pass to wait_for_expected_message.
+ */
+function expect_console_message(/* msgId, args, ... */) {
+ let expectations = [];
+ // process repeated paired arguments of: msgId, args
+ for (let i = 0; i < arguments.length; i += 2) {
+ let msgId = arguments[i];
+ let args = arguments[i + 1];
+ expectations.push({
+ errorMessage: localizer.formatStringFromName(msgId, args, args.length)
+ });
+ }
+ return new Promise(resolve => {
+ SimpleTest.monitorConsole(resolve, expectations);
+ });
+}
+let expect_console_messages = expect_console_message;
+
+/**
+ * Stop monitoring the console, returning a Promise that will be resolved when
+ * the sentinel console message sent through the async data path has been
+ * received. The Promise will not reject on failure; instead a mochitest
+ * failure will have been generated by ok(false)/equivalent by the time it is
+ * resolved.
+ */
+function wait_for_expected_message(expectedPromise) {
+ SimpleTest.endMonitorConsole();
+ return expectedPromise;
+}
+
+/**
+ * Derive an absolute URL string from a relative URL to simplify error message
+ * argument generation.
+ */
+function make_absolute_url(relUrl) {
+ return new URL(relUrl, window.location).href;
+}
diff --git a/dom/workers/test/serviceworkers/eval_worker.js b/dom/workers/test/serviceworkers/eval_worker.js
new file mode 100644
index 000000000..c60f2f637
--- /dev/null
+++ b/dom/workers/test/serviceworkers/eval_worker.js
@@ -0,0 +1 @@
+eval('1+1');
diff --git a/dom/workers/test/serviceworkers/eventsource/eventsource.resource b/dom/workers/test/serviceworkers/eventsource/eventsource.resource
new file mode 100644
index 000000000..eb62cbd4c
--- /dev/null
+++ b/dom/workers/test/serviceworkers/eventsource/eventsource.resource
@@ -0,0 +1,22 @@
+:this file must be enconded in utf8
+:and its Content-Type must be equal to text/event-stream
+
+retry:500
+data: 2
+unknow: unknow
+
+event: other_event_name
+retry:500
+data: 2
+unknow: unknow
+
+event: click
+retry:500
+
+event: blur
+retry:500
+
+event:keypress
+retry:500
+
+
diff --git a/dom/workers/test/serviceworkers/eventsource/eventsource.resource^headers^ b/dom/workers/test/serviceworkers/eventsource/eventsource.resource^headers^
new file mode 100644
index 000000000..5b88be7c3
--- /dev/null
+++ b/dom/workers/test/serviceworkers/eventsource/eventsource.resource^headers^
@@ -0,0 +1,3 @@
+Content-Type: text/event-stream
+Cache-Control: no-cache, must-revalidate
+Access-Control-Allow-Origin: *
diff --git a/dom/workers/test/serviceworkers/eventsource/eventsource_cors_response.html b/dom/workers/test/serviceworkers/eventsource/eventsource_cors_response.html
new file mode 100644
index 000000000..7c6f7302f
--- /dev/null
+++ b/dom/workers/test/serviceworkers/eventsource/eventsource_cors_response.html
@@ -0,0 +1,75 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1182103 - Test EventSource scenarios with fetch interception</title>
+ <script type="text/javascript">
+
+ var prefix = "http://mochi.test:8888/tests/dom/workers/test/serviceworkers/eventsource/";
+
+ function ok(aCondition, aMessage) {
+ parent.postMessage({status: "callback", data: "ok", condition: aCondition, message: aMessage}, "*");
+ }
+
+ function doUnregister() {
+ navigator.serviceWorker.getRegistration().then(swr => {
+ swr.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ parent.postMessage({status: "callback", data: "done"}, "*");
+ }, function(e) {
+ ok(false, "Unregistering the SW failed with " + e);
+ });
+ });
+ }
+
+ function doEventSource() {
+ var source = new EventSource(prefix + "eventsource.resource");
+ source.onmessage = function(e) {
+ source.onmessage = null;
+ source.close();
+ ok(true, "EventSource should work with cors responses");
+ doUnregister();
+ };
+ source.onerror = function(error) {
+ source.onerror = null;
+ source.close();
+ ok(false, "Something went wrong");
+ };
+ }
+
+ function onLoad() {
+ if (!parent) {
+ dump("eventsource/eventsource_cors_response.html shouldn't be launched directly!");
+ }
+
+ window.addEventListener("message", function onMessage(e) {
+ if (e.data.status === "callback") {
+ switch(e.data.data) {
+ case "eventsource":
+ doEventSource();
+ window.removeEventListener("message", onMessage);
+ break;
+ default:
+ ok(false, "Something went wrong")
+ break
+ }
+ }
+ });
+
+ navigator.serviceWorker.ready.then(function() {
+ parent.postMessage({status: "callback", data: "ready"}, "*");
+ });
+
+ navigator.serviceWorker.addEventListener("message", function(event) {
+ parent.postMessage(event.data, "*");
+ });
+ }
+
+ </script>
+</head>
+<body onload="onLoad()">
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/eventsource/eventsource_cors_response_intercept_worker.js b/dom/workers/test/serviceworkers/eventsource/eventsource_cors_response_intercept_worker.js
new file mode 100644
index 000000000..579e9f568
--- /dev/null
+++ b/dom/workers/test/serviceworkers/eventsource/eventsource_cors_response_intercept_worker.js
@@ -0,0 +1,20 @@
+// Cross origin request
+var prefix = 'http://example.com/tests/dom/workers/test/serviceworkers/eventsource/';
+
+self.importScripts('eventsource_worker_helper.js');
+
+self.addEventListener('fetch', function (event) {
+ var request = event.request;
+ var url = new URL(request.url);
+
+ if (url.pathname !== '/tests/dom/workers/test/serviceworkers/eventsource/eventsource.resource') {
+ return;
+ }
+
+ ok(request.mode === 'cors', 'EventSource should make a CORS request');
+ ok(request.cache === 'no-store', 'EventSource should make a no-store request');
+ var fetchRequest = new Request(prefix + 'eventsource.resource', { mode: 'cors'});
+ event.respondWith(fetch(fetchRequest).then((fetchResponse) => {
+ return fetchResponse;
+ }));
+});
diff --git a/dom/workers/test/serviceworkers/eventsource/eventsource_mixed_content_cors_response.html b/dom/workers/test/serviceworkers/eventsource/eventsource_mixed_content_cors_response.html
new file mode 100644
index 000000000..f6ae0d96f
--- /dev/null
+++ b/dom/workers/test/serviceworkers/eventsource/eventsource_mixed_content_cors_response.html
@@ -0,0 +1,75 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1182103 - Test EventSource scenarios with fetch interception</title>
+ <script type="text/javascript">
+
+ var prefix = "https://example.com/tests/dom/workers/test/serviceworkers/eventsource/";
+
+ function ok(aCondition, aMessage) {
+ parent.postMessage({status: "callback", data: "ok", condition: aCondition, message: aMessage}, "*");
+ }
+
+ function doUnregister() {
+ navigator.serviceWorker.getRegistration().then(swr => {
+ swr.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ parent.postMessage({status: "callback", data: "done"}, "*");
+ }, function(e) {
+ ok(false, "Unregistering the SW failed with " + e);
+ });
+ });
+ }
+
+ function doEventSource() {
+ var source = new EventSource(prefix + "eventsource.resource");
+ source.onmessage = function(e) {
+ source.onmessage = null;
+ source.close();
+ ok(false, "Something went wrong");
+ };
+ source.onerror = function(error) {
+ source.onerror = null;
+ source.close();
+ ok(true, "EventSource should not work with mixed content cors responses");
+ doUnregister();
+ };
+ }
+
+ function onLoad() {
+ if (!parent) {
+ dump("eventsource/eventsource_cors_response.html shouldn't be launched directly!");
+ }
+
+ window.addEventListener("message", function onMessage(e) {
+ if (e.data.status === "callback") {
+ switch(e.data.data) {
+ case "eventsource":
+ doEventSource();
+ window.removeEventListener("message", onMessage);
+ break;
+ default:
+ ok(false, "Something went wrong")
+ break
+ }
+ }
+ });
+
+ navigator.serviceWorker.ready.then(function() {
+ parent.postMessage({status: "callback", data: "ready"}, "*");
+ });
+
+ navigator.serviceWorker.addEventListener("message", function(event) {
+ parent.postMessage(event.data, "*");
+ });
+ }
+
+ </script>
+</head>
+<body onload="onLoad()">
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/eventsource/eventsource_mixed_content_cors_response_intercept_worker.js b/dom/workers/test/serviceworkers/eventsource/eventsource_mixed_content_cors_response_intercept_worker.js
new file mode 100644
index 000000000..187d0bc6f
--- /dev/null
+++ b/dom/workers/test/serviceworkers/eventsource/eventsource_mixed_content_cors_response_intercept_worker.js
@@ -0,0 +1,19 @@
+var prefix = 'http://example.com/tests/dom/workers/test/serviceworkers/eventsource/';
+
+self.importScripts('eventsource_worker_helper.js');
+
+self.addEventListener('fetch', function (event) {
+ var request = event.request;
+ var url = new URL(request.url);
+
+ if (url.pathname !== '/tests/dom/workers/test/serviceworkers/eventsource/eventsource.resource') {
+ return;
+ }
+
+ ok(request.mode === 'cors', 'EventSource should make a CORS request');
+ ok(request.cache === 'no-store', 'EventSource should make a no-store request');
+ var fetchRequest = new Request(prefix + 'eventsource.resource', { mode: 'cors'});
+ event.respondWith(fetch(fetchRequest).then((fetchResponse) => {
+ return fetchResponse;
+ }));
+});
diff --git a/dom/workers/test/serviceworkers/eventsource/eventsource_opaque_response.html b/dom/workers/test/serviceworkers/eventsource/eventsource_opaque_response.html
new file mode 100644
index 000000000..f92811e63
--- /dev/null
+++ b/dom/workers/test/serviceworkers/eventsource/eventsource_opaque_response.html
@@ -0,0 +1,75 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1182103 - Test EventSource scenarios with fetch interception</title>
+ <script type="text/javascript">
+
+ var prefix = "http://mochi.test:8888/tests/dom/workers/test/serviceworkers/eventsource/";
+
+ function ok(aCondition, aMessage) {
+ parent.postMessage({status: "callback", data: "ok", condition: aCondition, message: aMessage}, "*");
+ }
+
+ function doUnregister() {
+ navigator.serviceWorker.getRegistration().then(swr => {
+ swr.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ parent.postMessage({status: "callback", data: "done"}, "*");
+ }, function(e) {
+ ok(false, "Unregistering the SW failed with " + e);
+ });
+ });
+ }
+
+ function doEventSource() {
+ var source = new EventSource(prefix + "eventsource.resource");
+ source.onmessage = function(e) {
+ source.onmessage = null;
+ source.close();
+ ok(false, "Something went wrong");
+ };
+ source.onerror = function(error) {
+ source.onerror = null;
+ source.close();
+ ok(true, "EventSource should not work with opaque responses");
+ doUnregister();
+ };
+ }
+
+ function onLoad() {
+ if (!parent) {
+ dump("eventsource/eventsource_opaque_response.html shouldn't be launched directly!");
+ }
+
+ window.addEventListener("message", function onMessage(e) {
+ if (e.data.status === "callback") {
+ switch(e.data.data) {
+ case "eventsource":
+ doEventSource();
+ window.removeEventListener("message", onMessage);
+ break;
+ default:
+ ok(false, "Something went wrong")
+ break
+ }
+ }
+ });
+
+ navigator.serviceWorker.ready.then(function() {
+ parent.postMessage({status: "callback", data: "ready"}, "*");
+ });
+
+ navigator.serviceWorker.addEventListener("message", function(event) {
+ parent.postMessage(event.data, "*");
+ });
+ }
+
+ </script>
+</head>
+<body onload="onLoad()">
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/eventsource/eventsource_opaque_response_intercept_worker.js b/dom/workers/test/serviceworkers/eventsource/eventsource_opaque_response_intercept_worker.js
new file mode 100644
index 000000000..45a80e324
--- /dev/null
+++ b/dom/workers/test/serviceworkers/eventsource/eventsource_opaque_response_intercept_worker.js
@@ -0,0 +1,20 @@
+// Cross origin request
+var prefix = 'http://example.com/tests/dom/workers/test/serviceworkers/eventsource/';
+
+self.importScripts('eventsource_worker_helper.js');
+
+self.addEventListener('fetch', function (event) {
+ var request = event.request;
+ var url = new URL(request.url);
+
+ if (url.pathname !== '/tests/dom/workers/test/serviceworkers/eventsource/eventsource.resource') {
+ return;
+ }
+
+ ok(request.mode === 'cors', 'EventSource should make a CORS request');
+ ok(request.cache === 'no-store', 'EventSource should make a no-store request');
+ var fetchRequest = new Request(prefix + 'eventsource.resource', { mode: 'no-cors'});
+ event.respondWith(fetch(fetchRequest).then((fetchResponse) => {
+ return fetchResponse;
+ }));
+});
diff --git a/dom/workers/test/serviceworkers/eventsource/eventsource_register_worker.html b/dom/workers/test/serviceworkers/eventsource/eventsource_register_worker.html
new file mode 100644
index 000000000..59e8e92ab
--- /dev/null
+++ b/dom/workers/test/serviceworkers/eventsource/eventsource_register_worker.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>Bug 1182103 - Test EventSource scenarios with fetch interception</title>
+ <script type="text/javascript">
+
+ function getURLParam (aTarget, aValue) {
+ return decodeURI(aTarget.search.replace(new RegExp("^(?:.*[&\\?]" + encodeURI(aValue).replace(/[\.\+\*]/g, "\\$&") + "(?:\\=([^&]*))?)?.*$", "i"), "$1"));
+ }
+
+ function onLoad() {
+ navigator.serviceWorker.ready.then(function() {
+ parent.postMessage({status: "callback", data: "done"}, "*");
+ });
+
+ navigator.serviceWorker.register(getURLParam(document.location, "script"), {scope: "."});
+ }
+
+ </script>
+</head>
+<body onload="onLoad()">
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/eventsource/eventsource_synthetic_response.html b/dom/workers/test/serviceworkers/eventsource/eventsource_synthetic_response.html
new file mode 100644
index 000000000..d9f380e2f
--- /dev/null
+++ b/dom/workers/test/serviceworkers/eventsource/eventsource_synthetic_response.html
@@ -0,0 +1,75 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1182103 - Test EventSource scenarios with fetch interception</title>
+ <script type="text/javascript">
+
+ var prefix = "http://mochi.test:8888/tests/dom/workers/test/serviceworkers/eventsource/";
+
+ function ok(aCondition, aMessage) {
+ parent.postMessage({status: "callback", data: "ok", condition: aCondition, message: aMessage}, "*");
+ }
+
+ function doUnregister() {
+ navigator.serviceWorker.getRegistration().then(swr => {
+ swr.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ parent.postMessage({status: "callback", data: "done"}, "*");
+ }, function(e) {
+ ok(false, "Unregistering the SW failed with " + e);
+ });
+ });
+ }
+
+ function doEventSource() {
+ var source = new EventSource(prefix + "eventsource.resource");
+ source.onmessage = function(e) {
+ source.onmessage = null;
+ source.close();
+ ok(true, "EventSource should work with synthetic responses");
+ doUnregister();
+ };
+ source.onerror = function(error) {
+ source.onmessage = null;
+ source.close();
+ ok(false, "Something went wrong");
+ };
+ }
+
+ function onLoad() {
+ if (!parent) {
+ dump("eventsource/eventsource_synthetic_response.html shouldn't be launched directly!");
+ }
+
+ window.addEventListener("message", function onMessage(e) {
+ if (e.data.status === "callback") {
+ switch(e.data.data) {
+ case "eventsource":
+ doEventSource();
+ window.removeEventListener("message", onMessage);
+ break;
+ default:
+ ok(false, "Something went wrong")
+ break
+ }
+ }
+ });
+
+ navigator.serviceWorker.ready.then(function() {
+ parent.postMessage({status: "callback", data: "ready"}, "*");
+ });
+
+ navigator.serviceWorker.addEventListener("message", function(event) {
+ parent.postMessage(event.data, "*");
+ });
+ }
+
+ </script>
+</head>
+<body onload="onLoad()">
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/eventsource/eventsource_synthetic_response_intercept_worker.js b/dom/workers/test/serviceworkers/eventsource/eventsource_synthetic_response_intercept_worker.js
new file mode 100644
index 000000000..8692f9186
--- /dev/null
+++ b/dom/workers/test/serviceworkers/eventsource/eventsource_synthetic_response_intercept_worker.js
@@ -0,0 +1,24 @@
+self.importScripts('eventsource_worker_helper.js');
+
+self.addEventListener('fetch', function (event) {
+ var request = event.request;
+ var url = new URL(request.url);
+
+ if (url.pathname !== '/tests/dom/workers/test/serviceworkers/eventsource/eventsource.resource') {
+ return;
+ }
+
+ ok(request.mode === 'cors', 'EventSource should make a CORS request');
+ var headerList = {
+ 'Content-Type': 'text/event-stream',
+ 'Cache-Control': 'no-cache, must-revalidate'
+ };
+ var headers = new Headers(headerList);
+ var init = {
+ headers: headers,
+ mode: 'cors'
+ };
+ var body = 'data: data0\r\r';
+ var response = new Response(body, init);
+ event.respondWith(response);
+});
diff --git a/dom/workers/test/serviceworkers/eventsource/eventsource_worker_helper.js b/dom/workers/test/serviceworkers/eventsource/eventsource_worker_helper.js
new file mode 100644
index 000000000..6d5dbb024
--- /dev/null
+++ b/dom/workers/test/serviceworkers/eventsource/eventsource_worker_helper.js
@@ -0,0 +1,12 @@
+function ok(aCondition, aMessage) {
+ return new Promise(function(resolve, reject) {
+ self.clients.matchAll().then(function(res) {
+ if (!res.length) {
+ reject();
+ return;
+ }
+ res[0].postMessage({status: "callback", data: "ok", condition: aCondition, message: aMessage});
+ resolve();
+ });
+ });
+}
diff --git a/dom/workers/test/serviceworkers/fetch.js b/dom/workers/test/serviceworkers/fetch.js
new file mode 100644
index 000000000..38d20a638
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch.js
@@ -0,0 +1,11 @@
+addEventListener('fetch', function(event) {
+ if (event.request.url.indexOf("fail.html") !== -1) {
+ event.respondWith(fetch("hello.html", {"integrity": "abc"}));
+ } else if (event.request.url.indexOf("fake.html") !== -1) {
+ event.respondWith(fetch("hello.html"));
+ }
+});
+
+addEventListener("activate", function(event) {
+ event.waitUntil(clients.claim());
+});
diff --git a/dom/workers/test/serviceworkers/fetch/context/beacon.sjs b/dom/workers/test/serviceworkers/fetch/context/beacon.sjs
new file mode 100644
index 000000000..8401bc29b
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/beacon.sjs
@@ -0,0 +1,43 @@
+/*
+ * This is based on dom/tests/mochitest/beacon/beacon-originheader-handler.sjs.
+ */
+
+function handleRequest(request, response)
+{
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+
+ // case XHR-REQUEST: the xhr-request tries to query the
+ // stored context from the beacon request.
+ if (request.queryString == "queryContext") {
+ var context = getState("interceptContext");
+ // if the beacon already stored the context - return.
+ if (context) {
+ response.write(context);
+ setState("interceptContext", "");
+ return;
+ }
+ // otherwise wait for the beacon request
+ response.processAsync();
+ setObjectState("sw-xhr-response", response);
+ return;
+ }
+
+ // case BEACON-REQUEST: get the beacon context and
+ // store the context on the server.
+ var context = request.queryString;
+ setState("interceptContext", context);
+
+ // if there is an xhr-request waiting, return the context now.
+ try{
+ getObjectState("sw-xhr-response", function(xhrResponse) {
+ if (!xhrResponse) {
+ return;
+ }
+ setState("interceptContext", "");
+ xhrResponse.write(context);
+ xhrResponse.finish();
+ });
+ } catch(e) {
+ }
+}
diff --git a/dom/workers/test/serviceworkers/fetch/context/context_test.js b/dom/workers/test/serviceworkers/fetch/context/context_test.js
new file mode 100644
index 000000000..b98d2ab3c
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/context_test.js
@@ -0,0 +1,135 @@
+self.addEventListener("fetch", function(event) {
+ if (event.request.url.indexOf("index.html") >= 0 ||
+ event.request.url.indexOf("register.html") >= 0 ||
+ event.request.url.indexOf("unregister.html") >= 0 ||
+ event.request.url.indexOf("ping.html") >= 0 ||
+ event.request.url.indexOf("xml.xml") >= 0 ||
+ event.request.url.indexOf("csp-violate.sjs") >= 0) {
+ // Handle pass-through requests
+ event.respondWith(fetch(event.request));
+ } else if (event.request.url.indexOf("fetch.txt") >= 0) {
+ var body = event.request.context == "fetch" ?
+ "so fetch" : "so unfetch";
+ event.respondWith(new Response(body));
+ } else if (event.request.url.indexOf("img.jpg") >= 0) {
+ if (event.request.context == "image") {
+ event.respondWith(fetch("realimg.jpg"));
+ }
+ } else if (event.request.url.indexOf("responsive.jpg") >= 0) {
+ if (event.request.context == "imageset") {
+ event.respondWith(fetch("realimg.jpg"));
+ }
+ } else if (event.request.url.indexOf("audio.ogg") >= 0) {
+ if (event.request.context == "audio") {
+ event.respondWith(fetch("realaudio.ogg"));
+ }
+ } else if (event.request.url.indexOf("video.ogg") >= 0) {
+ if (event.request.context == "video") {
+ event.respondWith(fetch("realaudio.ogg"));
+ }
+ } else if (event.request.url.indexOf("beacon.sjs") >= 0) {
+ if (event.request.url.indexOf("queryContext") == -1) {
+ event.respondWith(fetch("beacon.sjs?" + event.request.context));
+ } else {
+ event.respondWith(fetch(event.request));
+ }
+ } else if (event.request.url.indexOf("csp-report.sjs") >= 0) {
+ respondToServiceWorker(event, "csp-report");
+ } else if (event.request.url.indexOf("embed") >= 0) {
+ respondToServiceWorker(event, "embed");
+ } else if (event.request.url.indexOf("object") >= 0) {
+ respondToServiceWorker(event, "object");
+ } else if (event.request.url.indexOf("font") >= 0) {
+ respondToServiceWorker(event, "font");
+ } else if (event.request.url.indexOf("iframe") >= 0) {
+ if (event.request.context == "iframe") {
+ event.respondWith(fetch("context_test.js"));
+ }
+ } else if (event.request.url.indexOf("frame") >= 0) {
+ if (event.request.context == "frame") {
+ event.respondWith(fetch("context_test.js"));
+ }
+ } else if (event.request.url.indexOf("newwindow") >= 0) {
+ respondToServiceWorker(event, "newwindow");
+ } else if (event.request.url.indexOf("ping") >= 0) {
+ respondToServiceWorker(event, "ping");
+ } else if (event.request.url.indexOf("plugin") >= 0) {
+ respondToServiceWorker(event, "plugin");
+ } else if (event.request.url.indexOf("script.js") >= 0) {
+ if (event.request.context == "script") {
+ event.respondWith(new Response(""));
+ }
+ } else if (event.request.url.indexOf("style.css") >= 0) {
+ respondToServiceWorker(event, "style");
+ } else if (event.request.url.indexOf("track") >= 0) {
+ respondToServiceWorker(event, "track");
+ } else if (event.request.url.indexOf("xhr") >= 0) {
+ if (event.request.context == "xmlhttprequest") {
+ event.respondWith(new Response(""));
+ }
+ } else if (event.request.url.indexOf("xslt") >= 0) {
+ respondToServiceWorker(event, "xslt");
+ } else if (event.request.url.indexOf("myworker") >= 0) {
+ if (event.request.context == "worker") {
+ event.respondWith(fetch("worker.js"));
+ }
+ } else if (event.request.url.indexOf("myparentworker") >= 0) {
+ if (event.request.context == "worker") {
+ event.respondWith(fetch("parentworker.js"));
+ }
+ } else if (event.request.url.indexOf("mysharedworker") >= 0) {
+ if (event.request.context == "sharedworker") {
+ event.respondWith(fetch("sharedworker.js"));
+ }
+ } else if (event.request.url.indexOf("myparentsharedworker") >= 0) {
+ if (event.request.context == "sharedworker") {
+ event.respondWith(fetch("parentsharedworker.js"));
+ }
+ } else if (event.request.url.indexOf("cache") >= 0) {
+ var cache;
+ var origContext = event.request.context;
+ event.respondWith(caches.open("cache")
+ .then(function(c) {
+ cache = c;
+ // Store the Request in the cache.
+ return cache.put(event.request, new Response("fake"));
+ }).then(function() {
+ // Read it back.
+ return cache.keys(event.request);
+ }).then(function(res) {
+ var req = res[0];
+ // Check to see if the context remained the same.
+ var success = req.context === origContext;
+ return clients.matchAll()
+ .then(function(clients) {
+ // Report it back to the main page.
+ clients.forEach(function(c) {
+ c.postMessage({data: "cache", success: success});
+ });
+ })}).then(function() {
+ // Cleanup.
+ return caches.delete("cache");
+ }).then(function() {
+ return new Response("ack");
+ }));
+ }
+ // Fail any request that we don't know about.
+ try {
+ event.respondWith(Promise.reject(event.request.url));
+ dump("Fetch event received invalid context value " + event.request.context +
+ " for " + event.request.url + "\n");
+ } catch(e) {
+ // Eat up the possible InvalidStateError exception that we may get if some
+ // code above has called respondWith too.
+ }
+});
+
+function respondToServiceWorker(event, data) {
+ event.respondWith(clients.matchAll()
+ .then(function(clients) {
+ clients.forEach(function(c) {
+ c.postMessage({data: data, context: event.request.context});
+ });
+ return new Response("ack");
+ }));
+}
diff --git a/dom/workers/test/serviceworkers/fetch/context/csp-violate.sjs b/dom/workers/test/serviceworkers/fetch/context/csp-violate.sjs
new file mode 100644
index 000000000..4c3e76d15
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/csp-violate.sjs
@@ -0,0 +1,6 @@
+function handleRequest(request, response)
+{
+ response.setHeader("Content-Security-Policy", "default-src 'none'; report-uri /tests/dom/workers/test/serviceworkers/fetch/context/csp-report.sjs", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("<link rel=stylesheet href=style.css>");
+}
diff --git a/dom/workers/test/serviceworkers/fetch/context/index.html b/dom/workers/test/serviceworkers/fetch/context/index.html
new file mode 100644
index 000000000..c6dfef99c
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/index.html
@@ -0,0 +1,422 @@
+<!DOCTYPE html>
+<script>
+ var isAndroid = navigator.userAgent.includes("Android");
+ var isB2G = !isAndroid && /Mobile|Tablet/.test(navigator.userAgent);
+
+ function ok(v, msg) {
+ window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+ }
+
+ function is(a, b, msg) {
+ ok(a === b, msg + ", expected '" + b + "', got '" + a + "'");
+ }
+
+ function todo(v, msg) {
+ window.parent.postMessage({status: "todo", result: !!v, message: msg}, "*");
+ }
+
+ function finish() {
+ window.parent.postMessage({status: "done"}, "*");
+ }
+
+ function testFetch() {
+ return fetch("fetch.txt").then(function(r) {
+ return r.text();
+ }).then(function(body) {
+ is(body, "so fetch", "A fetch() Request should have the 'fetch' context");
+ });
+ }
+
+ function testImage() {
+ return new Promise(function(resolve, reject) {
+ var img = document.createElement("img");
+ img.src = "img.jpg";
+ // The service worker will respond with an existing image only if the
+ // Request has the correct context, otherwise the Promise will get
+ // rejected and the test will fail.
+ img.onload = resolve;
+ img.onerror = reject;
+ });
+ }
+
+ function testImageSrcSet() {
+ return new Promise(function(resolve, reject) {
+ var img = document.createElement("img");
+ img.srcset = "responsive.jpg 100w";
+ // The service worker will respond with an existing image only if the
+ // Request has the correct context, otherwise the Promise will get
+ // rejected and the test will fail.
+ img.onload = resolve;
+ img.onerror = reject;
+ });
+ }
+
+ function testPicture() {
+ return new Promise(function(resolve, reject) {
+ var pic = document.createElement("picture");
+ var img = document.createElement("img");
+ pic.appendChild(img);
+ img.src = "responsive.jpg?picture";
+ // The service worker will respond with an existing image only if the
+ // Request has the correct context, otherwise the Promise will get
+ // rejected and the test will fail.
+ img.onload = resolve;
+ img.onerror = reject;
+ });
+ }
+
+ function testAudio() {
+ return new Promise(function(resolve, reject) {
+ var audio = document.createElement("audio");
+ audio.src = "audio.ogg";
+ audio.preload = "metadata";
+ // The service worker will respond with an existing audio only if the
+ // Request has the correct context, otherwise the Promise will get
+ // rejected and the test will fail.
+ audio.onloadedmetadata = resolve;
+ audio.onerror = reject;
+ });
+ }
+
+ function testVideo() {
+ return new Promise(function(resolve, reject) {
+ var video = document.createElement("video");
+ video.src = "video.ogg";
+ video.preload = "metadata";
+ // The service worker will respond with an existing video only if the
+ // Request has the correct context, otherwise the Promise will get
+ // rejected and the test will fail.
+ video.onloadedmetadata = resolve;
+ video.onerror = reject;
+ });
+ }
+
+ function testBeacon() {
+ ok(navigator.sendBeacon("beacon.sjs"), "Sending the beacon should succeed");
+ // query the context from beacon.sjs
+ return fetch("beacon.sjs?queryContext")
+ .then(function(r) {
+ return r.text();
+ }).then(function(body) {
+ is(body, "beacon", "The context for the intercepted beacon should be correct");
+ });
+ }
+
+ function testCSPReport() {
+ return new Promise(function(resolve, reject) {
+ var iframe = document.createElement("iframe");
+ iframe.src = "csp-violate.sjs";
+ document.documentElement.appendChild(iframe);
+ navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+ if (e.data.data == "csp-report") {
+ is(e.data.context, "cspreport", "Expected the cspreport context on a CSP violation report");
+ navigator.serviceWorker.removeEventListener("message", onMessage);
+ resolve();
+ }
+ }, false);
+ });
+ }
+
+ function testEmbed() {
+ return Promise.resolve().then(function() {
+ todo(false, "<embed> tag is not currently intercepted. See Bug 1168676.");
+ });
+
+ return new Promise(function(resolve, reject) {
+ var embed = document.createElement("embed");
+ embed.src = "embed";
+ document.documentElement.appendChild(embed);
+ navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+ if (e.data.data == "embed") {
+ is(e.data.context, "embed", "Expected the object context on an embed");
+ navigator.serviceWorker.removeEventListener("message", onMessage);
+ resolve();
+ }
+ }, false);
+ });
+ }
+
+ function testObject() {
+ return Promise.resolve().then(function() {
+ todo(false, "<object> tag is not currently intercepted. See Bug 1168676");
+ });
+
+ return new Promise(function(resolve, reject) {
+ var object = document.createElement("object");
+ object.data = "object";
+ document.documentElement.appendChild(object);
+ navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+ if (e.data.data == "object") {
+ is(e.data.context, "object", "Expected the object context on an object");
+ navigator.serviceWorker.removeEventListener("message", onMessage);
+ resolve();
+ }
+ }, false);
+ });
+ }
+
+ function testFont() {
+ return new Promise(function(resolve, reject) {
+ var css = '@font-face { font-family: "sw-font"; src: url("font"); }';
+ css += '* { font-family: "sw-font"; }';
+ var style = document.createElement("style");
+ style.appendChild(document.createTextNode(css));
+ document.documentElement.appendChild(style);
+ navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+ if (e.data.data == "font") {
+ is(e.data.context, "font", "Expected the font context on an font");
+ navigator.serviceWorker.removeEventListener("message", onMessage);
+ resolve();
+ }
+ }, false);
+ });
+ }
+
+ function testIFrame() {
+ return new Promise(function(resolve, reject) {
+ var iframe = document.createElement("iframe");
+ iframe.src = "iframe";
+ document.documentElement.appendChild(iframe);
+ // The service worker will respond with an existing document only if the
+ // Request has the correct context, otherwise the Promise will get
+ // rejected and the test will fail.
+ iframe.onload = resolve;
+ iframe.onerror = reject;
+ });
+ }
+
+ function testFrame() {
+ return new Promise(function(resolve, reject) {
+ var frame = document.createElement("frame");
+ frame.src = "frame";
+ document.documentElement.appendChild(frame);
+ // The service worker will respond with an existing document only if the
+ // Request has the correct context, otherwise the Promise will get
+ // rejected and the test will fail.
+ frame.onload = resolve;
+ frame.onerror = reject;
+ });
+ }
+
+ function testInternal() {
+ if (isB2G) {
+ // We can't open new windows on b2g, so skip this part of the test there.
+ return Promise.resolve();
+ }
+ return new Promise(function(resolve, reject) {
+ // Test this with a new window opened through script. There are of course
+ // other possible ways of testing this too.
+ var win = window.open("newwindow", "_blank", "width=100,height=100");
+ navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+ if (e.data.data == "newwindow") {
+ is(e.data.context, "internal", "Expected the internal context on a newly opened window");
+ navigator.serviceWorker.removeEventListener("message", onMessage);
+ win.close();
+ resolve();
+ }
+ }, false);
+ });
+ }
+
+ function testPing() {
+ return new Promise(function(resolve, reject) {
+ var iframe = document.createElement("iframe");
+ iframe.src = "ping.html";
+ document.documentElement.appendChild(iframe);
+ navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+ if (e.data.data == "ping") {
+ is(e.data.context, "ping", "Expected the ping context on an anchor ping");
+ navigator.serviceWorker.removeEventListener("message", onMessage);
+ resolve();
+ }
+ }, false);
+ });
+ }
+
+ function testPlugin() {
+ return Promise.resolve().then(function() {
+ todo(false, "plugins are not currently intercepted. See Bug 1168676.");
+ });
+
+ var isMobile = /Mobile|Tablet/.test(navigator.userAgent);
+ if (isMobile || parent.isMulet()) {
+ // We can't use plugins on mobile, so skip this part of the test there.
+ return Promise.resolve();
+ }
+
+ return new Promise(function(resolve, reject) {
+ var embed = document.createElement("embed");
+ embed.type = "application/x-test";
+ embed.setAttribute("posturl", "plugin");
+ embed.setAttribute("postmode", "stream");
+ embed.setAttribute("streammode", "normal");
+ embed.setAttribute("src", "fetch.txt");
+ document.documentElement.appendChild(embed);
+ navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+ if (e.data.data == "plugin") {
+ is(e.data.context, "plugin", "Expected the plugin context on a request coming from a plugin");
+ navigator.serviceWorker.removeEventListener("message", onMessage);
+ // Without this, the test leaks in e10s!
+ embed.parentNode.removeChild(embed);
+ resolve();
+ }
+ }, false);
+ });
+ }
+
+ function testScript() {
+ return new Promise(function(resolve, reject) {
+ var script = document.createElement("script");
+ script.src = "script.js";
+ document.documentElement.appendChild(script);
+ // The service worker will respond with an existing script only if the
+ // Request has the correct context, otherwise the Promise will get
+ // rejected and the test will fail.
+ script.onload = resolve;
+ script.onerror = reject;
+ });
+ }
+
+ function testStyle() {
+ return new Promise(function(resolve, reject) {
+ var link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.href = "style.css";
+ document.documentElement.appendChild(link);
+ navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+ if (e.data.data == "style") {
+ is(e.data.context, "style", "Expected the style context on a request coming from a stylesheet");
+ navigator.serviceWorker.removeEventListener("message", onMessage);
+ resolve();
+ }
+ }, false);
+ });
+ }
+
+ function testTrack() {
+ return new Promise(function(resolve, reject) {
+ var video = document.createElement("video");
+ var track = document.createElement("track");
+ track.src = "track";
+ video.appendChild(track);
+ document.documentElement.appendChild(video);
+ navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+ if (e.data.data == "track") {
+ is(e.data.context, "track", "Expected the track context on a request coming from a track");
+ navigator.serviceWorker.removeEventListener("message", onMessage);
+ resolve();
+ }
+ }, false);
+ });
+ }
+
+ function testXHR() {
+ return new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("get", "xhr", true);
+ xhr.send();
+ // The service worker will respond with an existing resource only if the
+ // Request has the correct context, otherwise the Promise will get
+ // rejected and the test will fail.
+ xhr.onload = resolve;
+ xhr.onerror = reject;
+ });
+ }
+
+ function testXSLT() {
+ return new Promise(function(resolve, reject) {
+ var iframe = document.createElement("iframe");
+ iframe.src = "xml.xml";
+ document.documentElement.appendChild(iframe);
+ navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+ if (e.data.data == "xslt") {
+ is(e.data.context, "xslt", "Expected the xslt context on an XSLT stylesheet");
+ navigator.serviceWorker.removeEventListener("message", onMessage);
+ // Without this, the test leaks in e10s!
+ iframe.parentNode.removeChild(iframe);
+ resolve();
+ }
+ }, false);
+ });
+ }
+
+ function testWorker() {
+ return new Promise(function(resolve, reject) {
+ var worker = new Worker("myworker");
+ worker.onmessage = function(e) {
+ if (e.data == "ack") {
+ worker.terminate();
+ resolve();
+ }
+ };
+ worker.onerror = reject;
+ });
+ }
+
+ function testNestedWorker() {
+ return new Promise(function(resolve, reject) {
+ var worker = new Worker("myparentworker");
+ worker.onmessage = function(e) {
+ if (e.data == "ack") {
+ worker.terminate();
+ resolve();
+ }
+ };
+ worker.onerror = reject;
+ });
+ }
+
+ function testSharedWorker() {
+ return new Promise(function(resolve, reject) {
+ var worker = new SharedWorker("mysharedworker");
+ worker.port.start();
+ worker.port.onmessage = function(e) {
+ if (e.data == "ack") {
+ resolve();
+ }
+ };
+ worker.onerror = reject;
+ });
+ }
+
+ function testNestedWorkerInSharedWorker() {
+ return new Promise(function(resolve, reject) {
+ var worker = new SharedWorker("myparentsharedworker");
+ worker.port.start();
+ worker.port.onmessage = function(e) {
+ if (e.data == "ack") {
+ resolve();
+ }
+ };
+ worker.onerror = reject;
+ });
+ }
+
+ function testCache() {
+ return new Promise(function(resolve, reject) {
+ // Issue an XHR that will be intercepted by the SW in order to start off
+ // the test with a RequestContext value that is not the default ("fetch").
+ // This needs to run inside a fetch event handler because synthesized
+ // RequestContext objects can only have the "fetch" context, and we'd
+ // prefer to test the more general case of some other RequestContext value.
+ var xhr = new XMLHttpRequest();
+ xhr.open("get", "cache", true);
+ xhr.send();
+ navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+ if (e.data.data == "cache") {
+ ok(e.data.success, "The RequestContext can be persisted in the cache.");
+ navigator.serviceWorker.removeEventListener("message", onMessage);
+ resolve();
+ }
+ }, false);
+ });
+ }
+
+ var testName = location.search.substr(1);
+ window[testName]().then(function() {
+ finish();
+ }, function(e) {
+ ok(false, "A promise was rejected: " + e);
+ finish();
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/context/parentsharedworker.js b/dom/workers/test/serviceworkers/fetch/context/parentsharedworker.js
new file mode 100644
index 000000000..eac8d5e71
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/parentsharedworker.js
@@ -0,0 +1,8 @@
+onconnect = function(e) {
+ e.ports[0].start();
+ var worker = new Worker("myworker?shared");
+ worker.onmessage = function(e2) {
+ e.ports[0].postMessage(e2.data);
+ self.close();
+ };
+};
diff --git a/dom/workers/test/serviceworkers/fetch/context/parentworker.js b/dom/workers/test/serviceworkers/fetch/context/parentworker.js
new file mode 100644
index 000000000..839fb6640
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/parentworker.js
@@ -0,0 +1,4 @@
+var worker = new Worker("myworker");
+worker.onmessage = function(e) {
+ postMessage(e.data);
+};
diff --git a/dom/workers/test/serviceworkers/fetch/context/ping.html b/dom/workers/test/serviceworkers/fetch/context/ping.html
new file mode 100644
index 000000000..b1bebe41e
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/ping.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script>
+ onload = function() {
+ document.querySelector("a").click();
+ };
+</script>
+<a ping="ping" href="fetch.txt">link</a>
diff --git a/dom/workers/test/serviceworkers/fetch/context/realaudio.ogg b/dom/workers/test/serviceworkers/fetch/context/realaudio.ogg
new file mode 100644
index 000000000..1a41623f8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/realaudio.ogg
Binary files differ
diff --git a/dom/workers/test/serviceworkers/fetch/context/realimg.jpg b/dom/workers/test/serviceworkers/fetch/context/realimg.jpg
new file mode 100644
index 000000000..5b920f7c0
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/realimg.jpg
Binary files differ
diff --git a/dom/workers/test/serviceworkers/fetch/context/register.html b/dom/workers/test/serviceworkers/fetch/context/register.html
new file mode 100644
index 000000000..6528d0eae
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/register.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+ function ok(v, msg) {
+ window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+ }
+
+ function done(reg) {
+ ok(reg.active, "The active worker should be available.");
+ window.parent.postMessage({status: "registrationdone"}, "*");
+ }
+
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("context_test.js", {scope: "."});
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/context/sharedworker.js b/dom/workers/test/serviceworkers/fetch/context/sharedworker.js
new file mode 100644
index 000000000..94dca5839
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/sharedworker.js
@@ -0,0 +1,5 @@
+onconnect = function(e) {
+ e.ports[0].start();
+ e.ports[0].postMessage("ack");
+ self.close();
+};
diff --git a/dom/workers/test/serviceworkers/fetch/context/unregister.html b/dom/workers/test/serviceworkers/fetch/context/unregister.html
new file mode 100644
index 000000000..1f13508fa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ registration.unregister().then(function(success) {
+ if (success) {
+ window.parent.postMessage({status: "unregistrationdone"}, "*");
+ }
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/context/worker.js b/dom/workers/test/serviceworkers/fetch/context/worker.js
new file mode 100644
index 000000000..e26e5bc69
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/worker.js
@@ -0,0 +1 @@
+postMessage("ack");
diff --git a/dom/workers/test/serviceworkers/fetch/context/xml.xml b/dom/workers/test/serviceworkers/fetch/context/xml.xml
new file mode 100644
index 000000000..69c64adf1
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/xml.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="xslt"?>
+<root/>
diff --git a/dom/workers/test/serviceworkers/fetch/deliver-gzip.sjs b/dom/workers/test/serviceworkers/fetch/deliver-gzip.sjs
new file mode 100644
index 000000000..abacdd2ad
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/deliver-gzip.sjs
@@ -0,0 +1,17 @@
+function handleRequest(request, response) {
+ // The string "hello" repeated 10 times followed by newline. Compressed using gzip.
+ var bytes = [0x1f, 0x8b, 0x08, 0x08, 0x4d, 0xe2, 0xf9, 0x54, 0x00, 0x03, 0x68,
+ 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0xcb, 0x48, 0xcd, 0xc9, 0xc9, 0xcf,
+ 0x20, 0x85, 0xe0, 0x02, 0x00, 0xf5, 0x4b, 0x38, 0xcf, 0x33, 0x00,
+ 0x00, 0x00];
+
+ response.setHeader("Content-Encoding", "gzip", false);
+ response.setHeader("Content-Length", "" + bytes.length, false);
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var bos = Components.classes["@mozilla.org/binaryoutputstream;1"]
+ .createInstance(Components.interfaces.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+
+ bos.writeByteArray(bytes, bytes.length);
+}
diff --git a/dom/workers/test/serviceworkers/fetch/fetch_tests.js b/dom/workers/test/serviceworkers/fetch/fetch_tests.js
new file mode 100644
index 000000000..54da1b66e
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/fetch_tests.js
@@ -0,0 +1,416 @@
+var origin = 'http://mochi.test:8888';
+
+function fetchXHRWithMethod(name, method, onload, onerror, headers) {
+ expectAsyncResult();
+
+ onload = onload || function() {
+ my_ok(false, "XHR load should not complete successfully");
+ finish();
+ };
+ onerror = onerror || function() {
+ my_ok(false, "XHR load for " + name + " should be intercepted successfully");
+ finish();
+ };
+
+ var x = new XMLHttpRequest();
+ x.open(method, name, true);
+ x.onload = function() { onload(x) };
+ x.onerror = function() { onerror(x) };
+ headers = headers || [];
+ headers.forEach(function(header) {
+ x.setRequestHeader(header[0], header[1]);
+ });
+ x.send();
+}
+
+var corsServerPath = '/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs';
+var corsServerURL = 'http://example.com' + corsServerPath;
+
+function redirectURL(hops) {
+ return hops[0].server + corsServerPath + "?hop=1&hops=" +
+ encodeURIComponent(hops.toSource());
+}
+
+function fetchXHR(name, onload, onerror, headers) {
+ return fetchXHRWithMethod(name, 'GET', onload, onerror, headers);
+}
+
+fetchXHR('bare-synthesized.txt', function(xhr) {
+ my_ok(xhr.status == 200, "load should be successful");
+ my_ok(xhr.responseText == "synthesized response body", "load should have synthesized response");
+ finish();
+});
+
+fetchXHR('test-respondwith-response.txt', function(xhr) {
+ my_ok(xhr.status == 200, "test-respondwith-response load should be successful");
+ my_ok(xhr.responseText == "test-respondwith-response response body", "load should have response");
+ finish();
+});
+
+fetchXHR('synthesized-404.txt', function(xhr) {
+ my_ok(xhr.status == 404, "load should 404");
+ my_ok(xhr.responseText == "synthesized response body", "404 load should have synthesized response");
+ finish();
+});
+
+fetchXHR('synthesized-headers.txt', function(xhr) {
+ my_ok(xhr.status == 200, "load should be successful");
+ my_ok(xhr.getResponseHeader("X-Custom-Greeting") === "Hello", "custom header should be set");
+ my_ok(xhr.responseText == "synthesized response body", "custom header load should have synthesized response");
+ finish();
+});
+
+fetchXHR('synthesized-redirect-real-file.txt', function(xhr) {
+dump("Got status AARRGH " + xhr.status + " " + xhr.responseText + "\n");
+ my_ok(xhr.status == 200, "load should be successful");
+ my_ok(xhr.responseText == "This is a real file.\n", "Redirect to real file should complete.");
+ finish();
+});
+
+fetchXHR('synthesized-redirect-twice-real-file.txt', function(xhr) {
+ my_ok(xhr.status == 200, "load should be successful");
+ my_ok(xhr.responseText == "This is a real file.\n", "Redirect to real file (twice) should complete.");
+ finish();
+});
+
+fetchXHR('synthesized-redirect-synthesized.txt', function(xhr) {
+ my_ok(xhr.status == 200, "synth+redirect+synth load should be successful");
+ my_ok(xhr.responseText == "synthesized response body", "load should have redirected+synthesized response");
+ finish();
+});
+
+fetchXHR('synthesized-redirect-twice-synthesized.txt', function(xhr) {
+ my_ok(xhr.status == 200, "synth+redirect+synth (twice) load should be successful");
+ my_ok(xhr.responseText == "synthesized response body", "load should have redirected+synthesized (twice) response");
+ finish();
+});
+
+fetchXHR('redirect.sjs', function(xhr) {
+ my_ok(xhr.status == 404, "redirected load should be uninterrupted");
+ finish();
+});
+
+fetchXHR('ignored.txt', function(xhr) {
+ my_ok(xhr.status == 404, "load should be uninterrupted");
+ finish();
+});
+
+fetchXHR('rejected.txt', null, function(xhr) {
+ my_ok(xhr.status == 0, "load should not complete");
+ finish();
+});
+
+fetchXHR('nonresponse.txt', null, function(xhr) {
+ my_ok(xhr.status == 0, "load should not complete");
+ finish();
+});
+
+fetchXHR('nonresponse2.txt', null, function(xhr) {
+ my_ok(xhr.status == 0, "load should not complete");
+ finish();
+});
+
+fetchXHR('nonpromise.txt', null, function(xhr) {
+ my_ok(xhr.status == 0, "load should not complete");
+ finish();
+});
+
+fetchXHR('headers.txt', function(xhr) {
+ my_ok(xhr.status == 200, "load should be successful");
+ my_ok(xhr.responseText == "1", "request header checks should have passed");
+ finish();
+}, null, [["X-Test1", "header1"], ["X-Test2", "header2"]]);
+
+fetchXHR('http://user:pass@mochi.test:8888/user-pass', function(xhr) {
+ my_ok(xhr.status == 200, "load should be successful");
+ my_ok(xhr.responseText == 'http://user:pass@mochi.test:8888/user-pass', 'The username and password should be preserved');
+ finish();
+});
+
+var expectedUncompressedResponse = "";
+for (var i = 0; i < 10; ++i) {
+ expectedUncompressedResponse += "hello";
+}
+expectedUncompressedResponse += "\n";
+
+// ServiceWorker does not intercept, at which point the network request should
+// be correctly decoded.
+fetchXHR('deliver-gzip.sjs', function(xhr) {
+ my_ok(xhr.status == 200, "network gzip load should be successful");
+ my_ok(xhr.responseText == expectedUncompressedResponse, "network gzip load should have synthesized response.");
+ my_ok(xhr.getResponseHeader("Content-Encoding") == "gzip", "network Content-Encoding should be gzip.");
+ my_ok(xhr.getResponseHeader("Content-Length") == "35", "network Content-Length should be of original gzipped file.");
+ finish();
+});
+
+fetchXHR('hello.gz', function(xhr) {
+ my_ok(xhr.status == 200, "gzip load should be successful");
+ my_ok(xhr.responseText == expectedUncompressedResponse, "gzip load should have synthesized response.");
+ my_ok(xhr.getResponseHeader("Content-Encoding") == "gzip", "Content-Encoding should be gzip.");
+ my_ok(xhr.getResponseHeader("Content-Length") == "35", "Content-Length should be of original gzipped file.");
+ finish();
+});
+
+fetchXHR('hello-after-extracting.gz', function(xhr) {
+ my_ok(xhr.status == 200, "gzip load after extracting should be successful");
+ my_ok(xhr.responseText == expectedUncompressedResponse, "gzip load after extracting should have synthesized response.");
+ my_ok(xhr.getResponseHeader("Content-Encoding") == "gzip", "Content-Encoding after extracting should be gzip.");
+ my_ok(xhr.getResponseHeader("Content-Length") == "35", "Content-Length after extracting should be of original gzipped file.");
+ finish();
+});
+
+fetchXHR(corsServerURL + '?status=200&allowOrigin=*', function(xhr) {
+ my_ok(xhr.status == 200, "cross origin load with correct headers should be successful");
+ my_ok(xhr.getResponseHeader("access-control-allow-origin") == null, "cors headers should be filtered out");
+ finish();
+});
+
+// Verify origin header is sent properly even when we have a no-intercept SW.
+var uriOrigin = encodeURIComponent(origin);
+fetchXHR('http://example.org' + corsServerPath + '?ignore&status=200&origin=' + uriOrigin +
+ '&allowOrigin=' + uriOrigin, function(xhr) {
+ my_ok(xhr.status == 200, "cross origin load with correct headers should be successful");
+ my_ok(xhr.getResponseHeader("access-control-allow-origin") == null, "cors headers should be filtered out");
+ finish();
+});
+
+// Verify that XHR is considered CORS tainted even when original URL is same-origin
+// redirected to cross-origin.
+fetchXHR(redirectURL([{ server: origin },
+ { server: 'http://example.org',
+ allowOrigin: origin }]), function(xhr) {
+ my_ok(xhr.status == 200, "cross origin load with correct headers should be successful");
+ my_ok(xhr.getResponseHeader("access-control-allow-origin") == null, "cors headers should be filtered out");
+ finish();
+});
+
+// Test that CORS preflight requests cannot be intercepted. Performs a
+// cross-origin XHR that the SW chooses not to intercept. This requires a
+// preflight request, which the SW must not be allowed to intercept.
+fetchXHR(corsServerURL + '?status=200&allowOrigin=*', null, function(xhr) {
+ my_ok(xhr.status == 0, "cross origin load with incorrect headers should be a failure");
+ finish();
+}, [["X-Unsafe", "unsafe"]]);
+
+// Test that CORS preflight requests cannot be intercepted. Performs a
+// cross-origin XHR that the SW chooses to intercept and respond with a
+// cross-origin fetch. This requires a preflight request, which the SW must not
+// be allowed to intercept.
+fetchXHR('http://example.org' + corsServerPath + '?status=200&allowOrigin=*', null, function(xhr) {
+ my_ok(xhr.status == 0, "cross origin load with incorrect headers should be a failure");
+ finish();
+}, [["X-Unsafe", "unsafe"]]);
+
+// Test that when the page fetches a url the controlling SW forces a redirect to
+// another location. This other location fetch should also be intercepted by
+// the SW.
+fetchXHR('something.txt', function(xhr) {
+ my_ok(xhr.status == 200, "load should be successful");
+ my_ok(xhr.responseText == "something else response body", "load should have something else");
+ finish();
+});
+
+// Test fetch will internally get it's SkipServiceWorker flag set. The request is
+// made from the SW through fetch(). fetch() fetches a server-side JavaScript
+// file that force a redirect. The redirect location fetch does not go through
+// the SW.
+fetchXHR('redirect_serviceworker.sjs', function(xhr) {
+ my_ok(xhr.status == 200, "load should be successful");
+ my_ok(xhr.responseText == "// empty worker, always succeed!\n", "load should have redirection content");
+ finish();
+});
+
+fetchXHR('empty-header', function(xhr) {
+ my_ok(xhr.status == 200, "load should be successful");
+ my_ok(xhr.responseText == "emptyheader", "load should have the expected content");
+ finish();
+}, null, [["emptyheader", ""]]);
+
+expectAsyncResult();
+fetch('http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*')
+.then(function(res) {
+ my_ok(res.ok, "Valid CORS request should receive valid response");
+ my_ok(res.type == "cors", "Response type should be CORS");
+ res.text().then(function(body) {
+ my_ok(body === "<res>hello pass</res>\n", "cors response body should match");
+ finish();
+ });
+}, function(e) {
+ my_ok(false, "CORS Fetch failed");
+ finish();
+});
+
+expectAsyncResult();
+fetch('http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200', { mode: 'no-cors' })
+.then(function(res) {
+ my_ok(res.type == "opaque", "Response type should be opaque");
+ my_ok(res.status == 0, "Status should be 0");
+ res.text().then(function(body) {
+ my_ok(body === "", "opaque response body should be empty");
+ finish();
+ });
+}, function(e) {
+ my_ok(false, "no-cors Fetch failed");
+ finish();
+});
+
+expectAsyncResult();
+fetch('opaque-on-same-origin')
+.then(function(res) {
+ my_ok(false, "intercepted opaque response for non no-cors request should fail.");
+ finish();
+}, function(e) {
+ my_ok(true, "intercepted opaque response for non no-cors request should fail.");
+ finish();
+});
+
+expectAsyncResult();
+fetch('http://example.com/opaque-no-cors', { mode: "no-cors" })
+.then(function(res) {
+ my_ok(res.type == "opaque", "intercepted opaque response for no-cors request should have type opaque.");
+ finish();
+}, function(e) {
+ my_ok(false, "intercepted opaque response for no-cors request should pass.");
+ finish();
+});
+
+expectAsyncResult();
+fetch('http://example.com/cors-for-no-cors', { mode: "no-cors" })
+.then(function(res) {
+ my_ok(res.type == "opaque", "intercepted non-opaque response for no-cors request should resolve to opaque response.");
+ finish();
+}, function(e) {
+ my_ok(false, "intercepted non-opaque response for no-cors request should resolve to opaque response. It should not fail.");
+ finish();
+});
+
+function arrayBufferFromString(str) {
+ var arr = new Uint8Array(str.length);
+ for (var i = 0; i < str.length; ++i) {
+ arr[i] = str.charCodeAt(i);
+ }
+ return arr;
+}
+
+expectAsyncResult();
+fetch(new Request('body-simple', {method: 'POST', body: 'my body'}))
+.then(function(res) {
+ return res.text();
+}).then(function(body) {
+ my_ok(body == 'my bodymy body', "the body of the intercepted fetch should be visible in the SW");
+ finish();
+});
+
+expectAsyncResult();
+fetch(new Request('body-arraybufferview', {method: 'POST', body: arrayBufferFromString('my body')}))
+.then(function(res) {
+ return res.text();
+}).then(function(body) {
+ my_ok(body == 'my bodymy body', "the ArrayBufferView body of the intercepted fetch should be visible in the SW");
+ finish();
+});
+
+expectAsyncResult();
+fetch(new Request('body-arraybuffer', {method: 'POST', body: arrayBufferFromString('my body').buffer}))
+.then(function(res) {
+ return res.text();
+}).then(function(body) {
+ my_ok(body == 'my bodymy body', "the ArrayBuffer body of the intercepted fetch should be visible in the SW");
+ finish();
+});
+
+expectAsyncResult();
+var usp = new URLSearchParams();
+usp.set("foo", "bar");
+usp.set("baz", "qux");
+fetch(new Request('body-urlsearchparams', {method: 'POST', body: usp}))
+.then(function(res) {
+ return res.text();
+}).then(function(body) {
+ my_ok(body == 'foo=bar&baz=quxfoo=bar&baz=qux', "the URLSearchParams body of the intercepted fetch should be visible in the SW");
+ finish();
+});
+
+expectAsyncResult();
+var fd = new FormData();
+fd.set("foo", "bar");
+fd.set("baz", "qux");
+fetch(new Request('body-formdata', {method: 'POST', body: fd}))
+.then(function(res) {
+ return res.text();
+}).then(function(body) {
+ my_ok(body.indexOf("Content-Disposition: form-data; name=\"foo\"\r\n\r\nbar") <
+ body.indexOf("Content-Disposition: form-data; name=\"baz\"\r\n\r\nqux"),
+ "the FormData body of the intercepted fetch should be visible in the SW");
+ finish();
+});
+
+expectAsyncResult();
+fetch(new Request('body-blob', {method: 'POST', body: new Blob(new String('my body'))}))
+.then(function(res) {
+ return res.text();
+}).then(function(body) {
+ my_ok(body == 'my bodymy body', "the Blob body of the intercepted fetch should be visible in the SW");
+ finish();
+});
+
+expectAsyncResult();
+fetch('interrupt.sjs')
+.then(function(res) {
+ my_ok(true, "interrupted fetch succeeded");
+ res.text().then(function(body) {
+ my_ok(false, "interrupted fetch shouldn't have complete body");
+ finish();
+ },
+ function() {
+ my_ok(true, "interrupted fetch shouldn't have complete body");
+ finish();
+ })
+}, function(e) {
+ my_ok(false, "interrupted fetch failed");
+ finish();
+});
+
+['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'].forEach(function(method) {
+ fetchXHRWithMethod('xhr-method-test.txt', method, function(xhr) {
+ my_ok(xhr.status == 200, method + " load should be successful");
+ my_ok(xhr.responseText == ("intercepted " + method), method + " load should have synthesized response");
+ finish();
+ });
+});
+
+expectAsyncResult();
+fetch(new Request('empty-header', {headers:{"emptyheader":""}}))
+.then(function(res) {
+ return res.text();
+}).then(function(body) {
+ my_ok(body == "emptyheader", "The empty header was observed in the fetch event");
+ finish();
+}, function(err) {
+ my_ok(false, "A promise was rejected with " + err);
+ finish();
+});
+
+expectAsyncResult();
+fetch('fetchevent-extendable')
+.then(function(res) {
+ return res.text();
+}).then(function(body) {
+ my_ok(body == "extendable", "FetchEvent inherits from ExtendableEvent");
+ finish();
+}, function(err) {
+ my_ok(false, "A promise was rejected with " + err);
+ finish();
+});
+
+expectAsyncResult();
+fetch('fetchevent-request')
+.then(function(res) {
+ return res.text();
+}).then(function(body) {
+ my_ok(body == "non-nullable", "FetchEvent.request must be non-nullable");
+ finish();
+}, function(err) {
+ my_ok(false, "A promise was rejected with " + err);
+ finish();
+});
diff --git a/dom/workers/test/serviceworkers/fetch/fetch_worker_script.js b/dom/workers/test/serviceworkers/fetch/fetch_worker_script.js
new file mode 100644
index 000000000..61efb647c
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/fetch_worker_script.js
@@ -0,0 +1,29 @@
+function my_ok(v, msg) {
+ postMessage({type: "ok", value: v, msg: msg});
+}
+
+function finish() {
+ postMessage('finish');
+}
+
+function expectAsyncResult() {
+ postMessage('expect');
+}
+
+expectAsyncResult();
+try {
+ var success = false;
+ importScripts("nonexistent_imported_script.js");
+} catch(x) {
+}
+
+my_ok(success, "worker imported script should be intercepted");
+finish();
+
+function check_intercepted_script() {
+ success = true;
+}
+
+importScripts('fetch_tests.js')
+
+finish(); //corresponds to the gExpected increment before creating this worker
diff --git a/dom/workers/test/serviceworkers/fetch/hsts/embedder.html b/dom/workers/test/serviceworkers/fetch/hsts/embedder.html
new file mode 100644
index 000000000..c98555423
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/embedder.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script>
+ window.onmessage = function(e) {
+ window.parent.postMessage(e.data, "*");
+ };
+</script>
+<iframe src="http://example.com/tests/dom/workers/test/serviceworkers/fetch/hsts/index.html"></iframe>
diff --git a/dom/workers/test/serviceworkers/fetch/hsts/hsts_test.js b/dom/workers/test/serviceworkers/fetch/hsts/hsts_test.js
new file mode 100644
index 000000000..ab54164ed
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/hsts_test.js
@@ -0,0 +1,11 @@
+self.addEventListener("fetch", function(event) {
+ if (event.request.url.indexOf("index.html") >= 0) {
+ event.respondWith(fetch("realindex.html"));
+ } else if (event.request.url.indexOf("image-20px.png") >= 0) {
+ if (event.request.url.indexOf("https://") == 0) {
+ event.respondWith(fetch("image-40px.png"));
+ } else {
+ event.respondWith(Response.error());
+ }
+ }
+});
diff --git a/dom/workers/test/serviceworkers/fetch/hsts/image-20px.png b/dom/workers/test/serviceworkers/fetch/hsts/image-20px.png
new file mode 100644
index 000000000..ae6a8a6b8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/image-20px.png
Binary files differ
diff --git a/dom/workers/test/serviceworkers/fetch/hsts/image-40px.png b/dom/workers/test/serviceworkers/fetch/hsts/image-40px.png
new file mode 100644
index 000000000..fe391dc8a
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/image-40px.png
Binary files differ
diff --git a/dom/workers/test/serviceworkers/fetch/hsts/image.html b/dom/workers/test/serviceworkers/fetch/hsts/image.html
new file mode 100644
index 000000000..cadbdef5a
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/image.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script>
+onload=function(){
+ var img = new Image();
+ img.src = "http://example.com/tests/dom/workers/test/serviceworkers/fetch/hsts/image-20px.png";
+ img.onload = function() {
+ window.parent.postMessage({status: "image", data: img.width}, "*");
+ };
+ img.onerror = function() {
+ window.parent.postMessage({status: "image", data: "error"}, "*");
+ };
+};
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/hsts/realindex.html b/dom/workers/test/serviceworkers/fetch/hsts/realindex.html
new file mode 100644
index 000000000..b3d1d527e
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/realindex.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script>
+ var securityInfoPresent = !!SpecialPowers.wrap(document).docShell.currentDocumentChannel.securityInfo;
+ window.parent.postMessage({status: "protocol",
+ data: location.protocol,
+ securityInfoPresent: securityInfoPresent},
+ "*");
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/hsts/register.html b/dom/workers/test/serviceworkers/fetch/hsts/register.html
new file mode 100644
index 000000000..bcdc146ae
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/register.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+ function ok(v, msg) {
+ window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+ }
+
+ function done(reg) {
+ ok(reg.active, "The active worker should be available.");
+ window.parent.postMessage({status: "registrationdone"}, "*");
+ }
+
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("hsts_test.js", {scope: "."});
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/hsts/register.html^headers^ b/dom/workers/test/serviceworkers/fetch/hsts/register.html^headers^
new file mode 100644
index 000000000..a46bf65bd
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/register.html^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-cache
+Strict-Transport-Security: max-age=60
diff --git a/dom/workers/test/serviceworkers/fetch/hsts/unregister.html b/dom/workers/test/serviceworkers/fetch/hsts/unregister.html
new file mode 100644
index 000000000..1f13508fa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ registration.unregister().then(function(success) {
+ if (success) {
+ window.parent.postMessage({status: "unregistrationdone"}, "*");
+ }
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/https/clonedresponse/https_test.js b/dom/workers/test/serviceworkers/fetch/https/clonedresponse/https_test.js
new file mode 100644
index 000000000..48f7b9307
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/https/clonedresponse/https_test.js
@@ -0,0 +1,15 @@
+self.addEventListener("install", function(event) {
+ event.waitUntil(caches.open("cache").then(function(cache) {
+ return cache.add("index.html");
+ }));
+});
+
+self.addEventListener("fetch", function(event) {
+ if (event.request.url.indexOf("index.html") >= 0) {
+ event.respondWith(new Promise(function(resolve, reject) {
+ caches.match(event.request).then(function(response) {
+ resolve(response.clone());
+ });
+ }));
+ }
+});
diff --git a/dom/workers/test/serviceworkers/fetch/https/clonedresponse/index.html b/dom/workers/test/serviceworkers/fetch/https/clonedresponse/index.html
new file mode 100644
index 000000000..a43554844
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/https/clonedresponse/index.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+ window.parent.postMessage({status: "done"}, "*");
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/https/clonedresponse/register.html b/dom/workers/test/serviceworkers/fetch/https/clonedresponse/register.html
new file mode 100644
index 000000000..41774f70d
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/https/clonedresponse/register.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+ function ok(v, msg) {
+ window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+ }
+
+ function done(reg) {
+ ok(reg.active, "The active worker should be available.");
+ window.parent.postMessage({status: "registrationdone"}, "*");
+ }
+
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("https_test.js", {scope: "."});
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/https/clonedresponse/unregister.html b/dom/workers/test/serviceworkers/fetch/https/clonedresponse/unregister.html
new file mode 100644
index 000000000..1f13508fa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/https/clonedresponse/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ registration.unregister().then(function(success) {
+ if (success) {
+ window.parent.postMessage({status: "unregistrationdone"}, "*");
+ }
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/https/https_test.js b/dom/workers/test/serviceworkers/fetch/https/https_test.js
new file mode 100644
index 000000000..6f87bb5ee
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/https/https_test.js
@@ -0,0 +1,23 @@
+self.addEventListener("install", function(event) {
+ event.waitUntil(caches.open("cache").then(function(cache) {
+ var synth = new Response('<!DOCTYPE html><script>window.parent.postMessage({status: "done-synth-sw"}, "*");</script>',
+ {headers:{"Content-Type": "text/html"}});
+ return Promise.all([
+ cache.add("index.html"),
+ cache.put("synth-sw.html", synth),
+ ]);
+ }));
+});
+
+self.addEventListener("fetch", function(event) {
+ if (event.request.url.indexOf("index.html") >= 0) {
+ event.respondWith(caches.match(event.request));
+ } else if (event.request.url.indexOf("synth-sw.html") >= 0) {
+ event.respondWith(caches.match(event.request));
+ } else if (event.request.url.indexOf("synth-window.html") >= 0) {
+ event.respondWith(caches.match(event.request));
+ } else if (event.request.url.indexOf("synth.html") >= 0) {
+ event.respondWith(new Response('<!DOCTYPE html><script>window.parent.postMessage({status: "done-synth"}, "*");</script>',
+ {headers:{"Content-Type": "text/html"}}));
+ }
+});
diff --git a/dom/workers/test/serviceworkers/fetch/https/index.html b/dom/workers/test/serviceworkers/fetch/https/index.html
new file mode 100644
index 000000000..a43554844
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/https/index.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+ window.parent.postMessage({status: "done"}, "*");
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/https/register.html b/dom/workers/test/serviceworkers/fetch/https/register.html
new file mode 100644
index 000000000..fa666fe95
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/https/register.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<script>
+ function ok(v, msg) {
+ window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+ }
+
+ function done(reg) {
+ ok(reg.active, "The active worker should be available.");
+ window.parent.postMessage({status: "registrationdone"}, "*");
+ }
+
+ navigator.serviceWorker.ready.then(reg => {
+ return window.caches.open("cache").then(function(cache) {
+ var synth = new Response('<!DOCTYPE html><script>window.parent.postMessage({status: "done-synth-window"}, "*");</scri' + 'pt>',
+ {headers:{"Content-Type": "text/html"}});
+ return cache.put('synth-window.html', synth).then(_ => done(reg));
+ });
+ });
+ navigator.serviceWorker.register("https_test.js", {scope: "."});
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/https/unregister.html b/dom/workers/test/serviceworkers/fetch/https/unregister.html
new file mode 100644
index 000000000..1f13508fa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/https/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ registration.unregister().then(function(success) {
+ if (success) {
+ window.parent.postMessage({status: "unregistrationdone"}, "*");
+ }
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/imagecache-maxage/image-20px.png b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/image-20px.png
new file mode 100644
index 000000000..ae6a8a6b8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/image-20px.png
Binary files differ
diff --git a/dom/workers/test/serviceworkers/fetch/imagecache-maxage/image-40px.png b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/image-40px.png
new file mode 100644
index 000000000..fe391dc8a
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/image-40px.png
Binary files differ
diff --git a/dom/workers/test/serviceworkers/fetch/imagecache-maxage/index.html b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/index.html
new file mode 100644
index 000000000..426c27a73
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/index.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<script>
+var width, url, width2, url2;
+function maybeReport() {
+ if (width !== undefined && url !== undefined &&
+ width2 !== undefined && url2 !== undefined) {
+ window.parent.postMessage({status: "result",
+ width: width,
+ width2: width2,
+ url: url,
+ url2: url2}, "*");
+ }
+}
+onload = function() {
+ width = document.querySelector("img").width;
+ width2 = document.querySelector("img").width;
+ maybeReport();
+};
+navigator.serviceWorker.onmessage = function(event) {
+ if (event.data.suffix == "2") {
+ url2 = event.data.url;
+ } else {
+ url = event.data.url;
+ }
+ maybeReport();
+};
+</script>
+<img src="image.png">
+<img src="image2.png">
diff --git a/dom/workers/test/serviceworkers/fetch/imagecache-maxage/maxage_test.js b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/maxage_test.js
new file mode 100644
index 000000000..1922111df
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/maxage_test.js
@@ -0,0 +1,41 @@
+function synthesizeImage(suffix) {
+ // Serve image-20px for the first page, and image-40px for the second page.
+ return clients.matchAll().then(clients => {
+ var url = "image-20px.png";
+ clients.forEach(client => {
+ if (client.url.indexOf("?new") > 0) {
+ url = "image-40px.png";
+ }
+ client.postMessage({suffix: suffix, url: url});
+ });
+ return fetch(url);
+ }).then(response => {
+ return response.arrayBuffer();
+ }).then(ab => {
+ var headers;
+ if (suffix == "") {
+ headers = {
+ "Content-Type": "image/png",
+ "Date": "Tue, 1 Jan 1990 01:02:03 GMT",
+ "Cache-Control": "max-age=1",
+ };
+ } else {
+ headers = {
+ "Content-Type": "image/png",
+ "Cache-Control": "no-cache",
+ };
+ }
+ return new Response(ab, {
+ status: 200,
+ headers: headers,
+ });
+ });
+}
+
+self.addEventListener("fetch", function(event) {
+ if (event.request.url.indexOf("image.png") >= 0) {
+ event.respondWith(synthesizeImage(""));
+ } else if (event.request.url.indexOf("image2.png") >= 0) {
+ event.respondWith(synthesizeImage("2"));
+ }
+});
diff --git a/dom/workers/test/serviceworkers/fetch/imagecache-maxage/register.html b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/register.html
new file mode 100644
index 000000000..af4dde2e2
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/register.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+ function ok(v, msg) {
+ window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+ }
+
+ function done(reg) {
+ ok(reg.active, "The active worker should be available.");
+ window.parent.postMessage({status: "registrationdone"}, "*");
+ }
+
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("maxage_test.js", {scope: "."});
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/imagecache-maxage/unregister.html b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/unregister.html
new file mode 100644
index 000000000..1f13508fa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ registration.unregister().then(function(success) {
+ if (success) {
+ window.parent.postMessage({status: "unregistrationdone"}, "*");
+ }
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/imagecache/image-20px.png b/dom/workers/test/serviceworkers/fetch/imagecache/image-20px.png
new file mode 100644
index 000000000..ae6a8a6b8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache/image-20px.png
Binary files differ
diff --git a/dom/workers/test/serviceworkers/fetch/imagecache/image-40px.png b/dom/workers/test/serviceworkers/fetch/imagecache/image-40px.png
new file mode 100644
index 000000000..fe391dc8a
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache/image-40px.png
Binary files differ
diff --git a/dom/workers/test/serviceworkers/fetch/imagecache/imagecache_test.js b/dom/workers/test/serviceworkers/fetch/imagecache/imagecache_test.js
new file mode 100644
index 000000000..598d8213f
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache/imagecache_test.js
@@ -0,0 +1,15 @@
+function synthesizeImage() {
+ return clients.matchAll().then(clients => {
+ var url = "image-40px.png";
+ clients.forEach(client => {
+ client.postMessage(url);
+ });
+ return fetch(url);
+ });
+}
+
+self.addEventListener("fetch", function(event) {
+ if (event.request.url.indexOf("image-20px.png") >= 0) {
+ event.respondWith(synthesizeImage());
+ }
+});
diff --git a/dom/workers/test/serviceworkers/fetch/imagecache/index.html b/dom/workers/test/serviceworkers/fetch/imagecache/index.html
new file mode 100644
index 000000000..93b30f184
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache/index.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<script>
+var width, url;
+function maybeReport() {
+ if (width !== undefined && url !== undefined) {
+ window.parent.postMessage({status: "result",
+ width: width,
+ url: url}, "*");
+ }
+}
+onload = function() {
+ width = document.querySelector("img").width;
+ maybeReport();
+};
+navigator.serviceWorker.onmessage = function(event) {
+ url = event.data;
+ maybeReport();
+};
+</script>
+<img src="image-20px.png">
diff --git a/dom/workers/test/serviceworkers/fetch/imagecache/postmortem.html b/dom/workers/test/serviceworkers/fetch/imagecache/postmortem.html
new file mode 100644
index 000000000..72a650d26
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache/postmortem.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script>
+onload = function() {
+ var width = document.querySelector("img").width;
+ window.parent.postMessage({status: "postmortem",
+ width: width}, "*");
+};
+</script>
+<img src="image-20px.png">
diff --git a/dom/workers/test/serviceworkers/fetch/imagecache/register.html b/dom/workers/test/serviceworkers/fetch/imagecache/register.html
new file mode 100644
index 000000000..f6d1eb382
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache/register.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<!-- Load the image here to put it in the image cache -->
+<img src="image-20px.png">
+<script>
+ function ok(v, msg) {
+ window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+ }
+
+ function done(reg) {
+ ok(reg.active, "The active worker should be available.");
+ window.parent.postMessage({status: "registrationdone"}, "*");
+ }
+
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("imagecache_test.js", {scope: "."});
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/imagecache/unregister.html b/dom/workers/test/serviceworkers/fetch/imagecache/unregister.html
new file mode 100644
index 000000000..1f13508fa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ registration.unregister().then(function(success) {
+ if (success) {
+ window.parent.postMessage({status: "unregistrationdone"}, "*");
+ }
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/importscript-mixedcontent/https_test.js b/dom/workers/test/serviceworkers/fetch/importscript-mixedcontent/https_test.js
new file mode 100644
index 000000000..0f08ba74e
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/importscript-mixedcontent/https_test.js
@@ -0,0 +1,28 @@
+function sendResponseToParent(response) {
+ return `
+ <!DOCTYPE html>
+ <script>
+ window.parent.postMessage({status: "done", data: "${response}"}, "*");
+ </script>
+ `;
+}
+
+self.addEventListener("fetch", function(event) {
+ if (event.request.url.indexOf("index.html") >= 0) {
+ var response = "good";
+ try {
+ importScripts("http://example.org/tests/dom/workers/test/foreign.js");
+ } catch(e) {
+ dump("Got error " + e + " when importing the script\n");
+ }
+ if (response === "good") {
+ try {
+ importScripts("/tests/dom/workers/test/redirect_to_foreign.sjs");
+ } catch(e) {
+ dump("Got error " + e + " when importing the script\n");
+ }
+ }
+ event.respondWith(new Response(sendResponseToParent(response),
+ {headers: {'Content-Type': 'text/html'}}));
+ }
+});
diff --git a/dom/workers/test/serviceworkers/fetch/importscript-mixedcontent/register.html b/dom/workers/test/serviceworkers/fetch/importscript-mixedcontent/register.html
new file mode 100644
index 000000000..41774f70d
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/importscript-mixedcontent/register.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+ function ok(v, msg) {
+ window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+ }
+
+ function done(reg) {
+ ok(reg.active, "The active worker should be available.");
+ window.parent.postMessage({status: "registrationdone"}, "*");
+ }
+
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("https_test.js", {scope: "."});
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/importscript-mixedcontent/unregister.html b/dom/workers/test/serviceworkers/fetch/importscript-mixedcontent/unregister.html
new file mode 100644
index 000000000..1f13508fa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/importscript-mixedcontent/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ registration.unregister().then(function(success) {
+ if (success) {
+ window.parent.postMessage({status: "unregistrationdone"}, "*");
+ }
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/index.html b/dom/workers/test/serviceworkers/fetch/index.html
new file mode 100644
index 000000000..4db0fb139
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/index.html
@@ -0,0 +1,183 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 94048 - test install event.</title>
+ <script type="text/javascript" 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>
+<div id="style-test" style="background-color: white"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+ function my_ok(result, msg) {
+ window.opener.postMessage({status: "ok", result: result, message: msg}, "*");
+ }
+
+ function check_intercepted_script() {
+ document.getElementById('intercepted-script').test_result =
+ document.currentScript == document.getElementById('intercepted-script');
+ }
+
+ function fetchXHR(name, onload, onerror, headers) {
+ gExpected++;
+
+ onload = onload || function() {
+ my_ok(false, "load should not complete successfully");
+ finish();
+ };
+ onerror = onerror || function() {
+ my_ok(false, "load should be intercepted successfully");
+ finish();
+ };
+
+ var x = new XMLHttpRequest();
+ x.open('GET', name, true);
+ x.onload = function() { onload(x) };
+ x.onerror = function() { onerror(x) };
+ headers = headers || [];
+ headers.forEach(function(header) {
+ x.setRequestHeader(header[0], header[1]);
+ });
+ x.send();
+ }
+
+ var gExpected = 0;
+ var gEncountered = 0;
+ function finish() {
+ gEncountered++;
+ if (gEncountered == gExpected) {
+ window.opener.postMessage({status: "done"}, "*");
+ }
+ }
+
+ function test_onload(creator, complete) {
+ gExpected++;
+ var elem = creator();
+ elem.onload = function() {
+ complete.call(elem);
+ finish();
+ };
+ elem.onerror = function() {
+ my_ok(false, elem.tagName + " load should complete successfully");
+ finish();
+ };
+ document.body.appendChild(elem);
+ }
+
+ function expectAsyncResult() {
+ gExpected++;
+ }
+
+ my_ok(navigator.serviceWorker.controller != null, "should be controlled");
+</script>
+<script src="fetch_tests.js"></script>
+<script>
+ test_onload(function() {
+ var elem = document.createElement('img');
+ elem.src = "nonexistent_image.gifs";
+ elem.id = 'intercepted-img';
+ return elem;
+ }, function() {
+ my_ok(this.complete, "image should be complete");
+ my_ok(this.naturalWidth == 1 && this.naturalHeight == 1, "image should be 1x1 gif");
+ });
+
+ test_onload(function() {
+ var elem = document.createElement('script');
+ elem.id = 'intercepted-script';
+ elem.src = "nonexistent_script.js";
+ return elem;
+ }, function() {
+ my_ok(this.test_result, "script load should be intercepted");
+ });
+
+ test_onload(function() {
+ var elem = document.createElement('link');
+ elem.href = "nonexistent_stylesheet.css";
+ elem.rel = "stylesheet";
+ return elem;
+ }, function() {
+ var styled = document.getElementById('style-test');
+ my_ok(window.getComputedStyle(styled).backgroundColor == 'rgb(0, 0, 0)',
+ "stylesheet load should be intercepted");
+ });
+
+ test_onload(function() {
+ var elem = document.createElement('iframe');
+ elem.id = 'intercepted-iframe';
+ elem.src = "nonexistent_page.html";
+ return elem;
+ }, function() {
+ my_ok(this.test_result, "iframe load should be intercepted");
+ });
+
+ test_onload(function() {
+ var elem = document.createElement('iframe');
+ elem.id = 'intercepted-iframe-2';
+ elem.src = "navigate.html";
+ return elem;
+ }, function() {
+ my_ok(this.test_result, "iframe should successfully load");
+ });
+
+ gExpected++;
+ var xmlDoc = document.implementation.createDocument(null, null, null);
+ xmlDoc.load('load_cross_origin_xml_document_synthetic.xml');
+ xmlDoc.onload = function(evt) {
+ var content = new XMLSerializer().serializeToString(evt.target);
+ my_ok(!content.includes('parsererror'), "Load synthetic cross origin XML Document should be allowed");
+ finish();
+ };
+
+ gExpected++;
+ var xmlDoc = document.implementation.createDocument(null, null, null);
+ xmlDoc.load('load_cross_origin_xml_document_cors.xml');
+ xmlDoc.onload = function(evt) {
+ var content = new XMLSerializer().serializeToString(evt.target);
+ my_ok(!content.includes('parsererror'), "Load CORS cross origin XML Document should be allowed");
+ finish();
+ };
+
+ gExpected++;
+ var xmlDoc = document.implementation.createDocument(null, null, null);
+ xmlDoc.load('load_cross_origin_xml_document_opaque.xml');
+ xmlDoc.onload = function(evt) {
+ var content = new XMLSerializer().serializeToString(evt.target);
+ my_ok(content.includes('parsererror'), "Load opaque cross origin XML Document should not be allowed");
+ finish();
+ };
+
+ gExpected++;
+ var worker = new Worker('nonexistent_worker_script.js');
+ worker.onmessage = function(e) {
+ my_ok(e.data == "worker-intercept-success", "worker load intercepted");
+ finish();
+ };
+ worker.onerror = function() {
+ my_ok(false, "worker load should be intercepted");
+ };
+
+ gExpected++;
+ var worker = new Worker('fetch_worker_script.js');
+ worker.onmessage = function(e) {
+ if (e.data == "finish") {
+ finish();
+ } else if (e.data == "expect") {
+ gExpected++;
+ } else if (e.data.type == "ok") {
+ my_ok(e.data.value, "Fetch test on worker: " + e.data.msg);
+ }
+ };
+ worker.onerror = function() {
+ my_ok(false, "worker should not cause any errors");
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/fetch/interrupt.sjs b/dom/workers/test/serviceworkers/fetch/interrupt.sjs
new file mode 100644
index 000000000..f6fe870ef
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/interrupt.sjs
@@ -0,0 +1,20 @@
+function handleRequest(request, response) {
+ var body = "a";
+ for (var i = 0; i < 20; i++) {
+ body += body;
+ }
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n")
+ var count = 10;
+ response.write("Content-Length: " + body.length * count + "\r\n");
+ response.write("Content-Type: text/plain; charset=utf-8\r\n");
+ response.write("Cache-Control: no-cache, must-revalidate\r\n");
+ response.write("\r\n");
+
+ for (var i = 0; i < count; i++) {
+ response.write(body);
+ }
+
+ throw Components.results.NS_BINDING_ABORTED;
+}
diff --git a/dom/workers/test/serviceworkers/fetch/origin/https/index-https.sjs b/dom/workers/test/serviceworkers/fetch/origin/https/index-https.sjs
new file mode 100644
index 000000000..7266925ea
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/origin/https/index-https.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setStatusLine(null, 308, "Permanent Redirect");
+ response.setHeader("Location", "https://example.org/tests/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html", false);
+}
diff --git a/dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js b/dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js
new file mode 100644
index 000000000..9839fc5f0
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js
@@ -0,0 +1,29 @@
+var prefix = "/tests/dom/workers/test/serviceworkers/fetch/origin/https/";
+
+function addOpaqueRedirect(cache, file) {
+ return fetch(new Request(prefix + file, { redirect: "manual" })).then(function(response) {
+ return cache.put(prefix + file, response);
+ });
+}
+
+self.addEventListener("install", function(event) {
+ event.waitUntil(
+ self.caches.open("origin-cache")
+ .then(c => {
+ return addOpaqueRedirect(c, 'index-https.sjs');
+ })
+ );
+});
+
+self.addEventListener("fetch", function(event) {
+ if (event.request.url.indexOf("index-cached-https.sjs") >= 0) {
+ event.respondWith(
+ self.caches.open("origin-cache")
+ .then(c => {
+ return c.match(prefix + 'index-https.sjs');
+ })
+ );
+ } else {
+ event.respondWith(fetch(event.request));
+ }
+});
diff --git a/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html b/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html
new file mode 100644
index 000000000..87f348945
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<script>
+ window.opener.postMessage({status: "domain", data: document.domain}, "*");
+ window.opener.postMessage({status: "origin", data: location.origin}, "*");
+ window.opener.postMessage({status: "done"}, "*");
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^ b/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^
new file mode 100644
index 000000000..5ed82fd06
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: https://example.com
diff --git a/dom/workers/test/serviceworkers/fetch/origin/https/register.html b/dom/workers/test/serviceworkers/fetch/origin/https/register.html
new file mode 100644
index 000000000..2e99adba5
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/origin/https/register.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+ function ok(v, msg) {
+ window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+ }
+
+ function done(reg) {
+ ok(reg.active, "The active worker should be available.");
+ window.parent.postMessage({status: "registrationdone"}, "*");
+ }
+
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("origin_test.js", {scope: "."});
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/origin/https/unregister.html b/dom/workers/test/serviceworkers/fetch/origin/https/unregister.html
new file mode 100644
index 000000000..1f13508fa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/origin/https/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ registration.unregister().then(function(success) {
+ if (success) {
+ window.parent.postMessage({status: "unregistrationdone"}, "*");
+ }
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/origin/index-to-https.sjs b/dom/workers/test/serviceworkers/fetch/origin/index-to-https.sjs
new file mode 100644
index 000000000..1cc916ff3
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/origin/index-to-https.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setStatusLine(null, 308, "Permanent Redirect");
+ response.setHeader("Location", "https://example.org/tests/dom/workers/test/serviceworkers/fetch/origin/realindex.html", false);
+}
diff --git a/dom/workers/test/serviceworkers/fetch/origin/index.sjs b/dom/workers/test/serviceworkers/fetch/origin/index.sjs
new file mode 100644
index 000000000..a79588e76
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/origin/index.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setStatusLine(null, 308, "Permanent Redirect");
+ response.setHeader("Location", "http://example.org/tests/dom/workers/test/serviceworkers/fetch/origin/realindex.html", false);
+}
diff --git a/dom/workers/test/serviceworkers/fetch/origin/origin_test.js b/dom/workers/test/serviceworkers/fetch/origin/origin_test.js
new file mode 100644
index 000000000..d2be9573b
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/origin/origin_test.js
@@ -0,0 +1,41 @@
+var prefix = "/tests/dom/workers/test/serviceworkers/fetch/origin/";
+
+function addOpaqueRedirect(cache, file) {
+ return fetch(new Request(prefix + file, { redirect: "manual" })).then(function(response) {
+ return cache.put(prefix + file, response);
+ });
+}
+
+self.addEventListener("install", function(event) {
+ event.waitUntil(
+ self.caches.open("origin-cache")
+ .then(c => {
+ return Promise.all(
+ [
+ addOpaqueRedirect(c, 'index.sjs'),
+ addOpaqueRedirect(c, 'index-to-https.sjs')
+ ]
+ );
+ })
+ );
+});
+
+self.addEventListener("fetch", function(event) {
+ if (event.request.url.indexOf("index-cached.sjs") >= 0) {
+ event.respondWith(
+ self.caches.open("origin-cache")
+ .then(c => {
+ return c.match(prefix + 'index.sjs');
+ })
+ );
+ } else if (event.request.url.indexOf("index-to-https-cached.sjs") >= 0) {
+ event.respondWith(
+ self.caches.open("origin-cache")
+ .then(c => {
+ return c.match(prefix + 'index-to-https.sjs');
+ })
+ );
+ } else {
+ event.respondWith(fetch(event.request));
+ }
+});
diff --git a/dom/workers/test/serviceworkers/fetch/origin/realindex.html b/dom/workers/test/serviceworkers/fetch/origin/realindex.html
new file mode 100644
index 000000000..87f348945
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/origin/realindex.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<script>
+ window.opener.postMessage({status: "domain", data: document.domain}, "*");
+ window.opener.postMessage({status: "origin", data: location.origin}, "*");
+ window.opener.postMessage({status: "done"}, "*");
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^ b/dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^
new file mode 100644
index 000000000..3a6a85d89
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: http://mochi.test:8888
diff --git a/dom/workers/test/serviceworkers/fetch/origin/register.html b/dom/workers/test/serviceworkers/fetch/origin/register.html
new file mode 100644
index 000000000..2e99adba5
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/origin/register.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+ function ok(v, msg) {
+ window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+ }
+
+ function done(reg) {
+ ok(reg.active, "The active worker should be available.");
+ window.parent.postMessage({status: "registrationdone"}, "*");
+ }
+
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("origin_test.js", {scope: "."});
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/origin/unregister.html b/dom/workers/test/serviceworkers/fetch/origin/unregister.html
new file mode 100644
index 000000000..1f13508fa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/origin/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ registration.unregister().then(function(success) {
+ if (success) {
+ window.parent.postMessage({status: "unregistrationdone"}, "*");
+ }
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/plugin/plugins.html b/dom/workers/test/serviceworkers/fetch/plugin/plugins.html
new file mode 100644
index 000000000..78e31b3c2
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/plugin/plugins.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<script>
+ var obj, embed;
+
+ function ok(v, msg) {
+ window.opener.postMessage({status: "ok", result: !!v, message: msg}, "*");
+ }
+
+ function finish() {
+ document.documentElement.removeChild(obj);
+ document.documentElement.removeChild(embed);
+ window.opener.postMessage({status: "done"}, "*");
+ }
+
+ function test_object() {
+ obj = document.createElement("object");
+ obj.setAttribute('data', "object");
+ document.documentElement.appendChild(obj);
+ }
+
+ function test_embed() {
+ embed = document.createElement("embed");
+ embed.setAttribute('src', "embed");
+ document.documentElement.appendChild(embed);
+ }
+
+ navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+ if (e.data.context === "object") {
+ ok(false, "<object> should not be intercepted");
+ } else if (e.data.context === "embed") {
+ ok(false, "<embed> should not be intercepted");
+ } else if (e.data.context === "fetch" && e.data.resource === "foo.txt") {
+ navigator.serviceWorker.removeEventListener("message", onMessage);
+ finish();
+ }
+ }, false);
+
+ test_object();
+ test_embed();
+ // SW will definitely intercept fetch API, use this to see if plugins are
+ // intercepted before fetch().
+ fetch("foo.txt");
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/plugin/worker.js b/dom/workers/test/serviceworkers/fetch/plugin/worker.js
new file mode 100644
index 000000000..e97d06205
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/plugin/worker.js
@@ -0,0 +1,14 @@
+self.addEventListener("fetch", function(event) {
+ var resource = event.request.url.split('/').pop();
+ event.waitUntil(
+ clients.matchAll()
+ .then(clients => {
+ clients.forEach(client => {
+ if (client.url.includes("plugins.html")) {
+ client.postMessage({context: event.request.context,
+ resource: resource});
+ }
+ });
+ })
+ );
+});
diff --git a/dom/workers/test/serviceworkers/fetch/real-file.txt b/dom/workers/test/serviceworkers/fetch/real-file.txt
new file mode 100644
index 000000000..3ca2088ec
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/real-file.txt
@@ -0,0 +1 @@
+This is a real file.
diff --git a/dom/workers/test/serviceworkers/fetch/redirect.sjs b/dom/workers/test/serviceworkers/fetch/redirect.sjs
new file mode 100644
index 000000000..dab558f4a
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/redirect.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ response.setHeader("Location", "synthesized-redirect-twice-real-file.txt");
+}
diff --git a/dom/workers/test/serviceworkers/fetch/requesturl/index.html b/dom/workers/test/serviceworkers/fetch/requesturl/index.html
new file mode 100644
index 000000000..bc3e400a9
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/requesturl/index.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.onmessage = window.onmessage = e => {
+ window.parent.postMessage(e.data, "*");
+ };
+</script>
+<iframe src="redirector.html"></iframe>
diff --git a/dom/workers/test/serviceworkers/fetch/requesturl/redirect.sjs b/dom/workers/test/serviceworkers/fetch/requesturl/redirect.sjs
new file mode 100644
index 000000000..7b92fec20
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/requesturl/redirect.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setStatusLine(null, 308, "Permanent Redirect");
+ response.setHeader("Location", "http://example.org/tests/dom/workers/test/serviceworkers/fetch/requesturl/secret.html", false);
+}
diff --git a/dom/workers/test/serviceworkers/fetch/requesturl/redirector.html b/dom/workers/test/serviceworkers/fetch/requesturl/redirector.html
new file mode 100644
index 000000000..73bf4af49
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/requesturl/redirector.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<meta http-equiv="refresh" content="3;URL=/tests/dom/workers/test/serviceworkers/fetch/requesturl/redirect.sjs">
diff --git a/dom/workers/test/serviceworkers/fetch/requesturl/register.html b/dom/workers/test/serviceworkers/fetch/requesturl/register.html
new file mode 100644
index 000000000..19a2e022c
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/requesturl/register.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+ function ok(v, msg) {
+ window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+ }
+
+ function done(reg) {
+ ok(reg.active, "The active worker should be available.");
+ window.parent.postMessage({status: "registrationdone"}, "*");
+ }
+
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("requesturl_test.js", {scope: "."});
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/requesturl/requesturl_test.js b/dom/workers/test/serviceworkers/fetch/requesturl/requesturl_test.js
new file mode 100644
index 000000000..c8be3daf4
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/requesturl/requesturl_test.js
@@ -0,0 +1,17 @@
+addEventListener("fetch", event => {
+ var url = event.request.url;
+ var badURL = url.indexOf("secret.html") > -1;
+ event.respondWith(
+ new Promise(resolve => {
+ clients.matchAll().then(clients => {
+ for (var client of clients) {
+ if (client.url.indexOf("index.html") > -1) {
+ client.postMessage({status: "ok", result: !badURL, message: "Should not find a bad URL (" + url + ")"});
+ break;
+ }
+ }
+ resolve(fetch(event.request));
+ });
+ })
+ );
+});
diff --git a/dom/workers/test/serviceworkers/fetch/requesturl/secret.html b/dom/workers/test/serviceworkers/fetch/requesturl/secret.html
new file mode 100644
index 000000000..694c33635
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/requesturl/secret.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+secret stuff
+<script>
+ window.parent.postMessage({status: "done"}, "*");
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/requesturl/unregister.html b/dom/workers/test/serviceworkers/fetch/requesturl/unregister.html
new file mode 100644
index 000000000..1f13508fa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/requesturl/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ registration.unregister().then(function(success) {
+ if (success) {
+ window.parent.postMessage({status: "unregistrationdone"}, "*");
+ }
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/sandbox/index.html b/dom/workers/test/serviceworkers/fetch/sandbox/index.html
new file mode 100644
index 000000000..1094a3995
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/sandbox/index.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<script>
+ window.parent.postMessage({status: "ok", result: true, message: "The iframe is not being intercepted"}, "*");
+ window.parent.postMessage({status: "done"}, "*");
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/sandbox/intercepted_index.html b/dom/workers/test/serviceworkers/fetch/sandbox/intercepted_index.html
new file mode 100644
index 000000000..87261a495
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/sandbox/intercepted_index.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<script>
+ window.parent.postMessage({status: "ok", result: false, message: "The iframe is being intercepted"}, "*");
+ window.parent.postMessage({status: "done"}, "*");
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/sandbox/register.html b/dom/workers/test/serviceworkers/fetch/sandbox/register.html
new file mode 100644
index 000000000..427b1a8da
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/sandbox/register.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+ function ok(v, msg) {
+ window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+ }
+
+ function done(reg) {
+ ok(reg.active, "The active worker should be available.");
+ window.parent.postMessage({status: "registrationdone"}, "*");
+ }
+
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("sandbox_test.js", {scope: "."});
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/sandbox/sandbox_test.js b/dom/workers/test/serviceworkers/fetch/sandbox/sandbox_test.js
new file mode 100644
index 000000000..1ed351794
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/sandbox/sandbox_test.js
@@ -0,0 +1,5 @@
+self.addEventListener("fetch", function(event) {
+ if (event.request.url.indexOf("index.html") >= 0) {
+ event.respondWith(fetch("intercepted_index.html"));
+ }
+});
diff --git a/dom/workers/test/serviceworkers/fetch/sandbox/unregister.html b/dom/workers/test/serviceworkers/fetch/sandbox/unregister.html
new file mode 100644
index 000000000..1f13508fa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/sandbox/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ registration.unregister().then(function(success) {
+ if (success) {
+ window.parent.postMessage({status: "unregistrationdone"}, "*");
+ }
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html
new file mode 100644
index 000000000..6098a45dd
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script>
+ window.onmessage = function(e) {
+ window.parent.postMessage(e.data, "*");
+ if (e.data.status == "protocol") {
+ document.querySelector("iframe").src = "image.html";
+ }
+ };
+</script>
+<iframe src="http://example.com/tests/dom/workers/test/serviceworkers/fetch/upgrade-insecure/index.html"></iframe>
diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html^headers^ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html^headers^
new file mode 100644
index 000000000..602d9dc38
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: upgrade-insecure-requests
diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-20px.png b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-20px.png
new file mode 100644
index 000000000..ae6a8a6b8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-20px.png
Binary files differ
diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-40px.png b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-40px.png
new file mode 100644
index 000000000..fe391dc8a
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-40px.png
Binary files differ
diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image.html b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image.html
new file mode 100644
index 000000000..34e24e35a
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script>
+onload=function(){
+ var img = new Image();
+ img.src = "http://example.com/tests/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-20px.png";
+ img.onload = function() {
+ window.parent.postMessage({status: "image", data: img.width}, "*");
+ };
+ img.onerror = function() {
+ window.parent.postMessage({status: "image", data: "error"}, "*");
+ };
+};
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/realindex.html b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/realindex.html
new file mode 100644
index 000000000..aaa255aad
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/realindex.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+ window.parent.postMessage({status: "protocol", data: location.protocol}, "*");
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/register.html b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/register.html
new file mode 100644
index 000000000..6309b9b21
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/register.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+ function ok(v, msg) {
+ window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+ }
+
+ function done(reg) {
+ ok(reg.active, "The active worker should be available.");
+ window.parent.postMessage({status: "registrationdone"}, "*");
+ }
+
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("upgrade-insecure_test.js", {scope: "."});
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/unregister.html b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/unregister.html
new file mode 100644
index 000000000..1f13508fa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ registration.unregister().then(function(success) {
+ if (success) {
+ window.parent.postMessage({status: "unregistrationdone"}, "*");
+ }
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/fetch/upgrade-insecure/upgrade-insecure_test.js b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/upgrade-insecure_test.js
new file mode 100644
index 000000000..ab54164ed
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/upgrade-insecure_test.js
@@ -0,0 +1,11 @@
+self.addEventListener("fetch", function(event) {
+ if (event.request.url.indexOf("index.html") >= 0) {
+ event.respondWith(fetch("realindex.html"));
+ } else if (event.request.url.indexOf("image-20px.png") >= 0) {
+ if (event.request.url.indexOf("https://") == 0) {
+ event.respondWith(fetch("image-40px.png"));
+ } else {
+ event.respondWith(Response.error());
+ }
+ }
+});
diff --git a/dom/workers/test/serviceworkers/fetch_event_worker.js b/dom/workers/test/serviceworkers/fetch_event_worker.js
new file mode 100644
index 000000000..1caef71e8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch_event_worker.js
@@ -0,0 +1,337 @@
+var seenIndex = false;
+
+onfetch = function(ev) {
+ if (ev.request.url.includes("ignore")) {
+ return;
+ }
+
+ if (ev.request.url.includes("bare-synthesized.txt")) {
+ ev.respondWith(Promise.resolve(
+ new Response("synthesized response body", {})
+ ));
+ }
+
+ else if (ev.request.url.includes('file_CrossSiteXHR_server.sjs')) {
+ // N.B. this response would break the rules of CORS if it were allowed, but
+ // this test relies upon the preflight request not being intercepted and
+ // thus this response should not be used.
+ if (ev.request.method == 'OPTIONS') {
+ ev.respondWith(new Response('', {headers: {'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Headers': 'X-Unsafe'}}))
+ } else if (ev.request.url.includes('example.org')) {
+ ev.respondWith(fetch(ev.request));
+ }
+ }
+
+ else if (ev.request.url.includes("synthesized-404.txt")) {
+ ev.respondWith(Promise.resolve(
+ new Response("synthesized response body", { status: 404 })
+ ));
+ }
+
+ else if (ev.request.url.includes("synthesized-headers.txt")) {
+ ev.respondWith(Promise.resolve(
+ new Response("synthesized response body", {
+ headers: {
+ "X-Custom-Greeting": "Hello"
+ }
+ })
+ ));
+ }
+
+ else if (ev.request.url.includes("test-respondwith-response.txt")) {
+ ev.respondWith(new Response("test-respondwith-response response body", {}));
+ }
+
+ else if (ev.request.url.includes("synthesized-redirect-real-file.txt")) {
+ ev.respondWith(Promise.resolve(
+ Response.redirect("fetch/real-file.txt")
+ ));
+ }
+
+ else if (ev.request.url.includes("synthesized-redirect-twice-real-file.txt")) {
+ ev.respondWith(Promise.resolve(
+ Response.redirect("synthesized-redirect-real-file.txt")
+ ));
+ }
+
+ else if (ev.request.url.includes("synthesized-redirect-synthesized.txt")) {
+ ev.respondWith(Promise.resolve(
+ Response.redirect("bare-synthesized.txt")
+ ));
+ }
+
+ else if (ev.request.url.includes("synthesized-redirect-twice-synthesized.txt")) {
+ ev.respondWith(Promise.resolve(
+ Response.redirect("synthesized-redirect-synthesized.txt")
+ ));
+ }
+
+ else if (ev.request.url.includes("rejected.txt")) {
+ ev.respondWith(Promise.reject());
+ }
+
+ else if (ev.request.url.includes("nonresponse.txt")) {
+ ev.respondWith(Promise.resolve(5));
+ }
+
+ else if (ev.request.url.includes("nonresponse2.txt")) {
+ ev.respondWith(Promise.resolve({}));
+ }
+
+ else if (ev.request.url.includes("nonpromise.txt")) {
+ try {
+ // This should coerce to Promise(5) instead of throwing
+ ev.respondWith(5);
+ } catch (e) {
+ // test is expecting failure, so return a success if we get a thrown
+ // exception
+ ev.respondWith(new Response('respondWith(5) threw ' + e));
+ }
+ }
+
+ else if (ev.request.url.includes("headers.txt")) {
+ var ok = true;
+ ok &= ev.request.headers.get("X-Test1") == "header1";
+ ok &= ev.request.headers.get("X-Test2") == "header2";
+ ev.respondWith(Promise.resolve(
+ new Response(ok.toString(), {})
+ ));
+ }
+
+ else if (ev.request.url.includes('user-pass')) {
+ ev.respondWith(new Response(ev.request.url));
+ }
+
+ else if (ev.request.url.includes("nonexistent_image.gif")) {
+ var imageAsBinaryString = atob("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs");
+ var imageLength = imageAsBinaryString.length;
+
+ // If we just pass |imageAsBinaryString| to the Response constructor, an
+ // encoding conversion occurs that corrupts the image. Instead, we need to
+ // convert it to a typed array.
+ // typed array.
+ var imageAsArray = new Uint8Array(imageLength);
+ for (var i = 0; i < imageLength; ++i) {
+ imageAsArray[i] = imageAsBinaryString.charCodeAt(i);
+ }
+
+ ev.respondWith(Promise.resolve(
+ new Response(imageAsArray, { headers: { "Content-Type": "image/gif" } })
+ ));
+ }
+
+ else if (ev.request.url.includes("nonexistent_script.js")) {
+ ev.respondWith(Promise.resolve(
+ new Response("check_intercepted_script();", {})
+ ));
+ }
+
+ else if (ev.request.url.includes("nonexistent_stylesheet.css")) {
+ ev.respondWith(Promise.resolve(
+ new Response("#style-test { background-color: black !important; }", {
+ headers : {
+ "Content-Type": "text/css"
+ }
+ })
+ ));
+ }
+
+ else if (ev.request.url.includes("nonexistent_page.html")) {
+ ev.respondWith(Promise.resolve(
+ new Response("<script>window.frameElement.test_result = true;</script>", {
+ headers : {
+ "Content-Type": "text/html"
+ }
+ })
+ ));
+ }
+
+ else if (ev.request.url.includes("navigate.html")) {
+ var navigateModeCorrectlyChecked = false;
+ var requests = [ // should not throw
+ new Request(ev.request),
+ new Request(ev.request, undefined),
+ new Request(ev.request, null),
+ new Request(ev.request, {}),
+ new Request(ev.request, {someUnrelatedProperty: 42}),
+ ];
+ try {
+ var request3 = new Request(ev.request, {method: "GET"}); // should throw
+ } catch(e) {
+ navigateModeCorrectlyChecked = requests[0].mode == "navigate";
+ }
+ if (navigateModeCorrectlyChecked) {
+ ev.respondWith(Promise.resolve(
+ new Response("<script>window.frameElement.test_result = true;</script>", {
+ headers : {
+ "Content-Type": "text/html"
+ }
+ })
+ ));
+ }
+ }
+
+ else if (ev.request.url.includes("nonexistent_worker_script.js")) {
+ ev.respondWith(Promise.resolve(
+ new Response("postMessage('worker-intercept-success')", {})
+ ));
+ }
+
+ else if (ev.request.url.includes("nonexistent_imported_script.js")) {
+ ev.respondWith(Promise.resolve(
+ new Response("check_intercepted_script();", {})
+ ));
+ }
+
+ else if (ev.request.url.includes("deliver-gzip")) {
+ // Don't handle the request, this will make Necko perform a network request, at
+ // which point SetApplyConversion must be re-enabled, otherwise the request
+ // will fail.
+ return;
+ }
+
+ else if (ev.request.url.includes("hello.gz")) {
+ ev.respondWith(fetch("fetch/deliver-gzip.sjs"));
+ }
+
+ else if (ev.request.url.includes("hello-after-extracting.gz")) {
+ ev.respondWith(fetch("fetch/deliver-gzip.sjs").then(function(res) {
+ return res.text().then(function(body) {
+ return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });
+ });
+ }));
+ }
+
+ else if (ev.request.url.includes('opaque-on-same-origin')) {
+ var url = 'http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200';
+ ev.respondWith(fetch(url, { mode: 'no-cors' }));
+ }
+
+ else if (ev.request.url.includes('opaque-no-cors')) {
+ if (ev.request.mode != "no-cors") {
+ ev.respondWith(Promise.reject());
+ return;
+ }
+
+ var url = 'http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200';
+ ev.respondWith(fetch(url, { mode: ev.request.mode }));
+ }
+
+ else if (ev.request.url.includes('cors-for-no-cors')) {
+ if (ev.request.mode != "no-cors") {
+ ev.respondWith(Promise.reject());
+ return;
+ }
+
+ var url = 'http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*';
+ ev.respondWith(fetch(url));
+ }
+
+ else if (ev.request.url.includes('example.com')) {
+ ev.respondWith(fetch(ev.request));
+ }
+
+ else if (ev.request.url.includes("index.html")) {
+ if (seenIndex) {
+ var body = "<script>" +
+ "opener.postMessage({status: 'ok', result: " + ev.isReload + "," +
+ "message: 'reload status should be indicated'}, '*');" +
+ "opener.postMessage({status: 'done'}, '*');" +
+ "</script>";
+ ev.respondWith(new Response(body, {headers: {'Content-Type': 'text/html'}}));
+ } else {
+ seenIndex = true;
+ ev.respondWith(fetch(ev.request.url));
+ }
+ }
+
+ else if (ev.request.url.includes("body-")) {
+ ev.respondWith(ev.request.text().then(function (body) {
+ return new Response(body + body);
+ }));
+ }
+
+ else if (ev.request.url.includes('something.txt')) {
+ ev.respondWith(Response.redirect('fetch/somethingelse.txt'));
+ }
+
+ else if (ev.request.url.includes('somethingelse.txt')) {
+ ev.respondWith(new Response('something else response body', {}));
+ }
+
+ else if (ev.request.url.includes('redirect_serviceworker.sjs')) {
+ // The redirect_serviceworker.sjs server-side JavaScript file redirects to
+ // 'http://mochi.test:8888/tests/dom/workers/test/serviceworkers/worker.js'
+ // The redirected fetch should not go through the SW since the original
+ // fetch was initiated from a SW.
+ ev.respondWith(fetch('redirect_serviceworker.sjs'));
+ }
+
+ else if (ev.request.url.includes('load_cross_origin_xml_document_synthetic.xml')) {
+ if (ev.request.mode != 'same-origin') {
+ ev.respondWith(Promise.reject());
+ return;
+ }
+
+ ev.respondWith(Promise.resolve(
+ new Response("<response>body</response>", { headers: {'Content-Type': 'text/xtml'}})
+ ));
+ }
+
+ else if (ev.request.url.includes('load_cross_origin_xml_document_cors.xml')) {
+ if (ev.request.mode != 'same-origin') {
+ ev.respondWith(Promise.reject());
+ return;
+ }
+
+ var url = 'http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*';
+ ev.respondWith(fetch(url, { mode: 'cors' }));
+ }
+
+ else if (ev.request.url.includes('load_cross_origin_xml_document_opaque.xml')) {
+ if (ev.request.mode != 'same-origin') {
+ Promise.resolve(
+ new Response("<error>Invalid Request mode</error>", { headers: {'Content-Type': 'text/xtml'}})
+ );
+ return;
+ }
+
+ var url = 'http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200';
+ ev.respondWith(fetch(url, { mode: 'no-cors' }));
+ }
+
+ else if (ev.request.url.includes('xhr-method-test.txt')) {
+ ev.respondWith(new Response('intercepted ' + ev.request.method));
+ }
+
+ else if (ev.request.url.includes('empty-header')) {
+ if (!ev.request.headers.has("emptyheader") ||
+ ev.request.headers.get("emptyheader") !== "") {
+ ev.respondWith(Promise.reject());
+ return;
+ }
+ ev.respondWith(new Response("emptyheader"));
+ }
+
+ else if (ev.request.url.includes('fetchevent-extendable')) {
+ if (ev instanceof ExtendableEvent) {
+ ev.respondWith(new Response("extendable"));
+ } else {
+ ev.respondWith(Promise.reject());
+ }
+ }
+
+ else if (ev.request.url.includes('fetchevent-request')) {
+ var threw = false;
+ try {
+ new FetchEvent("foo");
+ } catch(e) {
+ if (e.name == "TypeError") {
+ threw = true;
+ }
+ } finally {
+ ev.respondWith(new Response(threw ? "non-nullable" : "nullable"));
+ }
+ }
+};
diff --git a/dom/workers/test/serviceworkers/file_blob_response_worker.js b/dom/workers/test/serviceworkers/file_blob_response_worker.js
new file mode 100644
index 000000000..4b4379d0b
--- /dev/null
+++ b/dom/workers/test/serviceworkers/file_blob_response_worker.js
@@ -0,0 +1,38 @@
+function makeFileBlob(obj) {
+ return new Promise(function(resolve, reject) {
+ var request = indexedDB.open('file_blob_response_worker', 1);
+ request.onerror = reject;
+ request.onupgradeneeded = function(evt) {
+ var db = evt.target.result;
+ db.onerror = reject;
+
+ var objectStore = db.createObjectStore('test', { autoIncrement: true });
+ var index = objectStore.createIndex('test', 'index');
+ };
+
+ request.onsuccess = function(evt) {
+ var db = evt.target.result;
+ db.onerror = reject;
+
+ var blob = new Blob([JSON.stringify(obj)],
+ { type: 'application/json' });
+ var data = { blob: blob, index: 5 };
+
+ objectStore = db.transaction('test', 'readwrite').objectStore('test');
+ objectStore.add(data).onsuccess = function(evt) {
+ var key = evt.target.result;
+ objectStore = db.transaction('test').objectStore('test');
+ objectStore.get(key).onsuccess = function(evt) {
+ resolve(evt.target.result.blob);
+ };
+ };
+ };
+ });
+}
+
+self.addEventListener('fetch', function(evt) {
+ var result = { value: 'success' };
+ evt.respondWith(makeFileBlob(result).then(function(blob) {
+ return new Response(blob)
+ }));
+});
diff --git a/dom/workers/test/serviceworkers/force_refresh_browser_worker.js b/dom/workers/test/serviceworkers/force_refresh_browser_worker.js
new file mode 100644
index 000000000..96d9d0f17
--- /dev/null
+++ b/dom/workers/test/serviceworkers/force_refresh_browser_worker.js
@@ -0,0 +1,34 @@
+var name = 'browserRefresherCache';
+
+self.addEventListener('install', function(event) {
+ event.waitUntil(
+ Promise.all([caches.open(name),
+ fetch('./browser_cached_force_refresh.html')]).then(function(results) {
+ var cache = results[0];
+ var response = results[1];
+ return cache.put('./browser_base_force_refresh.html', response);
+ })
+ );
+});
+
+self.addEventListener('fetch', function (event) {
+ event.respondWith(
+ caches.open(name).then(function(cache) {
+ return cache.match(event.request);
+ }).then(function(response) {
+ return response || fetch(event.request);
+ })
+ );
+});
+
+self.addEventListener('message', function (event) {
+ if (event.data.type === 'GET_UNCONTROLLED_CLIENTS') {
+ event.waitUntil(clients.matchAll({ includeUncontrolled: true })
+ .then(function(clientList) {
+ var resultList = clientList.map(function(c) {
+ return { url: c.url, frameType: c.frameType };
+ });
+ event.source.postMessage({ type: 'CLIENTS', detail: resultList });
+ }));
+ }
+});
diff --git a/dom/workers/test/serviceworkers/force_refresh_worker.js b/dom/workers/test/serviceworkers/force_refresh_worker.js
new file mode 100644
index 000000000..f0752d0cb
--- /dev/null
+++ b/dom/workers/test/serviceworkers/force_refresh_worker.js
@@ -0,0 +1,33 @@
+var name = 'refresherCache';
+
+self.addEventListener('install', function(event) {
+ event.waitUntil(
+ Promise.all([caches.open(name),
+ fetch('./sw_clients/refresher_cached.html'),
+ fetch('./sw_clients/refresher_cached_compressed.html')]).then(function(results) {
+ var cache = results[0];
+ var response = results[1];
+ var compressed = results[2];
+ return Promise.all([cache.put('./sw_clients/refresher.html', response),
+ cache.put('./sw_clients/refresher_compressed.html', compressed)]);
+ })
+ );
+});
+
+self.addEventListener('fetch', function (event) {
+ event.respondWith(
+ caches.open(name).then(function(cache) {
+ return cache.match(event.request);
+ }).then(function(response) {
+ // If this is one of our primary cached responses, then the window
+ // must have generated the request via a normal window reload. That
+ // should be detectable in the event.request.cache attribute.
+ if (response && event.request.cache !== 'no-cache') {
+ dump('### ### FetchEvent.request.cache is "' + event.request.cache +
+ '" instead of expected "no-cache"\n');
+ return Response.error();
+ }
+ return response || fetch(event.request);
+ })
+ );
+});
diff --git a/dom/workers/test/serviceworkers/gzip_redirect_worker.js b/dom/workers/test/serviceworkers/gzip_redirect_worker.js
new file mode 100644
index 000000000..72aeba222
--- /dev/null
+++ b/dom/workers/test/serviceworkers/gzip_redirect_worker.js
@@ -0,0 +1,13 @@
+self.addEventListener('fetch', function (event) {
+ if (!event.request.url.endsWith('sw_clients/does_not_exist.html')) {
+ return;
+ }
+
+ event.respondWith(new Response('', {
+ status: 301,
+ statusText: 'Moved Permanently',
+ headers: {
+ 'Location': 'refresher_compressed.html'
+ }
+ }));
+});
diff --git a/dom/workers/test/serviceworkers/header_checker.sjs b/dom/workers/test/serviceworkers/header_checker.sjs
new file mode 100644
index 000000000..706104103
--- /dev/null
+++ b/dom/workers/test/serviceworkers/header_checker.sjs
@@ -0,0 +1,9 @@
+function handleRequest(request, response) {
+ if (request.getHeader("Service-Worker") === "script") {
+ response.setStatusLine("1.1", 200, "OK");
+ response.setHeader("Content-Type", "text/javascript");
+ response.write("// empty");
+ } else {
+ response.setStatusLine("1.1", 404, "Not Found");
+ }
+}
diff --git a/dom/workers/test/serviceworkers/hello.html b/dom/workers/test/serviceworkers/hello.html
new file mode 100644
index 000000000..97eb03c90
--- /dev/null
+++ b/dom/workers/test/serviceworkers/hello.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ Hello.
+ </body>
+<html>
diff --git a/dom/workers/test/serviceworkers/importscript.sjs b/dom/workers/test/serviceworkers/importscript.sjs
new file mode 100644
index 000000000..6d177a734
--- /dev/null
+++ b/dom/workers/test/serviceworkers/importscript.sjs
@@ -0,0 +1,11 @@
+function handleRequest(request, response) {
+ if (request.queryString == 'clearcounter') {
+ setState('counter', '');
+ } else if (!getState('counter')) {
+ response.setHeader("Content-Type", "application/javascript", false);
+ response.write("callByScript();");
+ setState('counter', '1');
+ } else {
+ response.write("no cache no party!");
+ }
+}
diff --git a/dom/workers/test/serviceworkers/importscript_worker.js b/dom/workers/test/serviceworkers/importscript_worker.js
new file mode 100644
index 000000000..3cddec194
--- /dev/null
+++ b/dom/workers/test/serviceworkers/importscript_worker.js
@@ -0,0 +1,37 @@
+var counter = 0;
+function callByScript() {
+ ++counter;
+}
+
+// Use multiple scripts in this load to verify we support that case correctly.
+// See bug 1249351 for a case where we broke this.
+importScripts('lorem_script.js', 'importscript.sjs');
+
+importScripts('importscript.sjs');
+
+var missingScriptFailed = false;
+try {
+ importScripts(['there-is-nothing-here.js']);
+} catch(e) {
+ missingScriptFailed = true;
+}
+
+onmessage = function(e) {
+ self.clients.matchAll().then(function(res) {
+ if (!res.length) {
+ dump("ERROR: no clients are currently controlled.\n");
+ }
+
+ if (!missingScriptFailed) {
+ res[0].postMessage("KO");
+ }
+
+ try {
+ importScripts(['importscript.sjs']);
+ res[0].postMessage("KO");
+ return;
+ } catch(e) {}
+
+ res[0].postMessage(counter == 2 ? "OK" : "KO");
+ });
+};
diff --git a/dom/workers/test/serviceworkers/install_event_error_worker.js b/dom/workers/test/serviceworkers/install_event_error_worker.js
new file mode 100644
index 000000000..c06d648b8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/install_event_error_worker.js
@@ -0,0 +1,4 @@
+// Worker that errors on receiving an install event.
+oninstall = function(e) {
+ undefined.doSomething;
+};
diff --git a/dom/workers/test/serviceworkers/install_event_worker.js b/dom/workers/test/serviceworkers/install_event_worker.js
new file mode 100644
index 000000000..f965d28aa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/install_event_worker.js
@@ -0,0 +1,3 @@
+oninstall = function(e) {
+ dump("Got install event\n");
+}
diff --git a/dom/workers/test/serviceworkers/lorem_script.js b/dom/workers/test/serviceworkers/lorem_script.js
new file mode 100644
index 000000000..5502a44da
--- /dev/null
+++ b/dom/workers/test/serviceworkers/lorem_script.js
@@ -0,0 +1,8 @@
+var lorem_str = `
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
+nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
+dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident,
+sunt in culpa qui officia deserunt mollit anim id est laborum.
+`
diff --git a/dom/workers/test/serviceworkers/match_all_advanced_worker.js b/dom/workers/test/serviceworkers/match_all_advanced_worker.js
new file mode 100644
index 000000000..3721aedfe
--- /dev/null
+++ b/dom/workers/test/serviceworkers/match_all_advanced_worker.js
@@ -0,0 +1,5 @@
+onmessage = function(e) {
+ self.clients.matchAll().then(function(clients) {
+ e.source.postMessage(clients.length);
+ });
+}
diff --git a/dom/workers/test/serviceworkers/match_all_client/match_all_client_id.html b/dom/workers/test/serviceworkers/match_all_client/match_all_client_id.html
new file mode 100644
index 000000000..7ac6fc9d0
--- /dev/null
+++ b/dom/workers/test/serviceworkers/match_all_client/match_all_client_id.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>Bug 1139425 - controlled page</title>
+<script class="testbody" type="text/javascript">
+ var testWindow = parent;
+ if (opener) {
+ testWindow = opener;
+ }
+
+ window.onload = function() {
+ navigator.serviceWorker.ready.then(function(swr) {
+ swr.active.postMessage("Start");
+ });
+ }
+
+ navigator.serviceWorker.onmessage = function(msg) {
+ // worker message;
+ testWindow.postMessage(msg.data, "*");
+ window.close();
+ };
+</script>
+
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/match_all_client_id_worker.js b/dom/workers/test/serviceworkers/match_all_client_id_worker.js
new file mode 100644
index 000000000..a7d9ff594
--- /dev/null
+++ b/dom/workers/test/serviceworkers/match_all_client_id_worker.js
@@ -0,0 +1,28 @@
+onmessage = function(e) {
+ dump("MatchAllClientIdWorker:" + e.data + "\n");
+ var id = [];
+ var iterations = 5;
+ var counter = 0;
+
+ for (var i = 0; i < iterations; i++) {
+ self.clients.matchAll().then(function(res) {
+ if (!res.length) {
+ dump("ERROR: no clients are currently controlled.\n");
+ }
+
+ client = res[0];
+ id[counter] = client.id;
+ counter++;
+ if (counter >= iterations) {
+ var response = true;
+ for (var index = 1; index < iterations; index++) {
+ if (id[0] != id[index]) {
+ response = false;
+ break;
+ }
+ }
+ client.postMessage(response);
+ }
+ });
+ }
+}
diff --git a/dom/workers/test/serviceworkers/match_all_clients/match_all_controlled.html b/dom/workers/test/serviceworkers/match_all_clients/match_all_controlled.html
new file mode 100644
index 000000000..25317b9fc
--- /dev/null
+++ b/dom/workers/test/serviceworkers/match_all_clients/match_all_controlled.html
@@ -0,0 +1,65 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1058311 - controlled page</title>
+<script class="testbody" type="text/javascript">
+ var re = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
+ var frameType = "none";
+ var testWindow = parent;
+
+ if (parent != window) {
+ frameType = "nested";
+ } else if (opener) {
+ frameType = "auxiliary";
+ testWindow = opener;
+ } else if (parent != window) {
+ frameType = "top-level";
+ } else {
+ postResult(false, "Unexpected frameType");
+ }
+
+ window.onload = function() {
+ navigator.serviceWorker.ready.then(function(swr) {
+ swr.active.postMessage("Start");
+ });
+ }
+
+ function postResult(result, msg) {
+ response = {
+ result: result,
+ message: msg
+ };
+
+ testWindow.postMessage(response, "*");
+ }
+
+ navigator.serviceWorker.onmessage = function(msg) {
+ // worker message;
+ result = re.test(msg.data.id);
+ postResult(result, "Client id test");
+
+ result = msg.data.url == window.location;
+ postResult(result, "Client url test");
+
+ result = msg.data.visibilityState === document.visibilityState;
+ postResult(result, "Client visibility test. expected=" +document.visibilityState);
+
+ result = msg.data.focused === document.hasFocus();
+ postResult(result, "Client focus test. expected=" + document.hasFocus());
+
+ result = msg.data.frameType === frameType;
+ postResult(result, "Client frameType test. expected=" + frameType);
+
+ postResult(true, "DONE");
+ window.close();
+ };
+</script>
+
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/match_all_properties_worker.js b/dom/workers/test/serviceworkers/match_all_properties_worker.js
new file mode 100644
index 000000000..f007a5ce8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/match_all_properties_worker.js
@@ -0,0 +1,20 @@
+onmessage = function(e) {
+ dump("MatchAllPropertiesWorker:" + e.data + "\n");
+ self.clients.matchAll().then(function(res) {
+ if (!res.length) {
+ dump("ERROR: no clients are currently controlled.\n");
+ }
+
+ for (i = 0; i < res.length; i++) {
+ client = res[i];
+ response = {
+ id: client.id,
+ url: client.url,
+ visibilityState: client.visibilityState,
+ focused: client.focused,
+ frameType: client.frameType
+ };
+ client.postMessage(response);
+ }
+ });
+}
diff --git a/dom/workers/test/serviceworkers/match_all_worker.js b/dom/workers/test/serviceworkers/match_all_worker.js
new file mode 100644
index 000000000..9d1c8c363
--- /dev/null
+++ b/dom/workers/test/serviceworkers/match_all_worker.js
@@ -0,0 +1,10 @@
+function loop() {
+ self.clients.matchAll().then(function(result) {
+ setTimeout(loop, 0);
+ });
+}
+
+onactivate = function(e) {
+ // spam matchAll until the worker is closed.
+ loop();
+}
diff --git a/dom/workers/test/serviceworkers/message_posting_worker.js b/dom/workers/test/serviceworkers/message_posting_worker.js
new file mode 100644
index 000000000..26db99775
--- /dev/null
+++ b/dom/workers/test/serviceworkers/message_posting_worker.js
@@ -0,0 +1,8 @@
+onmessage = function(e) {
+ self.clients.matchAll().then(function(res) {
+ if (!res.length) {
+ dump("ERROR: no clients are currently controlled.\n");
+ }
+ res[0].postMessage(e.data);
+ });
+};
diff --git a/dom/workers/test/serviceworkers/message_receiver.html b/dom/workers/test/serviceworkers/message_receiver.html
new file mode 100644
index 000000000..82cb587c7
--- /dev/null
+++ b/dom/workers/test/serviceworkers/message_receiver.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.onmessage = function(e) {
+ window.parent.postMessage(e.data, "*");
+ };
+</script>
diff --git a/dom/workers/test/serviceworkers/mochitest.ini b/dom/workers/test/serviceworkers/mochitest.ini
new file mode 100644
index 000000000..29ac9e036
--- /dev/null
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -0,0 +1,317 @@
+[DEFAULT]
+
+support-files =
+ worker.js
+ worker2.js
+ worker3.js
+ fetch_event_worker.js
+ parse_error_worker.js
+ activate_event_error_worker.js
+ install_event_worker.js
+ install_event_error_worker.js
+ simpleregister/index.html
+ simpleregister/ready.html
+ controller/index.html
+ unregister/index.html
+ unregister/unregister.html
+ workerUpdate/update.html
+ sw_clients/simple.html
+ sw_clients/service_worker_controlled.html
+ match_all_worker.js
+ match_all_advanced_worker.js
+ worker_unregister.js
+ worker_update.js
+ message_posting_worker.js
+ fetch/index.html
+ fetch/fetch_worker_script.js
+ fetch/fetch_tests.js
+ fetch/deliver-gzip.sjs
+ fetch/redirect.sjs
+ fetch/real-file.txt
+ fetch/context/index.html
+ fetch/context/register.html
+ fetch/context/unregister.html
+ fetch/context/context_test.js
+ fetch/context/realimg.jpg
+ fetch/context/realaudio.ogg
+ fetch/context/beacon.sjs
+ fetch/context/csp-violate.sjs
+ fetch/context/ping.html
+ fetch/context/worker.js
+ fetch/context/parentworker.js
+ fetch/context/sharedworker.js
+ fetch/context/parentsharedworker.js
+ fetch/context/xml.xml
+ fetch/hsts/hsts_test.js
+ fetch/hsts/embedder.html
+ fetch/hsts/image.html
+ fetch/hsts/image-20px.png
+ fetch/hsts/image-40px.png
+ fetch/hsts/realindex.html
+ fetch/hsts/register.html
+ fetch/hsts/register.html^headers^
+ fetch/hsts/unregister.html
+ fetch/https/index.html
+ fetch/https/register.html
+ fetch/https/unregister.html
+ fetch/https/https_test.js
+ fetch/https/clonedresponse/index.html
+ fetch/https/clonedresponse/register.html
+ fetch/https/clonedresponse/unregister.html
+ fetch/https/clonedresponse/https_test.js
+ fetch/imagecache/image-20px.png
+ fetch/imagecache/image-40px.png
+ fetch/imagecache/imagecache_test.js
+ fetch/imagecache/index.html
+ fetch/imagecache/postmortem.html
+ fetch/imagecache/register.html
+ fetch/imagecache/unregister.html
+ fetch/imagecache-maxage/index.html
+ fetch/imagecache-maxage/image-20px.png
+ fetch/imagecache-maxage/image-40px.png
+ fetch/imagecache-maxage/maxage_test.js
+ fetch/imagecache-maxage/register.html
+ fetch/imagecache-maxage/unregister.html
+ fetch/importscript-mixedcontent/register.html
+ fetch/importscript-mixedcontent/unregister.html
+ fetch/importscript-mixedcontent/https_test.js
+ fetch/interrupt.sjs
+ fetch/origin/index.sjs
+ fetch/origin/index-to-https.sjs
+ fetch/origin/realindex.html
+ fetch/origin/realindex.html^headers^
+ fetch/origin/register.html
+ fetch/origin/unregister.html
+ fetch/origin/origin_test.js
+ fetch/origin/https/index-https.sjs
+ fetch/origin/https/realindex.html
+ fetch/origin/https/realindex.html^headers^
+ fetch/origin/https/register.html
+ fetch/origin/https/unregister.html
+ fetch/origin/https/origin_test.js
+ fetch/requesturl/index.html
+ fetch/requesturl/redirect.sjs
+ fetch/requesturl/redirector.html
+ fetch/requesturl/register.html
+ fetch/requesturl/requesturl_test.js
+ fetch/requesturl/secret.html
+ fetch/requesturl/unregister.html
+ fetch/sandbox/index.html
+ fetch/sandbox/intercepted_index.html
+ fetch/sandbox/register.html
+ fetch/sandbox/unregister.html
+ fetch/sandbox/sandbox_test.js
+ fetch/upgrade-insecure/upgrade-insecure_test.js
+ fetch/upgrade-insecure/embedder.html
+ fetch/upgrade-insecure/embedder.html^headers^
+ fetch/upgrade-insecure/image.html
+ fetch/upgrade-insecure/image-20px.png
+ fetch/upgrade-insecure/image-40px.png
+ fetch/upgrade-insecure/realindex.html
+ fetch/upgrade-insecure/register.html
+ fetch/upgrade-insecure/unregister.html
+ match_all_properties_worker.js
+ match_all_clients/match_all_controlled.html
+ test_serviceworker_interfaces.js
+ serviceworker_wrapper.js
+ message_receiver.html
+ close_test.js
+ serviceworker_not_sharedworker.js
+ match_all_client/match_all_client_id.html
+ match_all_client_id_worker.js
+ source_message_posting_worker.js
+ scope/scope_worker.js
+ redirect_serviceworker.sjs
+ importscript.sjs
+ importscript_worker.js
+ bug1151916_worker.js
+ bug1151916_driver.html
+ bug1240436_worker.js
+ notificationclick.html
+ notificationclick-otherwindow.html
+ notificationclick.js
+ notificationclick_focus.html
+ notificationclick_focus.js
+ notificationclose.html
+ notificationclose.js
+ worker_updatefoundevent.js
+ worker_updatefoundevent2.js
+ updatefoundevent.html
+ empty.js
+ notification_constructor_error.js
+ notification_get_sw.js
+ notification/register.html
+ notification/unregister.html
+ notification/listener.html
+ notification_alt/register.html
+ notification_alt/unregister.html
+ sanitize/frame.html
+ sanitize/register.html
+ sanitize/example_check_and_unregister.html
+ sanitize_worker.js
+ swa/worker_scope_different.js
+ swa/worker_scope_different.js^headers^
+ swa/worker_scope_different2.js
+ swa/worker_scope_different2.js^headers^
+ swa/worker_scope_precise.js
+ swa/worker_scope_precise.js^headers^
+ swa/worker_scope_too_deep.js
+ swa/worker_scope_too_deep.js^headers^
+ swa/worker_scope_too_narrow.js
+ swa/worker_scope_too_narrow.js^headers^
+ claim_oninstall_worker.js
+ claim_worker_1.js
+ claim_worker_2.js
+ claim_clients/client.html
+ claim_fetch_worker.js
+ force_refresh_worker.js
+ sw_clients/refresher.html
+ sw_clients/refresher_compressed.html
+ sw_clients/refresher_compressed.html^headers^
+ sw_clients/refresher_cached.html
+ sw_clients/refresher_cached_compressed.html
+ sw_clients/refresher_cached_compressed.html^headers^
+ strict_mode_warning.js
+ skip_waiting_installed_worker.js
+ skip_waiting_scope/index.html
+ thirdparty/iframe1.html
+ thirdparty/iframe2.html
+ thirdparty/register.html
+ thirdparty/unregister.html
+ thirdparty/sw.js
+ register_https.html
+ gzip_redirect_worker.js
+ sw_clients/navigator.html
+ eval_worker.js
+ test_eval_allowed.html^headers^
+ opaque_intercept_worker.js
+ notify_loaded.js
+ test_request_context.js
+ fetch/plugin/worker.js
+ fetch/plugin/plugins.html
+ eventsource/*
+ sw_clients/file_blob_upload_frame.html
+ redirect_post.sjs
+ xslt_worker.js
+ xslt/*
+ unresolved_fetch_worker.js
+ header_checker.sjs
+ openWindow_worker.js
+ redirect.sjs
+ open_window/client.html
+ lorem_script.js
+ file_blob_response_worker.js
+ !/dom/security/test/cors/file_CrossSiteXHR_server.sjs
+ !/dom/tests/mochitest/notification/MockServices.js
+ !/dom/tests/mochitest/notification/NotificationTest.js
+ blocking_install_event_worker.js
+ sw_bad_mime_type.js
+ sw_bad_mime_type.js^headers^
+ error_reporting_helpers.js
+ fetch.js
+ hello.html
+ create_another_sharedWorker.html
+ sharedWorker_fetch.js
+
+[test_bug1151916.html]
+[test_bug1240436.html]
+[test_claim.html]
+[test_claim_fetch.html]
+[test_claim_oninstall.html]
+[test_close.html]
+[test_controller.html]
+[test_cross_origin_url_after_redirect.html]
+[test_csp_upgrade-insecure_intercept.html]
+[test_empty_serviceworker.html]
+[test_error_reporting.html]
+[test_escapedSlashes.html]
+[test_eval_allowed.html]
+[test_eventsource_intercept.html]
+[test_fetch_event.html]
+skip-if = (debug && e10s) # Bug 1262224
+[test_fetch_integrity.html]
+[test_file_blob_response.html]
+[test_file_blob_upload.html]
+[test_force_refresh.html]
+[test_gzip_redirect.html]
+[test_hsts_upgrade_intercept.html]
+[test_https_fetch.html]
+[test_https_fetch_cloned_response.html]
+[test_https_origin_after_redirect.html]
+[test_https_origin_after_redirect_cached.html]
+[test_https_synth_fetch_from_cached_sw.html]
+[test_imagecache.html]
+[test_imagecache_max_age.html]
+[test_importscript.html]
+[test_importscript_mixedcontent.html]
+tags = mcb
+[test_install_event.html]
+[test_install_event_gc.html]
+[test_installation_simple.html]
+[test_match_all.html]
+[test_match_all_advanced.html]
+[test_match_all_client_id.html]
+[test_match_all_client_properties.html]
+[test_navigator.html]
+[test_not_intercept_plugin.html]
+[test_notification_constructor_error.html]
+[test_notification_get.html]
+[test_notificationclick.html]
+[test_notificationclick_focus.html]
+[test_notificationclick-otherwindow.html]
+[test_notificationclose.html]
+[test_opaque_intercept.html]
+[test_openWindow.html]
+tags = openwindow
+[test_origin_after_redirect.html]
+[test_origin_after_redirect_cached.html]
+[test_origin_after_redirect_to_https.html]
+[test_origin_after_redirect_to_https_cached.html]
+[test_post_message.html]
+[test_post_message_advanced.html]
+[test_post_message_source.html]
+[test_register_base.html]
+[test_register_https_in_http.html]
+[test_request_context_audio.html]
+[test_request_context_beacon.html]
+[test_request_context_cache.html]
+[test_request_context_cspreport.html]
+[test_request_context_embed.html]
+[test_request_context_fetch.html]
+[test_request_context_font.html]
+[test_request_context_frame.html]
+[test_request_context_iframe.html]
+[test_request_context_image.html]
+[test_request_context_imagesrcset.html]
+[test_request_context_internal.html]
+[test_request_context_nestedworker.html]
+[test_request_context_nestedworkerinsharedworker.html]
+[test_request_context_object.html]
+[test_request_context_picture.html]
+[test_request_context_ping.html]
+[test_request_context_plugin.html]
+[test_request_context_script.html]
+[test_request_context_sharedworker.html]
+[test_request_context_style.html]
+[test_request_context_track.html]
+[test_request_context_video.html]
+[test_request_context_worker.html]
+[test_request_context_xhr.html]
+[test_request_context_xslt.html]
+[test_sandbox_intercept.html]
+[test_scopes.html]
+[test_sanitize.html]
+[test_sanitize_domain.html]
+[test_service_worker_allowed.html]
+[test_serviceworker_header.html]
+[test_serviceworker_interfaces.html]
+[test_serviceworker_not_sharedworker.html]
+[test_skip_waiting.html]
+[test_strict_mode_warning.html]
+[test_third_party_iframes.html]
+[test_unregister.html]
+[test_unresolved_fetch_interception.html]
+[test_workerUnregister.html]
+[test_workerUpdate.html]
+[test_workerupdatefoundevent.html]
+[test_xslt.html]
diff --git a/dom/workers/test/serviceworkers/notification/listener.html b/dom/workers/test/serviceworkers/notification/listener.html
new file mode 100644
index 000000000..1c6e282ec
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notification/listener.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>Bug 1114554 - proxy to forward messages from SW to test</title>
+<script class="testbody" type="text/javascript">
+ var testWindow = parent;
+ if (opener) {
+ testWindow = opener;
+ }
+
+ navigator.serviceWorker.onmessage = function(msg) {
+ // worker message;
+ testWindow.postMessage(msg.data, "*");
+ if (msg.data.type == 'finish') {
+ window.close();
+ }
+ };
+</script>
+
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/notification/register.html b/dom/workers/test/serviceworkers/notification/register.html
new file mode 100644
index 000000000..b7df73bed
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notification/register.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script>
+ function done() {
+ parent.callback();
+ }
+
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("../notification_get_sw.js", {scope: "."}).catch(function(e) {
+ dump("Registration failure " + e.message + "\n");
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/notification/unregister.html b/dom/workers/test/serviceworkers/notification/unregister.html
new file mode 100644
index 000000000..d5a141f83
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notification/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ registration.unregister().then(function(success) {
+ if (success) {
+ window.parent.callback();
+ }
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/notification_alt/register.html b/dom/workers/test/serviceworkers/notification_alt/register.html
new file mode 100644
index 000000000..b7df73bed
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notification_alt/register.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script>
+ function done() {
+ parent.callback();
+ }
+
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("../notification_get_sw.js", {scope: "."}).catch(function(e) {
+ dump("Registration failure " + e.message + "\n");
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/notification_alt/unregister.html b/dom/workers/test/serviceworkers/notification_alt/unregister.html
new file mode 100644
index 000000000..d5a141f83
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notification_alt/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ registration.unregister().then(function(success) {
+ if (success) {
+ window.parent.callback();
+ }
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/notification_constructor_error.js b/dom/workers/test/serviceworkers/notification_constructor_error.js
new file mode 100644
index 000000000..644dba480
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notification_constructor_error.js
@@ -0,0 +1 @@
+new Notification("Hi there");
diff --git a/dom/workers/test/serviceworkers/notification_get_sw.js b/dom/workers/test/serviceworkers/notification_get_sw.js
new file mode 100644
index 000000000..540c9d93c
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notification_get_sw.js
@@ -0,0 +1,49 @@
+function postAll(data) {
+ self.clients.matchAll().then(function(clients) {
+ if (clients.length == 0) {
+ dump("***************** NO CLIENTS FOUND! Test messages are being lost *******************\n");
+ }
+ clients.forEach(function(client) {
+ client.postMessage(data);
+ });
+ });
+}
+
+function ok(a, msg) {
+ postAll({type: 'status', status: !!a, msg: a + ": " + msg });
+}
+
+function is(a, b, msg) {
+ postAll({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
+}
+
+function done() {
+ postAll({type: 'finish'});
+}
+
+onmessage = function(e) {
+dump("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% MESSAGE " + e.data + "\n");
+ var start;
+ if (e.data == 'create') {
+ start = registration.showNotification("This is a title");
+ } else {
+ start = Promise.resolve();
+ }
+
+ start.then(function() {
+ dump("CALLING getNotification\n");
+ registration.getNotifications().then(function(notifications) {
+ dump("RECD getNotification\n");
+ is(notifications.length, 1, "There should be one stored notification");
+ var notification = notifications[0];
+ if (!notification) {
+ done();
+ return;
+ }
+ ok(notification instanceof Notification, "Should be a Notification");
+ is(notification.title, "This is a title", "Title should match");
+ notification.close();
+ done();
+ });
+ });
+}
diff --git a/dom/workers/test/serviceworkers/notificationclick-otherwindow.html b/dom/workers/test/serviceworkers/notificationclick-otherwindow.html
new file mode 100644
index 000000000..f64e82aab
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notificationclick-otherwindow.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>Bug 1114554 - controlled page</title>
+<script class="testbody" type="text/javascript">
+ var testWindow = parent;
+ if (opener) {
+ testWindow = opener;
+ }
+
+ navigator.serviceWorker.ready.then(function(swr) {
+ var ifr = document.createElement("iframe");
+ document.documentElement.appendChild(ifr);
+ ifr.contentWindow.ServiceWorkerRegistration.prototype.showNotification
+ .call(swr, "Hi there. The ServiceWorker should receive a click event for this.", { data: { complex: ["jsval", 5] }});
+ });
+
+ navigator.serviceWorker.onmessage = function(msg) {
+ testWindow.callback(msg.data.result);
+ };
+</script>
+
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/notificationclick.html b/dom/workers/test/serviceworkers/notificationclick.html
new file mode 100644
index 000000000..448764a1c
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notificationclick.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>Bug 1114554 - controlled page</title>
+<script class="testbody" type="text/javascript">
+ var testWindow = parent;
+ if (opener) {
+ testWindow = opener;
+ }
+
+ navigator.serviceWorker.ready.then(function(swr) {
+ swr.showNotification("Hi there. The ServiceWorker should receive a click event for this.", { data: { complex: ["jsval", 5] }});
+ });
+
+ navigator.serviceWorker.onmessage = function(msg) {
+ testWindow.callback(msg.data.result);
+ };
+</script>
+
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/notificationclick.js b/dom/workers/test/serviceworkers/notificationclick.js
new file mode 100644
index 000000000..39e7adb81
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notificationclick.js
@@ -0,0 +1,19 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+//
+onnotificationclick = function(e) {
+ self.clients.matchAll().then(function(clients) {
+ if (clients.length === 0) {
+ dump("********************* CLIENTS LIST EMPTY! Test will timeout! ***********************\n");
+ return;
+ }
+
+ clients.forEach(function(client) {
+ client.postMessage({ result: e.notification.data &&
+ e.notification.data['complex'] &&
+ e.notification.data['complex'][0] == "jsval" &&
+ e.notification.data['complex'][1] == 5 });
+
+ });
+ });
+}
diff --git a/dom/workers/test/serviceworkers/notificationclick_focus.html b/dom/workers/test/serviceworkers/notificationclick_focus.html
new file mode 100644
index 000000000..0152d397f
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notificationclick_focus.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>Bug 1144660 - controlled page</title>
+<script class="testbody" type="text/javascript">
+ var testWindow = parent;
+ if (opener) {
+ testWindow = opener;
+ }
+
+ navigator.serviceWorker.ready.then(function(swr) {
+ swr.showNotification("Hi there. The ServiceWorker should receive a click event for this.");
+ });
+
+ navigator.serviceWorker.onmessage = function(msg) {
+ dump("GOT Message " + JSON.stringify(msg.data) + "\n");
+ testWindow.callback(msg.data.ok);
+ };
+</script>
+
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/notificationclick_focus.js b/dom/workers/test/serviceworkers/notificationclick_focus.js
new file mode 100644
index 000000000..5fb73651e
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notificationclick_focus.js
@@ -0,0 +1,40 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+//
+
+function promisifyTimerFocus(client, delay) {
+ return new Promise(function(resolve, reject) {
+ setTimeout(function() {
+ client.focus().then(resolve, reject);
+ }, delay);
+ });
+}
+
+onnotificationclick = function(e) {
+ e.waitUntil(self.clients.matchAll().then(function(clients) {
+ if (clients.length === 0) {
+ dump("********************* CLIENTS LIST EMPTY! Test will timeout! ***********************\n");
+ return Promise.resolve();
+ }
+
+ var immediatePromise = clients[0].focus();
+ var withinTimeout = promisifyTimerFocus(clients[0], 100);
+
+ var afterTimeout = promisifyTimerFocus(clients[0], 2000).then(function() {
+ throw "Should have failed!";
+ }, function() {
+ return Promise.resolve();
+ });
+
+ return Promise.all([immediatePromise, withinTimeout, afterTimeout]).then(function() {
+ clients.forEach(function(client) {
+ client.postMessage({ok: true});
+ });
+ }).catch(function(e) {
+ dump("Error " + e + "\n");
+ clients.forEach(function(client) {
+ client.postMessage({ok: false});
+ });
+ });
+ }));
+}
diff --git a/dom/workers/test/serviceworkers/notificationclose.html b/dom/workers/test/serviceworkers/notificationclose.html
new file mode 100644
index 000000000..10c8da453
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notificationclose.html
@@ -0,0 +1,37 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1265841 - controlled page</title>
+<script class="testbody" type="text/javascript">
+ var testWindow = parent;
+ if (opener) {
+ testWindow = opener;
+ }
+
+ navigator.serviceWorker.ready.then(function(swr) {
+ return swr.showNotification(
+ "Hi there. The ServiceWorker should receive a close event for this.",
+ { data: { complex: ["jsval", 5] }}).then(function() {
+ return swr;
+ });
+ }).then(function(swr) {
+ return swr.getNotifications();
+ }).then(function(notifications) {
+ notifications.forEach(function(notification) {
+ notification.close();
+ });
+ });
+
+ navigator.serviceWorker.onmessage = function(msg) {
+ testWindow.callback(msg.data.result);
+ };
+</script>
+
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/notificationclose.js b/dom/workers/test/serviceworkers/notificationclose.js
new file mode 100644
index 000000000..d48218075
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notificationclose.js
@@ -0,0 +1,19 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+//
+onnotificationclose = function(e) {
+ self.clients.matchAll().then(function(clients) {
+ if (clients.length === 0) {
+ dump("********************* CLIENTS LIST EMPTY! Test will timeout! ***********************\n");
+ return;
+ }
+
+ clients.forEach(function(client) {
+ client.postMessage({ result: e.notification.data &&
+ e.notification.data['complex'] &&
+ e.notification.data['complex'][0] == "jsval" &&
+ e.notification.data['complex'][1] == 5 });
+
+ });
+ });
+}
diff --git a/dom/workers/test/serviceworkers/notify_loaded.js b/dom/workers/test/serviceworkers/notify_loaded.js
new file mode 100644
index 000000000..d07573b2c
--- /dev/null
+++ b/dom/workers/test/serviceworkers/notify_loaded.js
@@ -0,0 +1 @@
+parent.postMessage('SCRIPT_LOADED', '*');
diff --git a/dom/workers/test/serviceworkers/opaque_intercept_worker.js b/dom/workers/test/serviceworkers/opaque_intercept_worker.js
new file mode 100644
index 000000000..d593be783
--- /dev/null
+++ b/dom/workers/test/serviceworkers/opaque_intercept_worker.js
@@ -0,0 +1,25 @@
+var name = 'opaqueInterceptCache';
+
+// Cross origin request to ensure that an opaque response is used
+var prefix = 'http://example.com/tests/dom/workers/test/serviceworkers/'
+
+self.addEventListener('install', function(event) {
+ var request = new Request(prefix + 'notify_loaded.js', { mode: 'no-cors' });
+ event.waitUntil(
+ Promise.all([caches.open(name), fetch(request)]).then(function(results) {
+ var cache = results[0];
+ var response = results[1];
+ return cache.put('./sw_clients/does_not_exist.js', response);
+ })
+ );
+});
+
+self.addEventListener('fetch', function (event) {
+ event.respondWith(
+ caches.open(name).then(function(cache) {
+ return cache.match(event.request);
+ }).then(function(response) {
+ return response || fetch(event.request);
+ })
+ );
+});
diff --git a/dom/workers/test/serviceworkers/openWindow_worker.js b/dom/workers/test/serviceworkers/openWindow_worker.js
new file mode 100644
index 000000000..46c0f998e
--- /dev/null
+++ b/dom/workers/test/serviceworkers/openWindow_worker.js
@@ -0,0 +1,116 @@
+// the worker won't shut down between events because we increased
+// the timeout values.
+var client;
+var window_count = 0;
+var expected_window_count = 7;
+var resolve_got_all_windows = null;
+var got_all_windows = new Promise(function(res, rej) {
+ resolve_got_all_windows = res;
+});
+
+// |expected_window_count| needs to be updated for every new call that's
+// expected to actually open a new window regardless of what |clients.openWindow|
+// returns.
+function testForUrl(url, throwType, clientProperties, resultsArray) {
+ return clients.openWindow(url)
+ .then(function(e) {
+ if (throwType != null) {
+ resultsArray.push({
+ result: false,
+ message: "openWindow should throw " + throwType
+ });
+ } else if (clientProperties) {
+ resultsArray.push({
+ result: (e instanceof WindowClient),
+ message: "openWindow should resolve to a WindowClient"
+ });
+ resultsArray.push({
+ result: e.url == clientProperties.url,
+ message: "Client url should be " + clientProperties.url
+ });
+ // Add more properties
+ } else {
+ resultsArray.push({
+ result: e == null,
+ message: "Open window should resolve to null. Got: " + e
+ });
+ }
+ })
+ .catch(function(err) {
+ if (throwType == null) {
+ resultsArray.push({
+ result: false,
+ message: "Unexpected throw: " + err
+ });
+ } else {
+ resultsArray.push({
+ result: err.toString().indexOf(throwType) >= 0,
+ message: "openWindow should throw: " + err
+ });
+ }
+ })
+}
+
+onmessage = function(event) {
+ if (event.data == "testNoPopup") {
+ client = event.source;
+
+ var results = [];
+ var promises = [];
+ promises.push(testForUrl("about:blank", "TypeError", null, results));
+ promises.push(testForUrl("http://example.com", "InvalidAccessError", null, results));
+ promises.push(testForUrl("_._*`InvalidURL", "InvalidAccessError", null, results));
+ Promise.all(promises).then(function(e) {
+ client.postMessage(results);
+ });
+ }
+ if (event.data == "NEW_WINDOW") {
+ window_count += 1;
+ if (window_count == expected_window_count) {
+ resolve_got_all_windows();
+ }
+ }
+
+ if (event.data == "CHECK_NUMBER_OF_WINDOWS") {
+ got_all_windows.then(function() {
+ return clients.matchAll();
+ }).then(function(cl) {
+ event.source.postMessage({result: cl.length == expected_window_count,
+ message: "The number of windows is correct."});
+ for (i = 0; i < cl.length; i++) {
+ cl[i].postMessage("CLOSE");
+ }
+ });
+ }
+}
+
+onnotificationclick = function(e) {
+ var results = [];
+ var promises = [];
+
+ var redirect = "http://mochi.test:8888/tests/dom/workers/test/serviceworkers/redirect.sjs?"
+ var redirect_xorigin = "http://example.com/tests/dom/workers/test/serviceworkers/redirect.sjs?"
+ var same_origin = "http://mochi.test:8888/tests/dom/workers/test/serviceworkers/open_window/client.html"
+ var different_origin = "http://example.com/tests/dom/workers/test/serviceworkers/open_window/client.html"
+
+
+ promises.push(testForUrl("about:blank", "TypeError", null, results));
+ promises.push(testForUrl(different_origin, null, null, results));
+ promises.push(testForUrl(same_origin, null, {url: same_origin}, results));
+ promises.push(testForUrl("open_window/client.html", null, {url: same_origin}, results));
+
+ // redirect tests
+ promises.push(testForUrl(redirect + "open_window/client.html", null,
+ {url: same_origin}, results));
+ promises.push(testForUrl(redirect + different_origin, null, null, results));
+
+ promises.push(testForUrl(redirect_xorigin + "open_window/client.html", null,
+ null, results));
+ promises.push(testForUrl(redirect_xorigin + same_origin, null,
+ {url: same_origin}, results));
+
+ Promise.all(promises).then(function(e) {
+ client.postMessage(results);
+ });
+}
+
diff --git a/dom/workers/test/serviceworkers/open_window/client.html b/dom/workers/test/serviceworkers/open_window/client.html
new file mode 100644
index 000000000..82b93ff7e
--- /dev/null
+++ b/dom/workers/test/serviceworkers/open_window/client.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>Bug 1172870 - page opened by ServiceWorkerClients.OpenWindow</title>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ window.onload = function() {
+ if (window.location == "http://mochi.test:8888/tests/dom/workers/test/serviceworkers/open_window/client.html") {
+ navigator.serviceWorker.ready.then(function(result) {
+ navigator.serviceWorker.onmessage = function(event) {
+ if (event.data !== "CLOSE") {
+ dump("ERROR: unexepected reply from the service worker.\n");
+ }
+ if (parent) {
+ parent.postMessage("CLOSE", "*");
+ }
+ window.close();
+ }
+ navigator.serviceWorker.controller.postMessage("NEW_WINDOW");
+ })
+ } else {
+ window.onmessage = function(event) {
+ if (event.data !== "CLOSE") {
+ dump("ERROR: unexepected reply from the iframe.\n");
+ }
+ window.close();
+ }
+
+ var iframe = document.createElement('iframe');
+ iframe.src = "http://mochi.test:8888/tests/dom/workers/test/serviceworkers/open_window/client.html";
+ document.body.appendChild(iframe);
+ }
+ }
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/parse_error_worker.js b/dom/workers/test/serviceworkers/parse_error_worker.js
new file mode 100644
index 000000000..b6a8ef0a1
--- /dev/null
+++ b/dom/workers/test/serviceworkers/parse_error_worker.js
@@ -0,0 +1,2 @@
+// intentional parse error.
+var foo = {;
diff --git a/dom/workers/test/serviceworkers/redirect.sjs b/dom/workers/test/serviceworkers/redirect.sjs
new file mode 100644
index 000000000..b6249cadf
--- /dev/null
+++ b/dom/workers/test/serviceworkers/redirect.sjs
@@ -0,0 +1,5 @@
+function handleRequest(request, response)
+{
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ response.setHeader("Location", request.queryString, false);
+}
diff --git a/dom/workers/test/serviceworkers/redirect_post.sjs b/dom/workers/test/serviceworkers/redirect_post.sjs
new file mode 100644
index 000000000..8b805be63
--- /dev/null
+++ b/dom/workers/test/serviceworkers/redirect_post.sjs
@@ -0,0 +1,35 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response)
+{
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ var [name, value] = val.split('=');
+ query[name] = unescape(value);
+ });
+
+ var bodyStream = new BinaryInputStream(request.bodyInputStream);
+ var bodyBytes = [];
+ while ((bodyAvail = bodyStream.available()) > 0)
+ Array.prototype.push.apply(bodyBytes, bodyStream.readByteArray(bodyAvail));
+
+ var body = decodeURIComponent(
+ escape(String.fromCharCode.apply(null, bodyBytes)));
+
+ var currentHop = query.hop ? parseInt(query.hop) : 0;
+
+ var obj = JSON.parse(body);
+ if (currentHop < obj.hops) {
+ var newURL = '/tests/dom/workers/test/serviceworkers/redirect_post.sjs?hop=' +
+ (1 + currentHop);
+ response.setStatusLine(null, 307, 'redirect');
+ response.setHeader('Location', newURL);
+ return;
+ }
+
+ response.setHeader('Content-Type', 'application/json');
+ response.write(body);
+}
diff --git a/dom/workers/test/serviceworkers/redirect_serviceworker.sjs b/dom/workers/test/serviceworkers/redirect_serviceworker.sjs
new file mode 100644
index 000000000..9d3a0a2cd
--- /dev/null
+++ b/dom/workers/test/serviceworkers/redirect_serviceworker.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", "http://mochi.test:8888/tests/dom/workers/test/serviceworkers/worker.js");
+}
diff --git a/dom/workers/test/serviceworkers/register_https.html b/dom/workers/test/serviceworkers/register_https.html
new file mode 100644
index 000000000..d14d8c380
--- /dev/null
+++ b/dom/workers/test/serviceworkers/register_https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script>
+function ok(condition, message) {
+ parent.postMessage({type: "ok", status: condition, msg: message}, "*");
+}
+
+function done() {
+ parent.postMessage({type: "done"}, "*");
+}
+
+ok(location.protocol == "https:", "We should be loaded from HTTPS");
+
+navigator.serviceWorker.register("empty.js", {scope: "register-https"})
+ .then(reg => {
+ ok(false, "Registration should fail");
+ done();
+ }).catch(err => {
+ ok(err.name === "SecurityError", "Registration should fail with SecurityError");
+ done();
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/sanitize/example_check_and_unregister.html b/dom/workers/test/serviceworkers/sanitize/example_check_and_unregister.html
new file mode 100644
index 000000000..4de8f317b
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sanitize/example_check_and_unregister.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script>
+ function done(exists) {
+ parent.postMessage(exists, '*');
+ }
+
+ function fail() {
+ parent.postMessage("FAIL", '*');
+ }
+
+ navigator.serviceWorker.getRegistration(".").then(function(reg) {
+ if (reg) {
+ reg.unregister().then(done.bind(undefined, true), fail);
+ } else {
+ dump("getRegistration() returned undefined registration\n");
+ done(false);
+ }
+ }, function(e) {
+ dump("getRegistration() failed\n");
+ fail();
+ });
+</script>
+
diff --git a/dom/workers/test/serviceworkers/sanitize/frame.html b/dom/workers/test/serviceworkers/sanitize/frame.html
new file mode 100644
index 000000000..b4bf7a1ff
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sanitize/frame.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script>
+ fetch("intercept-this").then(function(r) {
+ if (!r.ok) {
+ return "FAIL";
+ }
+ return r.text();
+ }).then(function(body) {
+ parent.postMessage(body, '*');
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/sanitize/register.html b/dom/workers/test/serviceworkers/sanitize/register.html
new file mode 100644
index 000000000..f1edd27be
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sanitize/register.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script>
+ function done() {
+ parent.postMessage('', '*');
+ }
+
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("../sanitize_worker.js", {scope: "."});
+</script>
+
diff --git a/dom/workers/test/serviceworkers/sanitize_worker.js b/dom/workers/test/serviceworkers/sanitize_worker.js
new file mode 100644
index 000000000..66495e186
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sanitize_worker.js
@@ -0,0 +1,5 @@
+onfetch = function(e) {
+ if (e.request.url.indexOf("intercept-this") != -1) {
+ e.respondWith(new Response("intercepted"));
+ }
+}
diff --git a/dom/workers/test/serviceworkers/scope/scope_worker.js b/dom/workers/test/serviceworkers/scope/scope_worker.js
new file mode 100644
index 000000000..4164e7a24
--- /dev/null
+++ b/dom/workers/test/serviceworkers/scope/scope_worker.js
@@ -0,0 +1,2 @@
+// This worker is used to test if calling register() without a scope argument
+// leads to scope being relative to service worker script.
diff --git a/dom/workers/test/serviceworkers/serviceworker.html b/dom/workers/test/serviceworkers/serviceworker.html
new file mode 100644
index 000000000..11edd001a
--- /dev/null
+++ b/dom/workers/test/serviceworkers/serviceworker.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <script>
+ navigator.serviceWorker.register("worker.js");
+ </script>
+ </head>
+ <body>
+ This is a test page.
+ </body>
+<html>
diff --git a/dom/workers/test/serviceworkers/serviceworker_not_sharedworker.js b/dom/workers/test/serviceworkers/serviceworker_not_sharedworker.js
new file mode 100644
index 000000000..077da2366
--- /dev/null
+++ b/dom/workers/test/serviceworkers/serviceworker_not_sharedworker.js
@@ -0,0 +1,21 @@
+function OnMessage(e)
+{
+ if (e.data.msg == "whoareyou") {
+ if ("ServiceWorker" in self) {
+ self.clients.matchAll().then(function(clients) {
+ clients[0].postMessage({result: "serviceworker"});
+ });
+ } else {
+ port.postMessage({result: "sharedworker"});
+ }
+ }
+};
+
+var port;
+onconnect = function(e) {
+ port = e.ports[0];
+ port.onmessage = OnMessage;
+ port.start();
+};
+
+onmessage = OnMessage;
diff --git a/dom/workers/test/serviceworkers/serviceworker_wrapper.js b/dom/workers/test/serviceworkers/serviceworker_wrapper.js
new file mode 100644
index 000000000..6a7ec0c0f
--- /dev/null
+++ b/dom/workers/test/serviceworkers/serviceworker_wrapper.js
@@ -0,0 +1,101 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+//
+// ServiceWorker equivalent of worker_wrapper.js.
+
+var client;
+
+function ok(a, msg) {
+ dump("OK: " + !!a + " => " + a + ": " + msg + "\n");
+ client.postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
+}
+
+function is(a, b, msg) {
+ dump("IS: " + (a===b) + " => " + a + " | " + b + ": " + msg + "\n");
+ client.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() {
+ client.postMessage({ type: 'finish' });
+}
+
+function workerTestGetVersion(cb) {
+ addEventListener('message', function workerTestGetVersionCB(e) {
+ if (e.data.type !== 'returnVersion') {
+ return;
+ }
+ removeEventListener('message', workerTestGetVersionCB);
+ cb(e.data.result);
+ });
+ client.postMessage({
+ type: 'getVersion'
+ });
+}
+
+function workerTestGetUserAgent(cb) {
+ addEventListener('message', function workerTestGetUserAgentCB(e) {
+ if (e.data.type !== 'returnUserAgent') {
+ return;
+ }
+ removeEventListener('message', workerTestGetUserAgentCB);
+ cb(e.data.result);
+ });
+ client.postMessage({
+ type: 'getUserAgent'
+ });
+}
+
+function workerTestGetOSCPU(cb) {
+ addEventListener('message', function workerTestGetOSCPUCB(e) {
+ if (e.data.type !== 'returnOSCPU') {
+ return;
+ }
+ removeEventListener('message', workerTestGetOSCPUCB);
+ cb(e.data.result);
+ });
+ client.postMessage({
+ type: 'getOSCPU'
+ });
+}
+
+function workerTestGetStorageManager(cb) {
+ addEventListener('message', function workerTestGetStorageManagerCB(e) {
+ if (e.data.type !== 'returnStorageManager') {
+ return;
+ }
+ removeEventListener('message', workerTestGetStorageManagerCB);
+ cb(e.data.result);
+ });
+ client.postMessage({
+ type: 'getStorageManager'
+ });
+}
+
+addEventListener('message', function workerWrapperOnMessage(e) {
+ removeEventListener('message', workerWrapperOnMessage);
+ var data = e.data;
+ self.clients.matchAll().then(function(clients) {
+ client = clients[0];
+ try {
+ importScripts(data.script);
+ } catch(e) {
+ client.postMessage({
+ type: 'status',
+ status: false,
+ msg: 'worker failed to import ' + data.script + "; error: " + e.message
+ });
+ }
+ });
+});
diff --git a/dom/workers/test/serviceworkers/serviceworkerinfo_iframe.html b/dom/workers/test/serviceworkers/serviceworkerinfo_iframe.html
new file mode 100644
index 000000000..a0a2de760
--- /dev/null
+++ b/dom/workers/test/serviceworkers/serviceworkerinfo_iframe.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <script>
+ window.onmessage = function (event) {
+ if (event.data !== "register") {
+ return;
+ }
+ var promise = navigator.serviceWorker.register("worker.js");
+ window.onmessage = function (event) {
+ if (event.data !== "unregister") {
+ return;
+ }
+ promise.then(function (registration) {
+ registration.unregister();
+ });
+ window.onmessage = null;
+ };
+ };
+ </script>
+ </head>
+ <body>
+ This is a test page.
+ </body>
+<html>
diff --git a/dom/workers/test/serviceworkers/serviceworkermanager_iframe.html b/dom/workers/test/serviceworkers/serviceworkermanager_iframe.html
new file mode 100644
index 000000000..0df93da96
--- /dev/null
+++ b/dom/workers/test/serviceworkers/serviceworkermanager_iframe.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <script>
+ window.onmessage = function (event) {
+ if (event.data !== "register") {
+ return;
+ }
+ var promise = navigator.serviceWorker.register("worker.js");
+ window.onmessage = function (event) {
+ if (event.data !== "register") {
+ return;
+ }
+ promise = promise.then(function (registration) {
+ return navigator.serviceWorker.register("worker2.js");
+ });
+ window.onmessage = function (event) {
+ if (event.data !== "unregister") {
+ return;
+ }
+ promise.then(function (registration) {
+ registration.unregister();
+ });
+ window.onmessage = null;
+ };
+ };
+ };
+ </script>
+ </head>
+ <body>
+ This is a test page.
+ </body>
+<html>
diff --git a/dom/workers/test/serviceworkers/serviceworkerregistrationinfo_iframe.html b/dom/workers/test/serviceworkers/serviceworkerregistrationinfo_iframe.html
new file mode 100644
index 000000000..f093d38db
--- /dev/null
+++ b/dom/workers/test/serviceworkers/serviceworkerregistrationinfo_iframe.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <script>
+ var reg;
+ window.onmessage = function (event) {
+ if (event.data !== "register") {
+ return;
+ }
+ var promise = navigator.serviceWorker.register("worker.js");
+ window.onmessage = function (event) {
+ if (event.data === "register") {
+ promise.then(function (registration) {
+ return navigator.serviceWorker.register("worker2.js")
+ .then(function(registration) {
+ reg = registration;
+ });
+ });
+ } else if (event.data === "unregister") {
+ reg.unregister();
+ }
+ };
+ };
+ </script>
+ </head>
+ <body>
+ This is a test page.
+ </body>
+<html>
diff --git a/dom/workers/test/serviceworkers/sharedWorker_fetch.js b/dom/workers/test/serviceworkers/sharedWorker_fetch.js
new file mode 100644
index 000000000..4970a1fd2
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sharedWorker_fetch.js
@@ -0,0 +1,29 @@
+var clients = new Array();
+clients.length = 0;
+
+var broadcast = function(message) {
+ var length = clients.length;
+ for (var i = 0; i < length; i++) {
+ port = clients[i];
+ port.postMessage(message);
+ }
+}
+
+onconnect = function(e) {
+ clients.push(e.ports[0]);
+ if (clients.length == 1) {
+ clients[0].postMessage("Connected");
+ } else if (clients.length == 2) {
+ broadcast("BothConnected");
+ clients[0].onmessage = function(e) {
+ if (e.data == "StartFetchWithWrongIntegrity") {
+ // The fetch will succeed because the integrity value is invalid and we
+ // are looking for the console message regarding the bad integrity value.
+ fetch("SharedWorker_SRIFailed.html", {"integrity": "abc"}).then(
+ function () {
+ clients[0].postMessage('SRI_failed');
+ });
+ }
+ }
+ }
+}
diff --git a/dom/workers/test/serviceworkers/simpleregister/index.html b/dom/workers/test/serviceworkers/simpleregister/index.html
new file mode 100644
index 000000000..2c0eb5345
--- /dev/null
+++ b/dom/workers/test/serviceworkers/simpleregister/index.html
@@ -0,0 +1,51 @@
+<html>
+ <head></head>
+ <body>
+ <script type="text/javascript">
+ var expectedEvents = 2;
+ function eventReceived() {
+ window.parent.postMessage({ type: "check", status: expectedEvents > 0, msg: "updatefound received" }, "*");
+
+ if (--expectedEvents) {
+ window.parent.postMessage({ type: "finish" }, "*");
+ }
+ }
+
+ navigator.serviceWorker.getRegistrations().then(function(a) {
+ window.parent.postMessage({ type: "check", status: Array.isArray(a),
+ msg: "getRegistrations returns an array" }, "*");
+ window.parent.postMessage({ type: "check", status: a.length > 0,
+ msg: "getRegistrations returns an array with 1 item" }, "*");
+ for (var i = 0; i < a.length; ++i) {
+ window.parent.postMessage({ type: "check", status: a[i] instanceof ServiceWorkerRegistration,
+ msg: "getRegistrations returns an array of ServiceWorkerRegistration objects" }, "*");
+ if (a[i].scope.match(/simpleregister\//)) {
+ a[i].onupdatefound = function(e) {
+ eventReceived();
+ }
+ }
+ }
+ });
+
+ navigator.serviceWorker.getRegistration('http://mochi.test:8888/tests/dom/workers/test/serviceworkers/simpleregister/')
+ .then(function(a) {
+ window.parent.postMessage({ type: "check", status: a instanceof ServiceWorkerRegistration,
+ msg: "getRegistration returns a ServiceWorkerRegistration" }, "*");
+ a.onupdatefound = function(e) {
+ eventReceived();
+ }
+ });
+
+ navigator.serviceWorker.getRegistration('http://www.something_else.net/')
+ .then(function(a) {
+ window.parent.postMessage({ type: "check", status: false,
+ msg: "getRegistration should throw for security error!" }, "*");
+ }, function(a) {
+ window.parent.postMessage({ type: "check", status: true,
+ msg: "getRegistration should throw for security error!" }, "*");
+ });
+
+ window.parent.postMessage({ type: "ready" }, "*");
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/serviceworkers/simpleregister/ready.html b/dom/workers/test/serviceworkers/simpleregister/ready.html
new file mode 100644
index 000000000..3afc4bfdb
--- /dev/null
+++ b/dom/workers/test/serviceworkers/simpleregister/ready.html
@@ -0,0 +1,15 @@
+<html>
+ <head></head>
+ <body>
+ <script type="text/javascript">
+
+ window.addEventListener('message', function(evt) {
+ navigator.serviceWorker.ready.then(function() {
+ evt.ports[0].postMessage("WOW!");
+ });
+ }, false);
+
+ </script>
+ </body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/skip_waiting_installed_worker.js b/dom/workers/test/serviceworkers/skip_waiting_installed_worker.js
new file mode 100644
index 000000000..68573f100
--- /dev/null
+++ b/dom/workers/test/serviceworkers/skip_waiting_installed_worker.js
@@ -0,0 +1,6 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+self.addEventListener('install', evt => {
+ evt.waitUntil(self.skipWaiting());
+});
diff --git a/dom/workers/test/serviceworkers/skip_waiting_scope/index.html b/dom/workers/test/serviceworkers/skip_waiting_scope/index.html
new file mode 100644
index 000000000..b8a64d512
--- /dev/null
+++ b/dom/workers/test/serviceworkers/skip_waiting_scope/index.html
@@ -0,0 +1,37 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1131352 - Add ServiceWorkerGlobalScope skipWaiting()</title>
+ <script type="text/javascript" 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">
+
+ if (!parent) {
+ info("skip_waiting_scope/index.html shouldn't be launched directly!");
+ }
+
+ navigator.serviceWorker.ready.then(function() {
+ parent.postMessage("READY", "*");
+ });
+
+ navigator.serviceWorker.oncontrollerchange = function() {
+ parent.postMessage({
+ event: "controllerchange",
+ controllerScriptURL: navigator.serviceWorker.controller &&
+ navigator.serviceWorker.controller.scriptURL
+ }, "*");
+ }
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/source_message_posting_worker.js b/dom/workers/test/serviceworkers/source_message_posting_worker.js
new file mode 100644
index 000000000..36ce951fe
--- /dev/null
+++ b/dom/workers/test/serviceworkers/source_message_posting_worker.js
@@ -0,0 +1,16 @@
+onmessage = function(e) {
+ if (!e.source) {
+ dump("ERROR: message doesn't have a source.");
+ }
+
+ if (!(e instanceof ExtendableMessageEvent)) {
+ e.source.postMessage("ERROR. event is not an extendable message event.");
+ }
+
+ // The client should be a window client
+ if (e.source instanceof WindowClient) {
+ e.source.postMessage(e.data);
+ } else {
+ e.source.postMessage("ERROR. source is not a window client.");
+ }
+};
diff --git a/dom/workers/test/serviceworkers/strict_mode_warning.js b/dom/workers/test/serviceworkers/strict_mode_warning.js
new file mode 100644
index 000000000..38418de3d
--- /dev/null
+++ b/dom/workers/test/serviceworkers/strict_mode_warning.js
@@ -0,0 +1,4 @@
+function f() {
+ return 1;
+ return 2;
+}
diff --git a/dom/workers/test/serviceworkers/sw_bad_mime_type.js b/dom/workers/test/serviceworkers/sw_bad_mime_type.js
new file mode 100644
index 000000000..f371807db
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sw_bad_mime_type.js
@@ -0,0 +1 @@
+// I need some contents.
diff --git a/dom/workers/test/serviceworkers/sw_bad_mime_type.js^headers^ b/dom/workers/test/serviceworkers/sw_bad_mime_type.js^headers^
new file mode 100644
index 000000000..a1f9e38d9
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sw_bad_mime_type.js^headers^
@@ -0,0 +1 @@
+Content-Type: text/plain
diff --git a/dom/workers/test/serviceworkers/sw_clients/file_blob_upload_frame.html b/dom/workers/test/serviceworkers/sw_clients/file_blob_upload_frame.html
new file mode 100644
index 000000000..e594c514d
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sw_clients/file_blob_upload_frame.html
@@ -0,0 +1,77 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>test file blob upload with SW interception</title>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+if (!parent) {
+ dump("sw_clients/file_blob_upload_frame.html shouldn't be launched directly!");
+}
+
+function makeFileBlob(obj) {
+ return new Promise(function(resolve, reject) {
+
+ var request = indexedDB.open(window.location.pathname, 1);
+ request.onerror = reject;
+ request.onupgradeneeded = function(evt) {
+ var db = evt.target.result;
+ db.onerror = reject;
+
+ var objectStore = db.createObjectStore('test', { autoIncrement: true });
+ var index = objectStore.createIndex('test', 'index');
+ };
+
+ request.onsuccess = function(evt) {
+ var db = evt.target.result;
+ db.onerror = reject;
+
+ var blob = new Blob([JSON.stringify(obj)],
+ { type: 'application/json' });
+ var data = { blob: blob, index: 5 };
+
+ objectStore = db.transaction('test', 'readwrite').objectStore('test');
+ objectStore.add(data).onsuccess = function(evt) {
+ var key = evt.target.result;
+ objectStore = db.transaction('test').objectStore('test');
+ objectStore.get(key).onsuccess = function(evt) {
+ resolve(evt.target.result.blob);
+ };
+ };
+ };
+ });
+}
+
+navigator.serviceWorker.ready.then(function() {
+ parent.postMessage({ status: 'READY' }, '*');
+});
+
+var URL = '/tests/dom/workers/test/serviceworkers/redirect_post.sjs';
+
+addEventListener('message', function(evt) {
+ if (evt.data.type = 'TEST') {
+ makeFileBlob(evt.data.body).then(function(blob) {
+ return fetch(URL, { method: 'POST', body: blob });
+ }).then(function(response) {
+ return response.json();
+ }).then(function(result) {
+ parent.postMessage({ status: 'OK', result: result }, '*');
+ }).catch(function(e) {
+ parent.postMessage({ status: 'ERROR', result: e.toString() }, '*');
+ });
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/sw_clients/navigator.html b/dom/workers/test/serviceworkers/sw_clients/navigator.html
new file mode 100644
index 000000000..f6019bf28
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sw_clients/navigator.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>Bug 982726 - test match_all not crashing</title>
+ <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">
+
+ if (!parent) {
+ dump("sw_clients/navigator.html shouldn't be launched directly!\n");
+ }
+
+ window.addEventListener("message", function(event) {
+ if (event.data.type === "NAVIGATE") {
+ window.location = event.data.url;
+ }
+ });
+
+ navigator.serviceWorker.ready.then(function() {
+ parent.postMessage("NAVIGATOR_READY", "*");
+ });
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/sw_clients/refresher.html b/dom/workers/test/serviceworkers/sw_clients/refresher.html
new file mode 100644
index 000000000..054f6bfc8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sw_clients/refresher.html
@@ -0,0 +1,39 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 982726 - test match_all not crashing</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <!-- some tests will intercept this bogus script request -->
+ <script type="text/javascript" src="does_not_exist.js"></script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ if (!parent) {
+ dump("sw_clients/simple.html shouldn't be launched directly!");
+ }
+
+ window.addEventListener("message", function(event) {
+ if (event.data === "REFRESH") {
+ window.location.reload();
+ } else if (event.data === "FORCE_REFRESH") {
+ window.location.reload(true);
+ }
+ });
+
+ navigator.serviceWorker.ready.then(function() {
+ parent.postMessage("READY", "*");
+ });
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/sw_clients/refresher_cached.html b/dom/workers/test/serviceworkers/sw_clients/refresher_cached.html
new file mode 100644
index 000000000..3ec0cc427
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sw_clients/refresher_cached.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>Bug 982726 - test match_all not crashing</title>
+ <script type="text/javascript" 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">
+
+ if (!parent) {
+ info("sw_clients/simple.html shouldn't be launched directly!");
+ }
+
+ window.addEventListener("message", function(event) {
+ if (event.data === "REFRESH") {
+ window.location.reload();
+ } else if (event.data === "FORCE_REFRESH") {
+ window.location.reload(true);
+ }
+ });
+
+ navigator.serviceWorker.ready.then(function() {
+ parent.postMessage("READY_CACHED", "*");
+ });
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/sw_clients/refresher_cached_compressed.html b/dom/workers/test/serviceworkers/sw_clients/refresher_cached_compressed.html
new file mode 100644
index 000000000..55e97ac24
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sw_clients/refresher_cached_compressed.html
Binary files differ
diff --git a/dom/workers/test/serviceworkers/sw_clients/refresher_cached_compressed.html^headers^ b/dom/workers/test/serviceworkers/sw_clients/refresher_cached_compressed.html^headers^
new file mode 100644
index 000000000..4204d8601
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sw_clients/refresher_cached_compressed.html^headers^
@@ -0,0 +1,2 @@
+Content-Type: text/html
+Content-Encoding: gzip
diff --git a/dom/workers/test/serviceworkers/sw_clients/refresher_compressed.html b/dom/workers/test/serviceworkers/sw_clients/refresher_compressed.html
new file mode 100644
index 000000000..7a45bcafa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sw_clients/refresher_compressed.html
Binary files differ
diff --git a/dom/workers/test/serviceworkers/sw_clients/refresher_compressed.html^headers^ b/dom/workers/test/serviceworkers/sw_clients/refresher_compressed.html^headers^
new file mode 100644
index 000000000..4204d8601
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sw_clients/refresher_compressed.html^headers^
@@ -0,0 +1,2 @@
+Content-Type: text/html
+Content-Encoding: gzip
diff --git a/dom/workers/test/serviceworkers/sw_clients/service_worker_controlled.html b/dom/workers/test/serviceworkers/sw_clients/service_worker_controlled.html
new file mode 100644
index 000000000..e0d7bce57
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sw_clients/service_worker_controlled.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>controlled page</title>
+ <!--
+ Paged controlled by a service worker for testing matchAll().
+ See bug 982726, 1058311.
+ -->
+<script class="testbody" type="text/javascript">
+ function fail(msg) {
+ info("service_worker_controlled.html: " + msg);
+ opener.postMessage("FAIL", "*");
+ }
+
+ if (!parent) {
+ info("service_worker_controlled.html should not be launched directly!");
+ }
+
+ window.onload = function() {
+ navigator.serviceWorker.ready.then(function(swr) {
+ parent.postMessage("READY", "*");
+ });
+ }
+
+ navigator.serviceWorker.onmessage = function(msg) {
+ // forward message to the test page.
+ parent.postMessage(msg.data, "*");
+ };
+</script>
+
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/sw_clients/simple.html b/dom/workers/test/serviceworkers/sw_clients/simple.html
new file mode 100644
index 000000000..3e4d7deca
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sw_clients/simple.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>Bug 982726 - test match_all not crashing</title>
+ <script type="text/javascript" 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">
+
+ if (!parent) {
+ info("sw_clients/simple.html shouldn't be launched directly!");
+ }
+
+ navigator.serviceWorker.ready.then(function() {
+ parent.postMessage("READY", "*");
+ });
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/swa/worker_scope_different.js b/dom/workers/test/serviceworkers/swa/worker_scope_different.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/dom/workers/test/serviceworkers/swa/worker_scope_different.js
diff --git a/dom/workers/test/serviceworkers/swa/worker_scope_different.js^headers^ b/dom/workers/test/serviceworkers/swa/worker_scope_different.js^headers^
new file mode 100644
index 000000000..e85a7f09d
--- /dev/null
+++ b/dom/workers/test/serviceworkers/swa/worker_scope_different.js^headers^
@@ -0,0 +1 @@
+Service-Worker-Allowed: different/path
diff --git a/dom/workers/test/serviceworkers/swa/worker_scope_different2.js b/dom/workers/test/serviceworkers/swa/worker_scope_different2.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/dom/workers/test/serviceworkers/swa/worker_scope_different2.js
diff --git a/dom/workers/test/serviceworkers/swa/worker_scope_different2.js^headers^ b/dom/workers/test/serviceworkers/swa/worker_scope_different2.js^headers^
new file mode 100644
index 000000000..e37307d66
--- /dev/null
+++ b/dom/workers/test/serviceworkers/swa/worker_scope_different2.js^headers^
@@ -0,0 +1 @@
+Service-Worker-Allowed: /different/path
diff --git a/dom/workers/test/serviceworkers/swa/worker_scope_precise.js b/dom/workers/test/serviceworkers/swa/worker_scope_precise.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/dom/workers/test/serviceworkers/swa/worker_scope_precise.js
diff --git a/dom/workers/test/serviceworkers/swa/worker_scope_precise.js^headers^ b/dom/workers/test/serviceworkers/swa/worker_scope_precise.js^headers^
new file mode 100644
index 000000000..30b053055
--- /dev/null
+++ b/dom/workers/test/serviceworkers/swa/worker_scope_precise.js^headers^
@@ -0,0 +1 @@
+Service-Worker-Allowed: /tests/dom/workers/test/serviceworkers/swa
diff --git a/dom/workers/test/serviceworkers/swa/worker_scope_too_deep.js b/dom/workers/test/serviceworkers/swa/worker_scope_too_deep.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/dom/workers/test/serviceworkers/swa/worker_scope_too_deep.js
diff --git a/dom/workers/test/serviceworkers/swa/worker_scope_too_deep.js^headers^ b/dom/workers/test/serviceworkers/swa/worker_scope_too_deep.js^headers^
new file mode 100644
index 000000000..b2056fc4a
--- /dev/null
+++ b/dom/workers/test/serviceworkers/swa/worker_scope_too_deep.js^headers^
@@ -0,0 +1 @@
+Service-Worker-Allowed: /tests/dom/workers/test/serviceworkers/swa/deep/way/too/specific
diff --git a/dom/workers/test/serviceworkers/swa/worker_scope_too_narrow.js b/dom/workers/test/serviceworkers/swa/worker_scope_too_narrow.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/dom/workers/test/serviceworkers/swa/worker_scope_too_narrow.js
diff --git a/dom/workers/test/serviceworkers/swa/worker_scope_too_narrow.js^headers^ b/dom/workers/test/serviceworkers/swa/worker_scope_too_narrow.js^headers^
new file mode 100644
index 000000000..22add13bf
--- /dev/null
+++ b/dom/workers/test/serviceworkers/swa/worker_scope_too_narrow.js^headers^
@@ -0,0 +1 @@
+Service-Worker-Allowed: /tests/dom/workers
diff --git a/dom/workers/test/serviceworkers/test_bug1151916.html b/dom/workers/test/serviceworkers/test_bug1151916.html
new file mode 100644
index 000000000..92811775b
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_bug1151916.html
@@ -0,0 +1,105 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1151916 - Test principal is set on cached serviceworkers</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<!--
+ If the principal is not set, accessing self.caches in the worker will crash.
+-->
+</head>
+<body>
+<p id="display"></p>
+<div id="content"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var frame;
+
+ function listenForMessage() {
+ var p = new Promise(function(resolve, reject) {
+ window.onmessage = function(e) {
+ if (e.data.status == "failed") {
+ ok(false, "iframe had error " + e.data.message);
+ reject(e.data.message);
+ } else if (e.data.status == "success") {
+ ok(true, "iframe step success " + e.data.message);
+ resolve(e.data.message);
+ } else {
+ ok(false, "Unexpected message " + e.data);
+ reject();
+ }
+ }
+ });
+
+ return p;
+ }
+
+ // We have the iframe register for its own scope so that this page is not
+ // holding any references when we GC.
+ function register() {
+ var p = listenForMessage();
+
+ frame = document.createElement("iframe");
+ document.body.appendChild(frame);
+ frame.src = "bug1151916_driver.html";
+
+ return p;
+ }
+
+ function unloadFrame() {
+ frame.src = "about:blank";
+ frame.parentNode.removeChild(frame);
+ frame = null;
+ }
+
+ function gc() {
+ return new Promise(function(resolve) {
+ SpecialPowers.exactGC(resolve);
+ });
+ }
+
+ function testCaches() {
+ var p = listenForMessage();
+
+ frame = document.createElement("iframe");
+ document.body.appendChild(frame);
+ frame.src = "bug1151916_driver.html";
+
+ return p;
+ }
+
+ function unregister() {
+ return navigator.serviceWorker.getRegistration("./bug1151916_driver.html").then(function(reg) {
+ ok(reg instanceof ServiceWorkerRegistration, "Must have valid registration.");
+ return reg.unregister();
+ });
+ }
+
+ function runTest() {
+ register()
+ .then(unloadFrame)
+ .then(gc)
+ .then(testCaches)
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_bug1240436.html b/dom/workers/test/serviceworkers/test_bug1240436.html
new file mode 100644
index 000000000..e93535840
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_bug1240436.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 encoding of service workers</title>
+ <script type="text/javascript" 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">
+ SimpleTest.waitForExplicitFinish();
+
+ function runTest() {
+ navigator.serviceWorker.register("bug1240436_worker.js")
+ .then(reg => reg.unregister())
+ .then(() => ok(true, "service worker register script succeed"))
+ .catch(err => ok(false, "service worker register script faled " + err))
+ .then(() => SimpleTest.finish());
+ }
+
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_claim.html b/dom/workers/test/serviceworkers/test_claim.html
new file mode 100644
index 000000000..d7015850f
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_claim.html
@@ -0,0 +1,172 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1130684 - Test service worker clients claim onactivate </title>
+ <script type="text/javascript" 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 registration_1;
+ var registration_2;
+ var client;
+
+ function register_1() {
+ return navigator.serviceWorker.register("claim_worker_1.js",
+ { scope: "./" })
+ .then((swr) => registration_1 = swr);
+ }
+
+ function register_2() {
+ return navigator.serviceWorker.register("claim_worker_2.js",
+ { scope: "./claim_clients/client.html" })
+ .then((swr) => registration_2 = swr);
+ }
+
+ function unregister(reg) {
+ return reg.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ });
+ }
+
+ function createClient() {
+ var p = new Promise(function(res, rej) {
+ window.onmessage = function(e) {
+ if (e.data === "READY") {
+ res();
+ }
+ }
+ });
+
+ content = document.getElementById("content");
+ ok(content, "parent exists.");
+
+ client = document.createElement("iframe");
+ client.setAttribute('src', "claim_clients/client.html");
+ content.appendChild(client);
+
+ return p;
+ }
+
+ function testController() {
+ ok(navigator.serviceWorker.controller.scriptURL.match("claim_worker_1"),
+ "Controlling service worker has the correct url.");
+ }
+
+ function testClientWasClaimed(expected) {
+ var resolveClientMessage, resolveClientControllerChange;
+ var messageFromClient = new Promise(function(res, rej) {
+ resolveClientMessage = res;
+ });
+ var controllerChangeFromClient = new Promise(function(res, rej) {
+ resolveClientControllerChange = res;
+ });
+ window.onmessage = function(e) {
+ if (!e.data.event) {
+ ok(false, "Unknown message received: " + e.data);
+ }
+
+ if (e.data.event === "controllerchange") {
+ ok(e.data.controller,
+ "Client was claimed and received controllerchange event.");
+ resolveClientControllerChange();
+ }
+
+ if (e.data.event === "message") {
+ ok(e.data.data.resolve_value === undefined,
+ "Claim should resolve with undefined.");
+ ok(e.data.data.message === expected.message,
+ "Client received message from claiming worker.");
+ ok(e.data.data.match_count_before === expected.match_count_before,
+ "MatchAll clients count before claim should be " + expected.match_count_before);
+ ok(e.data.data.match_count_after === expected.match_count_after,
+ "MatchAll clients count after claim should be " + expected.match_count_after);
+ resolveClientMessage();
+ }
+ }
+
+ return Promise.all([messageFromClient, controllerChangeFromClient])
+ .then(() => window.onmessage = null);
+ }
+
+ function testClaimFirstWorker() {
+ // wait for the worker to control us
+ var controllerChange = new Promise(function(res, rej) {
+ navigator.serviceWorker.oncontrollerchange = function(e) {
+ ok(true, "controller changed event received.");
+ res();
+ };
+ });
+
+ var messageFromWorker = new Promise(function(res, rej) {
+ navigator.serviceWorker.onmessage = function(e) {
+ ok(e.data.resolve_value === undefined,
+ "Claim should resolve with undefined.");
+ ok(e.data.message === "claim_worker_1",
+ "Received message from claiming worker.");
+ ok(e.data.match_count_before === 0,
+ "Worker doesn't control any client before claim.");
+ ok(e.data.match_count_after === 2, "Worker should claim 2 clients.");
+ res();
+ }
+ });
+
+ var clientClaim = testClientWasClaimed({
+ message: "claim_worker_1",
+ match_count_before: 0,
+ match_count_after: 2
+ });
+
+ return Promise.all([controllerChange, messageFromWorker, clientClaim])
+ .then(testController);
+ }
+
+ function testClaimSecondWorker() {
+ navigator.serviceWorker.oncontrollerchange = function(e) {
+ ok(false, "Claim_worker_2 shouldn't claim this window.");
+ }
+
+ navigator.serviceWorker.onmessage = function(e) {
+ ok(false, "Claim_worker_2 shouldn't claim this window.");
+ }
+
+ var clientClaim = testClientWasClaimed({
+ message: "claim_worker_2",
+ match_count_before: 0,
+ match_count_after: 1
+ });
+
+ return clientClaim.then(testController);
+ }
+
+ function runTest() {
+ createClient()
+ .then(register_1)
+ .then(testClaimFirstWorker)
+ .then(register_2)
+ .then(testClaimSecondWorker)
+ .then(function() { return unregister(registration_1); })
+ .then(function() { return unregister(registration_2); })
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_claim_fetch.html b/dom/workers/test/serviceworkers/test_claim_fetch.html
new file mode 100644
index 000000000..8db6d304e
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_claim_fetch.html
@@ -0,0 +1,98 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1130684 - Test fetch events are intercepted after claim </title>
+ <script type="text/javascript" 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">
+ <a ping="ping" href="fetch.txt">link</a>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+ var registration;
+
+ function register() {
+ return navigator.serviceWorker.register("claim_fetch_worker.js",
+ { scope: "./" })
+ .then((swr) => registration = swr);
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ });
+ }
+
+ function createClient() {
+ var p = new Promise(function(res, rej){
+ window.onmessage = function(e) {
+ if(e.data === "READY") {
+ res();
+ }
+ }
+ });
+
+ var content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ iframe = document.createElement("iframe");
+ iframe.setAttribute('src', "sw_clients/service_worker_controlled.html");
+ content.appendChild(iframe);
+
+ return p;
+ }
+
+ function testFetch(before) {
+ return fetch("fetch/real-file.txt").then(function(res) {
+ ok(res.ok, "Response should be valid.");
+ return res.text().then(function(body) {
+ if (before) {
+ ok(body !== "Fetch was intercepted", "Fetch events should not be intercepted.");
+ } else {
+ ok(body === "Fetch was intercepted", "Fetch events should be intercepted.");
+ }
+ });
+ });
+ }
+
+ function claimThisPage() {
+ ok(registration.active, "Worker is active.");
+ var p = new Promise(function (res, rej) {
+ navigator.serviceWorker.oncontrollerchange = res;
+ });
+
+ registration.active.postMessage("Claim");
+
+ return p;
+ }
+
+ function runTest() {
+ register()
+ .then(createClient)
+ .then(testFetch.bind(this, true))
+ .then(claimThisPage)
+ .then(testFetch.bind(this, false))
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_claim_oninstall.html b/dom/workers/test/serviceworkers/test_claim_oninstall.html
new file mode 100644
index 000000000..4605cfb76
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_claim_oninstall.html
@@ -0,0 +1,78 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1130684 - Test service worker clients.claim oninstall</title>
+ <script type="text/javascript" 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 registration;
+
+ function register() {
+ return navigator.serviceWorker.register("claim_oninstall_worker.js",
+ { scope: "./" })
+ .then((swr) => registration = swr);
+ }
+
+
+ function unregister() {
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ });
+ }
+
+ function testClaim() {
+ ok(registration.installing, "Worker should be in installing state");
+
+ navigator.serviceWorker.oncontrollerchange = function() {
+ ok(false, "Claim should not succeed when the worker is not active.");
+ }
+
+ var p = new Promise(function(res, rej) {
+ var worker = registration.installing;
+ worker.onstatechange = function(e) {
+ if (worker.state === 'installed') {
+ is(worker, registration.waiting, "Worker should be in waiting state");
+ } else if (worker.state === 'activated') {
+ // The worker will become active only if claim will reject inside the
+ // install handler.
+ is(worker, registration.active,
+ "Claim should reject if the worker is not active");
+ ok(navigator.serviceWorker.controller === null, "Client is not controlled.");
+ e.target.onstatechange = null;
+ res();
+ }
+ }
+ });
+
+ return p;
+ }
+
+ function runTest() {
+ register()
+ .then(testClaim)
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_client_focus.html b/dom/workers/test/serviceworkers/test_client_focus.html
new file mode 100644
index 000000000..b0bf43bb3
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_client_focus.html
@@ -0,0 +1,96 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1130686 - Test service worker client.focus </title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<!--
+ This test checks that client.focus is available.
+ Actual focusing is tested by test_notificationclick_focus.html since only notification events have permission to change focus.
+-->
+</head>
+<body>
+<p id="display"></p>
+<div id="content"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+ var registration;
+ var worker;
+
+ function start() {
+ return navigator.serviceWorker.register("client_focus_worker.js",
+ { scope: "./sw_clients/focus_stealing_client.html" })
+ .then((swr) => registration = swr);
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ }
+
+ function loseFocus() {
+ var p = new Promise(function(res, rej) {
+ window.onmessage = function(e) {
+ if (e.data == "READY") {
+ ok(true, "iframe created.");
+ iframe.contentWindow.focus();
+ }
+ }
+ window.onblur = function() {
+ ok(true, "blurred");
+ res();
+ }
+ });
+
+ content = document.getElementById("content");
+ ok(content, "parent exists.");
+
+ iframe = document.createElement("iframe");
+ content.appendChild(iframe);
+
+ iframe.setAttribute('src', "sw_clients/focus_stealing_client.html");
+ return p;
+ }
+
+ function testFocus() {
+ var p = new Promise(function(res, rej) {
+ navigator.serviceWorker.onmessage = function(e) {
+ ok(e.data, "client object is marked as focused.");
+ ok(document.hasFocus(), "document has focus.");
+ res();
+ }
+ });
+
+ ok(registration.active, "active worker exists.");
+ registration.active.postMessage("go");
+ return p;
+ }
+
+ function runTest() {
+ start()
+ .then(loseFocus)
+ .then(testFocus)
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_close.html b/dom/workers/test/serviceworkers/test_close.html
new file mode 100644
index 000000000..d2f72b9ef
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_close.html
@@ -0,0 +1,64 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1131353 - test WorkerGlobalScope.close() on service workers</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ function runTest() {
+ navigator.serviceWorker.ready.then(setupSW);
+ navigator.serviceWorker.register("close_test.js", {scope: "."});
+
+ function setupSW(registration) {
+ var worker = registration.waiting ||
+ registration.active;
+ var iframe = document.createElement("iframe");
+ iframe.src = "message_receiver.html";
+ iframe.onload = function() {
+ worker.postMessage({ message: "start" });
+ };
+ document.body.appendChild(iframe);
+ }
+
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "done") {
+ navigator.serviceWorker.getRegistration().then(function(registration) {
+ registration.unregister().then(function(result) {
+ ok(result, "Unregistering the service worker should succeed");
+ SimpleTest.finish();
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ SimpleTest.finish();
+ });
+ });
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_controller.html b/dom/workers/test/serviceworkers/test_controller.html
new file mode 100644
index 000000000..789d7746d
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_controller.html
@@ -0,0 +1,84 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1002570 - test controller instance.</title>
+ <script type="text/javascript" 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 content;
+ var iframe;
+ var registration;
+
+ function simpleRegister() {
+ // We use the control scope for the less specific registration. The window will register a worker on controller/
+ return navigator.serviceWorker.register("worker.js", { scope: "./control" })
+ .then(function(reg) {
+ registration = reg;
+ });;
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ dump("Unregistering the SW failed: " + e + "\n");
+ });
+ }
+
+ function testController() {
+ var p = new Promise(function(resolve, reject) {
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "done") {
+ window.onmessage = null;
+ content.removeChild(iframe);
+ resolve();
+ }
+ }
+ });
+
+ content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ iframe = document.createElement("iframe");
+ iframe.setAttribute('src', "controller/index.html");
+ content.appendChild(iframe);
+
+ return p;
+ }
+
+ // This document just flips the prefs and opens the iframe for the actual test.
+ function runTest() {
+ simpleRegister()
+ .then(testController)
+ .then(unregister)
+ .then(function() {
+ SimpleTest.finish();
+ }).catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_cross_origin_url_after_redirect.html b/dom/workers/test/serviceworkers/test_cross_origin_url_after_redirect.html
new file mode 100644
index 000000000..e56bb84ca
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_cross_origin_url_after_redirect.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>Test access to a cross origin Request.url property from a service worker for a redirected intercepted iframe</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/requesturl/register.html";
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/requesturl/index.html";
+ } else if (e.data.status == "done") {
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/requesturl/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_csp_upgrade-insecure_intercept.html b/dom/workers/test/serviceworkers/test_csp_upgrade-insecure_intercept.html
new file mode 100644
index 000000000..fe4cb991c
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_csp_upgrade-insecure_intercept.html
@@ -0,0 +1,55 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that a CSP upgraded request can be intercepted by a service worker</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/upgrade-insecure/register.html";
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html";
+ } else if (e.data.status == "protocol") {
+ is(e.data.data, "https:", "Correct protocol expected");
+ } else if (e.data.status == "image") {
+ is(e.data.data, 40, "The image request was upgraded before interception");
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/upgrade-insecure/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ // This is needed so that we can test upgrading a non-secure load inside an https iframe.
+ ["security.mixed_content.block_active_content", false],
+ ["security.mixed_content.block_display_content", false],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_empty_serviceworker.html b/dom/workers/test/serviceworkers/test_empty_serviceworker.html
new file mode 100644
index 000000000..e42951896
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_empty_serviceworker.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 that registering an empty service worker works</title>
+ <script type="text/javascript" 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() {
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("empty.js", {scope: "."});
+ }
+
+ function done(registration) {
+ ok(registration.waiting || registration.active, "registration worked");
+ registration.unregister().then(function(success) {
+ ok(success, "unregister worked");
+ SimpleTest.finish();
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_error_reporting.html b/dom/workers/test/serviceworkers/test_error_reporting.html
new file mode 100644
index 000000000..619d602e8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_error_reporting.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Error Reporting of Service Worker Failures</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script src="error_reporting_helpers.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+/**
+ * Test that a bunch of service worker coding errors and failure modes that
+ * might otherwise be hard to diagnose are surfaced as console error messages.
+ * The driving use-case is minimizing cursing from a developer looking at a
+ * document in Firefox testing a page that involves service workers.
+ *
+ * This test assumes that errors will be reported via
+ * ServiceWorkerManager::ReportToAllClients and that that method is reliable and
+ * tested via some other file.
+ **/
+
+add_task(function setupPrefs() {
+ return SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.testing.enabled", true],
+ ]});
+});
+
+/**
+ * Ensure an error is logged during the initial registration of a SW when a 404
+ * is received.
+ */
+add_task(function* register_404() {
+ // Start monitoring for the error
+ let expectedMessage = expect_console_message(
+ "ServiceWorkerRegisterNetworkError",
+ [make_absolute_url("network_error/"), "404", make_absolute_url("404.js")]);
+
+ // Register, generating the 404 error. This will reject with a TypeError
+ // which we need to consume so it doesn't get thrown at our generator.
+ yield navigator.serviceWorker.register("404.js", { scope: "network_error/" })
+ .then(
+ () => { ok(false, "should have rejected"); },
+ (e) => { ok(e.name === "TypeError", "404 failed as expected"); });
+
+ yield wait_for_expected_message(expectedMessage);
+});
+
+/**
+ * Ensure an error is logged when the service worker is being served with a
+ * MIME type of text/plain rather than a JS type.
+ */
+add_task(function* register_bad_mime_type() {
+ let expectedMessage = expect_console_message(
+ "ServiceWorkerRegisterMimeTypeError",
+ [make_absolute_url("bad_mime_type/"), "text/plain",
+ make_absolute_url("sw_bad_mime_type.js")]);
+
+ // consume the expected rejection so it doesn't get thrown at us.
+ yield navigator.serviceWorker.register("sw_bad_mime_type.js", { scope: "bad_mime_type/" })
+ .then(
+ () => { ok(false, "should have rejected"); },
+ (e) => { ok(e.name === "SecurityError", "bad MIME type failed as expected"); });
+
+ yield wait_for_expected_message(expectedMessage);
+});
+</script>
+
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_escapedSlashes.html b/dom/workers/test/serviceworkers/test_escapedSlashes.html
new file mode 100644
index 000000000..001c66024
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_escapedSlashes.html
@@ -0,0 +1,102 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for escaped slashes in navigator.serviceWorker.register</title>
+ <script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css" />
+ <base href="https://mozilla.org/">
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+var tests = [
+ { status: true,
+ scriptURL: "a.js?foo%2fbar",
+ scopeURL: null },
+ { status: false,
+ scriptURL: "foo%2fbar",
+ scopeURL: null },
+ { status: true,
+ scriptURL: "a.js?foo%2Fbar",
+ scopeURL: null },
+ { status: false,
+ scriptURL: "foo%2Fbar",
+ scopeURL: null },
+ { status: true,
+ scriptURL: "a.js?foo%5cbar",
+ scopeURL: null },
+ { status: false,
+ scriptURL: "foo%5cbar",
+ scopeURL: null },
+ { status: true,
+ scriptURL: "a.js?foo%2Cbar",
+ scopeURL: null },
+ { status: false,
+ scriptURL: "foo%5Cbar",
+ scopeURL: null },
+ { status: true,
+ scriptURL: "ok.js",
+ scopeURL: "/scope?foo%2fbar"},
+ { status: false,
+ scriptURL: "ok.js",
+ scopeURL: "/foo%2fbar"},
+ { status: true,
+ scriptURL: "ok.js",
+ scopeURL: "/scope?foo%2Fbar"},
+ { status: false,
+ scriptURL: "ok.js",
+ scopeURL: "foo%2Fbar"},
+ { status: true,
+ scriptURL: "ok.js",
+ scopeURL: "/scope?foo%5cbar"},
+ { status: false,
+ scriptURL: "ok.js",
+ scopeURL: "foo%5cbar"},
+ { status: true,
+ scriptURL: "ok.js",
+ scopeURL: "/scope?foo%5Cbar"},
+ { status: false,
+ scriptURL: "ok.js",
+ scopeURL: "foo%5Cbar"},
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ navigator.serviceWorker.register(test.scriptURL, test.scopeURL)
+ .then(reg => {
+ ok(false, "Register should fail");
+ }, err => {
+ if (!test.status) {
+ is(err.name, "TypeError", "Registration should fail with TypeError");
+ } else {
+ ok(test.status, "Register should fail");
+ }
+ })
+ .then(runTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.serviceWorkers.enabled", true],
+ ]}, runTest);
+};
+
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_eval_allowed.html b/dom/workers/test/serviceworkers/test_eval_allowed.html
new file mode 100644
index 000000000..bfe8ac280
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_eval_allowed.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 1160458 - CSP activated by default in Service Workers</title>
+ <script type="text/javascript" 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 register() {
+ return navigator.serviceWorker.register("eval_worker.js");
+ }
+
+ function runTest() {
+ try {
+ eval("1");
+ ok(false, "should throw");
+ }
+ catch (ex) {
+ ok(true, "did throw");
+ }
+ register()
+ .then(function(swr) {
+ ok(true, "eval restriction didn't get inherited");
+ swr.unregister()
+ .then(function() {
+ SimpleTest.finish();
+ });
+ }).catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_eval_allowed.html^headers^ b/dom/workers/test/serviceworkers/test_eval_allowed.html^headers^
new file mode 100644
index 000000000..51ffaa71d
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_eval_allowed.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self'"
diff --git a/dom/workers/test/serviceworkers/test_eventsource_intercept.html b/dom/workers/test/serviceworkers/test_eventsource_intercept.html
new file mode 100644
index 000000000..55df62bb7
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_eventsource_intercept.html
@@ -0,0 +1,103 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1182103 - Test EventSource scenarios with fetch interception</title>
+ <script type="text/javascript" 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 testFrame(src) {
+ return new Promise(function(resolve, reject) {
+ var iframe = document.createElement("iframe");
+ iframe.src = src;
+ window.onmessage = function(e) {
+ if (e.data.status == "callback") {
+ switch(e.data.data) {
+ case "ok":
+ ok(e.data.condition, e.data.message);
+ break;
+ case "ready":
+ iframe.contentWindow.postMessage({status: "callback", data: "eventsource"}, "*");
+ break;
+ case "done":
+ window.onmessage = null;
+ iframe.src = "about:blank";
+ document.body.removeChild(iframe);
+ iframe = null;
+ resolve();
+ break;
+ default:
+ ok(false, "Something went wrong");
+ break;
+ }
+ } else {
+ ok(false, "Something went wrong");
+ }
+ };
+ document.body.appendChild(iframe);
+ });
+ }
+
+ function runTest() {
+ Promise.resolve()
+ .then(() => {
+ info("Going to intercept and test opaque responses");
+ return testFrame("eventsource/eventsource_register_worker.html" +
+ "?script=eventsource_opaque_response_intercept_worker.js");
+ })
+ .then(() => {
+ return testFrame("eventsource/eventsource_opaque_response.html");
+ })
+ .then(() => {
+ info("Going to intercept and test cors responses");
+ return testFrame("eventsource/eventsource_register_worker.html" +
+ "?script=eventsource_cors_response_intercept_worker.js");
+ })
+ .then(() => {
+ return testFrame("eventsource/eventsource_cors_response.html");
+ })
+ .then(() => {
+ info("Going to intercept and test synthetic responses");
+ return testFrame("eventsource/eventsource_register_worker.html" +
+ "?script=eventsource_synthetic_response_intercept_worker.js");
+ })
+ .then(() => {
+ return testFrame("eventsource/eventsource_synthetic_response.html");
+ })
+ .then(() => {
+ info("Going to intercept and test mixed content cors responses");
+ return testFrame("https://example.com/tests/dom/workers/test/serviceworkers/" +
+ "eventsource/eventsource_register_worker.html" +
+ "?script=eventsource_mixed_content_cors_response_intercept_worker.js");
+ })
+ .then(() => {
+ return testFrame("https://example.com/tests/dom/workers/test/serviceworkers/" +
+ "eventsource/eventsource_mixed_content_cors_response.html");
+ })
+ .then(SimpleTest.finish)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_fetch_event.html b/dom/workers/test/serviceworkers/test_fetch_event.html
new file mode 100644
index 000000000..764be87b1
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_fetch_event.html
@@ -0,0 +1,83 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 94048 - test install event.</title>
+ <script type="text/javascript" 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">
+ SimpleTest.requestCompleteLog();
+
+ var registration;
+ function simpleRegister() {
+ var p = navigator.serviceWorker.register("fetch_event_worker.js", { scope: "./fetch" });
+ return p.then(function(swr) {
+ registration = swr;
+ return new Promise(function(resolve) {
+ swr.installing.onstatechange = resolve;
+ });
+ });
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(success) {
+ ok(success, "Service worker should be unregistered successfully");
+ }, function(e) {
+ dump("SW unregistration error: " + e + "\n");
+ });
+ }
+
+ function testController() {
+ var p = new Promise(function(resolve, reject) {
+ var reloaded = false;
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "done") {
+ if (reloaded) {
+ window.onmessage = null;
+ w.close();
+ resolve();
+ } else {
+ w.location.reload();
+ reloaded = true;
+ }
+ }
+ }
+ });
+
+ var w = window.open("fetch/index.html");
+ return p;
+ }
+
+ function runTest() {
+ simpleRegister()
+ .then(testController)
+ .then(unregister)
+ .then(function() {
+ SimpleTest.finish();
+ }).catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_fetch_integrity.html b/dom/workers/test/serviceworkers/test_fetch_integrity.html
new file mode 100644
index 000000000..50eb05581
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_fetch_integrity.html
@@ -0,0 +1,178 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title> Test fetch.integrity on console report for serviceWorker and sharedWorker </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script src="error_reporting_helpers.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+</head>
+<body>
+<div id="content" style="display: none"></div>
+<script type="text/javascript">
+"use strict";
+
+let security_localizer =
+ stringBundleService.createBundle("chrome://global/locale/security/security.properties");
+
+function expect_security_console_message(/* msgId, args, ... */) {
+ let expectations = [];
+ // process repeated paired arguments of: msgId, args
+ for (let i = 0; i < arguments.length; i += 4) {
+ let msgId = arguments[i];
+ let args = arguments[i + 1];
+ let filename = arguments[i + 2];
+ let windowId = arguments[i + 3];
+ expectations.push({
+ errorMessage: security_localizer.formatStringFromName(msgId, args, args.length),
+ sourceName: filename,
+ windowID: windowId
+ });
+ }
+ return new Promise(resolve => {
+ SimpleTest.monitorConsole(resolve, expectations);
+ });
+}
+
+// (This doesn't really need to be its own task, but it allows the actual test
+// case to be self-contained.)
+add_task(function setupPrefs() {
+ return SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]});
+});
+
+add_task(function* test_integrity_serviceWorker() {
+ var filename = make_absolute_url("fetch.js");
+ var filename2 = make_absolute_url("fake.html");
+
+ // The SW will claim us once it activates; this is async, start listening now.
+ let waitForControlled = new Promise((resolve) => {
+ navigator.serviceWorker.oncontrollerchange = resolve;
+ });
+
+ let registration = yield navigator.serviceWorker.register("fetch.js",
+ { scope: "./" });
+ yield waitForControlled;
+
+ info("Test for mNavigationInterceptions.")
+ // The client_win will reload to another URL after opening filename2.
+ let client_win = window.open(filename2);
+
+ // XXX windowID should be innerWindowID
+ let mainWindowID = SpecialPowers.getDOMWindowUtils(window).outerWindowID;
+ let clientWindowID = SpecialPowers.getDOMWindowUtils(client_win).outerWindowID;
+ let expectedMessage = expect_security_console_message(
+ "MalformedIntegrityHash",
+ ["abc"],
+ filename,
+ mainWindowID,
+ "NoValidMetadata",
+ [""],
+ filename,
+ mainWindowID
+ );
+ let expectedMessage2 = expect_security_console_message(
+ "MalformedIntegrityHash",
+ ["abc"],
+ filename,
+ clientWindowID,
+ "NoValidMetadata",
+ [""],
+ filename,
+ clientWindowID
+ );
+
+ info("Test for mControlledDocuments and report error message to console.");
+ // The fetch will succeed because the integrity value is invalid and we are
+ // looking for the console message regarding the bad integrity value.
+ yield fetch("fail.html");
+
+ yield wait_for_expected_message(expectedMessage);
+
+ yield wait_for_expected_message(expectedMessage2);
+
+ yield registration.unregister();
+ client_win.close();
+});
+
+add_task(function* test_integrity_sharedWorker() {
+ var filename = make_absolute_url("sharedWorker_fetch.js");
+
+ info("Attch main window to a SharedWorker.");
+ let sharedWorker = new SharedWorker(filename);
+ let waitForConnected = new Promise((resolve) => {
+ sharedWorker.port.onmessage = function (e) {
+ if (e.data == "Connected") {
+ resolve();
+ } else {
+ reject();
+ }
+ }
+ });
+ yield waitForConnected;
+
+ info("Attch another window to the same SharedWorker.");
+ // Open another window and its also managed by the shared worker.
+ let client_win = window.open("create_another_sharedWorker.html");
+ let waitForBothConnected = new Promise((resolve) => {
+ sharedWorker.port.onmessage = function (e) {
+ if (e.data == "BothConnected") {
+ resolve();
+ } else {
+ reject();
+ }
+ }
+ });
+ yield waitForBothConnected;
+
+ // XXX windowID should be innerWindowID
+ let mainWindowID = SpecialPowers.getDOMWindowUtils(window).outerWindowID;
+ let clientWindowID = SpecialPowers.getDOMWindowUtils(client_win).outerWindowID;
+ let expectedMessage = expect_security_console_message(
+ "MalformedIntegrityHash",
+ ["abc"],
+ filename,
+ mainWindowID,
+ "NoValidMetadata",
+ [""],
+ filename,
+ mainWindowID
+ );
+ let expectedMessage2 = expect_security_console_message(
+ "MalformedIntegrityHash",
+ ["abc"],
+ filename,
+ clientWindowID,
+ "NoValidMetadata",
+ [""],
+ filename,
+ clientWindowID
+ );
+
+ info("Start to fetch a URL with wrong integrity.")
+ sharedWorker.port.start();
+ sharedWorker.port.postMessage("StartFetchWithWrongIntegrity");
+
+ let waitForSRIFailed = new Promise((resolve) => {
+ sharedWorker.port.onmessage = function (e) {
+ if (e.data == "SRI_failed") {
+ resolve();
+ } else {
+ reject();
+ }
+ }
+ });
+ yield waitForSRIFailed;
+
+ yield wait_for_expected_message(expectedMessage);
+
+ yield wait_for_expected_message(expectedMessage2);
+ client_win.close();
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_file_blob_response.html b/dom/workers/test/serviceworkers/test_file_blob_response.html
new file mode 100644
index 000000000..6db0656c6
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_file_blob_response.html
@@ -0,0 +1,86 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1253777 - Test interception using file blob response body</title>
+ <script type="text/javascript" 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 registration;
+ var scope = './file_blob_response/';
+ function start() {
+ return navigator.serviceWorker.register("file_blob_response_worker.js",
+ { scope: scope })
+ .then(function(swr) {
+ registration = swr;
+ return new Promise(function(resolve) {
+ registration.installing.onstatechange = function(evt) {
+ if (evt.target.state === 'activated') {
+ evt.target.onstate = null;
+ resolve();
+ }
+ }
+ });
+ });
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ ok(false, "Unregistering the SW failed with " + e + "\n");
+ });
+ }
+
+ function withFrame(url) {
+ return new Promise(function(resolve, reject) {
+ var content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ var frame = document.createElement("iframe");
+ frame.setAttribute('src', url);
+ content.appendChild(frame);
+
+ frame.addEventListener('load', function loadCallback(evt) {
+ frame.removeEventListener('load', loadCallback);
+ resolve(frame);
+ });
+ });
+ }
+
+ function runTest() {
+ start()
+ .then(function() {
+ return withFrame(scope + 'dummy.txt');
+ })
+ .then(function(frame) {
+ var result = JSON.parse(frame.contentWindow.document.body.textContent);
+ frame.remove();
+ is(result.value, 'success');
+ })
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ })
+ .then(unregister)
+ .then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_file_blob_upload.html b/dom/workers/test/serviceworkers/test_file_blob_upload.html
new file mode 100644
index 000000000..30a31eb7e
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_file_blob_upload.html
@@ -0,0 +1,145 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1203680 - Test interception of file blob uploads</title>
+ <script type="text/javascript" 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 registration;
+ var iframe;
+ function start() {
+ return navigator.serviceWorker.register("empty.js",
+ { scope: "./sw_clients/" })
+ .then((swr) => registration = swr);
+ }
+
+ function unregister() {
+ if (iframe) {
+ iframe.remove();
+ iframe = null;
+ }
+
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ ok(false, "Unregistering the SW failed with " + e + "\n");
+ });
+ }
+
+ function withFrame() {
+ var content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ iframe = document.createElement("iframe");
+ iframe.setAttribute('src', "sw_clients/file_blob_upload_frame.html");
+ content.appendChild(iframe);
+
+ return new Promise(function(resolve, reject) {
+ window.addEventListener('message', function readyCallback(evt) {
+ window.removeEventListener('message', readyCallback);
+ if (evt.data.status === 'READY') {
+ resolve();
+ } else {
+ reject(evt.data.result);
+ }
+ });
+ });
+ }
+
+ function postBlob(body) {
+ return new Promise(function(resolve, reject) {
+ window.addEventListener('message', function postBlobCallback(evt) {
+ window.removeEventListener('message', postBlobCallback);
+ if (evt.data.status === 'OK') {
+ is(JSON.stringify(body), JSON.stringify(evt.data.result),
+ 'body echoed back correctly');
+ resolve();
+ } else {
+ reject(evt.data.result);
+ }
+ });
+
+ iframe.contentWindow.postMessage({ type: 'TEST', body: body }, '*');
+ });
+ }
+
+ function generateMessage(length) {
+
+ var lorem =
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis egestas '
+ 'vehicula tortor eget ultrices. Sed et luctus est. Nunc eu orci ligula. '
+ 'In vel ornare eros, eget lacinia diam. Praesent vel metus mattis, '
+ 'cursus nulla sit amet, rhoncus diam. Aliquam nulla tortor, aliquet et '
+ 'viverra non, dignissim vel tellus. Praesent sed ex in dolor aliquet '
+ 'aliquet. In at facilisis sem, et aliquet eros. Maecenas feugiat nisl '
+ 'quis elit blandit posuere. Duis viverra odio sed eros consectetur, '
+ 'viverra mattis ligula volutpat.';
+
+ var result = '';
+
+ while (result.length < length) {
+ var remaining = length - result.length;
+ if (remaining < lorem.length) {
+ result += lorem.slice(0, remaining);
+ } else {
+ result += lorem;
+ }
+ }
+
+ return result;
+ }
+
+ var smallBody = generateMessage(64);
+ var mediumBody = generateMessage(1024);
+
+ // TODO: Test large bodies over the default pipe size. Currently stalls
+ // due to bug 1134372.
+ //var largeBody = generateMessage(100 * 1024);
+
+ function runTest() {
+ start()
+ .then(withFrame)
+ .then(function() {
+ return postBlob({ hops: 0, message: smallBody });
+ })
+ .then(function() {
+ return postBlob({ hops: 1, message: smallBody });
+ })
+ .then(function() {
+ return postBlob({ hops: 10, message: smallBody });
+ })
+ .then(function() {
+ return postBlob({ hops: 0, message: mediumBody });
+ })
+ .then(function() {
+ return postBlob({ hops: 1, message: mediumBody });
+ })
+ .then(function() {
+ return postBlob({ hops: 10, message: mediumBody });
+ })
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_force_refresh.html b/dom/workers/test/serviceworkers/test_force_refresh.html
new file mode 100644
index 000000000..05caf0e6a
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_force_refresh.html
@@ -0,0 +1,84 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 982726 - Test service worker post message </title>
+ <script type="text/javascript" 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 registration;
+ function start() {
+ return navigator.serviceWorker.register("force_refresh_worker.js",
+ { scope: "./sw_clients/" })
+ .then((swr) => registration = swr);
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ }
+
+
+ function testForceRefresh(swr) {
+ var p = new Promise(function(res, rej) {
+ var count = 0;
+ var cachedCount = 0;
+ window.onmessage = function(e) {
+ if (e.data === "READY") {
+ count += 1;
+ if (count == 2) {
+ is(cachedCount, 1, "should have received cached message before " +
+ "second non-cached message");
+ res();
+ }
+ iframe.contentWindow.postMessage("REFRESH", "*");
+ } else if (e.data === "READY_CACHED") {
+ cachedCount += 1;
+ is(count, 1, "should have received non-cached message before " +
+ "cached message");
+ iframe.contentWindow.postMessage("FORCE_REFRESH", "*");
+ }
+ }
+ });
+
+ var content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ iframe = document.createElement("iframe");
+ iframe.setAttribute('src', "sw_clients/refresher_compressed.html");
+ content.appendChild(iframe);
+
+ return p.then(() => content.removeChild(iframe));
+ }
+
+ function runTest() {
+ start()
+ .then(testForceRefresh)
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_gzip_redirect.html b/dom/workers/test/serviceworkers/test_gzip_redirect.html
new file mode 100644
index 000000000..7ac92122c
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_gzip_redirect.html
@@ -0,0 +1,84 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 982726 - Test service worker post message </title>
+ <script type="text/javascript" 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 registration;
+ function start() {
+ return navigator.serviceWorker.register("gzip_redirect_worker.js",
+ { scope: "./sw_clients/" })
+ .then((swr) => registration = swr);
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ }
+
+
+ function testGzipRedirect(swr) {
+ var p = new Promise(function(res, rej) {
+ var navigatorReady = false;
+ var finalReady = false;
+
+ window.onmessage = function(e) {
+ if (e.data === "NAVIGATOR_READY") {
+ ok(!navigatorReady, "should only get navigator ready message once");
+ ok(!finalReady, "should get navigator ready before final redirect ready message");
+ navigatorReady = true;
+ iframe.contentWindow.postMessage({
+ type: "NAVIGATE",
+ url: "does_not_exist.html"
+ }, "*");
+ } else if (e.data === "READY") {
+ ok(navigatorReady, "should only get navigator ready message once");
+ ok(!finalReady, "should get final ready message only once");
+ finalReady = true;
+ res();
+ }
+ }
+ });
+
+ var content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ iframe = document.createElement("iframe");
+ iframe.setAttribute('src', "sw_clients/navigator.html");
+ content.appendChild(iframe);
+
+ return p.then(() => content.removeChild(iframe));
+ }
+
+ function runTest() {
+ start()
+ .then(testGzipRedirect)
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_hsts_upgrade_intercept.html b/dom/workers/test/serviceworkers/test_hsts_upgrade_intercept.html
new file mode 100644
index 000000000..dfce406b8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_hsts_upgrade_intercept.html
@@ -0,0 +1,66 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that an HSTS upgraded request can be intercepted by a service worker</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ var framesLoaded = 0;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/hsts/register.html";
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ iframe.src = "http://example.com/tests/dom/workers/test/serviceworkers/fetch/hsts/index.html";
+ } else if (e.data.status == "protocol") {
+ is(e.data.data, "https:", "Correct protocol expected");
+ ok(e.data.securityInfoPresent, "Security info present on intercepted value");
+ switch (++framesLoaded) {
+ case 1:
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/hsts/embedder.html";
+ break;
+ case 2:
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/hsts/image.html";
+ break;
+ }
+ } else if (e.data.status == "image") {
+ is(e.data.data, 40, "The image request was upgraded before interception");
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/hsts/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ SpecialPowers.cleanUpSTSData("http://example.com");
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ // This is needed so that we can test upgrading a non-secure load inside an https iframe.
+ ["security.mixed_content.block_active_content", false],
+ ["security.mixed_content.block_display_content", false],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_https_fetch.html b/dom/workers/test/serviceworkers/test_https_fetch.html
new file mode 100644
index 000000000..e990200f8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_https_fetch.html
@@ -0,0 +1,62 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1133763 - test fetch event in HTTPS origins</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/register.html";
+ var ios;
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ ios = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService);
+ ios.offline = true;
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/index.html";
+ } else if (e.data.status == "done") {
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/synth-sw.html";
+ } else if (e.data.status == "done-synth-sw") {
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/synth-window.html";
+ } else if (e.data.status == "done-synth-window") {
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/synth.html";
+ } else if (e.data.status == "done-synth") {
+ ios.offline = false;
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true]
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_https_fetch_cloned_response.html b/dom/workers/test/serviceworkers/test_https_fetch_cloned_response.html
new file mode 100644
index 000000000..1cf1dbef1
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_https_fetch_cloned_response.html
@@ -0,0 +1,56 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1133763 - test fetch event in HTTPS origins with a cloned response</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/clonedresponse/register.html";
+ var ios;
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ ios = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService);
+ ios.offline = true;
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/clonedresponse/index.html";
+ } else if (e.data.status == "done") {
+ ios.offline = false;
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/clonedresponse/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true]
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_https_origin_after_redirect.html b/dom/workers/test/serviceworkers/test_https_origin_after_redirect.html
new file mode 100644
index 000000000..3878a1df6
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_https_origin_after_redirect.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 the origin of a redirected response from a service worker</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/origin/https/register.html";
+ var win;
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ win = window.open("https://example.com/tests/dom/workers/test/serviceworkers/fetch/origin/https/index-https.sjs", "mywindow", "width=100,height=100");
+ } else if (e.data.status == "domain") {
+ is(e.data.data, "example.org", "Correct domain expected");
+ } else if (e.data.status == "origin") {
+ is(e.data.data, "https://example.org", "Correct origin expected");
+ } else if (e.data.status == "done") {
+ win.close();
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/origin/https/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_https_origin_after_redirect_cached.html b/dom/workers/test/serviceworkers/test_https_origin_after_redirect_cached.html
new file mode 100644
index 000000000..81a1d1da0
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_https_origin_after_redirect_cached.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 the origin of a redirected response from a service worker</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/origin/https/register.html";
+ var win;
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ win = window.open("https://example.com/tests/dom/workers/test/serviceworkers/fetch/origin/https/index-cached-https.sjs", "mywindow", "width=100,height=100");
+ } else if (e.data.status == "domain") {
+ is(e.data.data, "example.org", "Correct domain expected");
+ } else if (e.data.status == "origin") {
+ is(e.data.data, "https://example.org", "Correct origin expected");
+ } else if (e.data.status == "done") {
+ win.close();
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/origin/https/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_https_synth_fetch_from_cached_sw.html b/dom/workers/test/serviceworkers/test_https_synth_fetch_from_cached_sw.html
new file mode 100644
index 000000000..7bf3b352a
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_https_synth_fetch_from_cached_sw.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>Bug 1156847 - test fetch event generating a synthesized response in HTTPS origins from a cached SW</title>
+ <script type="text/javascript" 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" tyle="display: none">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/register.html";
+ var ios;
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ ios = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService);
+ ios.offline = true;
+
+ // In order to load synth.html from a cached service worker, we first
+ // remove the existing window that is keeping the service worker alive,
+ // and do a GC to ensure that the SW is destroyed. This way, when we
+ // load synth.html for the second time, we will first recreate the
+ // service worker from the cache. This is intended to test that we
+ // properly store and retrieve the security info from the cache.
+ iframe.parentNode.removeChild(iframe);
+ iframe = null;
+ SpecialPowers.exactGC(function() {
+ iframe = document.createElement("iframe");
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/synth.html";
+ document.body.appendChild(iframe);
+ });
+ } else if (e.data.status == "done-synth") {
+ ios.offline = false;
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true]
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_imagecache.html b/dom/workers/test/serviceworkers/test_imagecache.html
new file mode 100644
index 000000000..8627b54af
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_imagecache.html
@@ -0,0 +1,55 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1202085 - Test that images from different controllers don't cached together</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/imagecache/register.html";
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/imagecache/index.html";
+ } else if (e.data.status == "result") {
+ is(e.data.url, "image-40px.png", "Correct url expected");
+ is(e.data.width, 40, "Correct width expected");
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/imagecache/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/imagecache/postmortem.html";
+ } else if (e.data.status == "postmortem") {
+ is(e.data.width, 20, "Correct width expected");
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_imagecache_max_age.html b/dom/workers/test/serviceworkers/test_imagecache_max_age.html
new file mode 100644
index 000000000..eb3c1f166
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_imagecache_max_age.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 that the image cache respects a synthesized image's Cache headers</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ var framesLoaded = 0;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/imagecache-maxage/register.html";
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/imagecache-maxage/index.html";
+ } else if (e.data.status == "result") {
+ switch (++framesLoaded) {
+ case 1:
+ is(e.data.url, "image-20px.png", "Correct url expected");
+ is(e.data.url2, "image-20px.png", "Correct url expected");
+ is(e.data.width, 20, "Correct width expected");
+ is(e.data.width2, 20, "Correct width expected");
+ // Wait for 100ms so that the image gets expired.
+ setTimeout(function() {
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/imagecache-maxage/index.html?new"
+ }, 100);
+ break;
+ case 2:
+ is(e.data.url, "image-40px.png", "Correct url expected");
+ is(e.data.url2, "image-40px.png", "Correct url expected");
+ is(e.data.width, 40, "Correct width expected");
+ is(e.data.width2, 40, "Correct width expected");
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/imagecache-maxage/unregister.html";
+ break;
+ default:
+ ok(false, "This should never happen");
+ }
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.requestFlakyTimeout("This test needs to simulate the passing of time");
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_importscript.html b/dom/workers/test/serviceworkers/test_importscript.html
new file mode 100644
index 000000000..5d2d5b352
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_importscript.html
@@ -0,0 +1,72 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test service worker - script cache policy</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="content"></div>
+<script class="testbody" type="text/javascript">
+ function start() {
+ return navigator.serviceWorker.register("importscript_worker.js",
+ { scope: "./sw_clients/" })
+ .then((swr) => registration = swr);
+ }
+
+ function unregister() {
+ return fetch("importscript.sjs?clearcounter").then(function() {
+ return registration.unregister();
+ }).then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ }
+
+ function testPostMessage(swr) {
+ var p = new Promise(function(res, rej) {
+ window.onmessage = function(e) {
+ if (e.data === "READY") {
+ swr.active.postMessage("do magic");
+ return;
+ }
+
+ ok(e.data === "OK", "Worker posted the correct value: " + e.data);
+ res();
+ }
+ });
+
+ var content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ iframe = document.createElement("iframe");
+ iframe.setAttribute('src', "sw_clients/service_worker_controlled.html");
+ content.appendChild(iframe);
+
+ return p.then(() => content.removeChild(iframe));
+ }
+
+ function runTest() {
+ start()
+ .then(testPostMessage)
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_importscript_mixedcontent.html b/dom/workers/test/serviceworkers/test_importscript_mixedcontent.html
new file mode 100644
index 000000000..a659af92b
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_importscript_mixedcontent.html
@@ -0,0 +1,53 @@
+<!--
+ 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 service workers</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/importscript-mixedcontent/register.html";
+ var ios;
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/importscript-mixedcontent/index.html";
+ } else if (e.data.status == "done") {
+ ok(e.data.data, "good", "Mixed content blocking should work correctly for service workers");
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/importscript-mixedcontent/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["security.mixed_content.block_active_content", false],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_install_event.html b/dom/workers/test/serviceworkers/test_install_event.html
new file mode 100644
index 000000000..0e59510fe
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_install_event.html
@@ -0,0 +1,144 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 94048 - test install event.</title>
+ <script type="text/javascript" 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 simpleRegister() {
+ var p = navigator.serviceWorker.register("worker.js", { scope: "./install_event" });
+ return p;
+ }
+
+ function nextRegister(reg) {
+ ok(reg instanceof ServiceWorkerRegistration, "reg should be a ServiceWorkerRegistration");
+ var p = navigator.serviceWorker.register("install_event_worker.js", { scope: "./install_event" });
+ return p.then(function(swr) {
+ ok(reg === swr, "register should resolve to the same registration object");
+ var update_found_promise = new Promise(function(resolve, reject) {
+ swr.addEventListener('updatefound', function(e) {
+ ok(true, "Received onupdatefound");
+ resolve();
+ });
+ });
+
+ var worker_activating = new Promise(function(res, reject) {
+ ok(swr.installing instanceof ServiceWorker, "There should be an installing worker if promise resolves.");
+ ok(swr.installing.state == "installing", "Installing worker's state should be 'installing'");
+ swr.installing.onstatechange = function(e) {
+ if (e.target.state == "activating") {
+ e.target.onstatechange = null;
+ res();
+ }
+ }
+ });
+
+ return Promise.all([update_found_promise, worker_activating]);
+ }, function(e) {
+ ok(false, "Unexpected Error in nextRegister! " + e);
+ });
+ }
+
+ function installError() {
+ // Silence worker errors so they don't cause the test to fail.
+ window.onerror = function(e) {}
+ return navigator.serviceWorker.register("install_event_error_worker.js", { scope: "./install_event" })
+ .then(function(swr) {
+ ok(swr.installing instanceof ServiceWorker, "There should be an installing worker if promise resolves.");
+ ok(swr.installing.state == "installing", "Installing worker's state should be 'installing'");
+ return new Promise(function(resolve, reject) {
+ swr.installing.onstatechange = function(e) {
+ ok(e.target.state == "redundant", "Installation of worker with error should fail.");
+ resolve();
+ }
+ });
+ }).then(function() {
+ return navigator.serviceWorker.getRegistration("./install_event").then(function(swr) {
+ var newest = swr.waiting || swr.active;
+ ok(newest, "Waiting or active worker should still exist");
+ ok(newest.scriptURL.match(/install_event_worker.js$/), "Previous worker should remain the newest worker");
+ });
+ });
+ }
+
+ function testActive(worker) {
+ is(worker.state, "activating", "Should be activating");
+ return new Promise(function(resolve, reject) {
+ worker.onstatechange = function(e) {
+ e.target.onstatechange = null;
+ is(e.target.state, "activated", "Activation of worker with error in activate event handler should still succeed.");
+ resolve();
+ }
+ });
+ }
+
+ function activateErrorShouldSucceed() {
+ // Silence worker errors so they don't cause the test to fail.
+ window.onerror = function() { }
+ return navigator.serviceWorker.register("activate_event_error_worker.js", { scope: "./activate_error" })
+ .then(function(swr) {
+ var p = new Promise(function(resolve, reject) {
+ ok(swr.installing.state == "installing", "activateErrorShouldSucceed(): Installing worker's state should be 'installing'");
+ swr.installing.onstatechange = function(e) {
+ e.target.onstatechange = null;
+ if (swr.waiting) {
+ swr.waiting.onstatechange = function(e) {
+ e.target.onstatechange = null;
+ testActive(swr.active).then(resolve, reject);
+ }
+ } else {
+ testActive(swr.active).then(resolve, reject);
+ }
+ }
+ });
+
+ return p.then(function() {
+ return Promise.resolve(swr);
+ });
+ }).then(function(swr) {
+ return swr.unregister();
+ });
+ }
+
+ function unregister() {
+ return navigator.serviceWorker.getRegistration("./install_event").then(function(reg) {
+ return reg.unregister();
+ });
+ }
+
+ function runTest() {
+ Promise.resolve()
+ .then(simpleRegister)
+ .then(nextRegister)
+ .then(installError)
+ .then(activateErrorShouldSucceed)
+ .then(unregister)
+ .then(function() {
+ SimpleTest.finish();
+ }).catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_install_event_gc.html b/dom/workers/test/serviceworkers/test_install_event_gc.html
new file mode 100644
index 000000000..ccccd2b43
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_install_event_gc.html
@@ -0,0 +1,121 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test install event being GC'd before waitUntil fulfills</title>
+ <script type="text/javascript" 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 script = 'blocking_install_event_worker.js';
+var scope = 'sw_clients/simple.html?install-event-gc';
+var registration;
+
+function register() {
+ return navigator.serviceWorker.register(script, { scope: scope })
+ .then(swr => registration = swr);
+}
+
+function unregister() {
+ if (!registration) {
+ return;
+ }
+ return registration.unregister();
+}
+
+function waitForInstallEvent() {
+ return new Promise((resolve, reject) => {
+ navigator.serviceWorker.addEventListener('message', evt => {
+ if (evt.data.type === 'INSTALL_EVENT') {
+ resolve();
+ }
+ });
+ });
+}
+
+function gcWorker() {
+ return new Promise(function(resolve, reject) {
+ // We are able to trigger asynchronous garbage collection and cycle
+ // collection by emitting "child-cc-request" and "child-gc-request"
+ // observer notifications. The worker RuntimeService will translate
+ // these notifications into the appropriate operation on all known
+ // worker threads.
+ //
+ // In the failure case where GC/CC causes us to abort the installation,
+ // we will know something happened from the statechange event.
+ const statechangeHandler = evt => {
+ // Reject rather than resolving to avoid the possibility of us seeing
+ // an unrelated racing statechange somehow. Since in the success case we
+ // will still see a state change on termination, we do explicitly need to
+ // be removed on the success path.
+ ok(registration.installing, 'service worker is still installing?');
+ reject();
+ };
+ registration.installing.addEventListener('statechange', statechangeHandler);
+ // In the success case since the service worker installation is effectively
+ // hung, we instead depend on sending a 'ping' message to the service worker
+ // and hearing it 'pong' back. Since we issue our postMessage after we
+ // trigger the GC/CC, our 'ping' will only be processed after the GC/CC and
+ // therefore the pong will also strictly occur after the cycle collection.
+ navigator.serviceWorker.addEventListener('message', evt => {
+ if (evt.data.type === 'pong') {
+ registration.installing.removeEventListener(
+ 'statechange', statechangeHandler);
+ resolve();
+ }
+ });
+ // At the current time, the service worker will exist in our same process
+ // and notifyObservers is synchronous. However, in the future, service
+ // workers may end up in a separate process and in that case it will be
+ // appropriate to use notifyObserversInParentProcess or something like it.
+ // (notifyObserversInParentProcess is a synchronous IPC call to the parent
+ // process's main thread. IPDL PContent::CycleCollect is an async message.
+ // Ordering will be maintained if the postMessage goes via PContent as well,
+ // but that seems unlikely.)
+ SpecialPowers.notifyObservers(null, 'child-gc-request', null);
+ SpecialPowers.notifyObservers(null, 'child-cc-request', null);
+ SpecialPowers.notifyObservers(null, 'child-gc-request', null);
+ // (Only send the ping after we set the gc/cc/gc in motion.)
+ registration.installing.postMessage({ type: 'ping' });
+ });
+}
+
+function terminateWorker() {
+ return SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.idle_timeout", 0],
+ ["dom.serviceWorkers.idle_extended_timeout", 0]
+ ]
+ }).then(_ => {
+ registration.installing.postMessage({ type: 'RESET_TIMER' });
+ });
+}
+
+function runTest() {
+ Promise.all([
+ waitForInstallEvent(),
+ register()
+ ]).then(_ => ok(registration.installing, 'service worker is installing'))
+ .then(gcWorker)
+ .then(_ => ok(registration.installing, 'service worker is still installing'))
+ .then(terminateWorker)
+ .catch(e => ok(false, e))
+ .then(unregister)
+ .then(SimpleTest.finish);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true],
+]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_installation_simple.html b/dom/workers/test/serviceworkers/test_installation_simple.html
new file mode 100644
index 000000000..1b0d6c947
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_installation_simple.html
@@ -0,0 +1,212 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 930348 - test stub Navigator ServiceWorker utilities.</title>
+ <script type="text/javascript" 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 simpleRegister() {
+ var p = navigator.serviceWorker.register("worker.js", { scope: "simpleregister/" });
+ ok(p instanceof Promise, "register() should return a Promise");
+ return Promise.resolve();
+ }
+
+ function sameOriginWorker() {
+ p = navigator.serviceWorker.register("http://some-other-origin/worker.js");
+ return p.then(function(w) {
+ ok(false, "Worker from different origin should fail");
+ }, function(e) {
+ ok(e.name === "SecurityError", "Should fail with a SecurityError");
+ });
+ }
+
+ function sameOriginScope() {
+ p = navigator.serviceWorker.register("worker.js", { scope: "http://www.example.com/" });
+ return p.then(function(w) {
+ ok(false, "Worker controlling scope for different origin should fail");
+ }, function(e) {
+ ok(e.name === "SecurityError", "Should fail with a SecurityError");
+ });
+ }
+
+ function httpsOnly() {
+ var promise = new Promise(function(resolve) {
+ SpecialPowers.pushPrefEnv({'set': [["dom.serviceWorkers.testing.enabled", false]] }, resolve);
+ });
+
+ return promise.then(function() {
+ return navigator.serviceWorker.register("/worker.js");
+ }).then(function(w) {
+ ok(false, "non-HTTPS pages cannot register ServiceWorkers");
+ }, function(e) {
+ ok(e.name === "SecurityError", "Should fail with a SecurityError");
+ }).then(function() {
+ return new Promise((resolve) => SpecialPowers.popPrefEnv(resolve));
+ });
+ }
+
+ function realWorker() {
+ var p = navigator.serviceWorker.register("worker.js", { scope: "realworker" });
+ return p.then(function(wr) {
+ ok(wr instanceof ServiceWorkerRegistration, "Register a ServiceWorker");
+
+ info(wr.scope);
+ ok(wr.scope == (new URL("realworker", document.baseURI)).href, "Scope should match");
+ // active, waiting, installing should return valid worker instances
+ // because the registration is for the realworker scope, so the workers
+ // should be obtained for that scope and not for
+ // test_installation_simple.html
+ var worker = wr.installing;
+ ok(worker && wr.scope.match(/realworker$/) &&
+ worker.scriptURL.match(/worker.js$/), "Valid worker instance should be available.");
+ return wr.unregister().then(function(success) {
+ ok(success, "The worker should be unregistered successfully");
+ }, function(e) {
+ dump("Error unregistering the worker: " + e + "\n");
+ });
+ }, function(e) {
+ info("Error: " + e.name);
+ ok(false, "realWorker Registration should have succeeded!");
+ });
+ }
+
+ function networkError404() {
+ return navigator.serviceWorker.register("404.js", { scope: "network_error/"}).then(function(w) {
+ ok(false, "404 response should fail with TypeError");
+ }, function(e) {
+ ok(e.name === "TypeError", "404 response should fail with TypeError");
+ });
+ }
+
+ function redirectError() {
+ return navigator.serviceWorker.register("redirect_serviceworker.sjs", { scope: "redirect_error/" }).then(function(swr) {
+ ok(false, "redirection should fail");
+ }, function (e) {
+ ok(e.name === "SecurityError", "redirection should fail with SecurityError");
+ });
+ }
+
+ function parseError() {
+ var p = navigator.serviceWorker.register("parse_error_worker.js", { scope: "parse_error/" });
+ return p.then(function(wr) {
+ ok(false, "Registration should fail with parse error");
+ return navigator.serviceWorker.getRegistration("parse_error/").then(function(swr) {
+ // See https://github.com/slightlyoff/ServiceWorker/issues/547
+ is(swr, undefined, "A failed registration for a scope with no prior controllers should clear itself");
+ });
+ }, function(e) {
+ ok(e instanceof Error, "Registration should fail with parse error");
+ });
+ }
+
+ // FIXME(nsm): test for parse error when Update step doesn't happen (directly from register).
+
+ function updatefound() {
+ var frame = document.createElement("iframe");
+ frame.setAttribute("id", "simpleregister-frame");
+ frame.setAttribute("src", new URL("simpleregister/index.html", document.baseURI).href);
+ document.body.appendChild(frame);
+ var resolve, reject;
+ var p = new Promise(function(res, rej) {
+ resolve = res;
+ reject = rej;
+ });
+
+ var reg;
+ function continueTest() {
+ navigator.serviceWorker.register("worker2.js", { scope: "simpleregister/" })
+ .then(function(r) {
+ reg = r;
+ });;
+ }
+
+ window.onmessage = function(e) {
+ if (e.data.type == "ready") {
+ continueTest();
+ } else if (e.data.type == "finish") {
+ window.onmessage = null;
+ // We have to make frame navigate away, otherwise it will call
+ // MaybeStopControlling() when this document is unloaded. At that point
+ // the pref has been disabled, so the ServiceWorkerManager is not available.
+ frame.setAttribute("src", new URL("about:blank").href);
+ reg.unregister().then(function(success) {
+ ok(success, "The worker should be unregistered successfully");
+ resolve();
+ }, function(e) {
+ dump("Error unregistering the worker: " + e + "\n");
+ });
+ } else if (e.data.type == "check") {
+ ok(e.data.status, e.data.msg);
+ }
+ }
+ return p;
+ }
+
+ var readyPromiseResolved = false;
+
+ function readyPromise() {
+ var frame = document.createElement("iframe");
+ frame.setAttribute("id", "simpleregister-frame-ready");
+ frame.setAttribute("src", new URL("simpleregister/ready.html", document.baseURI).href);
+ document.body.appendChild(frame);
+
+ var channel = new MessageChannel();
+ frame.addEventListener('load', function() {
+ frame.contentWindow.postMessage('your port!', '*', [channel.port2]);
+ }, false);
+
+ channel.port1.onmessage = function() {
+ readyPromiseResolved = true;
+ }
+
+ return Promise.resolve();
+ }
+
+ function checkReadyPromise() {
+ ok(readyPromiseResolved, "The ready promise has been resolved!");
+ return Promise.resolve();
+ }
+
+ function runTest() {
+ simpleRegister()
+ .then(readyPromise)
+ .then(sameOriginWorker)
+ .then(sameOriginScope)
+ .then(httpsOnly)
+ .then(realWorker)
+ .then(networkError404)
+ .then(redirectError)
+ .then(parseError)
+ .then(updatefound)
+ .then(checkReadyPromise)
+ // put more tests here.
+ .then(function() {
+ SimpleTest.finish();
+ }).catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.testing.enabled", true],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_match_all.html b/dom/workers/test/serviceworkers/test_match_all.html
new file mode 100644
index 000000000..f4e65a730
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_match_all.html
@@ -0,0 +1,80 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 982726 - test match_all not crashing</title>
+ <script type="text/javascript" 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">
+ // match_all_worker will call matchAll until the worker shuts down.
+ // Test passes if the browser doesn't crash on leaked promise objects.
+ var registration;
+ var content;
+ var iframe;
+
+ function simpleRegister() {
+ return navigator.serviceWorker.register("match_all_worker.js",
+ { scope: "./sw_clients/" })
+ .then((swr) => registration = swr);
+ }
+
+ function closeAndUnregister() {
+ content.removeChild(iframe);
+
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ }
+
+ function openClient() {
+ var p = new Promise(function(resolve, reject) {
+ window.onmessage = function(e) {
+ if (e.data === "READY") {
+ resolve();
+ }
+ }
+ });
+
+ content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ iframe = document.createElement("iframe");
+ iframe.setAttribute('src', "sw_clients/simple.html");
+ content.appendChild(iframe);
+
+ return p;
+ }
+
+ function runTest() {
+ simpleRegister()
+ .then(openClient)
+ .then(closeAndUnregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(function() {
+ ok(true, "Didn't crash on resolving matchAll promises while worker shuts down.");
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_match_all_advanced.html b/dom/workers/test/serviceworkers/test_match_all_advanced.html
new file mode 100644
index 000000000..a458ed70b
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_match_all_advanced.html
@@ -0,0 +1,100 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 982726 - Test matchAll with multiple clients</title>
+ <script type="text/javascript" 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 client_iframes = [];
+ var registration;
+
+ function start() {
+ return navigator.serviceWorker.register("match_all_advanced_worker.js",
+ { scope: "./sw_clients/" }).then(function(swr) {
+ registration = swr;
+ window.onmessage = function (e) {
+ if (e.data === "READY") {
+ ok(registration.active, "Worker is active.");
+ registration.active.postMessage("RUN");
+ }
+ }
+ });
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ }
+
+
+ function testMatchAll() {
+ var p = new Promise(function(res, rej) {
+ navigator.serviceWorker.onmessage = function (e) {
+ ok(e.data === client_iframes.length, "MatchAll returned the correct number of clients.");
+ res();
+ }
+ });
+
+ content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ iframe = document.createElement("iframe");
+ iframe.setAttribute('src', "sw_clients/service_worker_controlled.html");
+ content.appendChild(iframe);
+
+ client_iframes.push(iframe);
+ return p;
+ }
+
+ function removeAndTest() {
+ content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ content.removeChild(client_iframes.pop());
+ content.removeChild(client_iframes.pop());
+
+ return testMatchAll();
+ }
+
+ function runTest() {
+ start()
+ .then(testMatchAll)
+ .then(testMatchAll)
+ .then(testMatchAll)
+ .then(removeAndTest)
+ .then(function(e) {
+ content = document.getElementById("content");
+ while (client_iframes.length) {
+ content.removeChild(client_iframes.pop());
+ }
+ }).then(unregister).catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(function() {
+ SimpleTest.finish();
+ });
+
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_match_all_client_id.html b/dom/workers/test/serviceworkers/test_match_all_client_id.html
new file mode 100644
index 000000000..4c8ed9673
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_match_all_client_id.html
@@ -0,0 +1,91 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1058311 - Test matchAll client id </title>
+ <script type="text/javascript" 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 registration;
+ var clientURL = "match_all_client/match_all_client_id.html";
+ function start() {
+ return navigator.serviceWorker.register("match_all_client_id_worker.js",
+ { scope: "./match_all_client/" })
+ .then((swr) => registration = swr);
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ }
+
+ function getMessageListener() {
+ return new Promise(function(res, rej) {
+ window.onmessage = function(e) {
+ ok(e.data, "Same client id for multiple calls.");
+ is(e.origin, "http://mochi.test:8888", "Event should have the correct origin");
+
+ if (!e.data) {
+ rej();
+ return;
+ }
+
+ info("DONE from: " + e.source);
+ res();
+ }
+ });
+ }
+
+ function testNestedWindow() {
+ var p = getMessageListener();
+
+ var content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ iframe = document.createElement("iframe");
+
+ content.appendChild(iframe);
+ iframe.setAttribute('src', clientURL);
+
+ return p.then(() => content.removeChild(iframe));
+ }
+
+ function testAuxiliaryWindow() {
+ var p = getMessageListener();
+ var w = window.open(clientURL);
+
+ return p.then(() => w.close());
+ }
+
+ function runTest() {
+ info(window.opener == undefined);
+ start()
+ .then(testAuxiliaryWindow)
+ .then(testNestedWindow)
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_match_all_client_properties.html b/dom/workers/test/serviceworkers/test_match_all_client_properties.html
new file mode 100644
index 000000000..14e3445a4
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_match_all_client_properties.html
@@ -0,0 +1,97 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1058311 - Test matchAll clients properties </title>
+ <script type="text/javascript" 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 registration;
+ var clientURL = "match_all_clients/match_all_controlled.html";
+ function start() {
+ return navigator.serviceWorker.register("match_all_properties_worker.js",
+ { scope: "./match_all_clients/" })
+ .then((swr) => registration = swr);
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ }
+
+ function getMessageListener() {
+ return new Promise(function(res, rej) {
+ window.onmessage = function(e) {
+ if (e.data.message === undefined) {
+ info("rejecting promise");
+ rej();
+ return;
+ }
+
+ ok(e.data.result, e.data.message);
+
+ if (!e.data.result) {
+ rej();
+ }
+ if (e.data.message == "DONE") {
+ info("DONE from: " + e.source);
+ res();
+ }
+ }
+ });
+ }
+
+ function testNestedWindow() {
+ var p = getMessageListener();
+
+ var content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ iframe = document.createElement("iframe");
+
+ content.appendChild(iframe);
+ iframe.setAttribute('src', clientURL);
+
+ return p.then(() => content.removeChild(iframe));
+ }
+
+ function testAuxiliaryWindow() {
+ var p = getMessageListener();
+ var w = window.open(clientURL);
+
+ return p.then(() => w.close());
+ }
+
+ function runTest() {
+ info("catalin");
+ info(window.opener == undefined);
+ start()
+ .then(testAuxiliaryWindow)
+ .then(testNestedWindow)
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_navigator.html b/dom/workers/test/serviceworkers/test_navigator.html
new file mode 100644
index 000000000..164f41bcd
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_navigator.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>Bug 930348 - test stub Navigator ServiceWorker utilities.</title>
+ <script type="text/javascript" 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 checkEnabled() {
+ ok(navigator.serviceWorker, "navigator.serviceWorker should exist when ServiceWorkers are enabled.");
+ ok(typeof navigator.serviceWorker.register === "function", "navigator.serviceWorker.register() should be a function.");
+ ok(typeof navigator.serviceWorker.getRegistration === "function", "navigator.serviceWorker.getAll() should be a function.");
+ ok(typeof navigator.serviceWorker.getRegistrations === "function", "navigator.serviceWorker.getAll() should be a function.");
+ ok(navigator.serviceWorker.ready instanceof Promise, "navigator.serviceWorker.ready should be a Promise.");
+ ok(navigator.serviceWorker.controller === null, "There should be no controller worker for an uncontrolled document.");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true]
+ ]}, function() {
+ checkEnabled();
+ SimpleTest.finish();
+ });
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_not_intercept_plugin.html b/dom/workers/test/serviceworkers/test_not_intercept_plugin.html
new file mode 100644
index 000000000..a90e068d3
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_not_intercept_plugin.html
@@ -0,0 +1,78 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1187766 - Test loading plugins scenarios with fetch interception.</title>
+ <script type="text/javascript" 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">
+ SimpleTest.requestCompleteLog();
+
+ var registration;
+ function simpleRegister() {
+ var p = navigator.serviceWorker.register("./fetch/plugin/worker.js", { scope: "./fetch/plugin/" });
+ return p.then(function(swr) {
+ registration = swr;
+ return new Promise(function(resolve) {
+ swr.installing.onstatechange = resolve;
+ });
+ });
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(success) {
+ ok(success, "Service worker should be unregistered successfully");
+ }, function(e) {
+ dump("SW unregistration error: " + e + "\n");
+ });
+ }
+
+ function testPlugins() {
+ var p = new Promise(function(resolve, reject) {
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "done") {
+ window.onmessage = null;
+ w.close();
+ resolve();
+ }
+ }
+ });
+
+ var w = window.open("fetch/plugin/plugins.html");
+ return p;
+ }
+
+ function runTest() {
+ simpleRegister()
+ .then(testPlugins)
+ .then(unregister)
+ .then(function() {
+ SimpleTest.finish();
+ }).catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.requestcontext.enabled", true],
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_notification_constructor_error.html b/dom/workers/test/serviceworkers/test_notification_constructor_error.html
new file mode 100644
index 000000000..6a8ecf8c0
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_notification_constructor_error.html
@@ -0,0 +1,52 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug XXXXXXX - Check that Notification constructor throws in ServiceWorkerGlobalScope</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/NotificationTest.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 simpleRegister() {
+ return navigator.serviceWorker.register("notification_constructor_error.js", { scope: "notification_constructor_error/" }).then(function(swr) {
+ ok(false, "Registration should fail.");
+ }, function(e) {
+ is(e.name, 'TypeError', "Registration should fail with a TypeError.");
+ });
+ }
+
+ function runTest() {
+ MockServices.register();
+ simpleRegister()
+ .then(function() {
+ MockServices.unregister();
+ SimpleTest.finish();
+ }).catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ MockServices.unregister();
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["notification.prompt.testing", true],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_notification_get.html b/dom/workers/test/serviceworkers/test_notification_get.html
new file mode 100644
index 000000000..dbb312e7b
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_notification_get.html
@@ -0,0 +1,213 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>ServiceWorkerRegistration.getNotifications() on main thread and worker thread.</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/NotificationTest.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 type="text/javascript">
+
+ SimpleTest.requestFlakyTimeout("untriaged");
+
+ function testFrame(src) {
+ return new Promise(function(resolve, reject) {
+ var iframe = document.createElement("iframe");
+ iframe.src = src;
+ window.callback = function(result) {
+ iframe.src = "about:blank";
+ document.body.removeChild(iframe);
+ iframe = null;
+ SpecialPowers.exactGC(function() {
+ resolve(result);
+ });
+ };
+ document.body.appendChild(iframe);
+ });
+ }
+
+ function registerSW() {
+ return testFrame('notification/register.html').then(function() {
+ ok(true, "Registered service worker.");
+ });
+ }
+
+ function unregisterSW() {
+ return testFrame('notification/unregister.html').then(function() {
+ ok(true, "Unregistered service worker.");
+ });
+ }
+
+ // To check that the scope is respected when retrieving notifications.
+ function registerAlternateSWAndAddNotification() {
+ return testFrame('notification_alt/register.html').then(function() {
+ ok(true, "Registered alternate service worker.");
+ return navigator.serviceWorker.getRegistration("./notification_alt/").then(function(reg) {
+ return reg.showNotification("This is a notification_alt");
+ });
+ });
+ }
+
+ function unregisterAlternateSWAndAddNotification() {
+ return testFrame('notification_alt/unregister.html').then(function() {
+ ok(true, "unregistered alternate service worker.");
+ });
+ }
+
+ function testDismiss() {
+ // Dismissed persistent notifications should be removed from the
+ // notification list.
+ var alertsService = SpecialPowers.Cc["@mozilla.org/alerts-service;1"]
+ .getService(SpecialPowers.Ci.nsIAlertsService);
+ return navigator.serviceWorker.getRegistration("./notification/")
+ .then(function(reg) {
+ return reg.showNotification(
+ "This is a notification that will be closed", { tag: "dismiss" })
+ .then(function() {
+ return reg;
+ });
+ }).then(function(reg) {
+ return reg.getNotifications()
+ .then(function(notifications) {
+ is(notifications.length, 1, "There should be one visible notification");
+ is(notifications[0].tag, "dismiss", "Tag should match");
+
+ // Simulate dismissing the notification by using the alerts service
+ // directly, instead of `Notification#close`.
+ var principal = SpecialPowers.wrap(document).nodePrincipal;
+ var id = principal.origin + "#tag:dismiss";
+ alertsService.closeAlert(id, principal);
+
+ return reg;
+ });
+ }).then(function(reg) {
+ return reg.getNotifications();
+ }).then(function(notifications) {
+ // Make sure dismissed notifications are no longer retrieved.
+ is(notifications.length, 0, "There should be no more stored notifications");
+ });
+ }
+
+ function testGet() {
+ // Non persistent notifications will not show up in getNotification().
+ var n = new Notification("Scope does not match");
+ var options = NotificationTest.payload;
+ return navigator.serviceWorker.getRegistration("./notification/")
+ .then(function(reg) {
+ return reg.showNotification("This is a title", options)
+ .then(function() {
+ return reg;
+ });
+ }).then(function(reg) {
+ return registerAlternateSWAndAddNotification().then(function() {
+ return reg;
+ });
+ }).then(function(reg) {
+ return reg.getNotifications();
+ }).then(function(notifications) {
+ is(notifications.length, 1, "There should be one stored notification");
+ var notification = notifications[0];
+ ok(notification instanceof Notification, "Should be a Notification");
+ is(notification.title, "This is a title", "Title should match");
+ for (var key in options) {
+ if (key === "data") {
+ ok(NotificationTest.customDataMatches(notification.data),
+ "data property should match");
+ continue;
+ }
+ is(notification[key], options[key], key + " property should match");
+ }
+ notification.close();
+ }).then(function() {
+ return navigator.serviceWorker.getRegistration("./notification/").then(function(reg) {
+ return reg.getNotifications();
+ });
+ }).then(function(notifications) {
+ // Make sure closed notifications are no longer retrieved.
+ is(notifications.length, 0, "There should be no more stored notifications");
+ }).catch(function(e) {
+ ok(false, "Something went wrong " + e.message);
+ }).then(unregisterAlternateSWAndAddNotification);
+ }
+
+ function testGetWorker() {
+ todo(false, "navigator.serviceWorker is not available on workers yet");
+ return Promise.resolve();
+ }
+
+ function waitForSWTests(reg, msg) {
+ return new Promise(function(resolve, reject) {
+ var content = document.getElementById("content");
+
+ iframe = document.createElement("iframe");
+
+ content.appendChild(iframe);
+ iframe.setAttribute('src', "notification/listener.html");
+
+ window.onmessage = function(e) {
+ if (e.data.type == 'status') {
+ ok(e.data.status, "Service worker test: " + e.data.msg);
+ } else if (e.data.type == 'finish') {
+ content.removeChild(iframe);
+ resolve();
+ }
+ }
+
+ iframe.onload = function(e) {
+ iframe.onload = null;
+ reg.active.postMessage(msg);
+ }
+ });
+ }
+
+ function testGetServiceWorker() {
+ return navigator.serviceWorker.getRegistration("./notification/")
+ .then(function(reg) {
+ return waitForSWTests(reg, 'create');
+ });
+ }
+
+ // Create a Notification here, make sure ServiceWorker sees it.
+ function testAcrossThreads() {
+ return navigator.serviceWorker.getRegistration("./notification/")
+ .then(function(reg) {
+ return reg.showNotification("This is a title")
+ .then(function() {
+ return reg;
+ });
+ }).then(function(reg) {
+ return waitForSWTests(reg, 'do-not-create');
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ MockServices.register();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.webnotifications.workers.enabled", true],
+ ["dom.webnotifications.serviceworker.enabled", true],
+ ["notification.prompt.testing", true],
+ ]}, function() {
+ registerSW()
+ .then(testGet)
+ .then(testGetWorker)
+ .then(testGetServiceWorker)
+ .then(testAcrossThreads)
+ .then(testDismiss)
+ .then(unregisterSW)
+ .then(function() {
+ MockServices.unregister();
+ SimpleTest.finish();
+ });
+ });
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_notificationclick-otherwindow.html b/dom/workers/test/serviceworkers/test_notificationclick-otherwindow.html
new file mode 100644
index 000000000..4a785be9a
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_notificationclick-otherwindow.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=916893
+-->
+<head>
+ <title>Bug 1114554 - Test ServiceWorkerGlobalScope.notificationclick event.</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/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=1114554">Bug 1114554</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 testFrame(src) {
+ var iframe = document.createElement("iframe");
+ iframe.src = src;
+ window.callback = function(result) {
+ window.callback = null;
+ document.body.removeChild(iframe);
+ iframe = null;
+ ok(result, "Got notificationclick event with correct data.");
+ MockServices.unregister();
+ registration.unregister().then(function() {
+ SimpleTest.finish();
+ });
+ };
+ document.body.appendChild(iframe);
+ }
+
+ var registration;
+
+ function runTest() {
+ MockServices.register();
+ testFrame('notificationclick-otherwindow.html');
+ navigator.serviceWorker.register("notificationclick.js", { scope: "notificationclick-otherwindow.html" }).then(function(reg) {
+ registration = reg;
+ }, function(e) {
+ ok(false, "registration should have passed!");
+ });
+ };
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.webnotifications.workers.enabled", true],
+ ["dom.webnotifications.serviceworker.enabled", true],
+ ["notification.prompt.testing", true],
+ ]}, runTest);
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_notificationclick.html b/dom/workers/test/serviceworkers/test_notificationclick.html
new file mode 100644
index 000000000..d5c3ecf8b
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_notificationclick.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=916893
+-->
+<head>
+ <title>Bug 1114554 - Test ServiceWorkerGlobalScope.notificationclick event.</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/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=1114554">Bug 1114554</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 testFrame(src) {
+ var iframe = document.createElement("iframe");
+ iframe.src = src;
+ window.callback = function(result) {
+ window.callback = null;
+ document.body.removeChild(iframe);
+ iframe = null;
+ ok(result, "Got notificationclick event with correct data.");
+ MockServices.unregister();
+ registration.unregister().then(function() {
+ SimpleTest.finish();
+ });
+ };
+ document.body.appendChild(iframe);
+ }
+
+ var registration;
+
+ function runTest() {
+ MockServices.register();
+ testFrame('notificationclick.html');
+ navigator.serviceWorker.register("notificationclick.js", { scope: "notificationclick.html" }).then(function(reg) {
+ registration = reg;
+ }, function(e) {
+ ok(false, "registration should have passed!");
+ });
+ };
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.webnotifications.workers.enabled", true],
+ ["dom.webnotifications.serviceworker.enabled", true],
+ ["notification.prompt.testing", true],
+ ]}, runTest);
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_notificationclick_focus.html b/dom/workers/test/serviceworkers/test_notificationclick_focus.html
new file mode 100644
index 000000000..81d6e269c
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_notificationclick_focus.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=916893
+-->
+<head>
+ <title>Bug 1144660 - Test client.focus() permissions on notification click</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/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=1114554">Bug 1114554</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 testFrame(src) {
+ var iframe = document.createElement("iframe");
+ iframe.src = src;
+ window.callback = function(result) {
+ window.callback = null;
+ document.body.removeChild(iframe);
+ iframe = null;
+ ok(result, "All tests passed.");
+ MockServices.unregister();
+ registration.unregister().then(function() {
+ SimpleTest.finish();
+ });
+ };
+ document.body.appendChild(iframe);
+ }
+
+ var registration;
+
+ function runTest() {
+ MockServices.register();
+ testFrame('notificationclick_focus.html');
+ navigator.serviceWorker.register("notificationclick_focus.js", { scope: "notificationclick_focus.html" }).then(function(reg) {
+ registration = reg;
+ }, function(e) {
+ ok(false, "registration should have passed!");
+ });
+ };
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.webnotifications.workers.enabled", true],
+ ["dom.webnotifications.serviceworker.enabled", true],
+ ["notification.prompt.testing", true],
+ ["dom.disable_open_click_delay", 1000],
+ ]}, runTest);
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_notificationclose.html b/dom/workers/test/serviceworkers/test_notificationclose.html
new file mode 100644
index 000000000..3b81132c4
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_notificationclose.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1265841
+-->
+<head>
+ <title>Bug 1265841 - Test ServiceWorkerGlobalScope.notificationclose event.</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/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=1265841">Bug 1265841</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, click, and close events.");
+
+ function testFrame(src) {
+ var iframe = document.createElement("iframe");
+ iframe.src = src;
+ window.callback = function(result) {
+ window.callback = null;
+ document.body.removeChild(iframe);
+ iframe = null;
+ ok(result, "Got notificationclose event with correct data.");
+ MockServices.unregister();
+ registration.unregister().then(function() {
+ SimpleTest.finish();
+ });
+ };
+ document.body.appendChild(iframe);
+ }
+
+ var registration;
+
+ function runTest() {
+ MockServices.register();
+ testFrame('notificationclose.html');
+ navigator.serviceWorker.register("notificationclose.js", { scope: "notificationclose.html" }).then(function(reg) {
+ registration = reg;
+ }, function(e) {
+ ok(false, "registration should have passed!");
+ });
+ };
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.webnotifications.workers.enabled", true],
+ ["dom.webnotifications.serviceworker.enabled", true],
+ ["notification.prompt.testing", true],
+ ]}, runTest);
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_opaque_intercept.html b/dom/workers/test/serviceworkers/test_opaque_intercept.html
new file mode 100644
index 000000000..5cb12e518
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_opaque_intercept.html
@@ -0,0 +1,85 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 982726 - Test service worker post message </title>
+ <script type="text/javascript" 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 registration;
+ function start() {
+ return navigator.serviceWorker.register("opaque_intercept_worker.js",
+ { scope: "./sw_clients/" })
+ .then((swr) => registration = swr);
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ }
+
+
+ function testOpaqueIntercept(swr) {
+ var p = new Promise(function(res, rej) {
+ var ready = false;
+ var scriptLoaded = false;
+ window.onmessage = function(e) {
+ if (e.data === "READY") {
+ ok(!ready, "ready message should only be received once");
+ ok(!scriptLoaded, "ready message should be received before script loaded");
+ if (ready) {
+ res();
+ return;
+ }
+ ready = true;
+ iframe.contentWindow.postMessage("REFRESH", "*");
+ } else if (e.data === "SCRIPT_LOADED") {
+ ok(ready, "script loaded should be received after ready");
+ ok(!scriptLoaded, "script loaded message should be received only once");
+ scriptLoaded = true;
+ res();
+ }
+ }
+ });
+
+ var content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ iframe = document.createElement("iframe");
+ iframe.setAttribute('src', "sw_clients/refresher.html");
+ content.appendChild(iframe);
+
+ return p.then(() => content.removeChild(iframe));
+ }
+
+ function runTest() {
+ start()
+ .then(testOpaqueIntercept)
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_openWindow.html b/dom/workers/test/serviceworkers/test_openWindow.html
new file mode 100644
index 000000000..2417648b9
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_openWindow.html
@@ -0,0 +1,117 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1172870
+-->
+<head>
+ <title>Bug 1172870 - Test clients.openWindow</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/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=1172870">Bug 1172870</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 setup(ctx) {
+ MockServices.register();
+
+ return navigator.serviceWorker.register("openWindow_worker.js", {scope: "./"})
+ .then(function(swr) {
+ ok(swr, "Registration successful");
+ ctx.registration = swr;
+ return ctx;
+ });
+ }
+
+ function waitForActiveServiceWorker(ctx) {
+ return navigator.serviceWorker.ready.then(function(result) {
+ ok(ctx.registration.active, "Service Worker is active");
+ return ctx;
+ });
+ }
+
+ function setupMessageHandler(ctx) {
+ return new Promise(function(res, rej) {
+ navigator.serviceWorker.onmessage = function(event) {
+ navigator.serviceWorker.onmessage = null;
+ for (i = 0; i < event.data.length; i++) {
+ ok(event.data[i].result, event.data[i].message);
+ }
+ res(ctx);
+ }
+ });
+ }
+
+ function testPopupNotAllowed(ctx) {
+ var p = setupMessageHandler(ctx);
+ ok(ctx.registration.active, "Worker is active.");
+ ctx.registration.active.postMessage("testNoPopup");
+
+ return p;
+ }
+
+ function testPopupAllowed(ctx) {
+ var p = setupMessageHandler(ctx);
+ ctx.registration.showNotification("testPopup");
+
+ return p;
+ }
+
+ function checkNumberOfWindows(ctx) {
+ return new Promise(function(res, rej) {
+ navigator.serviceWorker.onmessage = function(event) {
+ navigator.serviceWorker.onmessage = null;
+ ok(event.data.result, event.data.message);
+ res(ctx);
+ }
+ ctx.registration.active.postMessage("CHECK_NUMBER_OF_WINDOWS");
+ });
+ }
+
+ function clear(ctx) {
+ MockServices.unregister();
+
+ return ctx.registration.unregister().then(function(result) {
+ ctx.registration = null;
+ ok(result, "Unregister was successful.");
+ });
+ }
+
+ function runTest() {
+ setup({})
+ .then(waitForActiveServiceWorker)
+ // Permission to allow popups persists for some time after a notification
+ // click event, so the order here is important.
+ .then(testPopupNotAllowed)
+ .then(testPopupAllowed)
+ .then(checkNumberOfWindows)
+ .then(clear)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.webnotifications.workers.enabled", true],
+ ["dom.webnotifications.serviceworker.enabled", true],
+ ["notification.prompt.testing", true],
+ ["dom.disable_open_click_delay", 1000],
+ ["dom.serviceWorkers.idle_timeout", 299999],
+ ["dom.serviceWorkers.idle_extended_timeout", 299999]
+ ]}, runTest);
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_origin_after_redirect.html b/dom/workers/test/serviceworkers/test_origin_after_redirect.html
new file mode 100644
index 000000000..b68537d9d
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_origin_after_redirect.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 origin of a redirected response from a service worker</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/register.html";
+ var win;
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ win = window.open("/tests/dom/workers/test/serviceworkers/fetch/origin/index.sjs", "mywindow", "width=100,height=100");
+ } else if (e.data.status == "domain") {
+ is(e.data.data, "example.org", "Correct domain expected");
+ } else if (e.data.status == "origin") {
+ is(e.data.data, "http://example.org", "Correct origin expected");
+ } else if (e.data.status == "done") {
+ win.close();
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_origin_after_redirect_cached.html b/dom/workers/test/serviceworkers/test_origin_after_redirect_cached.html
new file mode 100644
index 000000000..69644abfa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_origin_after_redirect_cached.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 origin of a redirected response from a service worker</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/register.html";
+ var win;
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ win = window.open("/tests/dom/workers/test/serviceworkers/fetch/origin/index-cached.sjs", "mywindow", "width=100,height=100");
+ } else if (e.data.status == "domain") {
+ is(e.data.data, "example.org", "Correct domain expected");
+ } else if (e.data.status == "origin") {
+ is(e.data.data, "http://example.org", "Correct origin expected");
+ } else if (e.data.status == "done") {
+ win.close();
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https.html b/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https.html
new file mode 100644
index 000000000..dcac11aea
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https.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 the origin of a redirected response from a service worker</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/register.html";
+ var win;
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ win = window.open("/tests/dom/workers/test/serviceworkers/fetch/origin/index-to-https.sjs", "mywindow", "width=100,height=100");
+ } else if (e.data.status == "domain") {
+ is(e.data.data, "example.org", "Correct domain expected");
+ } else if (e.data.status == "origin") {
+ is(e.data.data, "https://example.org", "Correct origin expected");
+ } else if (e.data.status == "done") {
+ win.close();
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https_cached.html b/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https_cached.html
new file mode 100644
index 000000000..3922fdb90
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https_cached.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 origin of a redirected response from a service worker</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/register.html";
+ var win;
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ win = window.open("/tests/dom/workers/test/serviceworkers/fetch/origin/index-to-https-cached.sjs", "mywindow", "width=100,height=100");
+ } else if (e.data.status == "domain") {
+ is(e.data.data, "example.org", "Correct domain expected");
+ } else if (e.data.status == "origin") {
+ is(e.data.data, "https://example.org", "Correct origin expected");
+ } else if (e.data.status == "done") {
+ win.close();
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/origin/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_post_message.html b/dom/workers/test/serviceworkers/test_post_message.html
new file mode 100644
index 000000000..e366423cb
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_post_message.html
@@ -0,0 +1,80 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 982726 - Test service worker post message </title>
+ <script type="text/javascript" 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 magic_value = "MAGIC_VALUE_123";
+ var registration;
+ function start() {
+ return navigator.serviceWorker.register("message_posting_worker.js",
+ { scope: "./sw_clients/" })
+ .then((swr) => registration = swr);
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ }
+
+
+ function testPostMessage(swr) {
+ var p = new Promise(function(res, rej) {
+ window.onmessage = function(e) {
+ if (e.data === "READY") {
+ swr.active.postMessage(magic_value);
+ } else if (e.data === magic_value) {
+ ok(true, "Worker posted the correct value.");
+ res();
+ } else {
+ ok(false, "Wrong value. Expected: " + magic_value +
+ ", got: " + e.data);
+ res();
+ }
+ }
+ });
+
+ var content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ iframe = document.createElement("iframe");
+ iframe.setAttribute('src', "sw_clients/service_worker_controlled.html");
+ content.appendChild(iframe);
+
+ return p.then(() => content.removeChild(iframe));
+ }
+
+ function runTest() {
+ start()
+ .then(testPostMessage)
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_post_message_advanced.html b/dom/workers/test/serviceworkers/test_post_message_advanced.html
new file mode 100644
index 000000000..8ea0d2300
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_post_message_advanced.html
@@ -0,0 +1,109 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 982726 - Test service worker post message advanced </title>
+ <script type="text/javascript" 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 registration;
+ var base = ["string", true, 42];
+ var blob = new Blob(["blob_content"]);
+ var file = new File(["file_content"], "file");
+ var obj = { body : "object_content" };
+
+ function readBlob(blob) {
+ return new Promise(function(resolve, reject) {
+ var reader = new FileReader();
+ reader.onloadend = () => resolve(reader.result);
+ reader.readAsText(blob);
+ });
+ }
+
+ function equals(v1, v2) {
+ return Promise.all([v1, v2]).then(function(val) {
+ ok(val[0] === val[1], "Values should match.");
+ });
+ }
+
+ function blob_equals(b1, b2) {
+ return equals(readBlob(b1), readBlob(b2));
+ }
+
+ function file_equals(f1, f2) {
+ return equals(f1.name, f2.name).then(blob_equals(f1, f2));
+ }
+
+ function obj_equals(o1, o2) {
+ return equals(o1.body, o2.body);
+ }
+
+ function start() {
+ return navigator.serviceWorker.register("message_posting_worker.js",
+ { scope: "./sw_clients/" })
+ .then((swr) => registration = swr);
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ }
+
+ function testPostMessageObject(obj, test) {
+ var p = new Promise(function(res, rej) {
+ window.onmessage = function(e) {
+ if (e.data === "READY") {
+ registration.active.postMessage(obj)
+ } else {
+ test(obj, e.data).then(res);
+ }
+ }
+ });
+
+ var content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ iframe = document.createElement("iframe");
+ iframe.setAttribute('src', "sw_clients/service_worker_controlled.html");
+ content.appendChild(iframe);
+
+ return p.then(() => content.removeChild(iframe));
+ }
+
+ function runTest() {
+ start()
+ .then(testPostMessageObject.bind(this, base[0], equals))
+ .then(testPostMessageObject.bind(this, base[1], equals))
+ .then(testPostMessageObject.bind(this, base[2], equals))
+ .then(testPostMessageObject.bind(this, blob, blob_equals))
+ .then(testPostMessageObject.bind(this, file, file_equals))
+ .then(testPostMessageObject.bind(this, obj, obj_equals))
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_post_message_source.html b/dom/workers/test/serviceworkers/test_post_message_source.html
new file mode 100644
index 000000000..543f64b4a
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_post_message_source.html
@@ -0,0 +1,68 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1142015 - Test service worker post message source </title>
+ <script type="text/javascript" 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 magic_value = "MAGIC_VALUE_RANDOM";
+ var registration;
+ function start() {
+ return navigator.serviceWorker.register("source_message_posting_worker.js",
+ { scope: "./nonexistent_scope/" })
+ .then((swr) => registration = swr);
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ }
+
+
+ function testPostMessage(swr) {
+ var p = new Promise(function(res, rej) {
+ navigator.serviceWorker.onmessage = function(e) {
+ ok(e.data === magic_value, "Worker posted the correct value.");
+ res();
+ }
+ });
+
+ ok(swr.installing, "Installing worker exists.");
+ swr.installing.postMessage(magic_value);
+ return p;
+ }
+
+
+ function runTest() {
+ start()
+ .then(testPostMessage)
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_privateBrowsing.html b/dom/workers/test/serviceworkers/test_privateBrowsing.html
new file mode 100644
index 000000000..976337711
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_privateBrowsing.html
@@ -0,0 +1,113 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for ServiceWorker - Private Browsing</title>
+ <script type="application/javascript" 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 Ci = Components.interfaces;
+var mainWindow;
+
+var contentPage = "http://mochi.test:8888/chrome/dom/workers/test/empty.html";
+var workerScope = "http://mochi.test:8888/chrome/dom/workers/test/serviceworkers/";
+var workerURL = workerScope + "worker.js";
+
+function testOnWindow(aIsPrivate, aCallback) {
+ var win = mainWindow.OpenBrowserWindow({private: aIsPrivate});
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad, false);
+ win.addEventListener("DOMContentLoaded", function onInnerLoad() {
+ if (win.content.location.href != contentPage) {
+ win.gBrowser.loadURI(contentPage);
+ return;
+ }
+
+ win.removeEventListener("DOMContentLoaded", onInnerLoad, true);
+ SimpleTest.executeSoon(function() { aCallback(win); });
+ }, true);
+
+ if (!aIsPrivate) {
+ win.gBrowser.loadURI(contentPage);
+ }
+ }, true);
+}
+
+function setupWindow() {
+ mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ runTest();
+}
+
+var wN;
+var registration;
+var wP;
+
+function testPrivateWindow() {
+ testOnWindow(true, function(aWin) {
+ wP = aWin;
+ ok(!("serviceWorker" in wP.content.navigator), "ServiceWorkers are not available for private windows");
+ runTest();
+ });
+}
+
+function doTests() {
+ testOnWindow(false, function(aWin) {
+ wN = aWin;
+ ok("serviceWorker" in wN.content.navigator, "ServiceWorkers are available for normal windows");
+
+ wN.content.navigator.serviceWorker.register(workerURL,
+ { scope: workerScope })
+ .then(function(aRegistration) {
+ registration = aRegistration;
+ ok(registration, "Registering a service worker in a normal window should succeed");
+
+ // Bug 1255621: We should be able to load a controlled document in a private window.
+ testPrivateWindow();
+ }, function(aError) {
+ ok(false, "Error registering worker in normal window: " + aError);
+ testPrivateWindow();
+ });
+ });
+}
+
+var steps = [
+ setupWindow,
+ doTests
+];
+
+function cleanup() {
+ wN.close();
+ wP.close();
+
+ SimpleTest.finish();
+}
+
+function runTest() {
+ if (!steps.length) {
+ registration.unregister().then(cleanup, cleanup);
+
+ return;
+ }
+
+ var step = steps.shift();
+ step();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["browser.startup.page", 0],
+ ["browser.startup.homepage_override.mstone", "ignore"],
+]}, runTest);
+
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_register_base.html b/dom/workers/test/serviceworkers/test_register_base.html
new file mode 100644
index 000000000..58b08d27a
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_register_base.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 that registering a service worker uses the docuemnt URI for the secure origin check</title>
+ <script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css" />
+ <base href="https://mozilla.org/">
+</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() {
+ navigator.serviceWorker.register("http://mochi.test:8888/tests/dom/workers/test/serviceworkers/empty.js")
+ .then(reg => {
+ ok(false, "Register should fail");
+ SimpleTest.finish();
+ }, err => {
+ is(err.name, "SecurityError", "Registration should fail with SecurityError");
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_register_https_in_http.html b/dom/workers/test/serviceworkers/test_register_https_in_http.html
new file mode 100644
index 000000000..2c8e998f7
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_register_https_in_http.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>Bug 1172948 - Test that registering a service worker from inside an HTTPS iframe embedded in an HTTP iframe doesn't work</title>
+ <script type="text/javascript" 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 iframe = document.createElement("iframe");
+ iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/register_https.html";
+ document.body.appendChild(iframe);
+
+ window.onmessage = event => {
+ switch (event.data.type) {
+ case "ok":
+ ok(event.data.status, event.data.msg);
+ break;
+ case "done":
+ SimpleTest.finish();
+ break;
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context.js b/dom/workers/test/serviceworkers/test_request_context.js
new file mode 100644
index 000000000..aebba79a7
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context.js
@@ -0,0 +1,75 @@
+// Copied from /dom/plugins/test/mochitest/utils.js
+function getTestPlugin(pluginName) {
+ var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
+ .getService(SpecialPowers.Ci.nsIPluginHost);
+ var tags = ph.getPluginTags();
+ var name = pluginName || "Test Plug-in";
+ for (var tag of tags) {
+ if (tag.name == name) {
+ return tag;
+ }
+ }
+
+ ok(false, "Could not find plugin tag with plugin name '" + name + "'");
+ return null;
+}
+function setTestPluginEnabledState(newEnabledState, pluginName) {
+ var oldEnabledState = SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
+ if (!oldEnabledState) {
+ return;
+ }
+ var plugin = getTestPlugin(pluginName);
+ while (plugin.enabledState != newEnabledState) {
+ // Run a nested event loop to wait for the preference change to
+ // propagate to the child. Yuck!
+ SpecialPowers.Services.tm.currentThread.processNextEvent(true);
+ }
+ SimpleTest.registerCleanupFunction(function() {
+ SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
+ });
+}
+setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+
+function isMulet() {
+ try {
+ return SpecialPowers.getBoolPref("b2g.is_mulet");
+ } catch(e) {
+ return false;
+ }
+}
+
+var iframe;
+function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/context/register.html";
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "todo") {
+ todo(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/context/index.html?" + gTest;
+ } else if (e.data.status == "done") {
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/context/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ SimpleTest.finish();
+ }
+ };
+}
+
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["beacon.enabled", true],
+ ["browser.send_pings", true],
+ ["browser.send_pings.max_per_link", -1],
+ ["dom.caches.enabled", true],
+ ["dom.requestcontext.enabled", true],
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+};
diff --git a/dom/workers/test/serviceworkers/test_request_context_audio.html b/dom/workers/test/serviceworkers/test_request_context_audio.html
new file mode 100644
index 000000000..929a24428
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_audio.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testAudio";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_beacon.html b/dom/workers/test/serviceworkers/test_request_context_beacon.html
new file mode 100644
index 000000000..ce44214d6
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_beacon.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testBeacon";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_cache.html b/dom/workers/test/serviceworkers/test_request_context_cache.html
new file mode 100644
index 000000000..3d62baabc
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_cache.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testCache";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_cspreport.html b/dom/workers/test/serviceworkers/test_request_context_cspreport.html
new file mode 100644
index 000000000..a27e79303
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_cspreport.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testCSPReport";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_embed.html b/dom/workers/test/serviceworkers/test_request_context_embed.html
new file mode 100644
index 000000000..f8d374246
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_embed.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testEmbed";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_fetch.html b/dom/workers/test/serviceworkers/test_request_context_fetch.html
new file mode 100644
index 000000000..94de8be31
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_fetch.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testFetch";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_font.html b/dom/workers/test/serviceworkers/test_request_context_font.html
new file mode 100644
index 000000000..d81a0686b
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_font.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testFont";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_frame.html b/dom/workers/test/serviceworkers/test_request_context_frame.html
new file mode 100644
index 000000000..d5dc1f745
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_frame.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testFrame";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_iframe.html b/dom/workers/test/serviceworkers/test_request_context_iframe.html
new file mode 100644
index 000000000..d3b0675e0
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_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>
+ <title>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testIFrame";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_image.html b/dom/workers/test/serviceworkers/test_request_context_image.html
new file mode 100644
index 000000000..810063da4
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_image.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testImage";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_imagesrcset.html b/dom/workers/test/serviceworkers/test_request_context_imagesrcset.html
new file mode 100644
index 000000000..95b2b7214
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_imagesrcset.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testImageSrcSet";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_internal.html b/dom/workers/test/serviceworkers/test_request_context_internal.html
new file mode 100644
index 000000000..45f454495
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_internal.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testInternal";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_nestedworker.html b/dom/workers/test/serviceworkers/test_request_context_nestedworker.html
new file mode 100644
index 000000000..226de691b
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_nestedworker.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testNestedWorker";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_nestedworkerinsharedworker.html b/dom/workers/test/serviceworkers/test_request_context_nestedworkerinsharedworker.html
new file mode 100644
index 000000000..48934a57c
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_nestedworkerinsharedworker.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testNestedWorkerInSharedWorker";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_object.html b/dom/workers/test/serviceworkers/test_request_context_object.html
new file mode 100644
index 000000000..189c5adbb
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_object.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testObject";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_picture.html b/dom/workers/test/serviceworkers/test_request_context_picture.html
new file mode 100644
index 000000000..1b49e7eb9
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_picture.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testPicture";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_ping.html b/dom/workers/test/serviceworkers/test_request_context_ping.html
new file mode 100644
index 000000000..460e9efb4
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_ping.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testPing";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_plugin.html b/dom/workers/test/serviceworkers/test_request_context_plugin.html
new file mode 100644
index 000000000..862e9d4a5
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_plugin.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testPlugin";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_script.html b/dom/workers/test/serviceworkers/test_request_context_script.html
new file mode 100644
index 000000000..ec560ef72
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_script.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testScript";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_sharedworker.html b/dom/workers/test/serviceworkers/test_request_context_sharedworker.html
new file mode 100644
index 000000000..93ccdf3ae
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_sharedworker.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testSharedWorker";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_style.html b/dom/workers/test/serviceworkers/test_request_context_style.html
new file mode 100644
index 000000000..b557d5c04
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_style.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testStyle";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_track.html b/dom/workers/test/serviceworkers/test_request_context_track.html
new file mode 100644
index 000000000..1b161c4d0
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_track.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testTrack";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_video.html b/dom/workers/test/serviceworkers/test_request_context_video.html
new file mode 100644
index 000000000..1886e31d7
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_video.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testVideo";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_worker.html b/dom/workers/test/serviceworkers/test_request_context_worker.html
new file mode 100644
index 000000000..9de127304
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_worker.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testWorker";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_xhr.html b/dom/workers/test/serviceworkers/test_request_context_xhr.html
new file mode 100644
index 000000000..ed0d60bf8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_xhr.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testXHR";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_request_context_xslt.html b/dom/workers/test/serviceworkers/test_request_context_xslt.html
new file mode 100644
index 000000000..a6d837b69
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_request_context_xslt.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>Bug 1121157 - Test that Request objects passed to FetchEvent have the correct context</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="test_request_context.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<script type="text/javascript">
+var gTest = "testXSLT";
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_sandbox_intercept.html b/dom/workers/test/serviceworkers/test_sandbox_intercept.html
new file mode 100644
index 000000000..273df53ff
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_sandbox_intercept.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 1142727 - Test that sandboxed iframes are not intercepted</title>
+ <script type="text/javascript" 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">
+<iframe sandbox="allow-scripts allow-same-origin"></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ function runTest() {
+ iframe = document.querySelector("iframe");
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/sandbox/register.html";
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/sandbox/index.html";
+ } else if (e.data.status == "done") {
+ iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/sandbox/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_sanitize.html b/dom/workers/test/serviceworkers/test_sanitize.html
new file mode 100644
index 000000000..842cb38c3
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_sanitize.html
@@ -0,0 +1,87 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1080109 - Clear ServiceWorker registrations for all domains</title>
+ <script type="text/javascript" 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 start() {
+ const Cc = SpecialPowers.Cc;
+ const Ci = SpecialPowers.Ci;
+
+ function testNotIntercepted() {
+ testFrame("sanitize/frame.html").then(function(body) {
+ is(body, "FAIL", "Expected frame to not be controlled");
+ // No need to unregister since that already happened.
+ navigator.serviceWorker.getRegistration("sanitize/foo").then(function(reg) {
+ ok(reg === undefined, "There should no longer be a valid registration");
+ }, function(e) {
+ ok(false, "getRegistration() should not error");
+ }).then(function(e) {
+ SimpleTest.finish();
+ });
+ });
+ }
+
+ registerSW().then(function() {
+ return testFrame("sanitize/frame.html").then(function(body) {
+ is(body, "intercepted", "Expected serviceworker to intercept request");
+ });
+ }).then(function() {
+ return navigator.serviceWorker.getRegistration("sanitize/foo");
+ }).then(function(reg) {
+ reg.active.onstatechange = function(e) {
+ e.target.onstatechange = null;
+ ok(e.target.state, "redundant", "On clearing data, serviceworker should become redundant");
+ testNotIntercepted();
+ };
+ }).then(function() {
+ SpecialPowers.removeAllServiceWorkerData();
+ });
+ }
+
+ function testFrame(src) {
+ return new Promise(function(resolve, reject) {
+ var iframe = document.createElement("iframe");
+ iframe.src = src;
+ window.onmessage = function(message) {
+ window.onmessage = null;
+ iframe.src = "about:blank";
+ document.body.removeChild(iframe);
+ iframe = null;
+ SpecialPowers.exactGC(function() {
+ resolve(message.data);
+ });
+ };
+ document.body.appendChild(iframe);
+ });
+ }
+
+ function registerSW() {
+ return testFrame("sanitize/register.html");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, function() {
+ start();
+ });
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_sanitize_domain.html b/dom/workers/test/serviceworkers/test_sanitize_domain.html
new file mode 100644
index 000000000..054b60f37
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_sanitize_domain.html
@@ -0,0 +1,90 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1080109 - Clear ServiceWorker registrations for specific domains</title>
+ <script type="text/javascript" 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 start() {
+ const Cc = SpecialPowers.Cc;
+ const Ci = SpecialPowers.Ci;
+
+ function checkDomainRegistration(domain, exists) {
+ return testFrame("http://" + domain + "/tests/dom/workers/test/serviceworkers/sanitize/example_check_and_unregister.html").then(function(body) {
+ if (body === "FAIL") {
+ ok(false, "Error acquiring registration or unregistering for " + domain);
+ } else {
+ if (exists) {
+ ok(body === true, "Expected " + domain + " to still have a registration.");
+ } else {
+ ok(body === false, "Expected " + domain + " to have no registration.");
+ }
+ }
+ });
+ }
+
+ registerSW().then(function() {
+ return testFrame("http://example.com/tests/dom/workers/test/serviceworkers/sanitize/frame.html").then(function(body) {
+ is(body, "intercepted", "Expected serviceworker to intercept request");
+ });
+ }).then(function() {
+ SpecialPowers.removeServiceWorkerDataForExampleDomain();
+ }).then(function() {
+ return checkDomainRegistration("prefixexample.com", true /* exists */)
+ .then(function(e) {
+ return checkDomainRegistration("example.com", false /* exists */);
+ }).then(function(e) {
+ SimpleTest.finish();
+ });
+ })
+ }
+
+ function testFrame(src) {
+ return new Promise(function(resolve, reject) {
+ var iframe = document.createElement("iframe");
+ iframe.src = src;
+ window.onmessage = function(message) {
+ window.onmessage = null;
+ iframe.src = "about:blank";
+ document.body.removeChild(iframe);
+ iframe = null;
+ SpecialPowers.exactGC(function() {
+ resolve(message.data);
+ });
+ };
+ document.body.appendChild(iframe);
+ });
+ }
+
+ function registerSW() {
+ return testFrame("http://example.com/tests/dom/workers/test/serviceworkers/sanitize/register.html")
+ .then(function(e) {
+ // Register for prefixexample.com and then ensure it does not get unregistered.
+ return testFrame("http://prefixexample.com/tests/dom/workers/test/serviceworkers/sanitize/register.html");
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, function() {
+ start();
+ });
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_scopes.html b/dom/workers/test/serviceworkers/test_scopes.html
new file mode 100644
index 000000000..2d8116f83
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_scopes.html
@@ -0,0 +1,121 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 984048 - Test scope glob matching.</title>
+ <script type="text/javascript" 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 scriptsAndScopes = [
+ [ "worker.js", "./sub/dir/"],
+ [ "worker.js", "./sub/dir" ],
+ [ "worker.js", "./sub/dir.html" ],
+ [ "worker.js", "./sub/dir/a" ],
+ [ "worker.js", "./sub" ],
+ [ "worker.js", "./star*" ], // '*' has no special meaning
+ ];
+
+ function registerWorkers() {
+ var registerArray = [];
+ scriptsAndScopes.forEach(function(item) {
+ registerArray.push(navigator.serviceWorker.register(item[0], { scope: item[1] }));
+ });
+
+ // Check register()'s step 4 which uses script's url with "./" as the scope if no scope is passed.
+ // The other tests already check step 5.
+ registerArray.push(navigator.serviceWorker.register("scope/scope_worker.js"));
+
+ // Check that SW cannot be registered for a scope "above" the script's location.
+ registerArray.push(new Promise(function(resolve, reject) {
+ navigator.serviceWorker.register("scope/scope_worker.js", { scope: "./" })
+ .then(function() {
+ ok(false, "registration scope has to be inside service worker script scope.");
+ reject();
+ }, function() {
+ ok(true, "registration scope has to be inside service worker script scope.");
+ resolve();
+ });
+ }));
+ return Promise.all(registerArray);
+ }
+
+ function unregisterWorkers() {
+ var unregisterArray = [];
+ scriptsAndScopes.forEach(function(item) {
+ var p = navigator.serviceWorker.getRegistration(item[1]);
+ unregisterArray.push(p.then(function(reg) {
+ return reg.unregister();
+ }));
+ });
+
+ unregisterArray.push(navigator.serviceWorker.getRegistration("scope/").then(function (reg) {
+ return reg.unregister();
+ }));
+
+ return Promise.all(unregisterArray);
+ }
+
+ function testScopes() {
+ return new Promise(function(resolve, reject) {
+ var getScope = navigator.serviceWorker.getScopeForUrl.bind(navigator.serviceWorker);
+ var base = new URL(".", document.baseURI);
+
+ function p(s) {
+ return base + s;
+ }
+
+ function fail(fn) {
+ try {
+ getScope(p("index.html"));
+ ok(false, "No registration");
+ } catch(e) {
+ ok(true, "No registration");
+ }
+ }
+
+ ok(getScope(p("sub.html")) === p("sub"), "Scope should match");
+ ok(getScope(p("sub/dir.html")) === p("sub/dir.html"), "Scope should match");
+ ok(getScope(p("sub/dir")) === p("sub/dir"), "Scope should match");
+ ok(getScope(p("sub/dir/foo")) === p("sub/dir/"), "Scope should match");
+ ok(getScope(p("sub/dir/afoo")) === p("sub/dir/a"), "Scope should match");
+ ok(getScope(p("star*wars")) === p("star*"), "Scope should match");
+ ok(getScope(p("scope/some_file.html")) === p("scope/"), "Scope should match");
+ fail("index.html");
+ fail("sua.html");
+ fail("star/a.html");
+ resolve();
+ });
+ }
+
+ function runTest() {
+ registerWorkers()
+ .then(testScopes)
+ .then(unregisterWorkers)
+ .then(function() {
+ SimpleTest.finish();
+ }).catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_service_worker_allowed.html b/dom/workers/test/serviceworkers/test_service_worker_allowed.html
new file mode 100644
index 000000000..eca94ebb4
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_service_worker_allowed.html
@@ -0,0 +1,74 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the Service-Worker-Allowed header</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="content"></div>
+<script class="testbody" type="text/javascript">
+ var gTests = [
+ "worker_scope_different.js",
+ "worker_scope_different2.js",
+ "worker_scope_too_deep.js",
+ ];
+
+ function testPermissiveHeader() {
+ // Make sure that this registration succeeds, as the prefix check should pass.
+ return navigator.serviceWorker.register("swa/worker_scope_too_narrow.js", {scope: "swa/"})
+ .then(swr => {
+ ok(true, "Registration should finish successfully");
+ return swr.unregister();
+ }, err => {
+ ok(false, "Unexpected error when registering the service worker: " + err);
+ });
+ }
+
+ function testPreciseHeader() {
+ // Make sure that this registration succeeds, as the prefix check should pass
+ // given that we parse the use the full pathname from this URL..
+ return navigator.serviceWorker.register("swa/worker_scope_precise.js", {scope: "swa/"})
+ .then(swr => {
+ ok(true, "Registration should finish successfully");
+ return swr.unregister();
+ }, err => {
+ ok(false, "Unexpected error when registering the service worker: " + err);
+ });
+ }
+
+ function runTest() {
+ Promise.all(gTests.map(testName => {
+ return new Promise((resolve, reject) => {
+ // Make sure that registration fails.
+ navigator.serviceWorker.register("swa/" + testName, {scope: "swa/"})
+ .then(reject, resolve);
+ });
+ })).then(values => {
+ values.forEach(error => {
+ is(error.name, "SecurityError", "Registration should fail");
+ });
+ Promise.all([
+ testPermissiveHeader(),
+ testPreciseHeader(),
+ ]).then(SimpleTest.finish, SimpleTest.finish);
+ }, (x) => {
+ ok(false, "Registration should not succeed, but it did");
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_serviceworker_header.html b/dom/workers/test/serviceworkers/test_serviceworker_header.html
new file mode 100644
index 000000000..ac5a6e49f
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworker_header.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 that service worker scripts are fetched with a Service-Worker: script header</title>
+ <script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css" />
+ <base href="https://mozilla.org/">
+</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() {
+ navigator.serviceWorker.register("http://mochi.test:8888/tests/dom/workers/test/serviceworkers/header_checker.sjs")
+ .then(reg => {
+ ok(true, "Register should succeed");
+ reg.unregister().then(() => SimpleTest.finish());
+ }, err => {
+ ok(false, "Register should not fail");
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.serviceWorkers.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.html b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.html
new file mode 100644
index 000000000..0130ca2d9
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.html
@@ -0,0 +1,106 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Validate Interfaces Exposed to Service Workers</title>
+ <script type="text/javascript" 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 class="testbody" type="text/javascript">
+
+ function setupSW(registration) {
+ var worker = registration.waiting ||
+ registration.active;
+ window.onmessage = function(event) {
+ if (event.data.type == 'finish') {
+ registration.unregister().then(function(success) {
+ ok(success, "The service worker should be unregistered successfully");
+
+ SimpleTest.finish();
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ SimpleTest.finish();
+ });
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+
+ } else if (event.data.type == 'getPrefs') {
+ var result = {};
+ event.data.prefs.forEach(function(pref) {
+ result[pref] = SpecialPowers.Services.prefs.getBoolPref(pref);
+ });
+ worker.postMessage({
+ type: 'returnPrefs',
+ prefs: event.data.prefs,
+ result: result
+ });
+
+ } else if (event.data.type == 'getVersion') {
+ var result = SpecialPowers.Cc['@mozilla.org/xre/app-info;1'].getService(SpecialPowers.Ci.nsIXULAppInfo).version;
+ worker.postMessage({
+ type: 'returnVersion',
+ result: result
+ });
+
+ } else if (event.data.type == 'getUserAgent') {
+ worker.postMessage({
+ type: 'returnUserAgent',
+ result: navigator.userAgent
+ });
+ } else if (event.data.type == 'getOSCPU') {
+ worker.postMessage({
+ type: 'returnOSCPU',
+ result: navigator.oscpu
+ });
+ }
+ }
+
+ worker.onerror = function(event) {
+ ok(false, 'Worker had an error: ' + event.data);
+ SimpleTest.finish();
+ };
+
+ var iframe = document.createElement("iframe");
+ iframe.src = "message_receiver.html";
+ iframe.onload = function() {
+ worker.postMessage({ script: "test_serviceworker_interfaces.js" });
+ };
+ document.body.appendChild(iframe);
+ }
+
+ function runTest() {
+ navigator.serviceWorker.ready.then(setupSW);
+ navigator.serviceWorker.register("serviceworker_wrapper.js", {scope: "."});
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ // The handling of "dom.caches.enabled" here is a bit complicated. What we
+ // want to happen is that Cache is always enabled in service workers. So
+ // if service workers are disabled by default we want to force on both
+ // service workers and "dom.caches.enabled". But if service workers are
+ // enabled by default, we do not want to mess with the "dom.caches.enabled"
+ // value, since that would defeat the purpose of the test. Use a subframe
+ // to decide whether service workers are enabled by default, so we don't
+ // force creation of our own Navigator object before our prefs are set.
+ var prefs = [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ];
+
+ var subframe = document.createElement("iframe");
+ document.body.appendChild(subframe);
+ if (!("serviceWorker" in subframe.contentWindow.navigator)) {
+ prefs.push(["dom.caches.enabled", true]);
+ }
+ subframe.remove();
+
+ SpecialPowers.pushPrefEnv({"set": prefs}, runTest);
+ };
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
new file mode 100644
index 000000000..9dbfcc099
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
@@ -0,0 +1,278 @@
+// 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",
+// { 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.
+
+// IMPORTANT: Do not change this list without review from
+// a JavaScript Engine peer!
+var ecmaGlobals =
+ [
+ "Array",
+ "ArrayBuffer",
+ "Boolean",
+ "DataView",
+ "Date",
+ "Error",
+ "EvalError",
+ "Float32Array",
+ "Float64Array",
+ "Function",
+ "Infinity",
+ "Int16Array",
+ "Int32Array",
+ "Int8Array",
+ "InternalError",
+ {name: "Intl", android: false},
+ "Iterator",
+ "JSON",
+ "Map",
+ "Math",
+ "NaN",
+ "Number",
+ "Object",
+ "Promise",
+ "Proxy",
+ "RangeError",
+ "ReferenceError",
+ "Reflect",
+ "RegExp",
+ "Set",
+ {name: "SharedArrayBuffer", release: false},
+ {name: "SIMD", nightly: true},
+ {name: "Atomics", release: false},
+ "StopIteration",
+ "String",
+ "Symbol",
+ "SyntaxError",
+ {name: "TypedObject", nightly: true},
+ "TypeError",
+ "Uint16Array",
+ "Uint32Array",
+ "Uint8Array",
+ "Uint8ClampedArray",
+ "URIError",
+ "WeakMap",
+ "WeakSet",
+ ];
+// 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!
+var interfaceNamesInGlobalScope =
+ [
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Blob",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "BroadcastChannel",
+// 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!
+ "Client",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Clients",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Crypto",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "CustomEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Directory",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMCursor",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMError",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMException",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMStringList",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Event",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "EventTarget",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "ExtendableEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "ExtendableMessageEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "FetchEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "File",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "FileReader",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "FileReaderSync",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "FormData",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Headers",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBCursor",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBCursorWithValue",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBDatabase",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBFactory",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBIndex",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBKeyRange",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBObjectStore",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBOpenDBRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBTransaction",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBVersionChangeEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "ImageBitmap",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "ImageBitmapRenderingContext",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "ImageData",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "MessageChannel",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "MessageEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "MessagePort",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Notification",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "NotificationEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Performance",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "PerformanceEntry",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "PerformanceMark",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "PerformanceMeasure",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PerformanceObserver", nightly: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PerformanceObserverEntryList", nightly: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Request",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Response",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "ServiceWorker",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "ServiceWorkerGlobalScope",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "ServiceWorkerRegistration",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ {name: "StorageManager", nightly: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "SubtleCrypto",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "TextDecoder",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "TextEncoder",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "URL",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "URLSearchParams",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebSocket",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "WindowClient",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "WorkerGlobalScope",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "WorkerLocation",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "WorkerNavigator",
+// 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!
+
+function createInterfaceMap(version, userAgent) {
+ var isNightly = version.endsWith("a1");
+ var isRelease = !version.includes("a");
+ var isDesktop = !/Mobile|Tablet/.test(userAgent);
+ var isAndroid = !!navigator.userAgent.includes("Android");
+
+ var interfaceMap = {};
+
+ function addInterfaces(interfaces)
+ {
+ for (var entry of interfaces) {
+ if (typeof(entry) === "string") {
+ interfaceMap[entry] = true;
+ } else {
+ ok(!("pref" in entry), "Bogus pref annotation for " + entry.name);
+ if ((entry.nightly === !isNightly) ||
+ (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) ||
+ (entry.nonReleaseAndroid === !(isAndroid && !isRelease) && isAndroid) ||
+ (entry.desktop === !isDesktop) ||
+ (entry.android === !isAndroid && !entry.nonReleaseAndroid && !entry.nightlyAndroid) ||
+ (entry.release === !isRelease)) {
+ interfaceMap[entry.name] = false;
+ } else {
+ interfaceMap[entry.name] = true;
+ }
+ }
+ }
+ }
+
+ addInterfaces(ecmaGlobals);
+ addInterfaces(interfaceNamesInGlobalScope);
+
+ return interfaceMap;
+}
+
+function runTest(version, userAgent) {
+ var interfaceMap = createInterfaceMap(version, userAgent);
+ for (var name of Object.getOwnPropertyNames(self)) {
+ // An interface name should start with an upper case character.
+ if (!/^[A-Z]/.test(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 on the service worker? 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 self === interfaceMap[name],
+ name + " should " + (interfaceMap[name] ? "" : " NOT") + " be defined on the global scope");
+ if (!interfaceMap[name]) {
+ delete interfaceMap[name];
+ }
+ }
+ is(Object.keys(interfaceMap).length, 0,
+ "The following interface(s) are not enumerated: " + Object.keys(interfaceMap).join(", "));
+}
+
+workerTestGetVersion(function(version) {
+ workerTestGetUserAgent(function(userAgent) {
+ runTest(version, userAgent);
+ workerTestDone();
+ });
+});
diff --git a/dom/workers/test/serviceworkers/test_serviceworker_not_sharedworker.html b/dom/workers/test/serviceworkers/test_serviceworker_not_sharedworker.html
new file mode 100644
index 000000000..96dd9f159
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworker_not_sharedworker.html
@@ -0,0 +1,66 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1141274 - test that service workers and shared workers are separate</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var iframe;
+ const SCOPE = "http://mochi.test:8888/tests/dom/workers/test/serviceworkers/";
+ function runTest() {
+ navigator.serviceWorker.ready.then(setupSW);
+ navigator.serviceWorker.register("serviceworker_not_sharedworker.js",
+ {scope: SCOPE});
+ }
+
+ var sw, worker;
+ function setupSW(registration) {
+ sw = registration.waiting || registration.active;
+ worker = new SharedWorker("serviceworker_not_sharedworker.js", SCOPE);
+ worker.port.start();
+ iframe = document.querySelector("iframe");
+ iframe.src = "message_receiver.html";
+ iframe.onload = function() {
+ window.onmessage = function(e) {
+ is(e.data.result, "serviceworker", "We should be talking to a service worker");
+ window.onmessage = null;
+ worker.port.onmessage = function(e) {
+ is(e.data.result, "sharedworker", "We should be talking to a shared worker");
+ registration.unregister().then(function(success) {
+ ok(success, "unregister should succeed");
+ SimpleTest.finish();
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ SimpleTest.finish();
+ });
+ };
+ worker.port.postMessage({msg: "whoareyou"});
+ };
+ sw.postMessage({msg: "whoareyou"});
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_serviceworkerinfo.xul b/dom/workers/test/serviceworkers/test_serviceworkerinfo.xul
new file mode 100644
index 000000000..96e4bb1c3
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworkerinfo.xul
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for ServiceWorkerInfo"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="chrome_helpers.js"/>
+ <script type="application/javascript">
+ <![CDATA[
+
+ let IFRAME_URL = EXAMPLE_URL + "serviceworkerinfo_iframe.html";
+
+ function wait_for_active_worker(registration) {
+ ok(registration, "Registration is valid.");
+ return new Promise(function(res, rej) {
+ if (registration.activeWorker) {
+ res(registration);
+ return;
+ }
+ let listener = {
+ onChange: function() {
+ if (registration.activeWorker) {
+ registration.removeListener(listener);
+ res(registration);
+ }
+ }
+ }
+ registration.addListener(listener);
+ });
+ }
+
+ function test() {
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({'set': [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.idle_extended_timeout", 1000000],
+ ["dom.serviceWorkers.idle_timeout", 0],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, function () {
+ Task.spawn(function *() {
+ let iframe = $("iframe");
+ let promise = new Promise(function (resolve) {
+ iframe.onload = function () {
+ resolve();
+ };
+ });
+ iframe.src = IFRAME_URL;
+ yield promise;
+
+ info("Check that a service worker eventually shuts down.");
+ promise = Promise.all([
+ waitForRegister(EXAMPLE_URL),
+ waitForServiceWorkerShutdown()
+ ]);
+ iframe.contentWindow.postMessage("register", "*");
+ let [registration] = yield promise;
+
+ // Make sure the worker is active.
+ registration = yield wait_for_active_worker(registration);
+
+ let activeWorker = registration.activeWorker;
+ ok(activeWorker !== null, "Worker is not active!");
+ ok(activeWorker.debugger === null);
+
+ info("Attach a debugger to the service worker, and check that the " +
+ "service worker is restarted.");
+ activeWorker.attachDebugger();
+ let workerDebugger = activeWorker.debugger;
+ ok(workerDebugger !== null);
+
+ // Verify debugger properties
+ ok(workerDebugger.principal instanceof Ci.nsIPrincipal);
+ is(workerDebugger.url, EXAMPLE_URL + "worker.js");
+
+ info("Verify that getRegistrationByPrincipal return the same " +
+ "nsIServiceWorkerRegistrationInfo");
+ let reg = swm.getRegistrationByPrincipal(workerDebugger.principal,
+ workerDebugger.url);
+ is(reg, registration);
+
+ info("Check that getWorkerByID returns the same nsIWorkerDebugger");
+ is(activeWorker, reg.getWorkerByID(workerDebugger.serviceWorkerID));
+
+ info("Detach the debugger from the service worker, and check that " +
+ "the service worker eventually shuts down again.");
+ promise = waitForServiceWorkerShutdown();
+ activeWorker.detachDebugger();
+ yield promise;
+ ok(activeWorker.debugger === null);
+
+ promise = waitForUnregister(EXAMPLE_URL);
+ iframe.contentWindow.postMessage("unregister", "*");
+ registration = yield 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>
+ <iframe id="iframe"></iframe>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/serviceworkers/test_serviceworkermanager.xul b/dom/workers/test/serviceworkers/test_serviceworkermanager.xul
new file mode 100644
index 000000000..ead935a3c
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworkermanager.xul
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for ServiceWorkerManager"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="chrome_helpers.js"/>
+ <script type="application/javascript">
+ <![CDATA[
+
+ let IFRAME_URL = EXAMPLE_URL + "serviceworkermanager_iframe.html";
+
+ function test() {
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({'set': [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, function () {
+ Task.spawn(function* () {
+ let registrations = swm.getAllRegistrations();
+ is(registrations.length, 0);
+
+ let iframe = $("iframe");
+ let promise = waitForIframeLoad(iframe);
+ iframe.src = IFRAME_URL;
+ yield promise;
+
+ info("Check that the service worker manager notifies its listeners " +
+ "when a service worker is registered.");
+ promise = waitForRegister(EXAMPLE_URL);
+ iframe.contentWindow.postMessage("register", "*");
+ let registration = yield promise;
+
+ registrations = swm.getAllRegistrations();
+ is(registrations.length, 1);
+ is(registrations.queryElementAt(0, Ci.nsIServiceWorkerRegistrationInfo),
+ registration);
+
+ info("Check that the service worker manager does not notify its " +
+ "listeners when a service worker is registered with the same " +
+ "scope as an existing registration.");
+ let listener = {
+ onRegister: function () {
+ ok(false, "Listener should not have been notified.");
+ }
+ };
+ swm.addListener(listener);
+ iframe.contentWindow.postMessage("register", "*");
+
+ info("Check that the service worker manager notifies its listeners " +
+ "when a service worker is unregistered.");
+ promise = waitForUnregister(EXAMPLE_URL);
+ iframe.contentWindow.postMessage("unregister", "*");
+ registration = yield promise;
+ swm.removeListener(listener);
+
+ registrations = swm.getAllRegistrations();
+ is(registrations.length, 0);
+
+ 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>
+ <iframe id="iframe"></iframe>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/serviceworkers/test_serviceworkerregistrationinfo.xul b/dom/workers/test/serviceworkers/test_serviceworkerregistrationinfo.xul
new file mode 100644
index 000000000..c879dc01b
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworkerregistrationinfo.xul
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for ServiceWorkerRegistrationInfo"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="chrome_helpers.js"/>
+ <script type="application/javascript">
+ <![CDATA[
+
+ let IFRAME_URL = EXAMPLE_URL + "serviceworkerregistrationinfo_iframe.html";
+
+ function test() {
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({'set': [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, function () {
+ Task.spawn(function* () {
+ let iframe = $("iframe");
+ let promise = waitForIframeLoad(iframe);
+ iframe.src = IFRAME_URL;
+ yield promise;
+
+ // The change handler is not guaranteed to be called within the same
+ // tick of the event loop as the one in which the change happened.
+ // Because of this, the exact state of the service worker registration
+ // is only known until the handler returns.
+ //
+ // Because then-handlers are resolved asynchronously, the following
+ // checks are done using callbacks, which are called synchronously
+ // when then handler is called. These callbacks can return a promise,
+ // which is used to resolve the promise returned by the function.
+
+ info("Check that a service worker registration notifies its " +
+ "listeners when its state changes.");
+ promise = waitForRegister(EXAMPLE_URL, function (registration) {
+ is(registration.scriptSpec, "");
+ ok(registration.installingWorker === null);
+ ok(registration.waitingWorker === null);
+ ok(registration.activeWorker === null);
+
+ return waitForServiceWorkerRegistrationChange(registration, function () {
+ is(registration.scriptSpec, EXAMPLE_URL + "worker.js");
+ ok(registration.installingWorker !== null);
+ is(registration.installingWorker.scriptSpec, EXAMPLE_URL + "worker.js");
+ ok(registration.waitingWorker === null);
+ ok(registration.activeWorker === null);
+
+ return waitForServiceWorkerRegistrationChange(registration, function () {
+ ok(registration.installingWorker === null);
+ ok(registration.waitingWorker !== null);
+ ok(registration.activeWorker === null);
+
+ return waitForServiceWorkerRegistrationChange(registration, function () {
+ ok(registration.installingWorker === null);
+ ok(registration.waitingWorker === null);
+ ok(registration.activeWorker !== null);
+
+ return registration;
+ });
+ });
+ });
+ });
+ iframe.contentWindow.postMessage("register", "*");
+ let registration = yield promise;
+
+ promise = waitForServiceWorkerRegistrationChange(registration, function () {
+ is(registration.scriptSpec, EXAMPLE_URL + "worker2.js");
+ ok(registration.installingWorker !== null);
+ is(registration.installingWorker.scriptSpec, EXAMPLE_URL + "worker2.js");
+ ok(registration.waitingWorker === null);
+ ok(registration.activeWorker !== null);
+
+ return waitForServiceWorkerRegistrationChange(registration, function () {
+ ok(registration.installingWorker === null);
+ ok(registration.waitingWorker !== null);
+ ok(registration.activeWorker !== null);
+
+ return waitForServiceWorkerRegistrationChange(registration, function () {
+ ok(registration.installingWorker === null);
+ ok(registration.waitingWorker === null);
+ ok(registration.activeWorker !== null);
+
+ return registration;
+ });
+ });
+ });
+ iframe.contentWindow.postMessage("register", "*");
+ yield promise;
+
+ iframe.contentWindow.postMessage("unregister", "*");
+ yield waitForUnregister(EXAMPLE_URL);
+
+ 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>
+ <iframe id="iframe"></iframe>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/serviceworkers/test_skip_waiting.html b/dom/workers/test/serviceworkers/test_skip_waiting.html
new file mode 100644
index 000000000..7707d6035
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_skip_waiting.html
@@ -0,0 +1,95 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1131352 - Add ServiceWorkerGlobalScope skipWaiting()</title>
+ <script type="text/javascript" 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 registration, iframe, content;
+
+ function start() {
+ return navigator.serviceWorker.register("worker.js",
+ {scope: "./skip_waiting_scope/"});
+ }
+
+ function waitForActivated(swr) {
+ registration = swr;
+ var promise = new Promise(function(resolve, reject) {
+ window.onmessage = function(e) {
+ if (e.data === "READY") {
+ ok(true, "Active worker is activated now");
+ resolve();
+ } else {
+ ok(false, "Wrong value. Somenting went wrong");
+ resolve();
+ }
+ }
+ });
+
+ iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "skip_waiting_scope/index.html");
+
+ content = document.getElementById("content");
+ content.appendChild(iframe);
+
+ return promise;
+ }
+
+ function checkWhetherItSkippedWaiting() {
+ var promise = new Promise(function(resolve, reject) {
+ window.onmessage = function (evt) {
+ if (evt.data.event === "controllerchange") {
+ ok(evt.data.controllerScriptURL.match("skip_waiting_installed_worker"),
+ "The controller changed after skiping the waiting step");
+ resolve();
+ } else {
+ ok(false, "Wrong value. Somenting went wrong");
+ resolve();
+ }
+ };
+ });
+
+ navigator.serviceWorker.register("skip_waiting_installed_worker.js",
+ {scope: "./skip_waiting_scope/"})
+ .then(swr => {
+ registration = swr;
+ });
+
+ return promise;
+ }
+
+ function clean() {
+ content.removeChild(iframe);
+
+ return registration.unregister();
+ }
+
+ function runTest() {
+ start()
+ .then(waitForActivated)
+ .then(checkWhetherItSkippedWaiting)
+ .then(clean)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_strict_mode_warning.html b/dom/workers/test/serviceworkers/test_strict_mode_warning.html
new file mode 100644
index 000000000..5b66673b9
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_strict_mode_warning.html
@@ -0,0 +1,42 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1170550 - test registration of service worker scripts with a strict mode warning</title>
+ <script type="text/javascript" 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() {
+ navigator.serviceWorker
+ .register("strict_mode_warning.js", {scope: "strict_mode_warning"})
+ .then((reg) => {
+ ok(true, "Registration should not fail for warnings");
+ return reg.unregister();
+ })
+ .then(() => {
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_third_party_iframes.html b/dom/workers/test/serviceworkers/test_third_party_iframes.html
new file mode 100644
index 000000000..33e815379
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_third_party_iframes.html
@@ -0,0 +1,175 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+ <title>Bug 1152899 - Disallow the interception of third-party iframes using service workers when the third-party cookie preference is set</title>
+ <script type="text/javascript" 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">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript;version=1.7">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+
+let index = 0;
+function next() {
+ info("Step " + index);
+ if (index >= steps.length) {
+ SimpleTest.finish();
+ return;
+ }
+ try {
+ let i = index++;
+ steps[i]();
+ } catch(ex) {
+ ok(false, "Caught exception", ex);
+ }
+}
+
+onload = next;
+
+let iframe;
+let basePath = "/tests/dom/workers/test/serviceworkers/thirdparty/";
+let origin = window.location.protocol + "//" + window.location.host;
+let thirdPartyOrigin = "https://example.com";
+
+function testIframeLoaded() {
+ ok(true, "Iframe loaded");
+ iframe.removeEventListener("load", testIframeLoaded);
+ let message = {
+ source: "parent",
+ href: origin + basePath + "iframe2.html"
+ };
+ iframe.contentWindow.postMessage(message.toSource(), "*");
+}
+
+function loadThirdPartyIframe() {
+ let message = {
+ source: "parent",
+ href: thirdPartyOrigin + basePath + "iframe2.html"
+ }
+ iframe.contentWindow.postMessage(message.toSource(), "*");
+}
+
+function runTest(aExpectedResponses) {
+ iframe = document.querySelector("iframe");
+ iframe.src = thirdPartyOrigin + basePath + "register.html";
+ let responsesIndex = 0;
+ window.onmessage = function(e) {
+ let status = e.data.status;
+ let expected = aExpectedResponses[responsesIndex];
+ if (status == expected.status) {
+ ok(true, "Received expected " + expected.status);
+ if (expected.next) {
+ expected.next();
+ }
+ } else {
+ ok(false, "Expected " + expected.status + " got " + status);
+ }
+ responsesIndex++;
+ };
+}
+
+function testShouldIntercept(done) {
+ runTest([{
+ status: "ok"
+ }, {
+ status: "registrationdone",
+ next: function() {
+ iframe.addEventListener("load", testIframeLoaded);
+ iframe.src = origin + basePath + "iframe1.html";
+ }
+ }, {
+ status: "networkresponse",
+ next: loadThirdPartyIframe
+ }, {
+ status: "swresponse",
+ next: function() {
+ iframe.src = thirdPartyOrigin + basePath + "unregister.html";
+ }
+ }, {
+ status: "unregistrationdone",
+ next: function() {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ done();
+ }
+ }]);
+}
+
+function testShouldNotIntercept(done) {
+ runTest([{
+ status: "ok"
+ }, {
+ status: "registrationdone",
+ next: function() {
+ iframe.addEventListener("load", testIframeLoaded);
+ iframe.src = origin + basePath + "iframe1.html";
+ }
+ }, {
+ status: "networkresponse",
+ next: loadThirdPartyIframe
+ }, {
+ status: "networkresponse",
+ next: function() {
+ iframe.src = thirdPartyOrigin + basePath + "unregister.html";
+ }
+ }, {
+ status: "unregistrationdone",
+ next: function() {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ done();
+ }
+ }]);
+}
+
+const COOKIE_BEHAVIOR_ACCEPT = 0;
+const COOKIE_BEHAVIOR_REJECTFOREIGN = 1;
+const COOKIE_BEHAVIOR_REJECT = 2;
+const COOKIE_BEHAVIOR_LIMITFOREIGN = 3;
+
+let steps = [() => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["browser.dom.window.dump.enabled", true],
+ ["network.cookie.cookieBehavior", COOKIE_BEHAVIOR_ACCEPT]
+ ]}, next);
+}, () => {
+ testShouldIntercept(next);
+}, () => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["network.cookie.cookieBehavior", COOKIE_BEHAVIOR_REJECTFOREIGN]
+ ]}, next);
+}, () => {
+ testShouldNotIntercept(next);
+}, () => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["network.cookie.cookieBehavior", COOKIE_BEHAVIOR_REJECT]
+ ]}, next);
+}, () => {
+ testShouldIntercept(next);
+}, () => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["network.cookie.cookieBehavior", COOKIE_BEHAVIOR_LIMITFOREIGN]
+ ]}, next);
+}, () => {
+ testShouldIntercept(next);
+}];
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_unregister.html b/dom/workers/test/serviceworkers/test_unregister.html
new file mode 100644
index 000000000..8366f50c1
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_unregister.html
@@ -0,0 +1,138 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 984048 - Test unregister</title>
+ <script type="text/javascript" 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 simpleRegister() {
+ return navigator.serviceWorker.register("worker.js", { scope: "unregister/" }).then(function(swr) {
+ if (swr.installing) {
+ return new Promise(function(resolve, reject) {
+ swr.installing.onstatechange = function(e) {
+ if (swr.waiting) {
+ swr.waiting.onstatechange = function(e) {
+ if (swr.active) {
+ resolve();
+ } else if (swr.waiting && swr.waiting.state == "redundant") {
+ reject("Should not go into redundant");
+ }
+ }
+ } else {
+ if (swr.active) {
+ resolve();
+ } else {
+ reject("No waiting and no active!");
+ }
+ }
+ }
+ });
+ } else {
+ return Promise.reject("Installing should be non-null");
+ }
+ });
+ }
+
+ function testControlled() {
+ var testPromise = new Promise(function(res, rej) {
+ window.onmessage = function(e) {
+ if (!("controlled" in e.data)) {
+ ok(false, "Something went wrong.");
+ rej();
+ return;
+ }
+
+ ok(e.data.controlled, "New window should be controlled.");
+ res();
+ }
+ })
+
+ var div = document.getElementById("content");
+ ok(div, "Parent exists");
+
+ var ifr = document.createElement("iframe");
+ ifr.setAttribute('src', "unregister/index.html");
+ div.appendChild(ifr);
+
+ return testPromise.then(function() {
+ div.removeChild(ifr);
+ });
+ }
+
+ function unregister() {
+ return navigator.serviceWorker.getRegistration("unregister/")
+ .then(function(reg) {
+ if (!reg) {
+ info("Registration already removed");
+ return;
+ }
+
+ info("getRegistration() succeeded " + reg.scope);
+ return reg.unregister().then(function(v) {
+ ok(v, "Unregister should resolve to true");
+ }, function(e) {
+ ok(false, "Unregister failed with " + e.name);
+ });
+ });
+ }
+
+ function testUncontrolled() {
+ var testPromise = new Promise(function(res, rej) {
+ window.onmessage = function(e) {
+ if (!("controlled" in e.data)) {
+ ok(false, "Something went wrong.");
+ rej();
+ return;
+ }
+
+ ok(!e.data.controlled, "New window should not be controlled.");
+ res();
+ }
+ });
+
+ var div = document.getElementById("content");
+ ok(div, "Parent exists");
+
+ var ifr = document.createElement("iframe");
+ ifr.setAttribute('src', "unregister/index.html");
+ div.appendChild(ifr);
+
+ return testPromise.then(function() {
+ div.removeChild(ifr);
+ });
+ }
+
+ function runTest() {
+ simpleRegister()
+ .then(testControlled)
+ .then(unregister)
+ .then(testUncontrolled)
+ .then(function() {
+ SimpleTest.finish();
+ }).catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_unresolved_fetch_interception.html b/dom/workers/test/serviceworkers/test_unresolved_fetch_interception.html
new file mode 100644
index 000000000..7296a3ddf
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_unresolved_fetch_interception.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test that an unresolved respondWith promise will reset the channel when
+ the service worker is terminated due to idling, and that appropriate error
+ messages are logged for both the termination of the serice worker and the
+ resetting of the channel.
+ -->
+<head>
+ <title>Test for Bug 1188545</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script src="error_reporting_helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+</head>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1188545">Mozilla Bug 118845</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="text/javascript">
+// (This doesn't really need to be its own task, but it allows the actual test
+// case to be self-contained.)
+add_task(function setupPrefs() {
+ return SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]});
+});
+
+add_task(function* grace_timeout_termination_with_interrupted_intercept() {
+ // Setup timeouts so that the service worker will go into grace timeout after
+ // a zero-length idle timeout.
+ yield SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.idle_timeout", 0],
+ ["dom.serviceWorkers.idle_extended_timeout", 299999]]});
+
+ // The SW will claim us once it activates; this is async, start listening now.
+ let waitForControlled = new Promise((resolve) => {
+ navigator.serviceWorker.oncontrollerchange = resolve;
+ });
+
+ let registration = yield navigator.serviceWorker.register(
+ "unresolved_fetch_worker.js", { scope: "./"} );
+ yield waitForControlled;
+ ok(navigator.serviceWorker.controller, "Controlled"); // double check!
+
+ // We want to make sure the SW is active and processing the fetch before we
+ // try and kill it. It sends us a message when it has done so.
+ let waitForFetchActive = new Promise((resolve) => {
+ navigator.serviceWorker.onmessage = resolve;
+ });
+
+ // Issue a fetch which the SW will respondWith() a never resolved promise.
+ // The fetch, however, will terminate when the SW is killed, so check that.
+ let hangingFetch = fetch("does_not_exist.html")
+ .then(() => { ok(false, "should have rejected "); },
+ () => { ok(true, "hung fetch terminates when worker dies"); });
+
+ yield waitForFetchActive;
+
+ let expectedMessage = expect_console_message(
+ // Termination error
+ "ServiceWorkerGraceTimeoutTermination",
+ [make_absolute_url("./")],
+ // The interception failure error generated by the RespondWithHandler
+ // destructor when it notices it didn't get a response before being
+ // destroyed. It logs via the intercepted channel nsIConsoleReportCollector
+ // that is eventually flushed to our document and its console.
+ "InterceptionFailedWithURL",
+ [make_absolute_url("does_not_exist.html")]
+ );
+
+ // Zero out the grace timeout too so the worker will get terminated after two
+ // zero-length timer firings. Note that we need to do something to get the
+ // SW to renew its keepalive for this to actually cause the timers to be
+ // rescheduled...
+ yield SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.idle_extended_timeout", 0]]});
+ // ...which we do by postMessaging it.
+ navigator.serviceWorker.controller.postMessage("doomity doom doom");
+
+ // Now wait for signs that the worker was terminated by the fetch failing.
+ yield hangingFetch;
+
+ // The worker should now be dead and the error logged, wait/assert.
+ yield wait_for_expected_message(expectedMessage);
+
+ // roll back all of our test case specific preferences and otherwise cleanup
+ yield SpecialPowers.popPrefEnv();
+ yield SpecialPowers.popPrefEnv();
+ yield registration.unregister();
+});
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/test_workerUnregister.html b/dom/workers/test/serviceworkers/test_workerUnregister.html
new file mode 100644
index 000000000..947861c17
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_workerUnregister.html
@@ -0,0 +1,82 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 982728 - Test ServiceWorkerGlobalScope.unregister</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="container"></div>
+<script class="testbody" type="text/javascript">
+
+ function simpleRegister() {
+ return navigator.serviceWorker.register("worker_unregister.js", { scope: "unregister/" }).then(function(swr) {
+ if (swr.installing) {
+ return new Promise(function(resolve, reject) {
+ swr.installing.onstatechange = function(e) {
+ if (swr.waiting) {
+ swr.waiting.onstatechange = function(e) {
+ if (swr.active) {
+ resolve();
+ } else if (swr.waiting && swr.waiting.state == "redundant") {
+ reject("Should not go into redundant");
+ }
+ }
+ } else {
+ if (swr.active) {
+ resolve();
+ } else {
+ reject("No waiting and no active!");
+ }
+ }
+ }
+ });
+ } else {
+ return Promise.reject("Installing should be non-null");
+ }
+ });
+ }
+
+ function waitForMessages(sw) {
+ var p = new Promise(function(resolve, reject) {
+ window.onmessage = function(e) {
+ if (e.data === "DONE") {
+ ok(true, "The worker has unregistered itself");
+ } else if (e.data === "ERROR") {
+ ok(false, "The worker has unregistered itself");
+ } else if (e.data === "FINISH") {
+ resolve();
+ }
+ }
+ });
+
+ var frame = document.createElement("iframe");
+ frame.setAttribute("src", "unregister/unregister.html");
+ document.body.appendChild(frame);
+
+ return p;
+ }
+
+ function runTest() {
+ simpleRegister().then(waitForMessages).catch(function(e) {
+ ok(false, "Something went wrong.");
+ }).then(function() {
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_workerUpdate.html b/dom/workers/test/serviceworkers/test_workerUpdate.html
new file mode 100644
index 000000000..5621d6cb8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_workerUpdate.html
@@ -0,0 +1,62 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1065366 - Test ServiceWorkerGlobalScope.update</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="container"></div>
+<script class="testbody" type="text/javascript">
+
+ function simpleRegister() {
+ return navigator.serviceWorker.register("worker_update.js", { scope: "workerUpdate/" });
+ }
+
+ var registration;
+ function waitForMessages(sw) {
+ registration = sw;
+ var p = new Promise(function(resolve, reject) {
+ window.onmessage = function(e) {
+ if (e.data === "FINISH") {
+ ok(true, "The worker has updated itself");
+ resolve();
+ } else if (e.data === "FAIL") {
+ ok(false, "The worker failed to update itself");
+ resolve();
+ }
+ }
+ });
+
+ var frame = document.createElement("iframe");
+ frame.setAttribute("src", "workerUpdate/update.html");
+ document.body.appendChild(frame);
+
+ return p;
+ }
+
+ function runTest() {
+ simpleRegister().then(waitForMessages).catch(function(e) {
+ ok(false, "Something went wrong.");
+ }).then(function() {
+ return registration.unregister();
+ }).then(function() {
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_workerupdatefoundevent.html b/dom/workers/test/serviceworkers/test_workerupdatefoundevent.html
new file mode 100644
index 000000000..3361eba08
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_workerupdatefoundevent.html
@@ -0,0 +1,85 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1131327 - Test ServiceWorkerRegistration.onupdatefound on ServiceWorker</title>
+ <script type="text/javascript" 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"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+ var registration;
+ var promise;
+
+ function start() {
+ return navigator.serviceWorker.register("worker_updatefoundevent.js",
+ { scope: "./updatefoundevent.html" })
+ .then((swr) => registration = swr);
+ }
+
+ function startWaitForUpdateFound() {
+ registration.onupdatefound = function(e) {
+ }
+
+ promise = new Promise(function(resolve, reject) {
+ window.onmessage = function(e) {
+
+ if (e.data == "finish") {
+ ok(true, "Received updatefound");
+ resolve();
+ }
+ }
+ });
+
+ content = document.getElementById("content");
+ iframe = document.createElement("iframe");
+ content.appendChild(iframe);
+ iframe.setAttribute("src", "./updatefoundevent.html");
+
+ return Promise.resolve();
+ }
+
+ function registerNext() {
+ return navigator.serviceWorker.register("worker_updatefoundevent2.js",
+ { scope: "./updatefoundevent.html" });
+ }
+
+ function waitForUpdateFound() {
+ return promise;
+ }
+
+ function unregister() {
+ window.onmessage = null;
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ });
+ }
+
+ function runTest() {
+ start()
+ .then(startWaitForUpdateFound)
+ .then(registerNext)
+ .then(waitForUpdateFound)
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/test_xslt.html b/dom/workers/test/serviceworkers/test_xslt.html
new file mode 100644
index 000000000..44270753b
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_xslt.html
@@ -0,0 +1,128 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1182113 - Test service worker XSLT interception</title>
+ <script type="text/javascript" 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"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+ var registration;
+ var worker;
+
+ function start() {
+ return navigator.serviceWorker.register("xslt_worker.js",
+ { scope: "./" })
+ .then((swr) => {
+ registration = swr;
+
+ // Ensure the registration is active before continuing
+ var worker = registration.installing;
+ return new Promise((resolve) => {
+ if (worker.state === 'activated') {
+ resolve();
+ return;
+ }
+ worker.addEventListener('statechange', () => {
+ if (worker.state === 'activated') {
+ resolve();
+ }
+ });
+ });
+ });
+ }
+
+ function unregister() {
+ return registration.unregister().then(function(result) {
+ ok(result, "Unregister should return true.");
+ }, function(e) {
+ dump("Unregistering the SW failed with " + e + "\n");
+ });
+ }
+
+ function getXmlString(xmlObject) {
+ serializer = new XMLSerializer();
+ return serializer.serializeToString(iframe.contentDocument);
+ }
+
+ function synthetic() {
+ content = document.getElementById("content");
+ ok(content, "parent exists.");
+
+ iframe = document.createElement("iframe");
+ content.appendChild(iframe);
+
+ iframe.setAttribute('src', "xslt/test.xml");
+
+ var p = new Promise(function(res, rej) {
+ iframe.onload = function(e) {
+ dump("Set request mode\n");
+ registration.active.postMessage("synthetic");
+ xmlString = getXmlString(iframe.contentDocument);
+ ok(!xmlString.includes("Error"), "Load synthetic cross origin XSLT should be allowed");
+ res();
+ };
+ });
+
+ return p;
+ }
+
+ function cors() {
+ var p = new Promise(function(res, rej) {
+ iframe.onload = function(e) {
+ xmlString = getXmlString(iframe.contentDocument);
+ ok(!xmlString.includes("Error"), "Load CORS cross origin XSLT should be allowed");
+ res();
+ };
+ });
+
+ registration.active.postMessage("cors");
+ iframe.setAttribute('src', "xslt/test.xml");
+
+ return p;
+ }
+
+ function opaque() {
+ var p = new Promise(function(res, rej) {
+ iframe.onload = function(e) {
+ xmlString = getXmlString(iframe.contentDocument);
+ ok(xmlString.includes("Error"), "Load opaque cross origin XSLT should not be allowed");
+ res();
+ };
+ });
+
+ registration.active.postMessage("opaque");
+ iframe.setAttribute('src', "xslt/test.xml");
+
+ return p;
+ }
+
+ function runTest() {
+ start()
+ .then(synthetic)
+ .then(opaque)
+ .then(cors)
+ .then(unregister)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ }).then(SimpleTest.finish);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/thirdparty/iframe1.html b/dom/workers/test/serviceworkers/thirdparty/iframe1.html
new file mode 100644
index 000000000..43fe8c572
--- /dev/null
+++ b/dom/workers/test/serviceworkers/thirdparty/iframe1.html
@@ -0,0 +1,30 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+ <title>SW third party iframe test</title>
+
+ <script type="text/javascript;version=1.7">
+ function messageListener(event) {
+ let message = eval(event.data);
+
+ dump("got message " + JSON.stringify(message) + "\n");
+ if (message.source == "parent") {
+ document.getElementById("iframe2").src = message.href;
+ }
+ else if (message.source == "iframe") {
+ parent.postMessage(event.data, "*");
+ }
+ }
+ </script>
+
+</head>
+
+<body onload="window.addEventListener('message', messageListener, false);">
+ <iframe id="iframe2"></iframe>
+</body>
+
+</html>
diff --git a/dom/workers/test/serviceworkers/thirdparty/iframe2.html b/dom/workers/test/serviceworkers/thirdparty/iframe2.html
new file mode 100644
index 000000000..fac6a9395
--- /dev/null
+++ b/dom/workers/test/serviceworkers/thirdparty/iframe2.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script>
+ window.parent.postMessage({
+ source: "iframe",
+ status: "networkresponse"
+ }, "*");
+</script>
diff --git a/dom/workers/test/serviceworkers/thirdparty/register.html b/dom/workers/test/serviceworkers/thirdparty/register.html
new file mode 100644
index 000000000..59b8c5c41
--- /dev/null
+++ b/dom/workers/test/serviceworkers/thirdparty/register.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<script>
+ function ok(v, msg) {
+ window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+ }
+
+ var isDone = false;
+ function done(reg) {
+ if (!isDone) {
+ ok(reg.waiting || reg.active,
+ "Either active or waiting worker should be available.");
+ window.parent.postMessage({status: "registrationdone"}, "*");
+ isDone = true;
+ }
+ }
+
+ navigator.serviceWorker.register("sw.js", {scope: "."})
+ .then(function(registration) {
+ if (registration.installing) {
+ registration.installing.onstatechange = function(e) {
+ done(registration);
+ };
+ } else {
+ done(registration);
+ }
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/thirdparty/sw.js b/dom/workers/test/serviceworkers/thirdparty/sw.js
new file mode 100644
index 000000000..ca45698c8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/thirdparty/sw.js
@@ -0,0 +1,14 @@
+self.addEventListener("fetch", function(event) {
+ dump("fetch " + event.request.url + "\n");
+ if (event.request.url.indexOf("iframe2.html") >= 0) {
+ var body =
+ "<script>" +
+ "window.parent.postMessage({" +
+ "source: 'iframe', status: 'swresponse'" +
+ "}, '*');" +
+ "</script>";
+ event.respondWith(new Response(body, {
+ headers: {'Content-Type': 'text/html'}
+ }));
+ }
+});
diff --git a/dom/workers/test/serviceworkers/thirdparty/unregister.html b/dom/workers/test/serviceworkers/thirdparty/unregister.html
new file mode 100644
index 000000000..2cb6ee0ce
--- /dev/null
+++ b/dom/workers/test/serviceworkers/thirdparty/unregister.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script>
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ if(!registration) {
+ return;
+ }
+ registration.unregister().then(() => {
+ window.parent.postMessage({status: "unregistrationdone"}, "*");
+ });
+ });
+</script>
diff --git a/dom/workers/test/serviceworkers/unregister/index.html b/dom/workers/test/serviceworkers/unregister/index.html
new file mode 100644
index 000000000..db23d2cb7
--- /dev/null
+++ b/dom/workers/test/serviceworkers/unregister/index.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>Bug 984048 - Test unregister</title>
+ <script type="text/javascript" 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">
+
+ if (!parent) {
+ info("unregister/index.html should not to be launched directly!");
+ }
+
+ parent.postMessage({ controlled: !!navigator.serviceWorker.controller }, "*");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/serviceworkers/unregister/unregister.html b/dom/workers/test/serviceworkers/unregister/unregister.html
new file mode 100644
index 000000000..6fda82026
--- /dev/null
+++ b/dom/workers/test/serviceworkers/unregister/unregister.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 worker::unregister</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script type="text/javascript">
+
+ navigator.serviceWorker.onmessage = function(e) { parent.postMessage(e.data, "*"); }
+ navigator.serviceWorker.controller.postMessage("GO");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/unresolved_fetch_worker.js b/dom/workers/test/serviceworkers/unresolved_fetch_worker.js
new file mode 100644
index 000000000..71fbad781
--- /dev/null
+++ b/dom/workers/test/serviceworkers/unresolved_fetch_worker.js
@@ -0,0 +1,19 @@
+var keepPromiseAlive;
+onfetch = function(event) {
+ event.waitUntil(
+ clients.matchAll()
+ .then(clients => {
+ clients.forEach(client => {
+ client.postMessage("continue");
+ });
+ })
+ );
+
+ // Never resolve, and keep it alive on our global so it can't get GC'ed and
+ // make this test weird and intermittent.
+ event.respondWith((keepPromiseAlive = new Promise(function(res, rej) {})));
+}
+
+onactivate = function(event) {
+ event.waitUntil(clients.claim());
+}
diff --git a/dom/workers/test/serviceworkers/updatefoundevent.html b/dom/workers/test/serviceworkers/updatefoundevent.html
new file mode 100644
index 000000000..78088c7cd
--- /dev/null
+++ b/dom/workers/test/serviceworkers/updatefoundevent.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Bug 1131327 - Test ServiceWorkerRegistration.onupdatefound on ServiceWorker</title>
+</head>
+<body>
+<script>
+ navigator.serviceWorker.onmessage = function(e) {
+ dump("NSM iframe got message " + e.data + "\n");
+ window.parent.postMessage(e.data, "*");
+ };
+</script>
+</body>
diff --git a/dom/workers/test/serviceworkers/worker.js b/dom/workers/test/serviceworkers/worker.js
new file mode 100644
index 000000000..2aba167d1
--- /dev/null
+++ b/dom/workers/test/serviceworkers/worker.js
@@ -0,0 +1 @@
+// empty worker, always succeed!
diff --git a/dom/workers/test/serviceworkers/worker2.js b/dom/workers/test/serviceworkers/worker2.js
new file mode 100644
index 000000000..3072d0817
--- /dev/null
+++ b/dom/workers/test/serviceworkers/worker2.js
@@ -0,0 +1 @@
+// worker2.js
diff --git a/dom/workers/test/serviceworkers/worker3.js b/dom/workers/test/serviceworkers/worker3.js
new file mode 100644
index 000000000..449fc2f97
--- /dev/null
+++ b/dom/workers/test/serviceworkers/worker3.js
@@ -0,0 +1 @@
+// worker3.js
diff --git a/dom/workers/test/serviceworkers/workerUpdate/update.html b/dom/workers/test/serviceworkers/workerUpdate/update.html
new file mode 100644
index 000000000..8f984ccc4
--- /dev/null
+++ b/dom/workers/test/serviceworkers/workerUpdate/update.html
@@ -0,0 +1,24 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test worker::update</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script type="text/javascript">
+
+ navigator.serviceWorker.onmessage = function(e) { parent.postMessage(e.data, "*"); }
+ navigator.serviceWorker.ready.then(function() {
+ navigator.serviceWorker.controller.postMessage("GO");
+ });
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/serviceworkers/worker_unregister.js b/dom/workers/test/serviceworkers/worker_unregister.js
new file mode 100644
index 000000000..7a3e764f4
--- /dev/null
+++ b/dom/workers/test/serviceworkers/worker_unregister.js
@@ -0,0 +1,16 @@
+onmessage = function(e) {
+ clients.matchAll().then(function(c) {
+ if (c.length === 0) {
+ // We cannot proceed.
+ return;
+ }
+
+ registration.unregister().then(function() {
+ c[0].postMessage('DONE');
+ }, function() {
+ c[0].postMessage('ERROR');
+ }).then(function() {
+ c[0].postMessage('FINISH');
+ });
+ });
+}
diff --git a/dom/workers/test/serviceworkers/worker_update.js b/dom/workers/test/serviceworkers/worker_update.js
new file mode 100644
index 000000000..9f3e55b18
--- /dev/null
+++ b/dom/workers/test/serviceworkers/worker_update.js
@@ -0,0 +1,19 @@
+// For now this test only calls update to verify that our registration
+// job queueing works properly when called from the worker thread. We should
+// test actual update scenarios with a SJS test.
+onmessage = function(e) {
+ self.registration.update().then(function(v) {
+ return v === undefined ? 'FINISH' : 'FAIL';
+ }).catch(function(e) {
+ return 'FAIL';
+ }).then(function(result) {
+ clients.matchAll().then(function(c) {
+ if (c.length == 0) {
+ dump("!!!!!!!!!!! WORKER HAS NO CLIENTS TO FINISH TEST !!!!!!!!!!!!\n");
+ return;
+ }
+
+ c[0].postMessage(result);
+ });
+ });
+}
diff --git a/dom/workers/test/serviceworkers/worker_updatefoundevent.js b/dom/workers/test/serviceworkers/worker_updatefoundevent.js
new file mode 100644
index 000000000..a297bf455
--- /dev/null
+++ b/dom/workers/test/serviceworkers/worker_updatefoundevent.js
@@ -0,0 +1,23 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+onactivate = function(e) {
+ e.waitUntil(new Promise(function(resolve, reject) {
+ registration.onupdatefound = function(e) {
+ clients.matchAll().then(function(clients) {
+ if (!clients.length) {
+ reject("No clients found");
+ }
+
+ if (registration.scope.match(/updatefoundevent\.html$/)) {
+ clients[0].postMessage("finish");
+ resolve();
+ } else {
+ dump("Scope did not match");
+ }
+ }, reject);
+ }
+ }));
+}
diff --git a/dom/workers/test/serviceworkers/worker_updatefoundevent2.js b/dom/workers/test/serviceworkers/worker_updatefoundevent2.js
new file mode 100644
index 000000000..da4c592aa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/worker_updatefoundevent2.js
@@ -0,0 +1 @@
+// Not useful.
diff --git a/dom/workers/test/serviceworkers/xslt/test.xml b/dom/workers/test/serviceworkers/xslt/test.xml
new file mode 100644
index 000000000..83c777633
--- /dev/null
+++ b/dom/workers/test/serviceworkers/xslt/test.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="test.xsl"?>
+<result>
+ <Title>Example</Title>
+ <Error>Error</Error>
+</result>
diff --git a/dom/workers/test/serviceworkers/xslt/xslt.sjs b/dom/workers/test/serviceworkers/xslt/xslt.sjs
new file mode 100644
index 000000000..db681ab50
--- /dev/null
+++ b/dom/workers/test/serviceworkers/xslt/xslt.sjs
@@ -0,0 +1,12 @@
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "application/xslt+xml", false);
+ response.setHeader("Access-Control-Allow-Origin", "*");
+
+ var body = request.queryString;
+ if (!body) {
+ response.setStatusLine(null, 500, "Invalid querystring");
+ return;
+ }
+
+ response.write(unescape(body));
+}
diff --git a/dom/workers/test/serviceworkers/xslt_worker.js b/dom/workers/test/serviceworkers/xslt_worker.js
new file mode 100644
index 000000000..bf9bdbc56
--- /dev/null
+++ b/dom/workers/test/serviceworkers/xslt_worker.js
@@ -0,0 +1,52 @@
+var testType = 'synthetic';
+
+var xslt = "<?xml version=\"1.0\"?> " +
+ "<xsl:stylesheet version=\"1.0\"" +
+ " xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">" +
+ " <xsl:template match=\"node()|@*\">" +
+ " <xsl:copy>" +
+ " <xsl:apply-templates select=\"node()|@*\"/>" +
+ " </xsl:copy>" +
+ " </xsl:template>" +
+ " <xsl:template match=\"Error\"/>" +
+ "</xsl:stylesheet>";
+
+onfetch = function(event) {
+ if (event.request.url.includes('test.xsl')) {
+ if (testType == 'synthetic') {
+ if (event.request.mode != 'cors') {
+ event.respondWith(Response.error());
+ return;
+ }
+
+ event.respondWith(Promise.resolve(
+ new Response(xslt, { headers: {'Content-Type': 'application/xslt+xml'}})
+ ));
+ }
+ else if (testType == 'cors') {
+ if (event.request.mode != 'cors') {
+ event.respondWith(Response.error());
+ return;
+ }
+
+ var url = "http://example.com/tests/dom/workers/test/serviceworkers/xslt/xslt.sjs?" + escape(xslt);
+ event.respondWith(fetch(url, { mode: 'cors' }));
+ }
+ else if (testType == 'opaque') {
+ if (event.request.mode != 'cors') {
+ event.respondWith(Response.error());
+ return;
+ }
+
+ var url = "http://example.com/tests/dom/workers/test/serviceworkers/xslt/xslt.sjs?" + escape(xslt);
+ event.respondWith(fetch(url, { mode: 'no-cors' }));
+ }
+ else {
+ event.respondWith(Response.error());
+ }
+ }
+};
+
+onmessage = function(event) {
+ testType = event.data;
+};
diff --git a/dom/workers/test/sharedWorker_console.js b/dom/workers/test/sharedWorker_console.js
new file mode 100644
index 000000000..932235eb7
--- /dev/null
+++ b/dom/workers/test/sharedWorker_console.js
@@ -0,0 +1,11 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+onconnect = function(evt) {
+ console.profile("Hello profiling from a SharedWorker!");
+ console.log("Hello world from a SharedWorker!");
+ evt.ports[0].postMessage('ok!');
+}
diff --git a/dom/workers/test/sharedWorker_lifetime.js b/dom/workers/test/sharedWorker_lifetime.js
new file mode 100644
index 000000000..3d9a837bb
--- /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 000000000..64672e6ab
--- /dev/null
+++ b/dom/workers/test/sharedWorker_ports.js
@@ -0,0 +1,24 @@
+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 000000000..9d7ec886f
--- /dev/null
+++ b/dom/workers/test/sharedWorker_privateBrowsing.js
@@ -0,0 +1,5 @@
+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 000000000..5e8e93392
--- /dev/null
+++ b/dom/workers/test/sharedWorker_sharedWorker.js
@@ -0,0 +1,93 @@
+/**
+ * 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);
+ }
+
+ // The expression closures should trigger a warning in debug builds, but NOT
+ // fire error events at us. If we ever actually remove expression closures
+ // (in bug 1083458), we'll need something else to test this case.
+ (function() "Expected console warning: expression closures are deprecated");
+
+ event.ports[0].onmessage = function(event) {
+ if (!(event instanceof MessageEvent)) {
+ throw new Error("'message' event is not a MessageEvent!");
+ }
+ if (!("ports" in event)) {
+ throw new Error("'message' event doesn't have a 'ports' property!");
+ }
+ if (event.ports === null) {
+ throw new Error("'message' event has a null 'ports' property!");
+ }
+ event.target.postMessage(event.data);
+ throw new Error(event.data);
+ };
+};
diff --git a/dom/workers/test/simpleThread_worker.js b/dom/workers/test/simpleThread_worker.js
new file mode 100644
index 000000000..543d8b3dd
--- /dev/null
+++ b/dom/workers/test/simpleThread_worker.js
@@ -0,0 +1,53 @@
+/**
+ * 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, false);
+ 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/suspend_iframe.html b/dom/workers/test/suspend_iframe.html
new file mode 100644
index 000000000..86d9d6bc1
--- /dev/null
+++ b/dom/workers/test/suspend_iframe.html
@@ -0,0 +1,47 @@
+<!--
+ 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 output = document.getElementById("output");
+
+ var worker;
+
+ function terminateWorker() {
+ if (worker) {
+ worker.postMessage("stop");
+ worker = null;
+ }
+ }
+
+ function startWorker(messageCallback, errorCallback) {
+ 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);
+ };
+ }
+
+</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 000000000..43eb24a7a
--- /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 000000000..f1a49e032
--- /dev/null
+++ b/dom/workers/test/terminate_worker.js
@@ -0,0 +1,9 @@
+/**
+ * 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 000000000..e2e83a35e
--- /dev/null
+++ b/dom/workers/test/test_404.html
@@ -0,0 +1,41 @@
+<!--
+ 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 type="text/javascript" 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);
+ is(event.message, 'NetworkError: Failed to load worker script at "nonexistent_worker.js"');
+ event.preventDefault();
+ SimpleTest.finish();
+ };
+
+ worker.postMessage("dummy");
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/workers/test/test_WorkerDebugger.initialize.xul b/dom/workers/test/test_WorkerDebugger.initialize.xul
new file mode 100644
index 000000000..9e40bb78c
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger.initialize.xul
@@ -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="Test for WorkerDebugger.initialize"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ 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() {
+ Task.spawn(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);
+ yield promise;
+
+ info("Check that the debuggers are initialized before the workers " +
+ "start running.");
+ yield 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.xul b/dom/workers/test/test_WorkerDebugger.postMessage.xul
new file mode 100644
index 000000000..7affbed21
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger.postMessage.xul
@@ -0,0 +1,61 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ 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() {
+ Task.spawn(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] = yield 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");
+ yield 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");
+ yield 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.xul b/dom/workers/test/test_WorkerDebugger.xul
new file mode 100644
index 000000000..f3397bd54
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger.xul
@@ -0,0 +1,122 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebugger"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ const WORKER_URL = "WorkerDebugger_worker.js";
+ const CHILD_WORKER_URL = "WorkerDebugger_childWorker.js";
+ const SHARED_WORKER_URL = "WorkerDebugger_sharedWorker.js";
+
+ function test() {
+ Task.spawn(function* () {
+ SimpleTest.waitForExplicitFinish();
+
+ 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] = yield 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, null,
+ "Non-top-level worker debugger should not 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();
+ yield 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 = yield promise;
+
+ info("Check that the shared worker debugger has the correct " +
+ "properties.");
+ is(sharedDbg.isChrome, false,
+ "Shared worker debugger should be content.");
+ is(sharedDbg.parent, null,
+ "Shared worker debugger should not have parent.");
+ is(sharedDbg.type, Ci.nsIWorkerDebugger.TYPE_SHARED,
+ "Shared worker debugger should be shared.");
+ is(sharedDbg.window, null,
+ "Shared worker debugger should not have window.");
+
+ info("Create a shared worker with the same URL and check that its " +
+ "debugger is not registered again.");
+ let listener = {
+ onRegistered: function () {
+ ok(false,
+ "Shared worker debugger should not be registered again.");
+ },
+ };
+ wdm.addListener(listener);
+ worker = new SharedWorker(SHARED_WORKER_URL);
+
+ info("Send a message to the shared worker to tell it to close " +
+ "itself, and wait for its debugger to be closed.");
+ promise = waitForMultiple([
+ waitForUnregister(SHARED_WORKER_URL),
+ waitForDebuggerClose(sharedDbg)
+ ]);
+ worker.port.start();
+ worker.port.postMessage("close");
+ yield promise;
+
+ wdm.removeListener(listener);
+ SimpleTest.finish();
+ });
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_WorkerDebuggerGlobalScope.createSandbox.xul b/dom/workers/test/test_WorkerDebuggerGlobalScope.createSandbox.xul
new file mode 100644
index 000000000..0440c482b
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebuggerGlobalScope.createSandbox.xul
@@ -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.createSandbox"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ 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() {
+ Task.spawn(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 = yield 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");
+ yield 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.xul b/dom/workers/test/test_WorkerDebuggerGlobalScope.enterEventLoop.xul
new file mode 100644
index 000000000..081395308
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebuggerGlobalScope.enterEventLoop.xul
@@ -0,0 +1,126 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ 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() {
+ Task.spawn(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] = yield 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");
+ yield 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");
+ yield 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");
+ yield 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");
+ yield 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");
+ yield 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");
+ yield 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");
+ yield 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");
+ yield 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.xul b/dom/workers/test/test_WorkerDebuggerGlobalScope.reportError.xul
new file mode 100644
index 000000000..88b078674
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebuggerGlobalScope.reportError.xul
@@ -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.reportError"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ 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() {
+ Task.spawn(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] = yield 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 = yield 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 = yield 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 = yield 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 = yield 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.xul b/dom/workers/test/test_WorkerDebuggerGlobalScope.setImmediate.xul
new file mode 100644
index 000000000..3ecac681b
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebuggerGlobalScope.setImmediate.xul
@@ -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="Test for WorkerDebuggerGlobalScope.setImmediate"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ 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() {
+ Task.spawn(function* () {
+ SimpleTest.waitForExplicitFinish();
+
+ let promise = waitForRegister(WORKER_URL, DEBUGGER_URL);
+ let worker = new Worker(WORKER_URL);
+ let dbg = yield 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");
+ yield 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.xul b/dom/workers/test/test_WorkerDebuggerManager.xul
new file mode 100644
index 000000000..6807226bd
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebuggerManager.xul
@@ -0,0 +1,106 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebuggerManager"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ const WORKER_URL = "WorkerDebuggerManager_worker.js";
+ const CHILD_WORKER_URL = "WorkerDebuggerManager_childWorker.js";
+
+ function test() {
+ Task.spawn(function* () {
+ SimpleTest.waitForExplicitFinish();
+
+ info("Check that worker debuggers are not enumerated before they are " +
+ "registered.");
+ ok(!findDebugger(WORKER_URL),
+ "Worker debugger should not be enumerated before it is registered.");
+ ok(!findDebugger(CHILD_WORKER_URL),
+ "Child worker debugger should not be enumerated before it is " +
+ "registered.");
+
+ info("Create a worker that creates a child worker, and wait for " +
+ "their debuggers to be registered.");
+ let promise = waitForMultiple([
+ waitForRegister(WORKER_URL),
+ waitForRegister(CHILD_WORKER_URL)
+ ]);
+ let worker = new Worker(WORKER_URL);
+ let [dbg, childDbg] = yield 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();
+ yield promise;
+
+ info("Check that worker debuggers are not enumerated after they are " +
+ "unregistered.");
+ ok(!findDebugger(WORKER_URL),
+ "Worker debugger should not be enumerated after it is " +
+ "unregistered.");
+ ok(!findDebugger(CHILD_WORKER_URL),
+ "Child worker debugger should not be enumerated after it is " +
+ "unregistered.");
+
+ info("Check that worker debuggers are closed after they are " +
+ "unregistered.");
+ is(dbg.isClosed, true,
+ "Worker debugger should be closed after it is unregistered.");
+ is(childDbg.isClosed, true,
+ "Child worker debugger should be closed after it is unregistered.");
+
+ info("Check that property accesses on worker debuggers throws " +
+ "after they are closed.");
+ assertThrows(() => dbg.url,
+ "Property accesses on worker debugger should throw " +
+ "after it is closed.");
+ assertThrows(() => childDbg.url,
+ "Property accesses on child worker debugger should " +
+ "throw after it is closed.");
+
+ SimpleTest.finish();
+ });
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_WorkerDebugger_console.xul b/dom/workers/test/test_WorkerDebugger_console.xul
new file mode 100644
index 000000000..0852002ea
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger_console.xul
@@ -0,0 +1,97 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ 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() {
+ function consoleListener() {
+ SpecialPowers.addObserver(this, "console-api-log-event", false);
+ }
+
+ consoleListener.prototype = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "console-api-log-event") {
+ var obj = aSubject.wrappedJSObject;
+ if (obj.arguments[0] == "Hello from the debugger script!" &&
+ !consoleMessagesReceived) {
+ consoleMessagesReceived++;
+ ok(true, "Something has been received");
+ SpecialPowers.removeObserver(this, "console-api-log-event");
+ }
+ }
+ }
+ }
+
+ var cl = new consoleListener();
+
+ Task.spawn(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] = yield promise;
+
+ info("Send a request to the worker debugger. This should cause the " +
+ "the worker debugger to send a response.");
+ dbg.addListener({
+ onMessage: function(msg) {
+ try {
+ msg = JSON.parse(msg);
+ } catch(e) {
+ ok(false, "Something went wrong");
+ return;
+ }
+
+ if (msg.type == 'finish') {
+ ok(consoleMessagesReceived, "We received something via debugger console!");
+ dbg.removeListener(this);
+ SimpleTest.finish();
+ return;
+ }
+
+ if (msg.type == 'status') {
+ ok(msg.what, msg.msg);
+ return;
+ }
+
+ ok(false, "Something went wrong");
+ }
+ });
+
+ dbg.postMessage("do magic");
+ });
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_WorkerDebugger_frozen.xul b/dom/workers/test/test_WorkerDebugger_frozen.xul
new file mode 100644
index 000000000..6b22e7702
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger_frozen.xul
@@ -0,0 +1,90 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebugger with frozen workers"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ const CACHE_SUBFRAMES = "browser.sessionhistory.cache_subframes";
+ const MAX_TOTAL_VIEWERS = "browser.sessionhistory.max_total_viewers";
+
+ const IFRAME1_URL = "WorkerDebugger_frozen_iframe1.html";
+ const IFRAME2_URL = "WorkerDebugger_frozen_iframe2.html";
+
+ const WORKER1_URL = "WorkerDebugger_frozen_worker1.js";
+ const WORKER2_URL = "WorkerDebugger_frozen_worker2.js";
+
+ function test() {
+ Task.spawn(function* () {
+ SimpleTest.waitForExplicitFinish();
+
+ var oldMaxTotalViewers = SpecialPowers.getIntPref(MAX_TOTAL_VIEWERS);
+
+ SpecialPowers.setBoolPref(CACHE_SUBFRAMES, true);
+ SpecialPowers.setIntPref(MAX_TOTAL_VIEWERS, 10);
+
+ let iframe = $("iframe");
+
+ let promise = waitForMultiple([
+ waitForRegister(WORKER1_URL),
+ waitForWindowMessage(window, "ready"),
+ ]);
+ iframe.src = IFRAME1_URL;
+ let [dbg1] = yield 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"),
+ ]);
+ iframe.src = IFRAME2_URL;
+ let [,, dbg2] = yield 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)
+ ]);
+ iframe.contentWindow.history.back();
+ [,, dbg1] = yield 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");
+
+ SpecialPowers.clearUserPref(CACHE_SUBFRAMES);
+ SpecialPowers.setIntPref(MAX_TOTAL_VIEWERS, oldMaxTotalViewers);
+
+ 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>
+ <iframe id="iframe"></iframe>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/workers/test/test_WorkerDebugger_promise.xul b/dom/workers/test/test_WorkerDebugger_promise.xul
new file mode 100644
index 000000000..24ed07133
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger_promise.xul
@@ -0,0 +1,70 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ 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() {
+ Task.spawn(function* () {
+ SimpleTest.waitForExplicitFinish();
+
+ let promise = waitForRegister(WORKER_URL, DEBUGGER_URL);
+ let worker = new Worker(WORKER_URL);
+ let dbg = yield promise;
+
+ info("Send a request to the worker. This should cause the worker " +
+ "to send a response.");
+ promise = waitForWorkerMessage(worker, "resolved");
+ worker.postMessage("resolve");
+ yield promise;
+
+ info("Send a request to the debugger. This should cause the debugger " +
+ "to send a response.");
+ promise = waitForDebuggerMessage(dbg, "resolved");
+ dbg.postMessage("resolve");
+ yield 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");
+ yield 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");
+ yield 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.xul b/dom/workers/test/test_WorkerDebugger_suspended.xul
new file mode 100644
index 000000000..0ed8bb71a
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger_suspended.xul
@@ -0,0 +1,75 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ 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() {
+ Task.spawn(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 = yield 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");
+ yield 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.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ windowUtils.suspendTimeouts();
+ function onmessage() {
+ ok(false, "The worker should not send a response.");
+ };
+ worker.addEventListener("message", onmessage);
+ promise = waitForDebuggerMessage(dbg, "debugger");
+ worker.postMessage("ping");
+ yield 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();
+ yield 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 000000000..99419174c
--- /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 type="text/javascript" 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 000000000..e8cfaf19d
--- /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 type="text/javascript" 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 000000000..6e2a83f53
--- /dev/null
+++ b/dom/workers/test/test_blobWorkers.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 type="text/javascript" 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 000000000..3db6d2580
--- /dev/null
+++ b/dom/workers/test/test_bug1002702.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 1002702</title>
+ <script type="text/javascript" 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 000000000..f746f35e6
--- /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 type="application/javascript" 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 000000000..59f5a6185
--- /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 type="application/javascript" 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 000000000..b6db2aeb4
--- /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 type="application/javascript" 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 000000000..17b9d490f
--- /dev/null
+++ b/dom/workers/test/test_bug1036484.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: bug 1036484
+-->
+<head>
+ <title>Test for DOM Worker Threads: bug 1036484</title>
+ <script type="text/javascript" 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);
+ ok(event.message.startsWith("NetworkError: Failed to load worker script"))
+ 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 000000000..758bf996e
--- /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 type="text/javascript" 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 000000000..31061a2b1
--- /dev/null
+++ b/dom/workers/test/test_bug1062920.html
@@ -0,0 +1,70 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for navigator property override</title>
+ <script type="text/javascript" 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();
+ }, false);
+
+ document.getElementById('content').appendChild(ifr);
+ };
+ }
+
+ function replaceAndCheckValues() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["general.appname.override", "appName overridden"],
+ ["general.appversion.override", "appVersion overridden"],
+ ["general.platform.override", "platform overridden"],
+ ["general.useragent.override", "userAgent overridden"]
+ ]}, checkValues);
+ }
+
+ var tests = [
+ checkValues,
+ replaceAndCheckValues
+ ];
+
+ function runTests() {
+ if (tests.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTests();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_bug1062920.xul b/dom/workers/test/test_bug1062920.xul
new file mode 100644
index 000000000..635c1b9f9
--- /dev/null
+++ b/dom/workers/test/test_bug1062920.xul
@@ -0,0 +1,69 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+
+ function checkValues() {
+ var worker = new Worker("bug1062920_worker.js");
+
+ worker.onmessage = function(event) {
+ is(event.data.appCodeName, navigator.appCodeName, "appCodeName should match");
+ is(event.data.appName, navigator.appName, "appName should match");
+ isnot(event.data.appName, "appName overridden", "appName is not overridden");
+ is(event.data.appVersion, navigator.appVersion, "appVersion should match");
+ isnot(event.data.appVersion, "appVersion overridden", "appVersion is not overridden");
+ is(event.data.platform, navigator.platform, "platform should match");
+ isnot(event.data.platform, "platform overridden", "platform is not overridden");
+ is(event.data.userAgent, navigator.userAgent, "userAgent should match");
+ is(event.data.product, navigator.product, "product should match");
+ runTests();
+ };
+ }
+
+ function replaceAndCheckValues() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["general.appname.override", "appName overridden"],
+ ["general.appversion.override", "appVersion overridden"],
+ ["general.platform.override", "platform overridden"],
+ ["general.useragent.override", "userAgent overridden"]
+ ]}, checkValues);
+ }
+
+ var tests = [
+ replaceAndCheckValues,
+ checkValues
+ ];
+
+ function runTests() {
+ if (tests.length == 0) {
+ 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 000000000..7c32b8ed3
--- /dev/null
+++ b/dom/workers/test/test_bug1063538.html
@@ -0,0 +1,49 @@
+<!--
+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 type="application/javascript" 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.pushPrefEnv({"set": [["network.jar.block-remote-files", false]]}, 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 000000000..9f83fd007
--- /dev/null
+++ b/dom/workers/test/test_bug1104064.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 bug 1104064</title>
+ <script type="text/javascript" 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 000000000..30ca9b0ae
--- /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 type="text/javascript" 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 000000000..8c54813ef
--- /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 type="text/javascript" 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 000000000..a91902d26
--- /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 type="application/javascript" 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 000000000..ea396b32e
--- /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 type="application/javascript" 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 000000000..c23587318
--- /dev/null
+++ b/dom/workers/test/test_bug1317725.html
@@ -0,0 +1,62 @@
+<!--
+ 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 type="text/javascript" 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 type="text/js-worker" id="worker-src">
+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/");
+}
+</script>
+
+<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 blob = new Blob([ document.getElementById("worker-src").textContent ],
+ { type: "text/javascript" });
+ var worker = new Worker(URL.createObjectURL(blob));
+ 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_bug949946.html b/dom/workers/test/test_bug949946.html
new file mode 100644
index 000000000..547bdbda4
--- /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 type="text/javascript" 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 000000000..49e84c659
--- /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 type="text/javascript" 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 000000000..892b42ef9
--- /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 type="text/javascript" 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 000000000..644593949
--- /dev/null
+++ b/dom/workers/test/test_chromeWorker.html
@@ -0,0 +1,27 @@
+<!--
+ 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 type="text/javascript" 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.xul b/dom/workers/test/test_chromeWorker.xul
new file mode 100644
index 000000000..35df768be
--- /dev/null
+++ b/dom/workers/test/test_chromeWorker.xul
@@ -0,0 +1,45 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ 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 = new ChromeWorker("chromeWorker_worker.js");
+ worker.onmessage = function(event) {
+ is(event.data, "Done!", "Wrong message!");
+ finish();
+ }
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ worker.terminate();
+ finish();
+ }
+ 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.xul b/dom/workers/test/test_chromeWorkerJSM.xul
new file mode 100644
index 000000000..01a88bc2a
--- /dev/null
+++ b/dom/workers/test/test_chromeWorkerJSM.xul
@@ -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="DOM Worker Threads Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ 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();
+ }
+
+ Components.utils.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 000000000..d9bc6a9f9
--- /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 type="text/javascript" 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_console.html b/dom/workers/test/test_console.html
new file mode 100644
index 000000000..af82a7de0
--- /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 type="text/javascript" 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 000000000..e765500fa
--- /dev/null
+++ b/dom/workers/test/test_consoleAndBlobs.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 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">
+
+ function consoleListener() {
+ SpecialPowers.addObserver(this, "console-api-log-event", false);
+ }
+
+ var order = 0;
+ consoleListener.prototype = {
+ observe: function(aSubject, aTopic, aData) {
+ ok(true, "Something has been received");
+ is(aTopic, "console-api-log-event");
+
+ var obj = aSubject.wrappedJSObject;
+ if (obj.arguments[0] && obj.arguments[0].msg === 'consoleAndBlobs') {
+ SpecialPowers.removeObserver(this, "console-api-log-event");
+ 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 000000000..3886b679d
--- /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 type="text/javascript" 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_consoleSharedWorkers.html b/dom/workers/test/test_consoleSharedWorkers.html
new file mode 100644
index 000000000..74e1ec742
--- /dev/null
+++ b/dom/workers/test/test_consoleSharedWorkers.html
@@ -0,0 +1,56 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for console API in SharedWorker</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">
+
+ function consoleListener() {
+ SpecialPowers.addObserver(this, "console-api-log-event", false);
+ SpecialPowers.addObserver(this, "console-api-profiler", false);
+ }
+
+ var order = 0;
+ consoleListener.prototype = {
+ observe: function(aSubject, aTopic, aData) {
+ 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.");
+
+ SpecialPowers.removeObserver(this, "console-api-profiler");
+ return;
+ }
+
+ if (aTopic == "console-api-log-event") {
+ var obj = aSubject.wrappedJSObject;
+ is (obj.arguments[0], "Hello world from a SharedWorker!", "A message from a SharedWorker \\o/");
+ is (obj.ID, "http://mochi.test:8888/tests/dom/workers/test/sharedWorker_console.js", "The ID is SharedWorker");
+ is (obj.innerID, "SharedWorker", "The ID is SharedWorker");
+ is (order++, 1, "Then a log message.");
+
+ SpecialPowers.removeObserver(this, "console-api-log-event");
+ SimpleTest.finish();
+ return;
+ }
+ }
+ }
+
+ var cl = new consoleListener();
+ new SharedWorker('sharedWorker_console.js');
+
+ SimpleTest.waitForExplicitFinish();
+
+ </script>
+ </body>
+</html>
diff --git a/dom/workers/test/test_contentWorker.html b/dom/workers/test/test_contentWorker.html
new file mode 100644
index 000000000..e745ca4a0
--- /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 type="text/javascript" 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 000000000..a24217f04
--- /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 type="text/javascript" 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 000000000..1c9321079
--- /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 000000000..dcbcd8c3a
--- /dev/null
+++ b/dom/workers/test/test_csp.js
@@ -0,0 +1,48 @@
+/**
+ * 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, "Error: 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: uri, level: 3 })
+ worker.onmessage = function(event) {
+ is(event.data, "Error: 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 000000000..1ff72424f
--- /dev/null
+++ b/dom/workers/test/test_dataURLWorker.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 type="text/javascript" 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_errorPropagation.html b/dom/workers/test/test_errorPropagation.html
new file mode 100644
index 000000000..7e1aafe25
--- /dev/null
+++ b/dom/workers/test/test_errorPropagation.html
@@ -0,0 +1,66 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <iframe id="workerFrame" src="errorPropagation_iframe.html"
+ onload="workerFrameLoaded();"></iframe>
+ <script type="text/javascript">
+ const workerCount = 3;
+
+ const errorMessage = "Error: expectedError";
+ const errorFilename = "http://mochi.test:8888/tests/dom/workers/test/" +
+ "errorPropagation_worker.js";
+ const errorLineno = 48;
+
+ var workerFrame;
+
+ scopeErrorCount = 0;
+ workerErrorCount = 0;
+ windowErrorCount = 0;
+
+ function messageListener(event) {
+ if (event.type == "scope") {
+ scopeErrorCount++;
+ }
+ else if (event.type == "worker") {
+ workerErrorCount++;
+ }
+ else if (event.type == "window") {
+ windowErrorCount++;
+ }
+ else {
+ ok(false, "Bad event type: " + event.type);
+ }
+
+ is(event.data.message, errorMessage, "Correct message event.message");
+ is(event.data.filename, errorFilename,
+ "Correct message event.filename");
+ is(event.data.lineno, errorLineno, "Correct message event.lineno");
+
+ if (windowErrorCount == 1) {
+ is(scopeErrorCount, workerCount, "Good number of scope errors");
+ is(workerErrorCount, workerCount, "Good number of worker errors");
+ workerFrame.stop();
+ SimpleTest.finish();
+ }
+ }
+
+ function workerFrameLoaded() {
+ workerFrame = document.getElementById("workerFrame").contentWindow;
+ workerFrame.start(workerCount, messageListener);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+ </body>
+</html>
+
diff --git a/dom/workers/test/test_errorwarning.html b/dom/workers/test/test_errorwarning.html
new file mode 100644
index 000000000..04523c839
--- /dev/null
+++ b/dom/workers/test/test_errorwarning.html
@@ -0,0 +1,95 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Test javascript.options.strict in Workers
+-->
+<head>
+ <title>Test javascript.options.strict in Workers</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" language="javascript">
+
+ var errors = 0;
+ function errorHandler(e) {
+ ok(true, "An error has been received!");
+ errors++;
+ }
+
+ function test_noErrors() {
+ errors = 0;
+
+ var worker = new Worker('errorwarning_worker.js');
+ worker.onerror = errorHandler;
+ worker.onmessage = function(e) {
+ if (e.data.type == 'ignore')
+ return;
+
+ if (e.data.type == 'error') {
+ errorHandler();
+ return;
+ }
+
+ if (e.data.type == 'finish') {
+ ok(errors == 0, "Here we are with 0 errors!");
+ runTests();
+ return;
+ }
+ }
+
+ onerror = errorHandler;
+ worker.postMessage({ loop: 5, errors: false });
+ }
+
+ function test_errors() {
+ errors = 0;
+
+ var worker = new Worker('errorwarning_worker.js');
+ worker.onerror = errorHandler;
+ worker.onmessage = function(e) {
+ if (e.data.type == 'ignore')
+ return;
+
+ if (e.data.type == 'error') {
+ errorHandler();
+ return;
+ }
+
+ if (e.data.type == 'finish') {
+ ok(errors != 0, "Here we are with errors!");
+ runTests();
+ return;
+ }
+ }
+
+ onerror = errorHandler;
+ worker.postMessage({ loop: 5, errors: true });
+ }
+
+ var tests = [ test_noErrors, test_errors ];
+ function runTests() {
+ var test = tests.shift();
+ if (test) {
+ test();
+ } else {
+ SimpleTest.finish();
+ }
+ }
+
+ runTests();
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_eventDispatch.html b/dom/workers/test/test_eventDispatch.html
new file mode 100644
index 000000000..b3c3123f0
--- /dev/null
+++ b/dom/workers/test/test_eventDispatch.html
@@ -0,0 +1,33 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <script type="text/javascript" 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_extension.xul b/dom/workers/test/test_extension.xul
new file mode 100644
index 000000000..ba68ef03a
--- /dev/null
+++ b/dom/workers/test/test_extension.xul
@@ -0,0 +1,55 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ function test() {
+ const message = "woohoo";
+
+ var workertest =
+ Cc["@mozilla.org/test/workertest;1"].createInstance(Ci.nsIWorkerTest);
+
+ workertest.callback = {
+ onmessage: function(data) {
+ is(data, message, "Correct message");
+ workertest.callback = null;
+ workertest = null;
+ SimpleTest.finish();
+ },
+ onerror: function(data) {
+ ok(false, "Worker had an error: " + data.message);
+ workertest.callback = null;
+ workertest = null;
+ SimpleTest.finish();
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerTestCallback])
+ };
+
+ workertest.postMessage(message);
+
+ SimpleTest.waitForExplicitFinish();
+ }
+
+ ]]>
+ </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_extensionBootstrap.xul b/dom/workers/test/test_extensionBootstrap.xul
new file mode 100644
index 000000000..18bdde1d3
--- /dev/null
+++ b/dom/workers/test/test_extensionBootstrap.xul
@@ -0,0 +1,66 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ function test() {
+ const message = "woohoo";
+
+ var observer = {
+ observe: function(subject, topic, data) {
+ is(topic, "message", "Correct type of event");
+ is(data, message, "Correct message");
+
+ AddonManager.getAddonByID("workerbootstrap-test@mozilla.org",
+ function(addon) {
+ addon.uninstall();
+
+ const stages = [ "install", "startup", "shutdown", "uninstall" ];
+ const symbols = [ "Worker", "ChromeWorker" ];
+
+ for (var stage of stages) {
+ for (var symbol of symbols) {
+ is(Services.prefs.getBoolPref("workertest.bootstrap." + stage +
+ "." + symbol),
+ true,
+ "Symbol '" + symbol + "' present during '" + stage + "'");
+ }
+ }
+
+ SimpleTest.finish();
+ });
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+ };
+
+ var workertestbootstrap = Cc["@mozilla.org/test/workertestbootstrap;1"].
+ createInstance(Ci.nsIObserver);
+
+ workertestbootstrap.observe(observer, "postMessage", message);
+
+ SimpleTest.waitForExplicitFinish();
+ }
+
+ ]]>
+ </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_fibonacci.html b/dom/workers/test/test_fibonacci.html
new file mode 100644
index 000000000..d93eb12d4
--- /dev/null
+++ b/dom/workers/test/test_fibonacci.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 with Fibonacci
+-->
+<head>
+ <title>Test for DOM Worker Threads with Fibonacci</title>
+ <script type="text/javascript" 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.xul b/dom/workers/test/test_file.xul
new file mode 100644
index 000000000..800e88fbc
--- /dev/null
+++ b/dom/workers/test/test_file.xul
@@ -0,0 +1,97 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/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 type="application/javascript"
+ 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 = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("ProfD", Components.interfaces.nsIFile);
+ var fileExtension = (extension == undefined) ? "" : "." + extension;
+ testFile.append("workerFile" + fileNum++ + fileExtension);
+
+ var outStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Components.interfaces.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0666, 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.lastModifiedDate.toString(), file.lastModifiedDate.toString(), "lastModifiedDate 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.xul b/dom/workers/test/test_fileBlobPosting.xul
new file mode 100644
index 000000000..358054598
--- /dev/null
+++ b/dom/workers/test/test_fileBlobPosting.xul
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/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 type="application/javascript"
+ 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 = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("ProfD", Components.interfaces.nsIFile);
+ testFile.append("workerBlobPosting" + fileNum++);
+
+ var outStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Components.interfaces.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0666, 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.xul b/dom/workers/test/test_fileBlobSubWorker.xul
new file mode 100644
index 000000000..6a8dba636
--- /dev/null
+++ b/dom/workers/test/test_fileBlobSubWorker.xul
@@ -0,0 +1,98 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/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 type="application/javascript"
+ 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 = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("ProfD", Components.interfaces.nsIFile);
+ var fileExtension = (extension == undefined) ? "" : "." + extension;
+ testFile.append("workerBlobSubWorker" + fileNum++ + fileExtension);
+
+ var outStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Components.interfaces.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0666, 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.xul b/dom/workers/test/test_filePosting.xul
new file mode 100644
index 000000000..ff8520d7e
--- /dev/null
+++ b/dom/workers/test/test_filePosting.xul
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/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 type="application/javascript"
+ 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 = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("ProfD", Components.interfaces.nsIFile);
+ testFile.append("workerFilePosting" + fileNum++);
+
+ var outStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Components.interfaces.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0666, 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.xul b/dom/workers/test/test_fileReadSlice.xul
new file mode 100644
index 000000000..da2e16719
--- /dev/null
+++ b/dom/workers/test/test_fileReadSlice.xul
@@ -0,0 +1,94 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/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 type="application/javascript"
+ 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 = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("ProfD", Components.interfaces.nsIFile);
+ testFile.append("workerReadSlice" + fileNum++);
+
+ var outStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Components.interfaces.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0666, 0);
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ var fileList = document.getElementById('fileList');
+ fileList.value = testFile.path;
+
+ return fileList.files[0];
+ }
+
+ /**
+ * Creates a worker which slices a blob to the given start and end offset and
+ * reads the content as text.
+ */
+ function readSlice(blob, start, end, expectedText) {
+ var worker = new Worker("fileReadSlice_worker.js");
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ worker.onmessage = function(event) {
+ is(event.data, expectedText, "Text from sliced blob in worker is incorrect.");
+ finish();
+ };
+
+ var params = {blob: blob, start: start, end: end};
+ worker.postMessage(params);
+ waitForWorkerFinish();
+ }
+
+ // Empty file.
+ readSlice(createFileWithData(""), 0, 0, "");
+
+ // Typical use case.
+ readSlice(createFileWithData("HelloBye"), 5, 8, "Bye");
+
+ // End offset too large.
+ readSlice(createFileWithData("HelloBye"), 5, 9, "Bye");
+
+ // Start of file.
+ readSlice(createFileWithData("HelloBye"), 0, 5, "Hello");
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/workers/test/test_fileReader.html b/dom/workers/test/test_fileReader.html
new file mode 100644
index 000000000..26e73bdb6
--- /dev/null
+++ b/dom/workers/test/test_fileReader.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for FileReader in workers</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<script type="text/javascript;version=1.7">
+
+const minFileSize = 20000;
+SimpleTest.waitForExplicitFinish();
+
+// Create strings containing data we'll test with. We'll want long
+// strings to ensure they span multiple buffers while loading
+var testTextData = "asd b\tlah\u1234w\u00a0r";
+while (testTextData.length < minFileSize) {
+ testTextData = testTextData + testTextData;
+}
+
+var testASCIIData = "abcdef 123456\n";
+while (testASCIIData.length < minFileSize) {
+ testASCIIData = testASCIIData + testASCIIData;
+}
+
+var testBinaryData = "";
+for (var i = 0; i < 256; i++) {
+ testBinaryData += String.fromCharCode(i);
+}
+while (testBinaryData.length < minFileSize) {
+ testBinaryData = testBinaryData + testBinaryData;
+}
+
+var dataurldata0 = testBinaryData.substr(0, testBinaryData.length -
+ testBinaryData.length % 3);
+var dataurldata1 = testBinaryData.substr(0, testBinaryData.length - 2 -
+ testBinaryData.length % 3);
+var dataurldata2 = testBinaryData.substr(0, testBinaryData.length - 1 -
+ testBinaryData.length % 3);
+
+
+//Set up files for testing
+var openerURL = SimpleTest.getTestFileURL("fileapi_chromeScript.js");
+var opener = SpecialPowers.loadChromeScript(openerURL);
+opener.addMessageListener("files.opened", onFilesOpened);
+opener.sendAsyncMessage("files.open", [
+ testASCIIData,
+ testBinaryData,
+ null,
+ convertToUTF8(testTextData),
+ convertToUTF16(testTextData),
+ "",
+ dataurldata0,
+ dataurldata1,
+ dataurldata2,
+]);
+
+function onFilesOpened(message) {
+ var worker = new Worker('worker_fileReader.js');
+ worker.postMessage({ blobs: message,
+ testTextData: testTextData,
+ testASCIIData: testASCIIData,
+ testBinaryData: testBinaryData,
+ dataurldata0: dataurldata0,
+ dataurldata1: dataurldata1,
+ dataurldata2: dataurldata2 });
+
+ worker.onmessage = function(e) {
+ var msg = e.data;
+ if (msg.type == 'finish') {
+ SimpleTest.finish();
+ return;
+ }
+
+ if (msg.type == 'check') {
+ ok(msg.status, msg.msg);
+ return;
+ }
+
+ ok(false, "Unknown message.");
+ }
+}
+
+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;
+}
+
+function convertToUTF8(s) {
+ return unescape(encodeURIComponent(s));
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/test_fileReaderSync.xul b/dom/workers/test/test_fileReaderSync.xul
new file mode 100644
index 000000000..de93c3ed7
--- /dev/null
+++ b/dom/workers/test/test_fileReaderSync.xul
@@ -0,0 +1,199 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/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 type="application/javascript"
+ 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 = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("ProfD", Components.interfaces.nsIFile);
+ var fileExtension = (extension == undefined) ? "" : "." + extension;
+ testFile.append("workerFileReaderSync" + fileNum++ + fileExtension);
+
+ var outStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Components.interfaces.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0666, 0);
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ var fileList = document.getElementById('fileList');
+ fileList.value = testFile.path;
+
+ return fileList.files[0];
+ }
+
+ function convertToUTF16(s) {
+ res = "";
+ for (var i = 0; i < s.length; ++i) {
+ c = s.charCodeAt(i);
+ res += String.fromCharCode(c & 255, c >>> 8);
+ }
+ return res;
+ }
+
+ /**
+ * Converts the given string to a data URL of the specified mime type.
+ */
+ function convertToDataURL(mime, s) {
+ return "data:" + mime + ";base64," + btoa(s);
+ }
+
+ /**
+ * Create a worker to read a file containing fileData using FileReaderSync and
+ * checks the return type against the expected type. Optionally set an encoding
+ * for reading the file as text.
+ */
+ function readFileData(fileData, expectedText, /** optional */ encoding) {
+ var worker = new Worker("fileReaderSync_worker.js");
+
+ worker.onmessage = function(event) {
+ is(event.data.text, expectedText, "readAsText in worker returned incorrect result.");
+ is(event.data.bin, fileData, "readAsBinaryString in worker returned incorrect result.");
+ is(event.data.url, convertToDataURL("application/octet-stream", fileData), "readAsDataURL in worker returned incorrect result.");
+ is(event.data.arrayBuffer.byteLength, fileData.length, "readAsArrayBuffer returned buffer of incorrect length.");
+ finish();
+ };
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ var params = {file: createFileWithData(fileData), encoding: encoding};
+
+ worker.postMessage(params);
+
+ waitForWorkerFinish();
+ }
+
+ /**
+ * Create a worker which reuses a FileReaderSync to read multiple files as DataURLs.
+ */
+ function reuseReaderForURL(files, expected) {
+ var worker = new Worker("fileReaderSync_worker.js");
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ var k = 0;
+ worker.onmessage = function(event) {
+ is(event.data.url, expected[k], "readAsDataURL in worker returned incorrect result when reusing FileReaderSync.");
+ k++;
+ finish();
+ };
+
+ for (var i = 0; i < files.length; ++i) {
+ var params = {file: files[i], encoding: undefined};
+ worker.postMessage(params);
+ waitForWorkerFinish();
+ }
+ }
+
+ /**
+ * Create a worker which reuses a FileReaderSync to read multiple files as text.
+ */
+ function reuseReaderForText(fileData, expected) {
+ var worker = new Worker("fileReaderSync_worker.js");
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ var k = 0;
+ worker.onmessage = function(event) {
+ is(event.data.text, expected[k++], "readAsText in worker returned incorrect result when reusing FileReaderSync.");
+ finish();
+ };
+
+ for (var i = 0; i < fileData.length; ++i) {
+ var params = {file: createFileWithData(fileData[i]), encoding: undefined};
+ worker.postMessage(params);
+ waitForWorkerFinish();
+ }
+ }
+
+
+ /**
+ * Creates a a worker which reads a file containing fileData as an ArrayBuffer.
+ * Verifies that the ArrayBuffer when interpreted as a string matches the original data.
+ */
+ function readArrayBuffer(fileData) {
+ var worker = new Worker("fileReaderSync_worker.js");
+
+ worker.onmessage = function(event) {
+ var view = new Uint8Array(event.data.arrayBuffer);
+ is(event.data.arrayBuffer.byteLength, fileData.length, "readAsArrayBuffer returned buffer of incorrect length.");
+ is(String.fromCharCode.apply(String, view), fileData, "readAsArrayBuffer returned buffer containing incorrect data.");
+ finish();
+ };
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ var params = {file: createFileWithData(fileData), encoding: undefined};
+
+ worker.postMessage(params);
+
+ waitForWorkerFinish();
+ }
+
+ // Empty file.
+ readFileData("", "");
+
+ // Typical use case.
+ readFileData("text", "text");
+
+ // Test reading UTF-16 characters.
+ readFileData(convertToUTF16("text"), "text", "UTF-16");
+
+ // First read a file of type "text/plain", then read a file of type "application/octet-stream".
+ reuseReaderForURL([createFileWithData("text", "txt"), createFileWithData("text")],
+ [convertToDataURL("text/plain", "text"),
+ convertToDataURL("application/octet-stream", "text")]);
+
+ // First read UTF-16 characters marked using BOM, then read UTF-8 characters.
+ reuseReaderForText([convertToUTF16("\ufefftext"), "text"],
+ ["text", "text"]);
+
+ // Reading data as ArrayBuffer.
+ readArrayBuffer("");
+ readArrayBuffer("text");
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/workers/test/test_fileReaderSyncErrors.xul b/dom/workers/test/test_fileReaderSyncErrors.xul
new file mode 100644
index 000000000..9e416a603
--- /dev/null
+++ b/dom/workers/test/test_fileReaderSyncErrors.xul
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/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 type="application/javascript"
+ 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 = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("ProfD", Components.interfaces.nsIFile);
+ testFile.append("workerFileReaderSyncErrors" + fileNum++);
+
+ var outStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Components.interfaces.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0666, 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_fileSlice.xul b/dom/workers/test/test_fileSlice.xul
new file mode 100644
index 000000000..31531da2e
--- /dev/null
+++ b/dom/workers/test/test_fileSlice.xul
@@ -0,0 +1,106 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/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 type="application/javascript"
+ 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 = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("ProfD", Components.interfaces.nsIFile);
+ var fileExtension = (extension == undefined) ? "" : "." + extension;
+ testFile.append("workerSlice" + fileNum++ + fileExtension);
+
+ var outStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Components.interfaces.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0666, 0);
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ var fileList = document.getElementById('fileList');
+ fileList.value = testFile.path;
+
+ return fileList.files[0];
+ }
+
+ /**
+ * Starts a worker which slices the blob to the given start offset and optional end offset and
+ * content type. It then verifies that the size and type of the sliced blob is correct.
+ */
+ function createSlice(blob, start, expectedLength, /** optional */ end, /** optional */ contentType) {
+ var worker = new Worker("fileSlice_worker.js");
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ finish();
+ };
+
+ worker.onmessage = function(event) {
+ is(event.data.size, expectedLength, "size property of slice is incorrect.");
+ is(event.data.type, contentType ? contentType : blob.type, "type property of slice is incorrect.");
+ finish();
+ };
+
+ var params = {blob: blob, start: start, end: end, contentType: contentType};
+ worker.postMessage(params);
+ waitForWorkerFinish();
+ }
+
+ // Empty file.
+ createSlice(createFileWithData(""), 0, 0, 0);
+
+ // Typical use case.
+ createSlice(createFileWithData("Hello"), 1, 1, 2);
+
+ // Longish file.
+ var text = "";
+ for (var i = 0; i < 10000; ++i) {
+ text += "long";
+ }
+ createSlice(createFileWithData(text), 2000, 2000, 4000);
+
+ // Slice to different type.
+ createSlice(createFileWithData("text", "txt"), 0, 2, 2, "image/png");
+
+ // Length longer than blob.
+ createSlice(createFileWithData("text"), 0, 4, 20);
+
+ // Start longer than blob.
+ createSlice(createFileWithData("text"), 20, 0, 4);
+
+ // No optional arguments
+ createSlice(createFileWithData("text"), 0, 4);
+ createSlice(createFileWithData("text"), 2, 2);
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/workers/test/test_fileSubWorker.xul b/dom/workers/test/test_fileSubWorker.xul
new file mode 100644
index 000000000..94b41704a
--- /dev/null
+++ b/dom/workers/test/test_fileSubWorker.xul
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/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 type="application/javascript"
+ 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 = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("ProfD", Components.interfaces.nsIFile);
+ var fileExtension = (extension == undefined) ? "" : "." + extension;
+ testFile.append("workerSubWorker" + fileNum++ + fileExtension);
+
+ var outStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Components.interfaces.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0666, 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 000000000..718409ce3
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("importScripts_worker.js");
+
+ worker.onmessage = function(event) {
+ switch (event.data) {
+ case "started":
+ worker.postMessage("stop");
+ break;
+ case "stopped":
+ ok(true, "worker correctly stopped");
+ SimpleTest.finish();
+ break;
+ default:
+ ok(false, "Unexpected message:" + event.data);
+ SimpleTest.finish();
+ }
+ };
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error:" + event.message);
+ SimpleTest.finish();
+ }
+
+ worker.postMessage("start");
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_importScripts_3rdparty.html b/dom/workers/test/test_importScripts_3rdparty.html
new file mode 100644
index 000000000..a3d73c5b5
--- /dev/null
+++ b/dom/workers/test/test_importScripts_3rdparty.html
@@ -0,0 +1,134 @@
+<!--
+ 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 type="text/javascript" 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';
+
+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: location.href, 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: location.href, 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: location.href, 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: location.href, 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: location.href, 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: location.href, 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: location.href, 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: location.href, test: 'none', nested: true });
+ },
+
+ function() {
+ var url = URL.createObjectURL(new Blob(["%&%^&%^"]));
+ var worker = new Worker(url);
+ worker.onerror = function(event) {
+ event.preventDefault();
+ ok(event instanceof ErrorEvent, "ErrorEvent received.");
+ next();
+ };
+ }
+];
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+next();
+
+</script>
+</body>
+</html>
diff --git a/dom/workers/test/test_importScripts_mixedcontent.html b/dom/workers/test/test_importScripts_mixedcontent.html
new file mode 100644
index 000000000..0a7ce005c
--- /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 type="text/javascript" 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 000000000..f73b3b6a7
--- /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 type="text/javascript" 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 000000000..3a495de09
--- /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 type="text/javascript" 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_jsversion.html b/dom/workers/test/test_jsversion.html
new file mode 100644
index 000000000..495b8a3fa
--- /dev/null
+++ b/dom/workers/test/test_jsversion.html
@@ -0,0 +1,68 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for JSVersion in workers - Bug 487070</title>
+ <script type="text/javascript" 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 gExpectedError = false;
+
+ onerror = function(evt) {
+ ok(gExpectedError, "Error expected!");
+ runTest();
+ }
+
+ function doMagic() {
+ var worker = new Worker('jsversion_worker.js');
+ worker.onmessage = function(evt) {
+ ok(evt.data, 'All the tests passed');
+ runTest();
+ }
+ worker.postMessage(1);
+ }
+
+ var tests = [
+ // No custom version
+ function() {
+ gExpectedError = true;
+ SpecialPowers.pushPrefEnv({"set":[['dom.workers.latestJSVersion', false]]},
+ function() { doMagic(true); });
+ },
+
+ // Enable latest JS Version
+ function() {
+ gExpectedError = false;
+ SpecialPowers.pushPrefEnv({"set":[['dom.workers.latestJSVersion', true]]},
+ function() { doMagic(false); });
+ }
+ ];
+
+ function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest();
+
+</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 000000000..47e08f2f5
--- /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 type="text/javascript" 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 000000000..dc109b796
--- /dev/null
+++ b/dom/workers/test/test_loadError.html
@@ -0,0 +1,77 @@
+<!--
+ 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 type="text/javascript" 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";
+
+var loadErrorMessage = 'SecurityError: Failed to load worker script at "about:blank"';
+
+function nextTest() {
+ (function(){
+ function workerfunc() {
+ var subworker = new Worker("about:blank");
+ subworker.onerror = function(e) {
+ e.preventDefault();
+ postMessage(e.message);
+ }
+ }
+ var b = new Blob([workerfunc+'workerfunc();']);
+ var u = URL.createObjectURL(b);
+ function callworker(i) {
+ try {
+ var w = new Worker(u);
+ URL.revokeObjectURL(u);
+ is(i, 0, 'worker creation succeeded');
+ } catch (e) {
+ is(i, 1, 'worker creation failed');
+ SimpleTest.finish();
+ return;
+ }
+ w.onmessage = function(e) {
+ is(e.data, loadErrorMessage,
+ "Should catch the error when loading inner script");
+ if (++i < 2) callworker(i);
+ else SimpleTest.finish();
+ };
+ w.onerrror = function(e) {
+ ok(false, "Should not get any errors from this worker");
+ }
+ }
+ callworker(0);
+ })();
+}
+
+try {
+ var worker = new Worker("about:blank");
+ worker.onerror = function(e) {
+ e.preventDefault();
+ is(e.message, loadErrorMessage,
+ "Should get the right error from the toplevel script");
+ 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 000000000..cbd605307
--- /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 type="text/javascript" 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 000000000..d23989f8b
--- /dev/null
+++ b/dom/workers/test/test_longThread.html
@@ -0,0 +1,59 @@
+<!--
+ 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 type="text/javascript" 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 000000000..ba028302f
--- /dev/null
+++ b/dom/workers/test/test_multi_sharedWorker.html
@@ -0,0 +1,242 @@
+<!--
+ 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;version=1.7">
+ "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();
+ yield undefined;
+ })();
+
+ let sendToGenerator = testGenerator.send.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.html b/dom/workers/test/test_multi_sharedWorker_lifetimes.html
new file mode 100644
index 000000000..a3f4dc9b5
--- /dev/null
+++ b/dom/workers/test/test_multi_sharedWorker_lifetimes.html
@@ -0,0 +1,156 @@
+<!--
+ 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;version=1.7">
+ "use strict";
+
+ const scrollbarPref = "layout.testing.overlay-scrollbars.always-visible";
+ const bfCacheEnabledPref = "browser.sessionhistory.cache_subframes";
+ const bfCacheDepthPref = "browser.sessionhistory.max_total_viewers";
+ const bfCacheDepth = 10;
+
+ const frameRelativeURL = "multi_sharedWorker_frame.html";
+ const storedData = "0123456789abcdefghijklmnopqrstuvwxyz";
+
+ let testGenerator = (function() {
+ SimpleTest.waitForExplicitFinish();
+
+ // Force scrollbar to always be shown. The scrollbar setting is
+ // necessary to avoid the fade-in/fade-out from evicting our document
+ // from the BF cache below. If bug 1049277 is fixed, then we can
+ // stop setting the scrollbar pref here.
+ SpecialPowers.pushPrefEnv({ set: [[scrollbarPref, true]] },
+ sendToGenerator);
+ yield undefined;
+
+ window.addEventListener("message", function(event) {
+ if (typeof(event.data) == "string") {
+ info(event.data);
+ } else {
+ sendToGenerator(event);
+ }
+ });
+
+ let frame = document.getElementById("frame");
+ frame.src = frameRelativeURL;
+ frame.onload = sendToGenerator;
+
+ yield undefined;
+
+ frame = frame.contentWindow;
+ frame.postMessage({ command: "retrieve" }, "*");
+
+ let event = yield undefined;
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ is(event.source, frame, "Correct window got the event");
+ is(event.data.type, "result", "Got a result message");
+ is(event.data.data, undefined, "No data stored yet");
+
+ frame.postMessage({ command: "store", data: storedData }, "*");
+ frame.postMessage({ command: "retrieve" }, "*");
+
+ event = yield undefined;
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ is(event.source, frame, "Correct window got the event");
+ is(event.data.type, "result", "Got a result message");
+ is(event.data.data, storedData, "Got stored data");
+
+ // Navigate when the bfcache is disabled.
+ info("Navigating to about:blank");
+ frame = document.getElementById("frame");
+ frame.onload = sendToGenerator;
+ frame.src = "about:blank";
+ frame.contentWindow.document.body.offsetTop;
+
+ yield undefined;
+
+ info("Navigating to " + frameRelativeURL);
+ frame.src = frameRelativeURL;
+ frame.contentWindow.document.body.offsetTop;
+
+ yield undefined;
+
+ frame = frame.contentWindow;
+ frame.postMessage({ command: "retrieve" }, "*");
+
+ event = yield undefined;
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ is(event.source, frame, "Correct window got the event");
+ is(event.data.type, "result", "Got a result message");
+ is(event.data.data, undefined, "No data stored");
+
+ frame.postMessage({ command: "store", data: storedData }, "*");
+ frame.postMessage({ command: "retrieve" }, "*");
+
+ event = yield undefined;
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ is(event.source, frame, "Correct window got the event");
+ is(event.data.type, "result", "Got a result message");
+ is(event.data.data, storedData, "Got stored data");
+
+ info("Enabling '" + bfCacheEnabledPref + "' pref");
+ SpecialPowers.pushPrefEnv({ set: [[bfCacheEnabledPref, true],
+ [bfCacheDepthPref, bfCacheDepth]] },
+ sendToGenerator);
+ yield undefined;
+
+ // Navigate when the bfcache is enabled.
+ frame = document.getElementById("frame");
+ frame.onload = sendToGenerator;
+
+ info("Navigating to about:blank");
+ frame.src = "about:blank";
+ frame.contentWindow.document.body.offsetTop;
+
+ yield undefined;
+
+ for (let i = 0; i < 3; i++) {
+ info("Running GC");
+ SpecialPowers.exactGC(sendToGenerator);
+ yield undefined;
+
+ info("Waiting the event queue to clear");
+ SpecialPowers.executeSoon(sendToGenerator);
+ yield undefined;
+ }
+
+ info("Navigating to " + frameRelativeURL);
+ frame.src = frameRelativeURL;
+ frame.contentWindow.document.body.offsetTop;
+
+ yield undefined;
+
+ frame = frame.contentWindow;
+ frame.postMessage({ command: "retrieve" }, "*");
+
+ event = yield undefined;
+ ok(event instanceof MessageEvent, "Got a MessageEvent");
+ is(event.source, frame, "Correct window got the event");
+ is(event.data.type, "result", "Got a result message");
+ is(event.data.data, storedData, "Still have data stored");
+
+ info("Resetting '" + bfCacheEnabledPref + "' pref");
+ SpecialPowers.popPrefEnv(sendToGenerator);
+ yield undefined;
+
+ window.removeEventListener("message", sendToGenerator);
+
+ SimpleTest.finish();
+ yield undefined;
+ })();
+
+ let sendToGenerator = testGenerator.send.bind(testGenerator);
+
+ </script>
+ </head>
+ <body onload="testGenerator.next();">
+ <iframe id="frame"></iframe>
+ </body>
+</html>
diff --git a/dom/workers/test/test_navigator.html b/dom/workers/test/test_navigator.html
new file mode 100644
index 000000000..49d320967
--- /dev/null
+++ b/dom/workers/test/test_navigator.html
@@ -0,0 +1,68 @@
+<!--
+ 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 type="text/javascript" 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("navigator_worker.js");
+
+ 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;
+ }
+
+ if (args.name === "storage") {
+ is(typeof navigator.storage, typeof args.value, "storage 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 version = SpecialPowers.Cc["@mozilla.org/xre/app-info;1"].getService(SpecialPowers.Ci.nsIXULAppInfo).version;
+ var isNightly = version.endsWith("a1");
+ var isRelease = !version.includes("a");
+
+ worker.postMessage({ isNightly, isRelease });
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_navigator_languages.html b/dom/workers/test/test_navigator_languages.html
new file mode 100644
index 000000000..2111739d9
--- /dev/null
+++ b/dom/workers/test/test_navigator_languages.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 Navigator
+-->
+<head>
+ <title>Test for DOM Worker Navigator.languages</title>
+ <script type="text/javascript" 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 = [ 'en,it', 'it,en,fr', '', 'en' ];
+ var expectedLanguages;
+ function runTests() {
+ if (!tests.length) {
+ worker.postMessage('finish');
+ SimpleTest.finish();
+ return;
+ }
+
+ expectedLanguages = tests.shift();
+ SpecialPowers.pushPrefEnv({"set": [["intl.accept_languages", expectedLanguages]]}, 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 mach.");
+ is(event.data.toString(), expectedLanguages, "This is the correct result.");
+ runTests();
+ }
+
+ runTests();
+
+</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 000000000..6fbbc75a9
--- /dev/null
+++ b/dom/workers/test/test_navigator_workers_hardwareConcurrency.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Navigator.hardwareConcurrency</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+ var script = "postMessage(navigator.hardwareConcurrency)";
+ var url = URL.createObjectURL(new Blob([script]));
+ var w = new Worker(url);
+ w.onmessage = function(e) {
+ var x = e.data;
+ is(typeof x, "number", "hardwareConcurrency should be a number.");
+ ok(x > 0, "hardwareConcurrency should be greater than 0.");
+ 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 000000000..51aeae6b0
--- /dev/null
+++ b/dom/workers/test/test_newError.html
@@ -0,0 +1,34 @@
+<!--
+ 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 type="text/javascript" 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 000000000..409d9dfd1
--- /dev/null
+++ b/dom/workers/test/test_notification.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=916893
+-->
+<head>
+ <title>Bug 916893</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/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();
+ SpecialPowers.pushPrefEnv(
+ {"set": [["dom.webnotifications.workers.enabled", true]]},
+ 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 000000000..74a1e8e09
--- /dev/null
+++ b/dom/workers/test/test_notification_child.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=916893
+-->
+<head>
+ <title>Bug 916893 - Test Notifications in child workers.</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/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();
+ SpecialPowers.pushPrefEnv(
+ {"set": [["dom.webnotifications.workers.enabled", true]]},
+ 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 000000000..e51e060d3
--- /dev/null
+++ b/dom/workers/test/test_notification_permission.html
@@ -0,0 +1,51 @@
+<!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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/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();
+ SpecialPowers.pushPrefEnv(
+ {"set": [["dom.webnotifications.workers.enabled", true]]},
+ 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 000000000..660835f82
--- /dev/null
+++ b/dom/workers/test/test_onLine.html
@@ -0,0 +1,64 @@
+<!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 type="text/javascript" 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') {
+ 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.nsIIOService2);
+ 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 000000000..63f359f9d
--- /dev/null
+++ b/dom/workers/test/test_promise.html
@@ -0,0 +1,44 @@
+<!--
+ 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 type="text/javascript" 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 000000000..28caaaaa8
--- /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 type="application/javascript" 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_recursion.html b/dom/workers/test/test_recursion.html
new file mode 100644
index 000000000..888a607c4
--- /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 type="text/javascript" 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 000000000..06471d978
--- /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 type="text/javascript" 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 000000000..c3afec78e
--- /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 type="text/javascript" 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"), "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 000000000..d04c8b591
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <script class="testbody" type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv(
+ {"set": [
+ ['security.mixed_content.block_display_content', false],
+ ['security.mixed_content.block_active_content', false]
+ ]},
+ function() {
+ SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], test);
+ });
+
+ function test() {
+ function messageListener(event) {
+ eval(event.data);
+ }
+ window.addEventListener("message", messageListener, false);
+
+ 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 000000000..b6733e010
--- /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 type="application/javascript" 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 000000000..8e2ea5445
--- /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 type="application/javascript" 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 000000000..eba858928
--- /dev/null
+++ b/dom/workers/test/test_rvals.html
@@ -0,0 +1,37 @@
+<!--
+ 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 type="text/javascript" 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 000000000..4c0dacafb
--- /dev/null
+++ b/dom/workers/test/test_setTimeoutWith0.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+ <title>Test for DOM Worker setTimeout and strings containing 0</title>
+ <script type="text/javascript" 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 000000000..3d3d4e2c6
--- /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 = 91;
+ const errorColumn = 0;
+
+ 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 000000000..169746892
--- /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 type="text/javascript" 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 000000000..32698ab52
--- /dev/null
+++ b/dom/workers/test/test_sharedWorker_ports.html
@@ -0,0 +1,42 @@
+<!--
+ 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 type="text/javascript" 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 000000000..b0eac5831
--- /dev/null
+++ b/dom/workers/test/test_sharedWorker_privateBrowsing.html
@@ -0,0 +1,101 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for SharedWorker - Private Browsing</title>
+ <script type="application/javascript" 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 Ci = Components.interfaces;
+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 onLoad() {
+ win.removeEventListener("load", onLoad, false);
+ win.addEventListener("DOMContentLoaded", function onInnerLoad() {
+ if (win.content.location.href != contentPage) {
+ win.gBrowser.loadURI(contentPage);
+ return;
+ }
+
+ win.removeEventListener("DOMContentLoaded", onInnerLoad, true);
+ SimpleTest.executeSoon(function() { aCallback(win); });
+ }, true);
+
+ if (!aIsPrivate) {
+ win.gBrowser.loadURI(contentPage);
+ }
+ }, true);
+}
+
+function setupWindow() {
+ mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ runTest();
+}
+
+var wN;
+var wP;
+
+function doTests() {
+ testOnWindow(false, function(aWin) {
+ wN = aWin;
+
+ testOnWindow(true, function(aWin) {
+ wP = aWin;
+
+ 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(event) {
+ is(event.data, 1, "Only 1 sharedworker expected in the normal window");
+
+ var sharedWorker3 = new wP.content.SharedWorker('sharedWorker_privateBrowsing.js');
+ sharedWorker3.port.onmessage = function(event) {
+ is(event.data, 2, "Only 2 sharedworker expected in the private window");
+ runTest();
+ }
+ }
+ }
+ });
+ });
+}
+
+var steps = [
+ setupWindow,
+ doTests
+];
+
+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_simpleThread.html b/dom/workers/test/test_simpleThread.html
new file mode 100644
index 000000000..0dad90312
--- /dev/null
+++ b/dom/workers/test/test_simpleThread.html
@@ -0,0 +1,75 @@
+<!--
+ 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 type="text/javascript" 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();
+ }
+ }, false);
+
+ 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_subworkers_suspended.html b/dom/workers/test/test_subworkers_suspended.html
new file mode 100644
index 000000000..063ccaa64
--- /dev/null
+++ b/dom/workers/test/test_subworkers_suspended.html
@@ -0,0 +1,135 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for sub workers+bfcache behavior</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script type="application/javascript">
+
+ const WORKER_URL = "worker_suspended.js";
+ const SUB_WORKERS = 3
+
+ var testUrl1 = "window_suspended.html?page1Shown";
+ var testUrl2 = "window_suspended.html?page2Shown";
+
+ var testWin;
+ var counter = 0;
+
+ function cacheData() {
+ return caches.open("test")
+ .then(function(cache) {
+ return cache.match("http://mochi.test:888/foo");
+ })
+ .then(function(response) {
+ return response.text();
+ });
+ }
+
+ function page1Shown(e) {
+ info("Page1Shown: " + testWin.location.href);
+
+ // First time this page is shown.
+ if (counter == 0) {
+ ok(!e.persisted, "test page should have been persisted initially");
+
+ info("Create a worker and subworkers...");
+ let worker = new e.target.defaultView.Worker(WORKER_URL);
+
+ var promise = new Promise((resolve, reject) => {
+ info("Waiting until workers are ready...");
+ worker.addEventListener("message", function onmessage(e) {
+ is(e.data, "ready", "We want to receive: -ready-");
+ worker.removeEventListener("message", onmessage);
+ resolve();
+ });
+ worker.postMessage({ type: "page1", count: SUB_WORKERS });
+ });
+
+ 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() {
+ info("New location: " + testUrl2);
+ testWin.location.href = testUrl2;
+ });
+ } else {
+ is(e.persisted, true, "test page should have been persisted in pageshow");
+
+ var promise = new Promise((resolve, reject) => {
+ info("Waiting a few seconds...");
+ setTimeout(resolve, 5000);
+ });
+
+ 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() {
+ testWin.close();
+ SimpleTest.finish();
+ });
+ }
+
+ counter++;
+ }
+
+ function page2Shown(e) {
+ info("Page2Shown: " + testWin.location.href);
+
+ info("Create a worker...");
+ let worker = new e.target.defaultView.Worker(WORKER_URL);
+
+ var promise = new Promise((resolve, reject) => {
+ info("Waiting until workers are ready...");
+ worker.addEventListener("message", function onmessage(e) {
+ is(e.data, "ready", "We want to receive: -ready-");
+ worker.removeEventListener("message", onmessage);
+ resolve();
+ });
+ worker.postMessage({ type: "page2" });
+ });
+
+ promise.then(function() {
+ info("Retrieving data from cache...");
+ return cacheData();
+ })
+
+ .then(function(content) {
+ is(content, "page2-0", "We have data from the second worker");
+ })
+
+ .then(function() {
+ info("Going back");
+ testWin.history.back();
+ });
+ }
+
+ SpecialPowers.pushPrefEnv({ set: [
+ ["dom.caches.enabled", true],
+ ["dom.caches.testing.enabled", true],
+ ] },
+ function() {
+ testWin = window.open(testUrl1);
+ });
+
+ 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 000000000..806b97f6c
--- /dev/null
+++ b/dom/workers/test/test_suspend.html
@@ -0,0 +1,138 @@
+<!--
+ 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 type="text/javascript" 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">
+<iframe id="workerFrame" src="suspend_iframe.html" onload="subframeLoaded();">
+</iframe>
+<script class="testbody" type="text/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ var iframe;
+ var lastCount;
+
+ var suspended = false;
+ var resumed = false;
+ var finished = false;
+
+ var interval;
+ var oldMessageCount;
+ var waitCount = 0;
+
+ function finishTest() {
+ if (finished) {
+ return;
+ }
+ finished = true;
+ SpecialPowers.flushPrefEnv(function () {
+ iframe.terminateWorker();
+ SimpleTest.finish();
+ });
+ }
+
+ function waitInterval() {
+ if (finished) {
+ return;
+ }
+ is(String(iframe.location), "about:blank", "Wrong url!");
+ 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;
+ iframe.history.back();
+ }
+ }
+
+ function badOnloadCallback() {
+ if (finished) {
+ return;
+ }
+ ok(false, "We don't want suspend_iframe.html to fire a new load event, we want it to come out of the bfcache!");
+ finishTest();
+ }
+
+ function suspendCallback() {
+ if (finished) {
+ return;
+ }
+ is(String(iframe.location), "about:blank", "Wrong url!");
+ is(suspended, false, "Already suspended?");
+ is(resumed, false, "Already resumed?");
+ SpecialPowers.popPrefEnv(function () {
+ suspended = true;
+ var iframeElement = document.getElementById("workerFrame");
+ iframeElement.onload = badOnloadCallback;
+ 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) {
+ SpecialPowers.pushPrefEnv({"set": [["browser.sessionhistory.cache_subframes", true]]}, function () {
+ iframe.location = "about:blank";
+ // We want suspend_iframe.html to go into bfcache, so we need to flush
+ // out all pending notifications. Otherwise, if they're flushed too
+ // late, they could kick us out of the bfcache again.
+ iframe.document.body.offsetTop;
+ });
+ }
+ return;
+ }
+
+ var newLocation =
+ window.location.toString().replace("test_suspend.html",
+ "suspend_iframe.html");
+ is(newLocation.indexOf(iframe.location.toString()), 0, "Wrong url!");
+ 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, "Iframe had an error: '" + data + "'");
+ finishTest();
+ }
+
+ function subframeLoaded() {
+ if (finished) {
+ return;
+ }
+ var iframeElement = document.getElementById("workerFrame");
+ iframeElement.onload = suspendCallback;
+
+ iframe = iframeElement.contentWindow;
+ ok(iframe, "No iframe?!");
+
+ iframe.startWorker(messageCallback, errorCallback);
+ }
+
+</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 000000000..5d31bd165
--- /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 type="text/javascript" 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, false);
+ }
+ catch (e) {
+ exception = true;
+ }
+ is(exception, false, "addEventListener didn't throw after terminate");
+
+ exception = false;
+ try {
+ worker.removeEventListener("message", messageListener, false);
+ }
+ 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(event) { }
+
+ 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 000000000..034a443e7
--- /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 type="text/javascript" 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.indexOf(event.target) != -1) {
+ 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 000000000..0fa86935a
--- /dev/null
+++ b/dom/workers/test/test_threadTimeouts.html
@@ -0,0 +1,61 @@
+<!--
+ 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 type="text/javascript" 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 000000000..7676541f7
--- /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 type="text/javascript" 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 000000000..6770d36a1
--- /dev/null
+++ b/dom/workers/test/test_timeoutTracing.html
@@ -0,0 +1,48 @@
+<!--
+ 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 type="text/javascript" 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(event) {
+ 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 000000000..a3deec12a
--- /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 type="text/javascript" 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: ab, cb: ab, size: 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_webSocket_sharedWorker.html b/dom/workers/test/test_webSocket_sharedWorker.html
new file mode 100644
index 000000000..7609a164b
--- /dev/null
+++ b/dom/workers/test/test_webSocket_sharedWorker.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 bug 1090183</title>
+ <script type="text/javascript" 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 sw = new SharedWorker('webSocket_sharedWorker.js');
+sw.port.onmessage = function(event) {
+ if (event.data.type == 'finish') {
+ SimpleTest.finish();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_websocket1.html b/dom/workers/test/test_websocket1.html
new file mode 100644
index 000000000..6c74af03c
--- /dev/null
+++ b/dom/workers/test/test_websocket1.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 WebSocket object in workers</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<pre id="feedback"></pre>
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("websocket_worker1.js");
+
+ worker.onmessage = function(event) {
+ is(event.target, worker, "event.target should be a worker!");
+
+ if (event.data.type == 'finish') {
+ info("All done!");
+ SimpleTest.finish();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ } else if (event.data.type == 'feedback') {
+ info(event.data.msg);
+ document.getElementById('feedback').innerHTML += event.data.msg + "\n";
+ }
+ };
+
+ worker.onerror = function(event) {
+ is(event.target, worker);
+ info("error!");
+ ok(false, "Worker had an error: " + event.data);
+ SimpleTest.finish();
+ };
+
+ worker.postMessage('foobar');
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_websocket2.html b/dom/workers/test/test_websocket2.html
new file mode 100644
index 000000000..d774be5a2
--- /dev/null
+++ b/dom/workers/test/test_websocket2.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 WebSocket object in workers</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<pre id="feedback"></pre>
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("websocket_worker2.js");
+
+ worker.onmessage = function(event) {
+ is(event.target, worker, "event.target should be a worker!");
+
+ if (event.data.type == 'finish') {
+ info("All done!");
+ SimpleTest.finish();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ } else if (event.data.type == 'feedback') {
+ info(event.data.msg);
+ document.getElementById('feedback').innerHTML += event.data.msg + "\n";
+ }
+ };
+
+ worker.onerror = function(event) {
+ is(event.target, worker);
+ info("error!");
+ ok(false, "Worker had an error: " + event.data);
+ SimpleTest.finish();
+ };
+
+ worker.postMessage('foobar');
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_websocket3.html b/dom/workers/test/test_websocket3.html
new file mode 100644
index 000000000..0882cf313
--- /dev/null
+++ b/dom/workers/test/test_websocket3.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 WebSocket object in workers</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<pre id="feedback"></pre>
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("websocket_worker3.js");
+
+ worker.onmessage = function(event) {
+ is(event.target, worker, "event.target should be a worker!");
+
+ if (event.data.type == 'finish') {
+ info("All done!");
+ SimpleTest.finish();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ } else if (event.data.type == 'feedback') {
+ info(event.data.msg);
+ document.getElementById('feedback').innerHTML += event.data.msg + "\n";
+ }
+ };
+
+ worker.onerror = function(event) {
+ is(event.target, worker);
+ info("error!");
+ ok(false, "Worker had an error: " + event.data);
+ SimpleTest.finish();
+ };
+
+ worker.postMessage('foobar');
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_websocket4.html b/dom/workers/test/test_websocket4.html
new file mode 100644
index 000000000..8c6bef506
--- /dev/null
+++ b/dom/workers/test/test_websocket4.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 WebSocket object in workers</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<pre id="feedback"></pre>
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("websocket_worker4.js");
+
+ worker.onmessage = function(event) {
+ is(event.target, worker, "event.target should be a worker!");
+
+ if (event.data.type == 'finish') {
+ info("All done!");
+ SimpleTest.finish();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ } else if (event.data.type == 'feedback') {
+ info(event.data.msg);
+ document.getElementById('feedback').innerHTML += event.data.msg + "\n";
+ }
+ };
+
+ worker.onerror = function(event) {
+ is(event.target, worker);
+ info("error!");
+ ok(false, "Worker had an error: " + event.data);
+ SimpleTest.finish();
+ };
+
+ worker.postMessage('foobar');
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_websocket5.html b/dom/workers/test/test_websocket5.html
new file mode 100644
index 000000000..cadae3782
--- /dev/null
+++ b/dom/workers/test/test_websocket5.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 WebSocket object in workers</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<pre id="feedback"></pre>
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("websocket_worker5.js");
+
+ worker.onmessage = function(event) {
+ is(event.target, worker, "event.target should be a worker!");
+
+ if (event.data.type == 'finish') {
+ info("All done!");
+ SimpleTest.finish();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ } else if (event.data.type == 'feedback') {
+ info(event.data.msg);
+ document.getElementById('feedback').innerHTML += event.data.msg + "\n";
+ }
+ };
+
+ worker.onerror = function(event) {
+ is(event.target, worker);
+ info("error!");
+ ok(false, "Worker had an error: " + event.data);
+ SimpleTest.finish();
+ };
+
+ worker.postMessage('foobar');
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_websocket_basic.html b/dom/workers/test/test_websocket_basic.html
new file mode 100644
index 000000000..5c9f8bf10
--- /dev/null
+++ b/dom/workers/test/test_websocket_basic.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 WebSocket object in workers</title>
+ <script type="text/javascript" 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("websocket_basic_worker.js");
+
+ worker.onmessage = function(event) {
+ is(event.target, worker);
+
+ if (event.data.type == 'finish') {
+ runTest();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ }
+ };
+
+ worker.onerror = function(event) {
+ is(event.target, worker);
+ ok(false, "Worker had an error: " + event.data);
+ SimpleTest.finish();
+ };
+
+ var tests = [
+ function() { worker.postMessage(0); },
+ function() { worker.postMessage(1); }
+ ];
+
+ function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ runTest();
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/workers/test/test_websocket_https.html b/dom/workers/test/test_websocket_https.html
new file mode 100644
index 000000000..aa94141fd
--- /dev/null
+++ b/dom/workers/test/test_websocket_https.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 that creating insecure websockets from https workers is not possible</title>
+ <script type="text/javascript" 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">
+
+ onmessage = function(event) {
+ is(event.data, "not created", "WebSocket object must not be created");
+ SimpleTest.finish();
+ };
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<iframe src="https://example.com/tests/dom/workers/test/websocket_https.html"></iframe>
+</body>
+</html>
diff --git a/dom/workers/test/test_websocket_loadgroup.html b/dom/workers/test/test_websocket_loadgroup.html
new file mode 100644
index 000000000..3c65e262b
--- /dev/null
+++ b/dom/workers/test/test_websocket_loadgroup.html
@@ -0,0 +1,61 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for WebSocket object in workers</title>
+ <script type="text/javascript" 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("websocket_loadgroup_worker.js");
+
+ var stopped = false;
+ worker.onmessage = function(e) {
+ if (e.data == 'opened') {
+ stopped = true;
+ window.stop();
+ } else if (e.data == 'closed') {
+ ok(stopped, "Good!");
+ stopped = false;
+ runTest();
+ } else {
+ ok(false, "An error has been received");
+ }
+ };
+
+ worker.onerror = function(event) {
+ is(event.target, worker);
+ ok(false, "Worker had an error: " + event.data);
+ SimpleTest.finish();
+ };
+
+ var tests = [
+ function() { worker.postMessage(0); },
+ function() { worker.postMessage(1); }
+ ];
+
+ function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+ }
+
+ runTest();
+ SimpleTest.waitForExplicitFinish();
+
+</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 000000000..26af63e51
--- /dev/null
+++ b/dom/workers/test/test_worker_interfaces.html
@@ -0,0 +1,16 @@
+<!-- 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 type="text/javascript" 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 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 000000000..e0647682c
--- /dev/null
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -0,0 +1,291 @@
+// 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",
+// { 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.
+
+// IMPORTANT: Do not change this list without review from
+// a JavaScript Engine peer!
+var ecmaGlobals =
+ [
+ "Array",
+ "ArrayBuffer",
+ "Boolean",
+ "DataView",
+ "Date",
+ "Error",
+ "EvalError",
+ "Float32Array",
+ "Float64Array",
+ "Function",
+ "Infinity",
+ "Int16Array",
+ "Int32Array",
+ "Int8Array",
+ "InternalError",
+ {name: "Intl", android: false},
+ "Iterator",
+ "JSON",
+ "Map",
+ "Math",
+ "NaN",
+ "Number",
+ "Object",
+ "Promise",
+ "Proxy",
+ "RangeError",
+ "ReferenceError",
+ "Reflect",
+ "RegExp",
+ "Set",
+ {name: "SharedArrayBuffer", release: false},
+ {name: "SIMD", nightly: true},
+ {name: "Atomics", release: false},
+ "StopIteration",
+ "String",
+ "Symbol",
+ "SyntaxError",
+ {name: "TypedObject", nightly: true},
+ "TypeError",
+ "Uint16Array",
+ "Uint32Array",
+ "Uint8Array",
+ "Uint8ClampedArray",
+ "URIError",
+ "WeakMap",
+ "WeakSet",
+ ];
+// 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!
+var interfaceNamesInGlobalScope =
+ [
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Blob",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "BroadcastChannel",
+// 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!
+ "Crypto",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "CustomEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "DedicatedWorkerGlobalScope",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Directory",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMCursor",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMError",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMException",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMStringList",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Event",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "EventTarget",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "File",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "FileReader",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "FileReaderSync",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "FormData",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Headers",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBCursor",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBCursorWithValue",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBDatabase",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBFactory",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBIndex",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBKeyRange",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBObjectStore",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBOpenDBRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBTransaction",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBVersionChangeEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "ImageBitmap",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "ImageBitmapRenderingContext",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "ImageData",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "MessageChannel",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "MessageEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "MessagePort",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Notification",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "OffscreenCanvas", disabled: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Performance",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "PerformanceEntry",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "PerformanceMark",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "PerformanceMeasure",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PerformanceObserver", nightly: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PerformanceObserverEntryList", nightly: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Request",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Response",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ {name: "StorageManager", nightly: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "SubtleCrypto",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "TextDecoder",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "TextEncoder",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "XMLHttpRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "XMLHttpRequestEventTarget",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "XMLHttpRequestUpload",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "URL",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "URLSearchParams",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLActiveInfo", disabled: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLBuffer", disabled: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLContextEvent", disabled: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLFramebuffer", disabled: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLProgram", disabled: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLRenderbuffer", disabled: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLRenderingContext", disabled: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLShader", disabled: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLShaderPrecisionFormat", disabled: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLTexture", disabled: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebGLUniformLocation", disabled: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebSocket",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Worker",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "WorkerGlobalScope",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "WorkerLocation",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "WorkerNavigator",
+// 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!
+
+function createInterfaceMap(version, userAgent) {
+ var isNightly = version.endsWith("a1");
+ var isRelease = !version.includes("a");
+ var isDesktop = !/Mobile|Tablet/.test(userAgent);
+ var isAndroid = !!navigator.userAgent.includes("Android");
+
+ var interfaceMap = {};
+
+ function addInterfaces(interfaces)
+ {
+ for (var entry of interfaces) {
+ if (typeof(entry) === "string") {
+ interfaceMap[entry] = true;
+ } else {
+ ok(!("pref" in entry), "Bogus pref annotation for " + entry.name);
+ if ((entry.nightly === !isNightly) ||
+ (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) ||
+ (entry.desktop === !isDesktop) ||
+ (entry.android === !isAndroid && !entry.nightlyAndroid) ||
+ (entry.release === !isRelease) ||
+ entry.disabled) {
+ interfaceMap[entry.name] = false;
+ } else {
+ interfaceMap[entry.name] = true;
+ }
+ }
+ }
+ }
+
+ addInterfaces(ecmaGlobals);
+ addInterfaces(interfaceNamesInGlobalScope);
+
+ return interfaceMap;
+}
+
+function runTest(version, userAgent) {
+ var interfaceMap = createInterfaceMap(version, userAgent);
+ for (var name of Object.getOwnPropertyNames(self)) {
+ // An interface name should start with an upper case character.
+ if (!/^[A-Z]/.test(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 on the worker? 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 self === interfaceMap[name],
+ name + " should " + (interfaceMap[name] ? "" : " NOT") + " be defined on the global scope");
+ if (!interfaceMap[name]) {
+ delete interfaceMap[name];
+ }
+ }
+ is(Object.keys(interfaceMap).length, 0,
+ "The following interface(s) are not enumerated: " + Object.keys(interfaceMap).join(", "));
+}
+
+workerTestGetVersion(function(version) {
+ workerTestGetUserAgent(function(userAgent) {
+ runTest(version, userAgent);
+ workerTestDone();
+ });
+});
diff --git a/dom/workers/test/test_workersDisabled.html b/dom/workers/test/test_workersDisabled.html
new file mode 100644
index 000000000..39a7fcc5b
--- /dev/null
+++ b/dom/workers/test/test_workersDisabled.html
@@ -0,0 +1,54 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ const enabledPref = "dom.workers.enabled";
+
+ is(SpecialPowers.getBoolPref(enabledPref), true,
+ "Workers should be enabled.");
+
+ SpecialPowers.pushPrefEnv({"set": [[enabledPref, false]]}, test1);
+
+ function test1() {
+ ok(!("Worker" in window), "Worker constructor should not be available.");
+
+ var exception;
+ try {
+ var worker = new Worker("workersDisabled_worker.js");
+ }
+ catch(e) {
+ exception = e;
+ }
+
+ ok(exception, "Shouldn't be able to make a worker.");
+
+ SpecialPowers.pushPrefEnv({"set": [[enabledPref, true]]}, test2);
+ }
+
+ function test2() {
+ ok(("Worker" in window), "Worker constructor should be available.");
+
+ const message = "Hi";
+
+ var worker = new Worker("workersDisabled_worker.js");
+ worker.onmessage = function(event) {
+ is(event.data, message, "Good message.");
+ SimpleTest.finish();
+ }
+ worker.postMessage(message);
+ }
+ </script>
+ </body>
+</html>
+
diff --git a/dom/workers/test/test_workersDisabled.xul b/dom/workers/test/test_workersDisabled.xul
new file mode 100644
index 000000000..1674f819f
--- /dev/null
+++ b/dom/workers/test/test_workersDisabled.xul
@@ -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="DOM Worker Threads Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="dom_worker_helper.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function test()
+ {
+ const enabledPref = "dom.workers.enabled";
+ const message = "Hi";
+
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ is(prefs.getBoolPref(enabledPref), true, "Workers should be enabled.");
+
+ prefs.setBoolPref(enabledPref, false);
+
+ ok("Worker" in window, "Worker constructor should be available.");
+ ok("ChromeWorker" in window,
+ "ChromeWorker constructor should be available.");
+
+ var worker = new ChromeWorker("workersDisabled_worker.js");
+ worker.onmessage = function(event) {
+ is(event.data, message, "Good message.");
+ prefs.clearUserPref(enabledPref);
+ finish();
+ }
+ worker.postMessage(message);
+
+ waitForWorkerFinish();
+ }
+ ]]>
+ </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/threadErrors_worker1.js b/dom/workers/test/threadErrors_worker1.js
new file mode 100644
index 000000000..c0ddade82
--- /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 000000000..0462d9668
--- /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 000000000..151ffbe5e
--- /dev/null
+++ b/dom/workers/test/threadErrors_worker3.js
@@ -0,0 +1,9 @@
+/**
+ * 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 000000000..4518ce017
--- /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 000000000..7aaac03d2
--- /dev/null
+++ b/dom/workers/test/threadTimeouts_worker.js
@@ -0,0 +1,44 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+var gTimeoutId;
+var gTimeoutCount = 0;
+var gIntervalCount = 0;
+
+function timeoutFunc() {
+ if (++gTimeoutCount > 1) {
+ throw new Error("Timeout called more than once!");
+ }
+ postMessage("timeoutFinished");
+}
+
+function intervalFunc() {
+ if (++gIntervalCount == 2) {
+ postMessage("intervalFinished");
+ }
+}
+
+function messageListener(event) {
+ switch (event.data) {
+ case "startTimeout":
+ gTimeoutId = setTimeout(timeoutFunc, 2000);
+ clearTimeout(gTimeoutId);
+ gTimeoutId = setTimeout(timeoutFunc, 2000);
+ break;
+ case "startInterval":
+ gTimeoutId = setInterval(intervalFunc, 2000);
+ break;
+ case "cancelInterval":
+ clearInterval(gTimeoutId);
+ postMessage("intervalCanceled");
+ break;
+ case "startExpression":
+ setTimeout("this.postMessage('expressionFinished');", 2000);
+ break;
+ default:
+ throw "Bad message: " + event.data;
+ }
+}
+
+addEventListener("message", messageListener, false);
diff --git a/dom/workers/test/throwingOnerror_worker.js b/dom/workers/test/throwingOnerror_worker.js
new file mode 100644
index 000000000..07e01787b
--- /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 000000000..d62cc50c5
--- /dev/null
+++ b/dom/workers/test/timeoutTracing_worker.js
@@ -0,0 +1,13 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+onmessage = function(event) {
+ throw "No messages should reach me!";
+}
+
+setInterval(function() { postMessage("Still alive!"); }, 20);
+setInterval(";", 20);
+
+postMessage("Begin!");
diff --git a/dom/workers/test/transferable_worker.js b/dom/workers/test/transferable_worker.js
new file mode 100644
index 000000000..3caf6121b
--- /dev/null
+++ b/dom/workers/test/transferable_worker.js
@@ -0,0 +1,23 @@
+/**
+ * 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: 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/webSocket_sharedWorker.js b/dom/workers/test/webSocket_sharedWorker.js
new file mode 100644
index 000000000..7c8fa0e12
--- /dev/null
+++ b/dom/workers/test/webSocket_sharedWorker.js
@@ -0,0 +1,20 @@
+onconnect = function(evt) {
+ var ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/file_websocket_hello");
+
+ ws.onopen = function(e) {
+ evt.ports[0].postMessage({type: 'status', status: true, msg: 'OnOpen called' });
+ ws.send("data");
+ }
+
+ ws.onclose = function(e) {}
+
+ ws.onerror = function(e) {
+ evt.ports[0].postMessage({type: 'status', status: false, msg: 'onerror called!'});
+ }
+
+ ws.onmessage = function(e) {
+ evt.ports[0].postMessage({type: 'status', status: e.data == 'Hello world!', msg: 'Wrong data'});
+ ws.close();
+ evt.ports[0].postMessage({type: 'finish' });
+ }
+}
diff --git a/dom/workers/test/websocket_basic_worker.js b/dom/workers/test/websocket_basic_worker.js
new file mode 100644
index 000000000..256af569a
--- /dev/null
+++ b/dom/workers/test/websocket_basic_worker.js
@@ -0,0 +1,39 @@
+onmessage = function(event) {
+ if (event.data != 0) {
+ var worker = new Worker('websocket_basic_worker.js');
+ worker.onmessage = function(event) {
+ postMessage(event.data);
+ }
+
+ worker.postMessage(event.data - 1);
+ return;
+ }
+
+ status = false;
+ try {
+ if ((WebSocket instanceof Object)) {
+ status = true;
+ }
+ } catch(e) {
+ }
+
+ postMessage({type: 'status', status: status, msg: 'WebSocket object:' + WebSocket});
+
+ var ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/file_websocket_hello");
+ ws.onopen = function(e) {
+ postMessage({type: 'status', status: true, msg: 'OnOpen called' });
+ ws.send("data");
+ }
+
+ ws.onclose = function(e) {}
+
+ ws.onerror = function(e) {
+ postMessage({type: 'status', status: false, msg: 'onerror called!'});
+ }
+
+ ws.onmessage = function(e) {
+ postMessage({type: 'status', status: e.data == 'Hello world!', msg: 'Wrong data'});
+ ws.close();
+ postMessage({type: 'finish' });
+ }
+}
diff --git a/dom/workers/test/websocket_helpers.js b/dom/workers/test/websocket_helpers.js
new file mode 100644
index 000000000..c2dc3f969
--- /dev/null
+++ b/dom/workers/test/websocket_helpers.js
@@ -0,0 +1,19 @@
+function feedback() {
+ postMessage({type: 'feedback', msg: "executing test: " + (current_test+1) + " of " + tests.length + " tests." });
+}
+
+function ok(status, msg) {
+ postMessage({type: 'status', status: !!status, msg: msg});
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+function isnot(a, b, msg) {
+ ok(a != b, msg);
+}
+
+function finish() {
+ postMessage({type: 'finish'});
+}
diff --git a/dom/workers/test/websocket_https.html b/dom/workers/test/websocket_https.html
new file mode 100644
index 000000000..549147a33
--- /dev/null
+++ b/dom/workers/test/websocket_https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+ var worker = new Worker("https://example.com/tests/dom/workers/test/websocket_https_worker.js");
+
+ worker.onmessage = function(event) {
+ parent.postMessage(event.data, "*");
+ };
+
+ worker.onerror = function(event) {
+ parent.postMessage("error", "*");
+ };
+
+ worker.postMessage("start");
+</script>
diff --git a/dom/workers/test/websocket_https_worker.js b/dom/workers/test/websocket_https_worker.js
new file mode 100644
index 000000000..2592ed6d0
--- /dev/null
+++ b/dom/workers/test/websocket_https_worker.js
@@ -0,0 +1,9 @@
+onmessage = function() {
+ var wsCreated = true;
+ try {
+ new WebSocket("ws://mochi.test:8888/tests/dom/base/test/file_websocket_hello");
+ } catch(e) {
+ wsCreated = false;
+ }
+ postMessage(wsCreated ? "created" : "not created");
+};
diff --git a/dom/workers/test/websocket_loadgroup_worker.js b/dom/workers/test/websocket_loadgroup_worker.js
new file mode 100644
index 000000000..21cf248bc
--- /dev/null
+++ b/dom/workers/test/websocket_loadgroup_worker.js
@@ -0,0 +1,24 @@
+onmessage = function(event) {
+ if (event.data != 0) {
+ var worker = new Worker('websocket_loadgroup_worker.js');
+ worker.onmessage = function(event) {
+ postMessage(event.data);
+ }
+
+ worker.postMessage(event.data - 1);
+ return;
+ }
+
+ var ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/file_websocket_hello");
+ ws.onopen = function(e) {
+ postMessage('opened');
+ }
+
+ ws.onclose = function(e) {
+ postMessage('closed');
+ }
+
+ ws.onerror = function(e) {
+ postMessage('error');
+ }
+}
diff --git a/dom/workers/test/websocket_worker1.js b/dom/workers/test/websocket_worker1.js
new file mode 100644
index 000000000..cc2b6e825
--- /dev/null
+++ b/dom/workers/test/websocket_worker1.js
@@ -0,0 +1,19 @@
+importScripts('../../../dom/base/test/websocket_helpers.js');
+importScripts('../../../dom/base/test/websocket_tests.js');
+importScripts('websocket_helpers.js');
+
+var tests = [
+ test1, // client tries to connect to a http scheme location;
+ test2, // assure serialization of the connections;
+ test3, // client tries to connect to an non-existent ws server;
+ test4, // client tries to connect using a relative url;
+ test5, // client uses an invalid protocol value;
+ test6, // counter and encoding check;
+ test7, // onmessage event origin property check
+ test8, // client calls close() and the server sends the close frame (with no
+ // code or reason) in acknowledgement;
+ test9, // client closes the connection before the ws connection is established;
+ test10, // client sends a message before the ws connection is established;
+];
+
+doTest();
diff --git a/dom/workers/test/websocket_worker2.js b/dom/workers/test/websocket_worker2.js
new file mode 100644
index 000000000..9dd4ae685
--- /dev/null
+++ b/dom/workers/test/websocket_worker2.js
@@ -0,0 +1,19 @@
+importScripts('../../../dom/base/test/websocket_helpers.js');
+importScripts('../../../dom/base/test/websocket_tests.js');
+importScripts('websocket_helpers.js');
+
+var tests = [
+ test11, // a simple hello echo;
+ test12, // client sends a message containing unpaired surrogates
+ test13, //server sends an invalid message;
+ test14, // server sends the close frame, it doesn't close the tcp connection
+ // and it keeps sending normal ws messages;
+ test15, // server closes the tcp connection, but it doesn't send the close
+ // frame;
+ test16, // client calls close() and tries to send a message;
+ test18, // client tries to connect to an http resource;
+ test19, // server closes the tcp connection before establishing the ws
+ // connection;
+];
+
+doTest();
diff --git a/dom/workers/test/websocket_worker3.js b/dom/workers/test/websocket_worker3.js
new file mode 100644
index 000000000..d2811294f
--- /dev/null
+++ b/dom/workers/test/websocket_worker3.js
@@ -0,0 +1,17 @@
+importScripts('../../../dom/base/test/websocket_helpers.js');
+importScripts('../../../dom/base/test/websocket_tests.js');
+importScripts('websocket_helpers.js');
+
+var tests = [
+ test24, // server rejects sub-protocol string
+ test25, // ctor with valid empty sub-protocol array
+ test26, // ctor with invalid sub-protocol array containing 1 empty element
+ test27, // ctor with invalid sub-protocol array containing an empty element in
+ // list
+ test28, // ctor using valid 1 element sub-protocol array
+ test29, // ctor using all valid 5 element sub-protocol array
+ test30, // ctor using valid 1 element sub-protocol array with element server
+ // will reject
+];
+
+doTest();
diff --git a/dom/workers/test/websocket_worker4.js b/dom/workers/test/websocket_worker4.js
new file mode 100644
index 000000000..030ee67f5
--- /dev/null
+++ b/dom/workers/test/websocket_worker4.js
@@ -0,0 +1,19 @@
+importScripts('../../../dom/base/test/websocket_helpers.js');
+importScripts('../../../dom/base/test/websocket_tests.js');
+importScripts('websocket_helpers.js');
+
+var tests = [
+ test31, // ctor using valid 2 element sub-protocol array with 1 element server
+ // will reject and one server will accept
+ test32, // ctor using invalid sub-protocol array that contains duplicate items
+ test33, // test for sending/receiving custom close code (but no close reason)
+ test34, // test for receiving custom close code and reason
+ test35, // test for sending custom close code and reason
+ test36, // negative test for sending out of range close code
+ test37, // negative test for too long of a close reason
+ test38, // ensure extensions attribute is defined
+ test39, // a basic wss:// connectivity test
+ test40, // negative test for wss:// with no cert
+];
+
+doTest();
diff --git a/dom/workers/test/websocket_worker5.js b/dom/workers/test/websocket_worker5.js
new file mode 100644
index 000000000..41d6e9be0
--- /dev/null
+++ b/dom/workers/test/websocket_worker5.js
@@ -0,0 +1,13 @@
+importScripts('../../../dom/base/test/websocket_helpers.js');
+importScripts('../../../dom/base/test/websocket_tests.js');
+importScripts('websocket_helpers.js');
+
+var tests = [
+ test42, // non-char utf-8 sequences
+ test43, // Test setting binaryType attribute
+ test44, // Test sending/receving binary ArrayBuffer
+ test46, // Test that we don't dispatch incoming msgs once in CLOSING state
+ test47, // Make sure onerror/onclose aren't called during close()
+];
+
+doTest();
diff --git a/dom/workers/test/window_suspended.html b/dom/workers/test/window_suspended.html
new file mode 100644
index 000000000..b482cbe09
--- /dev/null
+++ b/dom/workers/test/window_suspended.html
@@ -0,0 +1,5 @@
+<script>
+onpageshow = function(e) {
+ opener[location.search.split('?')[1]](e);
+}
+</script>
diff --git a/dom/workers/test/worker_bug1278777.js b/dom/workers/test/worker_bug1278777.js
new file mode 100644
index 000000000..c056d0254
--- /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 000000000..69cc25d23
--- /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_consoleAndBlobs.js b/dom/workers/test/worker_consoleAndBlobs.js
new file mode 100644
index 000000000..41e8317ac
--- /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 000000000..35eb17985
--- /dev/null
+++ b/dom/workers/test/worker_driver.js
@@ -0,0 +1,64 @@
+// 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 == 'getVersion') {
+ var result = SpecialPowers.Cc['@mozilla.org/xre/app-info;1'].getService(SpecialPowers.Ci.nsIXULAppInfo).version;
+ worker.postMessage({
+ type: 'returnVersion',
+ result: result
+ });
+
+ } else if (event.data.type == 'getUserAgent') {
+ worker.postMessage({
+ type: 'returnUserAgent',
+ result: navigator.userAgent
+ });
+ } else if (event.data.type == 'getOSCPU') {
+ worker.postMessage({
+ type: 'returnOSCPU',
+ result: navigator.oscpu
+ });
+ }
+ }
+
+ worker.onerror = function(event) {
+ ok(false, 'Worker had an error: ' + event.data);
+ SimpleTest.finish();
+ };
+
+ worker.postMessage({ script: script });
+}
diff --git a/dom/workers/test/worker_fileReader.js b/dom/workers/test/worker_fileReader.js
new file mode 100644
index 000000000..f4e91b325
--- /dev/null
+++ b/dom/workers/test/worker_fileReader.js
@@ -0,0 +1,417 @@
+var testRanCounter = 0;
+var expectedTestCount = 0;
+var testSetupFinished = false;
+
+function ok(a, msg) {
+ postMessage({type: 'check', status: !!a, msg: msg });
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+function finish() {
+ postMessage({type: 'finish'});
+}
+
+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;
+}
+
+function convertToUTF8(s) {
+ return unescape(encodeURIComponent(s));
+}
+
+function convertToDataURL(s) {
+ return "data:application/octet-stream;base64," + btoa(s);
+}
+
+onmessage = function(message) {
+ is(FileReader.EMPTY, 0, "correct EMPTY value");
+ is(FileReader.LOADING, 1, "correct LOADING value");
+ is(FileReader.DONE, 2, "correct DONE value");
+
+ // List of blobs.
+ var asciiFile = message.data.blobs.shift();
+ var binaryFile = message.data.blobs.shift();
+ var nonExistingFile = message.data.blobs.shift();
+ var utf8TextFile = message.data.blobs.shift();
+ var utf16TextFile = message.data.blobs.shift();
+ var emptyFile = message.data.blobs.shift();
+ var dataUrlFile0 = message.data.blobs.shift();
+ var dataUrlFile1 = message.data.blobs.shift();
+ var dataUrlFile2 = message.data.blobs.shift();
+
+ // List of buffers for testing.
+ var testTextData = message.data.testTextData;
+ var testASCIIData = message.data.testASCIIData;
+ var testBinaryData = message.data.testBinaryData;
+ var dataurldata0 = message.data.dataurldata0;
+ var dataurldata1 = message.data.dataurldata1;
+ var dataurldata2 = message.data.dataurldata2;
+
+ // Test that plain reading works and fires events as expected, both
+ // for text and binary reading
+
+ var onloadHasRunText = false;
+ var onloadStartHasRunText = false;
+ r = new FileReader();
+ is(r.readyState, FileReader.EMPTY, "correct initial text readyState");
+ r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "plain reading");
+ r.addEventListener("load", function() { onloadHasRunText = true }, false);
+ r.addEventListener("loadstart", function() { onloadStartHasRunText = true }, false);
+ r.readAsText(asciiFile);
+ is(r.readyState, FileReader.LOADING, "correct loading text readyState");
+ is(onloadHasRunText, false, "text loading must be async");
+ is(onloadStartHasRunText, true, "text loadstart should fire sync");
+ expectedTestCount++;
+
+ var onloadHasRunBinary = false;
+ var onloadStartHasRunBinary = false;
+ r = new FileReader();
+ is(r.readyState, FileReader.EMPTY, "correct initial binary readyState");
+ r.addEventListener("load", function() { onloadHasRunBinary = true }, false);
+ r.addEventListener("loadstart", function() { onloadStartHasRunBinary = true }, false);
+ r.readAsBinaryString(binaryFile);
+ r.onload = getLoadHandler(testBinaryData, testBinaryData.length, "binary reading");
+ is(r.readyState, FileReader.LOADING, "correct loading binary readyState");
+ is(onloadHasRunBinary, false, "binary loading must be async");
+ is(onloadStartHasRunBinary, true, "binary loadstart should fire sync");
+ expectedTestCount++;
+
+ var onloadHasRunArrayBuffer = false;
+ var onloadStartHasRunArrayBuffer = false;
+ r = new FileReader();
+ is(r.readyState, FileReader.EMPTY, "correct initial arrayBuffer readyState");
+ r.addEventListener("load", function() { onloadHasRunArrayBuffer = true }, false);
+ r.addEventListener("loadstart", function() { onloadStartHasRunArrayBuffer = true }, false);
+ r.readAsArrayBuffer(binaryFile);
+ r.onload = getLoadHandlerForArrayBuffer(testBinaryData, testBinaryData.length, "array buffer reading");
+ is(r.readyState, FileReader.LOADING, "correct loading arrayBuffer readyState");
+ is(onloadHasRunArrayBuffer, false, "arrayBuffer loading must be async");
+ is(onloadStartHasRunArrayBuffer, true, "arrayBuffer loadstart should fire sync");
+ expectedTestCount++;
+
+ // Test a variety of encodings, and make sure they work properly
+ r = new FileReader();
+ r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "no encoding reading");
+ r.readAsText(asciiFile, "");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "iso8859 reading");
+ r.readAsText(asciiFile, "iso-8859-1");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler(testTextData,
+ convertToUTF8(testTextData).length,
+ "utf8 reading");
+ r.readAsText(utf8TextFile, "utf8");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.readAsText(utf16TextFile, "utf-16");
+ r.onload = getLoadHandler(testTextData,
+ convertToUTF16(testTextData).length,
+ "utf16 reading");
+ expectedTestCount++;
+
+ // Test get result without reading
+ r = new FileReader();
+ is(r.readyState, FileReader.EMPTY,
+ "readyState in test reader get result without reading");
+ is(r.error, null,
+ "no error in test reader get result without reading");
+ is(r.result, null,
+ "result in test reader get result without reading");
+
+ // Test loading an empty file works (and doesn't crash!)
+ r = new FileReader();
+ r.onload = getLoadHandler("", 0, "empty no encoding reading");
+ r.readAsText(emptyFile, "");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler("", 0, "empty utf8 reading");
+ r.readAsText(emptyFile, "utf8");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler("", 0, "empty utf16 reading");
+ r.readAsText(emptyFile, "utf-16");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler("", 0, "empty binary string reading");
+ r.readAsBinaryString(emptyFile);
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandlerForArrayBuffer("", 0, "empty array buffer reading");
+ r.readAsArrayBuffer(emptyFile);
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler(convertToDataURL(""), 0, "empt binary string reading");
+ r.readAsDataURL(emptyFile);
+ expectedTestCount++;
+
+ // Test reusing a FileReader to read multiple times
+ r = new FileReader();
+ r.onload = getLoadHandler(testASCIIData,
+ testASCIIData.length,
+ "to-be-reused reading text")
+ var makeAnotherReadListener = function(event) {
+ r = event.target;
+ r.removeEventListener("load", makeAnotherReadListener, false);
+ r.onload = getLoadHandler(testASCIIData,
+ testASCIIData.length,
+ "reused reading text");
+ r.readAsText(asciiFile);
+ };
+ r.addEventListener("load", makeAnotherReadListener, false);
+ r.readAsText(asciiFile);
+ expectedTestCount += 2;
+
+ r = new FileReader();
+ r.onload = getLoadHandler(testBinaryData,
+ testBinaryData.length,
+ "to-be-reused reading binary")
+ var makeAnotherReadListener2 = function(event) {
+ r = event.target;
+ r.removeEventListener("load", makeAnotherReadListener2, false);
+ r.onload = getLoadHandler(testBinaryData,
+ testBinaryData.length,
+ "reused reading binary");
+ r.readAsBinaryString(binaryFile);
+ };
+ r.addEventListener("load", makeAnotherReadListener2, false);
+ r.readAsBinaryString(binaryFile);
+ expectedTestCount += 2;
+
+ r = new FileReader();
+ r.onload = getLoadHandler(convertToDataURL(testBinaryData),
+ testBinaryData.length,
+ "to-be-reused reading data url")
+ var makeAnotherReadListener3 = function(event) {
+ r = event.target;
+ r.removeEventListener("load", makeAnotherReadListener3, false);
+ r.onload = getLoadHandler(convertToDataURL(testBinaryData),
+ testBinaryData.length,
+ "reused reading data url");
+ r.readAsDataURL(binaryFile);
+ };
+ r.addEventListener("load", makeAnotherReadListener3, false);
+ r.readAsDataURL(binaryFile);
+ expectedTestCount += 2;
+
+ r = new FileReader();
+ r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
+ testBinaryData.length,
+ "to-be-reused reading arrayBuffer")
+ var makeAnotherReadListener4 = function(event) {
+ r = event.target;
+ r.removeEventListener("load", makeAnotherReadListener4, false);
+ r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
+ testBinaryData.length,
+ "reused reading arrayBuffer");
+ r.readAsArrayBuffer(binaryFile);
+ };
+ r.addEventListener("load", makeAnotherReadListener4, false);
+ r.readAsArrayBuffer(binaryFile);
+ expectedTestCount += 2;
+
+ // Test first reading as ArrayBuffer then read as something else
+ // (BinaryString) and doesn't crash
+ r = new FileReader();
+ r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
+ testBinaryData.length,
+ "to-be-reused reading arrayBuffer")
+ var makeAnotherReadListener5 = function(event) {
+ r = event.target;
+ r.removeEventListener("load", makeAnotherReadListener5, false);
+ r.onload = getLoadHandler(testBinaryData,
+ testBinaryData.length,
+ "reused reading binary string");
+ r.readAsBinaryString(binaryFile);
+ };
+ r.addEventListener("load", makeAnotherReadListener5, false);
+ r.readAsArrayBuffer(binaryFile);
+ expectedTestCount += 2;
+
+ //Test data-URI encoding on differing file sizes
+ is(dataurldata0.length % 3, 0, "Want to test data with length % 3 == 0");
+ r = new FileReader();
+ r.onload = getLoadHandler(convertToDataURL(dataurldata0),
+ dataurldata0.length,
+ "dataurl reading, %3 = 0");
+ r.readAsDataURL(dataUrlFile0);
+ expectedTestCount++;
+
+ is(dataurldata1.length % 3, 1, "Want to test data with length % 3 == 1");
+ r = new FileReader();
+ r.onload = getLoadHandler(convertToDataURL(dataurldata1),
+ dataurldata1.length,
+ "dataurl reading, %3 = 1");
+ r.readAsDataURL(dataUrlFile1);
+ expectedTestCount++;
+
+ is(dataurldata2.length % 3, 2, "Want to test data with length % 3 == 2");
+ r = new FileReader();
+ r.onload = getLoadHandler(convertToDataURL(dataurldata2),
+ dataurldata2.length,
+ "dataurl reading, %3 = 2");
+ r.readAsDataURL(dataUrlFile2),
+ expectedTestCount++;
+
+
+ // Test abort()
+ var abortHasRun = false;
+ var loadEndHasRun = false;
+ r = new FileReader();
+ r.onabort = function (event) {
+ is(abortHasRun, false, "abort should only fire once");
+ is(loadEndHasRun, false, "loadend shouldn't have fired yet");
+ abortHasRun = true;
+ is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
+ is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
+ is(event.target.result, null, "file data should be null on aborted reads");
+ }
+ r.onloadend = function (event) {
+ is(abortHasRun, true, "abort should fire before loadend");
+ is(loadEndHasRun, false, "loadend should only fire once");
+ loadEndHasRun = true;
+ is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
+ is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
+ is(event.target.result, null, "file data should be null on aborted reads");
+ }
+ r.onload = function() { ok(false, "load should not fire for aborted reads") };
+ r.onerror = function() { ok(false, "error should not fire for aborted reads") };
+ r.onprogress = function() { ok(false, "progress should not fire for aborted reads") };
+ var abortThrew = false;
+ try {
+ r.abort();
+ } catch(e) {
+ abortThrew = true;
+ }
+ is(abortThrew, true, "abort() must throw if not loading");
+ is(abortHasRun, false, "abort() is a no-op unless loading");
+ r.readAsText(asciiFile);
+ r.abort();
+ is(abortHasRun, true, "abort should fire sync");
+ is(loadEndHasRun, true, "loadend should fire sync");
+
+ // Test calling readAsX to cause abort()
+ var reuseAbortHasRun = false;
+ r = new FileReader();
+ r.onabort = function (event) {
+ is(reuseAbortHasRun, false, "abort should only fire once");
+ reuseAbortHasRun = true;
+ is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
+ is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
+ is(event.target.result, null, "file data should be null on aborted reads");
+ }
+ r.onload = function() { ok(false, "load should not fire for aborted reads") };
+ var abortThrew = false;
+ try {
+ r.abort();
+ } catch(e) {
+ abortThrew = true;
+ }
+ is(abortThrew, true, "abort() must throw if not loading");
+ is(reuseAbortHasRun, false, "abort() is a no-op unless loading");
+ r.readAsText(asciiFile);
+ r.readAsText(asciiFile);
+ is(reuseAbortHasRun, true, "abort should fire sync");
+ r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "reuse-as-abort reading");
+ expectedTestCount++;
+
+
+ // Test reading from nonexistent files
+ r = new FileReader();
+ var didThrow = false;
+ r.onerror = function (event) {
+ is(event.target.readyState, FileReader.DONE, "should be DONE while firing onerror");
+ is(event.target.error.name, "NotFoundError", "error set to NotFoundError for nonexistent files");
+ is(event.target.result, null, "file data should be null on aborted reads");
+ testHasRun();
+ };
+ r.onload = function (event) {
+ is(false, "nonexistent file shouldn't load! (FIXME: bug 1122788)");
+ testHasRun();
+ };
+ try {
+ r.readAsDataURL(nonExistingFile);
+ expectedTestCount++;
+ } catch(ex) {
+ didThrow = true;
+ }
+ // Once this test passes, we should test that onerror gets called and
+ // that the FileReader object is in the right state during that call.
+ is(didThrow, false, "shouldn't throw when opening nonexistent file, should fire error instead");
+
+
+ function getLoadHandler(expectedResult, expectedLength, testName) {
+ return function (event) {
+ is(event.target.readyState, FileReader.DONE,
+ "readyState in test " + testName);
+ is(event.target.error, null,
+ "no error in test " + testName);
+ is(event.target.result, expectedResult,
+ "result in test " + testName);
+ is(event.lengthComputable, true,
+ "lengthComputable in test " + testName);
+ is(event.loaded, expectedLength,
+ "loaded in test " + testName);
+ is(event.total, expectedLength,
+ "total in test " + testName);
+ testHasRun();
+ }
+ }
+
+ function getLoadHandlerForArrayBuffer(expectedResult, expectedLength, testName) {
+ return function (event) {
+ is(event.target.readyState, FileReader.DONE,
+ "readyState in test " + testName);
+ is(event.target.error, null,
+ "no error in test " + testName);
+ is(event.lengthComputable, true,
+ "lengthComputable in test " + testName);
+ is(event.loaded, expectedLength,
+ "loaded in test " + testName);
+ is(event.total, expectedLength,
+ "total in test " + testName);
+ is(event.target.result.byteLength, expectedLength,
+ "array buffer size in test " + testName);
+ var u8v = new Uint8Array(event.target.result);
+ is(String.fromCharCode.apply(String, u8v), expectedResult,
+ "array buffer contents in test " + testName);
+ u8v = null;
+ is(event.target.result.byteLength, expectedLength,
+ "array buffer size after gc in test " + testName);
+ u8v = new Uint8Array(event.target.result);
+ is(String.fromCharCode.apply(String, u8v), expectedResult,
+ "array buffer contents after gc in test " + testName);
+ testHasRun();
+ }
+ }
+
+ function testHasRun() {
+ //alert(testRanCounter);
+ ++testRanCounter;
+ if (testRanCounter == expectedTestCount) {
+ is(testSetupFinished, true, "test setup should have finished; check for exceptions");
+ is(onloadHasRunText, true, "onload text should have fired by now");
+ is(onloadHasRunBinary, true, "onload binary should have fired by now");
+ finish();
+ }
+ }
+
+ testSetupFinished = true;
+}
diff --git a/dom/workers/test/worker_referrer.js b/dom/workers/test/worker_referrer.js
new file mode 100644
index 000000000..227a77caf
--- /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 000000000..2d8e5e6c2
--- /dev/null
+++ b/dom/workers/test/worker_setTimeoutWith0.js
@@ -0,0 +1,3 @@
+var x = 0;
+setTimeout("x++; '\x00'; x++;");
+setTimeout("postMessage(x);");
diff --git a/dom/workers/test/worker_suspended.js b/dom/workers/test/worker_suspended.js
new file mode 100644
index 000000000..3b53fcea2
--- /dev/null
+++ b/dom/workers/test/worker_suspended.js
@@ -0,0 +1,31 @@
+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 000000000..c385803c4
--- /dev/null
+++ b/dom/workers/test/worker_wrapper.js
@@ -0,0 +1,99 @@
+// 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: permissions
+ });
+}
+
+function workerTestGetVersion(cb) {
+ addEventListener('message', function workerTestGetVersionCB(e) {
+ if (e.data.type !== 'returnVersion') {
+ return;
+ }
+ removeEventListener('message', workerTestGetVersionCB);
+ cb(e.data.result);
+ });
+ postMessage({
+ type: 'getVersion'
+ });
+}
+
+function workerTestGetUserAgent(cb) {
+ addEventListener('message', function workerTestGetUserAgentCB(e) {
+ if (e.data.type !== 'returnUserAgent') {
+ return;
+ }
+ removeEventListener('message', workerTestGetUserAgentCB);
+ cb(e.data.result);
+ });
+ postMessage({
+ type: 'getUserAgent'
+ });
+}
+
+function workerTestGetOSCPU(cb) {
+ addEventListener('message', function workerTestGetOSCPUCB(e) {
+ if (e.data.type !== 'returnOSCPU') {
+ return;
+ }
+ removeEventListener('message', workerTestGetOSCPUCB);
+ cb(e.data.result);
+ });
+ postMessage({
+ type: 'getOSCPU'
+ });
+}
+
+addEventListener('message', function workerWrapperOnMessage(e) {
+ removeEventListener('message', workerWrapperOnMessage);
+ var data = e.data;
+ try {
+ importScripts(data.script);
+ } catch(e) {
+ postMessage({
+ type: 'status',
+ status: false,
+ msg: 'worker failed to import ' + data.script + "; error: " + e.message
+ });
+ }
+});
diff --git a/dom/workers/test/workersDisabled_worker.js b/dom/workers/test/workersDisabled_worker.js
new file mode 100644
index 000000000..7346fc142
--- /dev/null
+++ b/dom/workers/test/workersDisabled_worker.js
@@ -0,0 +1,7 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+onmessage = function(event) {
+ postMessage(event.data);
+}
diff --git a/dom/workers/test/xpcshell/data/chrome.manifest b/dom/workers/test/xpcshell/data/chrome.manifest
new file mode 100644
index 000000000..611e81fd4
--- /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 000000000..9a83bb128
--- /dev/null
+++ b/dom/workers/test/xpcshell/data/worker.js
@@ -0,0 +1,7 @@
+/* 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 000000000..b27366d17
--- /dev/null
+++ b/dom/workers/test/xpcshell/data/worker_fileReader.js
@@ -0,0 +1,8 @@
+/* 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_fileReader.js b/dom/workers/test/xpcshell/test_fileReader.js
new file mode 100644
index 000000000..0e283fbe0
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_fileReader.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/Promise.jsm");
+
+// 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 run_test() {
+ run_next_test();
+}
+
+function talk_with_worker(worker) {
+ let deferred = Promise.defer();
+ worker.onmessage = function(event) {
+ let success = true;
+ if (event.data == "OK") {
+ deferred.resolve();
+ } else {
+ success = false;
+ deferred.reject(event);
+ }
+ do_check_true(success);
+ worker.terminate();
+ };
+ worker.onerror = function(event) {
+ let error = new Error(event.message, event.filename, event.lineno);
+ worker.terminate();
+ deferred.reject(error);
+ };
+ worker.postMessage("START");
+ return deferred.promise;
+}
+
+
+add_task(function test_chrome_worker() {
+ return talk_with_worker(new ChromeWorker(WORKER_SOURCE_URI));
+});
diff --git a/dom/workers/test/xpcshell/test_workers.js b/dom/workers/test/xpcshell/test_workers.js
new file mode 100644
index 000000000..c06e62af3
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_workers.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/Promise.jsm");
+
+// 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 run_test() {
+ run_next_test();
+}
+
+function talk_with_worker(worker) {
+ let deferred = Promise.defer();
+ worker.onmessage = function(event) {
+ let success = true;
+ if (event.data == "OK") {
+ deferred.resolve();
+ } else {
+ success = false;
+ deferred.reject(event);
+ }
+ do_check_true(success);
+ worker.terminate();
+ };
+ worker.onerror = function(event) {
+ let error = new Error(event.message, event.filename, event.lineno);
+ worker.terminate();
+ deferred.reject(error);
+ };
+ worker.postMessage("START");
+ return deferred.promise;
+}
+
+
+add_task(function test_chrome_worker() {
+ return talk_with_worker(new ChromeWorker(WORKER_SOURCE_URI));
+});
+
+add_task(function test_worker() {
+ return talk_with_worker(new Worker(WORKER_SOURCE_URI));
+});
diff --git a/dom/workers/test/xpcshell/xpcshell.ini b/dom/workers/test/xpcshell/xpcshell.ini
new file mode 100644
index 000000000..917b842f5
--- /dev/null
+++ b/dom/workers/test/xpcshell/xpcshell.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+head =
+tail =
+skip-if = toolkit == 'android'
+support-files =
+ data/worker.js
+ data/worker_fileReader.js
+ data/chrome.manifest
+
+[test_workers.js]
+[test_fileReader.js]