summaryrefslogtreecommitdiffstats
path: root/dom/serviceworkers/test
diff options
context:
space:
mode:
Diffstat (limited to 'dom/serviceworkers/test')
-rw-r--r--dom/serviceworkers/test/ForceRefreshChild.sys.mjs12
-rw-r--r--dom/serviceworkers/test/ForceRefreshParent.sys.mjs79
-rw-r--r--dom/serviceworkers/test/abrupt_completion_worker.js18
-rw-r--r--dom/serviceworkers/test/activate_event_error_worker.js4
-rw-r--r--dom/serviceworkers/test/async_waituntil_worker.js53
-rw-r--r--dom/serviceworkers/test/blocking_install_event_worker.js22
-rw-r--r--dom/serviceworkers/test/browser-common.toml64
-rw-r--r--dom/serviceworkers/test/browser-dFPI.toml6
-rw-r--r--dom/serviceworkers/test/browser.toml4
-rw-r--r--dom/serviceworkers/test/browser_antitracking.js106
-rw-r--r--dom/serviceworkers/test/browser_antitracking_subiframes.js106
-rw-r--r--dom/serviceworkers/test/browser_base_force_refresh.html27
-rw-r--r--dom/serviceworkers/test/browser_cached_force_refresh.html60
-rw-r--r--dom/serviceworkers/test/browser_devtools_serviceworker_interception.js270
-rw-r--r--dom/serviceworkers/test/browser_download.js93
-rw-r--r--dom/serviceworkers/test/browser_download_canceled.js174
-rw-r--r--dom/serviceworkers/test/browser_force_refresh.js86
-rw-r--r--dom/serviceworkers/test/browser_head.js318
-rw-r--r--dom/serviceworkers/test/browser_intercepted_channel_process_swap.js110
-rw-r--r--dom/serviceworkers/test/browser_intercepted_worker_script.js102
-rw-r--r--dom/serviceworkers/test/browser_navigationPreload_read_after_respondWith.js121
-rw-r--r--dom/serviceworkers/test/browser_navigation_fetch_fault_handling.js276
-rw-r--r--dom/serviceworkers/test/browser_remote_type_process_swap.js138
-rw-r--r--dom/serviceworkers/test/browser_storage_permission.js297
-rw-r--r--dom/serviceworkers/test/browser_storage_recovery.js156
-rw-r--r--dom/serviceworkers/test/browser_unregister_with_containers.js153
-rw-r--r--dom/serviceworkers/test/browser_userContextId_openWindow.js161
-rw-r--r--dom/serviceworkers/test/bug1151916_driver.html53
-rw-r--r--dom/serviceworkers/test/bug1151916_worker.js15
-rw-r--r--dom/serviceworkers/test/bug1240436_worker.js2
-rw-r--r--dom/serviceworkers/test/chrome-common.toml26
-rw-r--r--dom/serviceworkers/test/chrome-dFPI.toml6
-rw-r--r--dom/serviceworkers/test/chrome.toml4
-rw-r--r--dom/serviceworkers/test/chrome_helpers.js71
-rw-r--r--dom/serviceworkers/test/claim_clients/client.html43
-rw-r--r--dom/serviceworkers/test/claim_oninstall_worker.js7
-rw-r--r--dom/serviceworkers/test/claim_worker_1.js32
-rw-r--r--dom/serviceworkers/test/claim_worker_2.js34
-rw-r--r--dom/serviceworkers/test/close_test.js22
-rw-r--r--dom/serviceworkers/test/console_monitor.js44
-rw-r--r--dom/serviceworkers/test/controller/index.html72
-rw-r--r--dom/serviceworkers/test/create_another_sharedWorker.html6
-rw-r--r--dom/serviceworkers/test/download/window.html47
-rw-r--r--dom/serviceworkers/test/download/worker.js34
-rw-r--r--dom/serviceworkers/test/download_canceled/page_download_canceled.html59
-rw-r--r--dom/serviceworkers/test/download_canceled/server-stream-download.sjs132
-rw-r--r--dom/serviceworkers/test/download_canceled/sw_download_canceled.js151
-rw-r--r--dom/serviceworkers/test/empty.html0
-rw-r--r--dom/serviceworkers/test/empty.js0
-rw-r--r--dom/serviceworkers/test/empty_with_utils.html13
-rw-r--r--dom/serviceworkers/test/error_reporting_helpers.js73
-rw-r--r--dom/serviceworkers/test/eval_worker.js2
-rw-r--r--dom/serviceworkers/test/eventsource/eventsource.resource22
-rw-r--r--dom/serviceworkers/test/eventsource/eventsource.resource^headers^3
-rw-r--r--dom/serviceworkers/test/eventsource/eventsource_cors_response.html75
-rw-r--r--dom/serviceworkers/test/eventsource/eventsource_cors_response_intercept_worker.js30
-rw-r--r--dom/serviceworkers/test/eventsource/eventsource_mixed_content_cors_response.html75
-rw-r--r--dom/serviceworkers/test/eventsource/eventsource_mixed_content_cors_response_intercept_worker.js29
-rw-r--r--dom/serviceworkers/test/eventsource/eventsource_opaque_response.html75
-rw-r--r--dom/serviceworkers/test/eventsource/eventsource_opaque_response_intercept_worker.js30
-rw-r--r--dom/serviceworkers/test/eventsource/eventsource_register_worker.html27
-rw-r--r--dom/serviceworkers/test/eventsource/eventsource_synthetic_response.html75
-rw-r--r--dom/serviceworkers/test/eventsource/eventsource_synthetic_response_intercept_worker.js27
-rw-r--r--dom/serviceworkers/test/eventsource/eventsource_worker_helper.js17
-rw-r--r--dom/serviceworkers/test/fetch.js33
-rw-r--r--dom/serviceworkers/test/fetch/cookie/cookie_test.js11
-rw-r--r--dom/serviceworkers/test/fetch/cookie/register.html19
-rw-r--r--dom/serviceworkers/test/fetch/cookie/unregister.html12
-rw-r--r--dom/serviceworkers/test/fetch/deliver-gzip.sjs21
-rw-r--r--dom/serviceworkers/test/fetch/fetch_tests.js716
-rw-r--r--dom/serviceworkers/test/fetch/fetch_worker_script.js28
-rw-r--r--dom/serviceworkers/test/fetch/hsts/embedder.html7
-rw-r--r--dom/serviceworkers/test/fetch/hsts/hsts_test.js11
-rw-r--r--dom/serviceworkers/test/fetch/hsts/image-20px.pngbin0 -> 87 bytes
-rw-r--r--dom/serviceworkers/test/fetch/hsts/image-40px.pngbin0 -> 123 bytes
-rw-r--r--dom/serviceworkers/test/fetch/hsts/image.html13
-rw-r--r--dom/serviceworkers/test/fetch/hsts/realindex.html8
-rw-r--r--dom/serviceworkers/test/fetch/hsts/register.html14
-rw-r--r--dom/serviceworkers/test/fetch/hsts/register.html^headers^2
-rw-r--r--dom/serviceworkers/test/fetch/hsts/unregister.html12
-rw-r--r--dom/serviceworkers/test/fetch/https/clonedresponse/https_test.js19
-rw-r--r--dom/serviceworkers/test/fetch/https/clonedresponse/index.html4
-rw-r--r--dom/serviceworkers/test/fetch/https/clonedresponse/register.html14
-rw-r--r--dom/serviceworkers/test/fetch/https/clonedresponse/unregister.html12
-rw-r--r--dom/serviceworkers/test/fetch/https/https_test.js31
-rw-r--r--dom/serviceworkers/test/fetch/https/index.html4
-rw-r--r--dom/serviceworkers/test/fetch/https/register.html20
-rw-r--r--dom/serviceworkers/test/fetch/https/unregister.html12
-rw-r--r--dom/serviceworkers/test/fetch/imagecache-maxage/image-20px.pngbin0 -> 87 bytes
-rw-r--r--dom/serviceworkers/test/fetch/imagecache-maxage/image-40px.pngbin0 -> 123 bytes
-rw-r--r--dom/serviceworkers/test/fetch/imagecache-maxage/index.html29
-rw-r--r--dom/serviceworkers/test/fetch/imagecache-maxage/maxage_test.js45
-rw-r--r--dom/serviceworkers/test/fetch/imagecache-maxage/register.html14
-rw-r--r--dom/serviceworkers/test/fetch/imagecache-maxage/unregister.html12
-rw-r--r--dom/serviceworkers/test/fetch/imagecache/image-20px.pngbin0 -> 87 bytes
-rw-r--r--dom/serviceworkers/test/fetch/imagecache/image-40px.pngbin0 -> 123 bytes
-rw-r--r--dom/serviceworkers/test/fetch/imagecache/imagecache_test.js15
-rw-r--r--dom/serviceworkers/test/fetch/imagecache/index.html20
-rw-r--r--dom/serviceworkers/test/fetch/imagecache/postmortem.html9
-rw-r--r--dom/serviceworkers/test/fetch/imagecache/register.html16
-rw-r--r--dom/serviceworkers/test/fetch/imagecache/unregister.html12
-rw-r--r--dom/serviceworkers/test/fetch/importscript-mixedcontent/https_test.js31
-rw-r--r--dom/serviceworkers/test/fetch/importscript-mixedcontent/register.html14
-rw-r--r--dom/serviceworkers/test/fetch/importscript-mixedcontent/unregister.html12
-rw-r--r--dom/serviceworkers/test/fetch/index.html191
-rw-r--r--dom/serviceworkers/test/fetch/interrupt.sjs20
-rw-r--r--dom/serviceworkers/test/fetch/origin/https/index-https.sjs8
-rw-r--r--dom/serviceworkers/test/fetch/origin/https/origin_test.js29
-rw-r--r--dom/serviceworkers/test/fetch/origin/https/realindex.html6
-rw-r--r--dom/serviceworkers/test/fetch/origin/https/realindex.html^headers^1
-rw-r--r--dom/serviceworkers/test/fetch/origin/https/register.html14
-rw-r--r--dom/serviceworkers/test/fetch/origin/https/unregister.html12
-rw-r--r--dom/serviceworkers/test/fetch/origin/index-to-https.sjs8
-rw-r--r--dom/serviceworkers/test/fetch/origin/index.sjs8
-rw-r--r--dom/serviceworkers/test/fetch/origin/origin_test.js38
-rw-r--r--dom/serviceworkers/test/fetch/origin/realindex.html6
-rw-r--r--dom/serviceworkers/test/fetch/origin/realindex.html^headers^1
-rw-r--r--dom/serviceworkers/test/fetch/origin/register.html14
-rw-r--r--dom/serviceworkers/test/fetch/origin/unregister.html12
-rw-r--r--dom/serviceworkers/test/fetch/plugin/plugins.html43
-rw-r--r--dom/serviceworkers/test/fetch/plugin/worker.js15
-rw-r--r--dom/serviceworkers/test/fetch/real-file.txt1
-rw-r--r--dom/serviceworkers/test/fetch/redirect.sjs4
-rw-r--r--dom/serviceworkers/test/fetch/requesturl/index.html7
-rw-r--r--dom/serviceworkers/test/fetch/requesturl/redirect.sjs8
-rw-r--r--dom/serviceworkers/test/fetch/requesturl/redirector.html2
-rw-r--r--dom/serviceworkers/test/fetch/requesturl/register.html14
-rw-r--r--dom/serviceworkers/test/fetch/requesturl/requesturl_test.js21
-rw-r--r--dom/serviceworkers/test/fetch/requesturl/secret.html5
-rw-r--r--dom/serviceworkers/test/fetch/requesturl/unregister.html12
-rw-r--r--dom/serviceworkers/test/fetch/sandbox/index.html5
-rw-r--r--dom/serviceworkers/test/fetch/sandbox/intercepted_index.html5
-rw-r--r--dom/serviceworkers/test/fetch/sandbox/register.html14
-rw-r--r--dom/serviceworkers/test/fetch/sandbox/sandbox_test.js5
-rw-r--r--dom/serviceworkers/test/fetch/sandbox/unregister.html12
-rw-r--r--dom/serviceworkers/test/fetch/upgrade-insecure/embedder.html10
-rw-r--r--dom/serviceworkers/test/fetch/upgrade-insecure/embedder.html^headers^1
-rw-r--r--dom/serviceworkers/test/fetch/upgrade-insecure/image-20px.pngbin0 -> 87 bytes
-rw-r--r--dom/serviceworkers/test/fetch/upgrade-insecure/image-40px.pngbin0 -> 123 bytes
-rw-r--r--dom/serviceworkers/test/fetch/upgrade-insecure/image.html13
-rw-r--r--dom/serviceworkers/test/fetch/upgrade-insecure/realindex.html4
-rw-r--r--dom/serviceworkers/test/fetch/upgrade-insecure/register.html14
-rw-r--r--dom/serviceworkers/test/fetch/upgrade-insecure/unregister.html12
-rw-r--r--dom/serviceworkers/test/fetch/upgrade-insecure/upgrade-insecure_test.js11
-rw-r--r--dom/serviceworkers/test/fetch_event_worker.js365
-rw-r--r--dom/serviceworkers/test/file_blob_response_worker.js39
-rw-r--r--dom/serviceworkers/test/file_js_cache.html10
-rw-r--r--dom/serviceworkers/test/file_js_cache.js5
-rw-r--r--dom/serviceworkers/test/file_js_cache_cleanup.js16
-rw-r--r--dom/serviceworkers/test/file_js_cache_save_after_load.html10
-rw-r--r--dom/serviceworkers/test/file_js_cache_save_after_load.js15
-rw-r--r--dom/serviceworkers/test/file_js_cache_syntax_error.html10
-rw-r--r--dom/serviceworkers/test/file_js_cache_syntax_error.js1
-rw-r--r--dom/serviceworkers/test/file_js_cache_with_sri.html12
-rw-r--r--dom/serviceworkers/test/file_notification_openWindow.html26
-rw-r--r--dom/serviceworkers/test/file_userContextId_openWindow.js3
-rw-r--r--dom/serviceworkers/test/force_refresh_browser_worker.js42
-rw-r--r--dom/serviceworkers/test/force_refresh_worker.js43
-rw-r--r--dom/serviceworkers/test/gtest/TestReadWrite.cpp955
-rw-r--r--dom/serviceworkers/test/gtest/moz.build13
-rw-r--r--dom/serviceworkers/test/gzip_redirect_worker.js15
-rw-r--r--dom/serviceworkers/test/header_checker.sjs9
-rw-r--r--dom/serviceworkers/test/hello.html9
-rw-r--r--dom/serviceworkers/test/importscript.sjs11
-rw-r--r--dom/serviceworkers/test/importscript_worker.js46
-rw-r--r--dom/serviceworkers/test/install_event_error_worker.js9
-rw-r--r--dom/serviceworkers/test/install_event_worker.js3
-rw-r--r--dom/serviceworkers/test/intercepted_channel_process_swap_worker.js7
-rw-r--r--dom/serviceworkers/test/isolated/README.md19
-rw-r--r--dom/serviceworkers/test/isolated/multi-e10s-update/browser.toml8
-rw-r--r--dom/serviceworkers/test/isolated/multi-e10s-update/browser_multie10s_update.js147
-rw-r--r--dom/serviceworkers/test/isolated/multi-e10s-update/file_multie10s_update.html40
-rw-r--r--dom/serviceworkers/test/isolated/multi-e10s-update/server_multie10s_update.sjs99
-rw-r--r--dom/serviceworkers/test/lazy_worker.js8
-rw-r--r--dom/serviceworkers/test/lorem_script.js8
-rw-r--r--dom/serviceworkers/test/match_all_advanced_worker.js5
-rw-r--r--dom/serviceworkers/test/match_all_client/match_all_client_id.html31
-rw-r--r--dom/serviceworkers/test/match_all_client_id_worker.js28
-rw-r--r--dom/serviceworkers/test/match_all_clients/match_all_controlled.html83
-rw-r--r--dom/serviceworkers/test/match_all_properties_worker.js27
-rw-r--r--dom/serviceworkers/test/match_all_worker.js10
-rw-r--r--dom/serviceworkers/test/message_posting_worker.js8
-rw-r--r--dom/serviceworkers/test/message_receiver.html6
-rw-r--r--dom/serviceworkers/test/mochitest-common.toml494
-rw-r--r--dom/serviceworkers/test/mochitest-dFPI.toml10
-rw-r--r--dom/serviceworkers/test/mochitest.toml56
-rw-r--r--dom/serviceworkers/test/navigationPreload_page.html1
-rw-r--r--dom/serviceworkers/test/network_with_utils.html14
-rw-r--r--dom/serviceworkers/test/nofetch_handler_worker.js14
-rw-r--r--dom/serviceworkers/test/notification/register.html11
-rw-r--r--dom/serviceworkers/test/notification_constructor_error.js1
-rw-r--r--dom/serviceworkers/test/notification_get_sw.js0
-rw-r--r--dom/serviceworkers/test/notification_openWindow_worker.js25
-rw-r--r--dom/serviceworkers/test/notificationclick-otherwindow.html30
-rw-r--r--dom/serviceworkers/test/notificationclick.html27
-rw-r--r--dom/serviceworkers/test/notificationclick.js23
-rw-r--r--dom/serviceworkers/test/notificationclick_focus.html28
-rw-r--r--dom/serviceworkers/test/notificationclick_focus.js49
-rw-r--r--dom/serviceworkers/test/notificationclose.html37
-rw-r--r--dom/serviceworkers/test/notificationclose.js31
-rw-r--r--dom/serviceworkers/test/notify_loaded.js1
-rw-r--r--dom/serviceworkers/test/onmessageerror_worker.js55
-rw-r--r--dom/serviceworkers/test/opaque_intercept_worker.js40
-rw-r--r--dom/serviceworkers/test/openWindow_worker.js178
-rw-r--r--dom/serviceworkers/test/open_window/client.sjs68
-rw-r--r--dom/serviceworkers/test/page_post_controlled.html27
-rw-r--r--dom/serviceworkers/test/parse_error_worker.js2
-rw-r--r--dom/serviceworkers/test/performance/intercepted.txt1
-rw-r--r--dom/serviceworkers/test/performance/perftest.toml14
-rw-r--r--dom/serviceworkers/test/performance/perfutils.js46
-rw-r--r--dom/serviceworkers/test/performance/sw_cacher.js18
-rw-r--r--dom/serviceworkers/test/performance/sw_empty.js0
-rw-r--r--dom/serviceworkers/test/performance/sw_intercept_target.js7
-rw-r--r--dom/serviceworkers/test/performance/target.txt1
-rw-r--r--dom/serviceworkers/test/performance/test_caching.html89
-rw-r--r--dom/serviceworkers/test/performance/test_fetch.html168
-rw-r--r--dom/serviceworkers/test/performance/test_registration.html89
-rw-r--r--dom/serviceworkers/test/performance/time_fetch.html38
-rw-r--r--dom/serviceworkers/test/pref/fetch_nonexistent_file.html15
-rw-r--r--dom/serviceworkers/test/pref/intercept_nonexistent_file_sw.js5
-rw-r--r--dom/serviceworkers/test/redirect.sjs4
-rw-r--r--dom/serviceworkers/test/redirect_post.sjs39
-rw-r--r--dom/serviceworkers/test/redirect_serviceworker.sjs7
-rw-r--r--dom/serviceworkers/test/register_https.html15
-rw-r--r--dom/serviceworkers/test/sanitize/example_check_and_unregister.html22
-rw-r--r--dom/serviceworkers/test/sanitize/frame.html11
-rw-r--r--dom/serviceworkers/test/sanitize/register.html9
-rw-r--r--dom/serviceworkers/test/sanitize_worker.js5
-rw-r--r--dom/serviceworkers/test/scope/scope_worker.js2
-rw-r--r--dom/serviceworkers/test/script_file_upload.js16
-rw-r--r--dom/serviceworkers/test/self_update_worker.sjs42
-rw-r--r--dom/serviceworkers/test/server_file_upload.sjs22
-rw-r--r--dom/serviceworkers/test/service_worker.js9
-rw-r--r--dom/serviceworkers/test/service_worker_client.html28
-rw-r--r--dom/serviceworkers/test/serviceworker.html12
-rw-r--r--dom/serviceworkers/test/serviceworker_not_sharedworker.js20
-rw-r--r--dom/serviceworkers/test/serviceworker_wrapper.js92
-rw-r--r--dom/serviceworkers/test/serviceworkerinfo_iframe.html27
-rw-r--r--dom/serviceworkers/test/serviceworkermanager_iframe.html34
-rw-r--r--dom/serviceworkers/test/serviceworkerregistrationinfo_iframe.html30
-rw-r--r--dom/serviceworkers/test/sharedWorker_fetch.js30
-rw-r--r--dom/serviceworkers/test/simple_fetch_worker.js18
-rw-r--r--dom/serviceworkers/test/simpleregister/index.html51
-rw-r--r--dom/serviceworkers/test/simpleregister/ready.html14
-rw-r--r--dom/serviceworkers/test/skip_waiting_installed_worker.js6
-rw-r--r--dom/serviceworkers/test/skip_waiting_scope/index.html33
-rw-r--r--dom/serviceworkers/test/source_message_posting_worker.js16
-rw-r--r--dom/serviceworkers/test/storage_recovery_worker.sjs23
-rw-r--r--dom/serviceworkers/test/streamfilter_server.sjs7
-rw-r--r--dom/serviceworkers/test/streamfilter_worker.js9
-rw-r--r--dom/serviceworkers/test/strict_mode_warning.js5
-rw-r--r--dom/serviceworkers/test/sw_bad_mime_type.js1
-rw-r--r--dom/serviceworkers/test/sw_bad_mime_type.js^headers^1
-rw-r--r--dom/serviceworkers/test/sw_clients/file_blob_upload_frame.html76
-rw-r--r--dom/serviceworkers/test/sw_clients/navigator.html34
-rw-r--r--dom/serviceworkers/test/sw_clients/refresher.html38
-rw-r--r--dom/serviceworkers/test/sw_clients/refresher_cached.html37
-rw-r--r--dom/serviceworkers/test/sw_clients/refresher_cached_compressed.htmlbin0 -> 560 bytes
-rw-r--r--dom/serviceworkers/test/sw_clients/refresher_cached_compressed.html^headers^2
-rw-r--r--dom/serviceworkers/test/sw_clients/refresher_compressed.htmlbin0 -> 609 bytes
-rw-r--r--dom/serviceworkers/test/sw_clients/refresher_compressed.html^headers^2
-rw-r--r--dom/serviceworkers/test/sw_clients/service_worker_controlled.html38
-rw-r--r--dom/serviceworkers/test/sw_clients/simple.html29
-rw-r--r--dom/serviceworkers/test/sw_file_upload.js16
-rw-r--r--dom/serviceworkers/test/sw_respondwith_serviceworker.js24
-rw-r--r--dom/serviceworkers/test/sw_storage_not_allow.js33
-rw-r--r--dom/serviceworkers/test/sw_with_navigationPreload.js28
-rw-r--r--dom/serviceworkers/test/swa/worker_scope_different.js0
-rw-r--r--dom/serviceworkers/test/swa/worker_scope_different.js^headers^1
-rw-r--r--dom/serviceworkers/test/swa/worker_scope_different2.js0
-rw-r--r--dom/serviceworkers/test/swa/worker_scope_different2.js^headers^1
-rw-r--r--dom/serviceworkers/test/swa/worker_scope_precise.js0
-rw-r--r--dom/serviceworkers/test/swa/worker_scope_precise.js^headers^1
-rw-r--r--dom/serviceworkers/test/swa/worker_scope_too_deep.js0
-rw-r--r--dom/serviceworkers/test/swa/worker_scope_too_deep.js^headers^1
-rw-r--r--dom/serviceworkers/test/swa/worker_scope_too_narrow.js0
-rw-r--r--dom/serviceworkers/test/swa/worker_scope_too_narrow.js^headers^1
-rw-r--r--dom/serviceworkers/test/test_abrupt_completion.html144
-rw-r--r--dom/serviceworkers/test/test_async_waituntil.html91
-rw-r--r--dom/serviceworkers/test/test_bad_script_cache.html95
-rw-r--r--dom/serviceworkers/test/test_bug1151916.html103
-rw-r--r--dom/serviceworkers/test/test_bug1240436.html34
-rw-r--r--dom/serviceworkers/test/test_bug1408734.html52
-rw-r--r--dom/serviceworkers/test/test_claim.html171
-rw-r--r--dom/serviceworkers/test/test_claim_oninstall.html77
-rw-r--r--dom/serviceworkers/test/test_controller.html83
-rw-r--r--dom/serviceworkers/test/test_cookie_fetch.html64
-rw-r--r--dom/serviceworkers/test/test_cross_origin_url_after_redirect.html50
-rw-r--r--dom/serviceworkers/test/test_csp_upgrade-insecure_intercept.html55
-rw-r--r--dom/serviceworkers/test/test_devtools_bypass_serviceworker.html106
-rw-r--r--dom/serviceworkers/test/test_devtools_track_serviceworker_time.html236
-rw-r--r--dom/serviceworkers/test/test_empty_serviceworker.html46
-rw-r--r--dom/serviceworkers/test/test_enabled_pref.html55
-rw-r--r--dom/serviceworkers/test/test_error_reporting.html241
-rw-r--r--dom/serviceworkers/test/test_escapedSlashes.html102
-rw-r--r--dom/serviceworkers/test/test_eval_allowed.html52
-rw-r--r--dom/serviceworkers/test/test_eval_allowed.html^headers^1
-rw-r--r--dom/serviceworkers/test/test_event_listener_leaks.html63
-rw-r--r--dom/serviceworkers/test/test_eventsource_intercept.html102
-rw-r--r--dom/serviceworkers/test/test_fetch_event.html75
-rw-r--r--dom/serviceworkers/test/test_fetch_event_with_thirdpartypref.html90
-rw-r--r--dom/serviceworkers/test/test_fetch_integrity.html228
-rw-r--r--dom/serviceworkers/test/test_file_blob_response.html78
-rw-r--r--dom/serviceworkers/test/test_file_blob_upload.html146
-rw-r--r--dom/serviceworkers/test/test_file_upload.html68
-rw-r--r--dom/serviceworkers/test/test_force_refresh.html104
-rw-r--r--dom/serviceworkers/test/test_gzip_redirect.html88
-rw-r--r--dom/serviceworkers/test/test_hsts_upgrade_intercept.html66
-rw-r--r--dom/serviceworkers/test/test_https_fetch.html61
-rw-r--r--dom/serviceworkers/test/test_https_fetch_cloned_response.html55
-rw-r--r--dom/serviceworkers/test/test_https_origin_after_redirect.html56
-rw-r--r--dom/serviceworkers/test/test_https_origin_after_redirect_cached.html56
-rw-r--r--dom/serviceworkers/test/test_https_synth_fetch_from_cached_sw.html68
-rw-r--r--dom/serviceworkers/test/test_imagecache.html55
-rw-r--r--dom/serviceworkers/test/test_imagecache_max_age.html71
-rw-r--r--dom/serviceworkers/test/test_importscript.html74
-rw-r--r--dom/serviceworkers/test/test_importscript_mixedcontent.html53
-rw-r--r--dom/serviceworkers/test/test_install_event.html143
-rw-r--r--dom/serviceworkers/test/test_install_event_gc.html120
-rw-r--r--dom/serviceworkers/test/test_installation_simple.html208
-rw-r--r--dom/serviceworkers/test/test_match_all.html83
-rw-r--r--dom/serviceworkers/test/test_match_all_advanced.html102
-rw-r--r--dom/serviceworkers/test/test_match_all_client_id.html95
-rw-r--r--dom/serviceworkers/test/test_match_all_client_properties.html101
-rw-r--r--dom/serviceworkers/test/test_navigationPreload_disable_crash.html52
-rw-r--r--dom/serviceworkers/test/test_navigator.html40
-rw-r--r--dom/serviceworkers/test/test_nofetch_handler.html57
-rw-r--r--dom/serviceworkers/test/test_not_intercept_plugin.html75
-rw-r--r--dom/serviceworkers/test/test_notification_constructor_error.html51
-rw-r--r--dom/serviceworkers/test/test_notification_get.html136
-rw-r--r--dom/serviceworkers/test/test_notification_openWindow.html89
-rw-r--r--dom/serviceworkers/test/test_notificationclick-otherwindow.html63
-rw-r--r--dom/serviceworkers/test/test_notificationclick.html64
-rw-r--r--dom/serviceworkers/test/test_notificationclick_focus.html64
-rw-r--r--dom/serviceworkers/test/test_notificationclose.html65
-rw-r--r--dom/serviceworkers/test/test_onmessageerror.html128
-rw-r--r--dom/serviceworkers/test/test_opaque_intercept.html92
-rw-r--r--dom/serviceworkers/test/test_openWindow.html110
-rw-r--r--dom/serviceworkers/test/test_origin_after_redirect.html57
-rw-r--r--dom/serviceworkers/test/test_origin_after_redirect_cached.html57
-rw-r--r--dom/serviceworkers/test/test_origin_after_redirect_to_https.html56
-rw-r--r--dom/serviceworkers/test/test_origin_after_redirect_to_https_cached.html56
-rw-r--r--dom/serviceworkers/test/test_post_message.html80
-rw-r--r--dom/serviceworkers/test/test_post_message_advanced.html109
-rw-r--r--dom/serviceworkers/test/test_post_message_source.html66
-rw-r--r--dom/serviceworkers/test/test_privateBrowsing.html105
-rw-r--r--dom/serviceworkers/test/test_register_base.html34
-rw-r--r--dom/serviceworkers/test/test_register_https_in_http.html45
-rw-r--r--dom/serviceworkers/test/test_sandbox_intercept.html56
-rw-r--r--dom/serviceworkers/test/test_sanitize.html86
-rw-r--r--dom/serviceworkers/test/test_sanitize_domain.html89
-rw-r--r--dom/serviceworkers/test/test_scopes.html143
-rw-r--r--dom/serviceworkers/test/test_script_loader_intercepted_js_cache.html224
-rw-r--r--dom/serviceworkers/test/test_self_update_worker.html136
-rw-r--r--dom/serviceworkers/test/test_service_worker_allowed.html74
-rw-r--r--dom/serviceworkers/test/test_serviceworker.html79
-rw-r--r--dom/serviceworkers/test/test_serviceworker_header.html41
-rw-r--r--dom/serviceworkers/test/test_serviceworker_interfaces.html100
-rw-r--r--dom/serviceworkers/test/test_serviceworker_interfaces.js567
-rw-r--r--dom/serviceworkers/test/test_serviceworker_not_sharedworker.html66
-rw-r--r--dom/serviceworkers/test/test_serviceworkerinfo.xhtml114
-rw-r--r--dom/serviceworkers/test/test_serviceworkermanager.xhtml79
-rw-r--r--dom/serviceworkers/test/test_serviceworkerregistrationinfo.xhtml155
-rw-r--r--dom/serviceworkers/test/test_skip_waiting.html86
-rw-r--r--dom/serviceworkers/test/test_streamfilter.html207
-rw-r--r--dom/serviceworkers/test/test_strict_mode_warning.html42
-rw-r--r--dom/serviceworkers/test/test_third_party_iframes.html263
-rw-r--r--dom/serviceworkers/test/test_unregister.html136
-rw-r--r--dom/serviceworkers/test/test_unresolved_fetch_interception.html95
-rw-r--r--dom/serviceworkers/test/test_workerUnregister.html81
-rw-r--r--dom/serviceworkers/test/test_workerUpdate.html63
-rw-r--r--dom/serviceworkers/test/test_worker_reference_gc_timeout.html76
-rw-r--r--dom/serviceworkers/test/test_workerupdatefoundevent.html91
-rw-r--r--dom/serviceworkers/test/test_xslt.html117
-rw-r--r--dom/serviceworkers/test/thirdparty/iframe1.html42
-rw-r--r--dom/serviceworkers/test/thirdparty/iframe2.html14
-rw-r--r--dom/serviceworkers/test/thirdparty/register.html29
-rw-r--r--dom/serviceworkers/test/thirdparty/sw.js32
-rw-r--r--dom/serviceworkers/test/thirdparty/unregister.html19
-rw-r--r--dom/serviceworkers/test/thirdparty/worker.js1
-rw-r--r--dom/serviceworkers/test/unregister/index.html26
-rw-r--r--dom/serviceworkers/test/unregister/unregister.html21
-rw-r--r--dom/serviceworkers/test/unresolved_fetch_worker.js18
-rw-r--r--dom/serviceworkers/test/update_worker.sjs12
-rw-r--r--dom/serviceworkers/test/updatefoundevent.html13
-rw-r--r--dom/serviceworkers/test/utils.js136
-rw-r--r--dom/serviceworkers/test/window_party_iframes.html18
-rw-r--r--dom/serviceworkers/test/worker.js1
-rw-r--r--dom/serviceworkers/test/worker2.js1
-rw-r--r--dom/serviceworkers/test/worker3.js1
-rw-r--r--dom/serviceworkers/test/workerUpdate/update.html23
-rw-r--r--dom/serviceworkers/test/worker_unregister.js22
-rw-r--r--dom/serviceworkers/test/worker_update.js25
-rw-r--r--dom/serviceworkers/test/worker_updatefoundevent.js20
-rw-r--r--dom/serviceworkers/test/worker_updatefoundevent2.js1
-rw-r--r--dom/serviceworkers/test/xslt/test.xml6
-rw-r--r--dom/serviceworkers/test/xslt/xslt.sjs12
-rw-r--r--dom/serviceworkers/test/xslt_worker.js58
398 files changed, 21178 insertions, 0 deletions
diff --git a/dom/serviceworkers/test/ForceRefreshChild.sys.mjs b/dom/serviceworkers/test/ForceRefreshChild.sys.mjs
new file mode 100644
index 0000000000..b2b965be9e
--- /dev/null
+++ b/dom/serviceworkers/test/ForceRefreshChild.sys.mjs
@@ -0,0 +1,12 @@
+export class ForceRefreshChild extends JSWindowActorChild {
+ constructor() {
+ super();
+ }
+
+ handleEvent(evt) {
+ this.sendAsyncMessage("test:event", {
+ type: evt.type,
+ detail: evt.details,
+ });
+ }
+}
diff --git a/dom/serviceworkers/test/ForceRefreshParent.sys.mjs b/dom/serviceworkers/test/ForceRefreshParent.sys.mjs
new file mode 100644
index 0000000000..69b0c1be42
--- /dev/null
+++ b/dom/serviceworkers/test/ForceRefreshParent.sys.mjs
@@ -0,0 +1,79 @@
+var maxCacheLoadCount = 3;
+var cachedLoadCount = 0;
+var baseLoadCount = 0;
+var done = false;
+
+export class ForceRefreshParent extends JSWindowActorParent {
+ constructor() {
+ super();
+ }
+
+ receiveMessage(msg) {
+ // if done is called, ignore the msg.
+ if (done) {
+ return;
+ }
+ if (msg.data.type === "base-load") {
+ baseLoadCount += 1;
+ if (cachedLoadCount === maxCacheLoadCount) {
+ ForceRefreshParent.SimpleTest.is(
+ baseLoadCount,
+ 2,
+ "cached load should occur before second base load"
+ );
+ done = true;
+ ForceRefreshParent.done();
+ return;
+ }
+ if (baseLoadCount !== 1) {
+ ForceRefreshParent.SimpleTest.ok(
+ false,
+ "base load without cached load should only occur once"
+ );
+ done = true;
+ ForceRefreshParent.done();
+ }
+ } else if (msg.data.type === "base-register") {
+ ForceRefreshParent.SimpleTest.ok(
+ !cachedLoadCount,
+ "cached load should not occur before base register"
+ );
+ ForceRefreshParent.SimpleTest.is(
+ baseLoadCount,
+ 1,
+ "register should occur after first base load"
+ );
+ } else if (msg.data.type === "base-sw-ready") {
+ ForceRefreshParent.SimpleTest.ok(
+ !cachedLoadCount,
+ "cached load should not occur before base ready"
+ );
+ ForceRefreshParent.SimpleTest.is(
+ baseLoadCount,
+ 1,
+ "ready should occur after first base load"
+ );
+ ForceRefreshParent.refresh();
+ } else if (msg.data.type === "cached-load") {
+ ForceRefreshParent.SimpleTest.ok(
+ cachedLoadCount < maxCacheLoadCount,
+ "cached load should not occur too many times"
+ );
+ ForceRefreshParent.SimpleTest.is(
+ baseLoadCount,
+ 1,
+ "cache load occur after first base load"
+ );
+ cachedLoadCount += 1;
+ if (cachedLoadCount < maxCacheLoadCount) {
+ ForceRefreshParent.refresh();
+ return;
+ }
+ ForceRefreshParent.forceRefresh();
+ } else if (msg.data.type === "cached-failure") {
+ ForceRefreshParent.SimpleTest.ok(false, "failure: " + msg.data.detail);
+ done = true;
+ ForceRefreshParent.done();
+ }
+ }
+}
diff --git a/dom/serviceworkers/test/abrupt_completion_worker.js b/dom/serviceworkers/test/abrupt_completion_worker.js
new file mode 100644
index 0000000000..7afebc6d45
--- /dev/null
+++ b/dom/serviceworkers/test/abrupt_completion_worker.js
@@ -0,0 +1,18 @@
+function setMessageHandler(response) {
+ onmessage = e => {
+ e.source.postMessage(response);
+ };
+}
+
+setMessageHandler("handler-before-throw");
+
+// importScripts will throw when the ServiceWorker is past the "intalling" state.
+importScripts(`empty.js?${Date.now()}`);
+
+// When importScripts throws an uncaught exception, these calls should never be
+// made and the message handler should remain responding "handler-before-throw".
+setMessageHandler("handler-after-throw");
+
+// There needs to be a fetch handler to avoid the no-fetch optimizaiton,
+// which will skip starting up this worker.
+onfetch = e => e.respondWith(new Response("handler-after-throw"));
diff --git a/dom/serviceworkers/test/activate_event_error_worker.js b/dom/serviceworkers/test/activate_event_error_worker.js
new file mode 100644
index 0000000000..a875f27d92
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/async_waituntil_worker.js b/dom/serviceworkers/test/async_waituntil_worker.js
new file mode 100644
index 0000000000..f830fc6f83
--- /dev/null
+++ b/dom/serviceworkers/test/async_waituntil_worker.js
@@ -0,0 +1,53 @@
+var keepAlivePromise;
+var resolvePromise;
+var result = "Failed";
+
+onactivate = function (event) {
+ event.waitUntil(clients.claim());
+};
+
+onmessage = function (event) {
+ if (event.data === "Start") {
+ event.waitUntil(Promise.reject());
+
+ keepAlivePromise = new Promise(function (resolve, reject) {
+ resolvePromise = resolve;
+ });
+
+ result = "Success";
+ event.waitUntil(keepAlivePromise);
+ event.source.postMessage("Started");
+ } else if (event.data === "Result") {
+ event.source.postMessage(result);
+ if (resolvePromise !== undefined) {
+ resolvePromise();
+ }
+ }
+};
+
+addEventListener("fetch", e => {
+ let respondWithPromise = new Promise(function (res, rej) {
+ setTimeout(() => {
+ res(new Response("ok"));
+ }, 0);
+ });
+ e.respondWith(respondWithPromise);
+ // Test that waitUntil can be called in the promise handler of the existing
+ // lifetime extension promise.
+ respondWithPromise.then(() => {
+ e.waitUntil(
+ clients.matchAll().then(cls => {
+ dump(`matchAll returned ${cls.length} client(s) with URLs:\n`);
+ cls.forEach(cl => {
+ dump(`${cl.url}\n`);
+ });
+
+ if (cls.length != 1) {
+ dump("ERROR: no controlled clients.\n");
+ }
+ client = cls[0];
+ client.postMessage("Done");
+ })
+ );
+ });
+});
diff --git a/dom/serviceworkers/test/blocking_install_event_worker.js b/dom/serviceworkers/test/blocking_install_event_worker.js
new file mode 100644
index 0000000000..8ca6201316
--- /dev/null
+++ b/dom/serviceworkers/test/blocking_install_event_worker.js
@@ -0,0 +1,22 @@
+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/serviceworkers/test/browser-common.toml b/dom/serviceworkers/test/browser-common.toml
new file mode 100644
index 0000000000..66e31e5f7f
--- /dev/null
+++ b/dom/serviceworkers/test/browser-common.toml
@@ -0,0 +1,64 @@
+[DEFAULT]
+support-files = [
+ "browser_base_force_refresh.html",
+ "browser_cached_force_refresh.html",
+ "browser_head.js",
+ "download/window.html",
+ "download/worker.js",
+ "download_canceled/page_download_canceled.html",
+ "download_canceled/server-stream-download.sjs",
+ "download_canceled/sw_download_canceled.js",
+ "fetch.js",
+ "file_userContextId_openWindow.js",
+ "force_refresh_browser_worker.js",
+ "ForceRefreshChild.sys.mjs",
+ "ForceRefreshParent.sys.mjs",
+ "empty.html",
+ "empty_with_utils.html",
+ "empty.js",
+ "intercepted_channel_process_swap_worker.js",
+ "navigationPreload_page.html",
+ "network_with_utils.html",
+ "page_post_controlled.html",
+ "redirect.sjs",
+ "simple_fetch_worker.js",
+ "storage_recovery_worker.sjs",
+ "sw_respondwith_serviceworker.js",
+ "sw_with_navigationPreload.js",
+ "utils.js",
+]
+
+["browser_antitracking.js"]
+
+["browser_antitracking_subiframes.js"]
+
+["browser_devtools_serviceworker_interception.js"]
+skip-if = ["serviceworker_e10s"]
+
+["browser_download.js"]
+
+["browser_download_canceled.js"]
+skip-if = ["verify"]
+
+["browser_force_refresh.js"]
+skip-if = ["verify"] # Bug 1603340
+
+["browser_intercepted_channel_process_swap.js"]
+
+["browser_intercepted_worker_script.js"]
+
+["browser_navigationPreload_read_after_respondWith.js"]
+
+["browser_navigation_fetch_fault_handling.js"]
+
+["browser_remote_type_process_swap.js"]
+
+["browser_storage_permission.js"]
+skip-if = ["true"] # Crashes: @ mozilla::dom::ServiceWorkerManagerService::PropagateUnregister(unsigned long, mozilla::ipc::PrincipalInfo const&, nsTSubstring<char16_t> const&), #Bug 1578337
+
+["browser_storage_recovery.js"]
+
+["browser_unregister_with_containers.js"]
+
+["browser_userContextId_openWindow.js"]
+skip-if = ["true"] # See bug 1769437.
diff --git a/dom/serviceworkers/test/browser-dFPI.toml b/dom/serviceworkers/test/browser-dFPI.toml
new file mode 100644
index 0000000000..4d0f9b820e
--- /dev/null
+++ b/dom/serviceworkers/test/browser-dFPI.toml
@@ -0,0 +1,6 @@
+[DEFAULT]
+# Enable dFPI(cookieBehavior 5) for service worker tests.
+prefs = ["network.cookie.cookieBehavior=5"]
+dupe-manifest = true
+
+["include:browser-common.toml"]
diff --git a/dom/serviceworkers/test/browser.toml b/dom/serviceworkers/test/browser.toml
new file mode 100644
index 0000000000..f6ce53e074
--- /dev/null
+++ b/dom/serviceworkers/test/browser.toml
@@ -0,0 +1,4 @@
+[DEFAULT]
+dupe-manifest = true
+
+["include:browser-common.toml"]
diff --git a/dom/serviceworkers/test/browser_antitracking.js b/dom/serviceworkers/test/browser_antitracking.js
new file mode 100644
index 0000000000..e42a030595
--- /dev/null
+++ b/dom/serviceworkers/test/browser_antitracking.js
@@ -0,0 +1,106 @@
+const BEHAVIOR_ACCEPT = Ci.nsICookieService.BEHAVIOR_ACCEPT;
+const BEHAVIOR_REJECT_TRACKER = Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
+
+let { UrlClassifierTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlClassifierTestUtils.sys.mjs"
+);
+
+const TOP_DOMAIN = "http://mochi.test:8888/";
+const SW_DOMAIN = "https://tracking.example.org/";
+
+const TOP_TEST_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ TOP_DOMAIN
+);
+const SW_TEST_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ SW_DOMAIN
+);
+
+const TOP_EMPTY_PAGE = `${TOP_TEST_ROOT}empty_with_utils.html`;
+const SW_REGISTER_PAGE = `${SW_TEST_ROOT}empty_with_utils.html`;
+const SW_IFRAME_PAGE = `${SW_TEST_ROOT}page_post_controlled.html`;
+// An empty script suffices for our SW needs; it's by definition no-fetch.
+const SW_REL_SW_SCRIPT = "empty.js";
+
+/**
+ * Set up a no-fetch-optimized ServiceWorker on a domain that will be covered by
+ * tracking protection (but is not yet). Once the SW is installed, activate TP
+ * and create a tab that embeds that tracking-site in an iframe.
+ */
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_ACCEPT],
+ ],
+ });
+
+ // Open the top-level page.
+ info("Opening a new tab: " + SW_REGISTER_PAGE);
+ let topTab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: SW_REGISTER_PAGE,
+ });
+
+ // ## Install SW
+ info("Installing SW");
+ await SpecialPowers.spawn(
+ topTab.linkedBrowser,
+ [{ sw: SW_REL_SW_SCRIPT }],
+ async function ({ sw }) {
+ // Waive the xray to use the content utils.js script functions.
+ await content.wrappedJSObject.registerAndWaitForActive(sw);
+ }
+ );
+
+ // Enable Anti-tracking.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_REJECT_TRACKER],
+ ],
+ });
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ // Open the top-level URL.
+ info("Loading a new top-level URL: " + TOP_EMPTY_PAGE);
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ topTab.linkedBrowser
+ );
+ BrowserTestUtils.startLoadingURIString(topTab.linkedBrowser, TOP_EMPTY_PAGE);
+ await browserLoadedPromise;
+
+ // Create Iframe in the top-level page and verify its state.
+ let { controlled } = await SpecialPowers.spawn(
+ topTab.linkedBrowser,
+ [{ url: SW_IFRAME_PAGE }],
+ async function ({ url }) {
+ const payload =
+ await content.wrappedJSObject.createIframeAndWaitForMessage(url);
+ return payload;
+ }
+ );
+
+ ok(!controlled, "Should not be controlled!");
+
+ // ## Cleanup
+ info("Loading the SW unregister page: " + SW_REGISTER_PAGE);
+ browserLoadedPromise = BrowserTestUtils.browserLoaded(topTab.linkedBrowser);
+ BrowserTestUtils.startLoadingURIString(
+ topTab.linkedBrowser,
+ SW_REGISTER_PAGE
+ );
+ await browserLoadedPromise;
+
+ await SpecialPowers.spawn(topTab.linkedBrowser, [], async function () {
+ await content.wrappedJSObject.unregisterAll();
+ });
+
+ // Close the testing tab.
+ BrowserTestUtils.removeTab(topTab);
+});
diff --git a/dom/serviceworkers/test/browser_antitracking_subiframes.js b/dom/serviceworkers/test/browser_antitracking_subiframes.js
new file mode 100644
index 0000000000..b19ee83063
--- /dev/null
+++ b/dom/serviceworkers/test/browser_antitracking_subiframes.js
@@ -0,0 +1,106 @@
+const BEHAVIOR_REJECT_TRACKER = Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
+
+const TOP_DOMAIN = "http://mochi.test:8888/";
+const SW_DOMAIN = "https://example.org/";
+
+const TOP_TEST_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ TOP_DOMAIN
+);
+const SW_TEST_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ SW_DOMAIN
+);
+
+const TOP_EMPTY_PAGE = `${TOP_TEST_ROOT}empty_with_utils.html`;
+const SW_REGISTER_PAGE = `${SW_TEST_ROOT}empty_with_utils.html`;
+const SW_IFRAME_PAGE = `${SW_TEST_ROOT}page_post_controlled.html`;
+const SW_REL_SW_SCRIPT = "empty.js";
+
+/**
+ * Set up a ServiceWorker on a domain that will be used as 3rd party iframe.
+ * That 3rd party frame should be controlled by the ServiceWorker.
+ * After that, we open a second iframe into the first one. That should not be
+ * controlled.
+ */
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["network.cookie.cookieBehavior", BEHAVIOR_REJECT_TRACKER],
+ ],
+ });
+
+ // Open the top-level page.
+ info("Opening a new tab: " + SW_REGISTER_PAGE);
+ let topTab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: SW_REGISTER_PAGE,
+ });
+
+ // Install SW
+ info("Registering a SW: " + SW_REL_SW_SCRIPT);
+ await SpecialPowers.spawn(
+ topTab.linkedBrowser,
+ [{ sw: SW_REL_SW_SCRIPT }],
+ async function ({ sw }) {
+ // Waive the xray to use the content utils.js script functions.
+ await content.wrappedJSObject.registerAndWaitForActive(sw);
+ // User interaction
+ content.document.userInteractionForTesting();
+ }
+ );
+
+ info("Loading a new top-level URL: " + TOP_EMPTY_PAGE);
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ topTab.linkedBrowser
+ );
+ BrowserTestUtils.startLoadingURIString(topTab.linkedBrowser, TOP_EMPTY_PAGE);
+ await browserLoadedPromise;
+
+ // Create Iframe in the top-level page and verify its state.
+ info("Creating iframe and checking if controlled");
+ let { controlled } = await SpecialPowers.spawn(
+ topTab.linkedBrowser,
+ [{ url: SW_IFRAME_PAGE }],
+ async function ({ url }) {
+ content.document.userInteractionForTesting();
+ const payload =
+ await content.wrappedJSObject.createIframeAndWaitForMessage(url);
+ return payload;
+ }
+ );
+
+ ok(controlled, "Should be controlled!");
+
+ // Create a nested Iframe.
+ info("Creating nested-iframe and checking if controlled");
+ let { nested_controlled } = await SpecialPowers.spawn(
+ topTab.linkedBrowser,
+ [{ url: SW_IFRAME_PAGE }],
+ async function ({ url }) {
+ const payload =
+ await content.wrappedJSObject.createNestedIframeAndWaitForMessage(url);
+ return payload;
+ }
+ );
+
+ ok(!nested_controlled, "Should not be controlled!");
+
+ info("Loading the SW unregister page: " + SW_REGISTER_PAGE);
+ browserLoadedPromise = BrowserTestUtils.browserLoaded(topTab.linkedBrowser);
+ BrowserTestUtils.startLoadingURIString(
+ topTab.linkedBrowser,
+ SW_REGISTER_PAGE
+ );
+ await browserLoadedPromise;
+
+ await SpecialPowers.spawn(topTab.linkedBrowser, [], async function () {
+ await content.wrappedJSObject.unregisterAll();
+ });
+
+ // Close the testing tab.
+ BrowserTestUtils.removeTab(topTab);
+});
diff --git a/dom/serviceworkers/test/browser_base_force_refresh.html b/dom/serviceworkers/test/browser_base_force_refresh.html
new file mode 100644
index 0000000000..1c3d02d42f
--- /dev/null
+++ b/dom/serviceworkers/test/browser_base_force_refresh.html
@@ -0,0 +1,27 @@
+<!--
+ 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;
+ }
+ window.dispatchEvent(new Event("base-register", { bubbles: true }));
+ });
+
+ navigator.serviceWorker.ready.then(function() {
+ window.dispatchEvent(new Event("base-sw-ready", { bubbles: true }));
+ });
+
+ window.dispatchEvent(new Event("base-load", { bubbles: true }));
+});
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/browser_cached_force_refresh.html b/dom/serviceworkers/test/browser_cached_force_refresh.html
new file mode 100644
index 0000000000..a0223d26b8
--- /dev/null
+++ b/dom/serviceworkers/test/browser_cached_force_refresh.html
@@ -0,0 +1,60 @@
+<!--
+ 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) {
+ window.dispatchEvent(new Event("cached-failure", { bubbles: true, detail: err }));
+}
+
+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) {
+ fail(window.location.href + ' is not controlled!');
+ return;
+ }
+
+ 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');
+ window.dispatchEvent(new Event('cached-load', { bubbles: true }));
+ })
+ .catch(function(err) {
+ fail(err);
+ });
+});
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/browser_devtools_serviceworker_interception.js b/dom/serviceworkers/test/browser_devtools_serviceworker_interception.js
new file mode 100644
index 0000000000..f31c2f7dff
--- /dev/null
+++ b/dom/serviceworkers/test/browser_devtools_serviceworker_interception.js
@@ -0,0 +1,270 @@
+"use strict";
+
+const BASE_URI = "http://mochi.test:8888/browser/dom/serviceworkers/test/";
+const emptyDoc = BASE_URI + "empty.html";
+const fakeDoc = BASE_URI + "fake.html";
+const helloDoc = BASE_URI + "hello.html";
+
+const CROSS_URI = "http://example.com/browser/dom/serviceworkers/test/";
+const crossRedirect = CROSS_URI + "redirect";
+const crossHelloDoc = CROSS_URI + "hello.html";
+
+const sw = BASE_URI + "fetch.js";
+
+async function checkObserver(aInput) {
+ let interceptedChannel = null;
+
+ // We always get two channels which receive the "http-on-stop-request"
+ // notification if the service worker hijacks the request and respondWith an
+ // another fetch. One is for the "outer" window request when the other one is
+ // for the "inner" service worker request. Therefore, distinguish them by the
+ // order.
+ let waitForSecondOnStopRequest = aInput.intercepted;
+
+ let promiseResolve;
+
+ function observer(aSubject) {
+ let channel = aSubject.QueryInterface(Ci.nsIChannel);
+ // Since we cannot make sure that the network event triggered by the fetch()
+ // in this testcase is the very next event processed by ObserverService, we
+ // have to wait until we catch the one we want.
+ if (!channel.URI.spec.includes(aInput.expectedURL)) {
+ return;
+ }
+
+ if (waitForSecondOnStopRequest) {
+ waitForSecondOnStopRequest = false;
+ return;
+ }
+
+ // Wait for the service worker to intercept the request if it's expected to
+ // be intercepted
+ if (aInput.intercepted && interceptedChannel === null) {
+ return;
+ } else if (interceptedChannel) {
+ ok(
+ aInput.intercepted,
+ "Service worker intercepted the channel as expected"
+ );
+ } else {
+ ok(!aInput.intercepted, "The channel doesn't be intercepted");
+ }
+
+ var tc = interceptedChannel
+ ? interceptedChannel.QueryInterface(Ci.nsITimedChannel)
+ : aSubject.QueryInterface(Ci.nsITimedChannel);
+
+ // Check service worker related timings.
+ var serviceWorkerTimings = [
+ {
+ start: tc.launchServiceWorkerStartTime,
+ end: tc.launchServiceWorkerEndTime,
+ },
+ {
+ start: tc.dispatchFetchEventStartTime,
+ end: tc.dispatchFetchEventEndTime,
+ },
+ { start: tc.handleFetchEventStartTime, end: tc.handleFetchEventEndTime },
+ ];
+ if (!aInput.swPresent) {
+ serviceWorkerTimings.forEach(aTimings => {
+ is(aTimings.start, 0, "SW timings should be 0.");
+ is(aTimings.end, 0, "SW timings should be 0.");
+ });
+ }
+
+ // Check network related timings.
+ var networkTimings = [
+ tc.domainLookupStartTime,
+ tc.domainLookupEndTime,
+ tc.connectStartTime,
+ tc.connectEndTime,
+ tc.requestStartTime,
+ tc.responseStartTime,
+ tc.responseEndTime,
+ ];
+ if (aInput.fetch) {
+ networkTimings.reduce((aPreviousTiming, aCurrentTiming) => {
+ Assert.lessOrEqual(
+ aPreviousTiming,
+ aCurrentTiming,
+ "Checking network timings"
+ );
+ return aCurrentTiming;
+ });
+ } else {
+ networkTimings.forEach(aTiming =>
+ is(aTiming, 0, "Network timings should be 0.")
+ );
+ }
+
+ interceptedChannel = null;
+ Services.obs.removeObserver(observer, topic);
+ promiseResolve();
+ }
+
+ function addInterceptedChannel(aSubject) {
+ let channel = aSubject.QueryInterface(Ci.nsIChannel);
+ if (!channel.URI.spec.includes(aInput.url)) {
+ return;
+ }
+
+ // Hold the interceptedChannel until checking timing information.
+ // Note: It's a interceptedChannel in the type of httpChannel
+ interceptedChannel = channel;
+ Services.obs.removeObserver(addInterceptedChannel, topic_SW);
+ }
+
+ const topic = "http-on-stop-request";
+ const topic_SW = "service-worker-synthesized-response";
+
+ Services.obs.addObserver(observer, topic);
+ if (aInput.intercepted) {
+ Services.obs.addObserver(addInterceptedChannel, topic_SW);
+ }
+
+ await new Promise(resolve => {
+ promiseResolve = resolve;
+ });
+}
+
+async function contentFetch(aURL) {
+ if (aURL.includes("redirect")) {
+ await content.window.fetch(aURL, { mode: "no-cors" });
+ return;
+ }
+ await content.window.fetch(aURL);
+}
+
+// The observer topics are fired in the parent process in parent-intercept
+// and the content process in child-intercept. This function will handle running
+// the check in the correct process. Note that it will block until the observers
+// are notified.
+async function fetchAndCheckObservers(
+ aFetchBrowser,
+ aObserverBrowser,
+ aTestCase
+) {
+ let promise = null;
+
+ promise = checkObserver(aTestCase);
+
+ await SpecialPowers.spawn(aFetchBrowser, [aTestCase.url], contentFetch);
+ await promise;
+}
+
+async function registerSWAndWaitForActive(aServiceWorker) {
+ let swr = await content.navigator.serviceWorker.register(aServiceWorker, {
+ scope: "empty.html",
+ });
+ await new Promise(resolve => {
+ let worker = swr.installing || swr.waiting || swr.active;
+ if (worker.state === "activated") {
+ resolve();
+ return;
+ }
+
+ worker.addEventListener("statechange", () => {
+ if (worker.state === "activated") {
+ resolve();
+ }
+ });
+ });
+
+ await new Promise(resolve => {
+ if (content.navigator.serviceWorker.controller) {
+ resolve();
+ return;
+ }
+
+ content.navigator.serviceWorker.addEventListener(
+ "controllerchange",
+ resolve,
+ { once: true }
+ );
+ });
+}
+
+async function unregisterSW() {
+ let swr = await content.navigator.serviceWorker.getRegistration();
+ swr.unregister();
+}
+
+add_task(async function test_serivce_worker_interception() {
+ info("Setting the prefs to having e10s enabled");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Make sure observer and testing function run in the same process
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ });
+
+ waitForExplicitFinish();
+
+ info("Open the tab");
+ let tab = BrowserTestUtils.addTab(gBrowser, emptyDoc);
+ let tabBrowser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(tabBrowser);
+
+ info("Open the tab for observing");
+ let tab_observer = BrowserTestUtils.addTab(gBrowser, emptyDoc);
+ let tabBrowser_observer = gBrowser.getBrowserForTab(tab_observer);
+ await BrowserTestUtils.browserLoaded(tabBrowser_observer);
+
+ let testcases = [
+ {
+ url: helloDoc,
+ expectedURL: helloDoc,
+ swPresent: false,
+ intercepted: false,
+ fetch: true,
+ },
+ {
+ url: fakeDoc,
+ expectedURL: helloDoc,
+ swPresent: true,
+ intercepted: true,
+ fetch: false, // should use HTTP cache
+ },
+ {
+ // Bypass http cache
+ url: helloDoc + "?ForBypassingHttpCache=" + Date.now(),
+ expectedURL: helloDoc,
+ swPresent: true,
+ intercepted: false,
+ fetch: true,
+ },
+ {
+ // no-cors mode redirect to no-cors mode (trigger internal redirect)
+ url: crossRedirect + "?url=" + crossHelloDoc + "&mode=no-cors",
+ expectedURL: crossHelloDoc,
+ swPresent: true,
+ redirect: "hello.html",
+ intercepted: true,
+ fetch: true,
+ },
+ ];
+
+ info("Test 1: Verify simple fetch");
+ await fetchAndCheckObservers(tabBrowser, tabBrowser_observer, testcases[0]);
+
+ info("Register a service worker");
+ await SpecialPowers.spawn(tabBrowser, [sw], registerSWAndWaitForActive);
+
+ info("Test 2: Verify simple hijack");
+ await fetchAndCheckObservers(tabBrowser, tabBrowser_observer, testcases[1]);
+
+ info("Test 3: Verify fetch without using http cache");
+ await fetchAndCheckObservers(tabBrowser, tabBrowser_observer, testcases[2]);
+
+ info("Test 4: make a internal redirect");
+ await fetchAndCheckObservers(tabBrowser, tabBrowser_observer, testcases[3]);
+
+ info("Clean up");
+ await SpecialPowers.spawn(tabBrowser, [undefined], unregisterSW);
+
+ gBrowser.removeTab(tab);
+ gBrowser.removeTab(tab_observer);
+});
diff --git a/dom/serviceworkers/test/browser_download.js b/dom/serviceworkers/test/browser_download.js
new file mode 100644
index 0000000000..0c69a48d17
--- /dev/null
+++ b/dom/serviceworkers/test/browser_download.js
@@ -0,0 +1,93 @@
+/* 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 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 () {
+ if (
+ win.document.documentURI ===
+ "chrome://mozapps/content/downloads/unknownContentType.xhtml"
+ ) {
+ executeSoon(function () {
+ let dialog = win.document.getElementById("unknownContentType");
+ let button = dialog.getButton("accept");
+ button.disabled = false;
+ dialog.acceptDialog();
+ });
+ }
+ },
+ { once: true }
+ );
+}
+
+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 = BrowserTestUtils.addTab(gBrowser);
+ 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);
+ downloadList.remove(aDownload).catch(console.error);
+ downloadList.removeView(downloadListener).catch(console.error);
+ gBrowser.removeTab(tab);
+ Services.ww.unregisterNotification(windowObserver);
+
+ executeSoon(finish);
+ }
+ }
+
+ downloadListener = {
+ onDownloadAdded: downloadVerifier,
+ onDownloadChanged: downloadVerifier,
+ };
+
+ return downloadList.addView(downloadListener);
+ })
+ .then(function () {
+ BrowserTestUtils.startLoadingURIString(gBrowser, url);
+ });
+ }
+ );
+}
diff --git a/dom/serviceworkers/test/browser_download_canceled.js b/dom/serviceworkers/test/browser_download_canceled.js
new file mode 100644
index 0000000000..2deb8389ef
--- /dev/null
+++ b/dom/serviceworkers/test/browser_download_canceled.js
@@ -0,0 +1,174 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Test cancellation of a download in order to test edge-cases related to
+ * channel diversion. Channel diversion occurs in cases of file (and PSM cert)
+ * downloads where we realize in the child that we really want to consume the
+ * channel data in the parent. For data "sourced" by the parent, like network
+ * data, data streaming to the child is suspended and the parent waits for the
+ * child to send back the data it already received, then the channel is resumed.
+ * For data generated by the child, such as (the current, to be mooted by
+ * parent-intercept) child-side intercept, the data (currently) stream is
+ * continually pumped up to the parent.
+ *
+ * In particular, we want to reproduce the circumstances of Bug 1418795 where
+ * the child-side input-stream pump attempts to send data to the parent process
+ * but the parent has canceled the channel and so the IPC Actor has been torn
+ * down. Diversion begins once the nsURILoader receives the OnStartRequest
+ * notification with the headers, so there are two ways to produce
+ */
+
+/**
+ * Clear the downloads list so other tests don't see our byproducts.
+ */
+async function clearDownloads() {
+ const downloads = await Downloads.getList(Downloads.ALL);
+ downloads.removeFinished();
+}
+
+/**
+ * Returns a Promise that will be resolved once the download dialog shows up and
+ * we have clicked the given button.
+ */
+function promiseClickDownloadDialogButton(buttonAction) {
+ const uri = "chrome://mozapps/content/downloads/unknownContentType.xhtml";
+ return BrowserTestUtils.promiseAlertDialogOpen(buttonAction, uri, {
+ async callback(win) {
+ // nsHelperAppDlg.js currently uses an eval-based setTimeout(0) to invoke
+ // its postShowCallback that results in a misleading error to the console
+ // if we close the dialog before it gets a chance to run. Just a
+ // setTimeout is not sufficient because it appears we get our "load"
+ // listener before the document's, so we use TestUtils.waitForTick() to
+ // defer until after its load handler runs, then use setTimeout(0) to end
+ // up after its eval.
+ await TestUtils.waitForTick();
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ const button = win.document
+ .getElementById("unknownContentType")
+ .getButton(buttonAction);
+ button.disabled = false;
+ info(`clicking ${buttonAction} button`);
+ button.click();
+ },
+ });
+}
+
+async function performCanceledDownload(tab, path) {
+ // If we're going to show a modal dialog for this download, then we should
+ // use it to cancel the download. If not, then we have to let the download
+ // start and then call into the downloads API ourselves to cancel it.
+ // We use this promise to signal the cancel being complete in either case.
+ let cancelledDownload;
+
+ if (
+ Services.prefs.getBoolPref(
+ "browser.download.always_ask_before_handling_new_types",
+ false
+ )
+ ) {
+ // Start waiting for the download dialog before triggering the download.
+ cancelledDownload = promiseClickDownloadDialogButton("cancel");
+ // Wait for the cancelation to have been triggered.
+ info("waiting for download popup");
+ } else {
+ let downloadView;
+ cancelledDownload = new Promise(resolve => {
+ downloadView = {
+ onDownloadAdded(aDownload) {
+ aDownload.cancel();
+ resolve();
+ },
+ };
+ });
+ const downloadList = await Downloads.getList(Downloads.ALL);
+ await downloadList.addView(downloadView);
+ }
+
+ // Trigger the download.
+ info(`triggering download of "${path}"`);
+ /* eslint-disable no-shadow */
+ await SpecialPowers.spawn(tab.linkedBrowser, [path], function (path) {
+ // Put a Promise in place that we can wait on for stream closure.
+ content.wrappedJSObject.trackStreamClosure(path);
+ // Create the link and trigger the download.
+ const link = content.document.createElement("a");
+ link.href = path;
+ link.download = path;
+ content.document.body.appendChild(link);
+ link.click();
+ });
+ /* eslint-enable no-shadow */
+
+ // Wait for the download to cancel.
+ await cancelledDownload;
+ info("cancelled download");
+
+ // Wait for confirmation that the stream stopped.
+ info(`wait for the ${path} stream to close.`);
+ /* eslint-disable no-shadow */
+ const why = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [path],
+ function (path) {
+ return content.wrappedJSObject.streamClosed[path].promise;
+ }
+ );
+ /* eslint-enable no-shadow */
+ is(why.why, "canceled", "Ensure the stream canceled instead of timing out.");
+ // Note that for the "sw-stream-download" case, we end up with a bogus
+ // reason of "'close' may only be called on a stream in the 'readable' state."
+ // Since we aren't actually invoking close(), I'm assuming this is an
+ // implementation bug that will be corrected in the web platform tests.
+ info(`Cancellation reason: ${why.message} after ${why.ticks} ticks`);
+}
+
+const gTestRoot = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://mochi.test:8888/"
+);
+
+const PAGE_URL = `${gTestRoot}download_canceled/page_download_canceled.html`;
+
+add_task(async function interruptedDownloads() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ });
+
+ // Open the tab
+ const tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: PAGE_URL,
+ });
+
+ // Wait for it to become controlled. Check that it was a promise that
+ // resolved as expected rather than undefined by checking the return value.
+ const controlled = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ function () {
+ // This is a promise set up by the page during load, and we are post-load.
+ return content.wrappedJSObject.controlled;
+ }
+ );
+ is(controlled, "controlled", "page became controlled");
+
+ // Download a pass-through fetch stream.
+ await performCanceledDownload(tab, "sw-passthrough-download");
+
+ // Download a SW-generated stream
+ await performCanceledDownload(tab, "sw-stream-download");
+
+ // Cleanup
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ return content.wrappedJSObject.registration.unregister();
+ });
+ BrowserTestUtils.removeTab(tab);
+ await clearDownloads();
+});
diff --git a/dom/serviceworkers/test/browser_force_refresh.js b/dom/serviceworkers/test/browser_force_refresh.js
new file mode 100644
index 0000000000..1f6b1b9d9f
--- /dev/null
+++ b/dom/serviceworkers/test/browser_force_refresh.js
@@ -0,0 +1,86 @@
+/* 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/"
+);
+
+async function refresh() {
+ EventUtils.synthesizeKey("R", { accelKey: true });
+}
+
+async function forceRefresh() {
+ EventUtils.synthesizeKey("R", { accelKey: true, shiftKey: true });
+}
+
+async function done() {
+ // unregister window actors
+ ChromeUtils.unregisterWindowActor("ForceRefresh");
+ let tab = gBrowser.selectedTab;
+ let tabBrowser = gBrowser.getBrowserForTab(tab);
+ await ContentTask.spawn(tabBrowser, null, async function () {
+ const swr = await content.navigator.serviceWorker.getRegistration();
+ await swr.unregister();
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ executeSoon(finish);
+}
+
+function test() {
+ waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["browser.cache.disk.enable", false],
+ ["browser.cache.memory.enable", false],
+ ],
+ },
+ async function () {
+ // create ForceRefreseh window actor
+ const { ForceRefreshParent } = ChromeUtils.importESModule(
+ getRootDirectory(gTestPath) + "ForceRefreshParent.sys.mjs"
+ );
+
+ // setup helper functions for ForceRefreshParent
+ ForceRefreshParent.SimpleTest = SimpleTest;
+ ForceRefreshParent.refresh = refresh;
+ ForceRefreshParent.forceRefresh = forceRefresh;
+ ForceRefreshParent.done = done;
+
+ // setup window actor options
+ let windowActorOptions = {
+ parent: {
+ esModuleURI:
+ getRootDirectory(gTestPath) + "ForceRefreshParent.sys.mjs",
+ },
+ child: {
+ esModuleURI:
+ getRootDirectory(gTestPath) + "ForceRefreshChild.sys.mjs",
+ events: {
+ "base-register": { capture: true, wantUntrusted: true },
+ "base-sw-ready": { capture: true, wantUntrusted: true },
+ "base-load": { capture: true, wantUntrusted: true },
+ "cached-load": { capture: true, wantUntrusted: true },
+ "cached-failure": { capture: true, wantUntrusted: true },
+ },
+ },
+ allFrames: true,
+ };
+
+ // register ForceRefresh window actors
+ ChromeUtils.registerWindowActor("ForceRefresh", windowActorOptions);
+
+ // create a new tab and load test url
+ var url = gTestRoot + "browser_base_force_refresh.html";
+ var tab = BrowserTestUtils.addTab(gBrowser);
+ var tabBrowser = gBrowser.getBrowserForTab(tab);
+ gBrowser.selectedTab = tab;
+ BrowserTestUtils.startLoadingURIString(gBrowser, url);
+ }
+ );
+}
diff --git a/dom/serviceworkers/test/browser_head.js b/dom/serviceworkers/test/browser_head.js
new file mode 100644
index 0000000000..78e4d327ec
--- /dev/null
+++ b/dom/serviceworkers/test/browser_head.js
@@ -0,0 +1,318 @@
+/**
+ * This file contains common functionality for ServiceWorker browser tests.
+ *
+ * Note that the normal auto-import mechanics for browser mochitests only
+ * handles "head.js", but we currently store all of our different varieties of
+ * mochitest in a single directory, which potentially results in a collision
+ * for similar heuristics for xpcshell.
+ *
+ * Many of the storage-related helpers in this file come from:
+ * https://searchfox.org/mozilla-central/source/dom/localstorage/test/unit/head.js
+ **/
+
+// To use this file, explicitly import it via:
+//
+// Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/dom/serviceworkers/test/browser_head.js", this);
+
+// Find the current parent directory of the test context we're being loaded into
+// such that one can do `${originNoTrailingSlash}/${DIR_PATH}/file_in_dir.foo`.
+const DIR_PATH = getRootDirectory(gTestPath)
+ .replace("chrome://mochitests/content/", "")
+ .slice(0, -1);
+
+const SWM = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+ Ci.nsIServiceWorkerManager
+);
+
+// The expected minimum usage for an origin that has any Cache API storage in
+// use. Currently, the DB uses a page size of 4k and a minimum growth size of
+// 32k and has enough tables/indices for this to round up to 64k.
+const kMinimumOriginUsageBytes = 65536;
+
+function getPrincipal(url, attrs) {
+ const uri = Services.io.newURI(url);
+ if (!attrs) {
+ attrs = {};
+ }
+ return Services.scriptSecurityManager.createContentPrincipal(uri, attrs);
+}
+
+async function _qm_requestFinished(request) {
+ await new Promise(function (resolve) {
+ request.callback = function () {
+ resolve();
+ };
+ });
+
+ if (request.resultCode !== Cr.NS_OK) {
+ throw new RequestError(request.resultCode, request.resultName);
+ }
+
+ return request.result;
+}
+
+async function qm_reset_storage() {
+ return new Promise(resolve => {
+ let request = Services.qms.reset();
+ request.callback = resolve;
+ });
+}
+
+async function get_qm_origin_usage(origin) {
+ return new Promise(resolve => {
+ const principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+ Services.qms.getUsageForPrincipal(principal, request =>
+ resolve(request.result.usage)
+ );
+ });
+}
+
+/**
+ * Clear the group associated with the given origin via nsIClearDataService. We
+ * are using nsIClearDataService here because nsIQuotaManagerService doesn't
+ * (directly) provide a means of clearing a group.
+ */
+async function clear_qm_origin_group_via_clearData(origin) {
+ const uri = Services.io.newURI(origin);
+ const baseDomain = Services.eTLD.getBaseDomain(uri);
+ info(`Clearing storage on domain ${baseDomain} (from origin ${origin})`);
+
+ // Initiate group clearing and wait for it.
+ await new Promise((resolve, reject) => {
+ Services.clearData.deleteDataFromBaseDomain(
+ baseDomain,
+ false,
+ Services.clearData.CLEAR_DOM_QUOTA,
+ failedFlags => {
+ if (failedFlags) {
+ reject(failedFlags);
+ } else {
+ resolve();
+ }
+ }
+ );
+ });
+}
+
+/**
+ * Look up the nsIServiceWorkerRegistrationInfo for a given SW descriptor.
+ */
+function swm_lookup_reg(swDesc) {
+ // Scopes always include the full origin.
+ const fullScope = `${swDesc.origin}/${DIR_PATH}/${swDesc.scope}`;
+ const principal = getPrincipal(fullScope);
+
+ const reg = SWM.getRegistrationByPrincipal(principal, fullScope);
+
+ return reg;
+}
+
+/**
+ * Install a ServiceWorker according to the provided descriptor by opening a
+ * fresh tab that will be closed when we are done. Returns the
+ * `nsIServiceWorkerRegistrationInfo` corresponding to the registration.
+ *
+ * The descriptor may have the following properties:
+ * - scope: Optional.
+ * - script: The script, which usually just wants to be a relative path.
+ * - origin: Requred, the origin (which should not include a trailing slash).
+ */
+async function install_sw(swDesc) {
+ info(
+ `Installing ServiceWorker ${swDesc.script} at ${swDesc.scope} on origin ${swDesc.origin}`
+ );
+ const pageUrlStr = `${swDesc.origin}/${DIR_PATH}/empty_with_utils.html`;
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: pageUrlStr,
+ },
+ async browser => {
+ await SpecialPowers.spawn(
+ browser,
+ [{ swScript: swDesc.script, swScope: swDesc.scope }],
+ async function ({ swScript, swScope }) {
+ await content.wrappedJSObject.registerAndWaitForActive(
+ swScript,
+ swScope
+ );
+ }
+ );
+ }
+ );
+ info(`ServiceWorker installed`);
+
+ return swm_lookup_reg(swDesc);
+}
+
+/**
+ * Consume storage in the given origin by storing randomly generated Blobs into
+ * Cache API storage and IndexedDB storage. We use both APIs in order to
+ * ensure that data clearing wipes both QM clients.
+ *
+ * Randomly generated Blobs means Blobs with literally random content. This is
+ * done to compensate for the Cache API using snappy for compression.
+ */
+async function consume_storage(origin, storageDesc) {
+ info(`Consuming storage on origin ${origin}`);
+ const pageUrlStr = `${origin}/${DIR_PATH}/empty_with_utils.html`;
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: pageUrlStr,
+ },
+ async browser => {
+ await SpecialPowers.spawn(
+ browser,
+ [storageDesc],
+ async function ({ cacheBytes, idbBytes }) {
+ await content.wrappedJSObject.fillStorage(cacheBytes, idbBytes);
+ }
+ );
+ }
+ );
+}
+
+// Check if the origin is effectively empty, but allowing for the minimum size
+// Cache API database to be present.
+function is_minimum_origin_usage(originUsageBytes) {
+ return originUsageBytes <= kMinimumOriginUsageBytes;
+}
+
+/**
+ * Perform a navigation, waiting until the navigation stops, then returning
+ * the `textContent` of the body node. The expectation is this will be used
+ * with ServiceWorkers that return a body that indicates the ServiceWorker
+ * provided the result (possibly derived from the request) versus if
+ * interception didn't happen.
+ */
+async function navigate_and_get_body(swDesc, debugTag) {
+ let pageUrlStr = `${swDesc.origin}/${DIR_PATH}/${swDesc.scope}`;
+ if (debugTag) {
+ pageUrlStr += "?" + debugTag;
+ }
+ info(`Navigating to ${pageUrlStr}`);
+
+ const tabResult = await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: pageUrlStr,
+ // In the event of an aborted navigation, the load event will never
+ // happen...
+ waitForLoad: false,
+ // ...but the stop will.
+ waitForStateStop: true,
+ },
+ async browser => {
+ info(` Tab opened, querying body content.`);
+ const spawnResult = await SpecialPowers.spawn(browser, [], function () {
+ const controlled = !!content.navigator.serviceWorker.controller;
+ // Special-case about: URL's.
+ let loc = content.document.documentURI;
+ if (loc.startsWith("about:")) {
+ // about:neterror is parameterized by query string, so truncate that
+ // off because our tests just care if we're seeing the neterror page.
+ const idxQuestion = loc.indexOf("?");
+ if (idxQuestion !== -1) {
+ loc = loc.substring(0, idxQuestion);
+ }
+ return { controlled, body: loc };
+ }
+ return {
+ controlled,
+ body: content.document?.body?.textContent?.trim(),
+ };
+ });
+
+ return spawnResult;
+ }
+ );
+
+ return tabResult;
+}
+
+function waitForIframeLoad(iframe) {
+ return new Promise(function (resolve) {
+ iframe.onload = resolve;
+ });
+}
+
+function waitForRegister(scope, callback) {
+ return new Promise(function (resolve) {
+ let listener = {
+ onRegister(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(registration) {
+ if (registration.scope !== scope) {
+ return;
+ }
+ SWM.removeListener(listener);
+ resolve(registration);
+ },
+ };
+ SWM.addListener(listener);
+ });
+}
+
+// Be careful using this helper function, please make sure QuotaUsageCheck must
+// happen, otherwise test would be stucked in this function.
+function waitForQuotaUsageCheckFinish(scope) {
+ return new Promise(function (resolve) {
+ let listener = {
+ onQuotaUsageCheckFinish(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() {
+ registration.removeListener(listener);
+ if (callback) {
+ callback();
+ }
+ resolve(callback ? callback() : undefined);
+ },
+ };
+ registration.addListener(listener);
+ });
+}
+
+function waitForServiceWorkerShutdown() {
+ return new Promise(function (resolve) {
+ let observer = {
+ observe(subject, topic, data) {
+ if (topic !== "service-worker-shutdown") {
+ return;
+ }
+ SpecialPowers.removeObserver(observer, "service-worker-shutdown");
+ resolve();
+ },
+ };
+ SpecialPowers.addObserver(observer, "service-worker-shutdown");
+ });
+}
diff --git a/dom/serviceworkers/test/browser_intercepted_channel_process_swap.js b/dom/serviceworkers/test/browser_intercepted_channel_process_swap.js
new file mode 100644
index 0000000000..2aa5618a20
--- /dev/null
+++ b/dom/serviceworkers/test/browser_intercepted_channel_process_swap.js
@@ -0,0 +1,110 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that navigation loads through intercepted channels result in the
+// appropriate process swaps. This appears to only be possible when navigating
+// to a cross-origin URL, where that navigation is controlled by a ServiceWorker.
+
+"use strict";
+
+const SAME_ORIGIN = "https://example.com";
+const CROSS_ORIGIN = "https://example.org";
+
+const SAME_ORIGIN_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ SAME_ORIGIN
+);
+const CROSS_ORIGIN_ROOT = SAME_ORIGIN_ROOT.replace(SAME_ORIGIN, CROSS_ORIGIN);
+
+const SW_REGISTER_URL = `${CROSS_ORIGIN_ROOT}empty_with_utils.html`;
+const SW_SCRIPT_URL = `${CROSS_ORIGIN_ROOT}intercepted_channel_process_swap_worker.js`;
+const URL_BEFORE_NAVIGATION = `${SAME_ORIGIN_ROOT}empty.html`;
+const CROSS_ORIGIN_URL = `${CROSS_ORIGIN_ROOT}empty.html`;
+
+const TESTCASES = [
+ {
+ url: CROSS_ORIGIN_URL,
+ description:
+ "Controlled cross-origin navigation with network-provided response",
+ },
+ {
+ url: `${CROSS_ORIGIN_ROOT}this-path-does-not-exist?respondWith=${CROSS_ORIGIN_URL}`,
+ description:
+ "Controlled cross-origin navigation with ServiceWorker-provided response",
+ },
+];
+
+async function navigateTab(aTab, aUrl) {
+ BrowserTestUtils.startLoadingURIString(aTab.linkedBrowser, aUrl);
+
+ await BrowserTestUtils.waitForLocationChange(gBrowser, aUrl).then(() =>
+ BrowserTestUtils.browserStopped(aTab.linkedBrowser)
+ );
+}
+
+async function runTestcase(aTab, aTestcase) {
+ info(`Testing ${aTestcase.description}`);
+
+ await navigateTab(aTab, URL_BEFORE_NAVIGATION);
+
+ const [initialPid] = E10SUtils.getBrowserPids(aTab.linkedBrowser);
+
+ await navigateTab(aTab, aTestcase.url);
+
+ const [finalPid] = E10SUtils.getBrowserPids(aTab.linkedBrowser);
+
+ await SpecialPowers.spawn(aTab.linkedBrowser, [], () => {
+ Assert.ok(
+ content.navigator.serviceWorker.controller,
+ `${content.location} should be controlled.`
+ );
+ });
+
+ Assert.notEqual(
+ initialPid,
+ finalPid,
+ `Navigating from ${URL_BEFORE_NAVIGATION} to ${aTab.linkedBrowser.currentURI.spec} should have resulted in a different PID.`
+ );
+}
+
+add_task(async function setupPrefs() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ });
+});
+
+add_task(async function setupBrowser() {
+ const tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: SW_REGISTER_URL,
+ });
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [SW_SCRIPT_URL],
+ async scriptUrl => {
+ await content.wrappedJSObject.registerAndWaitForActive(scriptUrl);
+ }
+ );
+});
+
+add_task(async function runTestcases() {
+ for (const testcase of TESTCASES) {
+ await runTestcase(gBrowser.selectedTab, testcase);
+ }
+});
+
+add_task(async function cleanup() {
+ const tab = gBrowser.selectedTab;
+
+ await navigateTab(tab, SW_REGISTER_URL);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await content.wrappedJSObject.unregisterAll();
+ });
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/dom/serviceworkers/test/browser_intercepted_worker_script.js b/dom/serviceworkers/test/browser_intercepted_worker_script.js
new file mode 100644
index 0000000000..123110952d
--- /dev/null
+++ b/dom/serviceworkers/test/browser_intercepted_worker_script.js
@@ -0,0 +1,102 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test tests if the service worker is able to intercept the script loading
+ * channel of a dedicated worker.
+ *
+ * On success, the test will not crash.
+ */
+
+const SAME_ORIGIN = "https://example.com";
+
+const SAME_ORIGIN_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ SAME_ORIGIN
+);
+
+const SW_REGISTER_URL = `${SAME_ORIGIN_ROOT}empty_with_utils.html`;
+const SW_SCRIPT_URL = `${SAME_ORIGIN_ROOT}simple_fetch_worker.js`;
+const SCRIPT_URL = `${SAME_ORIGIN_ROOT}empty.js`;
+
+async function navigateTab(aTab, aUrl) {
+ BrowserTestUtils.startLoadingURIString(aTab.linkedBrowser, aUrl);
+
+ await BrowserTestUtils.waitForLocationChange(gBrowser, aUrl).then(() =>
+ BrowserTestUtils.browserStopped(aTab.linkedBrowser)
+ );
+}
+
+async function runTest(aTestSharedWorker) {
+ const tab = gBrowser.selectedTab;
+
+ await navigateTab(tab, SW_REGISTER_URL);
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [SCRIPT_URL, aTestSharedWorker],
+ async (scriptUrl, testSharedWorker) => {
+ await new Promise(resolve => {
+ content.navigator.serviceWorker.onmessage = e => {
+ if (e.data == scriptUrl) {
+ resolve();
+ }
+ };
+
+ if (testSharedWorker) {
+ let worker = new content.Worker(scriptUrl);
+ } else {
+ let worker = new content.SharedWorker(scriptUrl);
+ }
+ });
+ }
+ );
+
+ ok(true, "The service worker has intercepted the script loading.");
+}
+
+add_task(async function setupPrefs() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ [
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ],
+ ],
+ });
+});
+
+add_task(async function setupBrowser() {
+ // The tab will be used by subsequent test steps via 'gBrowser.selectedTab'.
+ const tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: SW_REGISTER_URL,
+ });
+
+ registerCleanupFunction(async _ => {
+ await navigateTab(tab, SW_REGISTER_URL);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await content.wrappedJSObject.unregisterAll();
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ });
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [SW_SCRIPT_URL],
+ async scriptUrl => {
+ await content.wrappedJSObject.registerAndWaitForActive(scriptUrl);
+ }
+ );
+});
+
+add_task(async function runTests() {
+ await runTest(false);
+ await runTest(true);
+});
diff --git a/dom/serviceworkers/test/browser_navigationPreload_read_after_respondWith.js b/dom/serviceworkers/test/browser_navigationPreload_read_after_respondWith.js
new file mode 100644
index 0000000000..d321fd8b54
--- /dev/null
+++ b/dom/serviceworkers/test/browser_navigationPreload_read_after_respondWith.js
@@ -0,0 +1,121 @@
+const TOP_DOMAIN = "http://mochi.test:8888/";
+const SW_DOMAIN = "https://example.org/";
+
+const TOP_TEST_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ TOP_DOMAIN
+);
+const SW_TEST_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ SW_DOMAIN
+);
+
+const TOP_EMPTY_PAGE = `${TOP_TEST_ROOT}empty_with_utils.html`;
+const SW_REGISTER_PAGE = `${SW_TEST_ROOT}empty_with_utils.html`;
+const SW_IFRAME_PAGE = `${SW_TEST_ROOT}navigationPreload_page.html`;
+// An empty script suffices for our SW needs; it's by definition no-fetch.
+const SW_REL_SW_SCRIPT = "sw_with_navigationPreload.js";
+
+/**
+ * Test the FetchEvent.preloadResponse can be read after FetchEvent.respondWith()
+ *
+ * Step 1. register a ServiceWorker which only handles FetchEvent when request
+ * url includes navigationPreload_page.html. Otherwise, it alwasy
+ * fallbacks the fetch to the network.
+ * If the request url includes navigationPreload_page.html, it call
+ * FetchEvent.respondWith() with a new Resposne, and then call
+ * FetchEvent.waitUtil() to wait FetchEvent.preloadResponse and post the
+ * preloadResponse's text to clients.
+ * Step 2. Open a controlled page and register message event handler to receive
+ * the postMessage from ServiceWorker.
+ * Step 3. Create a iframe which url is navigationPreload_page.html, such that
+ * ServiceWorker can fake the response and then send preloadResponse's
+ * result.
+ * Step 4. Unregister the ServiceWorker and cleanup the environment.
+ */
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ });
+
+ // Step 1.
+ info("Opening a new tab: " + SW_REGISTER_PAGE);
+ let topTab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: SW_REGISTER_PAGE,
+ });
+
+ // ## Install SW
+ await SpecialPowers.spawn(
+ topTab.linkedBrowser,
+ [{ sw: SW_REL_SW_SCRIPT }],
+ async function ({ sw }) {
+ // Waive the xray to use the content utils.js script functions.
+ dump(`register serviceworker...\n`);
+ await content.wrappedJSObject.registerAndWaitForActive(sw);
+ }
+ );
+
+ // Step 2.
+ info("Loading a controlled page: " + SW_REGISTER_PAGE);
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ topTab.linkedBrowser
+ );
+ BrowserTestUtils.startLoadingURIString(
+ topTab.linkedBrowser,
+ SW_REGISTER_PAGE
+ );
+ await browserLoadedPromise;
+
+ info("Create a target iframe: " + SW_IFRAME_PAGE);
+ let result = await SpecialPowers.spawn(
+ topTab.linkedBrowser,
+ [{ url: SW_IFRAME_PAGE }],
+ async function ({ url }) {
+ async function waitForNavigationPreload() {
+ return new Promise(resolve => {
+ content.wrappedJSObject.navigator.serviceWorker.addEventListener(
+ `message`,
+ event => {
+ resolve(event.data);
+ }
+ );
+ });
+ }
+
+ let promise = waitForNavigationPreload();
+
+ // Step 3.
+ const iframe = content.wrappedJSObject.document.createElement("iframe");
+ iframe.src = url;
+ content.wrappedJSObject.document.body.appendChild(iframe);
+ await new Promise(r => {
+ iframe.onload = r;
+ });
+
+ let result = await promise;
+ return result;
+ }
+ );
+
+ is(result, "NavigationPreload\n", "Should get NavigationPreload result");
+
+ // Step 4.
+ info("Loading the SW unregister page: " + SW_REGISTER_PAGE);
+ browserLoadedPromise = BrowserTestUtils.browserLoaded(topTab.linkedBrowser);
+ BrowserTestUtils.startLoadingURIString(
+ topTab.linkedBrowser,
+ SW_REGISTER_PAGE
+ );
+ await browserLoadedPromise;
+
+ await SpecialPowers.spawn(topTab.linkedBrowser, [], async function () {
+ await content.wrappedJSObject.unregisterAll();
+ });
+
+ // Close the testing tab.
+ BrowserTestUtils.removeTab(topTab);
+});
diff --git a/dom/serviceworkers/test/browser_navigation_fetch_fault_handling.js b/dom/serviceworkers/test/browser_navigation_fetch_fault_handling.js
new file mode 100644
index 0000000000..a72fc68b69
--- /dev/null
+++ b/dom/serviceworkers/test/browser_navigation_fetch_fault_handling.js
@@ -0,0 +1,276 @@
+/**
+ * This test file tests our automatic recovery and any related mitigating
+ * heuristics that occur during intercepted navigation fetch request.
+ * Specifically, we should be resetting interception so that we go to the
+ * network in these cases and then potentially taking actions like unregistering
+ * the ServiceWorker and/or clearing QuotaManager-managed storage for the
+ * origin.
+ *
+ * See specific test permutations for specific details inline in the test.
+ *
+ * NOTE THAT CURRENTLY THIS TEST IS DISCUSSING MITIGATIONS THAT ARE NOT YET
+ * IMPLEMENTED, JUST PLANNED. These will be iterated on and added to the rest
+ * of the stack of patches on Bug 1503072.
+ *
+ * ## Test Mechanics
+ *
+ * ### Fetch Fault Injection
+ *
+ * We expose:
+ * - On nsIServiceWorkerInfo, the per-ServiceWorker XPCOM interface:
+ * - A mechanism for creating synthetic faults by setting the
+ * `nsIServiceWorkerInfo::testingInjectCancellation` attribute to a failing
+ * nsresult. The fault is applied at the beginning of the steps to dispatch
+ * the fetch event on the global.
+ * - A count of the number of times we experienced these navigation faults
+ * that had to be reset as `nsIServiceWorkerInfo::navigationFaultCount`.
+ * (This would also include real faults, but we only expect to see synthetic
+ * faults in this test.)
+ * - On nsIServiceWorkerRegistrationInfo, the per-registration XPCOM interface:
+ * - A readonly attribute that indicates how many times an origin storage
+ * usage check has been initiated.
+ *
+ * We also use:
+ * - `nsIServiceWorkerManager::addListener(nsIServiceWorkerManagerListener)`
+ * allows our test to listen for the unregistration of registrations. This
+ * allows us to be notified when unregistering or origin-clearing actions have
+ * been taken as a mitigation.
+ *
+ * ### General Test Approach
+ *
+ * For each test we:
+ * - Ensure/confirm the testing origin has no QuotaManager storage in use.
+ * - Install the ServiceWorker.
+ * - If we are testing the situation where we want to simulate the origin being
+ * near its quota limit, we also generate Cache API and IDB storage usage
+ * sufficient to put our origin over the threshold.
+ * - We run a quota check on the origin after doing this in order to make sure
+ * that we did this correctly and that we properly constrained the limit for
+ * the origin. We fail the test for test implementation reasons if we
+ * didn't accomplish this.
+ * - Verify a fetch navigation to the SW works without any fault injection,
+ * producing a result produced by the ServiceWorker.
+ * - Begin fault permutations in a loop, where for each pass of the loop:
+ * - We trigger a navigation which will result in an intercepted fetch
+ * which will fault. We wait until the navigation completes.
+ * - We verify that we got the request from the network.
+ * - We verify that the ServiceWorker's navigationFaultCount increased.
+ * - If this the count at which we expect a mitigation to take place, we wait
+ * for the registration to become unregistered AND:
+ * - We check whether the storage for the origin was cleared or not, which
+ * indicates which mitigation of the following happened:
+ * - Unregister the registration directly.
+ * - Clear the origin's data which will also unregister the registration
+ * as a side effect.
+ * - We check whether the registration indicates an origin quota check
+ * happened or not.
+ *
+ * ### Disk Usage Limits
+ *
+ * In order to avoid gratuitous disk I/O and related overheads, we limit QM
+ * ("temporary") storage to 10 MiB which ends up limiting group usage to 10 MiB.
+ * This lets us set a threshold situation where we claim that a SW needs at
+ * least 4 MiB of storage for installation/operation, meaning that any usage
+ * beyond 6 MiB in the group will constitute a need to clear the group or
+ * origin. We fill with the storage with 8 MiB of artificial usage to this end,
+ * storing 4 MiB in Cache API and 4 MiB in IDB.
+ **/
+
+// Because of the amount of I/O involved in this test, pernosco reproductions
+// may experience timeouts without a timeout multiplier.
+requestLongerTimeout(2);
+
+/* import-globals-from browser_head.js */
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/serviceworkers/test/browser_head.js",
+ this
+);
+
+// The origin we run the tests on.
+const TEST_ORIGIN = "https://test1.example.org";
+// An origin in the same group that impacts the usage of the TEST_ORIGIN. Used
+// to verify heuristics related to group-clearing (where clearing the
+// TEST_ORIGIN itself would not be sufficient for us to mitigate quota limits
+// being reached.)
+const SAME_GROUP_ORIGIN = "https://test2.example.org";
+
+const TEST_SW_SETUP = {
+ origin: TEST_ORIGIN,
+ // Page with a body textContent of "NETWORK" and has utils.js loaded.
+ scope: "network_with_utils.html",
+ // SW that serves a body with a textContent of "SERVICEWORKER" and
+ // has utils.js loaded.
+ script: "sw_respondwith_serviceworker.js",
+};
+
+const TEST_STORAGE_SETUP = {
+ cacheBytes: 4 * 1024 * 1024, // 4 MiB
+ idbBytes: 4 * 1024 * 1024, // 4 MiB
+};
+
+const FAULTS_BEFORE_MITIGATION = 3;
+
+/**
+ * Core test iteration logic.
+ *
+ * Parameters:
+ * - name: Human readable name of the fault we're injecting.
+ * - useError: The nsresult failure code to inject into fetch.
+ * - errorPage: The "about" page that we expect errors to leave us on.
+ * - consumeQuotaOrigin: If truthy, the origin to place the storage usage in.
+ * If falsey, we won't fill storage.
+ */
+async function do_fault_injection_test({
+ name,
+ useError,
+ errorPage,
+ consumeQuotaOrigin,
+}) {
+ info(
+ `### testing: error: ${name} (${useError}) consumeQuotaOrigin: ${consumeQuotaOrigin}`
+ );
+
+ // ## Ensure/confirm the testing origins have no QuotaManager storage in use.
+ await clear_qm_origin_group_via_clearData(TEST_ORIGIN);
+
+ // ## Install the ServiceWorker
+ const reg = await install_sw(TEST_SW_SETUP);
+ const sw = reg.activeWorker;
+
+ // ## Generate quota usage if appropriate
+ if (consumeQuotaOrigin) {
+ await consume_storage(consumeQuotaOrigin, TEST_STORAGE_SETUP);
+ }
+
+ // ## Verify normal navigation is served by the SW.
+ info(`## Checking normal operation.`);
+ {
+ const debugTag = `err=${name}&fault=0`;
+ const docInfo = await navigate_and_get_body(TEST_SW_SETUP, debugTag);
+ is(
+ docInfo.body,
+ "SERVICEWORKER",
+ "navigation without injected fault originates from ServiceWorker"
+ );
+
+ is(
+ docInfo.controlled,
+ true,
+ "successfully intercepted navigation should be controlled"
+ );
+ }
+
+ // Make sure the test is listening on the ServiceWorker unregistration, since
+ // we expect it happens after navigation fault threshold reached.
+ const unregisteredPromise = waitForUnregister(reg.scope);
+
+ // Make sure the test is listening on the finish of quota checking, since we
+ // expect it happens after navigation fault threshold reached.
+ const quotaUsageCheckFinishPromise = waitForQuotaUsageCheckFinish(reg.scope);
+
+ // ## Inject faults in a loop until expected mitigation.
+ sw.testingInjectCancellation = useError;
+ for (let iFault = 0; iFault < FAULTS_BEFORE_MITIGATION; iFault++) {
+ info(`## Testing with injected fault number ${iFault + 1}`);
+ // We should never have triggered an origin quota usage check before the
+ // final fault injection.
+ is(reg.quotaUsageCheckCount, 0, "No quota usage check yet");
+
+ // Make sure our loads encode the specific
+ const debugTag = `err=${name}&fault=${iFault + 1}`;
+
+ const docInfo = await navigate_and_get_body(TEST_SW_SETUP, debugTag);
+ // We should always be receiving network fallback.
+ is(
+ docInfo.body,
+ "NETWORK",
+ "navigation with injected fault originates from network"
+ );
+
+ is(docInfo.controlled, false, "bypassed pages shouldn't be controlled");
+
+ // The fault count should have increased
+ is(
+ sw.navigationFaultCount,
+ iFault + 1,
+ "navigation fault increased (to expected value)"
+ );
+ }
+
+ await unregisteredPromise;
+ is(reg.unregistered, true, "registration should be unregistered");
+
+ //is(reg.quotaUsageCheckCount, 1, "Quota usage check must be started");
+ await quotaUsageCheckFinishPromise;
+
+ if (consumeQuotaOrigin) {
+ // Check that there is no longer any storage usaged by the origin in this
+ // case.
+ const originUsage = await get_qm_origin_usage(TEST_ORIGIN);
+ ok(
+ is_minimum_origin_usage(originUsage),
+ "origin usage should be mitigated"
+ );
+
+ if (consumeQuotaOrigin === SAME_GROUP_ORIGIN) {
+ const sameGroupUsage = await get_qm_origin_usage(SAME_GROUP_ORIGIN);
+ Assert.strictEqual(
+ sameGroupUsage,
+ 0,
+ "same group usage should be mitigated"
+ );
+ }
+ }
+}
+
+add_task(async function test_navigation_fetch_fault_handling() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.serviceWorkers.mitigations.bypass_on_fault", true],
+ ["dom.serviceWorkers.mitigations.group_usage_headroom_kb", 5 * 1024],
+ ["dom.quotaManager.testing", true],
+ // We want the temporary global limit to be 10 MiB (the pref is in KiB).
+ // This will result in the group limit also being 10 MiB because on small
+ // disks we provide a group limit value of min(10 MiB, global limit).
+ ["dom.quotaManager.temporaryStorage.fixedLimit", 10 * 1024],
+ ],
+ });
+
+ // Need to reset the storages to make dom.quotaManager.temporaryStorage.fixedLimit
+ // works.
+ await qm_reset_storage();
+
+ const quotaOriginVariations = [
+ // Don't put us near the storage limit.
+ undefined,
+ // Put us near the storage limit in the SW origin itself.
+ TEST_ORIGIN,
+ // Put us near the storage limit in the SW origin's group but not the origin
+ // itself.
+ SAME_GROUP_ORIGIN,
+ ];
+
+ for (const consumeQuotaOrigin of quotaOriginVariations) {
+ await do_fault_injection_test({
+ name: "NS_ERROR_DOM_ABORT_ERR",
+ useError: 0x80530014, // Not in `Cr`.
+ // Abort errors manifest as about:blank pages.
+ errorPage: "about:blank",
+ consumeQuotaOrigin,
+ });
+
+ await do_fault_injection_test({
+ name: "NS_ERROR_INTERCEPTION_FAILED",
+ useError: 0x804b0064, // Not in `Cr`.
+ // Interception failures manifest as corrupt content pages.
+ errorPage: "about:neterror",
+ consumeQuotaOrigin,
+ });
+ }
+
+ // Cleanup: wipe the origin and group so all the ServiceWorkers go away.
+ await clear_qm_origin_group_via_clearData(TEST_ORIGIN);
+});
diff --git a/dom/serviceworkers/test/browser_remote_type_process_swap.js b/dom/serviceworkers/test/browser_remote_type_process_swap.js
new file mode 100644
index 0000000000..3a9f138fa6
--- /dev/null
+++ b/dom/serviceworkers/test/browser_remote_type_process_swap.js
@@ -0,0 +1,138 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test tests a navigation request to a Service Worker-controlled origin &
+ * scope that results in a cross-origin redirect to a
+ * non-Service Worker-controlled scope which additionally participates in
+ * cross-process redirect.
+ *
+ * On success, the test will not crash.
+ */
+
+const ORIGIN = "http://mochi.test:8888";
+const TEST_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ ORIGIN
+);
+
+const SW_REGISTER_PAGE_URL = `${TEST_ROOT}empty_with_utils.html`;
+const SW_SCRIPT_URL = `${TEST_ROOT}empty.js`;
+
+const FILE_URL = (() => {
+ // Get the file as an nsIFile.
+ const file = getChromeDir(getResolvedURI(gTestPath));
+ file.append("empty.html");
+
+ // Convert the nsIFile to an nsIURI to access the path.
+ return Services.io.newFileURI(file).spec;
+})();
+
+const CROSS_ORIGIN = "https://example.com";
+const CROSS_ORIGIN_URL = SW_REGISTER_PAGE_URL.replace(ORIGIN, CROSS_ORIGIN);
+const CROSS_ORIGIN_REDIRECT_URL = `${TEST_ROOT}redirect.sjs?${CROSS_ORIGIN_URL}`;
+
+async function loadURI(aXULBrowser, aURI) {
+ const browserLoadedPromise = BrowserTestUtils.browserLoaded(aXULBrowser);
+ BrowserTestUtils.startLoadingURIString(aXULBrowser, aURI);
+
+ return browserLoadedPromise;
+}
+
+async function runTest() {
+ // Step 1: register a Service Worker under `ORIGIN` so that all subsequent
+ // requests to `ORIGIN` will be marked as controlled.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["devtools.console.stdout.content", true],
+ ],
+ });
+
+ info(`Loading tab with page ${SW_REGISTER_PAGE_URL}`);
+ const tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: SW_REGISTER_PAGE_URL,
+ });
+ info(`Loaded page ${SW_REGISTER_PAGE_URL}`);
+
+ info(`Registering Service Worker ${SW_SCRIPT_URL}`);
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ scriptURL: SW_SCRIPT_URL }],
+ async ({ scriptURL }) => {
+ await content.wrappedJSObject.registerAndWaitForActive(scriptURL);
+ }
+ );
+ info(`Registered and activated Service Worker ${SW_SCRIPT_URL}`);
+
+ // Step 2: open a page over file:// and navigate to trigger a process swap
+ // for the response.
+ info(`Loading ${FILE_URL}`);
+ await loadURI(tab.linkedBrowser, FILE_URL);
+
+ Assert.equal(
+ tab.linkedBrowser.remoteType,
+ E10SUtils.FILE_REMOTE_TYPE,
+ `${FILE_URL} should load in a file process`
+ );
+
+ info(`Dynamically creating ${FILE_URL}'s link`);
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ href: CROSS_ORIGIN_REDIRECT_URL }],
+ ({ href }) => {
+ const { document } = content;
+ const link = document.createElement("a");
+ link.href = href;
+ link.id = "link";
+ link.appendChild(document.createTextNode(href));
+ document.body.appendChild(link);
+ }
+ );
+
+ const redirectPromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ CROSS_ORIGIN_URL
+ );
+
+ info("Starting navigation");
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#link",
+ {},
+ tab.linkedBrowser
+ );
+
+ info(`Waiting for location to change to ${CROSS_ORIGIN_URL}`);
+ await redirectPromise;
+
+ info("Waiting for the browser to stop");
+ await BrowserTestUtils.browserStopped(tab.linkedBrowser);
+
+ if (SpecialPowers.useRemoteSubframes) {
+ Assert.ok(
+ E10SUtils.isWebRemoteType(tab.linkedBrowser.remoteType),
+ `${CROSS_ORIGIN_URL} should load in a web-content process`
+ );
+ }
+
+ // Step 3: cleanup.
+ info("Loading initial page to unregister all Service Workers");
+ await loadURI(tab.linkedBrowser, SW_REGISTER_PAGE_URL);
+
+ info("Unregistering all Service Workers");
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async () => await content.wrappedJSObject.unregisterAll()
+ );
+
+ info("Closing tab");
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(runTest);
diff --git a/dom/serviceworkers/test/browser_storage_permission.js b/dom/serviceworkers/test/browser_storage_permission.js
new file mode 100644
index 0000000000..0bb99a9781
--- /dev/null
+++ b/dom/serviceworkers/test/browser_storage_permission.js
@@ -0,0 +1,297 @@
+"use strict";
+
+const { PermissionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PermissionTestUtils.sys.mjs"
+);
+
+const BASE_URI = "http://mochi.test:8888/browser/dom/serviceworkers/test/";
+const PAGE_URI = BASE_URI + "empty.html";
+const SCOPE = PAGE_URI + "?storage_permission";
+const SW_SCRIPT = BASE_URI + "empty.js";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Until the e10s refactor is complete, use a single process to avoid
+ // service worker propagation race.
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ });
+
+ let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI);
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(
+ browser,
+ [{ script: SW_SCRIPT, scope: SCOPE }],
+ async function (opts) {
+ let reg = await content.navigator.serviceWorker.register(opts.script, {
+ scope: opts.scope,
+ });
+ let worker = reg.installing || reg.waiting || reg.active;
+ await new Promise(resolve => {
+ if (worker.state === "activated") {
+ resolve();
+ return;
+ }
+ worker.addEventListener("statechange", function onStateChange() {
+ if (worker.state === "activated") {
+ worker.removeEventListener("statechange", onStateChange);
+ resolve();
+ }
+ });
+ });
+ }
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_allow_permission() {
+ PermissionTestUtils.add(
+ PAGE_URI,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_ALLOW
+ );
+
+ let tab = BrowserTestUtils.addTab(gBrowser, SCOPE);
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let controller = await SpecialPowers.spawn(browser, [], async function () {
+ return content.navigator.serviceWorker.controller;
+ });
+
+ ok(!!controller, "page should be controlled with storage allowed");
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_deny_permission() {
+ PermissionTestUtils.add(
+ PAGE_URI,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_DENY
+ );
+
+ let tab = BrowserTestUtils.addTab(gBrowser, SCOPE);
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let controller = await SpecialPowers.spawn(browser, [], async function () {
+ return content.navigator.serviceWorker.controller;
+ });
+
+ is(controller, null, "page should be not controlled with storage denied");
+
+ BrowserTestUtils.removeTab(tab);
+ PermissionTestUtils.remove(PAGE_URI, "cookie");
+});
+
+add_task(async function test_session_permission() {
+ PermissionTestUtils.add(
+ PAGE_URI,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_SESSION
+ );
+
+ let tab = BrowserTestUtils.addTab(gBrowser, SCOPE);
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let controller = await SpecialPowers.spawn(browser, [], async function () {
+ return content.navigator.serviceWorker.controller;
+ });
+
+ is(controller, null, "page should be not controlled with session storage");
+
+ BrowserTestUtils.removeTab(tab);
+ PermissionTestUtils.remove(PAGE_URI, "cookie");
+});
+
+// Test to verify an about:blank iframe successfully inherits the
+// parent's controller when storage is blocked between opening the
+// parent page and creating the iframe.
+add_task(async function test_block_storage_before_blank_iframe() {
+ PermissionTestUtils.remove(PAGE_URI, "cookie");
+
+ let tab = BrowserTestUtils.addTab(gBrowser, SCOPE);
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let controller = await SpecialPowers.spawn(browser, [], async function () {
+ return content.navigator.serviceWorker.controller;
+ });
+
+ ok(!!controller, "page should be controlled with storage allowed");
+
+ let controller2 = await SpecialPowers.spawn(browser, [], async function () {
+ let f = content.document.createElement("iframe");
+ content.document.body.appendChild(f);
+ await new Promise(resolve => (f.onload = resolve));
+ return !!f.contentWindow.navigator.serviceWorker.controller;
+ });
+
+ ok(!!controller2, "page should be controlled with storage allowed");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT],
+ ],
+ });
+
+ let controller3 = await SpecialPowers.spawn(browser, [], async function () {
+ let f = content.document.createElement("iframe");
+ content.document.body.appendChild(f);
+ await new Promise(resolve => (f.onload = resolve));
+ return !!f.contentWindow.navigator.serviceWorker.controller;
+ });
+
+ ok(!!controller3, "page should be controlled with storage allowed");
+
+ await SpecialPowers.popPrefEnv();
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Test to verify a blob URL iframe successfully inherits the
+// parent's controller when storage is blocked between opening the
+// parent page and creating the iframe.
+add_task(async function test_block_storage_before_blob_iframe() {
+ PermissionTestUtils.remove(PAGE_URI, "cookie");
+
+ let tab = BrowserTestUtils.addTab(gBrowser, SCOPE);
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let controller = await SpecialPowers.spawn(browser, [], async function () {
+ return content.navigator.serviceWorker.controller;
+ });
+
+ ok(!!controller, "page should be controlled with storage allowed");
+
+ let controller2 = await SpecialPowers.spawn(browser, [], async function () {
+ let b = new content.Blob(["<!DOCTYPE html><html></html>"], {
+ type: "text/html",
+ });
+ let f = content.document.createElement("iframe");
+ // No need to call revokeObjectURL() since the window will be closed shortly.
+ f.src = content.URL.createObjectURL(b);
+ content.document.body.appendChild(f);
+ await new Promise(resolve => (f.onload = resolve));
+ return !!f.contentWindow.navigator.serviceWorker.controller;
+ });
+
+ ok(!!controller2, "page should be controlled with storage allowed");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT],
+ ],
+ });
+
+ let controller3 = await SpecialPowers.spawn(browser, [], async function () {
+ let b = new content.Blob(["<!DOCTYPE html><html></html>"], {
+ type: "text/html",
+ });
+ let f = content.document.createElement("iframe");
+ // No need to call revokeObjectURL() since the window will be closed shortly.
+ f.src = content.URL.createObjectURL(b);
+ content.document.body.appendChild(f);
+ await new Promise(resolve => (f.onload = resolve));
+ return !!f.contentWindow.navigator.serviceWorker.controller;
+ });
+
+ ok(!!controller3, "page should be controlled with storage allowed");
+
+ await SpecialPowers.popPrefEnv();
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Test to verify a blob worker script does not hit our service
+// worker storage assertions when storage is blocked between opening
+// the parent page and creating the worker. Note, we cannot
+// explicitly check if the worker is controlled since we don't expose
+// WorkerNavigator.serviceWorkers.controller yet.
+add_task(async function test_block_storage_before_blob_worker() {
+ PermissionTestUtils.remove(PAGE_URI, "cookie");
+
+ let tab = BrowserTestUtils.addTab(gBrowser, SCOPE);
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let controller = await SpecialPowers.spawn(browser, [], async function () {
+ return content.navigator.serviceWorker.controller;
+ });
+
+ ok(!!controller, "page should be controlled with storage allowed");
+
+ let scriptURL = await SpecialPowers.spawn(browser, [], async function () {
+ let b = new content.Blob(
+ ["self.postMessage(self.location.href);self.close()"],
+ { type: "application/javascript" }
+ );
+ // No need to call revokeObjectURL() since the window will be closed shortly.
+ let u = content.URL.createObjectURL(b);
+ let w = new content.Worker(u);
+ return await new Promise(resolve => {
+ w.onmessage = e => resolve(e.data);
+ });
+ });
+
+ ok(scriptURL.startsWith("blob:"), "blob URL worker should run");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT],
+ ],
+ });
+
+ let scriptURL2 = await SpecialPowers.spawn(browser, [], async function () {
+ let b = new content.Blob(
+ ["self.postMessage(self.location.href);self.close()"],
+ { type: "application/javascript" }
+ );
+ // No need to call revokeObjectURL() since the window will be closed shortly.
+ let u = content.URL.createObjectURL(b);
+ let w = new content.Worker(u);
+ return await new Promise(resolve => {
+ w.onmessage = e => resolve(e.data);
+ });
+ });
+
+ ok(scriptURL2.startsWith("blob:"), "blob URL worker should run");
+
+ await SpecialPowers.popPrefEnv();
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function cleanup() {
+ PermissionTestUtils.remove(PAGE_URI, "cookie");
+
+ let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI);
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(browser, [SCOPE], async function (uri) {
+ let reg = await content.navigator.serviceWorker.getRegistration(uri);
+ let worker = reg.active;
+ await reg.unregister();
+ await new Promise(resolve => {
+ if (worker.state === "redundant") {
+ resolve();
+ return;
+ }
+ worker.addEventListener("statechange", function onStateChange() {
+ if (worker.state === "redundant") {
+ worker.removeEventListener("statechange", onStateChange);
+ resolve();
+ }
+ });
+ });
+ });
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/dom/serviceworkers/test/browser_storage_recovery.js b/dom/serviceworkers/test/browser_storage_recovery.js
new file mode 100644
index 0000000000..8b4a1181f7
--- /dev/null
+++ b/dom/serviceworkers/test/browser_storage_recovery.js
@@ -0,0 +1,156 @@
+"use strict";
+
+// This test registers a SW for a scope that will never control a document
+// and therefore never trigger a "fetch" functional event that would
+// automatically attempt to update the registration. The overlap of the
+// PAGE_URI and SCOPE is incidental. checkForUpdate is the only thing that
+// will trigger an update of the registration and so there is no need to
+// worry about Schedule Job races to coalesce an update job.
+
+const BASE_URI = "http://mochi.test:8888/browser/dom/serviceworkers/test/";
+const PAGE_URI = BASE_URI + "empty.html";
+const SCOPE = PAGE_URI + "?storage_recovery";
+const SW_SCRIPT = BASE_URI + "storage_recovery_worker.sjs";
+
+async function checkForUpdate(browser) {
+ return SpecialPowers.spawn(browser, [SCOPE], async function (uri) {
+ let reg = await content.navigator.serviceWorker.getRegistration(uri);
+ await reg.update();
+ return !!reg.installing;
+ });
+}
+
+// Delete all of our chrome-namespace Caches for this origin, leaving any
+// content-owned caches in place. This is exclusively for simulating loss
+// of the origin's storage without loss of the registration and without
+// having to worry that future enhancements to QuotaClients/ServiceWorkerRegistrar
+// will break this test. If you want to wipe storage for an origin, use
+// QuotaManager APIs
+async function wipeStorage(u) {
+ let uri = Services.io.newURI(u);
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+ let caches = new CacheStorage("chrome", principal);
+ let list = await caches.keys();
+ return Promise.all(list.map(c => caches.delete(c)));
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.serviceWorkers.idle_timeout", 0],
+ ],
+ });
+
+ // Configure the server script to not redirect.
+ await fetch(SW_SCRIPT + "?clear-redirect");
+
+ let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI);
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(
+ browser,
+ [{ script: SW_SCRIPT, scope: SCOPE }],
+ async function (opts) {
+ let reg = await content.navigator.serviceWorker.register(opts.script, {
+ scope: opts.scope,
+ });
+ let worker = reg.installing || reg.waiting || reg.active;
+ await new Promise(resolve => {
+ if (worker.state === "activated") {
+ resolve();
+ return;
+ }
+ worker.addEventListener("statechange", function onStateChange() {
+ if (worker.state === "activated") {
+ worker.removeEventListener("statechange", onStateChange);
+ resolve();
+ }
+ });
+ });
+ }
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Verify that our service worker doesn't update normally.
+add_task(async function normal_update_check() {
+ let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI);
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let updated = await checkForUpdate(browser);
+ ok(!updated, "normal update check should not trigger an update");
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Test what happens when we wipe the service worker scripts
+// out from under the site before triggering the update. This
+// should cause an update to occur.
+add_task(async function wiped_update_check() {
+ // Wipe the backing cache storage, but leave the SW registered.
+ await wipeStorage(PAGE_URI);
+
+ let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI);
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let updated = await checkForUpdate(browser);
+ ok(updated, "wiping the service worker scripts should trigger an update");
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Test what happens when we wipe the service worker scripts
+// out from under the site before triggering the update. This
+// should cause an update to occur.
+add_task(async function wiped_and_failed_update_check() {
+ // Wipe the backing cache storage, but leave the SW registered.
+ await wipeStorage(PAGE_URI);
+
+ // Configure the service worker script to redirect. This will
+ // prevent the update from completing successfully.
+ await fetch(SW_SCRIPT + "?set-redirect");
+
+ let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI);
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Attempt to update the service worker. This should throw
+ // an error because the script is now redirecting.
+ let updateFailed = false;
+ try {
+ await checkForUpdate(browser);
+ } catch (e) {
+ updateFailed = true;
+ }
+ ok(updateFailed, "redirecting service worker script should fail to update");
+
+ // Also, since the existing service worker's scripts are broken
+ // we should also remove the registration completely when the
+ // update fails.
+ let exists = await SpecialPowers.spawn(
+ browser,
+ [SCOPE],
+ async function (uri) {
+ let reg = await content.navigator.serviceWorker.getRegistration(uri);
+ return !!reg;
+ }
+ );
+ ok(
+ !exists,
+ "registration should be removed after scripts are wiped and update fails"
+ );
+
+ // Note, we don't have to clean up the service worker registration
+ // since its effectively been force-removed here.
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/dom/serviceworkers/test/browser_unregister_with_containers.js b/dom/serviceworkers/test/browser_unregister_with_containers.js
new file mode 100644
index 0000000000..c147e50f6e
--- /dev/null
+++ b/dom/serviceworkers/test/browser_unregister_with_containers.js
@@ -0,0 +1,153 @@
+"use strict";
+
+const BASE_URI = "http://mochi.test:8888/browser/dom/serviceworkers/test/";
+const PAGE_URI = BASE_URI + "empty.html";
+const SCOPE = PAGE_URI + "?unregister_with_containers";
+const SW_SCRIPT = BASE_URI + "empty.js";
+
+function doRegister(browser) {
+ return SpecialPowers.spawn(
+ browser,
+ [{ script: SW_SCRIPT, scope: SCOPE }],
+ async function (opts) {
+ let reg = await content.navigator.serviceWorker.register(opts.script, {
+ scope: opts.scope,
+ });
+ let worker = reg.installing || reg.waiting || reg.active;
+ await new Promise(resolve => {
+ if (worker.state === "activated") {
+ resolve();
+ return;
+ }
+ worker.addEventListener("statechange", function onStateChange() {
+ if (worker.state === "activated") {
+ worker.removeEventListener("statechange", onStateChange);
+ resolve();
+ }
+ });
+ });
+ }
+ );
+}
+
+function doUnregister(browser) {
+ return SpecialPowers.spawn(browser, [SCOPE], async function (uri) {
+ let reg = await content.navigator.serviceWorker.getRegistration(uri);
+ let worker = reg.active;
+ await reg.unregister();
+ await new Promise(resolve => {
+ if (worker.state === "redundant") {
+ resolve();
+ return;
+ }
+ worker.addEventListener("statechange", function onStateChange() {
+ if (worker.state === "redundant") {
+ worker.removeEventListener("statechange", onStateChange);
+ resolve();
+ }
+ });
+ });
+ });
+}
+
+function isControlled(browser) {
+ return SpecialPowers.spawn(browser, [], function () {
+ return !!content.navigator.serviceWorker.controller;
+ });
+}
+
+async function checkControlled(browser) {
+ let controlled = await isControlled(browser);
+ ok(controlled, "window should be controlled");
+}
+
+async function checkUncontrolled(browser) {
+ let controlled = await isControlled(browser);
+ ok(!controlled, "window should not be controlled");
+}
+
+add_task(async function test() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Avoid service worker propagation races by disabling multi-e10s for now.
+ // This can be removed after the e10s refactor is complete.
+ ["dom.ipc.processCount", 1],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ });
+
+ // Setup service workers in two different contexts with the same scope.
+ let containerTab1 = BrowserTestUtils.addTab(gBrowser, PAGE_URI, {
+ userContextId: 1,
+ });
+ let containerBrowser1 = gBrowser.getBrowserForTab(containerTab1);
+ await BrowserTestUtils.browserLoaded(containerBrowser1);
+
+ let containerTab2 = BrowserTestUtils.addTab(gBrowser, PAGE_URI, {
+ userContextId: 2,
+ });
+ let containerBrowser2 = gBrowser.getBrowserForTab(containerTab2);
+ await BrowserTestUtils.browserLoaded(containerBrowser2);
+
+ await doRegister(containerBrowser1);
+ await doRegister(containerBrowser2);
+
+ await checkUncontrolled(containerBrowser1);
+ await checkUncontrolled(containerBrowser2);
+
+ // Close the tabs we used to register the service workers. These are not
+ // controlled.
+ BrowserTestUtils.removeTab(containerTab1);
+ BrowserTestUtils.removeTab(containerTab2);
+
+ // Open a controlled tab in each container.
+ containerTab1 = BrowserTestUtils.addTab(gBrowser, SCOPE, {
+ userContextId: 1,
+ });
+ containerBrowser1 = gBrowser.getBrowserForTab(containerTab1);
+ await BrowserTestUtils.browserLoaded(containerBrowser1);
+
+ containerTab2 = BrowserTestUtils.addTab(gBrowser, SCOPE, {
+ userContextId: 2,
+ });
+ containerBrowser2 = gBrowser.getBrowserForTab(containerTab2);
+ await BrowserTestUtils.browserLoaded(containerBrowser2);
+
+ await checkControlled(containerBrowser1);
+ await checkControlled(containerBrowser2);
+
+ // Remove the first container's controlled tab
+ BrowserTestUtils.removeTab(containerTab1);
+
+ // Create a new uncontrolled tab for the first container and use it to
+ // unregister the service worker.
+ containerTab1 = BrowserTestUtils.addTab(gBrowser, PAGE_URI, {
+ userContextId: 1,
+ });
+ containerBrowser1 = gBrowser.getBrowserForTab(containerTab1);
+ await BrowserTestUtils.browserLoaded(containerBrowser1);
+ await doUnregister(containerBrowser1);
+
+ await checkUncontrolled(containerBrowser1);
+ await checkControlled(containerBrowser2);
+
+ // Remove the second container's controlled tab
+ BrowserTestUtils.removeTab(containerTab2);
+
+ // Create a new uncontrolled tab for the second container and use it to
+ // unregister the service worker.
+ containerTab2 = BrowserTestUtils.addTab(gBrowser, PAGE_URI, {
+ userContextId: 2,
+ });
+ containerBrowser2 = gBrowser.getBrowserForTab(containerTab2);
+ await BrowserTestUtils.browserLoaded(containerBrowser2);
+ await doUnregister(containerBrowser2);
+
+ await checkUncontrolled(containerBrowser1);
+ await checkUncontrolled(containerBrowser2);
+
+ // Close the two tabs we used to unregister the service worker.
+ BrowserTestUtils.removeTab(containerTab1);
+ BrowserTestUtils.removeTab(containerTab2);
+});
diff --git a/dom/serviceworkers/test/browser_userContextId_openWindow.js b/dom/serviceworkers/test/browser_userContextId_openWindow.js
new file mode 100644
index 0000000000..8a08d92cb1
--- /dev/null
+++ b/dom/serviceworkers/test/browser_userContextId_openWindow.js
@@ -0,0 +1,161 @@
+let Cm = Components.manager;
+
+let swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+ Ci.nsIServiceWorkerManager
+);
+
+const URI = "https://example.com/browser/dom/serviceworkers/test/empty.html";
+const MOCK_CID = Components.ID("{2a0f83c4-8818-4914-a184-f1172b4eaaa7}");
+const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1";
+const USER_CONTEXT_ID = 3;
+
+let mockAlertsService = {
+ showAlert(alert, alertListener) {
+ ok(true, "Showing alert");
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(function () {
+ alertListener.observe(null, "alertshow", alert.cookie);
+ }, 100);
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(function () {
+ alertListener.observe(null, "alertclickcallback", alert.cookie);
+ }, 100);
+ },
+
+ showAlertNotification(
+ imageUrl,
+ title,
+ text,
+ textClickable,
+ cookie,
+ alertListener,
+ name,
+ dir,
+ lang,
+ data
+ ) {
+ this.showAlert();
+ },
+
+ QueryInterface(aIID) {
+ if (aIID.equals(Ci.nsISupports) || aIID.equals(Ci.nsIAlertsService)) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ createInstance(aIID) {
+ return this.QueryInterface(aIID);
+ },
+};
+
+registerCleanupFunction(() => {
+ Cm.QueryInterface(Ci.nsIComponentRegistrar).unregisterFactory(
+ MOCK_CID,
+ mockAlertsService
+ );
+});
+
+add_setup(async function () {
+ // make sure userContext, SW and notifications are enabled.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.userContext.enabled", true],
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["notification.prompt.testing", true],
+ ["dom.serviceWorkers.disable_open_click_delay", 1000],
+ ["dom.serviceWorkers.idle_timeout", 299999],
+ ["dom.serviceWorkers.idle_extended_timeout", 299999],
+ ["browser.link.open_newwindow", 3],
+ ],
+ });
+});
+
+add_task(async function test() {
+ Cm.QueryInterface(Ci.nsIComponentRegistrar).registerFactory(
+ MOCK_CID,
+ "alerts service",
+ ALERTS_SERVICE_CONTRACT_ID,
+ mockAlertsService
+ );
+
+ // open the tab in the correct userContextId
+ let tab = BrowserTestUtils.addTab(gBrowser, URI, {
+ userContextId: USER_CONTEXT_ID,
+ });
+ let browser = gBrowser.getBrowserForTab(tab);
+
+ // select tab and make sure its browser is focused
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ // wait for tab load
+ await BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab));
+
+ // Waiting for new tab.
+ let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, null, true);
+
+ // here the test.
+ /* eslint-disable no-shadow */
+ let uci = await SpecialPowers.spawn(browser, [URI], uri => {
+ let uci = content.document.nodePrincipal.userContextId;
+
+ // Registration of the SW
+ return (
+ content.navigator.serviceWorker
+ .register("file_userContextId_openWindow.js")
+
+ // Activation
+ .then(swr => {
+ return new content.window.Promise(resolve => {
+ let worker = swr.installing;
+ worker.addEventListener("statechange", () => {
+ if (worker.state === "activated") {
+ resolve(swr);
+ }
+ });
+ });
+ })
+
+ // Ask for an openWindow.
+ .then(swr => {
+ swr.showNotification("testPopup");
+ return uci;
+ })
+ );
+ });
+ /* eslint-enable no-shadow */
+
+ is(uci, USER_CONTEXT_ID, "Tab runs with UCI " + USER_CONTEXT_ID);
+
+ let newTab = await newTabPromise;
+
+ is(
+ newTab.getAttribute("usercontextid"),
+ USER_CONTEXT_ID,
+ "New tab has UCI equal " + USER_CONTEXT_ID
+ );
+
+ // wait for SW unregistration
+ /* eslint-disable no-shadow */
+ uci = await SpecialPowers.spawn(browser, [], () => {
+ let uci = content.document.nodePrincipal.userContextId;
+
+ return content.navigator.serviceWorker
+ .getRegistration(".")
+ .then(registration => {
+ return registration.unregister();
+ })
+ .then(() => {
+ return uci;
+ });
+ });
+ /* eslint-enable no-shadow */
+
+ is(uci, USER_CONTEXT_ID, "Tab runs with UCI " + USER_CONTEXT_ID);
+
+ BrowserTestUtils.removeTab(newTab);
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/dom/serviceworkers/test/bug1151916_driver.html b/dom/serviceworkers/test/bug1151916_driver.html
new file mode 100644
index 0000000000..08e7d9414f
--- /dev/null
+++ b/dom/serviceworkers/test/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(registration) {
+ if (registration.active.state == "activating") {
+ registration.active.onstatechange = function(e) {
+ registration.active.onstatechange = null;
+ if (registration.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/serviceworkers/test/bug1151916_worker.js b/dom/serviceworkers/test/bug1151916_worker.js
new file mode 100644
index 0000000000..6bd26850bf
--- /dev/null
+++ b/dom/serviceworkers/test/bug1151916_worker.js
@@ -0,0 +1,15 @@
+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/serviceworkers/test/bug1240436_worker.js b/dom/serviceworkers/test/bug1240436_worker.js
new file mode 100644
index 0000000000..c21f60b60f
--- /dev/null
+++ b/dom/serviceworkers/test/bug1240436_worker.js
@@ -0,0 +1,2 @@
+// a contains a ZERO WIDTH JOINER (0x200D)
+var a = "‍";
diff --git a/dom/serviceworkers/test/chrome-common.toml b/dom/serviceworkers/test/chrome-common.toml
new file mode 100644
index 0000000000..e486456d11
--- /dev/null
+++ b/dom/serviceworkers/test/chrome-common.toml
@@ -0,0 +1,26 @@
+[DEFAULT]
+skip-if = ["os == 'android'"]
+support-files = [
+ "chrome_helpers.js",
+ "empty.js",
+ "fetch.js",
+ "hello.html",
+ "serviceworker.html",
+ "serviceworkerinfo_iframe.html",
+ "serviceworkermanager_iframe.html",
+ "serviceworkerregistrationinfo_iframe.html",
+ "utils.js",
+ "worker.js",
+ "worker2.js",
+]
+
+["test_devtools_track_serviceworker_time.html"]
+
+["test_privateBrowsing.html"]
+
+["test_serviceworkerinfo.xhtml"]
+skip-if = ["serviceworker_e10s"] # nsIWorkerDebugger attribute not implemented
+
+["test_serviceworkermanager.xhtml"]
+
+["test_serviceworkerregistrationinfo.xhtml"]
diff --git a/dom/serviceworkers/test/chrome-dFPI.toml b/dom/serviceworkers/test/chrome-dFPI.toml
new file mode 100644
index 0000000000..1aee0a219f
--- /dev/null
+++ b/dom/serviceworkers/test/chrome-dFPI.toml
@@ -0,0 +1,6 @@
+[DEFAULT]
+# Enable dFPI(cookieBehavior 5) for service worker tests.
+prefs = ["network.cookie.cookieBehavior=5"]
+dupe-manifest = true
+
+["include:chrome-common.toml"]
diff --git a/dom/serviceworkers/test/chrome.toml b/dom/serviceworkers/test/chrome.toml
new file mode 100644
index 0000000000..02efcc145d
--- /dev/null
+++ b/dom/serviceworkers/test/chrome.toml
@@ -0,0 +1,4 @@
+[DEFAULT]
+dupe-manifest = true
+
+["include:chrome-common.toml"]
diff --git a/dom/serviceworkers/test/chrome_helpers.js b/dom/serviceworkers/test/chrome_helpers.js
new file mode 100644
index 0000000000..9aaeb95625
--- /dev/null
+++ b/dom/serviceworkers/test/chrome_helpers.js
@@ -0,0 +1,71 @@
+let swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+ Ci.nsIServiceWorkerManager
+);
+
+let EXAMPLE_URL = "https://example.com/chrome/dom/serviceworkers/test/";
+
+function waitForIframeLoad(iframe) {
+ return new Promise(function (resolve) {
+ iframe.onload = resolve;
+ });
+}
+
+function waitForRegister(scope, callback) {
+ return new Promise(function (resolve) {
+ let listener = {
+ onRegister(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(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() {
+ registration.removeListener(listener);
+ if (callback) {
+ callback();
+ }
+ resolve(callback ? callback() : undefined);
+ },
+ };
+ registration.addListener(listener);
+ });
+}
+
+function waitForServiceWorkerShutdown() {
+ return new Promise(function (resolve) {
+ let observer = {
+ observe(subject, topic, data) {
+ if (topic !== "service-worker-shutdown") {
+ return;
+ }
+ SpecialPowers.removeObserver(observer, "service-worker-shutdown");
+ resolve();
+ },
+ };
+ SpecialPowers.addObserver(observer, "service-worker-shutdown");
+ });
+}
diff --git a/dom/serviceworkers/test/claim_clients/client.html b/dom/serviceworkers/test/claim_clients/client.html
new file mode 100644
index 0000000000..969a6dbf10
--- /dev/null
+++ b/dom/serviceworkers/test/claim_clients/client.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>Bug 1130684 - claim client </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ 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/serviceworkers/test/claim_oninstall_worker.js b/dom/serviceworkers/test/claim_oninstall_worker.js
new file mode 100644
index 0000000000..82c6999031
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/claim_worker_1.js b/dom/serviceworkers/test/claim_worker_1.js
new file mode 100644
index 0000000000..60ba5dfc5d
--- /dev/null
+++ b/dom/serviceworkers/test/claim_worker_1.js
@@ -0,0 +1,32 @@
+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 () {
+ return self.clients.claim();
+ })
+ .then(function (ret) {
+ result.resolve_value = ret;
+ return 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/serviceworkers/test/claim_worker_2.js b/dom/serviceworkers/test/claim_worker_2.js
new file mode 100644
index 0000000000..4293873da7
--- /dev/null
+++ b/dom/serviceworkers/test/claim_worker_2.js
@@ -0,0 +1,34 @@
+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 () {
+ return clients.claim();
+ })
+ .then(function (ret) {
+ result.resolve_value = ret;
+ return 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");
+ for (let i = 0; i < matched.length; ++i) {
+ dump("### ### matched[" + i + "]: " + matched[i].url + "\n");
+ }
+ }
+ });
+};
diff --git a/dom/serviceworkers/test/close_test.js b/dom/serviceworkers/test/close_test.js
new file mode 100644
index 0000000000..07f85617ef
--- /dev/null
+++ b/dom/serviceworkers/test/close_test.js
@@ -0,0 +1,22 @@
+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 (ex) {
+ ok(
+ ex.name === "InvalidAccessError",
+ "close() should throw InvalidAccessError"
+ );
+ }
+ client.postMessage({ status: "done" });
+ });
+ }
+};
diff --git a/dom/serviceworkers/test/console_monitor.js b/dom/serviceworkers/test/console_monitor.js
new file mode 100644
index 0000000000..099feb646d
--- /dev/null
+++ b/dom/serviceworkers/test/console_monitor.js
@@ -0,0 +1,44 @@
+/* eslint-env mozilla/chrome-script */
+
+let consoleListener;
+
+function ConsoleListener() {
+ Services.console.registerListener(this);
+}
+
+ConsoleListener.prototype = {
+ callbacks: [],
+
+ observe: aMsg => {
+ if (!(aMsg instanceof Ci.nsIScriptError)) {
+ return;
+ }
+
+ let msg = {
+ cssSelectors: aMsg.cssSelectors,
+ errorMessage: aMsg.errorMessage,
+ sourceName: aMsg.sourceName,
+ sourceLine: aMsg.sourceLine,
+ lineNumber: aMsg.lineNumber,
+ columnNumber: aMsg.columnNumber,
+ category: aMsg.category,
+ windowID: aMsg.outerWindowID,
+ innerWindowID: aMsg.innerWindowID,
+ isScriptError: true,
+ isWarning: (aMsg.flags & Ci.nsIScriptError.warningFlag) === 1,
+ };
+
+ sendAsyncMessage("monitor", msg);
+ },
+};
+
+addMessageListener("load", function (e) {
+ consoleListener = new ConsoleListener();
+ sendAsyncMessage("ready", {});
+});
+
+addMessageListener("unload", function (e) {
+ Services.console.unregisterListener(consoleListener);
+ consoleListener = null;
+ sendAsyncMessage("unloaded", {});
+});
diff --git a/dom/serviceworkers/test/controller/index.html b/dom/serviceworkers/test/controller/index.html
new file mode 100644
index 0000000000..2a68e3f4bb
--- /dev/null
+++ b/dom/serviceworkers/test/controller/index.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>Bug 94048 - test install event.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ // 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, message: msg}, "*");
+ }
+
+ function finish() {
+ parent.postMessage({status: "done"}, "*");
+ }
+
+ navigator.serviceWorker.ready.then(function(swr) {
+ my_ok(swr.scope.match(/serviceworkers\/test\/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\/test\/controller\/$/), "Scope is serviceworker/test/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(error) {
+ dump("Error unregistering the SW: " + error + "\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/serviceworkers/test/create_another_sharedWorker.html b/dom/serviceworkers/test/create_another_sharedWorker.html
new file mode 100644
index 0000000000..f49194fa50
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/download/window.html b/dom/serviceworkers/test/download/window.html
new file mode 100644
index 0000000000..5ca3c76f93
--- /dev/null
+++ b/dom/serviceworkers/test/download/window.html
@@ -0,0 +1,47 @@
+<!--
+ 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) {
+ resolve();
+ return;
+ }
+ navigator.serviceWorker.addEventListener('controllerchange', function onController() {
+ if (navigator.serviceWorker.controller) {
+ navigator.serviceWorker.removeEventListener('controllerchange', onController);
+ 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/serviceworkers/test/download/worker.js b/dom/serviceworkers/test/download/worker.js
new file mode 100644
index 0000000000..d96f18b34d
--- /dev/null
+++ b/dom/serviceworkers/test/download/worker.js
@@ -0,0 +1,34 @@
+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.includes("fake_download")) {
+ 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"',
+ // Prevent the default text editor from being launched
+ "Content-Type": "application/octet-stream",
+ // fake encoding header that should have no effect
+ "Content-Encoding": "gzip",
+ },
+ });
+ })
+ );
+});
diff --git a/dom/serviceworkers/test/download_canceled/page_download_canceled.html b/dom/serviceworkers/test/download_canceled/page_download_canceled.html
new file mode 100644
index 0000000000..dd67709004
--- /dev/null
+++ b/dom/serviceworkers/test/download_canceled/page_download_canceled.html
@@ -0,0 +1,59 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+</head>
+<body>
+
+<script src="../utils.js"></script>
+<script type="text/javascript">
+function wait_until_controlled() {
+ return new Promise(function(resolve) {
+ if (navigator.serviceWorker.controller) {
+ resolve('controlled');
+ return;
+ }
+ navigator.serviceWorker.addEventListener('controllerchange', function onController() {
+ if (navigator.serviceWorker.controller) {
+ navigator.serviceWorker.removeEventListener('controllerchange', onController);
+ resolve('controlled');
+ }
+ });
+ });
+}
+addEventListener('load', async function(event) {
+ window.controlled = wait_until_controlled();
+ window.registration =
+ await navigator.serviceWorker.register('sw_download_canceled.js');
+ let sw = registration.installing || registration.waiting ||
+ registration.active;
+ await waitForState(sw, 'activated');
+ sw.postMessage('claim');
+});
+
+// Place to hold promises for stream closures reported by the SW.
+window.streamClosed = {};
+
+// The ServiceWorker will postMessage to this BroadcastChannel when the streams
+// are closed. (Alternately, the SW could have used the clients API to post at
+// us, but the mechanism by which that operates would be different when this
+// test is uplifted, and it's desirable to avoid timing changes.)
+//
+// The browser test will use this promise to wait on stream shutdown.
+window.swStreamChannel = new BroadcastChannel("stream-closed");
+function trackStreamClosure(path) {
+ let resolve;
+ const promise = new Promise(r => { resolve = r });
+ window.streamClosed[path] = { promise, resolve };
+}
+window.swStreamChannel.onmessage = ({ data }) => {
+ window.streamClosed[data.what].resolve(data);
+};
+</script>
+
+</body>
+</html>
diff --git a/dom/serviceworkers/test/download_canceled/server-stream-download.sjs b/dom/serviceworkers/test/download_canceled/server-stream-download.sjs
new file mode 100644
index 0000000000..e6ae8c4f98
--- /dev/null
+++ b/dom/serviceworkers/test/download_canceled/server-stream-download.sjs
@@ -0,0 +1,132 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { setInterval, clearInterval } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+// stolen from file_blocked_script.sjs
+function setGlobalState(data, key) {
+ x = {
+ data,
+ QueryInterface(iid) {
+ return this;
+ },
+ };
+ x.wrappedJSObject = x;
+ setObjectState(key, x);
+}
+
+function getGlobalState(key) {
+ var data;
+ getObjectState(key, function (x) {
+ data = x && x.wrappedJSObject.data;
+ });
+ return data;
+}
+
+/*
+ * We want to let the sw_download_canceled.js service worker know when the
+ * stream was canceled. To this end, we let it issue a monitor request which we
+ * fulfill when the stream has been canceled. In order to coordinate between
+ * multiple requests, we use the getObjectState/setObjectState mechanism that
+ * httpd.js exposes to let data be shared and/or persist between requests. We
+ * handle both possible orderings of the requests because we currently don't
+ * try and impose an ordering between the two requests as issued by the SW, and
+ * file_blocked_script.sjs encourages us to do this, but we probably could order
+ * them.
+ */
+const MONITOR_KEY = "stream-monitor";
+function completeMonitorResponse(response, data) {
+ response.write(JSON.stringify(data));
+ response.finish();
+}
+function handleMonitorRequest(request, response) {
+ response.setHeader("Content-Type", "application/json");
+ response.setStatusLine(request.httpVersion, 200, "Found");
+
+ response.processAsync();
+ // Necessary to cause the headers to be flushed; that or touching the
+ // bodyOutputStream getter.
+ response.write("");
+ dump("server-stream-download.js: monitor headers issued\n");
+
+ const alreadyCompleted = getGlobalState(MONITOR_KEY);
+ if (alreadyCompleted) {
+ completeMonitorResponse(response, alreadyCompleted);
+ setGlobalState(null, MONITOR_KEY);
+ } else {
+ setGlobalState(response, MONITOR_KEY);
+ }
+}
+
+const MAX_TICK_COUNT = 3000;
+const TICK_INTERVAL = 2;
+function handleStreamRequest(request, response) {
+ const name = "server-stream-download";
+
+ // Create some payload to send.
+ let strChunk =
+ "Static routes are the future of ServiceWorkers! So say we all!\n";
+ while (strChunk.length < 1024) {
+ strChunk += strChunk;
+ }
+
+ response.setHeader("Content-Disposition", `attachment; filename="${name}"`);
+ response.setHeader(
+ "Content-Type",
+ `application/octet-stream; name="${name}"`
+ );
+ response.setHeader("Content-Length", `${strChunk.length * MAX_TICK_COUNT}`);
+ response.setStatusLine(request.httpVersion, 200, "Found");
+
+ response.processAsync();
+ response.write(strChunk);
+ dump("server-stream-download.js: stream headers + first payload issued\n");
+
+ let count = 0;
+ let intervalId;
+ function closeStream(why, message) {
+ dump("server-stream-download.js: closing stream: " + why + "\n");
+ clearInterval(intervalId);
+ response.finish();
+
+ const data = { why, message };
+ const monitorResponse = getGlobalState(MONITOR_KEY);
+ if (monitorResponse) {
+ completeMonitorResponse(monitorResponse, data);
+ setGlobalState(null, MONITOR_KEY);
+ } else {
+ setGlobalState(data, MONITOR_KEY);
+ }
+ }
+ function tick() {
+ try {
+ // bound worst-case behavior.
+ if (count++ > MAX_TICK_COUNT) {
+ closeStream("timeout", "timeout");
+ return;
+ }
+ response.write(strChunk);
+ } catch (e) {
+ closeStream("canceled", e.message);
+ }
+ }
+ intervalId = setInterval(tick, TICK_INTERVAL);
+}
+
+function handleRequest(request, response) {
+ dump(
+ "server-stream-download.js: processing request for " +
+ request.path +
+ "?" +
+ request.queryString +
+ "\n"
+ );
+ const query = new URLSearchParams(request.queryString);
+ if (query.has("monitor")) {
+ handleMonitorRequest(request, response);
+ } else {
+ handleStreamRequest(request, response);
+ }
+}
diff --git a/dom/serviceworkers/test/download_canceled/sw_download_canceled.js b/dom/serviceworkers/test/download_canceled/sw_download_canceled.js
new file mode 100644
index 0000000000..cd1d04df22
--- /dev/null
+++ b/dom/serviceworkers/test/download_canceled/sw_download_canceled.js
@@ -0,0 +1,151 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This file is derived from :bkelly's https://glitch.com/edit/#!/html-sw-stream
+
+addEventListener("install", evt => {
+ evt.waitUntil(self.skipWaiting());
+});
+
+// Create a BroadcastChannel to notify when we have closed our streams.
+const channel = new BroadcastChannel("stream-closed");
+
+const MAX_TICK_COUNT = 3000;
+const TICK_INTERVAL = 4;
+/**
+ * Generate a continuous stream of data at a sufficiently high frequency that a
+ * there"s a good chance of racing channel cancellation.
+ */
+function handleStream(evt, filename) {
+ // Create some payload to send.
+ const encoder = new TextEncoder();
+ let strChunk =
+ "Static routes are the future of ServiceWorkers! So say we all!\n";
+ while (strChunk.length < 1024) {
+ strChunk += strChunk;
+ }
+ const dataChunk = encoder.encode(strChunk);
+
+ evt.waitUntil(
+ new Promise(resolve => {
+ let body = new ReadableStream({
+ start: controller => {
+ const closeStream = why => {
+ console.log("closing stream: " + JSON.stringify(why) + "\n");
+ clearInterval(intervalId);
+ resolve();
+ // In event of error, the controller will automatically have closed.
+ if (why.why != "canceled") {
+ try {
+ controller.close();
+ } catch (ex) {
+ // If we thought we should cancel but experienced a problem,
+ // that's a different kind of failure and we need to report it.
+ // (If we didn't catch the exception here, we'd end up erroneously
+ // in the tick() method's canceled handler.)
+ channel.postMessage({
+ what: filename,
+ why: "close-failure",
+ message: ex.message,
+ ticks: why.ticks,
+ });
+ return;
+ }
+ }
+ // Post prior to performing any attempt to close...
+ channel.postMessage(why);
+ };
+
+ controller.enqueue(dataChunk);
+ let count = 0;
+ let intervalId;
+ function tick() {
+ try {
+ // bound worst-case behavior.
+ if (count++ > MAX_TICK_COUNT) {
+ closeStream({
+ what: filename,
+ why: "timeout",
+ message: "timeout",
+ ticks: count,
+ });
+ return;
+ }
+ controller.enqueue(dataChunk);
+ } catch (e) {
+ closeStream({
+ what: filename,
+ why: "canceled",
+ message: e.message,
+ ticks: count,
+ });
+ }
+ }
+ // Alternately, streams' pull mechanism could be used here, but this
+ // test doesn't so much want to saturate the stream as to make sure the
+ // data is at least flowing a little bit. (Also, the author had some
+ // concern about slowing down the test by overwhelming the event loop
+ // and concern that we might not have sufficent back-pressure plumbed
+ // through and an infinite pipe might make bad things happen.)
+ intervalId = setInterval(tick, TICK_INTERVAL);
+ tick();
+ },
+ });
+ evt.respondWith(
+ new Response(body, {
+ headers: {
+ "Content-Disposition": `attachment; filename="${filename}"`,
+ "Content-Type": "application/octet-stream",
+ },
+ })
+ );
+ })
+ );
+}
+
+/**
+ * Use an .sjs to generate a similar stream of data to the above, passing the
+ * response through directly. Because we're handing off the response but also
+ * want to be able to report when cancellation occurs, we create a second,
+ * overlapping long-poll style fetch that will not finish resolving until the
+ * .sjs experiences closure of its socket and terminates the payload stream.
+ */
+function handlePassThrough(evt, filename) {
+ evt.waitUntil(
+ (async () => {
+ console.log("issuing monitor fetch request");
+ const response = await fetch("server-stream-download.sjs?monitor");
+ console.log("monitor headers received, awaiting body");
+ const data = await response.json();
+ console.log("passthrough monitor fetch completed, notifying.");
+ channel.postMessage({
+ what: filename,
+ why: data.why,
+ message: data.message,
+ });
+ })()
+ );
+ evt.respondWith(
+ fetch("server-stream-download.sjs").then(response => {
+ console.log("server-stream-download.sjs Response received, propagating");
+ return response;
+ })
+ );
+}
+
+addEventListener("fetch", evt => {
+ console.log(`SW processing fetch of ${evt.request.url}`);
+ if (evt.request.url.includes("sw-stream-download")) {
+ handleStream(evt, "sw-stream-download");
+ return;
+ }
+ if (evt.request.url.includes("sw-passthrough-download")) {
+ handlePassThrough(evt, "sw-passthrough-download");
+ }
+});
+
+addEventListener("message", evt => {
+ if (evt.data === "claim") {
+ evt.waitUntil(clients.claim());
+ }
+});
diff --git a/dom/serviceworkers/test/empty.html b/dom/serviceworkers/test/empty.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/serviceworkers/test/empty.html
diff --git a/dom/serviceworkers/test/empty.js b/dom/serviceworkers/test/empty.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/serviceworkers/test/empty.js
diff --git a/dom/serviceworkers/test/empty_with_utils.html b/dom/serviceworkers/test/empty_with_utils.html
new file mode 100644
index 0000000000..75f0aa8872
--- /dev/null
+++ b/dom/serviceworkers/test/empty_with_utils.html
@@ -0,0 +1,13 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="utils.js" type="text/javascript"></script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/error_reporting_helpers.js b/dom/serviceworkers/test/error_reporting_helpers.js
new file mode 100644
index 0000000000..42ddbe42a2
--- /dev/null
+++ b/dom/serviceworkers/test/error_reporting_helpers.js
@@ -0,0 +1,73 @@
+"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];
+ if (args.length === 0) {
+ expectations.push({ errorMessage: localizer.GetStringFromName(msgId) });
+ } else {
+ expectations.push({
+ errorMessage: localizer.formatStringFromName(msgId, args),
+ });
+ }
+ }
+ 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/serviceworkers/test/eval_worker.js b/dom/serviceworkers/test/eval_worker.js
new file mode 100644
index 0000000000..b79db5c5be
--- /dev/null
+++ b/dom/serviceworkers/test/eval_worker.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line no-eval
+eval("1+1");
diff --git a/dom/serviceworkers/test/eventsource/eventsource.resource b/dom/serviceworkers/test/eventsource/eventsource.resource
new file mode 100644
index 0000000000..eb62cbd4c5
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/eventsource/eventsource.resource^headers^ b/dom/serviceworkers/test/eventsource/eventsource.resource^headers^
new file mode 100644
index 0000000000..5b88be7c32
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/eventsource/eventsource_cors_response.html b/dom/serviceworkers/test/eventsource/eventsource_cors_response.html
new file mode 100644
index 0000000000..115a0f5c65
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/eventsource/eventsource_cors_response_intercept_worker.js b/dom/serviceworkers/test/eventsource/eventsource_cors_response_intercept_worker.js
new file mode 100644
index 0000000000..c2e5d416e7
--- /dev/null
+++ b/dom/serviceworkers/test/eventsource/eventsource_cors_response_intercept_worker.js
@@ -0,0 +1,30 @@
+// Cross origin request
+var prefix = "http://example.com/tests/dom/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/eventsource/eventsource_mixed_content_cors_response.html b/dom/serviceworkers/test/eventsource/eventsource_mixed_content_cors_response.html
new file mode 100644
index 0000000000..970cae517f
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/eventsource/eventsource_mixed_content_cors_response_intercept_worker.js b/dom/serviceworkers/test/eventsource/eventsource_mixed_content_cors_response_intercept_worker.js
new file mode 100644
index 0000000000..9cb8d2d61f
--- /dev/null
+++ b/dom/serviceworkers/test/eventsource/eventsource_mixed_content_cors_response_intercept_worker.js
@@ -0,0 +1,29 @@
+var prefix = "http://example.com/tests/dom/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/eventsource/eventsource_opaque_response.html b/dom/serviceworkers/test/eventsource/eventsource_opaque_response.html
new file mode 100644
index 0000000000..bce12259cc
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/eventsource/eventsource_opaque_response_intercept_worker.js b/dom/serviceworkers/test/eventsource/eventsource_opaque_response_intercept_worker.js
new file mode 100644
index 0000000000..5c8c75a161
--- /dev/null
+++ b/dom/serviceworkers/test/eventsource/eventsource_opaque_response_intercept_worker.js
@@ -0,0 +1,30 @@
+// Cross origin request
+var prefix = "http://example.com/tests/dom/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/eventsource/eventsource_register_worker.html b/dom/serviceworkers/test/eventsource/eventsource_register_worker.html
new file mode 100644
index 0000000000..59e8e92ab6
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/eventsource/eventsource_synthetic_response.html b/dom/serviceworkers/test/eventsource/eventsource_synthetic_response.html
new file mode 100644
index 0000000000..7f6228c91e
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/eventsource/eventsource_synthetic_response_intercept_worker.js b/dom/serviceworkers/test/eventsource/eventsource_synthetic_response_intercept_worker.js
new file mode 100644
index 0000000000..72780e2979
--- /dev/null
+++ b/dom/serviceworkers/test/eventsource/eventsource_synthetic_response_intercept_worker.js
@@ -0,0 +1,27 @@
+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/serviceworkers/test/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,
+ mode: "cors",
+ };
+ var body = "data: data0\r\r";
+ var response = new Response(body, init);
+ event.respondWith(response);
+});
diff --git a/dom/serviceworkers/test/eventsource/eventsource_worker_helper.js b/dom/serviceworkers/test/eventsource/eventsource_worker_helper.js
new file mode 100644
index 0000000000..676d2a4bbe
--- /dev/null
+++ b/dom/serviceworkers/test/eventsource/eventsource_worker_helper.js
@@ -0,0 +1,17 @@
+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/serviceworkers/test/fetch.js b/dom/serviceworkers/test/fetch.js
new file mode 100644
index 0000000000..bf1bb4acb3
--- /dev/null
+++ b/dom/serviceworkers/test/fetch.js
@@ -0,0 +1,33 @@
+function get_query_params(url) {
+ var search = new URL(url).search;
+ if (!search) {
+ return {};
+ }
+ var ret = {};
+ var params = search.substring(1).split("&");
+ params.forEach(function (param) {
+ var element = param.split("=");
+ ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]);
+ });
+ return ret;
+}
+
+addEventListener("fetch", function (event) {
+ if (event.request.url.includes("fail.html")) {
+ event.respondWith(fetch("hello.html", { integrity: "abc" }));
+ } else if (event.request.url.includes("fake.html")) {
+ event.respondWith(fetch("hello.html"));
+ } else if (event.request.url.includes("file_js_cache")) {
+ event.respondWith(fetch(event.request));
+ } else if (event.request.url.includes("redirect")) {
+ let param = get_query_params(event.request.url);
+ let url = param.url;
+ let mode = param.mode;
+
+ event.respondWith(fetch(url, { mode }));
+ }
+});
+
+addEventListener("activate", function (event) {
+ event.waitUntil(clients.claim());
+});
diff --git a/dom/serviceworkers/test/fetch/cookie/cookie_test.js b/dom/serviceworkers/test/fetch/cookie/cookie_test.js
new file mode 100644
index 0000000000..4102b4b341
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/cookie/cookie_test.js
@@ -0,0 +1,11 @@
+self.addEventListener("fetch", function (event) {
+ if (event.request.url.includes("synth.html")) {
+ var body =
+ "<script>" +
+ 'window.parent.postMessage({status: "done", cookie: document.cookie}, "*");' +
+ "</script>";
+ event.respondWith(
+ new Response(body, { headers: { "Content-Type": "text/html" } })
+ );
+ }
+});
diff --git a/dom/serviceworkers/test/fetch/cookie/register.html b/dom/serviceworkers/test/fetch/cookie/register.html
new file mode 100644
index 0000000000..99eabaf0a2
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/cookie/register.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script src="../../utils.js"></script>
+<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"}, "*");
+ }
+
+ document.cookie = "foo=bar";
+
+ navigator.serviceWorker.register("cookie_test.js", {scope: "."})
+ .then(reg => {
+ return waitForState(reg.installing, "activated", reg);
+ }).then(done);
+</script>
diff --git a/dom/serviceworkers/test/fetch/cookie/unregister.html b/dom/serviceworkers/test/fetch/cookie/unregister.html
new file mode 100644
index 0000000000..1f13508fa7
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/cookie/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/serviceworkers/test/fetch/deliver-gzip.sjs b/dom/serviceworkers/test/fetch/deliver-gzip.sjs
new file mode 100644
index 0000000000..2faa09532d
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/deliver-gzip.sjs
@@ -0,0 +1,21 @@
+"use strict";
+
+function handleRequest(request, response) {
+ // The string "hello" repeated 10 times followed by newline. Compressed using gzip.
+ // prettier-ignore
+ let 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);
+
+ let bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
+ Ci.nsIBinaryOutputStream
+ );
+ bos.setOutputStream(response.bodyOutputStream);
+
+ bos.writeByteArray(bytes);
+}
diff --git a/dom/serviceworkers/test/fetch/fetch_tests.js b/dom/serviceworkers/test/fetch/fetch_tests.js
new file mode 100644
index 0000000000..69b8a89679
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/fetch_tests.js
@@ -0,0 +1,716 @@
+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(JSON.stringify(hops))
+ );
+}
+
+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();
+});
+
+fetchXHR("readable-stream.txt", function (xhr) {
+ my_ok(xhr.status == 200, "loading completed");
+ my_ok(xhr.responseText == "Hello!", "The message is correct!");
+ finish();
+});
+
+fetchXHR(
+ "readable-stream-locked.txt",
+ function (xhr) {
+ my_ok(false, "This should not be called!");
+ finish();
+ },
+ function () {
+ my_ok(true, "The exception has been correctly handled!");
+ finish();
+ }
+);
+
+fetchXHR(
+ "readable-stream-with-exception.txt",
+ function (xhr) {
+ my_ok(false, "This should not be called!");
+ finish();
+ },
+ function () {
+ my_ok(true, "The exception has been correctly handled!");
+ finish();
+ }
+);
+
+fetchXHR(
+ "readable-stream-with-exception2.txt",
+ function (xhr) {
+ my_ok(false, "This should not be called!");
+ finish();
+ },
+ function () {
+ my_ok(true, "The exception has been correctly handled!");
+ finish();
+ }
+);
+
+fetchXHR(
+ "readable-stream-already-consumed.txt",
+ function (xhr) {
+ my_ok(false, "This should not be called!");
+ finish();
+ },
+ function () {
+ my_ok(true, "The exception has been correctly handled!");
+ finish();
+ }
+);
+
+var expectedUncompressedResponse = "";
+for (let 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 == "cors",
+ "synthesize CORS response should result in outer CORS response"
+ );
+ finish();
+ },
+ function (e) {
+ my_ok(false, "cors-for-no-cors request should not reject");
+ finish();
+ }
+);
+
+function arrayBufferFromString(str) {
+ var arr = new Uint8Array(str.length);
+ for (let 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");
+ if (method === "HEAD") {
+ my_ok(
+ xhr.responseText == "",
+ method + "load should not have synthesized response"
+ );
+ } else {
+ 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/serviceworkers/test/fetch/fetch_worker_script.js b/dom/serviceworkers/test/fetch/fetch_worker_script.js
new file mode 100644
index 0000000000..6eb0b18a77
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/fetch_worker_script.js
@@ -0,0 +1,28 @@
+function my_ok(v, msg) {
+ postMessage({ type: "ok", value: v, 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/serviceworkers/test/fetch/hsts/embedder.html b/dom/serviceworkers/test/fetch/hsts/embedder.html
new file mode 100644
index 0000000000..ad44809042
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/hsts/index.html"></iframe>
diff --git a/dom/serviceworkers/test/fetch/hsts/hsts_test.js b/dom/serviceworkers/test/fetch/hsts/hsts_test.js
new file mode 100644
index 0000000000..74b9ed23ba
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/hsts/hsts_test.js
@@ -0,0 +1,11 @@
+self.addEventListener("fetch", function (event) {
+ if (event.request.url.includes("index.html")) {
+ event.respondWith(fetch("realindex.html"));
+ } else if (event.request.url.includes("image-20px.png")) {
+ if (event.request.url.indexOf("https://") == 0) {
+ event.respondWith(fetch("image-40px.png"));
+ } else {
+ event.respondWith(Response.error());
+ }
+ }
+});
diff --git a/dom/serviceworkers/test/fetch/hsts/image-20px.png b/dom/serviceworkers/test/fetch/hsts/image-20px.png
new file mode 100644
index 0000000000..ae6a8a6b88
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/hsts/image-20px.png
Binary files differ
diff --git a/dom/serviceworkers/test/fetch/hsts/image-40px.png b/dom/serviceworkers/test/fetch/hsts/image-40px.png
new file mode 100644
index 0000000000..fe391dc8a2
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/hsts/image-40px.png
Binary files differ
diff --git a/dom/serviceworkers/test/fetch/hsts/image.html b/dom/serviceworkers/test/fetch/hsts/image.html
new file mode 100644
index 0000000000..7036ea954e
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/hsts/image.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script>
+onload=function(){
+ var img = new Image();
+ img.src = "http://example.com/tests/dom/serviceworkers/test/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/serviceworkers/test/fetch/hsts/realindex.html b/dom/serviceworkers/test/fetch/hsts/realindex.html
new file mode 100644
index 0000000000..e7d282fe83
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/hsts/realindex.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script>
+ var securityInfoPresent = !!SpecialPowers.wrap(window).docShell.currentDocumentChannel.securityInfo;
+ window.parent.postMessage({status: "protocol",
+ data: location.protocol,
+ securityInfoPresent},
+ "*");
+</script>
diff --git a/dom/serviceworkers/test/fetch/hsts/register.html b/dom/serviceworkers/test/fetch/hsts/register.html
new file mode 100644
index 0000000000..bcdc146aec
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/hsts/register.html^headers^ b/dom/serviceworkers/test/fetch/hsts/register.html^headers^
new file mode 100644
index 0000000000..a46bf65bd9
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/hsts/register.html^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-cache
+Strict-Transport-Security: max-age=60
diff --git a/dom/serviceworkers/test/fetch/hsts/unregister.html b/dom/serviceworkers/test/fetch/hsts/unregister.html
new file mode 100644
index 0000000000..1f13508fa7
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/https/clonedresponse/https_test.js b/dom/serviceworkers/test/fetch/https/clonedresponse/https_test.js
new file mode 100644
index 0000000000..8ab34123af
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/https/clonedresponse/https_test.js
@@ -0,0 +1,19 @@
+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.includes("index.html")) {
+ event.respondWith(
+ new Promise(function (resolve, reject) {
+ caches.match(event.request).then(function (response) {
+ resolve(response.clone());
+ });
+ })
+ );
+ }
+});
diff --git a/dom/serviceworkers/test/fetch/https/clonedresponse/index.html b/dom/serviceworkers/test/fetch/https/clonedresponse/index.html
new file mode 100644
index 0000000000..a435548443
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/https/clonedresponse/index.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+ window.parent.postMessage({status: "done"}, "*");
+</script>
diff --git a/dom/serviceworkers/test/fetch/https/clonedresponse/register.html b/dom/serviceworkers/test/fetch/https/clonedresponse/register.html
new file mode 100644
index 0000000000..41774f70d1
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/https/clonedresponse/unregister.html b/dom/serviceworkers/test/fetch/https/clonedresponse/unregister.html
new file mode 100644
index 0000000000..1f13508fa7
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/https/https_test.js b/dom/serviceworkers/test/fetch/https/https_test.js
new file mode 100644
index 0000000000..5f20690bb5
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/https/https_test.js
@@ -0,0 +1,31 @@
+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.includes("index.html")) {
+ event.respondWith(caches.match(event.request));
+ } else if (event.request.url.includes("synth-sw.html")) {
+ event.respondWith(caches.match(event.request));
+ } else if (event.request.url.includes("synth-window.html")) {
+ event.respondWith(caches.match(event.request));
+ } else if (event.request.url.includes("synth.html")) {
+ event.respondWith(
+ new Response(
+ '<!DOCTYPE html><script>window.parent.postMessage({status: "done-synth"}, "*");</script>',
+ { headers: { "Content-Type": "text/html" } }
+ )
+ );
+ }
+});
diff --git a/dom/serviceworkers/test/fetch/https/index.html b/dom/serviceworkers/test/fetch/https/index.html
new file mode 100644
index 0000000000..a435548443
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/https/index.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+ window.parent.postMessage({status: "done"}, "*");
+</script>
diff --git a/dom/serviceworkers/test/fetch/https/register.html b/dom/serviceworkers/test/fetch/https/register.html
new file mode 100644
index 0000000000..fa666fe957
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/https/unregister.html b/dom/serviceworkers/test/fetch/https/unregister.html
new file mode 100644
index 0000000000..1f13508fa7
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/imagecache-maxage/image-20px.png b/dom/serviceworkers/test/fetch/imagecache-maxage/image-20px.png
new file mode 100644
index 0000000000..ae6a8a6b88
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/imagecache-maxage/image-20px.png
Binary files differ
diff --git a/dom/serviceworkers/test/fetch/imagecache-maxage/image-40px.png b/dom/serviceworkers/test/fetch/imagecache-maxage/image-40px.png
new file mode 100644
index 0000000000..fe391dc8a2
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/imagecache-maxage/image-40px.png
Binary files differ
diff --git a/dom/serviceworkers/test/fetch/imagecache-maxage/index.html b/dom/serviceworkers/test/fetch/imagecache-maxage/index.html
new file mode 100644
index 0000000000..0d4c52eedd
--- /dev/null
+++ b/dom/serviceworkers/test/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,
+ width2,
+ url,
+ 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/serviceworkers/test/fetch/imagecache-maxage/maxage_test.js b/dom/serviceworkers/test/fetch/imagecache-maxage/maxage_test.js
new file mode 100644
index 0000000000..c664e07c28
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/imagecache-maxage/maxage_test.js
@@ -0,0 +1,45 @@
+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, 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,
+ });
+ });
+}
+
+self.addEventListener("fetch", function (event) {
+ if (event.request.url.includes("image.png")) {
+ event.respondWith(synthesizeImage(""));
+ } else if (event.request.url.includes("image2.png")) {
+ event.respondWith(synthesizeImage("2"));
+ }
+});
diff --git a/dom/serviceworkers/test/fetch/imagecache-maxage/register.html b/dom/serviceworkers/test/fetch/imagecache-maxage/register.html
new file mode 100644
index 0000000000..af4dde2e29
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/imagecache-maxage/unregister.html b/dom/serviceworkers/test/fetch/imagecache-maxage/unregister.html
new file mode 100644
index 0000000000..1f13508fa7
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/imagecache/image-20px.png b/dom/serviceworkers/test/fetch/imagecache/image-20px.png
new file mode 100644
index 0000000000..ae6a8a6b88
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/imagecache/image-20px.png
Binary files differ
diff --git a/dom/serviceworkers/test/fetch/imagecache/image-40px.png b/dom/serviceworkers/test/fetch/imagecache/image-40px.png
new file mode 100644
index 0000000000..fe391dc8a2
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/imagecache/image-40px.png
Binary files differ
diff --git a/dom/serviceworkers/test/fetch/imagecache/imagecache_test.js b/dom/serviceworkers/test/fetch/imagecache/imagecache_test.js
new file mode 100644
index 0000000000..cd8f522728
--- /dev/null
+++ b/dom/serviceworkers/test/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.includes("image-20px.png")) {
+ event.respondWith(synthesizeImage());
+ }
+});
diff --git a/dom/serviceworkers/test/fetch/imagecache/index.html b/dom/serviceworkers/test/fetch/imagecache/index.html
new file mode 100644
index 0000000000..f634f68bb7
--- /dev/null
+++ b/dom/serviceworkers/test/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,
+ 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/serviceworkers/test/fetch/imagecache/postmortem.html b/dom/serviceworkers/test/fetch/imagecache/postmortem.html
new file mode 100644
index 0000000000..53356cd02c
--- /dev/null
+++ b/dom/serviceworkers/test/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}, "*");
+};
+</script>
+<img src="image-20px.png">
diff --git a/dom/serviceworkers/test/fetch/imagecache/register.html b/dom/serviceworkers/test/fetch/imagecache/register.html
new file mode 100644
index 0000000000..f6d1eb382f
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/imagecache/unregister.html b/dom/serviceworkers/test/fetch/imagecache/unregister.html
new file mode 100644
index 0000000000..1f13508fa7
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/importscript-mixedcontent/https_test.js b/dom/serviceworkers/test/fetch/importscript-mixedcontent/https_test.js
new file mode 100644
index 0000000000..138ca768aa
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/importscript-mixedcontent/https_test.js
@@ -0,0 +1,31 @@
+function sendResponseToParent(response) {
+ return `
+ <!DOCTYPE html>
+ <script>
+ window.parent.postMessage({status: "done", data: "${response}"}, "*");
+ </script>
+ `;
+}
+
+self.addEventListener("fetch", function (event) {
+ if (event.request.url.includes("index.html")) {
+ 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/serviceworkers/test/fetch/importscript-mixedcontent/register.html b/dom/serviceworkers/test/fetch/importscript-mixedcontent/register.html
new file mode 100644
index 0000000000..41774f70d1
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/importscript-mixedcontent/unregister.html b/dom/serviceworkers/test/fetch/importscript-mixedcontent/unregister.html
new file mode 100644
index 0000000000..1f13508fa7
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/index.html b/dom/serviceworkers/test/fetch/index.html
new file mode 100644
index 0000000000..693810c6fc
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/index.html
@@ -0,0 +1,191 @@
+<!--
+ 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 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, 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 xhr = new XMLHttpRequest();
+ xhr.addEventListener("load", function(evt) {
+ my_ok(evt.target.responseXML === null, "Load synthetic cross origin XML Document should be allowed");
+ finish();
+ });
+ xhr.responseType = "document";
+ xhr.open("GET", "load_cross_origin_xml_document_synthetic.xml");
+ xhr.send();
+
+ gExpected++;
+ fetch(
+ "load_cross_origin_xml_document_cors.xml",
+ {mode: "same-origin"}
+ ).then(function(response) {
+ // issue: https://github.com/whatwg/fetch/issues/629
+ my_ok(false, "Load CORS cross origin XML Document should not be allowed");
+ finish();
+ }, function(error) {
+ my_ok(true, "Load CORS cross origin XML Document should not be allowed");
+ finish();
+ });
+
+ gExpected++;
+ fetch(
+ "load_cross_origin_xml_document_opaque.xml",
+ {mode: "same-origin"}
+ ).then(function(response) {
+ my_ok(false, "Load opaque cross origin XML Document should not be allowed");
+ finish();
+ }, function(error) {
+ my_ok(true, "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/serviceworkers/test/fetch/interrupt.sjs b/dom/serviceworkers/test/fetch/interrupt.sjs
new file mode 100644
index 0000000000..a7aaa79a99
--- /dev/null
+++ b/dom/serviceworkers/test/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.Exception("", Cr.NS_BINDING_ABORTED);
+}
diff --git a/dom/serviceworkers/test/fetch/origin/https/index-https.sjs b/dom/serviceworkers/test/fetch/origin/https/index-https.sjs
new file mode 100644
index 0000000000..5250467ec7
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/origin/https/index-https.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response) {
+ response.setStatusLine(null, 308, "Permanent Redirect");
+ response.setHeader(
+ "Location",
+ "https://example.org/tests/dom/serviceworkers/test/fetch/origin/https/realindex.html",
+ false
+ );
+}
diff --git a/dom/serviceworkers/test/fetch/origin/https/origin_test.js b/dom/serviceworkers/test/fetch/origin/https/origin_test.js
new file mode 100644
index 0000000000..d148de2d83
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/origin/https/origin_test.js
@@ -0,0 +1,29 @@
+var prefix = "/tests/dom/serviceworkers/test/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.includes("index-cached-https.sjs")) {
+ 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/serviceworkers/test/fetch/origin/https/realindex.html b/dom/serviceworkers/test/fetch/origin/https/realindex.html
new file mode 100644
index 0000000000..87f3489455
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/origin/https/realindex.html^headers^ b/dom/serviceworkers/test/fetch/origin/https/realindex.html^headers^
new file mode 100644
index 0000000000..5ed82fd065
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/origin/https/realindex.html^headers^
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: https://example.com
diff --git a/dom/serviceworkers/test/fetch/origin/https/register.html b/dom/serviceworkers/test/fetch/origin/https/register.html
new file mode 100644
index 0000000000..2e99adba53
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/origin/https/unregister.html b/dom/serviceworkers/test/fetch/origin/https/unregister.html
new file mode 100644
index 0000000000..1f13508fa7
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/origin/index-to-https.sjs b/dom/serviceworkers/test/fetch/origin/index-to-https.sjs
new file mode 100644
index 0000000000..2505c03686
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/origin/index-to-https.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response) {
+ response.setStatusLine(null, 308, "Permanent Redirect");
+ response.setHeader(
+ "Location",
+ "https://example.org/tests/dom/serviceworkers/test/fetch/origin/realindex.html",
+ false
+ );
+}
diff --git a/dom/serviceworkers/test/fetch/origin/index.sjs b/dom/serviceworkers/test/fetch/origin/index.sjs
new file mode 100644
index 0000000000..ca11827792
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/origin/index.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response) {
+ response.setStatusLine(null, 308, "Permanent Redirect");
+ response.setHeader(
+ "Location",
+ "http://example.org/tests/dom/serviceworkers/test/fetch/origin/realindex.html",
+ false
+ );
+}
diff --git a/dom/serviceworkers/test/fetch/origin/origin_test.js b/dom/serviceworkers/test/fetch/origin/origin_test.js
new file mode 100644
index 0000000000..d57f10cc2a
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/origin/origin_test.js
@@ -0,0 +1,38 @@
+var prefix = "/tests/dom/serviceworkers/test/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.includes("index-cached.sjs")) {
+ event.respondWith(
+ self.caches.open("origin-cache").then(c => {
+ return c.match(prefix + "index.sjs");
+ })
+ );
+ } else if (event.request.url.includes("index-to-https-cached.sjs")) {
+ 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/serviceworkers/test/fetch/origin/realindex.html b/dom/serviceworkers/test/fetch/origin/realindex.html
new file mode 100644
index 0000000000..87f3489455
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/origin/realindex.html^headers^ b/dom/serviceworkers/test/fetch/origin/realindex.html^headers^
new file mode 100644
index 0000000000..3a6a85d894
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/origin/realindex.html^headers^
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: http://mochi.test:8888
diff --git a/dom/serviceworkers/test/fetch/origin/register.html b/dom/serviceworkers/test/fetch/origin/register.html
new file mode 100644
index 0000000000..2e99adba53
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/origin/unregister.html b/dom/serviceworkers/test/fetch/origin/unregister.html
new file mode 100644
index 0000000000..1f13508fa7
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/plugin/plugins.html b/dom/serviceworkers/test/fetch/plugin/plugins.html
new file mode 100644
index 0000000000..b268f6d99e
--- /dev/null
+++ b/dom/serviceworkers/test/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.destination === "object") {
+ ok(false, "<object> should not be intercepted");
+ } else if (e.data.destination === "embed") {
+ ok(false, "<embed> should not be intercepted");
+ } else if (e.data.destination === "" && e.data.resource === "foo.txt") {
+ navigator.serviceWorker.removeEventListener("message", onMessage);
+ finish();
+ }
+ });
+
+ 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/serviceworkers/test/fetch/plugin/worker.js b/dom/serviceworkers/test/fetch/plugin/worker.js
new file mode 100644
index 0000000000..9849c9e0d5
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/plugin/worker.js
@@ -0,0 +1,15 @@
+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({
+ destination: event.request.destination,
+ resource,
+ });
+ }
+ });
+ })
+ );
+});
diff --git a/dom/serviceworkers/test/fetch/real-file.txt b/dom/serviceworkers/test/fetch/real-file.txt
new file mode 100644
index 0000000000..3ca2088ec0
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/real-file.txt
@@ -0,0 +1 @@
+This is a real file.
diff --git a/dom/serviceworkers/test/fetch/redirect.sjs b/dom/serviceworkers/test/fetch/redirect.sjs
new file mode 100644
index 0000000000..dab558f4a8
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/requesturl/index.html b/dom/serviceworkers/test/fetch/requesturl/index.html
new file mode 100644
index 0000000000..bc3e400a94
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/requesturl/redirect.sjs b/dom/serviceworkers/test/fetch/requesturl/redirect.sjs
new file mode 100644
index 0000000000..ae50a78aef
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/requesturl/redirect.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response) {
+ response.setStatusLine(null, 308, "Permanent Redirect");
+ response.setHeader(
+ "Location",
+ "http://example.org/tests/dom/serviceworkers/test/fetch/requesturl/secret.html",
+ false
+ );
+}
diff --git a/dom/serviceworkers/test/fetch/requesturl/redirector.html b/dom/serviceworkers/test/fetch/requesturl/redirector.html
new file mode 100644
index 0000000000..0a3afab9ee
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/requesturl/redirector.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<meta http-equiv="refresh" content="3;URL=/tests/dom/serviceworkers/test/fetch/requesturl/redirect.sjs">
diff --git a/dom/serviceworkers/test/fetch/requesturl/register.html b/dom/serviceworkers/test/fetch/requesturl/register.html
new file mode 100644
index 0000000000..19a2e022c2
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/requesturl/requesturl_test.js b/dom/serviceworkers/test/fetch/requesturl/requesturl_test.js
new file mode 100644
index 0000000000..4d2680538f
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/requesturl/requesturl_test.js
@@ -0,0 +1,21 @@
+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/serviceworkers/test/fetch/requesturl/secret.html b/dom/serviceworkers/test/fetch/requesturl/secret.html
new file mode 100644
index 0000000000..694c336355
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/requesturl/secret.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+secret stuff
+<script>
+ window.parent.postMessage({status: "done"}, "*");
+</script>
diff --git a/dom/serviceworkers/test/fetch/requesturl/unregister.html b/dom/serviceworkers/test/fetch/requesturl/unregister.html
new file mode 100644
index 0000000000..1f13508fa7
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/sandbox/index.html b/dom/serviceworkers/test/fetch/sandbox/index.html
new file mode 100644
index 0000000000..1094a3995d
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/sandbox/intercepted_index.html b/dom/serviceworkers/test/fetch/sandbox/intercepted_index.html
new file mode 100644
index 0000000000..87261a495f
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/sandbox/register.html b/dom/serviceworkers/test/fetch/sandbox/register.html
new file mode 100644
index 0000000000..427b1a8da9
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/sandbox/sandbox_test.js b/dom/serviceworkers/test/fetch/sandbox/sandbox_test.js
new file mode 100644
index 0000000000..310cea0d16
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/sandbox/sandbox_test.js
@@ -0,0 +1,5 @@
+self.addEventListener("fetch", function (event) {
+ if (event.request.url.includes("index.html")) {
+ event.respondWith(fetch("intercepted_index.html"));
+ }
+});
diff --git a/dom/serviceworkers/test/fetch/sandbox/unregister.html b/dom/serviceworkers/test/fetch/sandbox/unregister.html
new file mode 100644
index 0000000000..1f13508fa7
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/upgrade-insecure/embedder.html b/dom/serviceworkers/test/fetch/upgrade-insecure/embedder.html
new file mode 100644
index 0000000000..e99209aa4d
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/upgrade-insecure/index.html"></iframe>
diff --git a/dom/serviceworkers/test/fetch/upgrade-insecure/embedder.html^headers^ b/dom/serviceworkers/test/fetch/upgrade-insecure/embedder.html^headers^
new file mode 100644
index 0000000000..602d9dc38d
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/upgrade-insecure/embedder.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: upgrade-insecure-requests
diff --git a/dom/serviceworkers/test/fetch/upgrade-insecure/image-20px.png b/dom/serviceworkers/test/fetch/upgrade-insecure/image-20px.png
new file mode 100644
index 0000000000..ae6a8a6b88
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/upgrade-insecure/image-20px.png
Binary files differ
diff --git a/dom/serviceworkers/test/fetch/upgrade-insecure/image-40px.png b/dom/serviceworkers/test/fetch/upgrade-insecure/image-40px.png
new file mode 100644
index 0000000000..fe391dc8a2
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/upgrade-insecure/image-40px.png
Binary files differ
diff --git a/dom/serviceworkers/test/fetch/upgrade-insecure/image.html b/dom/serviceworkers/test/fetch/upgrade-insecure/image.html
new file mode 100644
index 0000000000..dfcfd80014
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/fetch/upgrade-insecure/realindex.html b/dom/serviceworkers/test/fetch/upgrade-insecure/realindex.html
new file mode 100644
index 0000000000..aaa255aad3
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/upgrade-insecure/register.html b/dom/serviceworkers/test/fetch/upgrade-insecure/register.html
new file mode 100644
index 0000000000..6309b9b218
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/upgrade-insecure/unregister.html b/dom/serviceworkers/test/fetch/upgrade-insecure/unregister.html
new file mode 100644
index 0000000000..1f13508fa7
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/fetch/upgrade-insecure/upgrade-insecure_test.js b/dom/serviceworkers/test/fetch/upgrade-insecure/upgrade-insecure_test.js
new file mode 100644
index 0000000000..74b9ed23ba
--- /dev/null
+++ b/dom/serviceworkers/test/fetch/upgrade-insecure/upgrade-insecure_test.js
@@ -0,0 +1,11 @@
+self.addEventListener("fetch", function (event) {
+ if (event.request.url.includes("index.html")) {
+ event.respondWith(fetch("realindex.html"));
+ } else if (event.request.url.includes("image-20px.png")) {
+ if (event.request.url.indexOf("https://") == 0) {
+ event.respondWith(fetch("image-40px.png"));
+ } else {
+ event.respondWith(Response.error());
+ }
+ }
+});
diff --git a/dom/serviceworkers/test/fetch_event_worker.js b/dom/serviceworkers/test/fetch_event_worker.js
new file mode 100644
index 0000000000..6b8c37f802
--- /dev/null
+++ b/dom/serviceworkers/test/fetch_event_worker.js
@@ -0,0 +1,365 @@
+// eslint-disable-next-line complexity
+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("readable-stream.txt")) {
+ ev.respondWith(
+ new Response(
+ new ReadableStream({
+ start(controller) {
+ controller.enqueue(
+ new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x21])
+ );
+ controller.close();
+ },
+ })
+ )
+ );
+ } else if (ev.request.url.includes("readable-stream-locked.txt")) {
+ let stream = new ReadableStream({
+ start(controller) {
+ controller.enqueue(
+ new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x21])
+ );
+ controller.close();
+ },
+ });
+
+ ev.respondWith(new Response(stream));
+
+ // This locks the stream.
+ stream.getReader();
+ } else if (ev.request.url.includes("readable-stream-with-exception.txt")) {
+ ev.respondWith(
+ new Response(
+ new ReadableStream({
+ start(controller) {},
+ pull() {
+ throw "EXCEPTION!";
+ },
+ })
+ )
+ );
+ } else if (ev.request.url.includes("readable-stream-with-exception2.txt")) {
+ ev.respondWith(
+ new Response(
+ new ReadableStream({
+ _controller: null,
+ _count: 0,
+
+ start(controller) {
+ this._controller = controller;
+ },
+ pull() {
+ if (++this._count == 5) {
+ throw "EXCEPTION 2!";
+ }
+ this._controller.enqueue(new Uint8Array([this._count]));
+ },
+ })
+ )
+ );
+ } else if (ev.request.url.includes("readable-stream-already-consumed.txt")) {
+ let r = new Response(
+ new ReadableStream({
+ start(controller) {
+ controller.enqueue(
+ new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x21])
+ );
+ controller.close();
+ },
+ })
+ );
+
+ r.blob();
+
+ ev.respondWith(r);
+ } 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 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 }),
+ new Request(ev.request, { method: "GET" }),
+ ];
+ 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')", {
+ headers: { "Content-Type": "text/javascript" },
+ })
+ )
+ );
+ } else if (ev.request.url.includes("nonexistent_imported_script.js")) {
+ ev.respondWith(
+ Promise.resolve(
+ new Response("check_intercepted_script();", {
+ headers: { "Content-Type": "text/javascript" },
+ })
+ )
+ );
+ } 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.
+ // eslint-disable-next-line no-useless-return
+ 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("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/serviceworkers/test/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")
+ ) {
+ 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/serviceworkers/test/file_blob_response_worker.js b/dom/serviceworkers/test/file_blob_response_worker.js
new file mode 100644
index 0000000000..e9d5366c42
--- /dev/null
+++ b/dom/serviceworkers/test/file_blob_response_worker.js
@@ -0,0 +1,39 @@
+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, index: 5 };
+
+ objectStore = db.transaction("test", "readwrite").objectStore("test");
+ objectStore.add(data).onsuccess = function (event) {
+ var key = event.target.result;
+ objectStore = db.transaction("test").objectStore("test");
+ objectStore.get(key).onsuccess = function (event1) {
+ resolve(event1.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/serviceworkers/test/file_js_cache.html b/dom/serviceworkers/test/file_js_cache.html
new file mode 100644
index 0000000000..6feb94d872
--- /dev/null
+++ b/dom/serviceworkers/test/file_js_cache.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Add a tag script to save the bytecode</title>
+</head>
+<body>
+ <script id="watchme" src="file_js_cache.js"></script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/file_js_cache.js b/dom/serviceworkers/test/file_js_cache.js
new file mode 100644
index 0000000000..b9b966775c
--- /dev/null
+++ b/dom/serviceworkers/test/file_js_cache.js
@@ -0,0 +1,5 @@
+function baz() {}
+function bar() {}
+function foo() { bar() }
+foo();
+
diff --git a/dom/serviceworkers/test/file_js_cache_cleanup.js b/dom/serviceworkers/test/file_js_cache_cleanup.js
new file mode 100644
index 0000000000..c6853faaf2
--- /dev/null
+++ b/dom/serviceworkers/test/file_js_cache_cleanup.js
@@ -0,0 +1,16 @@
+"use strict";
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+function clearCache() {
+ const cacheStorageSrv = Cc[
+ "@mozilla.org/netwerk/cache-storage-service;1"
+ ].getService(Ci.nsICacheStorageService);
+ cacheStorageSrv.clear();
+}
+
+addMessageListener("teardown", function () {
+ clearCache();
+ sendAsyncMessage("teardown-complete");
+});
diff --git a/dom/serviceworkers/test/file_js_cache_save_after_load.html b/dom/serviceworkers/test/file_js_cache_save_after_load.html
new file mode 100644
index 0000000000..8a696c0026
--- /dev/null
+++ b/dom/serviceworkers/test/file_js_cache_save_after_load.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Save the bytecode when all scripts are executed</title>
+</head>
+<body>
+ <script id="watchme" src="file_js_cache_save_after_load.js"></script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/file_js_cache_save_after_load.js b/dom/serviceworkers/test/file_js_cache_save_after_load.js
new file mode 100644
index 0000000000..7f5a20b524
--- /dev/null
+++ b/dom/serviceworkers/test/file_js_cache_save_after_load.js
@@ -0,0 +1,15 @@
+function send_ping() {
+ window.dispatchEvent(new Event("ping"));
+}
+send_ping(); // ping (=1)
+
+window.addEventListener("load", function () {
+ send_ping(); // ping (=2)
+
+ // Append a script which should call |foo|, before the encoding of this script
+ // bytecode.
+ var script = document.createElement("script");
+ script.type = "text/javascript";
+ script.innerText = "send_ping();"; // ping (=3)
+ document.head.appendChild(script);
+});
diff --git a/dom/serviceworkers/test/file_js_cache_syntax_error.html b/dom/serviceworkers/test/file_js_cache_syntax_error.html
new file mode 100644
index 0000000000..cc4a9b2daa
--- /dev/null
+++ b/dom/serviceworkers/test/file_js_cache_syntax_error.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Do not save bytecode on compilation errors</title>
+</head>
+<body>
+ <script id="watchme" src="file_js_cache_syntax_error.js"></script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/file_js_cache_syntax_error.js b/dom/serviceworkers/test/file_js_cache_syntax_error.js
new file mode 100644
index 0000000000..fcf587ae70
--- /dev/null
+++ b/dom/serviceworkers/test/file_js_cache_syntax_error.js
@@ -0,0 +1 @@
+var // SyntaxError: missing variable name.
diff --git a/dom/serviceworkers/test/file_js_cache_with_sri.html b/dom/serviceworkers/test/file_js_cache_with_sri.html
new file mode 100644
index 0000000000..38ecb26984
--- /dev/null
+++ b/dom/serviceworkers/test/file_js_cache_with_sri.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Add a tag script to save the bytecode</title>
+</head>
+<body>
+ <script id="watchme" src="file_js_cache.js"
+ integrity="sha384-8YSwN2ywq1SVThihWhj7uTVZ4UeIDwo3GgdPYnug+C+OS0oa6kH2IXBclwMaDJFb">
+ </script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/file_notification_openWindow.html b/dom/serviceworkers/test/file_notification_openWindow.html
new file mode 100644
index 0000000000..f220f4808d
--- /dev/null
+++ b/dom/serviceworkers/test/file_notification_openWindow.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1578070</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 = () => {
+ navigator.serviceWorker.ready.then(() => {
+ // Open and close a new window.
+ window.open("https://example.org/").close();
+
+ // If we make it here, then we didn't crash. Tell the worker we're done.
+ navigator.serviceWorker.controller.postMessage("DONE");
+
+ // We're done!
+ window.close();
+ });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/file_userContextId_openWindow.js b/dom/serviceworkers/test/file_userContextId_openWindow.js
new file mode 100644
index 0000000000..649a3152ba
--- /dev/null
+++ b/dom/serviceworkers/test/file_userContextId_openWindow.js
@@ -0,0 +1,3 @@
+onnotificationclick = event => {
+ clients.openWindow("empty.html");
+};
diff --git a/dom/serviceworkers/test/force_refresh_browser_worker.js b/dom/serviceworkers/test/force_refresh_browser_worker.js
new file mode 100644
index 0000000000..58256468bd
--- /dev/null
+++ b/dom/serviceworkers/test/force_refresh_browser_worker.js
@@ -0,0 +1,42 @@
+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/serviceworkers/test/force_refresh_worker.js b/dom/serviceworkers/test/force_refresh_worker.js
new file mode 100644
index 0000000000..8c8382493a
--- /dev/null
+++ b/dom/serviceworkers/test/force_refresh_worker.js
@@ -0,0 +1,43 @@
+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/serviceworkers/test/gtest/TestReadWrite.cpp b/dom/serviceworkers/test/gtest/TestReadWrite.cpp
new file mode 100644
index 0000000000..823647d22e
--- /dev/null
+++ b/dom/serviceworkers/test/gtest/TestReadWrite.cpp
@@ -0,0 +1,955 @@
+/* -*- 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"
+#include "nsIServiceWorkerManager.h"
+
+#include "prtime.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+class ServiceWorkerRegistrarTest : public ServiceWorkerRegistrar {
+ public:
+ ServiceWorkerRegistrarTest() {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ 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() MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ return WriteData(mData);
+ }
+ void TestDeleteData() { DeleteData(); }
+
+ void TestRegisterServiceWorker(const ServiceWorkerRegistrationData& aData) {
+ mozilla::MonitorAutoLock lock(mMonitor);
+ RegisterServiceWorkerInternal(aData);
+ }
+
+ nsTArray<ServiceWorkerRegistrationData>& TestGetData()
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ 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(nsLiteralString(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(""_ns))
+ << "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(nsLiteralCString(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(nsLiteralCString(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.AppendLiteral("^inBrowser=1\n");
+ buffer.AppendLiteral("https://scope_0.org\ncurrentWorkerURL 0\n");
+ buffer.Append(SERVICEWORKERREGISTRAR_TRUE "\n");
+ buffer.AppendLiteral("cacheName 0\n");
+ buffer.AppendInt(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ 16);
+ buffer.AppendLiteral("\n");
+ buffer.AppendInt(0);
+ buffer.AppendLiteral("\n");
+ buffer.AppendInt(0);
+ buffer.AppendLiteral("\n");
+ buffer.AppendInt(0);
+ buffer.AppendLiteral("\n");
+ buffer.AppendInt(0);
+ buffer.AppendLiteral("\n");
+ buffer.AppendLiteral("true\n");
+ buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.AppendLiteral("\n");
+ buffer.AppendLiteral("https://scope_1.org\ncurrentWorkerURL 1\n");
+ buffer.Append(SERVICEWORKERREGISTRAR_FALSE "\n");
+ buffer.AppendLiteral("cacheName 1\n");
+ buffer.AppendInt(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL, 16);
+ buffer.AppendLiteral("\n");
+ PRTime ts = PR_Now();
+ buffer.AppendInt(ts);
+ buffer.AppendLiteral("\n");
+ buffer.AppendInt(ts);
+ buffer.AppendLiteral("\n");
+ buffer.AppendInt(ts);
+ buffer.AppendLiteral("\n");
+ buffer.AppendInt(1);
+ buffer.AppendLiteral("\n");
+ buffer.AppendLiteral("false\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("^inBrowser=1", suffix0.get());
+ ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
+ ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
+ ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
+ ASSERT_TRUE(data[0].currentWorkerHandlesFetch());
+ ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ data[0].updateViaCache());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
+ ASSERT_EQ(false, data[0].navigationPreloadState().enabled());
+ ASSERT_STREQ("true", data[0].navigationPreloadState().headerValue().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("https://scope_1.org", cInfo1.spec().get());
+ ASSERT_STREQ("https://scope_1.org", data[1].scope().get());
+ ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
+ ASSERT_FALSE(data[1].currentWorkerHandlesFetch());
+ ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL,
+ data[1].updateViaCache());
+ ASSERT_EQ((int64_t)ts, data[1].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)ts, data[1].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)ts, data[1].lastUpdateTime());
+ ASSERT_EQ(true, data[1].navigationPreloadState().enabled());
+ ASSERT_STREQ("false", data[1].navigationPreloadState().headerValue().get());
+}
+
+TEST(ServiceWorkerRegistrar, TestDeleteData)
+{
+ ASSERT_TRUE(CreateFile("Foobar"_ns))
+ << "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 < 2; ++i) {
+ ServiceWorkerRegistrationData reg;
+
+ reg.scope() = nsPrintfCString("https://scope_write_%d.org", i);
+ reg.currentWorkerURL() = nsPrintfCString("currentWorkerURL write %d", i);
+ reg.currentWorkerHandlesFetch() = true;
+ reg.cacheName() =
+ NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i));
+ reg.updateViaCache() =
+ nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
+
+ reg.currentWorkerInstalledTime() = PR_Now();
+ reg.currentWorkerActivatedTime() = PR_Now();
+ reg.lastUpdateTime() = PR_Now();
+
+ nsAutoCString spec;
+ spec.AppendPrintf("spec write %d", i);
+
+ reg.principal() = mozilla::ipc::ContentPrincipalInfo(
+ mozilla::OriginAttributes(i % 2), spec, spec, mozilla::Nothing(),
+ 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)2, data.Length()) << "2 entries should be found";
+
+ for (int i = 0; i < 2; ++i) {
+ nsAutoCString test;
+
+ ASSERT_EQ(data[i].principal().type(),
+ mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
+ const mozilla::ipc::ContentPrincipalInfo& cInfo = data[i].principal();
+
+ mozilla::OriginAttributes attrs(i % 2);
+ nsAutoCString suffix, expectSuffix;
+ attrs.CreateSuffix(expectSuffix);
+ cInfo.attrs().CreateSuffix(suffix);
+
+ ASSERT_STREQ(expectSuffix.get(), suffix.get());
+
+ test.AppendPrintf("https://scope_write_%d.org", i);
+ ASSERT_STREQ(test.get(), cInfo.spec().get());
+
+ test.Truncate();
+ test.AppendPrintf("https://scope_write_%d.org", 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());
+
+ ASSERT_EQ(true, data[i].currentWorkerHandlesFetch());
+
+ test.Truncate();
+ test.AppendPrintf("cacheName write %d", i);
+ ASSERT_STREQ(test.get(), NS_ConvertUTF16toUTF8(data[i].cacheName()).get());
+
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ data[i].updateViaCache());
+
+ ASSERT_NE((int64_t)0, data[i].currentWorkerInstalledTime());
+ ASSERT_NE((int64_t)0, data[i].currentWorkerActivatedTime());
+ ASSERT_NE((int64_t)0, data[i].lastUpdateTime());
+ }
+}
+
+TEST(ServiceWorkerRegistrar, TestVersion2Migration)
+{
+ nsAutoCString buffer(
+ "2"
+ "\n");
+
+ buffer.AppendLiteral("^appId=123&inBrowser=1\n");
+ buffer.AppendLiteral(
+ "spec 0\nhttps://scope_0.org\nscriptSpec 0\ncurrentWorkerURL "
+ "0\nactiveCache 0\nwaitingCache 0\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.AppendLiteral("\n");
+ buffer.AppendLiteral(
+ "spec 1\nhttps://scope_1.org\nscriptSpec 1\ncurrentWorkerURL "
+ "1\nactiveCache 1\nwaitingCache 1\n");
+ buffer.AppendLiteral(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("^inBrowser=1", suffix0.get());
+ ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
+ ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
+ ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
+ ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
+ ASSERT_STREQ("activeCache 0",
+ NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ data[0].updateViaCache());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
+
+ 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("https://scope_1.org", cInfo1.spec().get());
+ ASSERT_STREQ("https://scope_1.org", data[1].scope().get());
+ ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
+ ASSERT_EQ(true, data[1].currentWorkerHandlesFetch());
+ ASSERT_STREQ("activeCache 1",
+ NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ data[1].updateViaCache());
+ ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
+}
+
+TEST(ServiceWorkerRegistrar, TestVersion3Migration)
+{
+ nsAutoCString buffer(
+ "3"
+ "\n");
+
+ buffer.AppendLiteral("^appId=123&inBrowser=1\n");
+ buffer.AppendLiteral(
+ "spec 0\nhttps://scope_0.org\ncurrentWorkerURL 0\ncacheName 0\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.AppendLiteral("\n");
+ buffer.AppendLiteral(
+ "spec 1\nhttps://scope_1.org\ncurrentWorkerURL 1\ncacheName 1\n");
+ buffer.AppendLiteral(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("^inBrowser=1", suffix0.get());
+ ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
+ ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
+ ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
+ ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
+ ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ data[0].updateViaCache());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
+
+ 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("https://scope_1.org", cInfo1.spec().get());
+ ASSERT_STREQ("https://scope_1.org", data[1].scope().get());
+ ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
+ ASSERT_EQ(true, data[1].currentWorkerHandlesFetch());
+ ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ data[1].updateViaCache());
+ ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
+}
+
+TEST(ServiceWorkerRegistrar, TestVersion4Migration)
+{
+ nsAutoCString buffer(
+ "4"
+ "\n");
+
+ buffer.AppendLiteral("^appId=123&inBrowser=1\n");
+ buffer.AppendLiteral(
+ "https://scope_0.org\ncurrentWorkerURL 0\ncacheName 0\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.AppendLiteral("\n");
+ buffer.AppendLiteral(
+ "https://scope_1.org\ncurrentWorkerURL 1\ncacheName 1\n");
+ buffer.AppendLiteral(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("^inBrowser=1", suffix0.get());
+ ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
+ ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
+ ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
+ // default is true
+ ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
+ ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ data[0].updateViaCache());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
+
+ 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("https://scope_1.org", cInfo1.spec().get());
+ ASSERT_STREQ("https://scope_1.org", data[1].scope().get());
+ ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
+ // default is true
+ ASSERT_EQ(true, data[1].currentWorkerHandlesFetch());
+ ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ data[1].updateViaCache());
+ ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
+}
+
+TEST(ServiceWorkerRegistrar, TestVersion5Migration)
+{
+ nsAutoCString buffer(
+ "5"
+ "\n");
+
+ buffer.AppendLiteral("^appId=123&inBrowser=1\n");
+ buffer.AppendLiteral("https://scope_0.org\ncurrentWorkerURL 0\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TRUE "\n");
+ buffer.AppendLiteral("cacheName 0\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.AppendLiteral("\n");
+ buffer.AppendLiteral("https://scope_1.org\ncurrentWorkerURL 1\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_FALSE "\n");
+ buffer.AppendLiteral("cacheName 1\n");
+ buffer.AppendLiteral(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("^inBrowser=1", suffix0.get());
+ ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
+ ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
+ ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
+ ASSERT_TRUE(data[0].currentWorkerHandlesFetch());
+ ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ data[0].updateViaCache());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
+
+ 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("https://scope_1.org", cInfo1.spec().get());
+ ASSERT_STREQ("https://scope_1.org", data[1].scope().get());
+ ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
+ ASSERT_FALSE(data[1].currentWorkerHandlesFetch());
+ ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ data[1].updateViaCache());
+ ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
+}
+
+TEST(ServiceWorkerRegistrar, TestVersion6Migration)
+{
+ nsAutoCString buffer(
+ "6"
+ "\n");
+
+ buffer.AppendLiteral("^appId=123&inBrowser=1\n");
+ buffer.AppendLiteral("https://scope_0.org\ncurrentWorkerURL 0\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TRUE "\n");
+ buffer.AppendLiteral("cacheName 0\n");
+ buffer.AppendInt(nsIRequest::LOAD_NORMAL, 16);
+ buffer.AppendLiteral("\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.AppendLiteral("\n");
+ buffer.AppendLiteral("https://scope_1.org\ncurrentWorkerURL 1\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_FALSE "\n");
+ buffer.AppendLiteral("cacheName 1\n");
+ buffer.AppendInt(nsIRequest::VALIDATE_ALWAYS, 16);
+ buffer.AppendLiteral("\n");
+ buffer.AppendLiteral(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("^inBrowser=1", suffix0.get());
+ ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
+ ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
+ ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
+ ASSERT_TRUE(data[0].currentWorkerHandlesFetch());
+ ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL,
+ data[0].updateViaCache());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
+
+ 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("https://scope_1.org", cInfo1.spec().get());
+ ASSERT_STREQ("https://scope_1.org", data[1].scope().get());
+ ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
+ ASSERT_FALSE(data[1].currentWorkerHandlesFetch());
+ ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ data[1].updateViaCache());
+ ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
+}
+
+TEST(ServiceWorkerRegistrar, TestVersion7Migration)
+{
+ nsAutoCString buffer(
+ "7"
+ "\n");
+
+ buffer.AppendLiteral("^appId=123&inBrowser=1\n");
+ buffer.AppendLiteral("https://scope_0.org\ncurrentWorkerURL 0\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TRUE "\n");
+ buffer.AppendLiteral("cacheName 0\n");
+ buffer.AppendInt(nsIRequest::LOAD_NORMAL, 16);
+ buffer.AppendLiteral("\n");
+ buffer.AppendInt(0);
+ buffer.AppendLiteral("\n");
+ buffer.AppendInt(0);
+ buffer.AppendLiteral("\n");
+ buffer.AppendInt(0);
+ buffer.AppendLiteral("\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.AppendLiteral("\n");
+ buffer.AppendLiteral("https://scope_1.org\ncurrentWorkerURL 1\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_FALSE "\n");
+ buffer.AppendLiteral("cacheName 1\n");
+ buffer.AppendInt(nsIRequest::VALIDATE_ALWAYS, 16);
+ buffer.AppendLiteral("\n");
+ PRTime ts = PR_Now();
+ buffer.AppendInt(ts);
+ buffer.AppendLiteral("\n");
+ buffer.AppendInt(ts);
+ buffer.AppendLiteral("\n");
+ buffer.AppendInt(ts);
+ buffer.AppendLiteral("\n");
+ buffer.AppendLiteral(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("^inBrowser=1", suffix0.get());
+ ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
+ ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
+ ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
+ ASSERT_TRUE(data[0].currentWorkerHandlesFetch());
+ ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL,
+ data[0].updateViaCache());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
+
+ 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("https://scope_1.org", cInfo1.spec().get());
+ ASSERT_STREQ("https://scope_1.org", data[1].scope().get());
+ ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
+ ASSERT_FALSE(data[1].currentWorkerHandlesFetch());
+ ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ data[1].updateViaCache());
+ ASSERT_EQ((int64_t)ts, data[1].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)ts, data[1].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)ts, data[1].lastUpdateTime());
+}
+
+TEST(ServiceWorkerRegistrar, TestDedupeRead)
+{
+ nsAutoCString buffer(
+ "3"
+ "\n");
+
+ // unique entries
+ buffer.AppendLiteral("^inBrowser=1\n");
+ buffer.AppendLiteral(
+ "spec 0\nhttps://scope_0.org\ncurrentWorkerURL 0\ncacheName 0\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.AppendLiteral("\n");
+ buffer.AppendLiteral(
+ "spec 1\nhttps://scope_1.org\ncurrentWorkerURL 1\ncacheName 1\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ // dupe entries
+ buffer.AppendLiteral("^inBrowser=1\n");
+ buffer.AppendLiteral(
+ "spec 1\nhttps://scope_0.org\ncurrentWorkerURL 0\ncacheName 0\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.AppendLiteral("^inBrowser=1\n");
+ buffer.AppendLiteral(
+ "spec 2\nhttps://scope_0.org\ncurrentWorkerURL 0\ncacheName 0\n");
+ buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+ buffer.AppendLiteral("\n");
+ buffer.AppendLiteral(
+ "spec 3\nhttps://scope_1.org\ncurrentWorkerURL 1\ncacheName 1\n");
+ buffer.AppendLiteral(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("^inBrowser=1", suffix0.get());
+ ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
+ ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
+ ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
+ ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
+ ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ data[0].updateViaCache());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
+
+ 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("https://scope_1.org", cInfo1.spec().get());
+ ASSERT_STREQ("https://scope_1.org", data[1].scope().get());
+ ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
+ ASSERT_EQ(true, data[1].currentWorkerHandlesFetch());
+ ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ data[1].updateViaCache());
+ ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
+}
+
+TEST(ServiceWorkerRegistrar, TestDedupeWrite)
+{
+ {
+ RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+ for (int i = 0; i < 2; ++i) {
+ ServiceWorkerRegistrationData reg;
+
+ reg.scope() = "https://scope_write.dedupe"_ns;
+ reg.currentWorkerURL() = nsPrintfCString("currentWorkerURL write %d", i);
+ reg.currentWorkerHandlesFetch() = true;
+ reg.cacheName() =
+ NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i));
+ reg.updateViaCache() =
+ nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
+
+ nsAutoCString spec;
+ spec.AppendPrintf("spec write dedupe/%d", i);
+
+ reg.principal() = mozilla::ipc::ContentPrincipalInfo(
+ mozilla::OriginAttributes(false), spec, spec, mozilla::Nothing(),
+ 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::OriginAttributes attrs(false);
+ nsAutoCString suffix, expectSuffix;
+ attrs.CreateSuffix(expectSuffix);
+ cInfo.attrs().CreateSuffix(suffix);
+
+ // Last entry passed to RegisterServiceWorkerInternal() should overwrite
+ // previous values. So expect "1" in values here.
+ ASSERT_STREQ(expectSuffix.get(), suffix.get());
+ ASSERT_STREQ("https://scope_write.dedupe", cInfo.spec().get());
+ ASSERT_STREQ("https://scope_write.dedupe", data[0].scope().get());
+ ASSERT_STREQ("currentWorkerURL write 1", data[0].currentWorkerURL().get());
+ ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
+ ASSERT_STREQ("cacheName write 1",
+ NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+ ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+ data[0].updateViaCache());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+ ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+ ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ int rv = RUN_ALL_TESTS();
+ return rv;
+}
diff --git a/dom/serviceworkers/test/gtest/moz.build b/dom/serviceworkers/test/gtest/moz.build
new file mode 100644
index 0000000000..99e2945332
--- /dev/null
+++ b/dom/serviceworkers/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/serviceworkers/test/gzip_redirect_worker.js b/dom/serviceworkers/test/gzip_redirect_worker.js
new file mode 100644
index 0000000000..dcee4b3b18
--- /dev/null
+++ b/dom/serviceworkers/test/gzip_redirect_worker.js
@@ -0,0 +1,15 @@
+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/serviceworkers/test/header_checker.sjs b/dom/serviceworkers/test/header_checker.sjs
new file mode 100644
index 0000000000..7061041039
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/hello.html b/dom/serviceworkers/test/hello.html
new file mode 100644
index 0000000000..97eb03c902
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/importscript.sjs b/dom/serviceworkers/test/importscript.sjs
new file mode 100644
index 0000000000..e075eadd87
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/importscript_worker.js b/dom/serviceworkers/test/importscript_worker.js
new file mode 100644
index 0000000000..2ade477f63
--- /dev/null
+++ b/dom/serviceworkers/test/importscript_worker.js
@@ -0,0 +1,46 @@
+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 {
+ // new unique script should fail
+ importScripts(["importscript.sjs?unique=true"]);
+ res[0].postMessage("KO");
+ return;
+ } catch (ex) {}
+
+ try {
+ // duplicate script previously offlined should succeed
+ importScripts(["importscript.sjs"]);
+ } catch (ex) {
+ res[0].postMessage("KO");
+ return;
+ }
+
+ res[0].postMessage(counter == 3 ? "OK" : "KO");
+ });
+};
diff --git a/dom/serviceworkers/test/install_event_error_worker.js b/dom/serviceworkers/test/install_event_error_worker.js
new file mode 100644
index 0000000000..abcceb6b69
--- /dev/null
+++ b/dom/serviceworkers/test/install_event_error_worker.js
@@ -0,0 +1,9 @@
+// Worker that errors on receiving an install event.
+oninstall = function (e) {
+ e.waitUntil(
+ new Promise(function (resolve, reject) {
+ undefined.doSomething;
+ resolve();
+ })
+ );
+};
diff --git a/dom/serviceworkers/test/install_event_worker.js b/dom/serviceworkers/test/install_event_worker.js
new file mode 100644
index 0000000000..3001575189
--- /dev/null
+++ b/dom/serviceworkers/test/install_event_worker.js
@@ -0,0 +1,3 @@
+oninstall = function (e) {
+ dump("Got install event\n");
+};
diff --git a/dom/serviceworkers/test/intercepted_channel_process_swap_worker.js b/dom/serviceworkers/test/intercepted_channel_process_swap_worker.js
new file mode 100644
index 0000000000..ccc74bc895
--- /dev/null
+++ b/dom/serviceworkers/test/intercepted_channel_process_swap_worker.js
@@ -0,0 +1,7 @@
+onfetch = e => {
+ const url = new URL(e.request.url).searchParams.get("respondWith");
+
+ if (url) {
+ e.respondWith(fetch(url));
+ }
+};
diff --git a/dom/serviceworkers/test/isolated/README.md b/dom/serviceworkers/test/isolated/README.md
new file mode 100644
index 0000000000..2b462385af
--- /dev/null
+++ b/dom/serviceworkers/test/isolated/README.md
@@ -0,0 +1,19 @@
+This directory contains tests that are flaky when run with other tests
+but that we don't want to disable and where it's not trivial to make
+the tests not flaky at this time, but we have a plan to fix them via
+systemic fixes that are improving the codebase rather than hacking a
+test until it works.
+
+This directory and ugly hack structure needs to exist because of
+multi-e10s propagation races that will go away when we finish
+implementing the multi-e10s overhaul for ServiceWorkers. Most
+specifically, unregister() calls need to propagate across all
+content processes. There are fixes on bug 1318142, but they're
+ugly and complicate things.
+
+Specific test notes and rationalizations:
+- multi-e10s-update: This test relies on there being no registrations
+ existing at its start. The preceding test that induces the breakage
+ (`browser_force_refresh.js`) was made to clean itself up, but the
+ unregister() race issue is not easily/cleanly hacked around and this
+ test will itself become moot when the multi-e10s changes land.
diff --git a/dom/serviceworkers/test/isolated/multi-e10s-update/browser.toml b/dom/serviceworkers/test/isolated/multi-e10s-update/browser.toml
new file mode 100644
index 0000000000..9bb55cb78c
--- /dev/null
+++ b/dom/serviceworkers/test/isolated/multi-e10s-update/browser.toml
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files = [
+ "file_multie10s_update.html",
+ "server_multie10s_update.sjs",
+]
+
+["browser_multie10s_update.js"]
+skip-if = ["true"] # bug 1429794 is to re-enable
diff --git a/dom/serviceworkers/test/isolated/multi-e10s-update/browser_multie10s_update.js b/dom/serviceworkers/test/isolated/multi-e10s-update/browser_multie10s_update.js
new file mode 100644
index 0000000000..457d47863c
--- /dev/null
+++ b/dom/serviceworkers/test/isolated/multi-e10s-update/browser_multie10s_update.js
@@ -0,0 +1,147 @@
+"use strict";
+
+// Testing if 2 child processes are correctly managed when they both try to do
+// an SW update.
+
+const BASE_URI =
+ "http://mochi.test:8888/browser/dom/serviceworkers/test/isolated/multi-e10s-update/";
+
+add_task(async function test_update() {
+ info("Setting the prefs to having multi-e10s enabled");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processCount", 4],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ });
+
+ let url = BASE_URI + "file_multie10s_update.html";
+
+ info("Creating the first tab...");
+ let tab1 = BrowserTestUtils.addTab(gBrowser, url);
+ let browser1 = gBrowser.getBrowserForTab(tab1);
+ await BrowserTestUtils.browserLoaded(browser1);
+
+ info("Creating the second tab...");
+ let tab2 = BrowserTestUtils.addTab(gBrowser, url);
+ let browser2 = gBrowser.getBrowserForTab(tab2);
+ await BrowserTestUtils.browserLoaded(browser2);
+
+ let sw = BASE_URI + "server_multie10s_update.sjs";
+
+ info("Let's make sure there are no existing registrations...");
+ let existingCount = await SpecialPowers.spawn(
+ browser1,
+ [],
+ async function () {
+ const regs = await content.navigator.serviceWorker.getRegistrations();
+ return regs.length;
+ }
+ );
+ is(existingCount, 0, "Previous tests should have cleaned up!");
+
+ info("Let's start the test...");
+ /* eslint-disable no-shadow */
+ let status = await SpecialPowers.spawn(browser1, [sw], function (url) {
+ // Let the SW be served immediately once by triggering a relase immediately.
+ // We don't need to await this. We do this from a frame script because
+ // it has fetch.
+ content.fetch(url + "?release");
+
+ // Registration of the SW
+ return (
+ content.navigator.serviceWorker
+ .register(url)
+
+ // Activation
+ .then(function (r) {
+ content.registration = r;
+ return new content.window.Promise(resolve => {
+ let worker = r.installing;
+ worker.addEventListener("statechange", () => {
+ if (worker.state === "installed") {
+ resolve(true);
+ }
+ });
+ });
+ })
+
+ // Waiting for the result.
+ .then(() => {
+ return new content.window.Promise(resolveResults => {
+ // Once both updates have been issued and a single update has failed, we
+ // can tell the .sjs to release a single copy of the SW script.
+ let updateCount = 0;
+ const uc = new content.window.BroadcastChannel("update");
+ // This promise tracks the updates tally.
+ const updatesIssued = new Promise(resolveUpdatesIssued => {
+ uc.onmessage = function (e) {
+ updateCount++;
+ console.log("got update() number", updateCount);
+ if (updateCount === 2) {
+ resolveUpdatesIssued();
+ }
+ };
+ });
+
+ let results = [];
+ const rc = new content.window.BroadcastChannel("result");
+ // This promise resolves when an update has failed.
+ const oneFailed = new Promise(resolveOneFailed => {
+ rc.onmessage = function (e) {
+ console.log("got result", e.data);
+ results.push(e.data);
+ if (e.data === 1) {
+ resolveOneFailed();
+ }
+ if (results.length != 2) {
+ return;
+ }
+
+ resolveResults(results[0] + results[1]);
+ };
+ });
+
+ Promise.all([updatesIssued, oneFailed]).then(() => {
+ console.log("releasing update");
+ content.fetch(url + "?release").catch(ex => {
+ console.error("problem releasing:", ex);
+ });
+ });
+
+ // Let's inform the tabs.
+ const sc = new content.window.BroadcastChannel("start");
+ sc.postMessage("go");
+ });
+ })
+ );
+ });
+ /* eslint-enable no-shadow */
+
+ if (status == 0) {
+ ok(false, "both succeeded. This is wrong.");
+ } else if (status == 1) {
+ ok(true, "one succeded, one failed. This is good.");
+ } else {
+ ok(false, "both failed. This is definitely wrong.");
+ }
+
+ // let's clean up the registration and get the fetch count. The count
+ // should be 1 for the initial fetch and 1 for the update.
+ /* eslint-disable no-shadow */
+ const count = await SpecialPowers.spawn(browser1, [sw], async function (url) {
+ // We stored the registration on the frame script's wrapper, hence directly
+ // accesss content without using wrappedJSObject.
+ await content.registration.unregister();
+ const { count } = await content
+ .fetch(url + "?get-and-clear-count")
+ .then(r => r.json());
+ return count;
+ });
+ /* eslint-enable no-shadow */
+ is(count, 2, "SW should have been fetched only twice");
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+});
diff --git a/dom/serviceworkers/test/isolated/multi-e10s-update/file_multie10s_update.html b/dom/serviceworkers/test/isolated/multi-e10s-update/file_multie10s_update.html
new file mode 100644
index 0000000000..d611b01b59
--- /dev/null
+++ b/dom/serviceworkers/test/isolated/multi-e10s-update/file_multie10s_update.html
@@ -0,0 +1,40 @@
+<html>
+<body>
+<script>
+
+var bc = new BroadcastChannel('start');
+bc.onmessage = function(e) {
+ // This message is not for us.
+ if (e.data != 'go') {
+ return;
+ }
+
+ // It can happen that we don't have the registrations yet. Let's try with a
+ // timeout.
+ function proceed() {
+ return navigator.serviceWorker.getRegistrations().then(regs => {
+ if (!regs.length) {
+ setTimeout(proceed, 200);
+ return;
+ }
+
+ bc = new BroadcastChannel('result');
+ regs[0].update().then(() => {
+ bc.postMessage(0);
+ }, () => {
+ bc.postMessage(1);
+ });
+
+ // Tell the coordinating frame script that we've kicked off our update
+ // call so that the SW script can be released once both instances of us
+ // have triggered update() and 1 has failed.
+ const blockingChannel = new BroadcastChannel('update');
+ blockingChannel.postMessage(true);
+ });
+ }
+
+ proceed();
+}
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/isolated/multi-e10s-update/server_multie10s_update.sjs b/dom/serviceworkers/test/isolated/multi-e10s-update/server_multie10s_update.sjs
new file mode 100644
index 0000000000..186c9ebc7d
--- /dev/null
+++ b/dom/serviceworkers/test/isolated/multi-e10s-update/server_multie10s_update.sjs
@@ -0,0 +1,99 @@
+// stolen from file_blocked_script.sjs
+function setGlobalState(data, key) {
+ x = {
+ data,
+ QueryInterface(iid) {
+ return this;
+ },
+ };
+ x.wrappedJSObject = x;
+ setObjectState(key, x);
+}
+
+function getGlobalState(key) {
+ var data;
+ getObjectState(key, function (x) {
+ data = x && x.wrappedJSObject.data;
+ });
+ return data;
+}
+
+function completeBlockingRequest(response) {
+ response.write("42");
+ response.finish();
+}
+
+// This stores the response that's currently blocking, or true if the release
+// got here before the blocking request.
+const BLOCKING_KEY = "multie10s-update-release";
+// This tracks the number of blocking requests we received up to this point in
+// time. This value will be cleared when fetched. It's on the caller to make
+// sure that all the blocking requests that might occurr have already occurred.
+const COUNT_KEY = "multie10s-update-count";
+
+/**
+ * Serve a request that will only be completed when the ?release variant of this
+ * .sjs is fetched. This allows us to avoid using a timer, which slows down the
+ * tests and is brittle under slow hardware.
+ */
+function handleBlockingRequest(request, response) {
+ response.processAsync();
+ response.setHeader("Content-Type", "application/javascript", false);
+
+ const existingCount = getGlobalState(COUNT_KEY) || 0;
+ setGlobalState(existingCount + 1, COUNT_KEY);
+
+ const alreadyReleased = getGlobalState(BLOCKING_KEY);
+ if (alreadyReleased === true) {
+ completeBlockingRequest(response);
+ setGlobalState(null, BLOCKING_KEY);
+ } else if (alreadyReleased) {
+ // If we've got another response stacked up, this means something is wrong
+ // with the test. The count mechanism will detect this, so just let this
+ // one through so we fail fast rather than hanging.
+ dump("we got multiple blocking requests stacked up!!\n");
+ completeBlockingRequest(response);
+ } else {
+ setGlobalState(response, BLOCKING_KEY);
+ }
+}
+
+function handleReleaseRequest(request, response) {
+ const blockingResponse = getGlobalState(BLOCKING_KEY);
+ if (blockingResponse) {
+ completeBlockingRequest(blockingResponse);
+ setGlobalState(null, BLOCKING_KEY);
+ } else {
+ setGlobalState(true, BLOCKING_KEY);
+ }
+
+ response.setHeader("Content-Type", "application/json", false);
+ response.write(JSON.stringify({ released: true }));
+}
+
+function handleCountRequest(request, response) {
+ const count = getGlobalState(COUNT_KEY) || 0;
+ // --verify requires that we clear this so the test can be re-run.
+ setGlobalState(0, COUNT_KEY);
+
+ response.setHeader("Content-Type", "application/json", false);
+ response.write(JSON.stringify({ count }));
+}
+
+function handleRequest(request, response) {
+ dump(
+ "server_multie10s_update.sjs: processing request for " +
+ request.path +
+ "?" +
+ request.queryString +
+ "\n"
+ );
+ const query = new URLSearchParams(request.queryString);
+ if (query.has("release")) {
+ handleReleaseRequest(request, response);
+ } else if (query.has("get-and-clear-count")) {
+ handleCountRequest(request, response);
+ } else {
+ handleBlockingRequest(request, response);
+ }
+}
diff --git a/dom/serviceworkers/test/lazy_worker.js b/dom/serviceworkers/test/lazy_worker.js
new file mode 100644
index 0000000000..6f8681d25c
--- /dev/null
+++ b/dom/serviceworkers/test/lazy_worker.js
@@ -0,0 +1,8 @@
+onactivate = function (event) {
+ var promise = new Promise(function (res) {
+ setTimeout(function () {
+ res();
+ }, 500);
+ });
+ event.waitUntil(promise);
+};
diff --git a/dom/serviceworkers/test/lorem_script.js b/dom/serviceworkers/test/lorem_script.js
new file mode 100644
index 0000000000..bc8f3c8085
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/match_all_advanced_worker.js b/dom/serviceworkers/test/match_all_advanced_worker.js
new file mode 100644
index 0000000000..7aa623161a
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/match_all_client/match_all_client_id.html b/dom/serviceworkers/test/match_all_client/match_all_client_id.html
new file mode 100644
index 0000000000..7ac6fc9d05
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/match_all_client_id_worker.js b/dom/serviceworkers/test/match_all_client_id_worker.js
new file mode 100644
index 0000000000..607eec97d4
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/match_all_clients/match_all_controlled.html b/dom/serviceworkers/test/match_all_clients/match_all_controlled.html
new file mode 100644
index 0000000000..35f064815d
--- /dev/null
+++ b/dom/serviceworkers/test/match_all_clients/match_all_controlled.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 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) {
+ // Send a message to our SW that will cause it to do clients.matchAll()
+ // and send a message *to each client about themselves* (rather than
+ // replying directly to us with all the clients it finds).
+ swr.active.postMessage("Start");
+ });
+ }
+
+ function postResult(result, msg) {
+ response = {
+ result,
+ message: msg
+ };
+
+ testWindow.postMessage(response, "*");
+ }
+
+ navigator.serviceWorker.onmessage = async function(msg) {
+ // ## Verify the contents of the SW's serialized rep of our client info.
+ // Clients are opaque identifiers at a spec level, but we want to verify
+ // that they are UUID's *without wrapping "{}" characters*.
+ 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);
+
+ result = msg.data.type === "window";
+ postResult(result, "Client type test. expected=window");
+
+ // ## Verify the FetchEvent.clientId
+ // In bug 1446225 it turned out we provided UUID's wrapped with {}'s. We
+ // now also get coverage by forcing our clients.get() to forbid UUIDs
+ // with that form.
+
+ const clientIdResp = await fetch('clientId');
+ const fetchClientId = await clientIdResp.text();
+ result = re.test(fetchClientId);
+ postResult(result, "Fetch clientId test");
+
+ postResult(true, "DONE");
+ window.close();
+ };
+</script>
+
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/match_all_properties_worker.js b/dom/serviceworkers/test/match_all_properties_worker.js
new file mode 100644
index 0000000000..f07a44e233
--- /dev/null
+++ b/dom/serviceworkers/test/match_all_properties_worker.js
@@ -0,0 +1,27 @@
+onfetch = function (e) {
+ if (/\/clientId$/.test(e.request.url)) {
+ e.respondWith(new Response(e.clientId));
+ }
+};
+
+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 = {
+ type: client.type,
+ id: client.id,
+ url: client.url,
+ visibilityState: client.visibilityState,
+ focused: client.focused,
+ frameType: client.frameType,
+ };
+ client.postMessage(response);
+ }
+ });
+};
diff --git a/dom/serviceworkers/test/match_all_worker.js b/dom/serviceworkers/test/match_all_worker.js
new file mode 100644
index 0000000000..99b11c850d
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/message_posting_worker.js b/dom/serviceworkers/test/message_posting_worker.js
new file mode 100644
index 0000000000..5fcd0a741e
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/message_receiver.html b/dom/serviceworkers/test/message_receiver.html
new file mode 100644
index 0000000000..82cb587c72
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/mochitest-common.toml b/dom/serviceworkers/test/mochitest-common.toml
new file mode 100644
index 0000000000..94badb117f
--- /dev/null
+++ b/dom/serviceworkers/test/mochitest-common.toml
@@ -0,0 +1,494 @@
+[DEFAULT]
+tags = "condprof"
+support-files = [
+ "abrupt_completion_worker.js",
+ "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/cookie/cookie_test.js",
+ "fetch/cookie/register.html",
+ "fetch/cookie/unregister.html",
+ "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",
+ "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.html",
+ "empty.js",
+ "notification_constructor_error.js",
+ "notification_get_sw.js",
+ "notification/register.html",
+ "sanitize/frame.html",
+ "sanitize/register.html",
+ "sanitize/example_check_and_unregister.html",
+ "sanitize_worker.js",
+ "streamfilter_server.sjs",
+ "streamfilter_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",
+ "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",
+ "thirdparty/worker.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",
+ "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.sjs",
+ "lorem_script.js",
+ "file_blob_response_worker.js",
+ "file_js_cache_cleanup.js",
+ "file_js_cache.html",
+ "file_js_cache_with_sri.html",
+ "file_js_cache.js",
+ "file_js_cache_save_after_load.html",
+ "file_js_cache_save_after_load.js",
+ "file_js_cache_syntax_error.html",
+ "file_js_cache_syntax_error.js",
+ "!/dom/security/test/cors/file_CrossSiteXHR_server.sjs",
+ "!/dom/notification/test/mochitest/MockServices.js",
+ "!/dom/notification/test/mochitest/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",
+ "async_waituntil_worker.js",
+ "lazy_worker.js",
+ "nofetch_handler_worker.js",
+ "service_worker.js",
+ "service_worker_client.html",
+ "utils.js",
+ "sw_storage_not_allow.js",
+ "update_worker.sjs",
+ "self_update_worker.sjs",
+ "!/dom/events/test/event_leak_utils.js",
+ "onmessageerror_worker.js",
+ "pref/fetch_nonexistent_file.html",
+ "pref/intercept_nonexistent_file_sw.js",
+]
+
+["test_abrupt_completion.html"]
+skip-if = ["os == 'linux'"] # Bug 1615164
+
+["test_async_waituntil.html"]
+
+["test_bad_script_cache.html"]
+
+["test_bug1151916.html"]
+
+["test_bug1240436.html"]
+
+["test_bug1408734.html"]
+
+["test_claim.html"]
+
+["test_claim_oninstall.html"]
+
+["test_controller.html"]
+
+["test_cross_origin_url_after_redirect.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_devtools_bypass_serviceworker.html"]
+
+["test_empty_serviceworker.html"]
+
+["test_enabled_pref.html"]
+
+["test_error_reporting.html"]
+skip-if = ["serviceworker_e10s"]
+
+["test_escapedSlashes.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_eval_allowed.html"]
+
+["test_event_listener_leaks.html"]
+
+["test_fetch_event.html"]
+skip-if = ["debug"] # Bug 1262224
+
+["test_fetch_event_with_thirdpartypref.html"]
+skip-if = ["debug"] # Bug 1262224
+
+["test_fetch_integrity.html"]
+skip-if = ["serviceworker_e10s"]
+support-files = [
+ "console_monitor.js",
+]
+
+["test_file_blob_response.html"]
+
+["test_file_blob_upload.html"]
+
+["test_file_upload.html"]
+skip-if = ["os == 'android'"] #Bug 1430182
+support-files = [
+ "script_file_upload.js",
+ "sw_file_upload.js",
+ "server_file_upload.sjs",
+]
+
+["test_force_refresh.html"]
+
+["test_gzip_redirect.html"]
+
+["test_hsts_upgrade_intercept.html"]
+skip-if = [
+ "win11_2009 && !debug", # Bug 1797751
+ "os == 'linux' && bits == 64 && debug", # Bug 1749068
+ "apple_catalina && !debug", # Bug 1717091
+]
+scheme = "https"
+
+["test_imagecache.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_imagecache_max_age.html"]
+skip-if = [
+ "os == 'linux' && bits == 64 && !debug && asan && os_version == '18.04'", # Bug 1585668
+ "display == 'wayland' && os_version == '22.04' && debug", # Bug 1856980
+ "http3",
+ "http2",
+]
+
+["test_importscript.html"]
+
+["test_install_event.html"]
+
+["test_install_event_gc.html"]
+skip-if = ["xorigin"] # JavaScript error: http://mochi.xorigin-test:8888/tests/SimpleTest/TestRunner.js, line 157: SecurityError: Permission denied to access property "wrappedJSObject" on cross-origin object
+
+["test_installation_simple.html"]
+
+["test_match_all.html"]
+
+["test_match_all_advanced.html"]
+
+["test_match_all_client_id.html"]
+skip-if = [
+ "os == 'android'",
+ "http3",
+ "http2",
+]
+
+["test_match_all_client_properties.html"]
+skip-if = ["os == 'android'"]
+
+["test_navigationPreload_disable_crash.html"]
+scheme = "https"
+skip-if = ["os == 'linux' && bits == 64 && debug"] # Bug 1749068
+
+["test_navigator.html"]
+
+["test_nofetch_handler.html"]
+
+["test_not_intercept_plugin.html"]
+skip-if = ["serviceworker_e10s"] # leaks InterceptedHttpChannel and others things
+
+["test_notification_constructor_error.html"]
+
+["test_notification_get.html"]
+skip-if = ["xorigin"] # Bug 1792790
+
+["test_notification_openWindow.html"]
+skip-if = [
+ "os == 'android'", # Bug 1620052
+ "xorigin", # JavaScript error: http://mochi.xorigin-test:8888/tests/SimpleTest/TestRunner.js, line 157: SecurityError: Permission denied to access property "wrappedJSObject" on cross-origin object
+ "http3",
+ "http2",
+]
+support-files = [
+ "notification_openWindow_worker.js",
+ "file_notification_openWindow.html",
+]
+tags = "openwindow"
+
+["test_notificationclick-otherwindow.html"]
+skip-if = ["xorigin"] # Bug 1792790
+
+["test_notificationclick.html"]
+skip-if = ["xorigin"] # Bug 1792790
+
+["test_notificationclick_focus.html"]
+skip-if = ["xorigin"] # Bug 1792790
+
+["test_notificationclose.html"]
+skip-if = ["xorigin"] # Bug 1792790
+
+["test_onmessageerror.html"]
+skip-if = ["xorigin"] # Bug 1792790
+
+["test_opaque_intercept.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_origin_after_redirect.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_origin_after_redirect_cached.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["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"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_register_https_in_http.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_sandbox_intercept.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_sanitize.html"]
+
+["test_scopes.html"]
+
+["test_script_loader_intercepted_js_cache.html"]
+skip-if = ["serviceworker_e10s"]
+
+["test_self_update_worker.html"]
+skip-if = [
+ "serviceworker_e10s",
+ "os == 'android'",
+]
+
+["test_service_worker_allowed.html"]
+
+["test_serviceworker.html"]
+
+["test_serviceworker_header.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_serviceworker_interfaces.html"]
+
+["test_serviceworker_not_sharedworker.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_skip_waiting.html"]
+
+["test_streamfilter.html"]
+
+["test_strict_mode_warning.html"]
+
+["test_third_party_iframes.html"]
+support-files = [
+ "window_party_iframes.html",
+]
+
+["test_unregister.html"]
+
+["test_unresolved_fetch_interception.html"]
+skip-if = [
+ "verify",
+ "serviceworker_e10s",
+]
+
+["test_workerUnregister.html"]
+
+["test_workerUpdate.html"]
+
+["test_worker_reference_gc_timeout.html"]
+
+["test_workerupdatefoundevent.html"]
+
+["test_xslt.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
diff --git a/dom/serviceworkers/test/mochitest-dFPI.toml b/dom/serviceworkers/test/mochitest-dFPI.toml
new file mode 100644
index 0000000000..1fcbda03cf
--- /dev/null
+++ b/dom/serviceworkers/test/mochitest-dFPI.toml
@@ -0,0 +1,10 @@
+[DEFAULT]
+# Enable dFPI(cookieBehavior 5) for service worker tests.
+prefs = ["network.cookie.cookieBehavior=5"]
+tags = "serviceworker-dfpi"
+# We disable service workers for third-party contexts when dFPI is enabled. So,
+# we disable xorigin tests for dFPI.
+skip-if = ["xorigin"]
+dupe-manifest = true
+
+["include:mochitest-common.toml"]
diff --git a/dom/serviceworkers/test/mochitest.toml b/dom/serviceworkers/test/mochitest.toml
new file mode 100644
index 0000000000..13faa300c5
--- /dev/null
+++ b/dom/serviceworkers/test/mochitest.toml
@@ -0,0 +1,56 @@
+[DEFAULT]
+# Mochitests are executed in iframes. Several ServiceWorker tests use iframes
+# too. The result is that we have nested iframes. CookieBehavior 4
+# (BEHAVIOR_REJECT_TRACKER) doesn't grant storage access permission to nested
+# iframes because trackers could use them to follow users across sites. Let's
+# use cookieBehavior 0 (BEHAVIOR_ACCEPT) here.
+prefs = ["network.cookie.cookieBehavior=0"]
+dupe-manifest = true
+tags = "condprof"
+
+# Following tests are not working currently when dFPI is enabled. So, we put
+# these tests here instead of mochitest-common.toml so that these tests won't run
+# when dFPI is enabled.
+
+["include:mochitest-common.toml"]
+
+["test_cookie_fetch.html"]
+
+["test_csp_upgrade-insecure_intercept.html"]
+
+["test_eventsource_intercept.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_https_fetch.html"]
+skip-if = ["condprof"] #: timed out
+
+["test_https_fetch_cloned_response.html"]
+
+["test_https_origin_after_redirect.html"]
+
+["test_https_origin_after_redirect_cached.html"]
+skip-if = ["condprof"] #: timed out
+
+["test_https_synth_fetch_from_cached_sw.html"]
+
+["test_importscript_mixedcontent.html"]
+tags = "mcb"
+
+["test_openWindow.html"]
+skip-if = [
+ "os == 'android'", # Bug 1620052
+ "xorigin", # Bug 1792790
+ "condprof", #: timed out
+ "http3",
+ "http2",
+]
+tags = "openwindow"
+
+["test_sanitize_domain.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
diff --git a/dom/serviceworkers/test/navigationPreload_page.html b/dom/serviceworkers/test/navigationPreload_page.html
new file mode 100644
index 0000000000..39d4a79378
--- /dev/null
+++ b/dom/serviceworkers/test/navigationPreload_page.html
@@ -0,0 +1 @@
+NavigationPreload
diff --git a/dom/serviceworkers/test/network_with_utils.html b/dom/serviceworkers/test/network_with_utils.html
new file mode 100644
index 0000000000..63f6b0e796
--- /dev/null
+++ b/dom/serviceworkers/test/network_with_utils.html
@@ -0,0 +1,14 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="utils.js" type="text/javascript"></script>
+</head>
+<body>
+NETWORK
+</body>
+</html>
diff --git a/dom/serviceworkers/test/nofetch_handler_worker.js b/dom/serviceworkers/test/nofetch_handler_worker.js
new file mode 100644
index 0000000000..0e406b3761
--- /dev/null
+++ b/dom/serviceworkers/test/nofetch_handler_worker.js
@@ -0,0 +1,14 @@
+function handleFetch(event) {
+ event.respondWith(new Response("intercepted"));
+}
+
+self.oninstall = function (event) {
+ addEventListener("fetch", handleFetch);
+ self.onfetch = handleFetch;
+};
+
+// Bug 1325101. Make sure adding event listeners for other events
+// doesn't set the fetch flag.
+addEventListener("push", function () {});
+addEventListener("message", function () {});
+addEventListener("non-sw-event", function () {});
diff --git a/dom/serviceworkers/test/notification/register.html b/dom/serviceworkers/test/notification/register.html
new file mode 100644
index 0000000000..b7df73bede
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/notification_constructor_error.js b/dom/serviceworkers/test/notification_constructor_error.js
new file mode 100644
index 0000000000..644dba480e
--- /dev/null
+++ b/dom/serviceworkers/test/notification_constructor_error.js
@@ -0,0 +1 @@
+new Notification("Hi there");
diff --git a/dom/serviceworkers/test/notification_get_sw.js b/dom/serviceworkers/test/notification_get_sw.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/serviceworkers/test/notification_get_sw.js
diff --git a/dom/serviceworkers/test/notification_openWindow_worker.js b/dom/serviceworkers/test/notification_openWindow_worker.js
new file mode 100644
index 0000000000..890f70f795
--- /dev/null
+++ b/dom/serviceworkers/test/notification_openWindow_worker.js
@@ -0,0 +1,25 @@
+const gRoot = "http://mochi.test:8888/tests/dom/serviceworkers/test/";
+const gTestURL = gRoot + "test_notification_openWindow.html";
+const gClientURL = gRoot + "file_notification_openWindow.html";
+
+onmessage = function (event) {
+ if (event.data !== "DONE") {
+ dump(`ERROR: received unexpected message: ${JSON.stringify(event.data)}\n`);
+ }
+
+ event.waitUntil(
+ clients.matchAll({ includeUncontrolled: true }).then(cl => {
+ for (let client of cl) {
+ // The |gClientURL| window closes itself after posting the DONE message,
+ // so we don't need to send it anything here.
+ if (client.url === gTestURL) {
+ client.postMessage("DONE");
+ }
+ }
+ })
+ );
+};
+
+onnotificationclick = function (event) {
+ clients.openWindow(gClientURL);
+};
diff --git a/dom/serviceworkers/test/notificationclick-otherwindow.html b/dom/serviceworkers/test/notificationclick-otherwindow.html
new file mode 100644
index 0000000000..f64e82aabd
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/notificationclick.html b/dom/serviceworkers/test/notificationclick.html
new file mode 100644
index 0000000000..448764a1cb
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/notificationclick.js b/dom/serviceworkers/test/notificationclick.js
new file mode 100644
index 0000000000..ae776095c7
--- /dev/null
+++ b/dom/serviceworkers/test/notificationclick.js
@@ -0,0 +1,23 @@
+// 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/serviceworkers/test/notificationclick_focus.html b/dom/serviceworkers/test/notificationclick_focus.html
new file mode 100644
index 0000000000..0152d397f3
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/notificationclick_focus.js b/dom/serviceworkers/test/notificationclick_focus.js
new file mode 100644
index 0000000000..1f0924560a
--- /dev/null
+++ b/dom/serviceworkers/test/notificationclick_focus.js
@@ -0,0 +1,49 @@
+// 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 (ex) {
+ dump("Error " + ex + "\n");
+ clients.forEach(function (client) {
+ client.postMessage({ ok: false });
+ });
+ });
+ })
+ );
+};
diff --git a/dom/serviceworkers/test/notificationclose.html b/dom/serviceworkers/test/notificationclose.html
new file mode 100644
index 0000000000..f18801122e
--- /dev/null
+++ b/dom/serviceworkers/test/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);
+ };
+</script>
+
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/notificationclose.js b/dom/serviceworkers/test/notificationclose.js
new file mode 100644
index 0000000000..17c135a308
--- /dev/null
+++ b/dom/serviceworkers/test/notificationclose.js
@@ -0,0 +1,31 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+//
+onnotificationclose = function (e) {
+ e.waitUntil(
+ (async function () {
+ let windowOpened = true;
+ await clients.openWindow("hello.html").catch(err => {
+ windowOpened = false;
+ });
+
+ 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,
+ windowOpened,
+ });
+ });
+ });
+ })()
+ );
+};
diff --git a/dom/serviceworkers/test/notify_loaded.js b/dom/serviceworkers/test/notify_loaded.js
new file mode 100644
index 0000000000..3bf001abd6
--- /dev/null
+++ b/dom/serviceworkers/test/notify_loaded.js
@@ -0,0 +1 @@
+parent.postMessage("SCRIPT_LOADED", "*");
diff --git a/dom/serviceworkers/test/onmessageerror_worker.js b/dom/serviceworkers/test/onmessageerror_worker.js
new file mode 100644
index 0000000000..4932be6de0
--- /dev/null
+++ b/dom/serviceworkers/test/onmessageerror_worker.js
@@ -0,0 +1,55 @@
+async function getSwContainer() {
+ const clients = await self.clients.matchAll({
+ type: "window",
+ includeUncontrolled: true,
+ });
+
+ for (let client of clients) {
+ if (client.url.endsWith("test_onmessageerror.html")) {
+ return client;
+ }
+ }
+ return undefined;
+}
+
+self.addEventListener("message", async e => {
+ const config = e.data;
+ const swContainer = await getSwContainer();
+
+ if (config == "send-bad-message") {
+ const serializable = true;
+ const deserializable = false;
+
+ swContainer.postMessage(
+ new StructuredCloneTester(serializable, deserializable)
+ );
+
+ return;
+ }
+
+ if (!config.serializable) {
+ swContainer.postMessage({
+ result: "Error",
+ reason: "Service Worker received an unserializable object",
+ });
+
+ return;
+ }
+
+ if (!config.deserializable) {
+ swContainer.postMessage({
+ result: "Error",
+ reason:
+ "Service Worker received (and deserialized) an un-deserializable object",
+ });
+
+ return;
+ }
+
+ swContainer.postMessage({ received: "message" });
+});
+
+self.addEventListener("messageerror", async () => {
+ const swContainer = await getSwContainer();
+ swContainer.postMessage({ received: "messageerror" });
+});
diff --git a/dom/serviceworkers/test/opaque_intercept_worker.js b/dom/serviceworkers/test/opaque_intercept_worker.js
new file mode 100644
index 0000000000..8c0882ad11
--- /dev/null
+++ b/dom/serviceworkers/test/opaque_intercept_worker.js
@@ -0,0 +1,40 @@
+var name = "opaqueInterceptCache";
+
+// Cross origin request to ensure that an opaque response is used
+var prefix = "http://example.com/tests/dom/serviceworkers/test/";
+
+var testReady = new Promise(resolve => {
+ self.addEventListener(
+ "message",
+ m => {
+ resolve();
+ },
+ { once: true }
+ );
+});
+
+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), testReady]).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/serviceworkers/test/openWindow_worker.js b/dom/serviceworkers/test/openWindow_worker.js
new file mode 100644
index 0000000000..ffaad009be
--- /dev/null
+++ b/dom/serviceworkers/test/openWindow_worker.js
@@ -0,0 +1,178 @@
+// the worker won't shut down between events because we increased
+// the timeout values.
+var client;
+var window_count = 0;
+var expected_window_count = 9;
+var isolated_window_count = 0;
+var expected_isolated_window_count = 2;
+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 for url ${url}, got ${e}`,
+ });
+ 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().includes(throwType),
+ 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)
+ );
+ event.waitUntil(
+ Promise.all(promises).then(function (e) {
+ client.postMessage(results);
+ })
+ );
+ }
+
+ if (event.data == "NEW_WINDOW" || event.data == "NEW_ISOLATED_WINDOW") {
+ window_count += 1;
+ if (event.data == "NEW_ISOLATED_WINDOW") {
+ isolated_window_count += 1;
+ }
+ if (window_count == expected_window_count) {
+ resolve_got_all_windows();
+ }
+ }
+
+ if (event.data == "CHECK_NUMBER_OF_WINDOWS") {
+ event.waitUntil(
+ 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. ${cl.length} == ${expected_window_count}`,
+ },
+ {
+ result: isolated_window_count == expected_isolated_window_count,
+ message: `The number of isolated windows is correct. ${isolated_window_count} == ${expected_isolated_window_count}`,
+ },
+ ]);
+ 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/serviceworkers/test/redirect.sjs?";
+ var redirect_xorigin =
+ "http://example.com/tests/dom/serviceworkers/test/redirect.sjs?";
+ var same_origin =
+ "http://mochi.test:8888/tests/dom/serviceworkers/test/open_window/client.sjs";
+ var different_origin =
+ "http://example.com/tests/dom/serviceworkers/test/open_window/client.sjs";
+
+ 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.sjs", null, { url: same_origin }, results)
+ );
+
+ // redirect tests
+ promises.push(
+ testForUrl(
+ redirect + "open_window/client.sjs",
+ null,
+ { url: same_origin },
+ results
+ )
+ );
+ promises.push(testForUrl(redirect + different_origin, null, null, results));
+
+ promises.push(
+ testForUrl(redirect_xorigin + "open_window/client.sjs", null, null, results)
+ );
+ promises.push(
+ testForUrl(
+ redirect_xorigin + same_origin,
+ null,
+ { url: same_origin },
+ results
+ )
+ );
+
+ // coop+coep tests
+ promises.push(
+ testForUrl(
+ same_origin + "?crossOriginIsolated=true",
+ null,
+ { url: same_origin + "?crossOriginIsolated=true" },
+ results
+ )
+ );
+ promises.push(
+ testForUrl(
+ different_origin + "?crossOriginIsolated=true",
+ null,
+ null,
+ results
+ )
+ );
+
+ e.waitUntil(
+ Promise.all(promises).then(function () {
+ client.postMessage(results);
+ })
+ );
+};
diff --git a/dom/serviceworkers/test/open_window/client.sjs b/dom/serviceworkers/test/open_window/client.sjs
new file mode 100644
index 0000000000..bfb566ead0
--- /dev/null
+++ b/dom/serviceworkers/test/open_window/client.sjs
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const RESPONSE = `
+<!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>
+<h1>client.sjs</h1>
+<script class="testbody" type="text/javascript">
+
+ window.onload = function() {
+ if (document.domain === "mochi.test") {
+ 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();
+ }
+
+ let message = window.crossOriginIsolated ? "NEW_ISOLATED_WINDOW" : "NEW_WINDOW";
+ navigator.serviceWorker.controller.postMessage(message);
+ })
+ } 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/serviceworkers/test/open_window/client.sjs";
+ document.body.appendChild(iframe);
+ }
+ }
+
+</script>
+</pre>
+</body>
+</html>
+`;
+
+function handleRequest(request, response) {
+ let query = new URLSearchParams(request.queryString);
+
+ // If the request has been marked to be isolated with COOP+COEP, set the appropriate headers.
+ if (query.get("crossOriginIsolated") == "true") {
+ response.setHeader("Cross-Origin-Opener-Policy", "same-origin", false);
+ }
+
+ // Always set the COEP and CORP headers, so that this document can be framed
+ // by a document which has also set COEP to require-corp.
+ response.setHeader("Cross-Origin-Embedder-Policy", "require-corp", false);
+ response.setHeader("Cross-Origin-Resource-Policy", "cross-origin", false);
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(RESPONSE);
+}
diff --git a/dom/serviceworkers/test/page_post_controlled.html b/dom/serviceworkers/test/page_post_controlled.html
new file mode 100644
index 0000000000..27694c0027
--- /dev/null
+++ b/dom/serviceworkers/test/page_post_controlled.html
@@ -0,0 +1,27 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+</head>
+<body>
+<script type="text/javascript">
+ window.parent.postMessage({
+ controlled: !!navigator.serviceWorker.controller
+ }, "*");
+
+ addEventListener("message", e => {
+ if (e.data == "create nested iframe") {
+ const iframe = document.createElement('iframe');
+ document.body.appendChild(iframe);
+ iframe.src = location.href;
+ } else {
+ window.parent.postMessage(e.data, "*");
+ }
+ });
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/parse_error_worker.js b/dom/serviceworkers/test/parse_error_worker.js
new file mode 100644
index 0000000000..b6a8ef0a1a
--- /dev/null
+++ b/dom/serviceworkers/test/parse_error_worker.js
@@ -0,0 +1,2 @@
+// intentional parse error.
+var foo = {;
diff --git a/dom/serviceworkers/test/performance/intercepted.txt b/dom/serviceworkers/test/performance/intercepted.txt
new file mode 100644
index 0000000000..87c7a8efe7
--- /dev/null
+++ b/dom/serviceworkers/test/performance/intercepted.txt
@@ -0,0 +1 @@
+intercepted
diff --git a/dom/serviceworkers/test/performance/perftest.toml b/dom/serviceworkers/test/performance/perftest.toml
new file mode 100644
index 0000000000..6a7e5928be
--- /dev/null
+++ b/dom/serviceworkers/test/performance/perftest.toml
@@ -0,0 +1,14 @@
+[DEFAULT]
+support-files = [
+ "intercepted.txt",
+ "perfutils.js",
+ "sw_cacher.js",
+ "sw_empty.js",
+ "sw_intercept_target.js",
+ "target.txt",
+ "time_fetch.html",
+]
+
+["test_caching.html"]
+["test_fetch.html"]
+["test_registration.html"]
diff --git a/dom/serviceworkers/test/performance/perfutils.js b/dom/serviceworkers/test/performance/perfutils.js
new file mode 100644
index 0000000000..d7edbe2fe7
--- /dev/null
+++ b/dom/serviceworkers/test/performance/perfutils.js
@@ -0,0 +1,46 @@
+"use strict";
+
+/**
+ * Given a map from test names to arrays of results, report perfherder metrics
+ * and log full results.
+ */
+function reportMetrics(journal) {
+ let metrics = {};
+ let text = "\nResults (ms)\n";
+
+ const names = Object.keys(journal);
+ const prefixLen = 1 + Math.max(...names.map(str => str.length));
+
+ for (const name in journal) {
+ const med = median(journal[name]);
+ text += (name + ":").padEnd(prefixLen, " ") + stringify(journal[name]);
+ text += " median " + med + "\n";
+ metrics[name] = med;
+ }
+
+ dump(text);
+ info("perfMetrics", JSON.stringify(metrics));
+}
+
+function median(arr) {
+ arr = [...arr].sort((a, b) => a - b);
+ const mid = Math.floor(arr.length / 2);
+
+ if (arr.length % 2) {
+ return arr[mid];
+ }
+
+ return (arr[mid - 1] + arr[mid]) / 2;
+}
+
+function stringify(arr) {
+ function pad(num) {
+ let s = num.toString().padStart(5, " ");
+ if (s[0] != " ") {
+ s = " " + s;
+ }
+ return s;
+ }
+
+ return arr.reduce((acc, elem) => acc + pad(elem), "");
+}
diff --git a/dom/serviceworkers/test/performance/sw_cacher.js b/dom/serviceworkers/test/performance/sw_cacher.js
new file mode 100644
index 0000000000..5a441ef785
--- /dev/null
+++ b/dom/serviceworkers/test/performance/sw_cacher.js
@@ -0,0 +1,18 @@
+"use strict";
+
+oninstall = function (event) {
+ event.waitUntil(
+ caches.open("perftest").then(function (cache) {
+ return cache.put("cached.txt", new Response("cached.txt"));
+ })
+ );
+};
+
+onfetch = function (event) {
+ if (event.request.url.endsWith("/cached.txt")) {
+ var p = caches.match("cached.txt", { cacheName: "perftest" });
+ event.respondWith(p);
+ } else if (event.request.url.endsWith("/uncached.txt")) {
+ event.respondWith(new Response("uncached.txt"));
+ }
+};
diff --git a/dom/serviceworkers/test/performance/sw_empty.js b/dom/serviceworkers/test/performance/sw_empty.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/serviceworkers/test/performance/sw_empty.js
diff --git a/dom/serviceworkers/test/performance/sw_intercept_target.js b/dom/serviceworkers/test/performance/sw_intercept_target.js
new file mode 100644
index 0000000000..47b3853978
--- /dev/null
+++ b/dom/serviceworkers/test/performance/sw_intercept_target.js
@@ -0,0 +1,7 @@
+"use strict";
+
+onfetch = function (event) {
+ if (event.request.url.indexOf("target.txt") != -1) {
+ event.respondWith(fetch("intercepted.txt"));
+ }
+};
diff --git a/dom/serviceworkers/test/performance/target.txt b/dom/serviceworkers/test/performance/target.txt
new file mode 100644
index 0000000000..eb5a316cbd
--- /dev/null
+++ b/dom/serviceworkers/test/performance/target.txt
@@ -0,0 +1 @@
+target
diff --git a/dom/serviceworkers/test/performance/test_caching.html b/dom/serviceworkers/test/performance/test_caching.html
new file mode 100644
index 0000000000..cd6d4cf493
--- /dev/null
+++ b/dom/serviceworkers/test/performance/test_caching.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Service worker performance test: caching</title>
+</head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="../utils.js"></script>
+<script src="perfutils.js"></script>
+<script>
+
+ "use strict";
+
+ const NO_CACHE = "No cache";
+ const CACHED = "Cached";
+ const NO_CACHE_AGAIN = "No cache again";
+
+ var journal = {};
+ journal[NO_CACHE] = [];
+ journal[CACHED] = [];
+ journal[NO_CACHE_AGAIN] = [];
+
+ const ITERATIONS = 10;
+
+ var perfMetadata = {
+ owner: "DOM LWS",
+ name: "Service Worker Caching",
+ description: "Test service worker caching.",
+ options: {
+ default: {
+ perfherder: true,
+ perfherder_metrics: [
+ // Here, we can't use the constants defined above because perfherder
+ // grabs data from the parse tree.
+ { name: "No cache", unit: "ms", shouldAlert: true },
+ { name: "Cached", unit: "ms", shouldAlert: true },
+ { name: "No cache again", unit: "ms", shouldAlert: true },
+ ],
+ verbose: true,
+ manifest: "perftest.toml",
+ manifest_flavor: "plain",
+ },
+ },
+ };
+
+ add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.serviceWorkers.testing.enabled", true]]
+ });
+ });
+
+ function create_iframe(url) {
+ return new Promise(function(res) {
+ let iframe = document.createElement("iframe");
+ iframe.src = url;
+ iframe.onload = function() { res(iframe) }
+ document.body.appendChild(iframe);
+ });
+ }
+
+ async function time_fetch(journal, iframe, filename) {
+ for (let i = 0; i < ITERATIONS; i++) {
+ let result = await iframe.contentWindow.time_fetch(filename);
+ is(result.status, 200);
+ is(result.data, filename);
+ journal.push(result.elapsed_ms);
+ }
+ }
+
+ add_task(async () => {
+ let reg = await navigator.serviceWorker.register("sw_cacher.js");
+ await waitForState(reg.installing, "activated");
+
+ let iframe = await create_iframe("time_fetch.html");
+
+ await time_fetch(journal[NO_CACHE], iframe, "uncached.txt");
+ await time_fetch(journal[CACHED], iframe, "cached.txt");
+ await time_fetch(journal[NO_CACHE_AGAIN], iframe, "uncached.txt");
+
+ await reg.unregister();
+ });
+
+ add_task(() => {
+ reportMetrics(journal);
+ });
+
+</script>
+<body>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/performance/test_fetch.html b/dom/serviceworkers/test/performance/test_fetch.html
new file mode 100644
index 0000000000..29dd65b595
--- /dev/null
+++ b/dom/serviceworkers/test/performance/test_fetch.html
@@ -0,0 +1,168 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Service worker performance test: fetch</title>
+</head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="../utils.js"></script>
+<script src="perfutils.js"></script>
+<script>
+
+ "use strict";
+
+ const COLD_FETCH = "Cold fetch";
+ const UNDISTURBED_FETCH = "Undisturbed fetch";
+ const INTERCEPTED_FETCH = "Intercepted fetch";
+ const LIBERATED_FETCH = "Liberated fetch";
+ const UNDISTURBED_XHR = "Undisturbed XHR";
+ const INTERCEPTED_XHR = "Intercepted XHR";
+ const LIBERATED_XHR = "Liberated XHR";
+
+ var journal = {};
+ journal[COLD_FETCH] = [];
+ journal[UNDISTURBED_FETCH] = [];
+ journal[INTERCEPTED_FETCH] = [];
+ journal[LIBERATED_FETCH] = [];
+ journal[UNDISTURBED_XHR] = [];
+ journal[INTERCEPTED_XHR] = [];
+ journal[LIBERATED_XHR] = [];
+
+ const ITERATIONS = 10;
+
+ var perfMetadata = {
+ owner: "DOM LWS",
+ name: "Service Worker Fetch",
+ description: "Test cold and warm fetches.",
+ options: {
+ default: {
+ perfherder: true,
+ perfherder_metrics: [
+ // Here, we can't use the constants defined above because perfherder
+ // grabs data from the parse tree.
+ { name: "Cold fetch", unit: "ms", shouldAlert: true },
+ { name: "Undisturbed fetch", unit: "ms", shouldAlert: true },
+ { name: "Intercepted fetch", unit: "ms", shouldAlert: true },
+ { name: "Liberated fetch", unit: "ms", shouldAlert: true },
+ { name: "Undisturbed XHR", unit: "ms", shouldAlert: true },
+ { name: "Intercepted XHR", unit: "ms", shouldAlert: true },
+ { name: "Liberated XHR", unit: "ms", shouldAlert: true },
+ ],
+ verbose: true,
+ manifest: "perftest.toml",
+ manifest_flavor: "plain",
+ },
+ },
+ };
+
+ function create_iframe(url) {
+ return new Promise(function(res) {
+ let iframe = document.createElement("iframe");
+ iframe.src = url;
+ iframe.onload = function() { res(iframe) }
+ document.body.appendChild(iframe);
+ });
+ }
+
+ add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.serviceWorkers.testing.enabled", true]]
+ });
+ });
+
+ /**
+ * Time fetch from a fresh service worker.
+ */
+ add_task(async () => {
+ for (let i = 0; i < ITERATIONS; i++) {
+ let reg = await navigator.serviceWorker.register("sw_intercept_target.js");
+ await waitForState(reg.installing, "activated");
+
+ let iframe = await create_iframe("time_fetch.html");
+
+ let result = await iframe.contentWindow.time_fetch("target.txt");
+ is(result.status, 200);
+ is(result.data, "intercepted\n");
+ journal[COLD_FETCH].push(result.elapsed_ms);
+
+ ok(document.body.removeChild(iframe), "Failed to remove child iframe");
+
+ await reg.unregister();
+ }
+ });
+
+ /**
+ * Time unintercepted fetch, intercepted fetch, then unintercepted
+ * fetch again.
+ */
+ add_task(async () => {
+ let reg = await navigator.serviceWorker.register("sw_intercept_target.js");
+ await waitForState(reg.installing, "activated");
+
+ async function measure(journal, sw_enabled) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.serviceWorkers.enabled", sw_enabled]]
+ });
+
+ let iframe = await create_iframe("time_fetch.html");
+
+ for (let i = 0; i < ITERATIONS; i++) {
+ let result = await iframe.contentWindow.time_fetch("target.txt");
+ is(result.status, 200);
+ is(result.data, sw_enabled ? "intercepted\n" : "target\n");
+ journal.push(result.elapsed_ms);
+ }
+
+ ok(document.body.removeChild(iframe), "Failed to remove child iframe");
+
+ await SpecialPowers.popPrefEnv();
+ }
+
+ await measure(journal[UNDISTURBED_FETCH], false);
+ await measure(journal[INTERCEPTED_FETCH], true);
+ await measure(journal[LIBERATED_FETCH], false);
+
+ await reg.unregister();
+ });
+
+ /**
+ * Time unintercepted XHR, intercepted XHR, then unintercepted
+ * XHR again.
+ */
+ add_task(async () => {
+ let reg = await navigator.serviceWorker.register("sw_intercept_target.js");
+ await waitForState(reg.installing, "activated");
+
+ async function measure(journal, sw_enabled) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.serviceWorkers.enabled", sw_enabled]]
+ });
+
+ let iframe = await create_iframe("time_fetch.html");
+
+ for (let i = 0; i < ITERATIONS; i++) {
+ let result = await iframe.contentWindow.time_xhr("target.txt");
+ is(result.status, 200);
+ is(result.data, sw_enabled ? "intercepted\n" : "target\n");
+ journal.push(result.elapsed_ms);
+ }
+
+ ok(document.body.removeChild(iframe), "Failed to remove child iframe");
+
+ await SpecialPowers.popPrefEnv();
+ }
+
+ await measure(journal[UNDISTURBED_XHR], false);
+ await measure(journal[INTERCEPTED_XHR], true);
+ await measure(journal[LIBERATED_XHR], false);
+
+ await reg.unregister();
+ });
+
+ add_task(() => {
+ reportMetrics(journal);
+ });
+
+</script>
+<body>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/performance/test_registration.html b/dom/serviceworkers/test/performance/test_registration.html
new file mode 100644
index 0000000000..d5abbf6775
--- /dev/null
+++ b/dom/serviceworkers/test/performance/test_registration.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Service worker performance test: registration</title>
+</head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="../utils.js"></script>
+<script src="perfutils.js"></script>
+<script>
+
+ "use strict";
+
+ const REGISTRATION = "Registration";
+ const ACTIVATION = "Activation";
+ const UNREGISTRATION = "Unregistration";
+
+ var journal = [];
+ journal[REGISTRATION] = [];
+ journal[ACTIVATION] = [];
+ journal[UNREGISTRATION] = [];
+
+ const ITERATIONS = 10;
+
+ var perfMetadata = {
+ owner: "DOM LWS",
+ name: "Service Worker Registration",
+ description: "Test registration, activation, and unregistration.",
+ options: {
+ default: {
+ perfherder: true,
+ perfherder_metrics: [
+ // Here, we can't use the constants defined above because perfherder
+ // grabs data from the parse tree.
+ { name: "Registration", unit: "ms", shouldAlert: true },
+ { name: "Activation", unit: "ms", shouldAlert: true },
+ { name: "Unregistration", unit: "ms", shouldAlert: true },
+ ],
+ verbose: true,
+ manifest: "perftest.toml",
+ manifest_flavor: "plain",
+ },
+ },
+ };
+
+ function create_iframe(url) {
+ return new Promise(function(res) {
+ let iframe = document.createElement("iframe");
+ iframe.src = url;
+ iframe.onload = function() { res(iframe) }
+ document.body.appendChild(iframe);
+ });
+ }
+
+ add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.serviceWorkers.testing.enabled", true]]
+ });
+
+ async function measure() {
+ let begin_ts = performance.now();
+ let reg = await navigator.serviceWorker.register("sw_empty.js");
+ let reg_ts = performance.now();
+ await waitForState(reg.installing, "activated");
+ let act_ts = performance.now();
+ await reg.unregister();
+ let unreg_ts = performance.now();
+
+ journal[REGISTRATION].push(reg_ts - begin_ts);
+ journal[ACTIVATION].push(act_ts - reg_ts);
+ journal[UNREGISTRATION].push(unreg_ts - act_ts);
+ }
+
+ for (let i = 0; i < ITERATIONS; i++) {
+ await measure();
+ }
+
+ await SpecialPowers.popPrefEnv();
+
+ ok(true);
+ });
+
+ add_task(() => {
+ reportMetrics(journal);
+ });
+
+</script>
+<body>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/performance/time_fetch.html b/dom/serviceworkers/test/performance/time_fetch.html
new file mode 100644
index 0000000000..a771d4889f
--- /dev/null
+++ b/dom/serviceworkers/test/performance/time_fetch.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script>
+
+ "use strict";
+
+ async function time_fetch(url) {
+ let start = performance.now();
+ let res = await fetch(url);
+ let elapsed = performance.now() - start;
+
+ return {
+ elapsed_ms : elapsed,
+ status : res.status,
+ data : await res.text()
+ };
+ }
+
+ async function time_xhr(url) {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", url, false);
+ let start = performance.now();
+ xhr.send();
+ let elapsed = performance.now() - start;
+
+ return {
+ elapsed_ms : elapsed,
+ status : xhr.status,
+ data : xhr.responseText
+ }
+ }
+
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/pref/fetch_nonexistent_file.html b/dom/serviceworkers/test/pref/fetch_nonexistent_file.html
new file mode 100644
index 0000000000..84c3a1398d
--- /dev/null
+++ b/dom/serviceworkers/test/pref/fetch_nonexistent_file.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script>
+
+ async function fetch_status() {
+ let response = await fetch('this_file_does_not_exist.txt');
+ return response.status;
+ }
+
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/pref/intercept_nonexistent_file_sw.js b/dom/serviceworkers/test/pref/intercept_nonexistent_file_sw.js
new file mode 100644
index 0000000000..ab0f1d572d
--- /dev/null
+++ b/dom/serviceworkers/test/pref/intercept_nonexistent_file_sw.js
@@ -0,0 +1,5 @@
+onfetch = function (e) {
+ if (e.request.url.match(/this_file_does_not_exist.txt$/)) {
+ e.respondWith(new Response("intercepted"));
+ }
+};
diff --git a/dom/serviceworkers/test/redirect.sjs b/dom/serviceworkers/test/redirect.sjs
new file mode 100644
index 0000000000..43fec90b5a
--- /dev/null
+++ b/dom/serviceworkers/test/redirect.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ response.setHeader("Location", request.queryString, false);
+}
diff --git a/dom/serviceworkers/test/redirect_post.sjs b/dom/serviceworkers/test/redirect_post.sjs
new file mode 100644
index 0000000000..5483138d2b
--- /dev/null
+++ b/dom/serviceworkers/test/redirect_post.sjs
@@ -0,0 +1,39 @@
+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/serviceworkers/test/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/serviceworkers/test/redirect_serviceworker.sjs b/dom/serviceworkers/test/redirect_serviceworker.sjs
new file mode 100644
index 0000000000..858e6d4824
--- /dev/null
+++ b/dom/serviceworkers/test/redirect_serviceworker.sjs
@@ -0,0 +1,7 @@
+function handleRequest(request, response) {
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader(
+ "Location",
+ "http://mochi.test:8888/tests/dom/serviceworkers/test/worker.js"
+ );
+}
diff --git a/dom/serviceworkers/test/register_https.html b/dom/serviceworkers/test/register_https.html
new file mode 100644
index 0000000000..572c7ce6b8
--- /dev/null
+++ b/dom/serviceworkers/test/register_https.html
@@ -0,0 +1,15 @@
+<!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");
+ok(!window.isSecureContext, "Should not be secure context");
+ok(!("serviceWorker" in navigator), "ServiceWorkerContainer not availalble in insecure context");
+done();
+</script>
diff --git a/dom/serviceworkers/test/sanitize/example_check_and_unregister.html b/dom/serviceworkers/test/sanitize/example_check_and_unregister.html
new file mode 100644
index 0000000000..8553e442d6
--- /dev/null
+++ b/dom/serviceworkers/test/sanitize/example_check_and_unregister.html
@@ -0,0 +1,22 @@
+<!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/serviceworkers/test/sanitize/frame.html b/dom/serviceworkers/test/sanitize/frame.html
new file mode 100644
index 0000000000..b4bf7a1ff1
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/sanitize/register.html b/dom/serviceworkers/test/sanitize/register.html
new file mode 100644
index 0000000000..4ae74bec11
--- /dev/null
+++ b/dom/serviceworkers/test/sanitize/register.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script>
+ function done() {
+ parent.postMessage('', '*');
+ }
+
+ navigator.serviceWorker.ready.then(done);
+ navigator.serviceWorker.register("../sanitize_worker.js", {scope: "."});
+</script>
diff --git a/dom/serviceworkers/test/sanitize_worker.js b/dom/serviceworkers/test/sanitize_worker.js
new file mode 100644
index 0000000000..920eb7a4f7
--- /dev/null
+++ b/dom/serviceworkers/test/sanitize_worker.js
@@ -0,0 +1,5 @@
+onfetch = function (e) {
+ if (e.request.url.includes("intercept-this")) {
+ e.respondWith(new Response("intercepted"));
+ }
+};
diff --git a/dom/serviceworkers/test/scope/scope_worker.js b/dom/serviceworkers/test/scope/scope_worker.js
new file mode 100644
index 0000000000..4164e7a244
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/script_file_upload.js b/dom/serviceworkers/test/script_file_upload.js
new file mode 100644
index 0000000000..5e9aeb6098
--- /dev/null
+++ b/dom/serviceworkers/test/script_file_upload.js
@@ -0,0 +1,16 @@
+/* eslint-env mozilla/chrome-script */
+
+// eslint-disable-next-line mozilla/reject-importGlobalProperties
+Cu.importGlobalProperties(["File"]);
+
+addMessageListener("file.open", function (e) {
+ var testFile = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ testFile.append("prefs.js");
+
+ File.createFromNsIFile(testFile).then(function (file) {
+ sendAsyncMessage("file.opened", { file });
+ });
+});
diff --git a/dom/serviceworkers/test/self_update_worker.sjs b/dom/serviceworkers/test/self_update_worker.sjs
new file mode 100644
index 0000000000..8081b20afd
--- /dev/null
+++ b/dom/serviceworkers/test/self_update_worker.sjs
@@ -0,0 +1,42 @@
+/* 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_BODY = `
+onactivate = function(event) {
+ let promise = clients.matchAll({includeUncontrolled: true}).then(function(clients) {
+ for (i = 0; i < clients.length; i++) {
+ clients[i].postMessage({version: version});
+ }
+ }).then(function() {
+ return self.registration.update();
+ });
+ event.waitUntil(promise);
+};
+`;
+
+function handleRequest(request, response) {
+ if (request.queryString == "clearcounter") {
+ setState("count", "1");
+ response.write("ok");
+ return;
+ }
+
+ let count = getState("count");
+ if (count === "") {
+ count = 1;
+ } else {
+ count = parseInt(count);
+ }
+
+ let worker = "var version = " + count + ";\n";
+ worker = worker + WORKER_BODY;
+
+ // This header is necessary for making this script able to be loaded.
+ response.setHeader("Content-Type", "application/javascript");
+
+ // If this is the first request, return the first source.
+ response.write(worker);
+ setState("count", "" + (count + 1));
+}
diff --git a/dom/serviceworkers/test/server_file_upload.sjs b/dom/serviceworkers/test/server_file_upload.sjs
new file mode 100644
index 0000000000..a2f960af94
--- /dev/null
+++ b/dom/serviceworkers/test/server_file_upload.sjs
@@ -0,0 +1,22 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+const BinaryOutputStream = CC(
+ "@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream"
+);
+
+function handleRequest(request, response) {
+ var bodyStream = new BinaryInputStream(request.bodyInputStream);
+ var bodyBytes = [];
+ while ((bodyAvail = bodyStream.available()) > 0) {
+ Array.prototype.push.apply(bodyBytes, bodyStream.readByteArray(bodyAvail));
+ }
+
+ var bos = new BinaryOutputStream(response.bodyOutputStream);
+ bos.writeByteArray(bodyBytes, bodyBytes.length);
+}
diff --git a/dom/serviceworkers/test/service_worker.js b/dom/serviceworkers/test/service_worker.js
new file mode 100644
index 0000000000..90cb97ef82
--- /dev/null
+++ b/dom/serviceworkers/test/service_worker.js
@@ -0,0 +1,9 @@
+onmessage = function (e) {
+ self.clients.matchAll().then(function (res) {
+ if (!res.length) {
+ dump("Error: no clients are currently controlled.\n");
+ return;
+ }
+ res[0].postMessage(indexedDB ? { available: true } : { available: false });
+ });
+};
diff --git a/dom/serviceworkers/test/service_worker_client.html b/dom/serviceworkers/test/service_worker_client.html
new file mode 100644
index 0000000000..c1c98eaabb
--- /dev/null
+++ b/dom/serviceworkers/test/service_worker_client.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>controlled page</title>
+<script class="testbody" type="text/javascript">
+ if (!parent) {
+ info("service_worker_client.html should not be launched directly!");
+ }
+
+ window.onload = function() {
+ navigator.serviceWorker.onmessage = function(msg) {
+ // Forward messages coming from the service worker to the test page.
+ parent.postMessage(msg.data, "*");
+ };
+ navigator.serviceWorker.ready.then(function(swr) {
+ parent.postMessage("READY", "*");
+ });
+ }
+</script>
+
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/serviceworker.html b/dom/serviceworkers/test/serviceworker.html
new file mode 100644
index 0000000000..11edd001a2
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/serviceworker_not_sharedworker.js b/dom/serviceworkers/test/serviceworker_not_sharedworker.js
new file mode 100644
index 0000000000..da0c98aea3
--- /dev/null
+++ b/dom/serviceworkers/test/serviceworker_not_sharedworker.js
@@ -0,0 +1,20 @@
+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/serviceworkers/test/serviceworker_wrapper.js b/dom/serviceworkers/test/serviceworker_wrapper.js
new file mode 100644
index 0000000000..a1538f43c4
--- /dev/null
+++ b/dom/serviceworkers/test/serviceworker_wrapper.js
@@ -0,0 +1,92 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+//
+// ServiceWorker equivalent of worker_wrapper.js.
+
+let 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 workerTestGetHelperData(cb) {
+ addEventListener("message", function workerTestGetHelperDataCB(e) {
+ if (e.data.type !== "returnHelperData") {
+ return;
+ }
+ removeEventListener("message", workerTestGetHelperDataCB);
+ cb(e.data.result);
+ });
+ client.postMessage({
+ type: "getHelperData",
+ });
+}
+
+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",
+ });
+}
+
+let completeInstall;
+
+addEventListener("message", function workerWrapperOnMessage(e) {
+ removeEventListener("message", workerWrapperOnMessage);
+ var data = e.data;
+ self.clients.matchAll({ includeUncontrolled: true }).then(function (clients) {
+ for (var i = 0; i < clients.length; ++i) {
+ if (clients[i].url.includes("message_receiver.html")) {
+ client = clients[i];
+ break;
+ }
+ }
+ try {
+ importScripts(data.script);
+ } catch (ex) {
+ client.postMessage({
+ type: "status",
+ status: false,
+ msg:
+ "worker failed to import " + data.script + "; error: " + ex.message,
+ });
+ }
+ completeInstall();
+ });
+});
+
+addEventListener("install", e => {
+ e.waitUntil(new Promise(resolve => (completeInstall = resolve)));
+});
diff --git a/dom/serviceworkers/test/serviceworkerinfo_iframe.html b/dom/serviceworkers/test/serviceworkerinfo_iframe.html
new file mode 100644
index 0000000000..24103d1757
--- /dev/null
+++ b/dom/serviceworkers/test/serviceworkerinfo_iframe.html
@@ -0,0 +1,27 @@
+<!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",
+ { updateViaCache: 'all' });
+ window.onmessage = function (e) {
+ if (e.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/serviceworkers/test/serviceworkermanager_iframe.html b/dom/serviceworkers/test/serviceworkermanager_iframe.html
new file mode 100644
index 0000000000..4ea21010cb
--- /dev/null
+++ b/dom/serviceworkers/test/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 (event1) {
+ if (event1.data !== "register") {
+ return;
+ }
+ promise = promise.then(function (registration) {
+ return navigator.serviceWorker.register("worker2.js");
+ });
+ window.onmessage = function (event2) {
+ if (event2.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/serviceworkers/test/serviceworkerregistrationinfo_iframe.html b/dom/serviceworkers/test/serviceworkerregistrationinfo_iframe.html
new file mode 100644
index 0000000000..8f382cf0dc
--- /dev/null
+++ b/dom/serviceworkers/test/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 (e) {
+ if (e.data === "register") {
+ promise.then(function() {
+ return navigator.serviceWorker.register("worker2.js")
+ .then(function(registration) {
+ reg = registration;
+ });
+ });
+ } else if (e.data === "unregister") {
+ reg.unregister();
+ }
+ };
+ };
+ </script>
+ </head>
+ <body>
+ This is a test page.
+ </body>
+<html>
diff --git a/dom/serviceworkers/test/sharedWorker_fetch.js b/dom/serviceworkers/test/sharedWorker_fetch.js
new file mode 100644
index 0000000000..89618c4e83
--- /dev/null
+++ b/dom/serviceworkers/test/sharedWorker_fetch.js
@@ -0,0 +1,30 @@
+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 (msg) {
+ if (msg.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/serviceworkers/test/simple_fetch_worker.js b/dom/serviceworkers/test/simple_fetch_worker.js
new file mode 100644
index 0000000000..09c82011e5
--- /dev/null
+++ b/dom/serviceworkers/test/simple_fetch_worker.js
@@ -0,0 +1,18 @@
+// A simple worker script that forward intercepted url to the controlled window.
+
+function responseMsg(msg) {
+ self.clients
+ .matchAll({
+ includeUncontrolled: true,
+ type: "window",
+ })
+ .then(clients => {
+ if (clients && clients.length) {
+ clients[0].postMessage(msg);
+ }
+ });
+}
+
+onfetch = function (e) {
+ responseMsg(e.request.url);
+};
diff --git a/dom/serviceworkers/test/simpleregister/index.html b/dom/serviceworkers/test/simpleregister/index.html
new file mode 100644
index 0000000000..99e4fe3f23
--- /dev/null
+++ b/dom/serviceworkers/test/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,
+ 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/serviceworkers/test/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/serviceworkers/test/simpleregister/ready.html b/dom/serviceworkers/test/simpleregister/ready.html
new file mode 100644
index 0000000000..6bc163e5f4
--- /dev/null
+++ b/dom/serviceworkers/test/simpleregister/ready.html
@@ -0,0 +1,14 @@
+<html>
+ <head></head>
+ <body>
+ <script type="text/javascript">
+
+ window.addEventListener('message', function(evt) {
+ navigator.serviceWorker.ready.then(function() {
+ evt.ports[0].postMessage("WOW!");
+ });
+ });
+
+ </script>
+ </body>
+</html>
diff --git a/dom/serviceworkers/test/skip_waiting_installed_worker.js b/dom/serviceworkers/test/skip_waiting_installed_worker.js
new file mode 100644
index 0000000000..a142576b9d
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/skip_waiting_scope/index.html b/dom/serviceworkers/test/skip_waiting_scope/index.html
new file mode 100644
index 0000000000..2b480d8707
--- /dev/null
+++ b/dom/serviceworkers/test/skip_waiting_scope/index.html
@@ -0,0 +1,33 @@
+<!--
+ 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 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.oncontrollerchange = function() {
+ parent.postMessage({
+ event: "controllerchange",
+ controllerScriptURL: navigator.serviceWorker.controller &&
+ navigator.serviceWorker.controller.scriptURL
+ }, "*");
+ }
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/source_message_posting_worker.js b/dom/serviceworkers/test/source_message_posting_worker.js
new file mode 100644
index 0000000000..8ca6246c51
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/storage_recovery_worker.sjs b/dom/serviceworkers/test/storage_recovery_worker.sjs
new file mode 100644
index 0000000000..9c9ce6a8d7
--- /dev/null
+++ b/dom/serviceworkers/test/storage_recovery_worker.sjs
@@ -0,0 +1,23 @@
+const BASE_URI = "http://mochi.test:8888/browser/dom/serviceworkers/test/";
+
+function handleRequest(request, response) {
+ let redirect = getState("redirect");
+ setState("redirect", "false");
+
+ if (request.queryString.includes("set-redirect")) {
+ setState("redirect", "true");
+ } else if (request.queryString.includes("clear-redirect")) {
+ setState("redirect", "false");
+ }
+
+ response.setHeader("Cache-Control", "no-store");
+
+ if (redirect === "true") {
+ response.setStatusLine(request.httpVersion, 307, "Moved Temporarily");
+ response.setHeader("Location", BASE_URI + "empty.js");
+ return;
+ }
+
+ response.setHeader("Content-Type", "application/javascript");
+ response.write("");
+}
diff --git a/dom/serviceworkers/test/streamfilter_server.sjs b/dom/serviceworkers/test/streamfilter_server.sjs
new file mode 100644
index 0000000000..8adf9d2eaf
--- /dev/null
+++ b/dom/serviceworkers/test/streamfilter_server.sjs
@@ -0,0 +1,7 @@
+function handleRequest(request, response) {
+ const searchParams = new URLSearchParams(request.queryString);
+
+ if (searchParams.get("syntheticResponse") === "0") {
+ response.write(String(searchParams));
+ }
+}
diff --git a/dom/serviceworkers/test/streamfilter_worker.js b/dom/serviceworkers/test/streamfilter_worker.js
new file mode 100644
index 0000000000..03a0f0a933
--- /dev/null
+++ b/dom/serviceworkers/test/streamfilter_worker.js
@@ -0,0 +1,9 @@
+onactivate = e => e.waitUntil(clients.claim());
+
+onfetch = e => {
+ const searchParams = new URL(e.request.url).searchParams;
+
+ if (searchParams.get("syntheticResponse") === "1") {
+ e.respondWith(new Response(String(searchParams)));
+ }
+};
diff --git a/dom/serviceworkers/test/strict_mode_warning.js b/dom/serviceworkers/test/strict_mode_warning.js
new file mode 100644
index 0000000000..4709b2b667
--- /dev/null
+++ b/dom/serviceworkers/test/strict_mode_warning.js
@@ -0,0 +1,5 @@
+function f() {
+ return 1;
+ // eslint-disable-next-line no-unreachable
+ return 2;
+}
diff --git a/dom/serviceworkers/test/sw_bad_mime_type.js b/dom/serviceworkers/test/sw_bad_mime_type.js
new file mode 100644
index 0000000000..f371807db9
--- /dev/null
+++ b/dom/serviceworkers/test/sw_bad_mime_type.js
@@ -0,0 +1 @@
+// I need some contents.
diff --git a/dom/serviceworkers/test/sw_bad_mime_type.js^headers^ b/dom/serviceworkers/test/sw_bad_mime_type.js^headers^
new file mode 100644
index 0000000000..a1f9e38d90
--- /dev/null
+++ b/dom/serviceworkers/test/sw_bad_mime_type.js^headers^
@@ -0,0 +1 @@
+Content-Type: text/plain
diff --git a/dom/serviceworkers/test/sw_clients/file_blob_upload_frame.html b/dom/serviceworkers/test/sw_clients/file_blob_upload_frame.html
new file mode 100644
index 0000000000..60b11c4e57
--- /dev/null
+++ b/dom/serviceworkers/test/sw_clients/file_blob_upload_frame.html
@@ -0,0 +1,76 @@
+<!--
+ 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, index: 5 };
+
+ objectStore = db.transaction('test', 'readwrite').objectStore('test');
+ objectStore.add(data).onsuccess = function(evt1) {
+ var key = evt1.target.result;
+ objectStore = db.transaction('test').objectStore('test');
+ objectStore.get(key).onsuccess = function(evt2) {
+ resolve(evt2.target.result.blob);
+ };
+ };
+ };
+ });
+}
+
+navigator.serviceWorker.ready.then(function() {
+ parent.postMessage({ status: 'READY' }, '*');
+});
+
+var URL = '/tests/dom/serviceworkers/test/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 }, '*');
+ }).catch(function(e) {
+ parent.postMessage({ status: 'ERROR', result: e.toString() }, '*');
+ });
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/sw_clients/navigator.html b/dom/serviceworkers/test/sw_clients/navigator.html
new file mode 100644
index 0000000000..16a4fe9189
--- /dev/null
+++ b/dom/serviceworkers/test/sw_clients/navigator.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>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/serviceworkers/test/sw_clients/refresher.html b/dom/serviceworkers/test/sw_clients/refresher.html
new file mode 100644
index 0000000000..b3c6e00152
--- /dev/null
+++ b/dom/serviceworkers/test/sw_clients/refresher.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>
+ <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/serviceworkers/test/sw_clients/refresher_cached.html b/dom/serviceworkers/test/sw_clients/refresher_cached.html
new file mode 100644
index 0000000000..4a91e46e99
--- /dev/null
+++ b/dom/serviceworkers/test/sw_clients/refresher_cached.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 982726 - test match_all not crashing</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ 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/serviceworkers/test/sw_clients/refresher_cached_compressed.html b/dom/serviceworkers/test/sw_clients/refresher_cached_compressed.html
new file mode 100644
index 0000000000..6b6a328211
--- /dev/null
+++ b/dom/serviceworkers/test/sw_clients/refresher_cached_compressed.html
Binary files differ
diff --git a/dom/serviceworkers/test/sw_clients/refresher_cached_compressed.html^headers^ b/dom/serviceworkers/test/sw_clients/refresher_cached_compressed.html^headers^
new file mode 100644
index 0000000000..4204d8601d
--- /dev/null
+++ b/dom/serviceworkers/test/sw_clients/refresher_cached_compressed.html^headers^
@@ -0,0 +1,2 @@
+Content-Type: text/html
+Content-Encoding: gzip
diff --git a/dom/serviceworkers/test/sw_clients/refresher_compressed.html b/dom/serviceworkers/test/sw_clients/refresher_compressed.html
new file mode 100644
index 0000000000..e0861a5180
--- /dev/null
+++ b/dom/serviceworkers/test/sw_clients/refresher_compressed.html
Binary files differ
diff --git a/dom/serviceworkers/test/sw_clients/refresher_compressed.html^headers^ b/dom/serviceworkers/test/sw_clients/refresher_compressed.html^headers^
new file mode 100644
index 0000000000..4204d8601d
--- /dev/null
+++ b/dom/serviceworkers/test/sw_clients/refresher_compressed.html^headers^
@@ -0,0 +1,2 @@
+Content-Type: text/html
+Content-Encoding: gzip
diff --git a/dom/serviceworkers/test/sw_clients/service_worker_controlled.html b/dom/serviceworkers/test/sw_clients/service_worker_controlled.html
new file mode 100644
index 0000000000..e0d7bce573
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/sw_clients/simple.html b/dom/serviceworkers/test/sw_clients/simple.html
new file mode 100644
index 0000000000..bbe6782e2a
--- /dev/null
+++ b/dom/serviceworkers/test/sw_clients/simple.html
@@ -0,0 +1,29 @@
+<!--
+ 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 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/serviceworkers/test/sw_file_upload.js b/dom/serviceworkers/test/sw_file_upload.js
new file mode 100644
index 0000000000..20c695614b
--- /dev/null
+++ b/dom/serviceworkers/test/sw_file_upload.js
@@ -0,0 +1,16 @@
+self.skipWaiting();
+
+addEventListener("fetch", event => {
+ const url = new URL(event.request.url);
+ const params = new URLSearchParams(url.search);
+
+ if (params.get("clone") === "1") {
+ event.respondWith(fetch(event.request.clone()));
+ } else {
+ event.respondWith(fetch(event.request));
+ }
+});
+
+addEventListener("activate", function (event) {
+ event.waitUntil(clients.claim());
+});
diff --git a/dom/serviceworkers/test/sw_respondwith_serviceworker.js b/dom/serviceworkers/test/sw_respondwith_serviceworker.js
new file mode 100644
index 0000000000..6ddbc3d5c1
--- /dev/null
+++ b/dom/serviceworkers/test/sw_respondwith_serviceworker.js
@@ -0,0 +1,24 @@
+const SERVICEWORKER_DOC = `<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="utils.js" type="text/javascript"></script>
+</head>
+<body>
+SERVICEWORKER
+</body>
+</html>
+`;
+
+const SERVICEWORKER_RESPONSE = new Response(SERVICEWORKER_DOC, {
+ headers: { "content-type": "text/html" },
+});
+
+addEventListener("fetch", event => {
+ // Allow utils.js which we explicitly include to be loaded by resetting
+ // interception.
+ if (event.request.url.endsWith("/utils.js")) {
+ return;
+ }
+ event.respondWith(SERVICEWORKER_RESPONSE.clone());
+});
diff --git a/dom/serviceworkers/test/sw_storage_not_allow.js b/dom/serviceworkers/test/sw_storage_not_allow.js
new file mode 100644
index 0000000000..2eb2403309
--- /dev/null
+++ b/dom/serviceworkers/test/sw_storage_not_allow.js
@@ -0,0 +1,33 @@
+let clientId;
+addEventListener("fetch", function (event) {
+ event.respondWith(
+ (async function () {
+ if (event.request.url.includes("getClients")) {
+ // Expected to fail since the storage access is not allowed.
+ try {
+ await self.clients.matchAll();
+ } catch (e) {
+ // expected failure
+ }
+ } else if (event.request.url.includes("getClient-stage1")) {
+ let clients = await self.clients.matchAll();
+ clientId = clients[0].id;
+ } else if (event.request.url.includes("getClient-stage2")) {
+ // Expected to fail since the storage access is not allowed.
+ try {
+ await self.clients.get(clientId);
+ } catch (e) {
+ // expected failure
+ }
+ }
+
+ // Pass through the network request once our various Clients API
+ // promises have completed.
+ return await fetch(event.request);
+ })()
+ );
+});
+
+addEventListener("activate", function (event) {
+ event.waitUntil(clients.claim());
+});
diff --git a/dom/serviceworkers/test/sw_with_navigationPreload.js b/dom/serviceworkers/test/sw_with_navigationPreload.js
new file mode 100644
index 0000000000..afd7181dcf
--- /dev/null
+++ b/dom/serviceworkers/test/sw_with_navigationPreload.js
@@ -0,0 +1,28 @@
+addEventListener("activate", event => {
+ event.waitUntil(self.registration.navigationPreload.enable());
+});
+
+async function post_to_page(data) {
+ let cs = await self.clients.matchAll();
+ for (const client of cs) {
+ client.postMessage(data);
+ }
+}
+
+addEventListener("fetch", event => {
+ if (event.request.url.includes("navigationPreload_page.html")) {
+ event.respondWith(
+ new Response("<!DOCTYPE html>", {
+ headers: { "Content-Type": "text/html; charset=utf-8" },
+ })
+ );
+
+ event.waitUntil(
+ (async function () {
+ let preloadResponse = await event.preloadResponse;
+ let text = await preloadResponse.text();
+ await post_to_page(text);
+ })()
+ );
+ }
+});
diff --git a/dom/serviceworkers/test/swa/worker_scope_different.js b/dom/serviceworkers/test/swa/worker_scope_different.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/serviceworkers/test/swa/worker_scope_different.js
diff --git a/dom/serviceworkers/test/swa/worker_scope_different.js^headers^ b/dom/serviceworkers/test/swa/worker_scope_different.js^headers^
new file mode 100644
index 0000000000..e85a7f09de
--- /dev/null
+++ b/dom/serviceworkers/test/swa/worker_scope_different.js^headers^
@@ -0,0 +1 @@
+Service-Worker-Allowed: different/path
diff --git a/dom/serviceworkers/test/swa/worker_scope_different2.js b/dom/serviceworkers/test/swa/worker_scope_different2.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/serviceworkers/test/swa/worker_scope_different2.js
diff --git a/dom/serviceworkers/test/swa/worker_scope_different2.js^headers^ b/dom/serviceworkers/test/swa/worker_scope_different2.js^headers^
new file mode 100644
index 0000000000..e37307d666
--- /dev/null
+++ b/dom/serviceworkers/test/swa/worker_scope_different2.js^headers^
@@ -0,0 +1 @@
+Service-Worker-Allowed: /different/path
diff --git a/dom/serviceworkers/test/swa/worker_scope_precise.js b/dom/serviceworkers/test/swa/worker_scope_precise.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/serviceworkers/test/swa/worker_scope_precise.js
diff --git a/dom/serviceworkers/test/swa/worker_scope_precise.js^headers^ b/dom/serviceworkers/test/swa/worker_scope_precise.js^headers^
new file mode 100644
index 0000000000..7488cafbb0
--- /dev/null
+++ b/dom/serviceworkers/test/swa/worker_scope_precise.js^headers^
@@ -0,0 +1 @@
+Service-Worker-Allowed: /tests/dom/serviceworkers/test/swa
diff --git a/dom/serviceworkers/test/swa/worker_scope_too_deep.js b/dom/serviceworkers/test/swa/worker_scope_too_deep.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/serviceworkers/test/swa/worker_scope_too_deep.js
diff --git a/dom/serviceworkers/test/swa/worker_scope_too_deep.js^headers^ b/dom/serviceworkers/test/swa/worker_scope_too_deep.js^headers^
new file mode 100644
index 0000000000..9a66c3d153
--- /dev/null
+++ b/dom/serviceworkers/test/swa/worker_scope_too_deep.js^headers^
@@ -0,0 +1 @@
+Service-Worker-Allowed: /tests/dom/serviceworkers/test/swa/deep/way/too/specific
diff --git a/dom/serviceworkers/test/swa/worker_scope_too_narrow.js b/dom/serviceworkers/test/swa/worker_scope_too_narrow.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/serviceworkers/test/swa/worker_scope_too_narrow.js
diff --git a/dom/serviceworkers/test/swa/worker_scope_too_narrow.js^headers^ b/dom/serviceworkers/test/swa/worker_scope_too_narrow.js^headers^
new file mode 100644
index 0000000000..407361a3c7
--- /dev/null
+++ b/dom/serviceworkers/test/swa/worker_scope_too_narrow.js^headers^
@@ -0,0 +1 @@
+Service-Worker-Allowed: /tests/dom/serviceworkers
diff --git a/dom/serviceworkers/test/test_abrupt_completion.html b/dom/serviceworkers/test/test_abrupt_completion.html
new file mode 100644
index 0000000000..bbf9e965f0
--- /dev/null
+++ b/dom/serviceworkers/test/test_abrupt_completion.html
@@ -0,0 +1,144 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+
+// Tests a _registered_ ServiceWorker whose script evaluation results in an
+// "abrupt completion", e.g. threw an uncaught exception. Such a ServiceWorker's
+// first script evaluation must result in a "normal completion", however, for
+// the Update algorithm to not abort in its step 18 when registering:
+//
+// 18. If runResult is failure or an abrupt completion, then: [...]
+
+const script = "./abrupt_completion_worker.js";
+const scope = "./empty.html";
+const expectedMessage = "handler-before-throw";
+let registration = null;
+
+// Should only be called once registration.active is non-null. Uses
+// implementation details by zero-ing the "idle timeout"s and then sending an
+// event to the ServiceWorker, which should immediately cause its termination.
+// The idle timeouts are restored after the ServiceWorker is terminated.
+async function startAndStopServiceWorker() {
+ SpecialPowers.registerObservers("service-worker-shutdown");
+
+ const spTopic = "specialpowers-service-worker-shutdown";
+
+ const origIdleTimeout =
+ SpecialPowers.getIntPref("dom.serviceWorkers.idle_timeout");
+
+ const origIdleExtendedTimeout =
+ SpecialPowers.getIntPref("dom.serviceWorkers.idle_extended_timeout");
+
+ await new Promise(resolve => {
+ const observer = {
+ async observe(subject, topic, data) {
+ if (topic !== spTopic) {
+ return;
+ }
+
+ SpecialPowers.removeObserver(observer, spTopic);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.idle_timeout", origIdleTimeout],
+ ["dom.serviceWorkers.idle_extended_timeout", origIdleExtendedTimeout]
+ ]
+ });
+
+ resolve();
+ },
+ };
+
+ // Speed things up.
+ SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.idle_timeout", 0],
+ ["dom.serviceWorkers.idle_extended_timeout", 0]
+ ]
+ }).then(() => {
+ SpecialPowers.addObserver(observer, spTopic);
+
+ registration.active.postMessage("");
+ });
+ });
+}
+
+// eslint-disable-next-line mozilla/no-addtask-setup
+add_task(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]
+ });
+
+ registration = await navigator.serviceWorker.register(script, { scope });
+ SimpleTest.registerCleanupFunction(async function unregisterRegistration() {
+ await registration.unregister();
+ });
+
+ await new Promise(resolve => {
+ const serviceWorker = registration.installing;
+
+ serviceWorker.onstatechange = () => {
+ if (serviceWorker.state === "activated") {
+ resolve();
+ }
+ };
+ });
+
+ ok(registration.active instanceof ServiceWorker, "ServiceWorker is activated");
+});
+
+// We expect that the restarted SW that experiences an abrupt completion at
+// startup after adding its message handler 1) will be active in order to
+// respond to our postMessage and 2) will respond with the global value set
+// prior to the importScripts call that throws (and not the global value that
+// would have been assigned after the importScripts call if it didn't throw).
+add_task(async function testMessageHandler() {
+ await startAndStopServiceWorker();
+
+ await new Promise(resolve => {
+ navigator.serviceWorker.onmessage = e => {
+ is(e.data, expectedMessage, "Correct message handler");
+ resolve();
+ };
+ registration.active.postMessage("");
+ });
+});
+
+// We expect that the restarted SW that experiences an abrupt completion at
+// startup before adding its "fetch" listener will 1) successfully dispatch the
+// event and 2) it will not be handled (respondWith() will not be called) so
+// interception will be reset and the response will contain the contents of
+// empty.html. Before the fix in bug 1603484 the SW would fail to properly start
+// up and the fetch event would result in a NetworkError, breaking the
+// controlled page.
+add_task(async function testFetchHandler() {
+ await startAndStopServiceWorker();
+
+ const iframe = document.createElement("iframe");
+ SimpleTest.registerCleanupFunction(function removeIframe() {
+ iframe.remove();
+ });
+
+ await new Promise(resolve => {
+ iframe.src = scope;
+ iframe.onload = resolve;
+ document.body.appendChild(iframe);
+ });
+
+ const response = await iframe.contentWindow.fetch(scope);
+
+ // NetworkError will have a status of 0, which is not "ok", and this is
+ // a stronger guarantee that should be true instead of just checking if there
+ // isn't a NetworkError.
+ ok(response.ok, "Fetch succeeded and didn't result in a NetworkError");
+
+ const text = await response.text();
+ is(text, "", "Correct response text");
+});
+
+</script>
diff --git a/dom/serviceworkers/test/test_async_waituntil.html b/dom/serviceworkers/test/test_async_waituntil.html
new file mode 100644
index 0000000000..8c15eb2b11
--- /dev/null
+++ b/dom/serviceworkers/test/test_async_waituntil.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test that:
+ 1. waitUntil() waits for each individual promise separately, even if
+ one of them was rejected.
+ 2. waitUntil() can be called asynchronously as long as there is still
+ a pending extension promise.
+ -->
+<head>
+ <title>Test for Bug 1263304</title>
+ <script src="/tests/SimpleTest/SimpleTest.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=1263304">Mozilla Bug 1263304</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script src="utils.js"></script>
+<script class="testbody" type="text/javascript">
+add_task(function setupPrefs() {
+ return SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]});
+});
+
+function wait_for_message(expected_message) {
+ return new Promise(function(resolve, reject) {
+ navigator.serviceWorker.onmessage = function(event) {
+ navigator.serviceWorker.onmessage = null;
+ ok(event.data === expected_message, "Received expected message event: " + event.data);
+ resolve();
+ }
+ });
+}
+
+add_task(async function async_wait_until() {
+ var worker;
+ let registration = await navigator.serviceWorker.register(
+ "async_waituntil_worker.js", { scope: "./"} )
+ .then(function(reg) {
+ worker = reg.installing;
+ return waitForState(worker, 'activated', reg);
+ });
+
+ // The service worker will claim us when it becomes active.
+ ok(navigator.serviceWorker.controller, "Controlled");
+
+ // This will make the service worker die immediately if there are no pending
+ // waitUntil promises to keep it alive.
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.idle_timeout", 0],
+ ["dom.serviceWorkers.idle_extended_timeout", 299999]]});
+
+ // The service worker will wait on two promises, one of which
+ // will be rejected. We check whether the SW is killed using
+ // the value of a global variable.
+ let waitForStart = wait_for_message("Started");
+ worker.postMessage("Start");
+ await waitForStart;
+
+ await new Promise((res, rej) => {
+ setTimeout(res, 0);
+ });
+
+ let waitResult = wait_for_message("Success");
+ worker.postMessage("Result");
+ await waitResult;
+
+ // Test the behaviour of calling waitUntil asynchronously. The important
+ // part is that we receive the message event.
+ let waitForMessage = wait_for_message("Done");
+ await fetch("doesnt_exist.html").then(() => {
+ ok(true, "Fetch was successful.");
+ });
+ await waitForMessage;
+
+ await SpecialPowers.popPrefEnv();
+ await SpecialPowers.popPrefEnv();
+ await registration.unregister();
+});
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_bad_script_cache.html b/dom/serviceworkers/test/test_bad_script_cache.html
new file mode 100644
index 0000000000..93df0c37bb
--- /dev/null
+++ b/dom/serviceworkers/test/test_bad_script_cache.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>Test updating a service worker with a bad script cache.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script src='utils.js'></script>
+<script class="testbody" type="text/javascript">
+
+async function deleteCaches(cacheStorage) {
+ let keyList = await cacheStorage.keys();
+ let promiseList = [];
+ keyList.forEach(key => {
+ promiseList.push(cacheStorage.delete(key));
+ });
+ return await Promise.all(keyList);
+}
+
+function waitForUpdate(reg) {
+ return new Promise(resolve => {
+ reg.addEventListener('updatefound', resolve, { once: true });
+ });
+}
+
+async function runTest() {
+ let reg;
+ try {
+ const script = 'update_worker.sjs';
+ const scope = 'bad-script-cache';
+
+ reg = await navigator.serviceWorker.register(script, { scope });
+ await waitForState(reg.installing, 'activated');
+
+ // Verify the service worker script cache has the worker script stored.
+ let chromeCaches = SpecialPowers.createChromeCache('chrome', window.origin);
+ let scriptURL = new URL(script, window.location.href);
+ let response = await chromeCaches.match(scriptURL.href);
+ is(response.url, scriptURL.href, 'worker script should be stored');
+
+ // Force delete the service worker script out from under the service worker.
+ // Note: Prefs are set to kill the SW thread immediately on idle.
+ await deleteCaches(chromeCaches);
+
+ // Verify the service script cache no longer knows about the worker script.
+ response = await chromeCaches.match(scriptURL.href);
+ is(response, undefined, 'worker script should not be stored');
+
+ // Force an update and wait for it to fire an update event.
+ reg.update();
+ await waitForUpdate(reg);
+ await waitForState(reg.installing, 'activated');
+
+ // Verify that the script cache knows about the worker script again.
+ response = await chromeCaches.match(scriptURL.href);
+ is(response.url, scriptURL.href, 'worker script should be stored');
+ } catch (e) {
+ ok(false, e);
+ }
+ if (reg) {
+ await reg.unregister();
+ }
+
+ // If this test is run on windows and the process shuts down immediately after, then
+ // we may fail to remove some of the Cache API body files. This is because the GC
+ // runs late causing Cache API to cleanup after shutdown begins. It seems something
+ // during shutdown scans these files and conflicts with removing the file on windows.
+ //
+ // To avoid this we perform an explict GC here to ensure that Cache API can cleanup
+ // earlier.
+ await new Promise(resolve => SpecialPowers.exactGC(resolve));
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [
+ // standard prefs
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+
+ // immediately kill the service worker thread when idle
+ ["dom.serviceWorkers.idle_timeout", 0],
+
+]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_bug1151916.html b/dom/serviceworkers/test/test_bug1151916.html
new file mode 100644
index 0000000000..1cb0c1b100
--- /dev/null
+++ b/dom/serviceworkers/test/test_bug1151916.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 1151916 - Test principal is set on cached serviceworkers</title>
+ <script 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.remove();
+ 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],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_bug1240436.html b/dom/serviceworkers/test/test_bug1240436.html
new file mode 100644
index 0000000000..8b76ada6a8
--- /dev/null
+++ b/dom/serviceworkers/test/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 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/serviceworkers/test/test_bug1408734.html b/dom/serviceworkers/test/test_bug1408734.html
new file mode 100644
index 0000000000..27559e695f
--- /dev/null
+++ b/dom/serviceworkers/test/test_bug1408734.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 1408734</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="error_reporting_helpers.js"></script>
+ <script src="utils.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">
+
+// setup prefs
+add_task(() => {
+ return SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]});
+});
+
+// test for bug 1408734
+add_task(async () => {
+ // register a service worker
+ let registration = await navigator.serviceWorker.register("fetch.js",
+ {scope: "./"});
+ // wait for service worker be activated
+ await waitForState(registration.installing, "activated");
+
+ // get the ServiceWorkerRegistration we just register through GetRegistration
+ registration = await navigator.serviceWorker.getRegistration("./");
+ ok(registration, "should get the registration under scope './'");
+
+ // call unregister()
+ await registration.unregister();
+
+ // access registration.updateViaCache to trigger the bug
+ // we really care that we don't crash. In the future we will fix
+ is(registration.updateViaCache, "imports",
+ "registration.updateViaCache should work after unregister()");
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_claim.html b/dom/serviceworkers/test/test_claim.html
new file mode 100644
index 0000000000..e72f1173e8
--- /dev/null
+++ b/dom/serviceworkers/test/test_claim.html
@@ -0,0 +1,171 @@
+<!--
+ 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 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/serviceworkers/test/test_claim_oninstall.html b/dom/serviceworkers/test/test_claim_oninstall.html
new file mode 100644
index 0000000000..54933405ce
--- /dev/null
+++ b/dom/serviceworkers/test/test_claim_oninstall.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>Bug 1130684 - Test service worker clients.claim oninstall</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+ var 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/serviceworkers/test/test_controller.html b/dom/serviceworkers/test/test_controller.html
new file mode 100644
index 0000000000..c0e220a36e
--- /dev/null
+++ b/dom/serviceworkers/test/test_controller.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 1002570 - test controller instance.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script src="utils.js"></script>
+<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(swr => waitForState(swr.installing, 'activated', swr))
+ .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: " + 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/serviceworkers/test/test_cookie_fetch.html b/dom/serviceworkers/test/test_cookie_fetch.html
new file mode 100644
index 0000000000..8c4324c759
--- /dev/null
+++ b/dom/serviceworkers/test/test_cookie_fetch.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 1331680 - test access to cookies in the documents synthesized from service worker responses</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">
+<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/serviceworkers/test/fetch/cookie/register.html";
+ window.onmessage = function(e) {
+ if (e.data.status == "ok") {
+ ok(e.data.result, e.data.message);
+ } else if (e.data.status == "registrationdone") {
+ // Remove the iframe and recreate a new one to ensure that any traces
+ // of the cookies have been removed from the child process.
+ iframe.remove();
+ iframe = document.createElement("iframe");
+ document.getElementById("content").appendChild(iframe);
+
+ iframe.src = "https://example.com/tests/dom/serviceworkers/test/fetch/cookie/synth.html";
+ } else if (e.data.status == "done") {
+ // Note, we can't do an exact is() comparison here since other
+ // tests can leave cookies on the domain.
+ ok(e.data.cookie.includes("foo=bar"),
+ "The synthesized document has access to its cookies");
+
+ iframe.src = "https://example.com/tests/dom/serviceworkers/test/fetch/cookie/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ window.onmessage = null;
+ ok(true, "Test finished successfully");
+ SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+ ["network.cookie.sameSite.laxByDefault", false],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_cross_origin_url_after_redirect.html b/dom/serviceworkers/test/test_cross_origin_url_after_redirect.html
new file mode 100644
index 0000000000..bfd4f700be
--- /dev/null
+++ b/dom/serviceworkers/test/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 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/serviceworkers/test/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/serviceworkers/test/fetch/requesturl/index.html";
+ } else if (e.data.status == "done") {
+ iframe.src = "/tests/dom/serviceworkers/test/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/serviceworkers/test/test_csp_upgrade-insecure_intercept.html b/dom/serviceworkers/test/test_csp_upgrade-insecure_intercept.html
new file mode 100644
index 0000000000..b5ddbb97b6
--- /dev/null
+++ b/dom/serviceworkers/test/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 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/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/test_devtools_bypass_serviceworker.html b/dom/serviceworkers/test/test_devtools_bypass_serviceworker.html
new file mode 100644
index 0000000000..ec73f59dc0
--- /dev/null
+++ b/dom/serviceworkers/test/test_devtools_bypass_serviceworker.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title> Verify devtools can utilize nsIChannel::LOAD_BYPASS_SERVICE_WORKER to bypass the service worker </title>
+ <script src="/tests/SimpleTest/SimpleTest.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 src="utils.js"></script>
+<script type="text/javascript">
+"use strict";
+
+async function testBypassSW () {
+ let Ci = SpecialPowers.Ci;
+
+ // Bypass SW imitates the "Disable Cache" option in dev-tools.
+ // Note: if we put the setter/getter into dev-tools, we should take care of
+ // the implementation of enabling/disabling cache since it just overwrite the
+ // defaultLoadFlags of docShell.
+ function setBypassServiceWorker(aDocShell, aBypass) {
+ if (aBypass) {
+ aDocShell.defaultLoadFlags |= Ci.nsIChannel.LOAD_BYPASS_SERVICE_WORKER;
+ return;
+ }
+
+ aDocShell.defaultLoadFlags &= ~Ci.nsIChannel.LOAD_BYPASS_SERVICE_WORKER;
+ }
+
+ function getBypassServiceWorker(aDocShell) {
+ return !!(aDocShell.defaultLoadFlags &
+ Ci.nsIChannel.LOAD_BYPASS_SERVICE_WORKER);
+ }
+
+ async function fetchFakeDocAndCheckIfIntercepted(aWindow) {
+ const fakeDoc = "fake.html";
+
+ // Note: The fetching document doesn't exist, so the expected status of the
+ // repsonse is 404 unless the request is hijacked.
+ let response = await aWindow.fetch(fakeDoc);
+ if (response.status === 404) {
+ return false;
+ } else if (!response.ok) {
+ throw(response.statusText);
+ }
+
+ let text = await response.text();
+ if (text.includes("Hello")) {
+ // Intercepted
+ return true;
+ }
+
+ throw("Unexpected error");
+ }
+
+ let docShell = SpecialPowers.wrap(window).docShell;
+
+ info("Test 1: Enable bypass service worker for the docShell");
+
+ setBypassServiceWorker(docShell, true);
+ ok(getBypassServiceWorker(docShell),
+ "The loadFlags in docShell does bypass the serviceWorker by default");
+
+ let intercepted = await fetchFakeDocAndCheckIfIntercepted(window);
+ ok(!intercepted,
+ "The fetched document wasn't intercepted by the serviceWorker");
+
+ info("Test 2: Disable the bypass service worker for the docShell");
+
+ setBypassServiceWorker(docShell, false);
+ ok(!getBypassServiceWorker(docShell),
+ "The loadFlags in docShell doesn't bypass the serviceWorker by default");
+
+ intercepted = await fetchFakeDocAndCheckIfIntercepted(window);
+ ok(intercepted,
+ "The fetched document was intercepted by the serviceWorker");
+}
+
+// (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(async function test_bypassServiceWorker() {
+ const swURL = "fetch.js";
+ let registration = await navigator.serviceWorker.register(swURL);
+ await waitForState(registration.installing, 'activated');
+
+ try {
+ await testBypassSW();
+ } catch (e) {
+ ok(false, "Reason:" + e);
+ }
+
+ await registration.unregister();
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_devtools_track_serviceworker_time.html b/dom/serviceworkers/test/test_devtools_track_serviceworker_time.html
new file mode 100644
index 0000000000..ac27ebcd33
--- /dev/null
+++ b/dom/serviceworkers/test/test_devtools_track_serviceworker_time.html
@@ -0,0 +1,236 @@
+<html>
+<head>
+ <title>Bug 1251238 - track service worker install time</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+</head>
+<iframe id="iframe"></iframe>
+<body>
+
+<script type="text/javascript">
+
+const State = {
+ BYTECHECK: -1,
+ PARSED: Ci.nsIServiceWorkerInfo.STATE_PARSED,
+ INSTALLING: Ci.nsIServiceWorkerInfo.STATE_INSTALLING,
+ INSTALLED: Ci.nsIServiceWorkerInfo.STATE_INSTALLED,
+ ACTIVATING: Ci.nsIServiceWorkerInfo.STATE_ACTIVATING,
+ ACTIVATED: Ci.nsIServiceWorkerInfo.STATE_ACTIVATED,
+ REDUNDANT: Ci.nsIServiceWorkerInfo.STATE_REDUNDANT
+};
+let swm = Cc["@mozilla.org/serviceworkers/manager;1"].
+ getService(Ci.nsIServiceWorkerManager);
+
+let EXAMPLE_URL = "https://example.com/chrome/dom/serviceworkers/test/";
+
+let swrlistener = null;
+let registrationInfo = null;
+
+// Use it to keep the sw after unregistration.
+let astrayServiceWorkerInfo = null;
+
+let expectedResults = [
+ {
+ // Speacial state for verifying update since we will do the byte-check
+ // first.
+ state: State.BYTECHECK, installedTimeRecorded: false,
+ activatedTimeRecorded: false, redundantTimeRecorded: false
+ },
+ {
+ state: State.PARSED, installedTimeRecorded: false,
+ activatedTimeRecorded: false, redundantTimeRecorded: false
+ },
+ {
+ state: State.INSTALLING, installedTimeRecorded: false,
+ activatedTimeRecorded: false, redundantTimeRecorded: false
+ },
+ {
+ state: State.INSTALLED, installedTimeRecorded: true,
+ activatedTimeRecorded: false, redundantTimeRecorded: false
+ },
+ {
+ state: State.ACTIVATING, installedTimeRecorded: true,
+ activatedTimeRecorded: false, redundantTimeRecorded: false
+ },
+ {
+ state: State.ACTIVATED, installedTimeRecorded: true,
+ activatedTimeRecorded: true, redundantTimeRecorded: false
+ },
+
+ // When first being marked as unregistered (but the worker can remain
+ // actively controlling pages)
+ {
+ state: State.ACTIVATED, installedTimeRecorded: true,
+ activatedTimeRecorded: true, redundantTimeRecorded: false
+ },
+ // When cleared (when idle)
+ {
+ state: State.REDUNDANT, installedTimeRecorded: true,
+ activatedTimeRecorded: true, redundantTimeRecorded: true
+ },
+];
+
+function waitForRegister(aScope, aCallback) {
+ return new Promise(function (aResolve) {
+ let listener = {
+ onRegister (aRegistration) {
+ if (aRegistration.scope !== aScope) {
+ return;
+ }
+ swm.removeListener(listener);
+ registrationInfo = aRegistration;
+ aResolve();
+ }
+ };
+ swm.addListener(listener);
+ });
+}
+
+function waitForUnregister(aScope) {
+ return new Promise(function (aResolve) {
+ let listener = {
+ onUnregister (aRegistration) {
+ if (aRegistration.scope !== aScope) {
+ return;
+ }
+ swm.removeListener(listener);
+ aResolve();
+ }
+ };
+ swm.addListener(listener);
+ });
+}
+
+function register() {
+ info("Register a ServiceWorker in the iframe");
+
+ let iframe = document.querySelector("iframe");
+ iframe.src = EXAMPLE_URL + "serviceworkerinfo_iframe.html";
+
+ let promise = new Promise(function(aResolve) {
+ iframe.onload = aResolve;
+ });
+
+ return promise.then(function() {
+ iframe.contentWindow.postMessage("register", "*");
+ return waitForRegister(EXAMPLE_URL);
+ })
+}
+
+function verifyServiceWorkTime(aSWRInfo, resolve) {
+ let expectedResult = expectedResults.shift();
+ ok(!!expectedResult, "We should be able to get test from expectedResults");
+
+ info("Check the ServiceWorker time in its state is " + expectedResult.state);
+
+ // Get serviceWorkerInfo from swrInfo or get the astray one which we hold.
+ let swInfo = aSWRInfo.evaluatingWorker ||
+ aSWRInfo.installingWorker ||
+ aSWRInfo.waitingWorker ||
+ aSWRInfo.activeWorker ||
+ astrayServiceWorkerInfo;
+
+ ok(!!aSWRInfo.lastUpdateTime,
+ "We should do the byte-check and update the update timeStamp");
+
+ if (!swInfo) {
+ is(expectedResult.state, State.BYTECHECK,
+ "We shouldn't get sw when we are notified for first time updating");
+ return;
+ }
+
+ ok(!!swInfo);
+
+ is(expectedResult.state, swInfo.state,
+ "The service worker's state should be " + swInfo.state + ", but got " +
+ expectedResult.state);
+
+ is(expectedResult.installedTimeRecorded, !!swInfo.installedTime,
+ "InstalledTime should be recorded when their state is greater than " +
+ "INSTALLING");
+
+ is(expectedResult.activatedTimeRecorded, !!swInfo.activatedTime,
+ "ActivatedTime should be recorded when their state is greater than " +
+ "ACTIVATING");
+
+ is(expectedResult.redundantTimeRecorded, !!swInfo.redundantTime,
+ "RedundantTime should be recorded when their state is REDUNDANT");
+
+ // We need to hold sw to avoid losing it since we'll unregister the swr later.
+ if (expectedResult.state === State.ACTIVATED) {
+ astrayServiceWorkerInfo = aSWRInfo.activeWorker;
+
+ // Resolve the promise for testServiceWorkerInfo after sw is activated.
+ resolve();
+ }
+}
+
+function testServiceWorkerInfo() {
+ info("Listen onChange event and verify service worker's information");
+
+ let promise_resolve;
+ let promise = new Promise(aResolve => promise_resolve = aResolve);
+
+ swrlistener = {
+ onChange: () => {
+ verifyServiceWorkTime(registrationInfo, promise_resolve);
+ }
+ };
+
+ registrationInfo.addListener(swrlistener);
+
+ return promise;
+}
+
+async function testHttpCacheUpdateTime() {
+ let iframe = document.querySelector("iframe");
+ let reg = await iframe.contentWindow.navigator.serviceWorker.getRegistration();
+ let lastUpdateTime = registrationInfo.lastUpdateTime;
+ await reg.update();
+ is(lastUpdateTime, registrationInfo.lastUpdateTime,
+ "The update time should not change when SW script is read from http cache.");
+}
+
+function unregister() {
+ info("Unregister the ServiceWorker");
+
+ let iframe = document.querySelector("iframe");
+ iframe.contentWindow.postMessage("unregister", "*");
+ return waitForUnregister(EXAMPLE_URL);
+}
+
+function cleanAll() {
+ return new Promise((aResolve, aReject) => {
+ is(expectedResults.length, 0, "All the tests should be tested");
+
+ registrationInfo.removeListener(swrlistener);
+
+ swm = null;
+ swrlistener = null;
+ registrationInfo = null;
+ astrayServiceWorkerInfo = null;
+ aResolve();
+ })
+}
+
+function runTest() {
+ return Promise.resolve()
+ .then(register)
+ .then(testServiceWorkerInfo)
+ .then(testHttpCacheUpdateTime)
+ .then(unregister)
+ .catch(aError => ok(false, "Some test failed with error " + aError))
+ .then(cleanAll)
+ .then(SimpleTest.finish);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+]}, runTest);
+
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_empty_serviceworker.html b/dom/serviceworkers/test/test_empty_serviceworker.html
new file mode 100644
index 0000000000..00b77939f8
--- /dev/null
+++ b/dom/serviceworkers/test/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 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/serviceworkers/test/test_enabled_pref.html b/dom/serviceworkers/test/test_enabled_pref.html
new file mode 100644
index 0000000000..b821fdaf5a
--- /dev/null
+++ b/dom/serviceworkers/test/test_enabled_pref.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1645054 - test dom.serviceWorkers.enabled preference</title>
+</head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="utils.js"></script>
+<script>
+
+ function create_iframe(url) {
+ return new Promise(function(res) {
+ iframe = document.createElement('iframe');
+ iframe.src = url;
+ iframe.onload = function() { res(iframe) }
+ document.body.appendChild(iframe);
+ });
+ }
+
+ async function do_fetch(pref) {
+ await SpecialPowers.pushPrefEnv({ set: [pref] });
+
+ let iframe = await create_iframe("./pref/fetch_nonexistent_file.html");
+ let status = await iframe.contentWindow.fetch_status();
+
+ await SpecialPowers.popPrefEnv();
+ return status;
+ }
+
+ add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [['dom.serviceWorkers.testing.enabled', true]]
+ });
+
+ let reg = await navigator.serviceWorker.register(
+ 'pref/intercept_nonexistent_file_sw.js');
+ await waitForState(reg.installing, 'activated');
+
+ let status;
+
+ status = await do_fetch(['dom.serviceWorkers.enabled', true]);
+ is(status, 200, 'SW enabled');
+
+ status = await do_fetch(['dom.serviceWorkers.enabled', false]);
+ is(status, 404, 'SW disabled');
+
+ status = await do_fetch(['dom.serviceWorkers.enabled', true]);
+ is(status, 200, 'SW enabled again');
+
+ await reg.unregister();
+ });
+
+</script>
+<body>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_error_reporting.html b/dom/serviceworkers/test/test_error_reporting.html
new file mode 100644
index 0000000000..7c2d56fb9e
--- /dev/null
+++ b/dom/serviceworkers/test/test_error_reporting.html
@@ -0,0 +1,241 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Error Reporting of Service Worker Failures</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="error_reporting_helpers.js"></script>
+ <script src="utils.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(async 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.
+ await navigator.serviceWorker.register("404.js", { scope: "network_error/" })
+ .then(
+ () => { ok(false, "should have rejected"); },
+ (e) => { ok(e.name === "TypeError", "404 failed as expected"); });
+
+ await 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(async function register_bad_mime_type() {
+ let expectedMessage = expect_console_message(
+ "ServiceWorkerRegisterMimeTypeError2",
+ [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.
+ await 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"); });
+
+ await wait_for_expected_message(expectedMessage);
+});
+
+async function notAllowStorageAccess() {
+ throw new Error("Storage permissions should be used when bug 1774860 overhauls this test.");
+}
+
+async function allowStorageAccess() {
+ throw new Error("Storage permissions should be used when bug 1774860 overhauls this test.");
+}
+
+/**
+ * Ensure an error is logged when the storage is not allowed and the content
+ * script is trying to register a service worker.
+ */
+add_task(async function register_storage_error() {
+ let expectedMessage = expect_console_message(
+ "ServiceWorkerRegisterStorageError",
+ [make_absolute_url("storage_not_allow/")]);
+
+ await notAllowStorageAccess();
+
+ // consume the expected rejection so it doesn't get thrown at us.
+ await navigator.serviceWorker.register("sw_storage_not_allow.js",
+ { scope: "storage_not_allow/" })
+ .then(
+ () => { ok(false, "should have rejected"); },
+ (e) => { ok(e.name === "SecurityError",
+ "storage access failed as expected."); });
+
+ await wait_for_expected_message(expectedMessage);
+
+ await allowStorageAccess();
+});
+
+/**
+ * Ensure an error is logged when the storage is not allowed and the content
+ * script is trying to get the service worker registration.
+ */
+add_task(async function get_registration_storage_error() {
+ let expectedMessage =
+ expect_console_message("ServiceWorkerGetRegistrationStorageError", []);
+
+ await notAllowStorageAccess();
+
+ // consume the expected rejection so it doesn't get thrown at us.
+ await navigator.serviceWorker.getRegistration()
+ .then(
+ () => { ok(false, "should have rejected"); },
+ (e) => { ok(e.name === "SecurityError",
+ "storage access failed as expected."); });
+
+ await wait_for_expected_message(expectedMessage);
+
+ await allowStorageAccess();
+});
+
+/**
+ * Ensure an error is logged when the storage is not allowed and the content
+ * script is trying to get the service worker registrations.
+ */
+add_task(async function get_registrations_storage_error() {
+ let expectedMessage =
+ expect_console_message("ServiceWorkerGetRegistrationStorageError", []);
+
+ await notAllowStorageAccess();
+
+ // consume the expected rejection so it doesn't get thrown at us.
+ await navigator.serviceWorker.getRegistrations()
+ .then(
+ () => { ok(false, "should have rejected"); },
+ (e) => { ok(e.name === "SecurityError",
+ "storage access failed as expected."); });
+
+ await wait_for_expected_message(expectedMessage);
+
+ await allowStorageAccess();
+});
+
+/**
+ * Ensure an error is logged when the storage is not allowed and the content
+ * script is trying to post a message to the service worker.
+ */
+add_task(async function postMessage_storage_error() {
+ let expectedMessage = expect_console_message(
+ "ServiceWorkerPostMessageStorageError",
+ [make_absolute_url("storage_not_allow/")]);
+
+ let registration;
+ // consume the expected rejection so it doesn't get thrown at us.
+ await navigator.serviceWorker.register("sw_storage_not_allow.js",
+ { scope: "storage_not_allow/" })
+ .then(reg => { registration = reg; })
+ .then(() => notAllowStorageAccess())
+ .then(() => registration.installing ||
+ registration.waiting ||
+ registration.active)
+ .then(worker => worker.postMessage('ha'))
+ .then(
+ () => { ok(false, "should have rejected"); },
+ (e) => { ok(e.name === "SecurityError",
+ "storage access failed as expected."); });
+
+ await wait_for_expected_message(expectedMessage);
+
+ await registration.unregister();
+ await allowStorageAccess();
+});
+
+/**
+ * Ensure an error is logged when the storage is not allowed and the service
+ * worker is trying to get its client.
+ */
+add_task(async function get_client_storage_error() {
+ let expectedMessage =
+ expect_console_message("ServiceWorkerGetClientStorageError", []);
+
+ await SpecialPowers.pushPrefEnv({"set": [
+ // Make the test pass the IsOriginPotentiallyTrustworthy.
+ ["dom.securecontext.allowlist", "mochi.test"]
+ ]});
+
+ let registration;
+ // consume the expected rejection so it doesn't get thrown at us.
+ await navigator.serviceWorker.register("sw_storage_not_allow.js",
+ { scope: "test_error_reporting.html" })
+ .then(reg => {
+ registration = reg;
+ return waitForState(registration.installing, "activated");
+ })
+ // Get the client's ID in the stage 1
+ .then(() => fetch("getClient-stage1"))
+ .then(() => notAllowStorageAccess())
+ // Trigger the clients.get() in the stage 2
+ .then(() => fetch("getClient-stage2"))
+ .catch(e => ok(false, "fail due to:" + e));
+
+ await wait_for_expected_message(expectedMessage);
+
+ await registration.unregister();
+ await allowStorageAccess();
+});
+
+/**
+ * Ensure an error is logged when the storage is not allowed and the service
+ * worker is trying to get its clients.
+ */
+add_task(async function get_clients_storage_error() {
+ let expectedMessage =
+ expect_console_message("ServiceWorkerGetClientStorageError", []);
+
+ let registration;
+ // consume the expected rejection so it doesn't get thrown at us.
+ await navigator.serviceWorker.register("sw_storage_not_allow.js",
+ { scope: "test_error_reporting.html" })
+ .then(reg => {
+ registration = reg;
+ return waitForState(registration.installing, "activated");
+ })
+ .then(() => notAllowStorageAccess())
+ .then(() => fetch("getClients"))
+ .catch(e => ok(false, "fail due to:" + e));
+
+ await wait_for_expected_message(expectedMessage);
+
+ await registration.unregister();
+ await allowStorageAccess();
+});
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_escapedSlashes.html b/dom/serviceworkers/test/test_escapedSlashes.html
new file mode 100644
index 0000000000..001c660242
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/test_eval_allowed.html b/dom/serviceworkers/test/test_eval_allowed.html
new file mode 100644
index 0000000000..82c6626fd4
--- /dev/null
+++ b/dom/serviceworkers/test/test_eval_allowed.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 1160458 - CSP activated by default in Service Workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+ function register() {
+ return navigator.serviceWorker.register("eval_worker.js");
+ }
+
+ function runTest() {
+ try {
+ // eslint-disable-next-line no-eval
+ 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/serviceworkers/test/test_eval_allowed.html^headers^ b/dom/serviceworkers/test/test_eval_allowed.html^headers^
new file mode 100644
index 0000000000..51ffaa71dd
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/test_event_listener_leaks.html b/dom/serviceworkers/test/test_event_listener_leaks.html
new file mode 100644
index 0000000000..33ffeb44c4
--- /dev/null
+++ b/dom/serviceworkers/test/test_event_listener_leaks.html
@@ -0,0 +1,63 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1447871 - Test some service worker leak conditions</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="utils.js"></script>
+ <script type="text/javascript" src="/tests/dom/events/test/event_leak_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<script class="testbody" type="text/javascript">
+
+const scope = new URL("empty.html?leak_tests", location).href;
+const script = new URL("empty.js", location).href;
+
+// Manipulate service worker DOM objects in the frame's context.
+// Its important here that we create a listener callback from
+// the DOM objects back to the frame's global in order to
+// exercise the leak condition.
+async function useServiceWorker(contentWindow) {
+ contentWindow.navigator.serviceWorker.oncontrollerchange = _ => {
+ contentWindow.controlledChangeCount += 1;
+ };
+ let reg = await contentWindow.navigator.serviceWorker.getRegistration(scope);
+ reg.onupdatefound = _ => {
+ contentWindow.updateCount += 1;
+ };
+ reg.active.onstatechange = _ => {
+ contentWindow.stateChangeCount += 1;
+ };
+}
+
+async function runTest() {
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]});
+
+ let reg = await navigator.serviceWorker.register(script, { scope });
+ await waitForState(reg.installing, "activated");
+
+ try {
+ await checkForEventListenerLeaks("ServiceWorker", useServiceWorker);
+ } catch (e) {
+ ok(false, e);
+ } finally {
+ await reg.unregister();
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addEventListener("load", runTest, { once: true });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_eventsource_intercept.html b/dom/serviceworkers/test/test_eventsource_intercept.html
new file mode 100644
index 0000000000..b49f557792
--- /dev/null
+++ b/dom/serviceworkers/test/test_eventsource_intercept.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>Bug 1182103 - Test EventSource scenarios with fetch interception</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ function 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/serviceworkers/test/" +
+ "eventsource/eventsource_register_worker.html" +
+ "?script=eventsource_mixed_content_cors_response_intercept_worker.js");
+ })
+ .then(() => {
+ return testFrame("https://example.com/tests/dom/serviceworkers/test/" +
+ "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],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_fetch_event.html b/dom/serviceworkers/test/test_fetch_event.html
new file mode 100644
index 0000000000..5227f6ae34
--- /dev/null
+++ b/dom/serviceworkers/test/test_fetch_event.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 94048 - test install event.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script src="utils.js"></script>
+<script class="testbody" type="text/javascript">
+ SimpleTest.requestCompleteLog();
+
+ var registration;
+ function simpleRegister() {
+ return navigator.serviceWorker.register("fetch_event_worker.js", { scope: "./fetch" })
+ .then(swr => {
+ registration = swr;
+ return waitForState(swr.installing, 'activated');
+ });
+ }
+
+ 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) {
+ 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/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/serviceworkers/test/test_fetch_event_with_thirdpartypref.html b/dom/serviceworkers/test/test_fetch_event_with_thirdpartypref.html
new file mode 100644
index 0000000000..53552e03c3
--- /dev/null
+++ b/dom/serviceworkers/test/test_fetch_event_with_thirdpartypref.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 94048 - test install event.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script src="utils.js"></script>
+<script class="testbody" type="text/javascript">
+
+ // NOTE: This is just test_fetch_event.html but with an alternate cookie
+ // mode preference set to make sure that setting the preference does
+ // not break interception as observed in bug 1336364.
+ // TODO: Refactor this test so it doesn't duplicate so much code logic.
+
+ SimpleTest.requestCompleteLog();
+
+ var registration;
+ function simpleRegister() {
+ return navigator.serviceWorker.register("fetch_event_worker.js", { scope: "./fetch" })
+ .then(swr => {
+ registration = swr;
+ return waitForState(swr.installing, 'activated');
+ });
+ }
+
+ 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();
+ });
+ }
+
+ const COOKIE_BEHAVIOR_REJECTFOREIGN = 1;
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["network.cookie.cookieBehavior", COOKIE_BEHAVIOR_REJECTFOREIGN],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_fetch_integrity.html b/dom/serviceworkers/test/test_fetch_integrity.html
new file mode 100644
index 0000000000..35879d5749
--- /dev/null
+++ b/dom/serviceworkers/test/test_fetch_integrity.html
@@ -0,0 +1,228 @@
+<!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="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 src="utils.js"></script>
+<script type="text/javascript">
+"use strict";
+
+let security_localizer =
+ stringBundleService.createBundle("chrome://global/locale/security/security.properties");
+
+let consoleScript;
+let monitorCallbacks = [];
+
+function registerConsoleMonitor() {
+ return new Promise(resolve => {
+ var url = SimpleTest.getTestFileURL("console_monitor.js");
+ consoleScript = SpecialPowers.loadChromeScript(url);
+
+ consoleScript.addMessageListener("ready", resolve);
+ consoleScript.addMessageListener("monitor", function(msg) {
+ for (let i = 0; i < monitorCallbacks.length;) {
+ if (monitorCallbacks[i](msg)) {
+ ++i;
+ } else {
+ monitorCallbacks.splice(i, 1);
+ }
+ }
+ });
+ consoleScript.sendAsyncMessage("load", {});
+ });
+}
+
+function unregisterConsoleMonitor() {
+ return new Promise(resolve => {
+ consoleScript.addMessageListener("unloaded", () => {
+ consoleScript.destroy();
+ resolve();
+ });
+ consoleScript.sendAsyncMessage("unload", {});
+ });
+}
+
+function registerConsoleMonitorCallback(callback) {
+ monitorCallbacks.push(callback);
+}
+
+function waitForMessages() {
+ let messages = [];
+
+ // process repeated paired arguments of: msgId, args
+ for (let i = 0; i < arguments.length; i += 3) {
+ let msgId = arguments[i];
+ let args = arguments[i + 1];
+ messages.push(security_localizer.formatStringFromName(msgId, args));
+ }
+
+ return new Promise(resolve => {
+ registerConsoleMonitorCallback(msg => {
+ for (let i = 0; i < messages.length; ++i) {
+ if (messages[i] == msg.errorMessage) {
+ messages.splice(i, 1);
+ break;
+ }
+ }
+
+ if (!messages.length) {
+ resolve();
+ return false;
+ }
+
+ return true;
+ });
+ });
+}
+
+function expect_security_console_message(/* msgId, args, ... */) {
+ let expectations = [];
+ // process repeated paired arguments of: msgId, args
+ for (let i = 0; i < arguments.length; i += 3) {
+ let msgId = arguments[i];
+ let args = arguments[i + 1];
+ let filename = arguments[i + 2];
+ expectations.push({
+ errorMessage: security_localizer.formatStringFromName(msgId, args),
+ sourceName: filename,
+ });
+ }
+ 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],
+ ["browser.newtab.preload", false],
+ ]});
+});
+
+add_task(async function test_integrity_serviceWorker() {
+ var filename = make_absolute_url("fetch.js");
+ var filename2 = make_absolute_url("fake.html");
+
+ let registration = await navigator.serviceWorker.register("fetch.js",
+ { scope: "./" });
+ await waitForState(registration.installing, "activated");
+
+ info("Test for mNavigationInterceptions.")
+ // The client_win will reload to another URL after opening filename2.
+ let client_win = window.open(filename2);
+
+ let expectedMessage = expect_security_console_message(
+ "MalformedIntegrityHash",
+ ["abc"],
+ filename,
+ "NoValidMetadata",
+ [""],
+ filename,
+ );
+ let expectedMessage2 = expect_security_console_message(
+ "MalformedIntegrityHash",
+ ["abc"],
+ filename,
+ "NoValidMetadata",
+ [""],
+ filename,
+ );
+
+ 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.
+ await fetch("fail.html");
+
+ await wait_for_expected_message(expectedMessage);
+
+ await wait_for_expected_message(expectedMessage2);
+
+ await registration.unregister();
+ client_win.close();
+});
+
+add_task(async function test_integrity_sharedWorker() {
+ var filename = make_absolute_url("sharedWorker_fetch.js");
+
+ await registerConsoleMonitor();
+
+ info("Attach 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();
+ }
+ }
+ });
+ await 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();
+ }
+ }
+ });
+ await waitForBothConnected;
+
+ let expectedMessage = waitForMessages(
+ "MalformedIntegrityHash",
+ ["abc"],
+ filename,
+ "NoValidMetadata",
+ [""],
+ filename,
+ );
+
+ let expectedMessage2 = waitForMessages(
+ "MalformedIntegrityHash",
+ ["abc"],
+ filename,
+ "NoValidMetadata",
+ [""],
+ filename,
+ );
+
+ 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();
+ }
+ }
+ });
+ await waitForSRIFailed;
+
+ await expectedMessage;
+ await expectedMessage2;
+
+ client_win.close();
+
+ await unregisterConsoleMonitor();
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_file_blob_response.html b/dom/serviceworkers/test/test_file_blob_response.html
new file mode 100644
index 0000000000..3aa72c3dda
--- /dev/null
+++ b/dom/serviceworkers/test/test_file_blob_response.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 1253777 - Test interception using file blob response body</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script src="utils.js"></script>
+<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 })
+ .then(function(swr) {
+ registration = swr;
+ return new waitForState(swr.installing, 'activated');
+ });
+ }
+
+ 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(evt) {
+ resolve(frame);
+ }, {once: true});
+ });
+ }
+
+ 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/serviceworkers/test/test_file_blob_upload.html b/dom/serviceworkers/test/test_file_blob_upload.html
new file mode 100644
index 0000000000..e60e65badd
--- /dev/null
+++ b/dom/serviceworkers/test/test_file_blob_upload.html
@@ -0,0 +1,146 @@
+<!--
+ 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 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 src="utils.js"></script>
+<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
+ return waitForState(swr.installing, 'activated', 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(evt) {
+ if (evt.data.status === 'READY') {
+ resolve();
+ } else {
+ reject(evt.data.result);
+ }
+ }, {once: true});
+ });
+ }
+
+ function postBlob(body) {
+ return new Promise(function(resolve, reject) {
+ window.addEventListener('message', function(evt) {
+ if (evt.data.status === 'OK') {
+ is(JSON.stringify(body), JSON.stringify(evt.data.result),
+ 'body echoed back correctly');
+ resolve();
+ } else {
+ reject(evt.data.result);
+ }
+ }, {once: true});
+
+ iframe.contentWindow.postMessage({ type: 'TEST', 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/serviceworkers/test/test_file_upload.html b/dom/serviceworkers/test/test_file_upload.html
new file mode 100644
index 0000000000..0c502686af
--- /dev/null
+++ b/dom/serviceworkers/test/test_file_upload.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 1424701 - Test for service worker + file upload</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<input id="input" type="file">
+<script class="testbody" type="text/javascript">
+
+function GetFormData(file) {
+ const formData = new FormData();
+ formData.append('file', file);
+ return formData;
+}
+
+async function onOpened(message) {
+ let input = document.getElementById("input");
+ SpecialPowers.wrap(input).mozSetFileArray([message.file]);
+ script.destroy();
+
+ let reg = await navigator.serviceWorker.register('sw_file_upload.js',
+ {scope: "." });
+ let serviceWorker = reg.installing || reg.waiting || reg.active;
+ await waitForState(serviceWorker, 'activated');
+
+ let res = await fetch('server_file_upload.sjs?clone=0', {
+ method: 'POST',
+ body: input.files[0],
+ });
+
+ let data = await res.clone().text();
+ ok(data.length, "We have data for an uncloned request!");
+
+ res = await fetch('server_file_upload.sjs?clone=1', {
+ method: 'POST',
+ // Make sure the underlying stream is a file stream
+ body: GetFormData(input.files[0]),
+ });
+
+ data = await res.clone().text();
+ ok(data.length, "We have data for a file-stream-backed cloned request!");
+
+ await reg.unregister();
+ SimpleTest.finish();
+}
+
+let url = SimpleTest.getTestFileURL("script_file_upload.js");
+let script = SpecialPowers.loadChromeScript(url);
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+]}).then(() => {
+ script.addMessageListener("file.opened", onOpened);
+ script.sendAsyncMessage("file.open");
+});
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_force_refresh.html b/dom/serviceworkers/test/test_force_refresh.html
new file mode 100644
index 0000000000..85332d3ecc
--- /dev/null
+++ b/dom/serviceworkers/test/test_force_refresh.html
@@ -0,0 +1,104 @@
+<!--
+ 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 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">
+ /**
+ *
+ */
+ let iframe;
+ let registration;
+
+ function start() {
+ return new Promise(resolve => {
+ const content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "sw_clients/refresher_compressed.html");
+
+ /*
+ * The initial iframe must be the _uncached_ version, which means its
+ * load must happen before the Service Worker's `activate` event.
+ * Rather than `waitUntil`-ing the Service Worker's `install` event
+ * until the load finishes (more concurrency, but involves coordinating
+ * `postMessage`s), just ensure the load finishes before registering
+ * the Service Worker (which is simpler).
+ */
+ iframe.onload = resolve;
+
+ content.appendChild(iframe);
+ }).then(async () => {
+ /*
+ * There's no need _here_ to explicitly wait for this Service Worker to be
+ * "activated"; this test will progress when the "READY"/"READY_CACHED"
+ * messages are received from the iframe, and the iframe will only send
+ * those messages once the Service Worker is "activated" (by chaining on
+ * its `navigator.serviceWorker.ready` promise).
+ */
+ registration = await navigator.serviceWorker.register(
+ "force_refresh_worker.js", { scope: "./sw_clients/" });
+ });
+ }
+
+ 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) {
+ return 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", "*");
+ }
+ }
+ }).then(() => document.getElementById("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],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_gzip_redirect.html b/dom/serviceworkers/test/test_gzip_redirect.html
new file mode 100644
index 0000000000..8119303ae7
--- /dev/null
+++ b/dom/serviceworkers/test/test_gzip_redirect.html
@@ -0,0 +1,88 @@
+<!--
+ 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 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 src="utils.js"></script>
+<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;
+ return waitForState(swr.installing, 'activated', 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/serviceworkers/test/test_hsts_upgrade_intercept.html b/dom/serviceworkers/test/test_hsts_upgrade_intercept.html
new file mode 100644
index 0000000000..59fef0ec14
--- /dev/null
+++ b/dom/serviceworkers/test/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 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/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/fetch/hsts/embedder.html";
+ break;
+ case 2:
+ iframe.src = "https://example.com/tests/dom/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/test_https_fetch.html b/dom/serviceworkers/test/test_https_fetch.html
new file mode 100644
index 0000000000..801d0c8a3a
--- /dev/null
+++ b/dom/serviceworkers/test/test_https_fetch.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>Bug 1133763 - test fetch event in HTTPS origins</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">
+<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/serviceworkers/test/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/serviceworkers/test/fetch/https/index.html";
+ } else if (e.data.status == "done") {
+ iframe.src = "https://example.com/tests/dom/serviceworkers/test/fetch/https/synth-sw.html";
+ } else if (e.data.status == "done-synth-sw") {
+ iframe.src = "https://example.com/tests/dom/serviceworkers/test/fetch/https/synth-window.html";
+ } else if (e.data.status == "done-synth-window") {
+ iframe.src = "https://example.com/tests/dom/serviceworkers/test/fetch/https/synth.html";
+ } else if (e.data.status == "done-synth") {
+ ios.offline = false;
+ iframe.src = "https://example.com/tests/dom/serviceworkers/test/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],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_https_fetch_cloned_response.html b/dom/serviceworkers/test/test_https_fetch_cloned_response.html
new file mode 100644
index 0000000000..19066297c5
--- /dev/null
+++ b/dom/serviceworkers/test/test_https_fetch_cloned_response.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 1133763 - test fetch event in HTTPS origins with a cloned response</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">
+<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/serviceworkers/test/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/serviceworkers/test/fetch/https/clonedresponse/index.html";
+ } else if (e.data.status == "done") {
+ ios.offline = false;
+ iframe.src = "https://example.com/tests/dom/serviceworkers/test/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],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_https_origin_after_redirect.html b/dom/serviceworkers/test/test_https_origin_after_redirect.html
new file mode 100644
index 0000000000..f0871950d8
--- /dev/null
+++ b/dom/serviceworkers/test/test_https_origin_after_redirect.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 the origin of a redirected response from a service worker</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">
+<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/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/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],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_https_origin_after_redirect_cached.html b/dom/serviceworkers/test/test_https_origin_after_redirect_cached.html
new file mode 100644
index 0000000000..fa580a8109
--- /dev/null
+++ b/dom/serviceworkers/test/test_https_origin_after_redirect_cached.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 the origin of a redirected response from a service worker</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">
+<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/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/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],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_https_synth_fetch_from_cached_sw.html b/dom/serviceworkers/test/test_https_synth_fetch_from_cached_sw.html
new file mode 100644
index 0000000000..444ef356dd
--- /dev/null
+++ b/dom/serviceworkers/test/test_https_synth_fetch_from_cached_sw.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 1156847 - test fetch event generating a synthesized response in HTTPS origins from a cached SW</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" 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/serviceworkers/test/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.remove();
+ iframe = null;
+ SpecialPowers.exactGC(function() {
+ iframe = document.createElement("iframe");
+ iframe.src = "https://example.com/tests/dom/serviceworkers/test/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/serviceworkers/test/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],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_imagecache.html b/dom/serviceworkers/test/test_imagecache.html
new file mode 100644
index 0000000000..52a793bfb9
--- /dev/null
+++ b/dom/serviceworkers/test/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 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/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/fetch/imagecache/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ iframe.src = "/tests/dom/serviceworkers/test/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/serviceworkers/test/test_imagecache_max_age.html b/dom/serviceworkers/test/test_imagecache_max_age.html
new file mode 100644
index 0000000000..fcb8d3e306
--- /dev/null
+++ b/dom/serviceworkers/test/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 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/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/test_importscript.html b/dom/serviceworkers/test/test_importscript.html
new file mode 100644
index 0000000000..c0a894cf3c
--- /dev/null
+++ b/dom/serviceworkers/test/test_importscript.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 service worker - script cache policy</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="content"></div>
+<script src="utils.js"></script>
+<script class="testbody" type="text/javascript">
+ function start() {
+ return navigator.serviceWorker.register("importscript_worker.js",
+ { scope: "./sw_clients/" })
+ .then(swr => waitForState(swr.installing, 'activated', swr))
+ .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/serviceworkers/test/test_importscript_mixedcontent.html b/dom/serviceworkers/test/test_importscript_mixedcontent.html
new file mode 100644
index 0000000000..15fe5e88b6
--- /dev/null
+++ b/dom/serviceworkers/test/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 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/serviceworkers/test/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/serviceworkers/test/fetch/importscript-mixedcontent/index.html";
+ } else if (e.data.status == "done") {
+ is(e.data.data, "good", "Mixed content blocking should work correctly for service workers");
+ iframe.src = "https://example.com/tests/dom/serviceworkers/test/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/serviceworkers/test/test_install_event.html b/dom/serviceworkers/test/test_install_event.html
new file mode 100644
index 0000000000..87f89725dc
--- /dev/null
+++ b/dom/serviceworkers/test/test_install_event.html
@@ -0,0 +1,143 @@
+<!--
+ 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 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(event) {
+ event.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/serviceworkers/test/test_install_event_gc.html b/dom/serviceworkers/test/test_install_event_gc.html
new file mode 100644
index 0000000000..eadab685f2
--- /dev/null
+++ b/dom/serviceworkers/test/test_install_event_gc.html
@@ -0,0 +1,120 @@
+<!--
+ 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 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 })
+ .then(swr => registration = swr);
+}
+
+function unregister() {
+ if (!registration) {
+ return undefined;
+ }
+ 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');
+ SpecialPowers.notifyObservers(null, 'child-cc-request');
+ SpecialPowers.notifyObservers(null, 'child-gc-request');
+ // (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],
+]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_installation_simple.html b/dom/serviceworkers/test/test_installation_simple.html
new file mode 100644
index 0000000000..69c9518ea0
--- /dev/null
+++ b/dom/serviceworkers/test/test_installation_simple.html
@@ -0,0 +1,208 @@
+<!--
+ 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 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() {
+ return SpecialPowers.pushPrefEnv({'set': [["dom.serviceWorkers.testing.enabled", false]] })
+ .then(function() {
+ return navigator.serviceWorker.register("/worker.js");
+ }).then(function(w) {
+ ok(false, "non-HTTPS pages cannot register ServiceWorkers");
+ }, function(e) {
+ ok(e.name === "TypeError", "navigator.serviceWorker should be undefined");
+ }).then(function() {
+ return SpecialPowers.popPrefEnv();
+ });
+ }
+
+ 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 regPromise;
+ function continueTest() {
+ regPromise = navigator.serviceWorker.register(
+ "worker2.js", { scope: "simpleregister/" });
+ }
+
+ 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);
+ regPromise.then(function(reg) {
+ reg.unregister().then(function(success) {
+ ok(success, "The worker should be unregistered successfully");
+ resolve();
+ }, function(error) {
+ dump("Error unregistering the worker: " + error + "\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]);
+ });
+
+ 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(sameOriginWorker)
+ .then(sameOriginScope)
+ .then(httpsOnly)
+ .then(readyPromise)
+ .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/serviceworkers/test/test_match_all.html b/dom/serviceworkers/test/test_match_all.html
new file mode 100644
index 0000000000..a1ee01507c
--- /dev/null
+++ b/dom/serviceworkers/test/test_match_all.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 982726 - test match_all not crashing</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script src="utils.js"></script>
+<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;
+ return waitForState(swr.installing, 'activated', 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/serviceworkers/test/test_match_all_advanced.html b/dom/serviceworkers/test/test_match_all_advanced.html
new file mode 100644
index 0000000000..b4359511f3
--- /dev/null
+++ b/dom/serviceworkers/test/test_match_all_advanced.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>Bug 982726 - Test matchAll with multiple clients</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script src="utils.js"></script>
+<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;
+ return waitForState(swr.installing, 'activated');
+ }).then(_ => {
+ 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/serviceworkers/test/test_match_all_client_id.html b/dom/serviceworkers/test/test_match_all_client_id.html
new file mode 100644
index 0000000000..0294c00aba
--- /dev/null
+++ b/dom/serviceworkers/test/test_match_all_client_id.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 1058311 - Test matchAll client id </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script src="utils.js"></script>
+<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;
+ return waitForState(swr.installing, 'activated', 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/serviceworkers/test/test_match_all_client_properties.html b/dom/serviceworkers/test/test_match_all_client_properties.html
new file mode 100644
index 0000000000..c8a0b448c2
--- /dev/null
+++ b/dom/serviceworkers/test/test_match_all_client_properties.html
@@ -0,0 +1,101 @@
+<!--
+ 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 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 src="utils.js"></script>
+<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;
+ return waitForState(swr.installing, 'activated', 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/serviceworkers/test/test_navigationPreload_disable_crash.html b/dom/serviceworkers/test/test_navigationPreload_disable_crash.html
new file mode 100644
index 0000000000..ea6439284d
--- /dev/null
+++ b/dom/serviceworkers/test/test_navigationPreload_disable_crash.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>Failure to create a Promise shouldn't crash</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+ async function runTest() {
+ const iframe = document.createElement('iframe');
+ document.getElementById("content").appendChild(iframe);
+
+ const serviceWorker = iframe.contentWindow.navigator.serviceWorker;
+ const worker = await iframe.contentWindow.navigator.serviceWorker.register("empty.js", {});
+
+ iframe.remove();
+
+ // We can't wait for this promise to settle, because the global's
+ // browsing context has been discarded when the iframe was removed.
+ // We're just checking if this call crashes, which would happen
+ // immediately, so ignoring the promise should be fine.
+ worker.navigationPreload.disable();
+ ok(true, "navigationPreload.disable() failed but didn't crash.");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ // We can't call unregister on the worker after its browsing context has been
+ // discarded, so use SpecialPowers.removeAllServiceWorkerData.
+ SimpleTest.registerCleanupFunction(() => SpecialPowers.removeAllServiceWorkerData());
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.navigationPreload.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_navigator.html b/dom/serviceworkers/test/test_navigator.html
new file mode 100644
index 0000000000..aaac04e926
--- /dev/null
+++ b/dom/serviceworkers/test/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 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],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, function() {
+ checkEnabled();
+ SimpleTest.finish();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_nofetch_handler.html b/dom/serviceworkers/test/test_nofetch_handler.html
new file mode 100644
index 0000000000..0725a68561
--- /dev/null
+++ b/dom/serviceworkers/test/test_nofetch_handler.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bugs 1181127 and 1325101</title>
+ <script src="/tests/SimpleTest/SimpleTest.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=1181127">Mozilla Bug 1181127</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1181127">Mozilla Bug 1325101</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script src="utils.js"></script>
+<script class="testbody" type="text/javascript">
+
+add_task(function setupPrefs() {
+ return SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ // Make sure the event handler during the install event persists. This ensures
+ // the reason for which the interception doesn't occur is because of the
+ // handlesFetch=false flag from ServiceWorkerInfo.
+ ["dom.serviceWorkers.idle_timeout", 299999],
+ ]});
+});
+
+var iframeg;
+function create_iframe(url) {
+ return new Promise(function(res) {
+ iframe = document.createElement('iframe');
+ iframe.src = url;
+ iframe.onload = function() { res(iframe) }
+ document.body.appendChild(iframe);
+ iframeg = iframe;
+ })
+}
+
+add_task(async function test_nofetch_worker() {
+ let registration = await navigator.serviceWorker.register(
+ "nofetch_handler_worker.js", { scope: "./nofetch_handler_worker/"} )
+ .then(swr => waitForState(swr.installing, 'activated', swr));
+
+ let iframe = await create_iframe("./nofetch_handler_worker/doesnt_exist.html");
+ ok(!iframe.contentDocument.body.innerHTML.includes("intercepted"), "Request was not intercepted.");
+
+ await SpecialPowers.popPrefEnv();
+ await registration.unregister();
+});
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_not_intercept_plugin.html b/dom/serviceworkers/test/test_not_intercept_plugin.html
new file mode 100644
index 0000000000..4e7654deea
--- /dev/null
+++ b/dom/serviceworkers/test/test_not_intercept_plugin.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 1187766 - Test loading plugins scenarios with fetch interception.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script src="utils.js"></script>
+<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 waitForState(swr.installing, 'activated');
+ });
+ }
+
+ 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.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_notification_constructor_error.html b/dom/serviceworkers/test/test_notification_constructor_error.html
new file mode 100644
index 0000000000..46d93e781f
--- /dev/null
+++ b/dom/serviceworkers/test/test_notification_constructor_error.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 XXXXXXX - Check that Notification constructor throws in ServiceWorkerGlobalScope</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/NotificationTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<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/serviceworkers/test/test_notification_get.html b/dom/serviceworkers/test/test_notification_get.html
new file mode 100644
index 0000000000..6c3d1b10c7
--- /dev/null
+++ b/dom/serviceworkers/test/test_notification_get.html
@@ -0,0 +1,136 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>ServiceWorkerRegistration.getNotifications() on main thread and worker thread.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/NotificationTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<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.");
+ });
+ }
+
+ async function unregisterSW() {
+ const reg = await navigator.serviceWorker.getRegistration("./notification/");
+ await reg.unregister();
+ }
+
+ 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() {
+ 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 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) {
+ 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);
+ })
+ }
+
+ function testGetWorker() {
+ todo(false, "navigator.serviceWorker is not available on workers yet");
+ return Promise.resolve();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ MockServices.register();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["notification.prompt.testing", true],
+ ]}, function() {
+ registerSW()
+ .then(testGet)
+ .then(testGetWorker)
+ .then(testDismiss)
+ .then(unregisterSW)
+ .then(function() {
+ MockServices.unregister();
+ SimpleTest.finish();
+ });
+ });
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_notification_openWindow.html b/dom/serviceworkers/test/test_notification_openWindow.html
new file mode 100644
index 0000000000..5180f20f37
--- /dev/null
+++ b/dom/serviceworkers/test/test_notification_openWindow.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1578070</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="utils.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/NotificationTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+// eslint-disable-next-line mozilla/no-addtask-setup
+add_task(async function setup() {
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["notification.prompt.testing", true],
+ ["dom.serviceWorkers.disable_open_click_delay", 1000],
+ ["dom.serviceWorkers.idle_timeout", 299999],
+ ["dom.serviceWorkers.idle_extended_timeout", 299999]
+ ]});
+
+ MockServices.register();
+ SimpleTest.requestFlakyTimeout("Mock alert service dispatches show and click events.");
+ SimpleTest.registerCleanupFunction(() => {
+ MockServices.unregister();
+ });
+});
+
+add_task(async function test() {
+ info("Registering service worker.");
+ let swr = await navigator.serviceWorker.register("notification_openWindow_worker.js");
+ await waitForState(swr.installing, "activated");
+
+ SimpleTest.registerCleanupFunction(async () => {
+ await swr.unregister();
+ navigator.serviceWorker.onmessage = null;
+ });
+
+ for (let prefValue of [
+ SpecialPowers.Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW,
+ SpecialPowers.Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW,
+ SpecialPowers.Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
+ ]) {
+ if (prefValue == SpecialPowers.Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW) {
+ // Let's open a new tab and focus on it. When the service
+ // worker notification is shown, the document will open in the focused tab.
+ // If we don't open a new tab, the document will be opened in the
+ // current test-runner tab and mess up the test setup.
+ window.open("");
+ }
+ info(`Setting browser.link.open_newwindow to ${prefValue}.`);
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.link.open_newwindow", prefValue]],
+ });
+
+ // The onclicknotification handler uses Clients.openWindow() to open a new
+ // window. This newly created window will attempt to open another window with
+ // Window.open() and some arbitrary URL. We crash before the second window
+ // finishes loading.
+ info("Showing notification.");
+ await swr.showNotification("notification");
+
+ info("Waiting for \"DONE\" from worker.");
+ await new Promise(resolve => {
+ navigator.serviceWorker.onmessage = event => {
+ if (event.data !== "DONE") {
+ ok(false, `Unexpected message from service worker: ${JSON.stringify(event.data)}`);
+ }
+ resolve();
+ }
+ });
+
+ // If we make it here, then we didn't crash.
+ ok(true, "Didn't crash!");
+
+ navigator.serviceWorker.onmessage = null;
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_notificationclick-otherwindow.html b/dom/serviceworkers/test/test_notificationclick-otherwindow.html
new file mode 100644
index 0000000000..5f35757929
--- /dev/null
+++ b/dom/serviceworkers/test/test_notificationclick-otherwindow.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=916893
+-->
+<head>
+ <title>Bug 1114554 - Test ServiceWorkerGlobalScope.notificationclick event.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/NotificationTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1114554">Bug 1114554</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script src="utils.js"></script>
+<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();
+ navigator.serviceWorker.register("notificationclick.js", { scope: "notificationclick-otherwindow.html" }).then(function(reg) {
+ registration = reg;
+ return waitForState(reg.installing, 'activated');
+ }, function(e) {
+ ok(false, "registration should have passed!");
+ }).then(() => {
+ testFrame('notificationclick-otherwindow.html');
+ });
+ };
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["notification.prompt.testing", true],
+ ]}, runTest);
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_notificationclick.html b/dom/serviceworkers/test/test_notificationclick.html
new file mode 100644
index 0000000000..ea85ae56c7
--- /dev/null
+++ b/dom/serviceworkers/test/test_notificationclick.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=916893
+-->
+<head>
+ <title>Bug 1114554 - Test ServiceWorkerGlobalScope.notificationclick event.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/NotificationTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1114554">Bug 1114554</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script src="utils.js"></script>
+<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();
+ navigator.serviceWorker.register("notificationclick.js", { scope: "notificationclick.html" }).then(function(reg) {
+ registration = reg;
+ return waitForState(reg.installing, 'activated');
+ }, function(e) {
+ ok(false, "registration should have passed!");
+ }).then(() => {
+ // Now that we know the document will be controlled, create the frame.
+ testFrame('notificationclick.html');
+ });
+ };
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["notification.prompt.testing", true],
+ ]}, runTest);
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_notificationclick_focus.html b/dom/serviceworkers/test/test_notificationclick_focus.html
new file mode 100644
index 0000000000..2ce0c6a809
--- /dev/null
+++ b/dom/serviceworkers/test/test_notificationclick_focus.html
@@ -0,0 +1,64 @@
+<!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 src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/NotificationTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1114554">Bug 1114554</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script src="utils.js"></script>
+<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();
+ navigator.serviceWorker.register("notificationclick_focus.js", { scope: "notificationclick_focus.html" }).then(function(reg) {
+ registration = reg;
+ return waitForState(reg.installing, 'activated');
+ }, function(e) {
+ ok(false, "registration should have passed!");
+ }).then(() => {
+ testFrame('notificationclick_focus.html');
+ });
+ };
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["notification.prompt.testing", true],
+ ["dom.serviceWorkers.disable_open_click_delay", 1000],
+ ]}, runTest);
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_notificationclose.html b/dom/serviceworkers/test/test_notificationclose.html
new file mode 100644
index 0000000000..936501fafd
--- /dev/null
+++ b/dom/serviceworkers/test/test_notificationclose.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1265841
+-->
+<head>
+ <title>Bug 1265841 - Test ServiceWorkerGlobalScope.notificationclose event.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/NotificationTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265841">Bug 1265841</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script src="utils.js"></script>
+<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(data) {
+ window.callback = null;
+ document.body.removeChild(iframe);
+ iframe = null;
+ ok(data.result, "Got notificationclose event with correct data.");
+ ok(!data.windowOpened,
+ "Shouldn't allow to openWindow in notificationclose");
+ MockServices.unregister();
+ registration.unregister().then(function() {
+ SimpleTest.finish();
+ });
+ };
+ document.body.appendChild(iframe);
+ }
+
+ var registration;
+
+ function runTest() {
+ MockServices.register();
+ navigator.serviceWorker.register("notificationclose.js", { scope: "notificationclose.html" }).then(function(reg) {
+ registration = reg;
+ return waitForState(reg.installing, 'activated');
+ }, function(e) {
+ ok(false, "registration should have passed!");
+ }).then(() => {
+ testFrame('notificationclose.html');
+ });
+ };
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["notification.prompt.testing", true],
+ ]}, runTest);
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_onmessageerror.html b/dom/serviceworkers/test/test_onmessageerror.html
new file mode 100644
index 0000000000..425b890951
--- /dev/null
+++ b/dom/serviceworkers/test/test_onmessageerror.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test onmessageerror event handlers</title>
+ </head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="utils.js"></script>
+ <script>
+ /**
+ * Test that ServiceWorkerGlobalScope and ServiceWorkerContainer handle
+ * `messageerror` events, using a test helper class `StructuredCloneTester`.
+ * Intances of this class can be configured to fail to serialize or
+ * deserialize, as it's difficult to artificially create the case where an
+ * object successfully serializes but fails to deserialize (which can be
+ * caused by out-of-memory failures or the target global not supporting a
+ * serialized interface).
+ */
+
+ let registration = null;
+ let serviceWorker = null;
+ let serviceWorkerContainer = null;
+ const swScript = 'onmessageerror_worker.js';
+
+ add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ['dom.serviceWorkers.enabled', true],
+ ['dom.serviceWorkers.testing.enabled', true],
+ ['dom.testing.structuredclonetester.enabled', true],
+ ],
+ });
+
+ swContainer = navigator.serviceWorker;
+
+ registration = await swContainer.register(swScript);
+ ok(registration, 'Service Worker regsisters');
+
+ serviceWorker = registration.installing;
+ await waitForState(serviceWorker, 'activated');
+ }); // setup
+
+ add_task(async () => {
+ const serializable = true;
+ const deserializable = true;
+ let sct = new StructuredCloneTester(serializable, deserializable);
+
+ const p = new Promise((resolve, reject) => {
+ function onMessage(e) {
+ const expectedBehavior = 'Serializable and deserializable ' +
+ 'StructuredCloneTester serializes and deserializes';
+
+ is(e.data.received, 'message', expectedBehavior);
+ swContainer.removeEventListener('message', onMessage);
+ resolve();
+ }
+
+ swContainer.addEventListener('message', onMessage);
+ });
+
+ serviceWorker.postMessage({ serializable, deserializable, sct });
+
+ await p;
+ });
+
+ add_task(async () => {
+ const serializable = false;
+ // if it's not serializable, being deserializable or not doesn't matter
+ const deserializable = false;
+ let sct = new StructuredCloneTester(serializable, deserializable);
+
+ try {
+ serviceWorker.postMessage({ serializable, deserializable, sct });
+ ok(false, 'StructuredCloneTester serialization should have thrown -- ' +
+ 'this line should not have been reached.');
+ } catch (e) {
+ const expectedBehavior = 'Unserializable StructuredCloneTester fails ' +
+ `to send, with exception name: ${e.name}`;
+ is(e.name, 'DataCloneError', expectedBehavior);
+ }
+ });
+
+ add_task(async () => {
+ const serializable = true;
+ const deserializable = false;
+ let sct = new StructuredCloneTester(serializable, deserializable);
+
+ const p = new Promise((resolve, reject) => {
+ function onMessage(e) {
+ const expectedBehavior = 'ServiceWorkerGlobalScope handles ' +
+ 'messageerror events';
+
+ is(e.data.received, 'messageerror', expectedBehavior);
+ swContainer.removeEventListener('message', onMessage);
+ resolve();
+ }
+
+ swContainer.addEventListener('message', onMessage);
+ });
+
+ serviceWorker.postMessage({ serializable, deserializable, sct });
+
+ await p;
+ }); // test ServiceWorkerGlobalScope onmessageerror
+
+ add_task(async () => {
+ const p = new Promise((resolve, reject) => {
+ function onMessageError(e) {
+ ok(true, 'ServiceWorkerContainer handles messageerror events');
+ swContainer.removeEventListener('messageerror', onMessageError);
+ resolve();
+ }
+
+ swContainer.addEventListener('messageerror', onMessageError);
+ });
+
+ serviceWorker.postMessage('send-bad-message');
+
+ await p;
+ }); // test ServiceWorkerContainer onmessageerror
+
+ add_task(async () => {
+ await SpecialPowers.popPrefEnv();
+ ok(await registration.unregister(), 'Service Worker unregisters');
+ }); // teardown
+ </script>
+ <body>
+ </body>
+</html>
diff --git a/dom/serviceworkers/test/test_opaque_intercept.html b/dom/serviceworkers/test/test_opaque_intercept.html
new file mode 100644
index 0000000000..095f2e5f63
--- /dev/null
+++ b/dom/serviceworkers/test/test_opaque_intercept.html
@@ -0,0 +1,92 @@
+<!--
+ 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 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.");
+
+ var iframe = document.createElement("iframe");
+ iframe.setAttribute('src', "sw_clients/refresher.html");
+ content.appendChild(iframe);
+
+ // Our service worker waits for us to finish installing. If it didn't do
+ // this, then loading our frame would race with it becoming active,
+ // possibly intercepting the first load of the iframe. This guarantees
+ // that our iframe will load first directly from the network. Note that
+ // refresher.html explicitly waits for the service worker to transition to
+ // active.
+ registration.installing.postMessage("ready");
+
+ 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],
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_openWindow.html b/dom/serviceworkers/test/test_openWindow.html
new file mode 100644
index 0000000000..85e5ea26da
--- /dev/null
+++ b/dom/serviceworkers/test/test_openWindow.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1172870
+-->
+<head>
+ <title>Bug 1172870 - Test clients.openWindow</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/MockServices.js"></script>
+ <script type="text/javascript" src="/tests/dom/notification/test/mochitest/NotificationTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1172870">Bug 1172870</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script src="utils.js"></script>
+<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 waitForState(swr.installing, 'activated', 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;
+ for (i = 0; i < event.data.length; i++) {
+ ok(event.data[i].result, event.data[i].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({})
+ // 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.testing.enabled", true],
+ ["notification.prompt.testing", true],
+ ["dom.serviceWorkers.disable_open_click_delay", 1000],
+ ["dom.serviceWorkers.idle_timeout", 299999],
+ ["dom.serviceWorkers.idle_extended_timeout", 299999],
+ ["dom.securecontext.allowlist", "mochi.test,example.com"],
+ ]}, runTest);
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_origin_after_redirect.html b/dom/serviceworkers/test/test_origin_after_redirect.html
new file mode 100644
index 0000000000..e9cd6ea929
--- /dev/null
+++ b/dom/serviceworkers/test/test_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 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/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/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.security.https_first", false],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_origin_after_redirect_cached.html b/dom/serviceworkers/test/test_origin_after_redirect_cached.html
new file mode 100644
index 0000000000..a7c36d24d8
--- /dev/null
+++ b/dom/serviceworkers/test/test_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 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/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/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.security.https_first", false],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_origin_after_redirect_to_https.html b/dom/serviceworkers/test/test_origin_after_redirect_to_https.html
new file mode 100644
index 0000000000..2e0173cefd
--- /dev/null
+++ b/dom/serviceworkers/test/test_origin_after_redirect_to_https.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 the origin of a redirected response from a service worker</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">
+<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/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/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],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_origin_after_redirect_to_https_cached.html b/dom/serviceworkers/test/test_origin_after_redirect_to_https_cached.html
new file mode 100644
index 0000000000..12a88865c2
--- /dev/null
+++ b/dom/serviceworkers/test/test_origin_after_redirect_to_https_cached.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 the origin of a redirected response from a service worker</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">
+<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/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/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],
+ ]}, runTest);
+ };
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_post_message.html b/dom/serviceworkers/test/test_post_message.html
new file mode 100644
index 0000000000..b72f948dd6
--- /dev/null
+++ b/dom/serviceworkers/test/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 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 src="utils.js"></script>
+<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 => waitForState(swr.installing, 'activated', swr))
+ .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.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_post_message_advanced.html b/dom/serviceworkers/test/test_post_message_advanced.html
new file mode 100644
index 0000000000..580dfd3f07
--- /dev/null
+++ b/dom/serviceworkers/test/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 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 src="utils.js"></script>
+<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(blobToRead) {
+ return new Promise(function(resolve, reject) {
+ var reader = new FileReader();
+ reader.onloadend = () => resolve(reader.result);
+ reader.readAsText(blobToRead);
+ });
+ }
+
+ 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 => waitForState(swr.installing, 'activated', swr))
+ .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(object, test) {
+ var p = new Promise(function(res, rej) {
+ window.onmessage = function(e) {
+ if (e.data === "READY") {
+ registration.active.postMessage(object)
+ } else {
+ test(object, 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.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_post_message_source.html b/dom/serviceworkers/test/test_post_message_source.html
new file mode 100644
index 0000000000..b72ebe3a7c
--- /dev/null
+++ b/dom/serviceworkers/test/test_post_message_source.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 1142015 - Test service worker post message source </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+ var 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.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_privateBrowsing.html b/dom/serviceworkers/test/test_privateBrowsing.html
new file mode 100644
index 0000000000..e33272d641
--- /dev/null
+++ b/dom/serviceworkers/test/test_privateBrowsing.html
@@ -0,0 +1,105 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for ServiceWorker - Private Browsing</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+</head>
+<body>
+
+<script type="application/javascript">
+const {BrowserTestUtils} = ChromeUtils.importESModule(
+ "resource://testing-common/BrowserTestUtils.sys.mjs"
+);
+
+var mainWindow;
+
+var contentPage = "http://mochi.test:8888/chrome/dom/workers/test/empty.html";
+var workerScope = "http://mochi.test:8888/chrome/dom/serviceworkers/test/";
+var workerURL = workerScope + "worker.js";
+
+function testOnWindow(aIsPrivate, aCallback) {
+ var win = mainWindow.OpenBrowserWindow({private: aIsPrivate});
+ win.addEventListener("load", function() {
+ win.addEventListener("DOMContentLoaded", function onInnerLoad() {
+ if (win.content.location.href != contentPage) {
+ BrowserTestUtils.startLoadingURIString(win.gBrowser, contentPage);
+ return;
+ }
+
+ win.removeEventListener("DOMContentLoaded", onInnerLoad, true);
+ SimpleTest.executeSoon(function() { aCallback(win); });
+ }, true);
+ }, {capture: true, once: true});
+}
+
+function setupWindow() {
+ mainWindow = window.browsingContext.topChromeWindow;
+ runTest();
+}
+
+var wN;
+var registration;
+var wP;
+
+function testPrivateWindow() {
+ testOnWindow(true, function(aWin) {
+ wP = aWin;
+ ok(!wP.content.eval('"serviceWorker" in 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/serviceworkers/test/test_register_base.html b/dom/serviceworkers/test/test_register_base.html
new file mode 100644
index 0000000000..3a1f2f2621
--- /dev/null
+++ b/dom/serviceworkers/test/test_register_base.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 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() {
+ ok(!("serviceWorker" in navigator), "ServiceWorkerContainer shouldn't be defined");
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ]}, runTest);
+ };
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_register_https_in_http.html b/dom/serviceworkers/test/test_register_https_in_http.html
new file mode 100644
index 0000000000..096c3733a0
--- /dev/null
+++ b/dom/serviceworkers/test/test_register_https_in_http.html
@@ -0,0 +1,45 @@
+<!--
+ 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 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/serviceworkers/test/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],
+ ]}, runTest);
+ };
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_sandbox_intercept.html b/dom/serviceworkers/test/test_sandbox_intercept.html
new file mode 100644
index 0000000000..2aa120994f
--- /dev/null
+++ b/dom/serviceworkers/test/test_sandbox_intercept.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 1142727 - Test that sandboxed iframes are not intercepted</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">
+<iframe id="normal-frame"></iframe>
+<iframe sandbox="allow-scripts" id="sandbox-frame"></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ var normalFrame;
+ var sandboxFrame;
+ function runTest() {
+ normalFrame = document.getElementById("normal-frame");
+ sandboxFrame = document.getElementById("sandbox-frame");
+ normalFrame.src = "/tests/dom/serviceworkers/test/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") {
+ normalFrame.src = "about:blank";
+ sandboxFrame.src = "/tests/dom/serviceworkers/test/fetch/sandbox/index.html";
+ } else if (e.data.status == "done") {
+ sandboxFrame.src = "about:blank";
+ normalFrame.src = "/tests/dom/serviceworkers/test/fetch/sandbox/unregister.html";
+ } else if (e.data.status == "unregistrationdone") {
+ normalFrame.src = "about:blank";
+ 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/serviceworkers/test/test_sanitize.html b/dom/serviceworkers/test/test_sanitize.html
new file mode 100644
index 0000000000..dd6bd42c8f
--- /dev/null
+++ b/dom/serviceworkers/test/test_sanitize.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 1080109 - Clear ServiceWorker registrations for all domains</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ function 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;
+ is(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/serviceworkers/test/test_sanitize_domain.html b/dom/serviceworkers/test/test_sanitize_domain.html
new file mode 100644
index 0000000000..d0f5f7f69a
--- /dev/null
+++ b/dom/serviceworkers/test/test_sanitize_domain.html
@@ -0,0 +1,89 @@
+<!--
+ 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 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/serviceworkers/test/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/serviceworkers/test/sanitize/frame.html").then(function(body) {
+ is(body, "intercepted", "Expected serviceworker to intercept request");
+ });
+ }).then(function() {
+ return 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/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/test_scopes.html b/dom/serviceworkers/test/test_scopes.html
new file mode 100644
index 0000000000..77e997766d
--- /dev/null
+++ b/dom/serviceworkers/test/test_scopes.html
@@ -0,0 +1,143 @@
+<!--
+ 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 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);
+ }
+
+ async function testScopes() {
+ function chromeScriptSource() {
+ /* eslint-env mozilla/chrome-script */
+
+ let swm = Cc["@mozilla.org/serviceworkers/manager;1"]
+ .getService(Ci.nsIServiceWorkerManager);
+ let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ addMessageListener("getScope", (msg) => {
+ let principal = secMan.createContentPrincipalFromOrigin(msg.principal);
+ try {
+ return { scope: swm.getScopeForUrl(principal, msg.path) };
+ } catch (e) {
+ return { exception: e.message };
+ }
+ });
+ }
+
+ let chromeScript = SpecialPowers.loadChromeScript(chromeScriptSource);
+ let docPrincipal = SpecialPowers.wrap(document).nodePrincipal.spec;
+
+ getScope = async (path) => {
+ let rv = await chromeScript.sendQuery("getScope", { principal: docPrincipal, path });
+ if (rv.exception)
+ throw rv.exception;
+ return rv.scope;
+ };
+
+ var base = new URL(".", document.baseURI);
+
+ function p(s) {
+ return base + s;
+ }
+
+ async function fail(fn) {
+ try {
+ await getScope(p("index.html"));
+ ok(false, "No registration");
+ } catch(e) {
+ ok(true, "No registration");
+ }
+ }
+
+ is(await getScope(p("sub.html")), p("sub"), "Scope should match");
+ is(await getScope(p("sub/dir.html")), p("sub/dir.html"), "Scope should match");
+ is(await getScope(p("sub/dir")), p("sub/dir"), "Scope should match");
+ is(await getScope(p("sub/dir/foo")), p("sub/dir/"), "Scope should match");
+ is(await getScope(p("sub/dir/afoo")), p("sub/dir/a"), "Scope should match");
+ is(await getScope(p("star*wars")), p("star*"), "Scope should match");
+ is(await getScope(p("scope/some_file.html")), p("scope/"), "Scope should match");
+ await fail("index.html");
+ await fail("sua.html");
+ await fail("star/a.html");
+ }
+
+ 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/serviceworkers/test/test_script_loader_intercepted_js_cache.html b/dom/serviceworkers/test/test_script_loader_intercepted_js_cache.html
new file mode 100644
index 0000000000..d0073705bb
--- /dev/null
+++ b/dom/serviceworkers/test/test_script_loader_intercepted_js_cache.html
@@ -0,0 +1,224 @@
+<!DOCTYPE html>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1350359 -->
+<!-- The JS bytecode cache is not supposed to be observable. To make it
+ observable, the ScriptLoader is instrumented to trigger events on the
+ script tag. These events are followed to reconstruct the code path taken by
+ the script loader and associate a simple name which is checked in these
+ test cases.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for saving and loading bytecode in/from the necko cache</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="utils.js"></script>
+ <script type="application/javascript">
+
+ // This is the state machine of the trace events produced by the
+ // ScriptLoader. This state machine is used to give a name to each
+ // code path, such that we can assert each code path with a single word.
+ var scriptLoaderStateMachine = {
+ "scriptloader_load_source": {
+ "scriptloader_execute": {
+ "scriptloader_encode": {
+ "scriptloader_bytecode_saved": "bytecode_saved",
+ "scriptloader_bytecode_failed": "bytecode_failed"
+ },
+ "scriptloader_no_encode": "source_exec"
+ }
+ },
+ "scriptloader_load_bytecode": {
+ "scriptloader_fallback": {
+ // Replicate the top-level state machine without
+ // "scriptloader_load_bytecode" transition.
+ "scriptloader_load_source": {
+ "scriptloader_execute": {
+ "scriptloader_encode": {
+ "scriptloader_bytecode_saved": "fallback_bytecode_saved",
+ "scriptloader_bytecode_failed": "fallback_bytecode_failed"
+ },
+ "scriptloader_no_encode": "fallback_source_exec"
+ }
+ }
+ },
+ "scriptloader_execute": "bytecode_exec"
+ }
+ };
+
+ var gScript = SpecialPowers.
+ loadChromeScript('http://mochi.test:8888/tests/dom/serviceworkers/test/file_js_cache_cleanup.js');
+
+ function WaitForScriptTagEvent(url) {
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+
+ var stateMachine = scriptLoaderStateMachine;
+ var stateHistory = [];
+ var stateMachineResolve, stateMachineReject;
+ var statePromise = new Promise((resolve, reject) => {
+ stateMachineResolve = resolve;
+ stateMachineReject = reject;
+ });
+ var ping = 0;
+
+ // Walk the script loader state machine with the emitted events.
+ function log_event(evt) {
+ // If we have multiple script tags in the loaded source, make sure
+ // we only watch a single one.
+ if (evt.target.id != "watchme")
+ return;
+
+ dump("## ScriptLoader event: " + evt.type + "\n");
+ stateHistory.push(evt.type)
+ if (typeof stateMachine == "object")
+ stateMachine = stateMachine[evt.type];
+ if (typeof stateMachine == "string") {
+ // We arrived to a final state, report the name of it.
+ var result = stateMachine;
+ if (ping) {
+ result = `${result} & ping(=${ping})`;
+ }
+ stateMachineResolve(result);
+ } else if (stateMachine === undefined) {
+ // We followed an unknown transition, report the known history.
+ stateMachineReject(stateHistory);
+ }
+ }
+
+ var iwin = iframe.contentWindow;
+ iwin.addEventListener("scriptloader_load_source", log_event);
+ iwin.addEventListener("scriptloader_load_bytecode", log_event);
+ iwin.addEventListener("scriptloader_generate_bytecode", log_event);
+ iwin.addEventListener("scriptloader_execute", log_event);
+ iwin.addEventListener("scriptloader_encode", log_event);
+ iwin.addEventListener("scriptloader_no_encode", log_event);
+ iwin.addEventListener("scriptloader_bytecode_saved", log_event);
+ iwin.addEventListener("scriptloader_bytecode_failed", log_event);
+ iwin.addEventListener("scriptloader_fallback", log_event);
+ iwin.addEventListener("ping", (evt) => {
+ ping += 1;
+ dump(`## Content event: ${evt.type} (=${ping})\n`);
+ });
+ iframe.src = url;
+
+ statePromise.then(() => {
+ document.body.removeChild(iframe);
+ });
+ return statePromise;
+ }
+
+ promise_test(async function() {
+ // Setting dom.expose_test_interfaces pref causes the
+ // nsScriptLoadRequest to fire event on script tags, with information
+ // about its internal state. The ScriptLoader source send events to
+ // trace these and resolve a promise with the path taken by the
+ // script loader.
+ //
+ // Setting dom.script_loader.bytecode_cache.strategy to -1 causes the
+ // nsScriptLoadRequest to force all the conditions necessary to make a
+ // script be saved as bytecode in the alternate data storage provided
+ // by the channel (necko cache).
+ await SpecialPowers.pushPrefEnv({set: [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ['dom.script_loader.bytecode_cache.enabled', true],
+ ['dom.expose_test_interfaces', true],
+ ['dom.script_loader.bytecode_cache.strategy', -1]
+ ]});
+
+ // Register the service worker that perform the pass-through fetch.
+ var registration = await navigator.serviceWorker
+ .register("fetch.js", {scope: "./"});
+ let sw = registration.installing || registration.active;
+
+ // wait for service worker be activated
+ await waitForState(sw, 'activated');
+
+ await testCheckTheJSBytecodeCache();
+ await testSavebytecodeAfterTheInitializationOfThePage();
+ await testDoNotSaveBytecodeOnCompilationErrors();
+
+ await registration.unregister();
+ await teardown();
+ });
+
+ function teardown() {
+ return new Promise((resolve, reject) => {
+ gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() {
+ gScript.removeMessageListener("teardown-complete", teardownCompleteHandler);
+ gScript.destroy();
+ resolve();
+ });
+ gScript.sendAsyncMessage("teardown");
+ });
+ }
+
+ async function testCheckTheJSBytecodeCache() {
+ dump("## Test: Check the JS bytecode cache\n");
+
+ // Load the test page, and verify that the code path taken by the
+ // nsScriptLoadRequest corresponds to the code path which is loading a
+ // source and saving it as bytecode.
+ var stateMachineResult = WaitForScriptTagEvent("file_js_cache.html");
+ assert_equals(await stateMachineResult, "bytecode_saved",
+ "[1] ScriptLoadRequest status after the first visit");
+
+ // Reload the same test page, and verify that the code path taken by
+ // the nsScriptLoadRequest corresponds to the code path which is
+ // loading bytecode and executing it.
+ stateMachineResult = WaitForScriptTagEvent("file_js_cache.html");
+ assert_equals(await stateMachineResult, "bytecode_exec",
+ "[2] ScriptLoadRequest status after the second visit");
+
+ // Load another page which loads the same script with an SRI, while
+ // the cached bytecode does not have any. This should fallback to
+ // loading the source before saving the bytecode once more.
+ stateMachineResult = WaitForScriptTagEvent("file_js_cache_with_sri.html");
+ assert_equals(await stateMachineResult, "fallback_bytecode_saved",
+ "[3] ScriptLoadRequest status after the SRI hash");
+
+ // Loading a page, which has the same SRI should verify the SRI and
+ // continue by executing the bytecode.
+ var stateMachineResult1 = WaitForScriptTagEvent("file_js_cache_with_sri.html");
+
+ // Loading a page which does not have a SRI while we have one in the
+ // cache should not change anything. We should also be able to load
+ // the cache simultanesouly.
+ var stateMachineResult2 = WaitForScriptTagEvent("file_js_cache.html");
+
+ assert_equals(await stateMachineResult1, "bytecode_exec",
+ "[4] ScriptLoadRequest status after same SRI hash");
+ assert_equals(await stateMachineResult2, "bytecode_exec",
+ "[5] ScriptLoadRequest status after visit with no SRI");
+ }
+
+ async function testSavebytecodeAfterTheInitializationOfThePage() {
+ dump("## Test: Save bytecode after the initialization of the page");
+
+ // The test page add a new script which generate a "ping" event, which
+ // should be recorded before the bytecode is stored in the cache.
+ var stateMachineResult =
+ WaitForScriptTagEvent("file_js_cache_save_after_load.html");
+ assert_equals(await stateMachineResult, "bytecode_saved & ping(=3)",
+ "Wait on all scripts to be executed");
+ }
+
+ async function testDoNotSaveBytecodeOnCompilationErrors() {
+ dump("## Test: Do not save bytecode on compilation errors");
+
+ // The test page loads a script which contains a syntax error, we should
+ // not attempt to encode any bytecode for it.
+ var stateMachineResult =
+ WaitForScriptTagEvent("file_js_cache_syntax_error.html");
+ assert_equals(await stateMachineResult, "source_exec",
+ "Check the lack of bytecode encoding");
+ }
+
+ done();
+ </script>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1350359">Mozilla Bug 1350359</a>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_self_update_worker.html b/dom/serviceworkers/test/test_self_update_worker.html
new file mode 100644
index 0000000000..d6d4544dd9
--- /dev/null
+++ b/dom/serviceworkers/test/test_self_update_worker.html
@@ -0,0 +1,136 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test that a self updating service worker can't keep running forever when the
+ script changes.
+
+ - self_update_worker.sjs is a stateful server-side js script that returns a
+ SW script with a different version every time it's invoked. (version=1..n)
+ - The SW script will trigger an update when it reaches the activating state,
+ which, if not for the update delaying mechanism, would result in an iterative
+ cycle.
+ - We currently delay registration.update() calls originating from SWs not currently
+ controlling any clients. The delay is: 0s, 30s, 900s etc, but for the purpose of
+ this test, the delay is: 0s, infinite etc.
+ - We assert that the SW script never reaches version 3, meaning it will only
+ successfully update once.
+ - We give the worker reasonable time to self update by repeatedly registering
+ and unregistering an empty service worker.
+ -->
+<head>
+ <title>Test for Bug 1432846</title>
+ <script src="/tests/SimpleTest/SimpleTest.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=1432846">Mozilla Bug 1432846</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script src="utils.js"></script>
+<script class="testbody" type="text/javascript">
+add_task(function setupPrefs() {
+ return SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]});
+});
+
+function activateDummyWorker() {
+ return navigator.serviceWorker.register("empty.js",
+ { scope: "./empty?random=" + Date.now() })
+ .then(function(registration) {
+ var worker = registration.installing;
+ return waitForState(worker, 'activated', registration).then(function() {
+ ok(true, "got dummy!");
+ return registration.unregister();
+ });
+ });
+}
+
+add_task(async function test_update() {
+ navigator.serviceWorker.onmessage = function(event) {
+ ok (event.data.version < 3, "Service worker updated too many times." + event.data.version);
+ }
+
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.update_delay", 30000],
+ ["dom.serviceWorkers.idle_extended_timeout", 299999]]});
+
+ // clear version counter
+ await fetch("self_update_worker.sjs?clearcounter");
+
+ var worker;
+ let registration = await navigator.serviceWorker.register(
+ "self_update_worker.sjs",
+ { scope: "./test_self_update_worker.html?random=" + Date.now()})
+ .then(function(reg) {
+ worker = reg.installing;
+ // We can't wait for 'activated' here, since it's possible for
+ // the update process to kill the worker before it activates.
+ // See: https://github.com/w3c/ServiceWorker/issues/1285
+ return waitForState(worker, 'activating', reg);
+ });
+
+ // We need to wait a reasonable time to give the self updating worker a chance
+ // to change to a newer version. Register and activate an empty worker 5 times.
+ for (i = 0; i < 5; i++) {
+ await activateDummyWorker();
+ }
+
+
+ await registration.unregister();
+ await SpecialPowers.popPrefEnv();
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test variant to ensure that we properly keep the timer alive by having a
+// non-zero but small timer duration. In this case, the delay is simply our
+// exponential growth rate of 30, so if we end up getting to version 4, that's
+// okay and the test may need to be updated.
+add_task(async function test_delay_update() {
+ let version;
+ navigator.serviceWorker.onmessage = function(event) {
+ ok (event.data.version <= 3, "Service worker updated too many times." + event.data.version);
+ version = event.data.version;
+ }
+
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.update_delay", 1],
+ ["dom.serviceWorkers.idle_extended_timeout", 299999]]});
+
+ // clear version counter
+ await fetch("self_update_worker.sjs?clearcounter");
+
+ var worker;
+ let registration = await navigator.serviceWorker.register(
+ "self_update_worker.sjs",
+ { scope: "./test_self_update_worker.html?random=" + Date.now()})
+ .then(function(reg) {
+ worker = reg.installing;
+ // We can't wait for 'activated' here, since it's possible for
+ // the update process to kill the worker before it activates.
+ // See: https://github.com/w3c/ServiceWorker/issues/1285
+ return waitForState(worker, 'activating', reg);
+ });
+
+ // We need to wait a reasonable time to give the self updating worker a chance
+ // to change to a newer version. Register and activate an empty worker 5 times.
+ for (i = 0; i < 5; i++) {
+ await activateDummyWorker();
+ }
+
+ is(version, 3, "Service worker version should be 3.");
+
+ await registration.unregister();
+ await SpecialPowers.popPrefEnv();
+ await SpecialPowers.popPrefEnv();
+});
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_service_worker_allowed.html b/dom/serviceworkers/test/test_service_worker_allowed.html
new file mode 100644
index 0000000000..a74379f383
--- /dev/null
+++ b/dom/serviceworkers/test/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 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/serviceworkers/test/test_serviceworker.html b/dom/serviceworkers/test/test_serviceworker.html
new file mode 100644
index 0000000000..bfc5749405
--- /dev/null
+++ b/dom/serviceworkers/test/test_serviceworker.html
@@ -0,0 +1,79 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1137245 - Allow IndexedDB usage in ServiceWorkers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script src="utils.js"></script>
+<script class="testbody" type="text/javascript">
+
+ var regisration;
+ function simpleRegister() {
+ return navigator.serviceWorker.register("service_worker.js", {
+ scope: 'service_worker_client.html'
+ }).then(swr => waitForState(swr.installing, 'activated', swr));
+ }
+
+ function unregister() {
+ return registration.unregister();
+ }
+
+ function testIndexedDBAvailable(sw) {
+ registration = sw;
+ var p = new Promise(function(resolve, reject) {
+ window.onmessage = function(e) {
+ if (e.data === "READY") {
+ sw.active.postMessage("GO");
+ return;
+ }
+
+ if (!("available" in e.data)) {
+ ok(false, "Something went wrong");
+ reject();
+ return;
+ }
+
+ ok(e.data.available, "IndexedDB available in service worker.");
+ resolve();
+ }
+ });
+
+ var content = document.getElementById("content");
+ ok(content, "Parent exists.");
+
+ iframe = document.createElement("iframe");
+ iframe.setAttribute('src', "service_worker_client.html");
+ content.appendChild(iframe);
+
+ return p.then(() => content.removeChild(iframe));
+ }
+
+ function runTest() {
+ simpleRegister()
+ .then(testIndexedDBAvailable)
+ .then(unregister)
+ .then(SimpleTest.finish)
+ .catch(function(e) {
+ ok(false, "Some test failed with error " + e);
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+ ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_serviceworker_header.html b/dom/serviceworkers/test/test_serviceworker_header.html
new file mode 100644
index 0000000000..f607aeba3d
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/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/serviceworkers/test/test_serviceworker_interfaces.html b/dom/serviceworkers/test/test_serviceworker_interfaces.html
new file mode 100644
index 0000000000..8a62950bde
--- /dev/null
+++ b/dom/serviceworkers/test/test_serviceworker_interfaces.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>Validate Interfaces Exposed to Service Workers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="../worker_driver.js"></script>
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+ function setupSW(registration) {
+ var iframe;
+ var worker = registration.installing ||
+ registration.waiting ||
+ registration.active;
+ window.onmessage = function(event) {
+ if (event.data.type == 'finish') {
+ iframe.remove();
+ 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') {
+ let result = {};
+ event.data.prefs.forEach(function(pref) {
+ result[pref] = SpecialPowers.Services.prefs.getBoolPref(pref);
+ });
+ worker.postMessage({
+ type: 'returnPrefs',
+ prefs: event.data.prefs,
+ result
+ });
+
+ } else if (event.data.type == 'getHelperData') {
+ const { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+ );
+ const isNightly = AppConstants.NIGHTLY_BUILD;
+ const isEarlyBetaOrEarlier = AppConstants.EARLY_BETA_OR_EARLIER;
+ const isRelease = AppConstants.RELEASE_OR_BETA;
+ const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
+ const isMac = AppConstants.platform == "macosx";
+ const isWindows = AppConstants.platform == "win";
+ const isAndroid = AppConstants.platform == "android";
+ const isLinux = AppConstants.platform == "linux";
+ const isInsecureContext = !window.isSecureContext;
+ // Currently, MOZ_APP_NAME is always "fennec" for all mobile builds, so we can't use AppConstants for this
+ const isFennec = isAndroid && SpecialPowers.Cc["@mozilla.org/android/bridge;1"].getService(SpecialPowers.Ci.nsIAndroidBridge).isFennec;
+
+ const result = {
+ isNightly, isEarlyBetaOrEarlier, isRelease, isDesktop, isMac,
+ isWindows, isAndroid, isLinux, isInsecureContext, isFennec
+ };
+
+ worker.postMessage({
+ type: 'returnHelperData', result
+ });
+ }
+ }
+
+ worker.onerror = function(event) {
+ ok(false, 'Worker had an error: ' + event.data);
+ SimpleTest.finish();
+ };
+
+ 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.register("serviceworker_wrapper.js", {scope: "."})
+ .then(setupSW);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ onload = function() {
+ var prefs = [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ];
+ SpecialPowers.pushPrefEnv({"set": prefs}, runTest);
+ };
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_serviceworker_interfaces.js b/dom/serviceworkers/test/test_serviceworker_interfaces.js
new file mode 100644
index 0000000000..1cf0896edf
--- /dev/null
+++ b/dom/serviceworkers/test/test_serviceworker_interfaces.js
@@ -0,0 +1,567 @@
+// 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!
+let wasmGlobalEntry = {
+ name: "WebAssembly",
+ insecureContext: true,
+ disabled: !getJSTestingFunctions().wasmIsSupportedByHardware(),
+};
+let wasmGlobalInterfaces = [
+ { name: "Module", insecureContext: true },
+ { name: "Instance", insecureContext: true },
+ { name: "Memory", insecureContext: true },
+ { name: "Table", insecureContext: true },
+ { name: "Global", insecureContext: true },
+ { name: "CompileError", insecureContext: true },
+ { name: "LinkError", insecureContext: true },
+ { name: "RuntimeError", insecureContext: true },
+ { name: "Function", insecureContext: true, nightly: true },
+ { name: "Exception", insecureContext: true },
+ { name: "Tag", insecureContext: true },
+ { name: "compile", insecureContext: true },
+ { name: "compileStreaming", insecureContext: true },
+ { name: "instantiate", insecureContext: true },
+ { name: "instantiateStreaming", insecureContext: true },
+ { name: "validate", insecureContext: true },
+];
+// IMPORTANT: Do not change this list without review from
+// a JavaScript Engine peer!
+let ecmaGlobals = [
+ "AggregateError",
+ "Array",
+ "ArrayBuffer",
+ "Atomics",
+ "Boolean",
+ "BigInt",
+ "BigInt64Array",
+ "BigUint64Array",
+ "DataView",
+ "Date",
+ "Error",
+ "EvalError",
+ "FinalizationRegistry",
+ "Float32Array",
+ "Float64Array",
+ "Function",
+ "Infinity",
+ "Int16Array",
+ "Int32Array",
+ "Int8Array",
+ "InternalError",
+ "Intl",
+ "JSON",
+ "Map",
+ "Math",
+ "NaN",
+ "Number",
+ "Object",
+ "Promise",
+ "Proxy",
+ "RangeError",
+ "ReferenceError",
+ "Reflect",
+ "RegExp",
+ "Set",
+ {
+ name: "SharedArrayBuffer",
+ crossOriginIsolated: true,
+ },
+ "String",
+ "Symbol",
+ "SyntaxError",
+ "TypeError",
+ "Uint16Array",
+ "Uint32Array",
+ "Uint8Array",
+ "Uint8ClampedArray",
+ "URIError",
+ "WeakMap",
+ "WeakRef",
+ "WeakSet",
+ wasmGlobalEntry,
+ "decodeURI",
+ "decodeURIComponent",
+ "encodeURI",
+ "encodeURIComponent",
+ "escape",
+ "eval",
+ "globalThis",
+ "isFinite",
+ "isNaN",
+ "parseFloat",
+ "parseInt",
+ "undefined",
+ "unescape",
+];
+// IMPORTANT: Do not change the list above without review from
+// a JavaScript Engine peer!
+
+// IMPORTANT: Do not change the list below without review from a DOM peer!
+let interfaceNamesInGlobalScope = [
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "AbortController",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "AbortSignal",
+ // 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!
+ "ByteLengthQueuingStrategy",
+ // 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!
+ "CanvasGradient",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "CanvasPattern",
+ // 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!
+ "CloseEvent",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "CompressionStream",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "CountQueuingStrategy",
+ // 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!
+ "CryptoKey",
+ // 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!
+ "DecompressionStream",
+ // 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!
+ "DOMException",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMMatrix",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMMatrixReadOnly",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMPoint",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMPointReadOnly",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMQuad",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMRect",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMRectReadOnly",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "DOMRequest", disabled: true },
+ // 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!
+ "ErrorEvent",
+ // 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!
+ "FileList",
+ // 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!
+ { name: "FileSystemDirectoryHandle" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "FileSystemFileHandle" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "FileSystemHandle" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "FileSystemWritableFileStream" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "FontFace",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "FontFaceSet",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "FontFaceSetLoadEvent",
+ // 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!
+ "Lock",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "LockManager",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "MediaCapabilities",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "MediaCapabilitiesInfo",
+ // 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!
+ { name: "NetworkInformation", disabled: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "NavigationPreloadManager",
+ // 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!
+ "OffscreenCanvas",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "OffscreenCanvasRenderingContext2D",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "Path2D",
+ // 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!
+ "PerformanceObserver",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "PerformanceObserverEntryList",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "PerformanceResourceTiming",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "PerformanceServerTiming",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "ProgressEvent",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "PromiseRejectionEvent",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PushEvent" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PushManager" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PushMessageData" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PushSubscription" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PushSubscriptionOptions" },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "ReadableByteStreamController",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "ReadableStream",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "ReadableStreamBYOBReader",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "ReadableStreamBYOBRequest",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "ReadableStreamDefaultController",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "ReadableStreamDefaultReader",
+ // 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: "Scheduler", nightly: true },
+ // 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", fennec: false },
+ // 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!
+ { name: "TaskController", nightly: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "TaskPriorityChangeEvent", nightly: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "TaskSignal", nightly: true },
+ // 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!
+ "TextDecoderStream",
+ // 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!
+ "TextEncoderStream",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "TextMetrics",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "TransformStream",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "TransformStreamDefaultController",
+ // 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!
+ "WebTransport",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebTransportBidirectionalStream",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebTransportDatagramDuplexStream",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebTransportError",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebTransportReceiveStream",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebTransportSendStream",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGL2RenderingContext",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLActiveInfo",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLBuffer",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLContextEvent",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLFramebuffer",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLProgram",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLQuery",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLRenderbuffer",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLRenderingContext",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLSampler",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLShader",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLShaderPrecisionFormat",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLSync",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLTexture",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLTransformFeedback",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLUniformLocation",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WebGLVertexArrayObject",
+ // 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!
+ "WritableStream",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WritableStreamDefaultController",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "WritableStreamDefaultWriter",
+ // 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!
+ "console",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "onactivate",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "onfetch",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "oninstall",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "onmessage",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "onmessageerror",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "onnotificationclick",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "onnotificationclose",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "onpush",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "onpushsubscriptionchange",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "registration",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ "skipWaiting",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+];
+// IMPORTANT: Do not change the list above without review from a DOM peer!
+
+// List of functions defined on the global by the test harness or this test
+// file.
+let testFunctions = [
+ "ok",
+ "is",
+ "workerTestArrayEquals",
+ "workerTestDone",
+ "workerTestGetHelperData",
+ "workerTestGetStorageManager",
+ "entryDisabled",
+ "createInterfaceMap",
+ "runTest",
+];
+
+function entryDisabled(
+ entry,
+ {
+ isNightly,
+ isEarlyBetaOrEarlier,
+ isRelease,
+ isDesktop,
+ isAndroid,
+ isInsecureContext,
+ isFennec,
+ isCrossOriginIsolated,
+ }
+) {
+ return (
+ entry.nightly === !isNightly ||
+ (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) ||
+ (entry.nonReleaseAndroid === !(isAndroid && !isRelease) && isAndroid) ||
+ entry.desktop === !isDesktop ||
+ (entry.android === !isAndroid &&
+ !entry.nonReleaseAndroid &&
+ !entry.nightlyAndroid) ||
+ entry.fennecOrDesktop === (isAndroid && !isFennec) ||
+ entry.fennec === !isFennec ||
+ entry.release === !isRelease ||
+ entry.earlyBetaOrEarlier === !isEarlyBetaOrEarlier ||
+ entry.crossOriginIsolated === !isCrossOriginIsolated ||
+ entry.disabled
+ );
+}
+
+function createInterfaceMap(data, ...interfaceGroups) {
+ var interfaceMap = {};
+
+ function addInterfaces(interfaces) {
+ for (var entry of interfaces) {
+ if (typeof entry === "string") {
+ ok(!(entry in interfaceMap), "duplicate entry for " + entry);
+ interfaceMap[entry] = true;
+ } else {
+ ok(!(entry.name in interfaceMap), "duplicate entry for " + entry.name);
+ ok(!("pref" in entry), "Bogus pref annotation for " + entry.name);
+ if (entryDisabled(entry, data)) {
+ interfaceMap[entry.name] = false;
+ } else if (entry.optional) {
+ interfaceMap[entry.name] = "optional";
+ } else {
+ interfaceMap[entry.name] = true;
+ }
+ }
+ }
+ }
+
+ for (let interfaceGroup of interfaceGroups) {
+ addInterfaces(interfaceGroup);
+ }
+
+ return interfaceMap;
+}
+
+function runTest(parentName, parent, data, ...interfaceGroups) {
+ var interfaceMap = createInterfaceMap(data, ...interfaceGroups);
+ for (var name of Object.getOwnPropertyNames(parent)) {
+ // Ignore functions on the global that are part of the test (harness).
+ if (parent === self && testFunctions.includes(name)) {
+ continue;
+ }
+ ok(
+ interfaceMap[name] === "optional" || 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 " +
+ parentName +
+ "? Do not make a change to this file without a " +
+ " review from a DOM peer for that specific change!!! (or a JS peer for changes to ecmaGlobals)"
+ );
+ delete interfaceMap[name];
+ }
+ for (var name of Object.keys(interfaceMap)) {
+ if (interfaceMap[name] === "optional") {
+ delete interfaceMap[name];
+ } else {
+ ok(
+ name in parent === interfaceMap[name],
+ name +
+ " should " +
+ (interfaceMap[name] ? "" : " NOT") +
+ " be defined on " +
+ parentName
+ );
+ if (!interfaceMap[name]) {
+ delete interfaceMap[name];
+ }
+ }
+ }
+ is(
+ Object.keys(interfaceMap).length,
+ 0,
+ "The following interface(s) are not enumerated: " +
+ Object.keys(interfaceMap).join(", ")
+ );
+}
+
+workerTestGetHelperData(function (data) {
+ runTest("self", self, data, ecmaGlobals, interfaceNamesInGlobalScope);
+ if (WebAssembly && !entryDisabled(wasmGlobalEntry, data)) {
+ runTest("WebAssembly", WebAssembly, data, wasmGlobalInterfaces);
+ }
+ workerTestDone();
+});
diff --git a/dom/serviceworkers/test/test_serviceworker_not_sharedworker.html b/dom/serviceworkers/test/test_serviceworker_not_sharedworker.html
new file mode 100644
index 0000000000..33b4428e95
--- /dev/null
+++ b/dom/serviceworkers/test/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 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/serviceworkers/test/";
+ 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(msg) {
+ is(msg.data.result, "sharedworker", "We should be talking to a shared worker");
+ registration.unregister().then(function(success) {
+ ok(success, "unregister should succeed");
+ SimpleTest.finish();
+ }, function(ex) {
+ dump("Unregistering the SW failed with " + ex + "\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/serviceworkers/test/test_serviceworkerinfo.xhtml b/dom/serviceworkers/test/test_serviceworkerinfo.xhtml
new file mode 100644
index 0000000000..07b6a30345
--- /dev/null
+++ b/dom/serviceworkers/test/test_serviceworkerinfo.xhtml
@@ -0,0 +1,114 @@
+<?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 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() {
+ 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 () {
+ (async function() {
+ let iframe = $("iframe");
+ let promise = new Promise(function (resolve) {
+ iframe.onload = function () {
+ resolve();
+ };
+ });
+ iframe.src = IFRAME_URL;
+ await promise;
+
+ info("Check that a service worker eventually shuts down.");
+ promise = Promise.all([
+ waitForRegister(EXAMPLE_URL),
+ waitForServiceWorkerShutdown()
+ ]);
+ iframe.contentWindow.postMessage("register", "*");
+ let [registration] = await promise;
+
+ // Make sure the worker is active.
+ registration = await 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();
+ await promise;
+ ok(activeWorker.debugger === null);
+
+ promise = waitForUnregister(EXAMPLE_URL);
+ iframe.contentWindow.postMessage("unregister", "*");
+ registration = await promise;
+
+ SimpleTest.finish();
+ })();
+ });
+ }
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ <iframe id="iframe"></iframe>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/serviceworkers/test/test_serviceworkermanager.xhtml b/dom/serviceworkers/test/test_serviceworkermanager.xhtml
new file mode 100644
index 0000000000..5beb6c3f20
--- /dev/null
+++ b/dom/serviceworkers/test/test_serviceworkermanager.xhtml
@@ -0,0 +1,79 @@
+<?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 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 () {
+ (async function() {
+ let registrations = swm.getAllRegistrations();
+ is(registrations.length, 0);
+
+ let iframe = $("iframe");
+ let promise = waitForIframeLoad(iframe);
+ iframe.src = IFRAME_URL;
+ await 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 = await 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 () {
+ 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 = await 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/serviceworkers/test/test_serviceworkerregistrationinfo.xhtml b/dom/serviceworkers/test/test_serviceworkerregistrationinfo.xhtml
new file mode 100644
index 0000000000..5b39350897
--- /dev/null
+++ b/dom/serviceworkers/test/test_serviceworkerregistrationinfo.xhtml
@@ -0,0 +1,155 @@
+<?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 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 () {
+ (async function() {
+ let iframe = $("iframe");
+ let promise = waitForIframeLoad(iframe);
+ iframe.src = IFRAME_URL;
+ await 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 () {
+ // Got change event for updating (byte-check)
+ 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.evaluatingWorker !== null);
+ ok(registration.installingWorker === null);
+ ok(registration.waitingWorker === null);
+ ok(registration.activeWorker === null);
+
+ return waitForServiceWorkerRegistrationChange(registration, function () {
+ 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 () {
+ // Activating
+ ok(registration.installingWorker === null);
+ ok(registration.waitingWorker === null);
+ ok(registration.activeWorker !== null);
+
+ return waitForServiceWorkerRegistrationChange(registration, function () {
+ // Activated
+ ok(registration.installingWorker === null);
+ ok(registration.waitingWorker === null);
+ ok(registration.activeWorker !== null);
+
+ return registration;
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ iframe.contentWindow.postMessage("register", "*");
+ let registration = await promise;
+
+ promise = waitForServiceWorkerRegistrationChange(registration, function () {
+ // Got change event for updating (byte-check)
+ ok(registration.installingWorker === null);
+ ok(registration.waitingWorker === null);
+ ok(registration.activeWorker !== null);
+
+ return waitForServiceWorkerRegistrationChange(registration, function () {
+ is(registration.scriptSpec, EXAMPLE_URL + "worker2.js");
+ ok(registration.evaluatingWorker !== null);
+
+ return waitForServiceWorkerRegistrationChange(registration, function () {
+ 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 () {
+ // Activating
+ ok(registration.installingWorker === null);
+ ok(registration.waitingWorker === null);
+ ok(registration.activeWorker !== null);
+
+ return waitForServiceWorkerRegistrationChange(registration, function () {
+ // Activated
+ ok(registration.installingWorker === null);
+ ok(registration.waitingWorker === null);
+ ok(registration.activeWorker !== null);
+
+ return registration;
+ });
+ });
+ });
+ });
+ });
+ });
+ iframe.contentWindow.postMessage("register", "*");
+ await promise;
+
+ iframe.contentWindow.postMessage("unregister", "*");
+ await 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/serviceworkers/test/test_skip_waiting.html b/dom/serviceworkers/test/test_skip_waiting.html
new file mode 100644
index 0000000000..6147ad6b38
--- /dev/null
+++ b/dom/serviceworkers/test/test_skip_waiting.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 1131352 - Add ServiceWorkerGlobalScope skipWaiting()</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script src="utils.js"></script>
+<script class="testbody" type="text/javascript">
+ var registration, iframe, content;
+
+ function start() {
+ return navigator.serviceWorker.register("worker.js",
+ {scope: "./skip_waiting_scope/"});
+ }
+
+ async function waitForActivated(swr) {
+ registration = swr;
+ await waitForState(registration.installing, "activated")
+
+ iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "skip_waiting_scope/index.html");
+
+ content = document.getElementById("content");
+ content.appendChild(iframe);
+
+ await new Promise(resolve => iframe.onload = resolve);
+ }
+
+ 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/serviceworkers/test/test_streamfilter.html b/dom/serviceworkers/test/test_streamfilter.html
new file mode 100644
index 0000000000..7367fb8b84
--- /dev/null
+++ b/dom/serviceworkers/test/test_streamfilter.html
@@ -0,0 +1,207 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>
+ Test StreamFilter-monitored responses for ServiceWorker-intercepted requests
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script>
+// eslint-disable-next-line mozilla/no-addtask-setup
+add_task(async function setup() {
+ SimpleTest.waitForExplicitFinish();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ });
+
+ const registration = await navigator.serviceWorker.register(
+ "streamfilter_worker.js"
+ );
+
+ SimpleTest.registerCleanupFunction(async function unregisterRegistration() {
+ await registration.unregister();
+ });
+
+ await new Promise(resolve => {
+ const serviceWorker = registration.installing;
+
+ serviceWorker.onstatechange = () => {
+ if (serviceWorker.state == "activated") {
+ resolve();
+ }
+ };
+ });
+
+ ok(navigator.serviceWorker.controller, "Page is controlled");
+});
+
+async function getExtension() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["webRequest", "webRequestBlocking", "<all_urls>"],
+ },
+
+ // This WebExtension only proxies a response's data through a StreamFilter;
+ // it doesn't modify the data itself in any way.
+ background() {
+ class FilterWrapper {
+ constructor(requestId) {
+ const filter = browser.webRequest.filterResponseData(requestId);
+ const arrayBuffers = [];
+
+ filter.onstart = () => {
+ browser.test.sendMessage("start");
+ };
+
+ filter.ondata = ({ data }) => {
+ arrayBuffers.push(data);
+ };
+
+ filter.onstop = () => {
+ browser.test.sendMessage("stop");
+ new Blob(arrayBuffers).arrayBuffer().then(buffer => {
+ filter.write(buffer);
+ filter.close();
+ });
+ };
+
+ filter.onerror = () => {
+ // We only ever expect a redirect error here.
+ browser.test.assertEq(filter.error, "ServiceWorker fallback redirection");
+ browser.test.sendMessage("error");
+ };
+ }
+ }
+
+ browser.webRequest.onBeforeRequest.addListener(
+ details => {
+ new FilterWrapper(details.requestId);
+ },
+ {
+ urls: ["<all_urls>"],
+ types: ["xmlhttprequest"],
+ },
+ ["blocking"]
+ );
+ },
+ });
+
+ await extension.startup();
+ return extension;
+}
+
+const streamFilterServerUrl = `${location.origin}/tests/dom/serviceworkers/test/streamfilter_server.sjs`;
+
+const requestUrlForServerQueryString = "syntheticResponse=0";
+
+// streamfilter_server.sjs is expected to respond to a request to this URL.
+const requestUrlForServer = `${streamFilterServerUrl}?${requestUrlForServerQueryString}`;
+
+const requestUrlForServiceWorkerQueryString = "syntheticResponse=1";
+
+// streamfilter_worker.js is expected to respond to a request to this URL.
+const requestUrlForServiceWorker = `${streamFilterServerUrl}?${requestUrlForServiceWorkerQueryString}`;
+
+// startNetworkerRequestFn must be a function that, when called, starts a
+// network request and returns a promise that resolves after the request
+// completes (or fails). This function will return the value that that promise
+// resolves with (or throw if it rejects).
+async function observeFilteredNetworkRequest(startNetworkRequestFn, promises) {
+ const networkRequestPromise = startNetworkRequestFn();
+ await Promise.all(promises);
+ return networkRequestPromise;
+}
+
+// Returns a promise that resolves with the XHR's response text.
+function callXHR(requestUrl, promises) {
+ return observeFilteredNetworkRequest(() => {
+ return new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+ xhr.onload = () => {
+ resolve(xhr.responseText);
+ };
+ xhr.onerror = reject;
+ xhr.open("GET", requestUrl);
+ xhr.send();
+ });
+ }, promises);
+}
+
+// Returns a promise that resolves with the Fetch's response text.
+function callFetch(requestUrl, promises) {
+ return observeFilteredNetworkRequest(() => {
+ return fetch(requestUrl).then(response => response.text());
+ }, promises);
+}
+
+// The expected response text is always the query string (without the leading
+// "?") of the request URL.
+add_task(async function callXhrExpectServerResponse() {
+ info(`Performing XHR at ${requestUrlForServer}...`);
+ let extension = await getExtension();
+ is(
+ await callXHR(requestUrlForServer, [
+ extension.awaitMessage("start"),
+ extension.awaitMessage("error"),
+ extension.awaitMessage("stop"),
+ ]),
+ requestUrlForServerQueryString,
+ "Server-supplied response for XHR completed successfully"
+ );
+ await extension.unload();
+});
+
+add_task(async function callXhrExpectServiceWorkerResponse() {
+ info(`Performing XHR at ${requestUrlForServiceWorker}...`);
+ let extension = await getExtension();
+ is(
+ await callXHR(requestUrlForServiceWorker, [
+ extension.awaitMessage("start"),
+ extension.awaitMessage("stop"),
+ ]),
+ requestUrlForServiceWorkerQueryString,
+ "ServiceWorker-supplied response for XHR completed successfully"
+ );
+ await extension.unload();
+});
+
+add_task(async function callFetchExpectServerResponse() {
+ info(`Performing Fetch at ${requestUrlForServer}...`);
+ let extension = await getExtension();
+ is(
+ await callFetch(requestUrlForServer, [
+ extension.awaitMessage("start"),
+ extension.awaitMessage("error"),
+ extension.awaitMessage("stop"),
+ ]),
+ requestUrlForServerQueryString,
+ "Server-supplied response for Fetch completed successfully"
+ );
+ await extension.unload();
+});
+
+add_task(async function callFetchExpectServiceWorkerResponse() {
+ info(`Performing Fetch at ${requestUrlForServiceWorker}...`);
+ let extension = await getExtension();
+ is(
+ await callFetch(requestUrlForServiceWorker, [
+ extension.awaitMessage("start"),
+ extension.awaitMessage("stop"),
+ ]),
+ requestUrlForServiceWorkerQueryString,
+ "ServiceWorker-supplied response for Fetch completed successfully"
+ );
+ await extension.unload();
+});
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_strict_mode_warning.html b/dom/serviceworkers/test/test_strict_mode_warning.html
new file mode 100644
index 0000000000..4df0d1a380
--- /dev/null
+++ b/dom/serviceworkers/test/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 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/serviceworkers/test/test_third_party_iframes.html b/dom/serviceworkers/test/test_third_party_iframes.html
new file mode 100644
index 0000000000..90e9dadfa8
--- /dev/null
+++ b/dom/serviceworkers/test/test_third_party_iframes.html
@@ -0,0 +1,263 @@
+<!--
+ 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 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 chromeScript;
+chromeScript = SpecialPowers.loadChromeScript(_ => {
+ /* eslint-env mozilla/chrome-script */
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+});
+
+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 proxyWindow;
+let basePath = "/tests/dom/serviceworkers/test/thirdparty/";
+let origin = window.location.protocol + "//" + window.location.host;
+let thirdPartyOrigin = "https://example.com";
+
+function loadIframe() {
+ let message = {
+ source: "parent",
+ href: origin + basePath + "iframe2.html"
+ };
+ iframe.contentWindow.postMessage(message, "*");
+}
+
+function loadThirdPartyIframe() {
+ let message = {
+ source: "parent",
+ href: thirdPartyOrigin + basePath + "iframe2.html"
+ }
+ iframe.contentWindow.postMessage(message, "*");
+}
+
+function runTest(aExpectedResponses) {
+ // Let's use a proxy window to have the new cookie policy applied.
+ proxyWindow = window.open("window_party_iframes.html");
+ proxyWindow.onload = _ => {
+ iframe = proxyWindow.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++;
+ };
+ }
+}
+
+// Verify that we can register and intercept a 3rd party iframe with
+// the given cookie policy.
+function testShouldIntercept(behavior, done) {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["network.cookie.cookieBehavior", behavior],
+ ]}, function() {
+ runTest([{
+ status: "ok"
+ }, {
+ status: "registrationdone",
+ next() {
+ iframe.src = origin + basePath + "iframe1.html";
+ }
+ }, {
+ status: "iframeloaded",
+ next: loadIframe
+ }, {
+ status: "networkresponse",
+ }, {
+ status: "worker-networkresponse",
+ next: loadThirdPartyIframe
+ }, {
+ status: "swresponse",
+ }, {
+ status: "worker-swresponse",
+ next() {
+ iframe.src = thirdPartyOrigin + basePath + "unregister.html";
+ }
+ }, {
+ status: "controlled",
+ }, {
+ status: "unregistrationdone",
+ next() {
+ window.onmessage = null;
+ proxyWindow.close();
+ ok(true, "Test finished successfully");
+ done();
+ }
+ }]);
+ });
+}
+
+// Verify that we cannot register a service worker in a 3rd party
+// iframe with the given cookie policy.
+function testShouldNotRegister(behavior, done) {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["network.cookie.cookieBehavior", behavior],
+ ]}, function() {
+ runTest([{
+ status: "registrationfailed",
+ next() {
+ iframe.src = origin + basePath + "iframe1.html";
+ }
+ }, {
+ status: "iframeloaded",
+ next: loadIframe
+ }, {
+ status: "networkresponse",
+ }, {
+ status: "worker-networkresponse",
+ next: loadThirdPartyIframe
+ }, {
+ status: "networkresponse",
+ }, {
+ status: "worker-networkresponse",
+ next() {
+ window.onmessage = null;
+ proxyWindow.close();
+ ok(true, "Test finished successfully");
+ done();
+ }
+ }]);
+ });
+}
+
+// Verify that if a service worker is already registered a 3rd
+// party iframe will still not be intercepted with the given cookie
+// policy.
+function testShouldNotIntercept(behavior, done) {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["network.cookie.cookieBehavior", BEHAVIOR_ACCEPT],
+ ]}, function() {
+ runTest([{
+ status: "ok"
+ }, {
+ status: "registrationdone",
+ next() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["network.cookie.cookieBehavior", behavior],
+ ]}, function() {
+ proxyWindow.close();
+ proxyWindow = window.open("window_party_iframes.html");
+ proxyWindow.onload = _ => {
+ iframe = proxyWindow.document.querySelector("iframe");
+ iframe.src = origin + basePath + "iframe1.html";
+ }
+ });
+ }
+ }, {
+ status: "iframeloaded",
+ next: loadIframe
+ }, {
+ status: "networkresponse",
+ }, {
+ status: "worker-networkresponse",
+ next: loadThirdPartyIframe
+ }, {
+ status: "networkresponse",
+ }, {
+ status: "worker-networkresponse",
+ next() {
+ iframe.src = thirdPartyOrigin + basePath + "unregister.html";
+ }
+ }, {
+ status: "uncontrolled",
+ }, {
+ status: "getregistrationfailed",
+ next() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["network.cookie.cookieBehavior", BEHAVIOR_ACCEPT],
+ ]}, function() {
+ proxyWindow.close();
+ proxyWindow = window.open("window_party_iframes.html");
+ proxyWindow.onload = _ => {
+ iframe = proxyWindow.document.querySelector("iframe");
+ iframe.src = thirdPartyOrigin + basePath + "unregister.html";
+ }
+ });
+ }
+ }, {
+ status: "controlled",
+ }, {
+ status: "unregistrationdone",
+ next() {
+ window.onmessage = null;
+ proxyWindow.close();
+ ok(true, "Test finished successfully");
+ done();
+ }
+ }]);
+ });
+}
+
+const BEHAVIOR_ACCEPT = 0;
+const BEHAVIOR_REJECTFOREIGN = 1;
+const BEHAVIOR_REJECT = 2;
+const 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", BEHAVIOR_ACCEPT],
+ ]}, next);
+}, () => {
+ testShouldNotRegister(BEHAVIOR_REJECTFOREIGN, next);
+}, () => {
+ testShouldNotIntercept(BEHAVIOR_REJECTFOREIGN, next);
+}, () => {
+ testShouldNotRegister(BEHAVIOR_REJECT, next);
+}, () => {
+ testShouldNotIntercept(BEHAVIOR_REJECT, next);
+}, () => {
+ testShouldNotRegister(BEHAVIOR_LIMITFOREIGN, next);
+}, () => {
+ testShouldNotIntercept(BEHAVIOR_LIMITFOREIGN, next);
+}, () => {
+ testShouldIntercept(BEHAVIOR_ACCEPT, next);
+}];
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_unregister.html b/dom/serviceworkers/test/test_unregister.html
new file mode 100644
index 0000000000..af02931efb
--- /dev/null
+++ b/dom/serviceworkers/test/test_unregister.html
@@ -0,0 +1,136 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 984048 - Test unregister</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+ function 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(event) {
+ 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);
+ });
+ }
+
+ async function unregister() {
+ let reg = await navigator.serviceWorker.getRegistration("unregister/")
+ if (!reg) {
+ info("Registration already removed");
+ return;
+ }
+
+ info("getRegistration() succeeded " + reg.scope);
+ try {
+ let v = await reg.unregister();
+ ok(v, "Unregister should resolve to true");
+ } catch (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/serviceworkers/test/test_unresolved_fetch_interception.html b/dom/serviceworkers/test/test_unresolved_fetch_interception.html
new file mode 100644
index 0000000000..7182b0fb86
--- /dev/null
+++ b/dom/serviceworkers/test/test_unresolved_fetch_interception.html
@@ -0,0 +1,95 @@
+<!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="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 src="utils.js"></script>
+<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(async 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.
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.idle_timeout", 0],
+ ["dom.serviceWorkers.idle_extended_timeout", 299999]]});
+
+ let registration = await navigator.serviceWorker.register(
+ "unresolved_fetch_worker.js", { scope: "./"} );
+ await waitForState(registration.installing, "activated");
+ 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"); });
+
+ await 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...
+ await 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.
+ await hangingFetch;
+
+ // The worker should now be dead and the error logged, wait/assert.
+ await wait_for_expected_message(expectedMessage);
+
+ // roll back all of our test case specific preferences and otherwise cleanup
+ await SpecialPowers.popPrefEnv();
+ await SpecialPowers.popPrefEnv();
+ await registration.unregister();
+});
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_workerUnregister.html b/dom/serviceworkers/test/test_workerUnregister.html
new file mode 100644
index 0000000000..d0bc1d6ce4
--- /dev/null
+++ b/dom/serviceworkers/test/test_workerUnregister.html
@@ -0,0 +1,81 @@
+<!--
+ 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 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(event) {
+ 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/serviceworkers/test/test_workerUpdate.html b/dom/serviceworkers/test/test_workerUpdate.html
new file mode 100644
index 0000000000..015e6bb4ae
--- /dev/null
+++ b/dom/serviceworkers/test/test_workerUpdate.html
@@ -0,0 +1,63 @@
+<!--
+ 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 src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="container"></div>
+<script src="utils.js"></script>
+<script class="testbody" type="text/javascript">
+
+ function simpleRegister() {
+ return navigator.serviceWorker.register("worker_update.js", { scope: "workerUpdate/" })
+ .then(swr => waitForState(swr.installing, 'activated', swr));
+ }
+
+ 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/serviceworkers/test/test_worker_reference_gc_timeout.html b/dom/serviceworkers/test/test_worker_reference_gc_timeout.html
new file mode 100644
index 0000000000..cf04e13f2e
--- /dev/null
+++ b/dom/serviceworkers/test/test_worker_reference_gc_timeout.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ -->
+<head>
+ <title>Test for Bug 1317266</title>
+ <script src="/tests/SimpleTest/SimpleTest.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=1317266">Mozilla Bug 1317266</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="text/javascript">
+SimpleTest.requestFlakyTimeout("Forcing a race with the cycle collector.");
+
+add_task(function setupPrefs() {
+ return SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]});
+});
+
+add_task(async function test_worker_ref_gc() {
+ let registration = await navigator.serviceWorker.register(
+ "lazy_worker.js", { scope: "./lazy_worker_scope_timeout"} )
+ .then(function(reg) {
+ SpecialPowers.exactGC();
+ var worker = reg.installing;
+ return new Promise(function(resolve) {
+ worker.addEventListener('statechange', function() {
+ info("state is " + worker.state + "\n");
+ SpecialPowers.exactGC();
+ if (worker.state === 'activated') {
+ resolve(reg);
+ }
+ });
+ });
+ });
+ ok(true, "Got activated event!");
+
+ await registration.unregister();
+});
+
+add_task(async function test_worker_ref_gc_ready_promise() {
+ let wait_active = navigator.serviceWorker.ready.then(function(reg) {
+ SpecialPowers.exactGC();
+ ok(reg.active, "Got active worker.");
+ ok(reg.active.state === "activating", "Worker is in activating state");
+ return new Promise(function(res) {
+ reg.active.onstatechange = function(e) {
+ reg.active.onstatechange = null;
+ ok(reg.active.state === "activated", "Worker was activated");
+ res();
+ }
+ });
+ });
+
+ let registration = await navigator.serviceWorker.register(
+ "lazy_worker.js", { scope: "."} );
+ await wait_active;
+ await registration.unregister();
+});
+
+add_task(async function cleanup() {
+ await SpecialPowers.popPrefEnv();
+});
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/test_workerupdatefoundevent.html b/dom/serviceworkers/test/test_workerupdatefoundevent.html
new file mode 100644
index 0000000000..1c3ced13bd
--- /dev/null
+++ b/dom/serviceworkers/test/test_workerupdatefoundevent.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 1131327 - Test ServiceWorkerRegistration.onupdatefound on ServiceWorker</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"></div>
+<pre id="test"></pre>
+<script src="utils.js"></script>
+<script class="testbody" type="text/javascript">
+ var registration;
+ var promise;
+
+ async function start() {
+ registration = await navigator.serviceWorker.register("worker_updatefoundevent.js",
+ { scope: "./updatefoundevent.html" })
+ await waitForState(registration.installing, 'activated');
+
+ content = document.getElementById("content");
+ iframe = document.createElement("iframe");
+ content.appendChild(iframe);
+ iframe.setAttribute("src", "./updatefoundevent.html");
+
+ await new Promise(function(resolve) { iframe.onload = resolve; });
+ ok(iframe.contentWindow.navigator.serviceWorker.controller, "Controlled client.");
+
+ return Promise.resolve();
+
+ }
+
+ 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();
+ }
+ }
+ });
+
+ 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/serviceworkers/test/test_xslt.html b/dom/serviceworkers/test/test_xslt.html
new file mode 100644
index 0000000000..a955c843ac
--- /dev/null
+++ b/dom/serviceworkers/test/test_xslt.html
@@ -0,0 +1,117 @@
+<!--
+ 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 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 src="utils.js"></script>
+<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
+ return waitForState(swr.installing, 'activated');
+ });
+ }
+
+ 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/serviceworkers/test/thirdparty/iframe1.html b/dom/serviceworkers/test/thirdparty/iframe1.html
new file mode 100644
index 0000000000..e8982d306a
--- /dev/null
+++ b/dom/serviceworkers/test/thirdparty/iframe1.html
@@ -0,0 +1,42 @@
+<!--
+ 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">
+ function messageListener(event) {
+ let message = 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, "*");
+ } else if (message.source == "worker") {
+ parent.postMessage(event.data, "*");
+ }
+ }
+ </script>
+
+</head>
+
+<body>
+ <script>
+ onload = function() {
+ window.addEventListener('message', messageListener);
+ let message = {
+ source: "iframe",
+ status: "iframeloaded",
+ }
+ parent.postMessage(message, "*");
+ }
+ </script>
+ <iframe id="iframe2"></iframe>
+</body>
+
+</html>
diff --git a/dom/serviceworkers/test/thirdparty/iframe2.html b/dom/serviceworkers/test/thirdparty/iframe2.html
new file mode 100644
index 0000000000..8013899195
--- /dev/null
+++ b/dom/serviceworkers/test/thirdparty/iframe2.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+ window.parent.postMessage({
+ source: "iframe",
+ status: "networkresponse"
+ }, "*");
+ var w = new Worker('worker.js');
+ w.onmessage = function(evt) {
+ window.parent.postMessage({
+ source: 'worker',
+ status: evt.data,
+ }, '*');
+ };
+</script>
diff --git a/dom/serviceworkers/test/thirdparty/register.html b/dom/serviceworkers/test/thirdparty/register.html
new file mode 100644
index 0000000000..b166acb8a4
--- /dev/null
+++ b/dom/serviceworkers/test/thirdparty/register.html
@@ -0,0 +1,29 @@
+<!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);
+ }
+ }).catch(function(e) {
+ window.parent.postMessage({status: "registrationfailed"}, "*");
+ });
+</script>
diff --git a/dom/serviceworkers/test/thirdparty/sw.js b/dom/serviceworkers/test/thirdparty/sw.js
new file mode 100644
index 0000000000..ed91f333bf
--- /dev/null
+++ b/dom/serviceworkers/test/thirdparty/sw.js
@@ -0,0 +1,32 @@
+self.addEventListener("fetch", function (event) {
+ dump("fetch " + event.request.url + "\n");
+ if (event.request.url.includes("iframe2.html")) {
+ var body =
+ "<script>" +
+ "window.parent.postMessage({" +
+ "source: 'iframe', status: 'swresponse'" +
+ "}, '*');" +
+ "var w = new Worker('worker.js');" +
+ "w.onmessage = function(evt) {" +
+ "window.parent.postMessage({" +
+ "source: 'worker'," +
+ "status: evt.data," +
+ "}, '*');" +
+ "};" +
+ "</script>";
+ event.respondWith(
+ new Response(body, {
+ headers: { "Content-Type": "text/html" },
+ })
+ );
+ return;
+ }
+ if (event.request.url.includes("worker.js")) {
+ var body = "self.postMessage('worker-swresponse');";
+ event.respondWith(
+ new Response(body, {
+ headers: { "Content-Type": "application/javascript" },
+ })
+ );
+ }
+});
diff --git a/dom/serviceworkers/test/thirdparty/unregister.html b/dom/serviceworkers/test/thirdparty/unregister.html
new file mode 100644
index 0000000000..65b29d5648
--- /dev/null
+++ b/dom/serviceworkers/test/thirdparty/unregister.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script>
+ if (navigator.serviceWorker.controller) {
+ window.parent.postMessage({status: "controlled"}, "*");
+ } else {
+ window.parent.postMessage({status: "uncontrolled"}, "*");
+ }
+
+ navigator.serviceWorker.getRegistration(".").then(function(registration) {
+ if(!registration) {
+ return;
+ }
+ registration.unregister().then(() => {
+ window.parent.postMessage({status: "unregistrationdone"}, "*");
+ });
+ }).catch(function(e) {
+ window.parent.postMessage({status: "getregistrationfailed"}, "*");
+ });
+</script>
diff --git a/dom/serviceworkers/test/thirdparty/worker.js b/dom/serviceworkers/test/thirdparty/worker.js
new file mode 100644
index 0000000000..bbdc608cde
--- /dev/null
+++ b/dom/serviceworkers/test/thirdparty/worker.js
@@ -0,0 +1 @@
+self.postMessage("worker-networkresponse");
diff --git a/dom/serviceworkers/test/unregister/index.html b/dom/serviceworkers/test/unregister/index.html
new file mode 100644
index 0000000000..36cac9fcf6
--- /dev/null
+++ b/dom/serviceworkers/test/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 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/serviceworkers/test/unregister/unregister.html b/dom/serviceworkers/test/unregister/unregister.html
new file mode 100644
index 0000000000..42633ca343
--- /dev/null
+++ b/dom/serviceworkers/test/unregister/unregister.html
@@ -0,0 +1,21 @@
+<!--
+ 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 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/serviceworkers/test/unresolved_fetch_worker.js b/dom/serviceworkers/test/unresolved_fetch_worker.js
new file mode 100644
index 0000000000..fae74f34b8
--- /dev/null
+++ b/dom/serviceworkers/test/unresolved_fetch_worker.js
@@ -0,0 +1,18 @@
+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) {})));
+};
+
+addEventListener("activate", function (event) {
+ event.waitUntil(clients.claim());
+});
diff --git a/dom/serviceworkers/test/update_worker.sjs b/dom/serviceworkers/test/update_worker.sjs
new file mode 100644
index 0000000000..44782a2732
--- /dev/null
+++ b/dom/serviceworkers/test/update_worker.sjs
@@ -0,0 +1,12 @@
+/* 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";
+
+function handleRequest(request, response) {
+ // This header is necessary for making this script able to be loaded.
+ response.setHeader("Content-Type", "application/javascript");
+
+ var body = "/* " + Date.now() + " */";
+ response.write(body);
+}
diff --git a/dom/serviceworkers/test/updatefoundevent.html b/dom/serviceworkers/test/updatefoundevent.html
new file mode 100644
index 0000000000..78088c7cd0
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/utils.js b/dom/serviceworkers/test/utils.js
new file mode 100644
index 0000000000..28be239593
--- /dev/null
+++ b/dom/serviceworkers/test/utils.js
@@ -0,0 +1,136 @@
+function waitForState(worker, state, context) {
+ return new Promise(resolve => {
+ function onStateChange() {
+ if (worker.state === state) {
+ worker.removeEventListener("statechange", onStateChange);
+ resolve(context);
+ }
+ }
+
+ // First add an event listener, so we won't miss any change that happens
+ // before we check the current state.
+ worker.addEventListener("statechange", onStateChange);
+
+ // Now check if the worker is already in the desired state.
+ onStateChange();
+ });
+}
+
+/**
+ * Helper for browser tests to issue register calls from the content global and
+ * wait for the SW to progress to the active state, as most tests desire.
+ * From the ContentTask.spawn, use via
+ * `content.wrappedJSObject.registerAndWaitForActive`.
+ */
+async function registerAndWaitForActive(script, maybeScope) {
+ console.log("...calling register");
+ let opts = undefined;
+ if (maybeScope) {
+ opts = { scope: maybeScope };
+ }
+ const reg = await navigator.serviceWorker.register(script, opts);
+ // Unless registration resurrection happens, the SW should be in the
+ // installing slot.
+ console.log("...waiting for activation");
+ await waitForState(reg.installing, "activated", reg);
+ console.log("...activated!");
+ return reg;
+}
+
+/**
+ * Helper to create an iframe with the given URL and return the first
+ * postMessage payload received. This is intended to be used when creating
+ * cross-origin iframes.
+ *
+ * A promise will be returned that resolves with the payload of the postMessage
+ * call.
+ */
+function createIframeAndWaitForMessage(url) {
+ const iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ return new Promise(resolve => {
+ window.addEventListener(
+ "message",
+ event => {
+ resolve(event.data);
+ },
+ { once: true }
+ );
+ iframe.src = url;
+ });
+}
+
+/**
+ * Helper to create a nested iframe into the iframe created by
+ * createIframeAndWaitForMessage().
+ *
+ * A promise will be returned that resolves with the payload of the postMessage
+ * call.
+ */
+function createNestedIframeAndWaitForMessage(url) {
+ const iframe = document.getElementsByTagName("iframe")[0];
+ iframe.contentWindow.postMessage("create nested iframe", "*");
+ return new Promise(resolve => {
+ window.addEventListener(
+ "message",
+ event => {
+ resolve(event.data);
+ },
+ { once: true }
+ );
+ });
+}
+
+async function unregisterAll() {
+ const registrations = await navigator.serviceWorker.getRegistrations();
+ for (const reg of registrations) {
+ await reg.unregister();
+ }
+}
+
+/**
+ * Make a blob that contains random data and therefore shouldn't compress all
+ * that well.
+ */
+function makeRandomBlob(size) {
+ const arr = new Uint8Array(size);
+ let offset = 0;
+ /**
+ * getRandomValues will only provide a maximum of 64k of data at a time and
+ * will error if we ask for more, so using a while loop for get a random value
+ * which much larger than 64k.
+ * https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#exceptions
+ */
+ while (offset < size) {
+ const nextSize = Math.min(size - offset, 65536);
+ window.crypto.getRandomValues(new Uint8Array(arr.buffer, offset, nextSize));
+ offset += nextSize;
+ }
+ return new Blob([arr], { type: "application/octet-stream" });
+}
+
+async function fillStorage(cacheBytes, idbBytes) {
+ // ## Fill Cache API Storage
+ const cache = await caches.open("filler");
+ await cache.put("fill", new Response(makeRandomBlob(cacheBytes)));
+
+ // ## Fill IDB
+ const storeName = "filler";
+ let db = await new Promise((resolve, reject) => {
+ let openReq = indexedDB.open("filler", 1);
+ openReq.onerror = event => {
+ reject(event.target.error);
+ };
+ openReq.onsuccess = event => {
+ resolve(event.target.result);
+ };
+ openReq.onupgradeneeded = event => {
+ const useDB = event.target.result;
+ useDB.onerror = error => {
+ reject(error);
+ };
+ const store = useDB.createObjectStore(storeName);
+ store.put({ blob: makeRandomBlob(idbBytes) }, "filler-blob");
+ };
+ });
+}
diff --git a/dom/serviceworkers/test/window_party_iframes.html b/dom/serviceworkers/test/window_party_iframes.html
new file mode 100644
index 0000000000..abeea4449b
--- /dev/null
+++ b/dom/serviceworkers/test/window_party_iframes.html
@@ -0,0 +1,18 @@
+<!--
+ 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">
+</head>
+<body>
+<iframe></iframe>
+<script>
+window.onmessage = e => {
+ opener.postMessage(e.data, "*");
+}
+</script>
+</body>
+</html>
diff --git a/dom/serviceworkers/test/worker.js b/dom/serviceworkers/test/worker.js
new file mode 100644
index 0000000000..2aba167d18
--- /dev/null
+++ b/dom/serviceworkers/test/worker.js
@@ -0,0 +1 @@
+// empty worker, always succeed!
diff --git a/dom/serviceworkers/test/worker2.js b/dom/serviceworkers/test/worker2.js
new file mode 100644
index 0000000000..3072d0817f
--- /dev/null
+++ b/dom/serviceworkers/test/worker2.js
@@ -0,0 +1 @@
+// worker2.js
diff --git a/dom/serviceworkers/test/worker3.js b/dom/serviceworkers/test/worker3.js
new file mode 100644
index 0000000000..449fc2f976
--- /dev/null
+++ b/dom/serviceworkers/test/worker3.js
@@ -0,0 +1 @@
+// worker3.js
diff --git a/dom/serviceworkers/test/workerUpdate/update.html b/dom/serviceworkers/test/workerUpdate/update.html
new file mode 100644
index 0000000000..666e213d14
--- /dev/null
+++ b/dom/serviceworkers/test/workerUpdate/update.html
@@ -0,0 +1,23 @@
+<!--
+ 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 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/serviceworkers/test/worker_unregister.js b/dom/serviceworkers/test/worker_unregister.js
new file mode 100644
index 0000000000..6aa7c3d501
--- /dev/null
+++ b/dom/serviceworkers/test/worker_unregister.js
@@ -0,0 +1,22 @@
+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/serviceworkers/test/worker_update.js b/dom/serviceworkers/test/worker_update.js
new file mode 100644
index 0000000000..8935cedc52
--- /dev/null
+++ b/dom/serviceworkers/test/worker_update.js
@@ -0,0 +1,25 @@
+// 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 instanceof ServiceWorkerRegistration ? "FINISH" : "FAIL";
+ })
+ .catch(function (ex) {
+ return "FAIL";
+ })
+ .then(function (result) {
+ clients.matchAll().then(function (c) {
+ if (!c.length) {
+ dump(
+ "!!!!!!!!!!! WORKER HAS NO CLIENTS TO FINISH TEST !!!!!!!!!!!!\n"
+ );
+ return;
+ }
+
+ c[0].postMessage(result);
+ });
+ });
+};
diff --git a/dom/serviceworkers/test/worker_updatefoundevent.js b/dom/serviceworkers/test/worker_updatefoundevent.js
new file mode 100644
index 0000000000..96a1815ee5
--- /dev/null
+++ b/dom/serviceworkers/test/worker_updatefoundevent.js
@@ -0,0 +1,20 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+registration.onupdatefound = function (e) {
+ clients.matchAll().then(function (clients) {
+ if (!clients.length) {
+ // We don't control any clients when the first update event is fired
+ // because we haven't reached the 'activated' state.
+ return;
+ }
+
+ if (registration.scope.match(/updatefoundevent\.html$/)) {
+ clients[0].postMessage("finish");
+ } else {
+ dump("Scope did not match");
+ }
+ });
+};
diff --git a/dom/serviceworkers/test/worker_updatefoundevent2.js b/dom/serviceworkers/test/worker_updatefoundevent2.js
new file mode 100644
index 0000000000..da4c592aad
--- /dev/null
+++ b/dom/serviceworkers/test/worker_updatefoundevent2.js
@@ -0,0 +1 @@
+// Not useful.
diff --git a/dom/serviceworkers/test/xslt/test.xml b/dom/serviceworkers/test/xslt/test.xml
new file mode 100644
index 0000000000..83c7776339
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/xslt/xslt.sjs b/dom/serviceworkers/test/xslt/xslt.sjs
new file mode 100644
index 0000000000..db681ab500
--- /dev/null
+++ b/dom/serviceworkers/test/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/serviceworkers/test/xslt_worker.js b/dom/serviceworkers/test/xslt_worker.js
new file mode 100644
index 0000000000..d7e6eea129
--- /dev/null
+++ b/dom/serviceworkers/test/xslt_worker.js
@@ -0,0 +1,58 @@
+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/serviceworkers/test/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/serviceworkers/test/xslt/xslt.sjs?" +
+ escape(xslt);
+ event.respondWith(fetch(url, { mode: "no-cors" }));
+ } else {
+ event.respondWith(Response.error());
+ }
+ }
+};
+
+onmessage = function (event) {
+ testType = event.data;
+};