From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- dom/serviceworkers/test/ForceRefreshChild.sys.mjs | 12 + dom/serviceworkers/test/ForceRefreshParent.sys.mjs | 77 ++ .../test/abrupt_completion_worker.js | 18 + .../test/activate_event_error_worker.js | 4 + dom/serviceworkers/test/async_waituntil_worker.js | 53 ++ .../test/blocking_install_event_worker.js | 22 + dom/serviceworkers/test/browser-common.ini | 52 ++ dom/serviceworkers/test/browser-dFPI.ini | 7 + dom/serviceworkers/test/browser.ini | 4 + dom/serviceworkers/test/browser_antitracking.js | 103 +++ .../test/browser_antitracking_subiframes.js | 103 +++ .../test/browser_base_force_refresh.html | 27 + .../test/browser_cached_force_refresh.html | 59 ++ .../browser_devtools_serviceworker_interception.js | 264 ++++++ dom/serviceworkers/test/browser_download.js | 93 ++ .../test/browser_download_canceled.js | 174 ++++ dom/serviceworkers/test/browser_force_refresh.js | 87 ++ dom/serviceworkers/test/browser_head.js | 318 +++++++ .../browser_intercepted_channel_process_swap.js | 110 +++ .../test/browser_intercepted_worker_script.js | 102 +++ ...ser_navigationPreload_read_after_respondWith.js | 115 +++ .../browser_navigation_fetch_fault_handling.js | 272 ++++++ .../test/browser_remote_type_process_swap.js | 138 +++ .../test/browser_storage_permission.js | 297 +++++++ .../test/browser_storage_recovery.js | 156 ++++ .../test/browser_unregister_with_containers.js | 153 ++++ .../test/browser_userContextId_openWindow.js | 162 ++++ dom/serviceworkers/test/bug1151916_driver.html | 53 ++ dom/serviceworkers/test/bug1151916_worker.js | 15 + dom/serviceworkers/test/bug1240436_worker.js | 2 + dom/serviceworkers/test/chrome-common.ini | 21 + dom/serviceworkers/test/chrome-dFPI.ini | 7 + dom/serviceworkers/test/chrome.ini | 4 + dom/serviceworkers/test/chrome_helpers.js | 71 ++ dom/serviceworkers/test/claim_clients/client.html | 43 + dom/serviceworkers/test/claim_oninstall_worker.js | 7 + dom/serviceworkers/test/claim_worker_1.js | 32 + dom/serviceworkers/test/claim_worker_2.js | 34 + dom/serviceworkers/test/close_test.js | 22 + dom/serviceworkers/test/console_monitor.js | 44 + dom/serviceworkers/test/controller/index.html | 72 ++ .../test/create_another_sharedWorker.html | 6 + dom/serviceworkers/test/download/window.html | 46 + dom/serviceworkers/test/download/worker.js | 34 + .../download_canceled/page_download_canceled.html | 58 ++ .../download_canceled/server-stream-download.sjs | 133 +++ .../test/download_canceled/sw_download_canceled.js | 150 ++++ dom/serviceworkers/test/empty.html | 0 dom/serviceworkers/test/empty.js | 0 dom/serviceworkers/test/empty_with_utils.html | 13 + dom/serviceworkers/test/error_reporting_helpers.js | 73 ++ dom/serviceworkers/test/eval_worker.js | 1 + .../test/eventsource/eventsource.resource | 22 + .../test/eventsource/eventsource.resource^headers^ | 3 + .../eventsource/eventsource_cors_response.html | 75 ++ .../eventsource_cors_response_intercept_worker.js | 30 + .../eventsource_mixed_content_cors_response.html | 75 ++ ...mixed_content_cors_response_intercept_worker.js | 29 + .../eventsource/eventsource_opaque_response.html | 75 ++ ...eventsource_opaque_response_intercept_worker.js | 30 + .../eventsource/eventsource_register_worker.html | 27 + .../eventsource_synthetic_response.html | 75 ++ ...ntsource_synthetic_response_intercept_worker.js | 27 + .../test/eventsource/eventsource_worker_helper.js | 17 + dom/serviceworkers/test/fetch.js | 33 + .../test/fetch/cookie/cookie_test.js | 11 + dom/serviceworkers/test/fetch/cookie/register.html | 19 + .../test/fetch/cookie/unregister.html | 12 + dom/serviceworkers/test/fetch/deliver-gzip.sjs | 21 + dom/serviceworkers/test/fetch/fetch_tests.js | 716 ++++++++++++++++ .../test/fetch/fetch_worker_script.js | 28 + dom/serviceworkers/test/fetch/hsts/embedder.html | 7 + dom/serviceworkers/test/fetch/hsts/hsts_test.js | 11 + dom/serviceworkers/test/fetch/hsts/image-20px.png | Bin 0 -> 87 bytes dom/serviceworkers/test/fetch/hsts/image-40px.png | Bin 0 -> 123 bytes dom/serviceworkers/test/fetch/hsts/image.html | 13 + dom/serviceworkers/test/fetch/hsts/realindex.html | 8 + dom/serviceworkers/test/fetch/hsts/register.html | 14 + .../test/fetch/hsts/register.html^headers^ | 2 + dom/serviceworkers/test/fetch/hsts/unregister.html | 12 + .../test/fetch/https/clonedresponse/https_test.js | 19 + .../test/fetch/https/clonedresponse/index.html | 4 + .../test/fetch/https/clonedresponse/register.html | 14 + .../fetch/https/clonedresponse/unregister.html | 12 + dom/serviceworkers/test/fetch/https/https_test.js | 31 + dom/serviceworkers/test/fetch/https/index.html | 4 + dom/serviceworkers/test/fetch/https/register.html | 20 + .../test/fetch/https/unregister.html | 12 + .../test/fetch/imagecache-maxage/image-20px.png | Bin 0 -> 87 bytes .../test/fetch/imagecache-maxage/image-40px.png | Bin 0 -> 123 bytes .../test/fetch/imagecache-maxage/index.html | 29 + .../test/fetch/imagecache-maxage/maxage_test.js | 45 + .../test/fetch/imagecache-maxage/register.html | 14 + .../test/fetch/imagecache-maxage/unregister.html | 12 + .../test/fetch/imagecache/image-20px.png | Bin 0 -> 87 bytes .../test/fetch/imagecache/image-40px.png | Bin 0 -> 123 bytes .../test/fetch/imagecache/imagecache_test.js | 15 + .../test/fetch/imagecache/index.html | 20 + .../test/fetch/imagecache/postmortem.html | 9 + .../test/fetch/imagecache/register.html | 16 + .../test/fetch/imagecache/unregister.html | 12 + .../fetch/importscript-mixedcontent/https_test.js | 31 + .../fetch/importscript-mixedcontent/register.html | 14 + .../importscript-mixedcontent/unregister.html | 12 + dom/serviceworkers/test/fetch/index.html | 191 +++++ dom/serviceworkers/test/fetch/interrupt.sjs | 20 + .../test/fetch/origin/https/index-https.sjs | 8 + .../test/fetch/origin/https/origin_test.js | 29 + .../test/fetch/origin/https/realindex.html | 6 + .../fetch/origin/https/realindex.html^headers^ | 1 + .../test/fetch/origin/https/register.html | 14 + .../test/fetch/origin/https/unregister.html | 12 + .../test/fetch/origin/index-to-https.sjs | 8 + dom/serviceworkers/test/fetch/origin/index.sjs | 8 + .../test/fetch/origin/origin_test.js | 38 + .../test/fetch/origin/realindex.html | 6 + .../test/fetch/origin/realindex.html^headers^ | 1 + dom/serviceworkers/test/fetch/origin/register.html | 14 + .../test/fetch/origin/unregister.html | 12 + dom/serviceworkers/test/fetch/plugin/plugins.html | 43 + dom/serviceworkers/test/fetch/plugin/worker.js | 15 + dom/serviceworkers/test/fetch/real-file.txt | 1 + dom/serviceworkers/test/fetch/redirect.sjs | 4 + .../test/fetch/requesturl/index.html | 7 + .../test/fetch/requesturl/redirect.sjs | 8 + .../test/fetch/requesturl/redirector.html | 2 + .../test/fetch/requesturl/register.html | 14 + .../test/fetch/requesturl/requesturl_test.js | 21 + .../test/fetch/requesturl/secret.html | 5 + .../test/fetch/requesturl/unregister.html | 12 + dom/serviceworkers/test/fetch/sandbox/index.html | 5 + .../test/fetch/sandbox/intercepted_index.html | 5 + .../test/fetch/sandbox/register.html | 14 + .../test/fetch/sandbox/sandbox_test.js | 5 + .../test/fetch/sandbox/unregister.html | 12 + .../test/fetch/upgrade-insecure/embedder.html | 10 + .../fetch/upgrade-insecure/embedder.html^headers^ | 1 + .../test/fetch/upgrade-insecure/image-20px.png | Bin 0 -> 87 bytes .../test/fetch/upgrade-insecure/image-40px.png | Bin 0 -> 123 bytes .../test/fetch/upgrade-insecure/image.html | 13 + .../test/fetch/upgrade-insecure/realindex.html | 4 + .../test/fetch/upgrade-insecure/register.html | 14 + .../test/fetch/upgrade-insecure/unregister.html | 12 + .../upgrade-insecure/upgrade-insecure_test.js | 11 + dom/serviceworkers/test/fetch_event_worker.js | 364 ++++++++ .../test/file_blob_response_worker.js | 39 + dom/serviceworkers/test/file_js_cache.html | 10 + dom/serviceworkers/test/file_js_cache.js | 5 + dom/serviceworkers/test/file_js_cache_cleanup.js | 16 + .../test/file_js_cache_save_after_load.html | 10 + .../test/file_js_cache_save_after_load.js | 15 + .../test/file_js_cache_syntax_error.html | 10 + .../test/file_js_cache_syntax_error.js | 1 + .../test/file_js_cache_with_sri.html | 12 + .../test/file_notification_openWindow.html | 26 + .../test/file_userContextId_openWindow.js | 3 + .../test/force_refresh_browser_worker.js | 42 + dom/serviceworkers/test/force_refresh_worker.js | 43 + dom/serviceworkers/test/gtest/TestReadWrite.cpp | 952 +++++++++++++++++++++ dom/serviceworkers/test/gtest/moz.build | 13 + dom/serviceworkers/test/gzip_redirect_worker.js | 15 + dom/serviceworkers/test/header_checker.sjs | 9 + dom/serviceworkers/test/hello.html | 9 + dom/serviceworkers/test/importscript.sjs | 11 + dom/serviceworkers/test/importscript_worker.js | 46 + .../test/install_event_error_worker.js | 9 + dom/serviceworkers/test/install_event_worker.js | 3 + .../intercepted_channel_process_swap_worker.js | 7 + dom/serviceworkers/test/isolated/README.md | 19 + .../test/isolated/multi-e10s-update/browser.ini | 7 + .../multi-e10s-update/browser_multie10s_update.js | 147 ++++ .../multi-e10s-update/file_multie10s_update.html | 40 + .../multi-e10s-update/server_multie10s_update.sjs | 100 +++ dom/serviceworkers/test/lazy_worker.js | 8 + dom/serviceworkers/test/lorem_script.js | 8 + .../test/match_all_advanced_worker.js | 5 + .../test/match_all_client/match_all_client_id.html | 31 + .../test/match_all_client_id_worker.js | 28 + .../match_all_clients/match_all_controlled.html | 83 ++ .../test/match_all_properties_worker.js | 28 + dom/serviceworkers/test/match_all_worker.js | 10 + dom/serviceworkers/test/message_posting_worker.js | 8 + dom/serviceworkers/test/message_receiver.html | 6 + dom/serviceworkers/test/mochitest-common.ini | 377 ++++++++ dom/serviceworkers/test/mochitest-dFPI.ini | 11 + dom/serviceworkers/test/mochitest.ini | 39 + .../test/navigationPreload_page.html | 1 + dom/serviceworkers/test/network_with_utils.html | 14 + dom/serviceworkers/test/nofetch_handler_worker.js | 14 + dom/serviceworkers/test/notification/register.html | 11 + .../test/notification_constructor_error.js | 1 + dom/serviceworkers/test/notification_get_sw.js | 0 .../test/notification_openWindow_worker.js | 25 + .../test/notificationclick-otherwindow.html | 30 + dom/serviceworkers/test/notificationclick.html | 27 + dom/serviceworkers/test/notificationclick.js | 23 + .../test/notificationclick_focus.html | 28 + dom/serviceworkers/test/notificationclick_focus.js | 49 ++ dom/serviceworkers/test/notificationclose.html | 37 + dom/serviceworkers/test/notificationclose.js | 31 + dom/serviceworkers/test/notify_loaded.js | 1 + dom/serviceworkers/test/onmessageerror_worker.js | 54 ++ dom/serviceworkers/test/opaque_intercept_worker.js | 40 + dom/serviceworkers/test/openWindow_worker.js | 178 ++++ dom/serviceworkers/test/open_window/client.sjs | 69 ++ dom/serviceworkers/test/page_post_controlled.html | 27 + dom/serviceworkers/test/parse_error_worker.js | 2 + .../test/pref/fetch_nonexistent_file.html | 15 + .../test/pref/intercept_nonexistent_file_sw.js | 5 + dom/serviceworkers/test/redirect.sjs | 4 + dom/serviceworkers/test/redirect_post.sjs | 39 + dom/serviceworkers/test/redirect_serviceworker.sjs | 7 + dom/serviceworkers/test/register_https.html | 15 + .../sanitize/example_check_and_unregister.html | 22 + dom/serviceworkers/test/sanitize/frame.html | 11 + dom/serviceworkers/test/sanitize/register.html | 9 + dom/serviceworkers/test/sanitize_worker.js | 5 + dom/serviceworkers/test/scope/scope_worker.js | 2 + dom/serviceworkers/test/script_file_upload.js | 15 + dom/serviceworkers/test/self_update_worker.sjs | 42 + dom/serviceworkers/test/server_file_upload.sjs | 22 + dom/serviceworkers/test/service_worker.js | 9 + dom/serviceworkers/test/service_worker_client.html | 28 + dom/serviceworkers/test/serviceworker.html | 12 + .../test/serviceworker_not_sharedworker.js | 20 + dom/serviceworkers/test/serviceworker_wrapper.js | 92 ++ .../test/serviceworkerinfo_iframe.html | 27 + .../test/serviceworkermanager_iframe.html | 34 + .../test/serviceworkerregistrationinfo_iframe.html | 30 + dom/serviceworkers/test/sharedWorker_fetch.js | 30 + dom/serviceworkers/test/simple_fetch_worker.js | 18 + dom/serviceworkers/test/simpleregister/index.html | 51 ++ dom/serviceworkers/test/simpleregister/ready.html | 14 + .../test/skip_waiting_installed_worker.js | 6 + .../test/skip_waiting_scope/index.html | 33 + .../test/source_message_posting_worker.js | 16 + .../test/storage_recovery_worker.sjs | 23 + dom/serviceworkers/test/streamfilter_server.sjs | 9 + dom/serviceworkers/test/streamfilter_worker.js | 9 + dom/serviceworkers/test/strict_mode_warning.js | 4 + dom/serviceworkers/test/sw_bad_mime_type.js | 1 + .../test/sw_bad_mime_type.js^headers^ | 1 + .../test/sw_clients/file_blob_upload_frame.html | 76 ++ dom/serviceworkers/test/sw_clients/navigator.html | 34 + dom/serviceworkers/test/sw_clients/refresher.html | 38 + .../test/sw_clients/refresher_cached.html | 37 + .../sw_clients/refresher_cached_compressed.html | Bin 0 -> 560 bytes .../refresher_cached_compressed.html^headers^ | 2 + .../test/sw_clients/refresher_compressed.html | Bin 0 -> 609 bytes .../sw_clients/refresher_compressed.html^headers^ | 2 + .../test/sw_clients/service_worker_controlled.html | 38 + dom/serviceworkers/test/sw_clients/simple.html | 29 + dom/serviceworkers/test/sw_file_upload.js | 16 + .../test/sw_respondwith_serviceworker.js | 24 + dom/serviceworkers/test/sw_storage_not_allow.js | 33 + .../test/sw_with_navigationPreload.js | 28 + .../test/swa/worker_scope_different.js | 0 .../test/swa/worker_scope_different.js^headers^ | 1 + .../test/swa/worker_scope_different2.js | 0 .../test/swa/worker_scope_different2.js^headers^ | 1 + .../test/swa/worker_scope_precise.js | 0 .../test/swa/worker_scope_precise.js^headers^ | 1 + .../test/swa/worker_scope_too_deep.js | 0 .../test/swa/worker_scope_too_deep.js^headers^ | 1 + .../test/swa/worker_scope_too_narrow.js | 0 .../test/swa/worker_scope_too_narrow.js^headers^ | 1 + .../test/test_abrupt_completion.html | 144 ++++ dom/serviceworkers/test/test_async_waituntil.html | 91 ++ dom/serviceworkers/test/test_bad_script_cache.html | 96 +++ dom/serviceworkers/test/test_bug1151916.html | 104 +++ dom/serviceworkers/test/test_bug1240436.html | 34 + dom/serviceworkers/test/test_bug1408734.html | 52 ++ dom/serviceworkers/test/test_claim.html | 171 ++++ dom/serviceworkers/test/test_claim_oninstall.html | 77 ++ dom/serviceworkers/test/test_controller.html | 83 ++ dom/serviceworkers/test/test_cookie_fetch.html | 64 ++ .../test/test_cross_origin_url_after_redirect.html | 50 ++ .../test/test_csp_upgrade-insecure_intercept.html | 55 ++ .../test/test_devtools_bypass_serviceworker.html | 107 +++ .../test_devtools_track_serviceworker_time.html | 236 +++++ .../test/test_empty_serviceworker.html | 46 + dom/serviceworkers/test/test_enabled_pref.html | 55 ++ dom/serviceworkers/test/test_error_reporting.html | 241 ++++++ dom/serviceworkers/test/test_escapedSlashes.html | 102 +++ dom/serviceworkers/test/test_eval_allowed.html | 51 ++ .../test/test_eval_allowed.html^headers^ | 1 + .../test/test_event_listener_leaks.html | 63 ++ .../test/test_eventsource_intercept.html | 103 +++ dom/serviceworkers/test/test_fetch_event.html | 75 ++ .../test/test_fetch_event_with_thirdpartypref.html | 90 ++ dom/serviceworkers/test/test_fetch_integrity.html | 228 +++++ .../test/test_file_blob_response.html | 78 ++ dom/serviceworkers/test/test_file_blob_upload.html | 146 ++++ dom/serviceworkers/test/test_file_upload.html | 68 ++ dom/serviceworkers/test/test_force_refresh.html | 105 +++ dom/serviceworkers/test/test_gzip_redirect.html | 88 ++ .../test/test_hsts_upgrade_intercept.html | 66 ++ dom/serviceworkers/test/test_https_fetch.html | 62 ++ .../test/test_https_fetch_cloned_response.html | 56 ++ .../test/test_https_origin_after_redirect.html | 57 ++ .../test_https_origin_after_redirect_cached.html | 57 ++ .../test_https_synth_fetch_from_cached_sw.html | 69 ++ dom/serviceworkers/test/test_imagecache.html | 55 ++ .../test/test_imagecache_max_age.html | 71 ++ dom/serviceworkers/test/test_importscript.html | 74 ++ .../test/test_importscript_mixedcontent.html | 53 ++ dom/serviceworkers/test/test_install_event.html | 143 ++++ dom/serviceworkers/test/test_install_event_gc.html | 121 +++ .../test/test_installation_simple.html | 208 +++++ dom/serviceworkers/test/test_match_all.html | 83 ++ .../test/test_match_all_advanced.html | 102 +++ .../test/test_match_all_client_id.html | 95 ++ .../test/test_match_all_client_properties.html | 101 +++ .../test/test_navigationPreload_disable_crash.html | 52 ++ dom/serviceworkers/test/test_navigator.html | 40 + dom/serviceworkers/test/test_nofetch_handler.html | 57 ++ .../test/test_not_intercept_plugin.html | 75 ++ .../test/test_notification_constructor_error.html | 51 ++ dom/serviceworkers/test/test_notification_get.html | 137 +++ .../test/test_notification_openWindow.html | 90 ++ .../test/test_notificationclick-otherwindow.html | 64 ++ .../test/test_notificationclick.html | 65 ++ .../test/test_notificationclick_focus.html | 65 ++ .../test/test_notificationclose.html | 66 ++ dom/serviceworkers/test/test_onmessageerror.html | 128 +++ dom/serviceworkers/test/test_opaque_intercept.html | 93 ++ dom/serviceworkers/test/test_openWindow.html | 111 +++ .../test/test_origin_after_redirect.html | 58 ++ .../test/test_origin_after_redirect_cached.html | 58 ++ .../test/test_origin_after_redirect_to_https.html | 57 ++ ...test_origin_after_redirect_to_https_cached.html | 57 ++ dom/serviceworkers/test/test_post_message.html | 80 ++ .../test/test_post_message_advanced.html | 109 +++ .../test/test_post_message_source.html | 66 ++ dom/serviceworkers/test/test_privateBrowsing.html | 103 +++ dom/serviceworkers/test/test_register_base.html | 34 + .../test/test_register_https_in_http.html | 45 + .../test/test_sandbox_intercept.html | 56 ++ dom/serviceworkers/test/test_sanitize.html | 86 ++ dom/serviceworkers/test/test_sanitize_domain.html | 89 ++ dom/serviceworkers/test/test_scopes.html | 143 ++++ .../test_script_loader_intercepted_js_cache.html | 224 +++++ .../test/test_self_update_worker.html | 136 +++ .../test/test_service_worker_allowed.html | 74 ++ dom/serviceworkers/test/test_serviceworker.html | 79 ++ .../test/test_serviceworker_header.html | 41 + .../test/test_serviceworker_interfaces.html | 116 +++ .../test/test_serviceworker_interfaces.js | 567 ++++++++++++ .../test/test_serviceworker_not_sharedworker.html | 66 ++ .../test/test_serviceworkerinfo.xhtml | 114 +++ .../test/test_serviceworkermanager.xhtml | 79 ++ .../test/test_serviceworkerregistrationinfo.xhtml | 155 ++++ dom/serviceworkers/test/test_skip_waiting.html | 86 ++ dom/serviceworkers/test/test_streamfilter.html | 207 +++++ .../test/test_strict_mode_warning.html | 42 + .../test/test_third_party_iframes.html | 263 ++++++ dom/serviceworkers/test/test_unregister.html | 137 +++ .../test/test_unresolved_fetch_interception.html | 95 ++ dom/serviceworkers/test/test_workerUnregister.html | 81 ++ dom/serviceworkers/test/test_workerUpdate.html | 63 ++ .../test/test_worker_reference_gc_timeout.html | 76 ++ .../test/test_workerupdatefoundevent.html | 91 ++ dom/serviceworkers/test/test_xslt.html | 117 +++ dom/serviceworkers/test/thirdparty/iframe1.html | 42 + dom/serviceworkers/test/thirdparty/iframe2.html | 14 + dom/serviceworkers/test/thirdparty/register.html | 29 + dom/serviceworkers/test/thirdparty/sw.js | 33 + dom/serviceworkers/test/thirdparty/unregister.html | 19 + dom/serviceworkers/test/thirdparty/worker.js | 1 + dom/serviceworkers/test/unregister/index.html | 26 + dom/serviceworkers/test/unregister/unregister.html | 21 + dom/serviceworkers/test/unresolved_fetch_worker.js | 18 + dom/serviceworkers/test/update_worker.sjs | 12 + dom/serviceworkers/test/updatefoundevent.html | 13 + dom/serviceworkers/test/utils.js | 136 +++ dom/serviceworkers/test/window_party_iframes.html | 18 + dom/serviceworkers/test/worker.js | 1 + dom/serviceworkers/test/worker2.js | 1 + dom/serviceworkers/test/worker3.js | 1 + dom/serviceworkers/test/workerUpdate/update.html | 23 + dom/serviceworkers/test/worker_unregister.js | 22 + dom/serviceworkers/test/worker_update.js | 25 + dom/serviceworkers/test/worker_updatefoundevent.js | 20 + .../test/worker_updatefoundevent2.js | 1 + dom/serviceworkers/test/xslt/test.xml | 6 + dom/serviceworkers/test/xslt/xslt.sjs | 12 + dom/serviceworkers/test/xslt_worker.js | 58 ++ 387 files changed, 20568 insertions(+) create mode 100644 dom/serviceworkers/test/ForceRefreshChild.sys.mjs create mode 100644 dom/serviceworkers/test/ForceRefreshParent.sys.mjs create mode 100644 dom/serviceworkers/test/abrupt_completion_worker.js create mode 100644 dom/serviceworkers/test/activate_event_error_worker.js create mode 100644 dom/serviceworkers/test/async_waituntil_worker.js create mode 100644 dom/serviceworkers/test/blocking_install_event_worker.js create mode 100644 dom/serviceworkers/test/browser-common.ini create mode 100644 dom/serviceworkers/test/browser-dFPI.ini create mode 100644 dom/serviceworkers/test/browser.ini create mode 100644 dom/serviceworkers/test/browser_antitracking.js create mode 100644 dom/serviceworkers/test/browser_antitracking_subiframes.js create mode 100644 dom/serviceworkers/test/browser_base_force_refresh.html create mode 100644 dom/serviceworkers/test/browser_cached_force_refresh.html create mode 100644 dom/serviceworkers/test/browser_devtools_serviceworker_interception.js create mode 100644 dom/serviceworkers/test/browser_download.js create mode 100644 dom/serviceworkers/test/browser_download_canceled.js create mode 100644 dom/serviceworkers/test/browser_force_refresh.js create mode 100644 dom/serviceworkers/test/browser_head.js create mode 100644 dom/serviceworkers/test/browser_intercepted_channel_process_swap.js create mode 100644 dom/serviceworkers/test/browser_intercepted_worker_script.js create mode 100644 dom/serviceworkers/test/browser_navigationPreload_read_after_respondWith.js create mode 100644 dom/serviceworkers/test/browser_navigation_fetch_fault_handling.js create mode 100644 dom/serviceworkers/test/browser_remote_type_process_swap.js create mode 100644 dom/serviceworkers/test/browser_storage_permission.js create mode 100644 dom/serviceworkers/test/browser_storage_recovery.js create mode 100644 dom/serviceworkers/test/browser_unregister_with_containers.js create mode 100644 dom/serviceworkers/test/browser_userContextId_openWindow.js create mode 100644 dom/serviceworkers/test/bug1151916_driver.html create mode 100644 dom/serviceworkers/test/bug1151916_worker.js create mode 100644 dom/serviceworkers/test/bug1240436_worker.js create mode 100644 dom/serviceworkers/test/chrome-common.ini create mode 100644 dom/serviceworkers/test/chrome-dFPI.ini create mode 100644 dom/serviceworkers/test/chrome.ini create mode 100644 dom/serviceworkers/test/chrome_helpers.js create mode 100644 dom/serviceworkers/test/claim_clients/client.html create mode 100644 dom/serviceworkers/test/claim_oninstall_worker.js create mode 100644 dom/serviceworkers/test/claim_worker_1.js create mode 100644 dom/serviceworkers/test/claim_worker_2.js create mode 100644 dom/serviceworkers/test/close_test.js create mode 100644 dom/serviceworkers/test/console_monitor.js create mode 100644 dom/serviceworkers/test/controller/index.html create mode 100644 dom/serviceworkers/test/create_another_sharedWorker.html create mode 100644 dom/serviceworkers/test/download/window.html create mode 100644 dom/serviceworkers/test/download/worker.js create mode 100644 dom/serviceworkers/test/download_canceled/page_download_canceled.html create mode 100644 dom/serviceworkers/test/download_canceled/server-stream-download.sjs create mode 100644 dom/serviceworkers/test/download_canceled/sw_download_canceled.js create mode 100644 dom/serviceworkers/test/empty.html create mode 100644 dom/serviceworkers/test/empty.js create mode 100644 dom/serviceworkers/test/empty_with_utils.html create mode 100644 dom/serviceworkers/test/error_reporting_helpers.js create mode 100644 dom/serviceworkers/test/eval_worker.js create mode 100644 dom/serviceworkers/test/eventsource/eventsource.resource create mode 100644 dom/serviceworkers/test/eventsource/eventsource.resource^headers^ create mode 100644 dom/serviceworkers/test/eventsource/eventsource_cors_response.html create mode 100644 dom/serviceworkers/test/eventsource/eventsource_cors_response_intercept_worker.js create mode 100644 dom/serviceworkers/test/eventsource/eventsource_mixed_content_cors_response.html create mode 100644 dom/serviceworkers/test/eventsource/eventsource_mixed_content_cors_response_intercept_worker.js create mode 100644 dom/serviceworkers/test/eventsource/eventsource_opaque_response.html create mode 100644 dom/serviceworkers/test/eventsource/eventsource_opaque_response_intercept_worker.js create mode 100644 dom/serviceworkers/test/eventsource/eventsource_register_worker.html create mode 100644 dom/serviceworkers/test/eventsource/eventsource_synthetic_response.html create mode 100644 dom/serviceworkers/test/eventsource/eventsource_synthetic_response_intercept_worker.js create mode 100644 dom/serviceworkers/test/eventsource/eventsource_worker_helper.js create mode 100644 dom/serviceworkers/test/fetch.js create mode 100644 dom/serviceworkers/test/fetch/cookie/cookie_test.js create mode 100644 dom/serviceworkers/test/fetch/cookie/register.html create mode 100644 dom/serviceworkers/test/fetch/cookie/unregister.html create mode 100644 dom/serviceworkers/test/fetch/deliver-gzip.sjs create mode 100644 dom/serviceworkers/test/fetch/fetch_tests.js create mode 100644 dom/serviceworkers/test/fetch/fetch_worker_script.js create mode 100644 dom/serviceworkers/test/fetch/hsts/embedder.html create mode 100644 dom/serviceworkers/test/fetch/hsts/hsts_test.js create mode 100644 dom/serviceworkers/test/fetch/hsts/image-20px.png create mode 100644 dom/serviceworkers/test/fetch/hsts/image-40px.png create mode 100644 dom/serviceworkers/test/fetch/hsts/image.html create mode 100644 dom/serviceworkers/test/fetch/hsts/realindex.html create mode 100644 dom/serviceworkers/test/fetch/hsts/register.html create mode 100644 dom/serviceworkers/test/fetch/hsts/register.html^headers^ create mode 100644 dom/serviceworkers/test/fetch/hsts/unregister.html create mode 100644 dom/serviceworkers/test/fetch/https/clonedresponse/https_test.js create mode 100644 dom/serviceworkers/test/fetch/https/clonedresponse/index.html create mode 100644 dom/serviceworkers/test/fetch/https/clonedresponse/register.html create mode 100644 dom/serviceworkers/test/fetch/https/clonedresponse/unregister.html create mode 100644 dom/serviceworkers/test/fetch/https/https_test.js create mode 100644 dom/serviceworkers/test/fetch/https/index.html create mode 100644 dom/serviceworkers/test/fetch/https/register.html create mode 100644 dom/serviceworkers/test/fetch/https/unregister.html create mode 100644 dom/serviceworkers/test/fetch/imagecache-maxage/image-20px.png create mode 100644 dom/serviceworkers/test/fetch/imagecache-maxage/image-40px.png create mode 100644 dom/serviceworkers/test/fetch/imagecache-maxage/index.html create mode 100644 dom/serviceworkers/test/fetch/imagecache-maxage/maxage_test.js create mode 100644 dom/serviceworkers/test/fetch/imagecache-maxage/register.html create mode 100644 dom/serviceworkers/test/fetch/imagecache-maxage/unregister.html create mode 100644 dom/serviceworkers/test/fetch/imagecache/image-20px.png create mode 100644 dom/serviceworkers/test/fetch/imagecache/image-40px.png create mode 100644 dom/serviceworkers/test/fetch/imagecache/imagecache_test.js create mode 100644 dom/serviceworkers/test/fetch/imagecache/index.html create mode 100644 dom/serviceworkers/test/fetch/imagecache/postmortem.html create mode 100644 dom/serviceworkers/test/fetch/imagecache/register.html create mode 100644 dom/serviceworkers/test/fetch/imagecache/unregister.html create mode 100644 dom/serviceworkers/test/fetch/importscript-mixedcontent/https_test.js create mode 100644 dom/serviceworkers/test/fetch/importscript-mixedcontent/register.html create mode 100644 dom/serviceworkers/test/fetch/importscript-mixedcontent/unregister.html create mode 100644 dom/serviceworkers/test/fetch/index.html create mode 100644 dom/serviceworkers/test/fetch/interrupt.sjs create mode 100644 dom/serviceworkers/test/fetch/origin/https/index-https.sjs create mode 100644 dom/serviceworkers/test/fetch/origin/https/origin_test.js create mode 100644 dom/serviceworkers/test/fetch/origin/https/realindex.html create mode 100644 dom/serviceworkers/test/fetch/origin/https/realindex.html^headers^ create mode 100644 dom/serviceworkers/test/fetch/origin/https/register.html create mode 100644 dom/serviceworkers/test/fetch/origin/https/unregister.html create mode 100644 dom/serviceworkers/test/fetch/origin/index-to-https.sjs create mode 100644 dom/serviceworkers/test/fetch/origin/index.sjs create mode 100644 dom/serviceworkers/test/fetch/origin/origin_test.js create mode 100644 dom/serviceworkers/test/fetch/origin/realindex.html create mode 100644 dom/serviceworkers/test/fetch/origin/realindex.html^headers^ create mode 100644 dom/serviceworkers/test/fetch/origin/register.html create mode 100644 dom/serviceworkers/test/fetch/origin/unregister.html create mode 100644 dom/serviceworkers/test/fetch/plugin/plugins.html create mode 100644 dom/serviceworkers/test/fetch/plugin/worker.js create mode 100644 dom/serviceworkers/test/fetch/real-file.txt create mode 100644 dom/serviceworkers/test/fetch/redirect.sjs create mode 100644 dom/serviceworkers/test/fetch/requesturl/index.html create mode 100644 dom/serviceworkers/test/fetch/requesturl/redirect.sjs create mode 100644 dom/serviceworkers/test/fetch/requesturl/redirector.html create mode 100644 dom/serviceworkers/test/fetch/requesturl/register.html create mode 100644 dom/serviceworkers/test/fetch/requesturl/requesturl_test.js create mode 100644 dom/serviceworkers/test/fetch/requesturl/secret.html create mode 100644 dom/serviceworkers/test/fetch/requesturl/unregister.html create mode 100644 dom/serviceworkers/test/fetch/sandbox/index.html create mode 100644 dom/serviceworkers/test/fetch/sandbox/intercepted_index.html create mode 100644 dom/serviceworkers/test/fetch/sandbox/register.html create mode 100644 dom/serviceworkers/test/fetch/sandbox/sandbox_test.js create mode 100644 dom/serviceworkers/test/fetch/sandbox/unregister.html create mode 100644 dom/serviceworkers/test/fetch/upgrade-insecure/embedder.html create mode 100644 dom/serviceworkers/test/fetch/upgrade-insecure/embedder.html^headers^ create mode 100644 dom/serviceworkers/test/fetch/upgrade-insecure/image-20px.png create mode 100644 dom/serviceworkers/test/fetch/upgrade-insecure/image-40px.png create mode 100644 dom/serviceworkers/test/fetch/upgrade-insecure/image.html create mode 100644 dom/serviceworkers/test/fetch/upgrade-insecure/realindex.html create mode 100644 dom/serviceworkers/test/fetch/upgrade-insecure/register.html create mode 100644 dom/serviceworkers/test/fetch/upgrade-insecure/unregister.html create mode 100644 dom/serviceworkers/test/fetch/upgrade-insecure/upgrade-insecure_test.js create mode 100644 dom/serviceworkers/test/fetch_event_worker.js create mode 100644 dom/serviceworkers/test/file_blob_response_worker.js create mode 100644 dom/serviceworkers/test/file_js_cache.html create mode 100644 dom/serviceworkers/test/file_js_cache.js create mode 100644 dom/serviceworkers/test/file_js_cache_cleanup.js create mode 100644 dom/serviceworkers/test/file_js_cache_save_after_load.html create mode 100644 dom/serviceworkers/test/file_js_cache_save_after_load.js create mode 100644 dom/serviceworkers/test/file_js_cache_syntax_error.html create mode 100644 dom/serviceworkers/test/file_js_cache_syntax_error.js create mode 100644 dom/serviceworkers/test/file_js_cache_with_sri.html create mode 100644 dom/serviceworkers/test/file_notification_openWindow.html create mode 100644 dom/serviceworkers/test/file_userContextId_openWindow.js create mode 100644 dom/serviceworkers/test/force_refresh_browser_worker.js create mode 100644 dom/serviceworkers/test/force_refresh_worker.js create mode 100644 dom/serviceworkers/test/gtest/TestReadWrite.cpp create mode 100644 dom/serviceworkers/test/gtest/moz.build create mode 100644 dom/serviceworkers/test/gzip_redirect_worker.js create mode 100644 dom/serviceworkers/test/header_checker.sjs create mode 100644 dom/serviceworkers/test/hello.html create mode 100644 dom/serviceworkers/test/importscript.sjs create mode 100644 dom/serviceworkers/test/importscript_worker.js create mode 100644 dom/serviceworkers/test/install_event_error_worker.js create mode 100644 dom/serviceworkers/test/install_event_worker.js create mode 100644 dom/serviceworkers/test/intercepted_channel_process_swap_worker.js create mode 100644 dom/serviceworkers/test/isolated/README.md create mode 100644 dom/serviceworkers/test/isolated/multi-e10s-update/browser.ini create mode 100644 dom/serviceworkers/test/isolated/multi-e10s-update/browser_multie10s_update.js create mode 100644 dom/serviceworkers/test/isolated/multi-e10s-update/file_multie10s_update.html create mode 100644 dom/serviceworkers/test/isolated/multi-e10s-update/server_multie10s_update.sjs create mode 100644 dom/serviceworkers/test/lazy_worker.js create mode 100644 dom/serviceworkers/test/lorem_script.js create mode 100644 dom/serviceworkers/test/match_all_advanced_worker.js create mode 100644 dom/serviceworkers/test/match_all_client/match_all_client_id.html create mode 100644 dom/serviceworkers/test/match_all_client_id_worker.js create mode 100644 dom/serviceworkers/test/match_all_clients/match_all_controlled.html create mode 100644 dom/serviceworkers/test/match_all_properties_worker.js create mode 100644 dom/serviceworkers/test/match_all_worker.js create mode 100644 dom/serviceworkers/test/message_posting_worker.js create mode 100644 dom/serviceworkers/test/message_receiver.html create mode 100644 dom/serviceworkers/test/mochitest-common.ini create mode 100644 dom/serviceworkers/test/mochitest-dFPI.ini create mode 100644 dom/serviceworkers/test/mochitest.ini create mode 100644 dom/serviceworkers/test/navigationPreload_page.html create mode 100644 dom/serviceworkers/test/network_with_utils.html create mode 100644 dom/serviceworkers/test/nofetch_handler_worker.js create mode 100644 dom/serviceworkers/test/notification/register.html create mode 100644 dom/serviceworkers/test/notification_constructor_error.js create mode 100644 dom/serviceworkers/test/notification_get_sw.js create mode 100644 dom/serviceworkers/test/notification_openWindow_worker.js create mode 100644 dom/serviceworkers/test/notificationclick-otherwindow.html create mode 100644 dom/serviceworkers/test/notificationclick.html create mode 100644 dom/serviceworkers/test/notificationclick.js create mode 100644 dom/serviceworkers/test/notificationclick_focus.html create mode 100644 dom/serviceworkers/test/notificationclick_focus.js create mode 100644 dom/serviceworkers/test/notificationclose.html create mode 100644 dom/serviceworkers/test/notificationclose.js create mode 100644 dom/serviceworkers/test/notify_loaded.js create mode 100644 dom/serviceworkers/test/onmessageerror_worker.js create mode 100644 dom/serviceworkers/test/opaque_intercept_worker.js create mode 100644 dom/serviceworkers/test/openWindow_worker.js create mode 100644 dom/serviceworkers/test/open_window/client.sjs create mode 100644 dom/serviceworkers/test/page_post_controlled.html create mode 100644 dom/serviceworkers/test/parse_error_worker.js create mode 100644 dom/serviceworkers/test/pref/fetch_nonexistent_file.html create mode 100644 dom/serviceworkers/test/pref/intercept_nonexistent_file_sw.js create mode 100644 dom/serviceworkers/test/redirect.sjs create mode 100644 dom/serviceworkers/test/redirect_post.sjs create mode 100644 dom/serviceworkers/test/redirect_serviceworker.sjs create mode 100644 dom/serviceworkers/test/register_https.html create mode 100644 dom/serviceworkers/test/sanitize/example_check_and_unregister.html create mode 100644 dom/serviceworkers/test/sanitize/frame.html create mode 100644 dom/serviceworkers/test/sanitize/register.html create mode 100644 dom/serviceworkers/test/sanitize_worker.js create mode 100644 dom/serviceworkers/test/scope/scope_worker.js create mode 100644 dom/serviceworkers/test/script_file_upload.js create mode 100644 dom/serviceworkers/test/self_update_worker.sjs create mode 100644 dom/serviceworkers/test/server_file_upload.sjs create mode 100644 dom/serviceworkers/test/service_worker.js create mode 100644 dom/serviceworkers/test/service_worker_client.html create mode 100644 dom/serviceworkers/test/serviceworker.html create mode 100644 dom/serviceworkers/test/serviceworker_not_sharedworker.js create mode 100644 dom/serviceworkers/test/serviceworker_wrapper.js create mode 100644 dom/serviceworkers/test/serviceworkerinfo_iframe.html create mode 100644 dom/serviceworkers/test/serviceworkermanager_iframe.html create mode 100644 dom/serviceworkers/test/serviceworkerregistrationinfo_iframe.html create mode 100644 dom/serviceworkers/test/sharedWorker_fetch.js create mode 100644 dom/serviceworkers/test/simple_fetch_worker.js create mode 100644 dom/serviceworkers/test/simpleregister/index.html create mode 100644 dom/serviceworkers/test/simpleregister/ready.html create mode 100644 dom/serviceworkers/test/skip_waiting_installed_worker.js create mode 100644 dom/serviceworkers/test/skip_waiting_scope/index.html create mode 100644 dom/serviceworkers/test/source_message_posting_worker.js create mode 100644 dom/serviceworkers/test/storage_recovery_worker.sjs create mode 100644 dom/serviceworkers/test/streamfilter_server.sjs create mode 100644 dom/serviceworkers/test/streamfilter_worker.js create mode 100644 dom/serviceworkers/test/strict_mode_warning.js create mode 100644 dom/serviceworkers/test/sw_bad_mime_type.js create mode 100644 dom/serviceworkers/test/sw_bad_mime_type.js^headers^ create mode 100644 dom/serviceworkers/test/sw_clients/file_blob_upload_frame.html create mode 100644 dom/serviceworkers/test/sw_clients/navigator.html create mode 100644 dom/serviceworkers/test/sw_clients/refresher.html create mode 100644 dom/serviceworkers/test/sw_clients/refresher_cached.html create mode 100644 dom/serviceworkers/test/sw_clients/refresher_cached_compressed.html create mode 100644 dom/serviceworkers/test/sw_clients/refresher_cached_compressed.html^headers^ create mode 100644 dom/serviceworkers/test/sw_clients/refresher_compressed.html create mode 100644 dom/serviceworkers/test/sw_clients/refresher_compressed.html^headers^ create mode 100644 dom/serviceworkers/test/sw_clients/service_worker_controlled.html create mode 100644 dom/serviceworkers/test/sw_clients/simple.html create mode 100644 dom/serviceworkers/test/sw_file_upload.js create mode 100644 dom/serviceworkers/test/sw_respondwith_serviceworker.js create mode 100644 dom/serviceworkers/test/sw_storage_not_allow.js create mode 100644 dom/serviceworkers/test/sw_with_navigationPreload.js create mode 100644 dom/serviceworkers/test/swa/worker_scope_different.js create mode 100644 dom/serviceworkers/test/swa/worker_scope_different.js^headers^ create mode 100644 dom/serviceworkers/test/swa/worker_scope_different2.js create mode 100644 dom/serviceworkers/test/swa/worker_scope_different2.js^headers^ create mode 100644 dom/serviceworkers/test/swa/worker_scope_precise.js create mode 100644 dom/serviceworkers/test/swa/worker_scope_precise.js^headers^ create mode 100644 dom/serviceworkers/test/swa/worker_scope_too_deep.js create mode 100644 dom/serviceworkers/test/swa/worker_scope_too_deep.js^headers^ create mode 100644 dom/serviceworkers/test/swa/worker_scope_too_narrow.js create mode 100644 dom/serviceworkers/test/swa/worker_scope_too_narrow.js^headers^ create mode 100644 dom/serviceworkers/test/test_abrupt_completion.html create mode 100644 dom/serviceworkers/test/test_async_waituntil.html create mode 100644 dom/serviceworkers/test/test_bad_script_cache.html create mode 100644 dom/serviceworkers/test/test_bug1151916.html create mode 100644 dom/serviceworkers/test/test_bug1240436.html create mode 100644 dom/serviceworkers/test/test_bug1408734.html create mode 100644 dom/serviceworkers/test/test_claim.html create mode 100644 dom/serviceworkers/test/test_claim_oninstall.html create mode 100644 dom/serviceworkers/test/test_controller.html create mode 100644 dom/serviceworkers/test/test_cookie_fetch.html create mode 100644 dom/serviceworkers/test/test_cross_origin_url_after_redirect.html create mode 100644 dom/serviceworkers/test/test_csp_upgrade-insecure_intercept.html create mode 100644 dom/serviceworkers/test/test_devtools_bypass_serviceworker.html create mode 100644 dom/serviceworkers/test/test_devtools_track_serviceworker_time.html create mode 100644 dom/serviceworkers/test/test_empty_serviceworker.html create mode 100644 dom/serviceworkers/test/test_enabled_pref.html create mode 100644 dom/serviceworkers/test/test_error_reporting.html create mode 100644 dom/serviceworkers/test/test_escapedSlashes.html create mode 100644 dom/serviceworkers/test/test_eval_allowed.html create mode 100644 dom/serviceworkers/test/test_eval_allowed.html^headers^ create mode 100644 dom/serviceworkers/test/test_event_listener_leaks.html create mode 100644 dom/serviceworkers/test/test_eventsource_intercept.html create mode 100644 dom/serviceworkers/test/test_fetch_event.html create mode 100644 dom/serviceworkers/test/test_fetch_event_with_thirdpartypref.html create mode 100644 dom/serviceworkers/test/test_fetch_integrity.html create mode 100644 dom/serviceworkers/test/test_file_blob_response.html create mode 100644 dom/serviceworkers/test/test_file_blob_upload.html create mode 100644 dom/serviceworkers/test/test_file_upload.html create mode 100644 dom/serviceworkers/test/test_force_refresh.html create mode 100644 dom/serviceworkers/test/test_gzip_redirect.html create mode 100644 dom/serviceworkers/test/test_hsts_upgrade_intercept.html create mode 100644 dom/serviceworkers/test/test_https_fetch.html create mode 100644 dom/serviceworkers/test/test_https_fetch_cloned_response.html create mode 100644 dom/serviceworkers/test/test_https_origin_after_redirect.html create mode 100644 dom/serviceworkers/test/test_https_origin_after_redirect_cached.html create mode 100644 dom/serviceworkers/test/test_https_synth_fetch_from_cached_sw.html create mode 100644 dom/serviceworkers/test/test_imagecache.html create mode 100644 dom/serviceworkers/test/test_imagecache_max_age.html create mode 100644 dom/serviceworkers/test/test_importscript.html create mode 100644 dom/serviceworkers/test/test_importscript_mixedcontent.html create mode 100644 dom/serviceworkers/test/test_install_event.html create mode 100644 dom/serviceworkers/test/test_install_event_gc.html create mode 100644 dom/serviceworkers/test/test_installation_simple.html create mode 100644 dom/serviceworkers/test/test_match_all.html create mode 100644 dom/serviceworkers/test/test_match_all_advanced.html create mode 100644 dom/serviceworkers/test/test_match_all_client_id.html create mode 100644 dom/serviceworkers/test/test_match_all_client_properties.html create mode 100644 dom/serviceworkers/test/test_navigationPreload_disable_crash.html create mode 100644 dom/serviceworkers/test/test_navigator.html create mode 100644 dom/serviceworkers/test/test_nofetch_handler.html create mode 100644 dom/serviceworkers/test/test_not_intercept_plugin.html create mode 100644 dom/serviceworkers/test/test_notification_constructor_error.html create mode 100644 dom/serviceworkers/test/test_notification_get.html create mode 100644 dom/serviceworkers/test/test_notification_openWindow.html create mode 100644 dom/serviceworkers/test/test_notificationclick-otherwindow.html create mode 100644 dom/serviceworkers/test/test_notificationclick.html create mode 100644 dom/serviceworkers/test/test_notificationclick_focus.html create mode 100644 dom/serviceworkers/test/test_notificationclose.html create mode 100644 dom/serviceworkers/test/test_onmessageerror.html create mode 100644 dom/serviceworkers/test/test_opaque_intercept.html create mode 100644 dom/serviceworkers/test/test_openWindow.html create mode 100644 dom/serviceworkers/test/test_origin_after_redirect.html create mode 100644 dom/serviceworkers/test/test_origin_after_redirect_cached.html create mode 100644 dom/serviceworkers/test/test_origin_after_redirect_to_https.html create mode 100644 dom/serviceworkers/test/test_origin_after_redirect_to_https_cached.html create mode 100644 dom/serviceworkers/test/test_post_message.html create mode 100644 dom/serviceworkers/test/test_post_message_advanced.html create mode 100644 dom/serviceworkers/test/test_post_message_source.html create mode 100644 dom/serviceworkers/test/test_privateBrowsing.html create mode 100644 dom/serviceworkers/test/test_register_base.html create mode 100644 dom/serviceworkers/test/test_register_https_in_http.html create mode 100644 dom/serviceworkers/test/test_sandbox_intercept.html create mode 100644 dom/serviceworkers/test/test_sanitize.html create mode 100644 dom/serviceworkers/test/test_sanitize_domain.html create mode 100644 dom/serviceworkers/test/test_scopes.html create mode 100644 dom/serviceworkers/test/test_script_loader_intercepted_js_cache.html create mode 100644 dom/serviceworkers/test/test_self_update_worker.html create mode 100644 dom/serviceworkers/test/test_service_worker_allowed.html create mode 100644 dom/serviceworkers/test/test_serviceworker.html create mode 100644 dom/serviceworkers/test/test_serviceworker_header.html create mode 100644 dom/serviceworkers/test/test_serviceworker_interfaces.html create mode 100644 dom/serviceworkers/test/test_serviceworker_interfaces.js create mode 100644 dom/serviceworkers/test/test_serviceworker_not_sharedworker.html create mode 100644 dom/serviceworkers/test/test_serviceworkerinfo.xhtml create mode 100644 dom/serviceworkers/test/test_serviceworkermanager.xhtml create mode 100644 dom/serviceworkers/test/test_serviceworkerregistrationinfo.xhtml create mode 100644 dom/serviceworkers/test/test_skip_waiting.html create mode 100644 dom/serviceworkers/test/test_streamfilter.html create mode 100644 dom/serviceworkers/test/test_strict_mode_warning.html create mode 100644 dom/serviceworkers/test/test_third_party_iframes.html create mode 100644 dom/serviceworkers/test/test_unregister.html create mode 100644 dom/serviceworkers/test/test_unresolved_fetch_interception.html create mode 100644 dom/serviceworkers/test/test_workerUnregister.html create mode 100644 dom/serviceworkers/test/test_workerUpdate.html create mode 100644 dom/serviceworkers/test/test_worker_reference_gc_timeout.html create mode 100644 dom/serviceworkers/test/test_workerupdatefoundevent.html create mode 100644 dom/serviceworkers/test/test_xslt.html create mode 100644 dom/serviceworkers/test/thirdparty/iframe1.html create mode 100644 dom/serviceworkers/test/thirdparty/iframe2.html create mode 100644 dom/serviceworkers/test/thirdparty/register.html create mode 100644 dom/serviceworkers/test/thirdparty/sw.js create mode 100644 dom/serviceworkers/test/thirdparty/unregister.html create mode 100644 dom/serviceworkers/test/thirdparty/worker.js create mode 100644 dom/serviceworkers/test/unregister/index.html create mode 100644 dom/serviceworkers/test/unregister/unregister.html create mode 100644 dom/serviceworkers/test/unresolved_fetch_worker.js create mode 100644 dom/serviceworkers/test/update_worker.sjs create mode 100644 dom/serviceworkers/test/updatefoundevent.html create mode 100644 dom/serviceworkers/test/utils.js create mode 100644 dom/serviceworkers/test/window_party_iframes.html create mode 100644 dom/serviceworkers/test/worker.js create mode 100644 dom/serviceworkers/test/worker2.js create mode 100644 dom/serviceworkers/test/worker3.js create mode 100644 dom/serviceworkers/test/workerUpdate/update.html create mode 100644 dom/serviceworkers/test/worker_unregister.js create mode 100644 dom/serviceworkers/test/worker_update.js create mode 100644 dom/serviceworkers/test/worker_updatefoundevent.js create mode 100644 dom/serviceworkers/test/worker_updatefoundevent2.js create mode 100644 dom/serviceworkers/test/xslt/test.xml create mode 100644 dom/serviceworkers/test/xslt/xslt.sjs create mode 100644 dom/serviceworkers/test/xslt_worker.js (limited to 'dom/serviceworkers/test') 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..cb2d4809e9 --- /dev/null +++ b/dom/serviceworkers/test/ForceRefreshParent.sys.mjs @@ -0,0 +1,77 @@ +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; + return ForceRefreshParent.done(); + } + if (baseLoadCount !== 1) { + ForceRefreshParent.SimpleTest.ok( + false, + "base load without cached load should only occur once" + ); + done = true; + return 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) { + return ForceRefreshParent.refresh(); + } + 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.ini b/dom/serviceworkers/test/browser-common.ini new file mode 100644 index 0000000000..e1c8cfbc48 --- /dev/null +++ b/dom/serviceworkers/test/browser-common.ini @@ -0,0 +1,52 @@ +[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_force_refresh.js] +skip-if = verify # Bug 1603340 +[browser_download.js] +[browser_download_canceled.js] +skip-if = verify +[browser_intercepted_channel_process_swap.js] +skip-if = !fission +[browser_intercepted_worker_script.js] +[browser_navigation_fetch_fault_handling.js] +skip-if = + os == "win" && os_version == "6.1" # Skip on Azure - frequent failure +[browser_navigationPreload_read_after_respondWith.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 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.ini b/dom/serviceworkers/test/browser-dFPI.ini new file mode 100644 index 0000000000..c327062f16 --- /dev/null +++ b/dom/serviceworkers/test/browser-dFPI.ini @@ -0,0 +1,7 @@ +[DEFAULT] +# Enable dFPI(cookieBehavior 5) for service worker tests. +prefs = + network.cookie.cookieBehavior=5 +dupe-manifest = true + +[include:browser-common.ini] diff --git a/dom/serviceworkers/test/browser.ini b/dom/serviceworkers/test/browser.ini new file mode 100644 index 0000000000..2a7162e9ed --- /dev/null +++ b/dom/serviceworkers/test/browser.ini @@ -0,0 +1,4 @@ +[DEFAULT] +dupe-manifest = true + +[include:browser-common.ini] diff --git a/dom/serviceworkers/test/browser_antitracking.js b/dom/serviceworkers/test/browser_antitracking.js new file mode 100644 index 0000000000..d5af4a3f41 --- /dev/null +++ b/dom/serviceworkers/test/browser_antitracking.js @@ -0,0 +1,103 @@ +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.loadURIString(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.loadURIString(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..aa3b2b0424 --- /dev/null +++ b/dom/serviceworkers/test/browser_antitracking_subiframes.js @@ -0,0 +1,103 @@ +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.loadURIString(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.loadURIString(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 @@ + + + + + + + + + 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..faf2ee7a83 --- /dev/null +++ b/dom/serviceworkers/test/browser_cached_force_refresh.html @@ -0,0 +1,59 @@ + + + + + + + + + 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..fc31159116 --- /dev/null +++ b/dom/serviceworkers/test/browser_devtools_serviceworker_interception.js @@ -0,0 +1,264 @@ +"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) => { + ok(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") { + return resolve(); + } + + worker.addEventListener("statechange", () => { + if (worker.state === "activated") { + return resolve(); + } + }); + }); + + await new Promise(resolve => { + if (content.navigator.serviceWorker.controller) { + return resolve(); + } + + 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..080d27e2ac --- /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.loadURIString(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..1cd4fc0398 --- /dev/null +++ b/dom/serviceworkers/test/browser_force_refresh.js @@ -0,0 +1,87 @@ +/* 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], + ["dom.caches.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.loadURIString(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..ab45998539 --- /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.loadURIString(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..b76da870cd --- /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.loadURIString(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..6c9fd65adb --- /dev/null +++ b/dom/serviceworkers/test/browser_navigationPreload_read_after_respondWith.js @@ -0,0 +1,115 @@ +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.loadURIString(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.loadURIString(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..6655c52a62 --- /dev/null +++ b/dom/serviceworkers/test/browser_navigation_fetch_fault_handling.js @@ -0,0 +1,272 @@ +/** + * 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); + ok(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..2acd3b9a51 --- /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.loadURIString(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([""], { + 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([""], { + 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..599745e372 --- /dev/null +++ b/dom/serviceworkers/test/browser_userContextId_openWindow.js @@ -0,0 +1,162 @@ +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], + ["dom.webnotifications.serviceworker.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 @@ + + + + + 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.ini b/dom/serviceworkers/test/chrome-common.ini new file mode 100644 index 0000000000..b5cae318e1 --- /dev/null +++ b/dom/serviceworkers/test/chrome-common.ini @@ -0,0 +1,21 @@ +[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.ini b/dom/serviceworkers/test/chrome-dFPI.ini new file mode 100644 index 0000000000..b07337f753 --- /dev/null +++ b/dom/serviceworkers/test/chrome-dFPI.ini @@ -0,0 +1,7 @@ +[DEFAULT] +# Enable dFPI(cookieBehavior 5) for service worker tests. +prefs = + network.cookie.cookieBehavior=5 +dupe-manifest = true + +[include:chrome-common.ini] diff --git a/dom/serviceworkers/test/chrome.ini b/dom/serviceworkers/test/chrome.ini new file mode 100644 index 0000000000..e6f7e3206d --- /dev/null +++ b/dom/serviceworkers/test/chrome.ini @@ -0,0 +1,4 @@ +[DEFAULT] +dupe-manifest = true + +[include:chrome-common.ini] 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 @@ + + + + + Bug 1130684 - claim client + + + + +

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 94048 - test install event.
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+Shared workers: create antoehr sharedworekr client
+
Hello World
+ diff --git a/dom/serviceworkers/test/download/window.html b/dom/serviceworkers/test/download/window.html new file mode 100644 index 0000000000..7d7893e0e6 --- /dev/null +++ b/dom/serviceworkers/test/download/window.html @@ -0,0 +1,46 @@ + + + + + + + + + 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..e3904c4967 --- /dev/null +++ b/dom/serviceworkers/test/download_canceled/page_download_canceled.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + + 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..f47e872feb --- /dev/null +++ b/dom/serviceworkers/test/download_canceled/server-stream-download.sjs @@ -0,0 +1,133 @@ +/* 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); +} + +Components.utils.importGlobalProperties(["URLSearchParams"]); +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..5d9d5f9bfd --- /dev/null +++ b/dom/serviceworkers/test/download_canceled/sw_download_canceled.js @@ -0,0 +1,150 @@ +/* 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")) { + return handleStream(evt, "sw-stream-download"); + } + if (evt.request.url.includes("sw-passthrough-download")) { + return 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 diff --git a/dom/serviceworkers/test/empty.js b/dom/serviceworkers/test/empty.js new file mode 100644 index 0000000000..e69de29bb2 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 @@ + + + + + + + + + + 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..79b4808e66 --- /dev/null +++ b/dom/serviceworkers/test/eval_worker.js @@ -0,0 +1 @@ +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 @@ + + + + + Bug 1182103 - Test EventSource scenarios with fetch interception + + + + + 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 @@ + + + + + Bug 1182103 - Test EventSource scenarios with fetch interception + + + + + 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 @@ + + + + + Bug 1182103 - Test EventSource scenarios with fetch interception + + + + + 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 @@ + + + + + Bug 1182103 - Test EventSource scenarios with fetch interception + + + + + 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 @@ + + + + + Bug 1182103 - Test EventSource scenarios with fetch interception + + + + + 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 = + ""; + 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 @@ + + + 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 @@ + + diff --git a/dom/serviceworkers/test/fetch/deliver-gzip.sjs b/dom/serviceworkers/test/fetch/deliver-gzip.sjs new file mode 100644 index 0000000000..d7cbdef06e --- /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 = Components.classes[ + "@mozilla.org/binaryoutputstream;1" + ].createInstance(Components.interfaces.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 === "hello pass\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 @@ + + + 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 Binary files /dev/null and b/dom/serviceworkers/test/fetch/hsts/image-20px.png 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 Binary files /dev/null and b/dom/serviceworkers/test/fetch/hsts/image-40px.png 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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( + '', + { 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( + '', + { 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 @@ + + 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 @@ + + 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 @@ + + 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 Binary files /dev/null and b/dom/serviceworkers/test/fetch/imagecache-maxage/image-20px.png 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 Binary files /dev/null and b/dom/serviceworkers/test/fetch/imagecache-maxage/image-40px.png 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 @@ + + + + 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 @@ + + 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 @@ + + 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 Binary files /dev/null and b/dom/serviceworkers/test/fetch/imagecache/image-20px.png 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 Binary files /dev/null and b/dom/serviceworkers/test/fetch/imagecache/image-40px.png 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 @@ + + + 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 @@ + + + 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 @@ + + + + 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 @@ + + 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 ` + + + `; +} + +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 @@ + + 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 @@ + + 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 @@ + + + + + Bug 94048 - test install event. + + + + +

+ +
+

+
+
+
+
+
+
diff --git a/dom/serviceworkers/test/fetch/interrupt.sjs b/dom/serviceworkers/test/fetch/interrupt.sjs
new file mode 100644
index 0000000000..6e5deeb832
--- /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("", Components.results.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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+secret stuff
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
+
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
Binary files /dev/null and b/dom/serviceworkers/test/fetch/upgrade-insecure/image-20px.png 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
Binary files /dev/null and b/dom/serviceworkers/test/fetch/upgrade-insecure/image-40px.png 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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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..b022ca4175
--- /dev/null
+++ b/dom/serviceworkers/test/fetch_event_worker.js
@@ -0,0 +1,364 @@
+// 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(
+          "",
+          {
+            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(
+          "",
+          {
+            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.
+    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("body", {
+          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("Invalid Request mode", {
+          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 @@
+
+
+
+  
+  Add a tag script to save the bytecode
+
+
+  
+
+
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 @@
+
+
+
+  
+  Save the bytecode when all scripts are executed
+
+
+  
+
+
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 @@
+
+
+
+  
+  Do not save bytecode on compilation errors
+
+
+  
+
+
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 @@
+
+
+
+  
+  Add a tag script to save the bytecode
+
+
+  
+
+
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 @@
+
+
+
+  Bug 1578070
+
+
+

+ +

+
+
+
+
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..fbe71d7108
--- /dev/null
+++ b/dom/serviceworkers/test/gtest/TestReadWrite.cpp
@@ -0,0 +1,952 @@
+/* -*- 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& TestGetData() { return mData; }
+};
+
+already_AddRefed GetFile() {
+  nsCOMPtr 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 file = GetFile();
+
+  nsCOMPtr 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 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 swr = new ServiceWorkerRegistrarTest;
+
+  rv = swr->TestReadData();
+  ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+  const nsTArray& 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 swr = new ServiceWorkerRegistrarTest;
+
+  nsresult rv = swr->TestReadData();
+  ASSERT_NE(NS_OK, rv) << "ReadData() should fail if the file is empty";
+
+  const nsTArray& 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 swr = new ServiceWorkerRegistrarTest;
+
+  nsresult rv = swr->TestReadData();
+  ASSERT_EQ(NS_OK, rv)
+      << "ReadData() should not fail when the version is correct";
+
+  const nsTArray& 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 swr = new ServiceWorkerRegistrarTest;
+
+  nsresult rv = swr->TestReadData();
+  ASSERT_NE(NS_OK, rv)
+      << "ReadData() should fail when the version is not correct";
+
+  const nsTArray& 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 swr = new ServiceWorkerRegistrarTest;
+
+  nsresult rv = swr->TestReadData();
+  ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+  const nsTArray& 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 swr = new ServiceWorkerRegistrarTest;
+
+  swr->TestDeleteData();
+
+  nsCOMPtr 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 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 swr = new ServiceWorkerRegistrarTest;
+
+  nsresult rv = swr->TestReadData();
+  ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+  const nsTArray& 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 swr = new ServiceWorkerRegistrarTest;
+
+  nsresult rv = swr->TestReadData();
+  ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+  const nsTArray& 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 swr = new ServiceWorkerRegistrarTest;
+
+  nsresult rv = swr->TestReadData();
+  ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+  const nsTArray& 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 swr = new ServiceWorkerRegistrarTest;
+
+  nsresult rv = swr->TestReadData();
+  ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+  const nsTArray& 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 swr = new ServiceWorkerRegistrarTest;
+
+  nsresult rv = swr->TestReadData();
+  ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+  const nsTArray& 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 swr = new ServiceWorkerRegistrarTest;
+
+  nsresult rv = swr->TestReadData();
+  ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+  const nsTArray& 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 swr = new ServiceWorkerRegistrarTest;
+
+  nsresult rv = swr->TestReadData();
+  ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+  const nsTArray& 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 swr = new ServiceWorkerRegistrarTest;
+
+  nsresult rv = swr->TestReadData();
+  ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+  const nsTArray& 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 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 swr = new ServiceWorkerRegistrarTest;
+
+  nsresult rv = swr->TestReadData();
+  ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+  // Duplicate entries should be removed.
+  const nsTArray& 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 @@
+
+
+  
+    
+  
+  
+    Hello.
+  
+
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.ini b/dom/serviceworkers/test/isolated/multi-e10s-update/browser.ini
new file mode 100644
index 0000000000..bb913e2583
--- /dev/null
+++ b/dom/serviceworkers/test/isolated/multi-e10s-update/browser.ini
@@ -0,0 +1,7 @@
+[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 @@
+
+
+
+
+
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..5bf48be29e
--- /dev/null
+++ b/dom/serviceworkers/test/isolated/multi-e10s-update/server_multie10s_update.sjs
@@ -0,0 +1,100 @@
+// 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 }));
+}
+
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+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 @@
+
+
+
+
+  Bug 1139425 - controlled page
+
+
+
+
+
+
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 @@
+
+
+
+
+  Bug 1058311 - controlled page
+
+
+
+
+
+
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..dd8cb2f2aa
--- /dev/null
+++ b/dom/serviceworkers/test/match_all_properties_worker.js
@@ -0,0 +1,28 @@
+onfetch = function (e) {
+  if (/\/clientId$/.test(e.request.url)) {
+    e.respondWith(new Response(e.clientId));
+    return;
+  }
+};
+
+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 @@
+
+
diff --git a/dom/serviceworkers/test/mochitest-common.ini b/dom/serviceworkers/test/mochitest-common.ini
new file mode 100644
index 0000000000..377c36e825
--- /dev/null
+++ b/dom/serviceworkers/test/mochitest-common.ini
@@ -0,0 +1,377 @@
+[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
+  win10_2004 # 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
+[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
+[test_eval_allowed.html]
+[test_event_listener_leaks.html]
+skip-if = (os == "win" && processor == "aarch64") #bug 1535784
+[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 = toolkit == '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 =
+  win10_2004 && !debug # Bug 1717091
+  win11_2009 && !debug # Bug 1797751
+  os == "linux" && bits == 64 && debug # Bug 1749068
+  apple_catalina && !debug # Bug 1717091
+  fission && os == "android" # Bug 1827325
+scheme = https
+[test_imagecache.html]
+skip-if =
+  http3
+[test_imagecache_max_age.html]
+skip-if =
+  os == 'linux' && bits == 64 && !debug && asan && os_version == '18.04' # Bug 1585668
+  http3
+[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 =
+  toolkit == 'android'
+  http3
+[test_match_all_client_properties.html]
+skip-if = toolkit == '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 =
+  toolkit == '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
+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 # Hangs with no error log
+[test_opaque_intercept.html]
+skip-if =
+  http3
+[test_origin_after_redirect.html]
+skip-if =
+  http3
+[test_origin_after_redirect_cached.html]
+skip-if =
+  http3
+[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
+[test_register_https_in_http.html]
+skip-if =
+  http3
+[test_sandbox_intercept.html]
+skip-if =
+  http3
+[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
+  toolkit == 'android'
+[test_service_worker_allowed.html]
+[test_serviceworker.html]
+[test_serviceworker_header.html]
+skip-if =
+  http3
+[test_serviceworker_interfaces.html]
+[test_serviceworker_not_sharedworker.html]
+skip-if =
+  http3
+[test_skip_waiting.html]
+[test_streamfilter.html]
+[test_strict_mode_warning.html]
+[test_third_party_iframes.html]
+skip-if =
+  fission && os == "android" # Bug 1827327
+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
diff --git a/dom/serviceworkers/test/mochitest-dFPI.ini b/dom/serviceworkers/test/mochitest-dFPI.ini
new file mode 100644
index 0000000000..37d6171837
--- /dev/null
+++ b/dom/serviceworkers/test/mochitest-dFPI.ini
@@ -0,0 +1,11 @@
+[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.ini]
diff --git a/dom/serviceworkers/test/mochitest.ini b/dom/serviceworkers/test/mochitest.ini
new file mode 100644
index 0000000000..7c29dc57ee
--- /dev/null
+++ b/dom/serviceworkers/test/mochitest.ini
@@ -0,0 +1,39 @@
+[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.ini so that these tests won't run
+# when dFPI is enabled.
+[test_cookie_fetch.html]
+[test_csp_upgrade-insecure_intercept.html]
+[test_eventsource_intercept.html]
+skip-if =
+  http3
+[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 =
+  toolkit == 'android' # Bug 1620052
+  xorigin # Bug 1792790
+  condprof  #: timed out
+  http3
+tags = openwindow
+[test_sanitize_domain.html]
+skip-if =
+  http3
+[include:mochitest-common.ini]
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 @@
+
+
+
+
+  
+  
+
+
+NETWORK
+
+
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 @@
+
+
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
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 @@
+
+
+
+
+  Bug 1114554 - controlled page
+
+
+
+
+
+
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 @@
+
+
+
+
+  Bug 1114554 - controlled page
+
+
+
+
+
+
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 @@
+
+
+
+
+  Bug 1144660 - controlled page
+
+
+
+
+
+
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 @@
+
+
+
+
+  Bug 1265841 - controlled page
+
+
+
+
+
+
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..15426128a6
--- /dev/null
+++ b/dom/serviceworkers/test/onmessageerror_worker.js
@@ -0,0 +1,54 @@
+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;
+    }
+  }
+}
+
+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..236a4a1226
--- /dev/null
+++ b/dom/serviceworkers/test/open_window/client.sjs
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const RESPONSE = `
+
+
+
+  Bug 1172870 - page opened by ServiceWorkerClients.OpenWindow
+
+
+

+ +

+

client.sjs

+ + + + +`; + +function handleRequest(request, response) { + Components.utils.importGlobalProperties(["URLSearchParams"]); + 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 @@ + + + + + + + + + + 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/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 @@ + + + + + + + + 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 @@ + + 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 @@ + + 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 @@ + + 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 @@ + + 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..d2dd227e92 --- /dev/null +++ b/dom/serviceworkers/test/script_file_upload.js @@ -0,0 +1,15 @@ +/* eslint-env mozilla/chrome-script */ + +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 @@ + + + + +controlled page + + + + + + 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 @@ + + + + + + + + This is a test page. + + 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 @@ + + + + + + + + This is a test page. + + 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 @@ + + + + + + + + This is a test page. + + 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 @@ + + + + + + + + This is a test page. + + 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 @@ + + + + + + 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 @@ + + + + + + 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 @@ + + + + + Bug 1131352 - Add ServiceWorkerGlobalScope skipWaiting() + + + + +

+ +

+
+
+
+
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..0e5a62d1dd
--- /dev/null
+++ b/dom/serviceworkers/test/streamfilter_server.sjs
@@ -0,0 +1,9 @@
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+
+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..38418de3d8
--- /dev/null
+++ b/dom/serviceworkers/test/strict_mode_warning.js
@@ -0,0 +1,4 @@
+function f() {
+  return 1;
+  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..ff9803622a
--- /dev/null
+++ b/dom/serviceworkers/test/sw_clients/file_blob_upload_frame.html
@@ -0,0 +1,76 @@
+
+
+
+
+  test file blob upload with SW interception
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 982726 - test match_all not crashing
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 982726 - test match_all not crashing
+  
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 982726 - test match_all not crashing
+  
+  
+
+
+

+ +

+
+
+
+
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
Binary files /dev/null and b/dom/serviceworkers/test/sw_clients/refresher_cached_compressed.html 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
Binary files /dev/null and b/dom/serviceworkers/test/sw_clients/refresher_compressed.html 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 @@
+
+
+
+
+  controlled page
+  
+
+
+
+
+
+
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 @@
+
+
+
+
+  Bug 982726 - test match_all not crashing
+  
+  
+
+
+

+ +

+
+
+
+
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 = `
+
+
+  
+  
+
+
+SERVICEWORKER
+
+
+`;
+
+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("", {
+        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
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
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
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
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
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 @@
+
+
+
+
+
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 @@
+
+
+
+
+  Test for Bug 1263304
+  
+  
+  
+  
+
+Mozilla Bug 1263304
+

+ +
+
+ + + + + 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..7919802678 --- /dev/null +++ b/dom/serviceworkers/test/test_bad_script_cache.html @@ -0,0 +1,96 @@ + + + + + Test updating a service worker with a bad script cache. + + + + + + + + + diff --git a/dom/serviceworkers/test/test_bug1151916.html b/dom/serviceworkers/test/test_bug1151916.html new file mode 100644 index 0000000000..b541129ccb --- /dev/null +++ b/dom/serviceworkers/test/test_bug1151916.html @@ -0,0 +1,104 @@ + + + + + Bug 1151916 - Test principal is set on cached serviceworkers + + + + + +

+
+

+
+
+
+
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 @@
+
+
+
+
+  Test for encoding of service workers
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 1408734
+  
+  
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 1130684 - Test service worker clients claim onactivate 
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 1130684 - Test service worker clients.claim oninstall
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 1002570 - test controller instance.
+  
+  
+
+
+

+ +

+
+
+
+
+
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 @@
+
+
+
+
+  Bug 1331680 - test access to cookies in the documents synthesized from service worker responses
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Test access to a cross origin Request.url property from a service worker for a redirected intercepted iframe
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Test that a CSP upgraded request can be intercepted by a service worker
+  
+  
+
+
+

+
+ +
+

+
+
+
+
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..09c05d557a
--- /dev/null
+++ b/dom/serviceworkers/test/test_devtools_bypass_serviceworker.html
@@ -0,0 +1,107 @@
+
+
+
+   Verify devtools can utilize nsIChannel::LOAD_BYPASS_SERVICE_WORKER to bypass the service worker 
+  
+  
+  
+  
+
+
+
+
+
+
+
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 @@
+
+
+  Bug 1251238 - track service worker install time
+  
+  
+
+
+
+
+
+
+
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 @@
+
+
+
+
+  Test that registering an empty service worker works
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+  Bug 1645054 - test dom.serviceWorkers.enabled preference
+
+
+
+
+
+
+
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 @@
+
+
+
+  Test Error Reporting of Service Worker Failures
+  
+  
+  
+  
+  
+
+
+
+
+
+
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 @@
+
+
+
+
+  Test for escaped slashes in navigator.serviceWorker.register
+  
+  
+  
+
+
+

+ +

+
+
+
diff --git a/dom/serviceworkers/test/test_eval_allowed.html b/dom/serviceworkers/test/test_eval_allowed.html
new file mode 100644
index 0000000000..5d6d7a7d9c
--- /dev/null
+++ b/dom/serviceworkers/test/test_eval_allowed.html
@@ -0,0 +1,51 @@
+
+
+
+
+  Bug 1160458 - CSP activated by default in Service Workers
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 1447871 - Test some service worker leak conditions
+  
+  
+  
+  
+
+
+

+ + + + diff --git a/dom/serviceworkers/test/test_eventsource_intercept.html b/dom/serviceworkers/test/test_eventsource_intercept.html new file mode 100644 index 0000000000..b76c0d1d1a --- /dev/null +++ b/dom/serviceworkers/test/test_eventsource_intercept.html @@ -0,0 +1,103 @@ + + + + + Bug 1182103 - Test EventSource scenarios with fetch interception + + + + +

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 94048 - test install event.
+  
+  
+
+
+

+ +

+
+
+
+
+
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 @@
+
+
+
+
+  Bug 94048 - test install event.
+  
+  
+
+
+

+ +

+
+
+
+
+
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 @@
+
+
+
+   Test fetch.integrity on console report for serviceWorker and sharedWorker 
+  
+  
+  
+  
+
+
+
+
+
+
+
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 @@
+
+
+
+
+  Bug 1253777 - Test interception using file blob response body
+  
+  
+
+
+

+ +

+
+
+
+
+
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 @@
+
+
+
+
+  Bug 1203680 - Test interception of file blob uploads
+  
+  
+
+
+

+ +

+
+
+
+
+
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 @@
+
+
+
+
+  Bug 1424701 - Test for service worker + file upload
+  
+  
+  
+
+
+
+
+
+
diff --git a/dom/serviceworkers/test/test_force_refresh.html b/dom/serviceworkers/test/test_force_refresh.html
new file mode 100644
index 0000000000..69da7b7de3
--- /dev/null
+++ b/dom/serviceworkers/test/test_force_refresh.html
@@ -0,0 +1,105 @@
+
+
+
+
+  Bug 982726 - Test service worker post message 
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 982726 - Test service worker post message 
+  
+  
+
+
+

+ +

+
+
+
+
+
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 @@
+
+
+
+
+  Test that an HSTS upgraded request can be intercepted by a service worker
+  
+  
+
+
+

+
+ +
+

+
+
+
+
diff --git a/dom/serviceworkers/test/test_https_fetch.html b/dom/serviceworkers/test/test_https_fetch.html
new file mode 100644
index 0000000000..4ac4255889
--- /dev/null
+++ b/dom/serviceworkers/test/test_https_fetch.html
@@ -0,0 +1,62 @@
+
+
+
+
+  Bug 1133763 - test fetch event in HTTPS origins
+  
+  
+
+
+

+ +

+
+
+
+
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..8c7129d39d
--- /dev/null
+++ b/dom/serviceworkers/test/test_https_fetch_cloned_response.html
@@ -0,0 +1,56 @@
+
+
+
+
+  Bug 1133763 - test fetch event in HTTPS origins with a cloned response
+  
+  
+
+
+

+ +

+
+
+
+
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..31ce173f34
--- /dev/null
+++ b/dom/serviceworkers/test/test_https_origin_after_redirect.html
@@ -0,0 +1,57 @@
+
+
+
+
+  Test the origin of a redirected response from a service worker
+  
+  
+
+
+

+ +

+
+
+
+
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..8bce413f21
--- /dev/null
+++ b/dom/serviceworkers/test/test_https_origin_after_redirect_cached.html
@@ -0,0 +1,57 @@
+
+
+
+
+  Test the origin of a redirected response from a service worker
+  
+  
+
+
+

+ +

+
+
+
+
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..4186cfc340
--- /dev/null
+++ b/dom/serviceworkers/test/test_https_synth_fetch_from_cached_sw.html
@@ -0,0 +1,69 @@
+
+
+
+
+  Bug 1156847 - test fetch event generating a synthesized response in HTTPS origins from a cached SW
+  
+  
+
+
+

+
+ +
+

+
+
+
+
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 @@
+
+
+
+
+  Bug 1202085 - Test that images from different controllers don't cached together
+  
+  
+
+
+

+
+ +
+

+
+
+
+
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 @@
+
+
+
+
+  Test that the image cache respects a synthesized image's Cache headers
+  
+  
+
+
+

+
+ +
+

+
+
+
+
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 @@
+
+
+
+
+  Test service worker - script cache policy
+  
+  
+
+
+
+ + + + + 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 @@ + + + + + Bug 1198078 - test that we respect mixed content blocking in importScript() inside service workers + + + + +

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 94048 - test install event.
+  
+  
+
+
+

+ +

+
+
+
+
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..8b68b8ac47
--- /dev/null
+++ b/dom/serviceworkers/test/test_install_event_gc.html
@@ -0,0 +1,121 @@
+
+
+
+
+  Test install event being GC'd before waitUntil fulfills
+  
+  
+
+
+
+
+
+
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 @@
+
+
+
+
+  Bug 930348 - test stub Navigator ServiceWorker utilities.
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 982726 - test match_all not crashing
+  
+  
+
+
+

+ +

+
+
+
+
+
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 @@
+
+
+
+
+  Bug 982726 - Test matchAll with multiple clients
+  
+  
+
+
+

+ +

+
+
+
+
+
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 @@
+
+
+
+
+  Bug 1058311 - Test matchAll client id 
+  
+  
+
+
+

+ +

+
+
+
+
+
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 @@
+
+
+
+
+  Bug 1058311 - Test matchAll clients properties 
+  
+  
+
+
+

+ +

+
+
+
+
+
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 @@
+
+
+
+
+  Failure to create a Promise shouldn't crash
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 930348 - test stub Navigator ServiceWorker utilities.
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+  Test for Bugs 1181127 and 1325101
+  
+  
+  
+  
+
+Mozilla Bug 1181127
+Mozilla Bug 1325101
+

+ +
+
+ + + + + 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 @@ + + + + + Bug 1187766 - Test loading plugins scenarios with fetch interception. + + + + +

+ +

+
+
+
+
+
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 @@
+
+
+
+
+  Bug XXXXXXX - Check that Notification constructor throws in ServiceWorkerGlobalScope
+  
+  
+  
+  
+
+
+

+ +

+
+
+
+
diff --git a/dom/serviceworkers/test/test_notification_get.html b/dom/serviceworkers/test/test_notification_get.html
new file mode 100644
index 0000000000..e4e8edb8b9
--- /dev/null
+++ b/dom/serviceworkers/test/test_notification_get.html
@@ -0,0 +1,137 @@
+
+
+
+  ServiceWorkerRegistration.getNotifications() on main thread and worker thread.
+  
+  
+  
+  
+
+
+

+ +

+
+
+
diff --git a/dom/serviceworkers/test/test_notification_openWindow.html b/dom/serviceworkers/test/test_notification_openWindow.html
new file mode 100644
index 0000000000..8665fb3e22
--- /dev/null
+++ b/dom/serviceworkers/test/test_notification_openWindow.html
@@ -0,0 +1,90 @@
+
+
+
+  Bug 1578070
+  
+  
+  
+  
+  
+
+
+

+ +

+
+
+
+
diff --git a/dom/serviceworkers/test/test_notificationclick-otherwindow.html b/dom/serviceworkers/test/test_notificationclick-otherwindow.html
new file mode 100644
index 0000000000..e4c7a98edb
--- /dev/null
+++ b/dom/serviceworkers/test/test_notificationclick-otherwindow.html
@@ -0,0 +1,64 @@
+
+
+
+
+  Bug 1114554 - Test ServiceWorkerGlobalScope.notificationclick event.
+  
+  
+  
+  
+
+
+Bug 1114554
+

+ +
+
+ + + + diff --git a/dom/serviceworkers/test/test_notificationclick.html b/dom/serviceworkers/test/test_notificationclick.html new file mode 100644 index 0000000000..8c7af700a9 --- /dev/null +++ b/dom/serviceworkers/test/test_notificationclick.html @@ -0,0 +1,65 @@ + + + + + Bug 1114554 - Test ServiceWorkerGlobalScope.notificationclick event. + + + + + + +Bug 1114554 +

+ +
+
+ + + + diff --git a/dom/serviceworkers/test/test_notificationclick_focus.html b/dom/serviceworkers/test/test_notificationclick_focus.html new file mode 100644 index 0000000000..d226c84852 --- /dev/null +++ b/dom/serviceworkers/test/test_notificationclick_focus.html @@ -0,0 +1,65 @@ + + + + + Bug 1144660 - Test client.focus() permissions on notification click + + + + + + +Bug 1114554 +

+ +
+
+ + + + diff --git a/dom/serviceworkers/test/test_notificationclose.html b/dom/serviceworkers/test/test_notificationclose.html new file mode 100644 index 0000000000..15241445d9 --- /dev/null +++ b/dom/serviceworkers/test/test_notificationclose.html @@ -0,0 +1,66 @@ + + + + + Bug 1265841 - Test ServiceWorkerGlobalScope.notificationclose event. + + + + + + +Bug 1265841 +

+ +
+
+ + + + 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 @@ + + + + Test onmessageerror event handlers + + + + + + + diff --git a/dom/serviceworkers/test/test_opaque_intercept.html b/dom/serviceworkers/test/test_opaque_intercept.html new file mode 100644 index 0000000000..f0e40e0402 --- /dev/null +++ b/dom/serviceworkers/test/test_opaque_intercept.html @@ -0,0 +1,93 @@ + + + + + Bug 982726 - Test service worker post message + + + + +

+ +

+
+
+
+
diff --git a/dom/serviceworkers/test/test_openWindow.html b/dom/serviceworkers/test/test_openWindow.html
new file mode 100644
index 0000000000..261927cc75
--- /dev/null
+++ b/dom/serviceworkers/test/test_openWindow.html
@@ -0,0 +1,111 @@
+
+
+
+
+  Bug 1172870 - Test clients.openWindow
+  
+  
+  
+  
+
+
+Bug 1172870
+

+ +
+
+ + + + 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..e4c0af23be --- /dev/null +++ b/dom/serviceworkers/test/test_origin_after_redirect.html @@ -0,0 +1,58 @@ + + + + + Test the origin of a redirected response from a service worker + + + + +

+ +

+
+
+
+
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..ca79581ccf
--- /dev/null
+++ b/dom/serviceworkers/test/test_origin_after_redirect_cached.html
@@ -0,0 +1,58 @@
+
+
+
+
+  Test the origin of a redirected response from a service worker
+  
+  
+
+
+

+ +

+
+
+
+
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..927a68ef3a
--- /dev/null
+++ b/dom/serviceworkers/test/test_origin_after_redirect_to_https.html
@@ -0,0 +1,57 @@
+
+
+
+
+  Test the origin of a redirected response from a service worker
+  
+  
+
+
+

+ +

+
+
+
+
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..29686e2302
--- /dev/null
+++ b/dom/serviceworkers/test/test_origin_after_redirect_to_https_cached.html
@@ -0,0 +1,57 @@
+
+
+
+
+  Test the origin of a redirected response from a service worker
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 982726 - Test service worker post message 
+  
+  
+
+
+

+ +

+
+
+
+
+
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 @@
+
+
+
+
+  Bug 982726 - Test service worker post message advanced 
+  
+  
+
+
+

+ +

+
+
+
+
+
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 @@
+
+
+
+
+  Bug 1142015 - Test service worker post message source 
+  
+  
+
+
+

+ +

+
+
+
+
diff --git a/dom/serviceworkers/test/test_privateBrowsing.html b/dom/serviceworkers/test/test_privateBrowsing.html
new file mode 100644
index 0000000000..825e9542ea
--- /dev/null
+++ b/dom/serviceworkers/test/test_privateBrowsing.html
@@ -0,0 +1,103 @@
+
+
+  Test for ServiceWorker - Private Browsing
+  
+  
+
+
+
+
+
+
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 @@
+
+
+
+
+  Test that registering a service worker uses the docuemnt URI for the secure origin check
+  
+  
+  
+
+
+

+ +

+
+
+
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 @@
+
+
+
+
+  Bug 1172948 - Test that registering a service worker from inside an HTTPS iframe embedded in an HTTP iframe doesn't work
+  
+  
+
+
+

+ +

+
+
+
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 @@
+
+
+
+
+  Bug 1142727 - Test that sandboxed iframes are not intercepted
+  
+  
+
+
+

+
+ + +
+

+
+
+
+
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 @@
+
+
+
+
+  Bug 1080109 - Clear ServiceWorker registrations for all domains
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 1080109 - Clear ServiceWorker registrations for specific domains
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Bug 984048 - Test scope glob matching.
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+
+  
+  Test for saving and loading bytecode in/from the necko cache
+  
+  
+  
+  
+
+
+  Mozilla Bug 1350359
+
+
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 @@
+
+
+
+
+  Test for Bug 1432846
+  
+  
+  
+  
+
+Mozilla Bug 1432846
+

+ +
+
+ + + + + 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 @@ + + + + + Test the Service-Worker-Allowed header + + + + +
+ + + + 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 @@ + + + + + Bug 1137245 - Allow IndexedDB usage in ServiceWorkers + + + + +

+ +

+
+
+
+
+
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 @@
+
+
+
+
+  Test that service worker scripts are fetched with a Service-Worker: script header
+  
+  
+  
+
+
+

+ +

+
+
+
diff --git a/dom/serviceworkers/test/test_serviceworker_interfaces.html b/dom/serviceworkers/test/test_serviceworker_interfaces.html
new file mode 100644
index 0000000000..3b4ec19134
--- /dev/null
+++ b/dom/serviceworkers/test/test_serviceworker_interfaces.html
@@ -0,0 +1,116 @@
+
+
+
+
+  Validate Interfaces Exposed to Service Workers
+  
+  
+  
+
+
+
+
+
diff --git a/dom/serviceworkers/test/test_serviceworker_interfaces.js b/dom/serviceworkers/test/test_serviceworker_interfaces.js
new file mode 100644
index 0000000000..d1dbfeb942
--- /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!
+  "DOMRequest",
+  // IMPORTANT: Do not change this list without review from a DOM peer!
+  "DOMStringList",
+  // IMPORTANT: Do not change this list without review from a DOM peer!
+  "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 @@
+
+
+
+
+  Bug 1141274 - test that service workers and shared workers are separate
+  
+  
+
+
+

+ +

+
+
+
+
diff --git a/dom/serviceworkers/test/test_serviceworkerinfo.xhtml b/dom/serviceworkers/test/test_serviceworkerinfo.xhtml
new file mode 100644
index 0000000000..a3b0fe8e53
--- /dev/null
+++ b/dom/serviceworkers/test/test_serviceworkerinfo.xhtml
@@ -0,0 +1,114 @@
+
+
+
+  
+
+  
+    

+ +

+    
+  
+  
diff --git a/dom/serviceworkers/test/test_serviceworkermanager.xhtml b/dom/serviceworkers/test/test_serviceworkermanager.xhtml new file mode 100644 index 0000000000..b118b7d3f1 --- /dev/null +++ b/dom/serviceworkers/test/test_serviceworkermanager.xhtml @@ -0,0 +1,79 @@ + + + + + + +

+ +

+    
+  
+  
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 @@ + + + + + + +

+ +

+    
+  
+  
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 @@ + + + + + Bug 1131352 - Add ServiceWorkerGlobalScope skipWaiting() + + + + +

+ +

+
+
+
+
+
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 @@
+
+
+
+  
+  
+    Test StreamFilter-monitored responses for ServiceWorker-intercepted requests
+  
+  
+  
+  
+
+
+
+
+
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 @@
+
+
+
+
+  Bug 1170550 - test registration of service worker scripts with a strict mode warning
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  
+  Bug 1152899 - Disallow the interception of third-party iframes using service workers when the third-party cookie preference is set
+  
+  
+
+
+
+
+
+
diff --git a/dom/serviceworkers/test/test_unregister.html b/dom/serviceworkers/test/test_unregister.html
new file mode 100644
index 0000000000..959378f03a
--- /dev/null
+++ b/dom/serviceworkers/test/test_unregister.html
@@ -0,0 +1,137 @@
+
+
+
+
+  Bug 984048 - Test unregister
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Test for Bug 1188545
+  
+  
+  
+  
+
+Mozilla Bug 118845
+

+ +
+
+ + + + + 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 @@ + + + + + Bug 982728 - Test ServiceWorkerGlobalScope.unregister + + + + +
+ + + + 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 @@ + + + + + Bug 1065366 - Test ServiceWorkerGlobalScope.update + + + + +
+ + + + + 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 @@ + + + + + Test for Bug 1317266 + + + + + +Mozilla Bug 1317266 +

+ +
+
+ + + + 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 @@ + + + + + Bug 1131327 - Test ServiceWorkerRegistration.onupdatefound on ServiceWorker + + + + +

+
+

+
+
+
+
+
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 @@
+
+
+
+
+  Bug 1182113 - Test service worker XSLT interception
+  
+  
+
+
+

+
+

+
+
+
+
+
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 @@
+
+
+
+  
+  SW third party iframe test
+
+  
+
+
+
+
+  
+  
+
+
+
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 @@
+
+
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 @@
+
+
diff --git a/dom/serviceworkers/test/thirdparty/sw.js b/dom/serviceworkers/test/thirdparty/sw.js
new file mode 100644
index 0000000000..0ecd801106
--- /dev/null
+++ b/dom/serviceworkers/test/thirdparty/sw.js
@@ -0,0 +1,33 @@
+self.addEventListener("fetch", function (event) {
+  dump("fetch " + event.request.url + "\n");
+  if (event.request.url.includes("iframe2.html")) {
+    var body =
+      "";
+    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" },
+      })
+    );
+    return;
+  }
+});
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 @@
+
+
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 @@
+
+
+
+
+  Bug 984048 - Test unregister
+  
+  
+
+
+

+ +

+
+
+
+
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 @@
+
+
+
+
+  Test worker::unregister
+  
+  
+
+
+
+
+
+
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 @@
+
+
+
+  Bug 1131327 - Test ServiceWorkerRegistration.onupdatefound on ServiceWorker
+
+
+
+
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 @@
+
+
+
+
+  
+
+
+
+
+
+
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 @@
+
+
+
+
+  Test worker::update
+  
+  
+
+
+
+
+
+
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 @@
+
+
+
+  Example
+  Error
+
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 =
+  ' ' +
+  '' +
+  '  ' +
+  "    " +
+  '      ' +
+  "    " +
+  "  " +
+  '  ' +
+  "";
+
+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;
+};
-- 
cgit v1.2.3