From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- .../extensions/test/browser/.eslintrc.js | 11 + .../test/browser/browser-serviceworker.ini | 9 + .../components/extensions/test/browser/browser.ini | 50 + .../browser_ext_background_serviceworker.js | 292 +++ ...r_ext_background_serviceworker_pref_disabled.js | 122 ++ .../test/browser/browser_ext_downloads_filters.js | 138 ++ .../test/browser/browser_ext_downloads_referrer.js | 82 + .../test/browser/browser_ext_management_themes.js | 149 ++ .../test/browser/browser_ext_test_mock.js | 45 + ..._ext_themes_additional_backgrounds_alignment.js | 102 + .../browser_ext_themes_alpha_accentcolor.js | 34 + .../test/browser/browser_ext_themes_arrowpanels.js | 99 + .../browser_ext_themes_autocomplete_popup.js | 170 ++ .../browser/browser_ext_themes_chromeparity.js | 161 ++ .../browser_ext_themes_dynamic_getCurrent.js | 203 ++ .../browser_ext_themes_dynamic_onUpdated.js | 154 ++ .../browser/browser_ext_themes_dynamic_updates.js | 185 ++ .../test/browser/browser_ext_themes_experiment.js | 401 ++++ .../test/browser/browser_ext_themes_findbar.js | 217 ++ .../browser_ext_themes_getCurrent_differentExt.js | 66 + .../test/browser/browser_ext_themes_highlight.js | 61 + .../test/browser/browser_ext_themes_incognito.js | 81 + .../test/browser/browser_ext_themes_lwtsupport.js | 57 + .../browser_ext_themes_multiple_backgrounds.js | 216 ++ .../test/browser/browser_ext_themes_ntp_colors.js | 157 ++ .../browser_ext_themes_ntp_colors_perwindow.js | 249 +++ .../test/browser/browser_ext_themes_persistence.js | 58 + .../test/browser/browser_ext_themes_reset.js | 112 + .../browser/browser_ext_themes_sanitization.js | 175 ++ .../test/browser/browser_ext_themes_separators.js | 69 + .../test/browser/browser_ext_themes_sidebars.js | 274 +++ .../browser/browser_ext_themes_static_onUpdated.js | 66 + .../test/browser/browser_ext_themes_tab_line.js | 50 + .../test/browser/browser_ext_themes_tab_loading.js | 51 + .../browser/browser_ext_themes_tab_selected.js | 54 + .../browser/browser_ext_themes_tab_separators.js | 38 + .../test/browser/browser_ext_themes_tab_text.js | 70 + .../browser/browser_ext_themes_theme_transition.js | 48 + .../browser/browser_ext_themes_toolbar_fields.js | 145 ++ .../browser_ext_themes_toolbar_fields_focus.js | 102 + .../browser_ext_themes_toolbarbutton_colors.js | 56 + .../browser_ext_themes_toolbarbutton_icons.js | 107 + .../test/browser/browser_ext_themes_toolbars.js | 105 + .../test/browser/browser_ext_themes_warnings.js | 143 ++ .../browser/browser_ext_thumbnails_bg_extension.js | 94 + ...browser_ext_webRequest_redirect_mozextension.js | 48 + .../browser/browser_ext_windows_popup_title.js | 61 + .../extensions/test/browser/data/test-download.txt | 1 + .../test/browser/data/test_downloads_referrer.html | 10 + toolkit/components/extensions/test/browser/head.js | 103 + .../extensions/test/browser/head_serviceworker.js | 123 ++ .../data/extension-with-bg-sw/manifest.json | 11 + .../marionette/data/extension-with-bg-sw/sw.js | 3 + .../extensions/test/marionette/manifest.ini | 1 + ...nsion_serviceworkers_purged_on_pref_disabled.py | 82 + .../extensions/test/mochitest/.eslintrc.js | 12 + .../extensions/test/mochitest/chrome.ini | 37 + .../test/mochitest/chrome_cleanup_script.js | 66 + .../extensions/test/mochitest/chrome_head.js | 1 + .../test/mochitest/file_WebNavigation_page1.html | 12 + .../test/mochitest/file_WebNavigation_page2.html | 7 + .../test/mochitest/file_WebNavigation_page3.html | 9 + .../test/mochitest/file_WebRequest_page3.html | 10 + .../test/mochitest/file_contains_iframe.html | 12 + .../test/mochitest/file_contains_img.html | 11 + .../mochitest/file_contentscript_activeTab.html | 11 + .../mochitest/file_contentscript_activeTab2.html | 10 + .../test/mochitest/file_contentscript_iframe.html | 10 + .../extensions/test/mochitest/file_green.html | 3 + .../extensions/test/mochitest/file_image_bad.png | Bin 0 -> 5401 bytes .../extensions/test/mochitest/file_image_good.png | Bin 0 -> 580 bytes .../extensions/test/mochitest/file_image_great.png | Bin 0 -> 580 bytes .../test/mochitest/file_image_redirect.png | Bin 0 -> 5401 bytes .../extensions/test/mochitest/file_indexedDB.html | 28 + .../extensions/test/mochitest/file_mixed.html | 13 + .../test/mochitest/file_redirect_cors_bypass.html | 30 + .../test/mochitest/file_redirect_data_uri.html | 9 + .../test/mochitest/file_remote_frame.html | 20 + .../extensions/test/mochitest/file_sample.html | 12 + .../extensions/test/mochitest/file_sample.txt | 1 + .../test/mochitest/file_sample.txt^headers^ | 1 + .../extensions/test/mochitest/file_script_bad.js | 3 + .../extensions/test/mochitest/file_script_good.js | 12 + .../test/mochitest/file_script_redirect.js | 3 + .../extensions/test/mochitest/file_script_xhr.js | 9 + .../test/mochitest/file_serviceWorker.html | 16 + .../mochitest/file_simple_sandboxed_frame.html | 23 + .../mochitest/file_simple_sandboxed_subframe.html | 10 + .../extensions/test/mochitest/file_simple_xhr.html | 19 + .../test/mochitest/file_simple_xhr_frame.html | 19 + .../test/mochitest/file_simple_xhr_frame2.html | 23 + .../test/mochitest/file_streamfilter.txt | 1 + .../extensions/test/mochitest/file_style_bad.css | 3 + .../extensions/test/mochitest/file_style_good.css | 3 + .../test/mochitest/file_style_redirect.css | 3 + .../test/mochitest/file_tabs_permission_page1.html | 10 + .../test/mochitest/file_tabs_permission_page2.html | 11 + .../test/mochitest/file_third_party.html | 21 + .../test/mochitest/file_to_drawWindow.html | 9 + .../file_webNavigation_clientRedirect.html | 9 + ...e_webNavigation_clientRedirect_httpHeaders.html | 8 + ...gation_clientRedirect_httpHeaders.html^headers^ | 1 + .../file_webNavigation_frameClientRedirect.html | 12 + .../file_webNavigation_frameRedirect.html | 12 + .../file_webNavigation_manualSubframe.html | 12 + .../file_webNavigation_manualSubframe_page1.html | 8 + .../file_webNavigation_manualSubframe_page2.html | 7 + .../test/mochitest/file_with_about_blank.html | 10 + .../test/mochitest/file_with_images.html | 10 + .../test/mochitest/file_with_xorigin_frame.html | 6 + .../components/extensions/test/mochitest/head.js | 123 ++ .../extensions/test/mochitest/head_cookies.js | 287 +++ .../test/mochitest/head_notifications.js | 169 ++ .../test/mochitest/head_unlimitedStorage.js | 50 + .../extensions/test/mochitest/head_webrequest.js | 482 ++++ .../components/extensions/test/mochitest/hsts.sjs | 8 + .../extensions/test/mochitest/mochitest-common.ini | 206 ++ .../extensions/test/mochitest/mochitest-remote.ini | 8 + .../extensions/test/mochitest/mochitest.ini | 12 + .../extensions/test/mochitest/mochitest_console.js | 53 + .../extensions/test/mochitest/oauth.html | 26 + .../extensions/test/mochitest/redirect_auto.sjs | 21 + .../extensions/test/mochitest/redirection.sjs | 4 + .../extensions/test/mochitest/return_headers.sjs | 20 + .../extensions/test/mochitest/serviceWorker.js | 0 .../extensions/test/mochitest/slow_response.sjs | 55 + .../test_chrome_ext_contentscript_data_uri.html | 104 + .../test_chrome_ext_contentscript_telemetry.html | 64 + ...ext_contentscript_unrecognizedprop_warning.html | 80 + .../mochitest/test_chrome_ext_downloads_open.html | 114 + .../test_chrome_ext_downloads_saveAs.html | 257 +++ .../test_chrome_ext_downloads_uniquify.html | 116 + .../mochitest/test_chrome_ext_permissions.html | 176 ++ .../test_chrome_ext_trackingprotection.html | 98 + ...est_chrome_ext_webnavigation_resolved_urls.html | 81 + ...st_chrome_ext_webrequest_background_events.html | 94 + ...est_chrome_ext_webrequest_host_permissions.html | 89 + .../test_chrome_ext_webrequest_mozextension.html | 193 ++ .../test_chrome_native_messaging_paths.html | 56 + .../test/mochitest/test_ext_activityLog.html | 390 ++++ .../extensions/test/mochitest/test_ext_all_apis.js | 181 ++ .../test/mochitest/test_ext_async_clipboard.html | 376 ++++ .../test/mochitest/test_ext_background_canvas.html | 50 + .../test/mochitest/test_ext_background_page.html | 84 + .../mochitest/test_ext_browsingData_indexedDB.html | 161 ++ .../test_ext_browsingData_localStorage.html | 322 +++ .../test_ext_browsingData_pluginData.html | 71 + .../test_ext_browsingData_serviceWorkers.html | 141 ++ .../mochitest/test_ext_browsingData_settings.html | 67 + .../test_ext_canvas_resistFingerprinting.html | 64 + .../test/mochitest/test_ext_clipboard.html | 210 ++ .../test/mochitest/test_ext_clipboard_image.html | 262 +++ .../test_ext_contentscript_about_blank.html | 116 + .../test_ext_contentscript_activeTab.html | 371 ++++ .../mochitest/test_ext_contentscript_cache.html | 113 + .../mochitest/test_ext_contentscript_canvas.html | 138 ++ .../test_ext_contentscript_devtools_metadata.html | 77 + .../test_ext_contentscript_fission_frame.html | 100 + .../test_ext_contentscript_incognito.html | 105 + .../test_ext_contentscript_permission.html | 61 + .../test/mochitest/test_ext_cookies.html | 366 ++++ .../mochitest/test_ext_cookies_containers.html | 97 + .../test/mochitest/test_ext_cookies_expiry.html | 72 + .../mochitest/test_ext_cookies_first_party.html | 316 +++ .../test/mochitest/test_ext_cookies_incognito.html | 112 + .../test_ext_cookies_permissions_bad.html | 115 + .../test_ext_cookies_permissions_good.html | 89 + .../mochitest/test_ext_downloads_download.html | 90 + ...test_ext_embeddedimg_iframe_frameAncestors.html | 94 + .../mochitest/test_ext_exclude_include_globs.html | 91 + .../mochitest/test_ext_external_messaging.html | 110 + .../test/mochitest/test_ext_generate.html | 48 + .../test/mochitest/test_ext_geolocation.html | 86 + .../test/mochitest/test_ext_identity.html | 390 ++++ .../extensions/test/mochitest/test_ext_idle.html | 68 + .../test_ext_inIncognitoContext_window.html | 49 + .../test/mochitest/test_ext_listener_proxies.html | 62 + .../mochitest/test_ext_new_tab_processType.html | 152 ++ .../test/mochitest/test_ext_notifications.html | 340 +++ .../test/mochitest/test_ext_protocolHandlers.html | 394 ++++ .../test/mochitest/test_ext_redirect_jar.html | 92 + .../test_ext_request_urlClassification.html | 129 ++ .../test/mochitest/test_ext_runtime_connect.html | 82 + .../test/mochitest/test_ext_runtime_connect2.html | 102 + .../mochitest/test_ext_runtime_connect_twoway.html | 126 ++ .../mochitest/test_ext_runtime_disconnect.html | 77 + .../test_ext_sendmessage_doublereply.html | 100 + .../mochitest/test_ext_sendmessage_frameId.html | 49 + .../test_ext_sendmessage_no_receiver.html | 82 + .../test/mochitest/test_ext_sendmessage_reply.html | 78 + .../mochitest/test_ext_sendmessage_reply2.html | 204 ++ .../test/mochitest/test_ext_storage_cleanup.html | 235 ++ .../test_ext_storage_manager_capabilities.html | 126 ++ .../mochitest/test_ext_storage_smoke_test.html | 110 + .../mochitest/test_ext_streamfilter_multiple.html | 91 + .../test_ext_streamfilter_processswitch.html | 73 + .../mochitest/test_ext_subframes_privileges.html | 340 +++ .../test/mochitest/test_ext_tabs_captureTab.html | 301 +++ .../test/mochitest/test_ext_tabs_permissions.html | 780 +++++++ .../test/mochitest/test_ext_tabs_query_popup.html | 95 + .../test/mochitest/test_ext_tabs_sendMessage.html | 95 + .../extensions/test/mochitest/test_ext_test.html | 196 ++ .../test/mochitest/test_ext_unlimitedStorage.html | 139 ++ ...limitedStorage_legacy_persistent_indexedDB.html | 81 + .../test_ext_web_accessible_incognito.html | 174 ++ .../test_ext_web_accessible_resources.html | 265 +++ .../test/mochitest/test_ext_webnavigation.html | 611 ++++++ .../mochitest/test_ext_webnavigation_filters.html | 299 +++ .../test_ext_webnavigation_incognito.html | 109 + .../test_ext_webrequest_and_proxy_filter.html | 134 ++ .../test/mochitest/test_ext_webrequest_auth.html | 182 ++ .../test_ext_webrequest_background_events.html | 120 + .../test/mochitest/test_ext_webrequest_basic.html | 446 ++++ .../test/mochitest/test_ext_webrequest_errors.html | 61 + .../test/mochitest/test_ext_webrequest_filter.html | 227 ++ .../mochitest/test_ext_webrequest_frameId.html | 214 ++ .../test/mochitest/test_ext_webrequest_hsts.html | 223 ++ .../test_ext_webrequest_redirect_bypass_cors.html | 70 + .../test_ext_webrequest_redirect_data_uri.html | 83 + .../mochitest/test_ext_webrequest_upgrade.html | 89 + .../test/mochitest/test_ext_webrequest_upload.html | 212 ++ .../mochitest/test_ext_window_postMessage.html | 104 + .../mochitest/test_verify_non_remote_mode.html | 31 + .../test/mochitest/test_verify_remote_mode.html | 22 + .../test/mochitest/webrequest_chromeworker.js | 9 + .../extensions/test/mochitest/webrequest_test.jsm | 22 + .../extensions/test/mochitest/webrequest_worker.js | 3 + .../extensions/test/xpcshell/.eslintrc.js | 9 + .../extensions/test/xpcshell/data/dummy_page.html | 7 + .../test/xpcshell/data/empty_file_download.txt | 0 .../test/xpcshell/data/file download.txt | 1 + .../test/xpcshell/data/file_WebRequest_page2.html | 25 + .../data/file_WebRequest_permission_original.html | 19 + .../data/file_WebRequest_permission_original.js | 2 + .../file_WebRequest_permission_redirected.html | 19 + .../data/file_WebRequest_permission_redirected.js | 2 + .../extensions/test/xpcshell/data/file_csp.html | 14 + .../test/xpcshell/data/file_csp.html^headers^ | 1 + .../data/file_do_load_script_subresource.html | 9 + .../test/xpcshell/data/file_document_open.html | 21 + .../test/xpcshell/data/file_document_write.html | 35 + .../test/xpcshell/data/file_download.html | 12 + .../test/xpcshell/data/file_download.txt | 1 + .../extensions/test/xpcshell/data/file_iframe.html | 9 + .../test/xpcshell/data/file_image_bad.png | Bin 0 -> 5401 bytes .../test/xpcshell/data/file_image_good.png | Bin 0 -> 580 bytes .../test/xpcshell/data/file_image_redirect.png | Bin 0 -> 5401 bytes .../test/xpcshell/data/file_page_xhr.html | 34 + .../test/xpcshell/data/file_permission_xhr.html | 61 + .../xpcshell/data/file_privilege_escalation.html | 13 + .../extensions/test/xpcshell/data/file_sample.html | 12 + .../data/file_sample_registered_styles.html | 13 + .../extensions/test/xpcshell/data/file_script.html | 14 + .../test/xpcshell/data/file_script_bad.js | 12 + .../test/xpcshell/data/file_script_good.js | 12 + .../test/xpcshell/data/file_script_redirect.js | 3 + .../test/xpcshell/data/file_script_xhr.js | 9 + .../test/xpcshell/data/file_shadowdom.html | 13 + .../test/xpcshell/data/file_style_bad.css | 3 + .../test/xpcshell/data/file_style_good.css | 3 + .../test/xpcshell/data/file_style_redirect.css | 3 + .../test/xpcshell/data/file_stylesheet_cache.css | 1 + .../test/xpcshell/data/file_stylesheet_cache.html | 3 + .../xpcshell/data/file_stylesheet_cache_2.html | 19 + .../test/xpcshell/data/file_toplevel.html | 12 + .../xpcshell/data/file_with_xorigin_frame.html | 10 + .../extensions/test/xpcshell/data/lorem.html.gz | Bin 0 -> 392 bytes .../extensions/test/xpcshell/data/pixel_green.gif | Bin 0 -> 35 bytes .../extensions/test/xpcshell/data/pixel_red.gif | Bin 0 -> 35 bytes .../components/extensions/test/xpcshell/head.js | 277 +++ .../extensions/test/xpcshell/head_e10s.js | 8 + .../extensions/test/xpcshell/head_legacy_ep.js | 13 + .../test/xpcshell/head_native_messaging.js | 153 ++ .../extensions/test/xpcshell/head_remote.js | 7 + .../extensions/test/xpcshell/head_storage.js | 1227 +++++++++++ .../extensions/test/xpcshell/head_sync.js | 65 + .../extensions/test/xpcshell/head_telemetry.js | 110 + .../extensions/test/xpcshell/native_messaging.ini | 15 + .../test_ExtensionStorageSync_migration_kinto.js | 86 + .../extensions/test/xpcshell/test_MatchPattern.js | 552 +++++ .../test/xpcshell/test_StorageSyncService.js | 286 +++ .../xpcshell/test_WebExtensionContentScript.js | 209 ++ .../test/xpcshell/test_WebExtensionPolicy.js | 376 ++++ .../test/xpcshell/test_change_remote_mode.js | 20 + .../test/xpcshell/test_csp_custom_policies.js | 278 +++ .../extensions/test/xpcshell/test_csp_validator.js | 298 +++ .../test/xpcshell/test_ext_MessageManagerProxy.js | 80 + .../test/xpcshell/test_ext_activityLog.js | 21 + .../test_ext_adoption_with_private_field_xrays.js | 160 ++ .../test/xpcshell/test_ext_adoption_with_xrays.js | 129 ++ .../extensions/test/xpcshell/test_ext_alarms.js | 219 ++ .../test/xpcshell/test_ext_alarms_does_not_fire.js | 34 + .../test/xpcshell/test_ext_alarms_periodic.js | 50 + .../test/xpcshell/test_ext_alarms_replaces.js | 56 + .../test/xpcshell/test_ext_api_permissions.js | 76 + .../xpcshell/test_ext_background_api_injection.js | 35 + .../xpcshell/test_ext_background_early_shutdown.js | 195 ++ .../test_ext_background_generated_load_events.js | 23 + .../test_ext_background_generated_reload.js | 24 + .../xpcshell/test_ext_background_global_history.js | 24 + .../test_ext_background_private_browsing.js | 46 + .../test_ext_background_runtime_connect_params.js | 88 + .../xpcshell/test_ext_background_sub_windows.js | 46 + .../test/xpcshell/test_ext_background_teardown.js | 99 + .../test/xpcshell/test_ext_background_telemetry.js | 104 + .../test_ext_background_window_properties.js | 41 + .../test/xpcshell/test_ext_brokenlinks.js | 54 + .../test/xpcshell/test_ext_browserSettings.js | 454 ++++ .../xpcshell/test_ext_browserSettings_homepage.js | 36 + .../test/xpcshell/test_ext_browsingData.js | 48 + .../test_ext_browsingData_cookies_cache.js | 456 ++++ .../test_ext_browsingData_cookies_cookieStoreId.js | 192 ++ .../test/xpcshell/test_ext_captivePortal.js | 109 + .../test/xpcshell/test_ext_captivePortal_url.js | 53 + .../xpcshell/test_ext_contentScripts_register.js | 591 +++++ .../xpcshell/test_ext_content_security_policy.js | 251 +++ .../test/xpcshell/test_ext_contentscript.js | 266 +++ .../test_ext_contentscript_about_blank_start.js | 78 + .../test_ext_contentscript_api_injection.js | 65 + .../test_ext_contentscript_async_loading.js | 79 + .../test_ext_contentscript_canvas_tainting.js | 128 ++ .../xpcshell/test_ext_contentscript_context.js | 348 +++ .../test_ext_contentscript_context_isolation.js | 160 ++ .../test_ext_contentscript_create_iframe.js | 177 ++ .../test/xpcshell/test_ext_contentscript_csp.js | 355 +++ .../test/xpcshell/test_ext_contentscript_css.js | 48 + .../test_ext_contentscript_exporthelpers.js | 98 + .../test_ext_contentscript_in_background.js | 61 + .../test_ext_contentscript_perf_observers.js | 71 + .../test_ext_contentscript_restrictSchemes.js | 70 + .../test_ext_contentscript_scriptCreated.js | 61 + .../xpcshell/test_ext_contentscript_teardown.js | 102 + .../test_ext_contentscript_triggeringPrincipal.js | 1373 ++++++++++++ ...ntscript_unregister_during_loadContentScript.js | 91 + .../test_ext_contentscript_xml_prettyprint.js | 75 + .../test_ext_contentscript_xorigin_frame.js | 85 + .../test/xpcshell/test_ext_contentscript_xrays.js | 59 + .../extensions/test/xpcshell/test_ext_contexts.js | 198 ++ .../test/xpcshell/test_ext_contexts_gc.js | 273 +++ .../xpcshell/test_ext_contextual_identities.js | 513 +++++ .../test/xpcshell/test_ext_cookieBehaviors.js | 675 ++++++ .../test/xpcshell/test_ext_cookies_firstParty.js | 334 +++ .../test/xpcshell/test_ext_cookies_samesite.js | 109 + .../test/xpcshell/test_ext_debugging_utils.js | 316 +++ .../extensions/test/xpcshell/test_ext_dns.js | 176 ++ .../extensions/test/xpcshell/test_ext_downloads.js | 38 + .../test/xpcshell/test_ext_downloads_cookies.js | 216 ++ .../test/xpcshell/test_ext_downloads_download.js | 680 ++++++ .../test/xpcshell/test_ext_downloads_misc.js | 1069 +++++++++ .../test/xpcshell/test_ext_downloads_private.js | 308 +++ .../test/xpcshell/test_ext_downloads_search.js | 682 ++++++ .../test/xpcshell/test_ext_downloads_urlencoded.js | 235 ++ .../test/xpcshell/test_ext_error_location.js | 48 + .../test/xpcshell/test_ext_eventpage_warning.js | 90 + .../test/xpcshell/test_ext_experiments.js | 358 +++ .../extensions/test/xpcshell/test_ext_extension.js | 80 + .../test_ext_extensionPreferencesManager.js | 887 ++++++++ .../xpcshell/test_ext_extensionSettingsStore.js | 1089 ++++++++++ .../test_ext_extension_content_telemetry.js | 151 ++ .../xpcshell/test_ext_extension_startup_failure.js | 46 + .../test_ext_extension_startup_telemetry.js | 93 + .../test/xpcshell/test_ext_file_access.js | 193 ++ .../xpcshell/test_ext_geckoProfiler_control.js | 208 ++ .../test/xpcshell/test_ext_geckoProfiler_schema.js | 56 + .../extensions/test/xpcshell/test_ext_geturl.js | 61 + .../extensions/test/xpcshell/test_ext_i18n.js | 574 +++++ .../extensions/test/xpcshell/test_ext_i18n_css.js | 197 ++ .../extensions/test/xpcshell/test_ext_idle.js | 270 +++ .../extensions/test/xpcshell/test_ext_incognito.js | 302 +++ .../test/xpcshell/test_ext_indexedDB_principal.js | 101 + .../extensions/test/xpcshell/test_ext_ipcBlob.js | 150 ++ .../test/xpcshell/test_ext_json_parser.js | 39 + .../extensions/test/xpcshell/test_ext_l10n.js | 150 ++ .../test/xpcshell/test_ext_localStorage.js | 50 + .../test/xpcshell/test_ext_management.js | 205 ++ .../xpcshell/test_ext_management_uninstall_self.js | 146 ++ .../extensions/test/xpcshell/test_ext_manifest.js | 95 + .../test_ext_manifest_content_security_policy.js | 82 + .../test/xpcshell/test_ext_manifest_incognito.js | 48 + .../test_ext_manifest_minimum_chrome_version.js | 12 + .../test_ext_manifest_minimum_opera_version.js | 12 + .../test/xpcshell/test_ext_manifest_themes.js | 35 + .../test/xpcshell/test_ext_messaging_startup.js | 270 +++ .../test/xpcshell/test_ext_native_messaging.js | 685 ++++++ .../xpcshell/test_ext_native_messaging_perf.js | 130 ++ .../test_ext_native_messaging_unresponsive.js | 85 + .../test/xpcshell/test_ext_networkStatus.js | 190 ++ .../xpcshell/test_ext_notifications_incognito.js | 108 + .../xpcshell/test_ext_notifications_unsupported.js | 41 + .../xpcshell/test_ext_onmessage_removelistener.js | 30 + .../test/xpcshell/test_ext_performance_counters.js | 86 + .../test/xpcshell/test_ext_permission_warnings.js | 654 ++++++ .../test/xpcshell/test_ext_permission_xhr.js | 235 ++ .../test/xpcshell/test_ext_permissions.js | 845 ++++++++ .../test/xpcshell/test_ext_permissions_api.js | 397 ++++ .../test/xpcshell/test_ext_permissions_migrate.js | 252 +++ .../xpcshell/test_ext_permissions_uninstall.js | 160 ++ .../test/xpcshell/test_ext_persistent_events.js | 521 +++++ .../extensions/test/xpcshell/test_ext_privacy.js | 964 ++++++++ .../test/xpcshell/test_ext_privacy_disable.js | 201 ++ .../test/xpcshell/test_ext_privacy_update.js | 167 ++ .../test_ext_proxy_authorization_via_proxyinfo.js | 116 + .../test/xpcshell/test_ext_proxy_config.js | 633 ++++++ .../test/xpcshell/test_ext_proxy_onauthrequired.js | 302 +++ .../test/xpcshell/test_ext_proxy_settings.js | 107 + .../test/xpcshell/test_ext_proxy_socks.js | 557 +++++ .../test/xpcshell/test_ext_proxy_speculative.js | 52 + .../test/xpcshell/test_ext_proxy_startup.js | 158 ++ .../extensions/test/xpcshell/test_ext_redirects.js | 567 +++++ .../test_ext_runtime_connect_no_receiver.js | 26 + .../xpcshell/test_ext_runtime_getBrowserInfo.js | 26 + .../xpcshell/test_ext_runtime_getPlatformInfo.js | 36 + .../test/xpcshell/test_ext_runtime_id.js | 46 + .../xpcshell/test_ext_runtime_messaging_self.js | 84 + .../test_ext_runtime_onInstalled_and_onStartup.js | 401 ++++ .../test/xpcshell/test_ext_runtime_ports.js | 69 + .../test/xpcshell/test_ext_runtime_ports_gc.js | 168 ++ .../test/xpcshell/test_ext_runtime_sendMessage.js | 452 ++++ .../xpcshell/test_ext_runtime_sendMessage_args.js | 101 + .../test_ext_runtime_sendMessage_errors.js | 66 + .../test_ext_runtime_sendMessage_multiple.js | 67 + .../test_ext_runtime_sendMessage_no_receiver.js | 93 + .../test/xpcshell/test_ext_same_site_cookies.js | 131 ++ .../test/xpcshell/test_ext_same_site_redirects.js | 233 ++ .../test/xpcshell/test_ext_sandbox_var.js | 42 + .../extensions/test/xpcshell/test_ext_schema.js | 79 + .../extensions/test/xpcshell/test_ext_schemas.js | 2097 ++++++++++++++++++ .../xpcshell/test_ext_schemas_allowed_contexts.js | 157 ++ .../test/xpcshell/test_ext_schemas_async.js | 352 +++ .../test/xpcshell/test_ext_schemas_interactive.js | 174 ++ .../test_ext_schemas_manifest_permissions.js | 174 ++ .../test/xpcshell/test_ext_schemas_privileged.js | 103 + .../test/xpcshell/test_ext_schemas_revoke.js | 507 +++++ .../test/xpcshell/test_ext_schemas_roots.js | 242 +++ .../extensions/test/xpcshell/test_ext_shadowdom.js | 59 + .../test/xpcshell/test_ext_shared_workers.js | 40 + .../test/xpcshell/test_ext_shutdown_cleanup.js | 42 + .../extensions/test/xpcshell/test_ext_simple.js | 111 + .../test/xpcshell/test_ext_startupData.js | 55 + .../test/xpcshell/test_ext_startup_cache.js | 172 ++ .../test/xpcshell/test_ext_startup_perf.js | 73 + .../xpcshell/test_ext_startup_request_handler.js | 64 + .../xpcshell/test_ext_storage_content_local.js | 39 + .../test/xpcshell/test_ext_storage_content_sync.js | 31 + .../test_ext_storage_content_sync_kinto.js | 31 + .../test_ext_storage_idb_data_migration.js | 787 +++++++ .../test/xpcshell/test_ext_storage_local.js | 73 + .../test/xpcshell/test_ext_storage_managed.js | 170 ++ .../xpcshell/test_ext_storage_managed_policy.js | 55 + .../test_ext_storage_quota_exceeded_errors.js | 82 + .../test/xpcshell/test_ext_storage_sanitizer.js | 106 + .../test/xpcshell/test_ext_storage_sync.js | 29 + .../test/xpcshell/test_ext_storage_sync_kinto.js | 2290 ++++++++++++++++++++ .../xpcshell/test_ext_storage_sync_kinto_crypto.js | 122 ++ .../test/xpcshell/test_ext_storage_tab.js | 245 +++ .../test/xpcshell/test_ext_storage_telemetry.js | 369 ++++ .../test/xpcshell/test_ext_tab_teardown.js | 98 + .../extensions/test/xpcshell/test_ext_telemetry.js | 870 ++++++++ .../extensions/test/xpcshell/test_ext_test_mock.js | 55 + .../test/xpcshell/test_ext_test_wrapper.js | 64 + .../test/xpcshell/test_ext_trustworthy_origin.js | 20 + .../test/xpcshell/test_ext_unknown_permissions.js | 63 + .../test/xpcshell/test_ext_unlimitedStorage.js | 213 ++ .../test/xpcshell/test_ext_unload_frame.js | 230 ++ .../test/xpcshell/test_ext_userScripts.js | 671 ++++++ .../test/xpcshell/test_ext_userScripts_exports.js | 1108 ++++++++++ .../xpcshell/test_ext_userScripts_telemetry.js | 175 ++ .../test/xpcshell/test_ext_webRequest_auth.js | 425 ++++ .../test/xpcshell/test_ext_webRequest_cached.js | 311 +++ .../test_ext_webRequest_cancelWithReason.js | 69 + .../test/xpcshell/test_ext_webRequest_download.js | 43 + .../test_ext_webRequest_filterResponseData.js | 523 +++++ .../xpcshell/test_ext_webRequest_filterTypes.js | 85 + .../xpcshell/test_ext_webRequest_filter_urls.js | 35 + .../test_ext_webRequest_from_extension_page.js | 57 + .../test/xpcshell/test_ext_webRequest_host.js | 99 + .../test/xpcshell/test_ext_webRequest_incognito.js | 81 + .../test/xpcshell/test_ext_webRequest_mergecsp.js | 214 ++ .../xpcshell/test_ext_webRequest_permission.js | 154 ++ .../test_ext_webRequest_redirect_StreamFilter.js | 129 ++ .../test_ext_webRequest_redirect_mozextension.js | 47 + .../xpcshell/test_ext_webRequest_requestSize.js | 57 + .../xpcshell/test_ext_webRequest_responseBody.js | 765 +++++++ .../xpcshell/test_ext_webRequest_set_cookie.js | 308 +++ .../test/xpcshell/test_ext_webRequest_startup.js | 603 ++++++ .../test_ext_webRequest_startup_StreamFilter.js | 84 + .../xpcshell/test_ext_webRequest_style_cache.js | 49 + .../test/xpcshell/test_ext_webRequest_suspend.js | 294 +++ .../test_ext_webRequest_urlclassification.js | 33 + .../xpcshell/test_ext_webRequest_userContextId.js | 41 + .../xpcshell/test_ext_webRequest_viewsource.js | 95 + .../test_ext_webRequest_viewsource_StreamFilter.js | 144 ++ .../test/xpcshell/test_ext_webRequest_webSocket.js | 55 + .../xpcshell/test_ext_web_accessible_resources.js | 150 ++ .../test/xpcshell/test_ext_xhr_capabilities.js | 72 + .../test_extension_permissions_migration.js | 99 + .../test/xpcshell/test_load_all_api_modules.js | 172 ++ .../test/xpcshell/test_locale_converter.js | 146 ++ .../extensions/test/xpcshell/test_locale_data.js | 221 ++ .../test/xpcshell/test_native_manifests.js | 443 ++++ .../test/xpcshell/test_proxy_incognito.js | 103 + .../test/xpcshell/test_proxy_info_results.js | 469 ++++ .../test/xpcshell/test_proxy_listener.js | 318 +++ .../test/xpcshell/test_proxy_userContextId.js | 43 + .../test/xpcshell/test_webRequest_ancestors.js | 79 + .../test/xpcshell/test_webRequest_cookies.js | 102 + .../test/xpcshell/test_webRequest_filtering.js | 182 ++ .../test/xpcshell/xpcshell-common-e10s.ini | 13 + .../extensions/test/xpcshell/xpcshell-common.ini | 260 +++ .../extensions/test/xpcshell/xpcshell-content.ini | 22 + .../extensions/test/xpcshell/xpcshell-e10s.ini | 28 + .../test/xpcshell/xpcshell-legacy-ep.ini | 23 + .../extensions/test/xpcshell/xpcshell-remote.ini | 30 + .../extensions/test/xpcshell/xpcshell.ini | 89 + 514 files changed, 78154 insertions(+) create mode 100644 toolkit/components/extensions/test/browser/.eslintrc.js create mode 100644 toolkit/components/extensions/test/browser/browser-serviceworker.ini create mode 100644 toolkit/components/extensions/test/browser/browser.ini create mode 100644 toolkit/components/extensions/test/browser/browser_ext_background_serviceworker.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_background_serviceworker_pref_disabled.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_downloads_filters.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_downloads_referrer.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_management_themes.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_test_mock.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_additional_backgrounds_alignment.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_alpha_accentcolor.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_chromeparity.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_getCurrent.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_onUpdated.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_experiment.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_findbar.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_getCurrent_differentExt.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_highlight.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_incognito.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_lwtsupport.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_persistence.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_reset.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_separators.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_sidebars.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_static_onUpdated.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_tab_line.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_tab_loading.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_tab_selected.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_tab_separators.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_tab_text.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_theme_transition.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields_focus.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_colors.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_icons.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_toolbars.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_themes_warnings.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_thumbnails_bg_extension.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_webRequest_redirect_mozextension.js create mode 100644 toolkit/components/extensions/test/browser/browser_ext_windows_popup_title.js create mode 100644 toolkit/components/extensions/test/browser/data/test-download.txt create mode 100644 toolkit/components/extensions/test/browser/data/test_downloads_referrer.html create mode 100644 toolkit/components/extensions/test/browser/head.js create mode 100644 toolkit/components/extensions/test/browser/head_serviceworker.js create mode 100644 toolkit/components/extensions/test/marionette/data/extension-with-bg-sw/manifest.json create mode 100644 toolkit/components/extensions/test/marionette/data/extension-with-bg-sw/sw.js create mode 100644 toolkit/components/extensions/test/marionette/manifest.ini create mode 100644 toolkit/components/extensions/test/marionette/test_extension_serviceworkers_purged_on_pref_disabled.py create mode 100644 toolkit/components/extensions/test/mochitest/.eslintrc.js create mode 100644 toolkit/components/extensions/test/mochitest/chrome.ini create mode 100644 toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js create mode 100644 toolkit/components/extensions/test/mochitest/chrome_head.js create mode 100644 toolkit/components/extensions/test/mochitest/file_WebNavigation_page1.html create mode 100644 toolkit/components/extensions/test/mochitest/file_WebNavigation_page2.html create mode 100644 toolkit/components/extensions/test/mochitest/file_WebNavigation_page3.html create mode 100644 toolkit/components/extensions/test/mochitest/file_WebRequest_page3.html create mode 100644 toolkit/components/extensions/test/mochitest/file_contains_iframe.html create mode 100644 toolkit/components/extensions/test/mochitest/file_contains_img.html create mode 100644 toolkit/components/extensions/test/mochitest/file_contentscript_activeTab.html create mode 100644 toolkit/components/extensions/test/mochitest/file_contentscript_activeTab2.html create mode 100644 toolkit/components/extensions/test/mochitest/file_contentscript_iframe.html create mode 100644 toolkit/components/extensions/test/mochitest/file_green.html create mode 100644 toolkit/components/extensions/test/mochitest/file_image_bad.png create mode 100644 toolkit/components/extensions/test/mochitest/file_image_good.png create mode 100644 toolkit/components/extensions/test/mochitest/file_image_great.png create mode 100644 toolkit/components/extensions/test/mochitest/file_image_redirect.png create mode 100644 toolkit/components/extensions/test/mochitest/file_indexedDB.html create mode 100644 toolkit/components/extensions/test/mochitest/file_mixed.html create mode 100644 toolkit/components/extensions/test/mochitest/file_redirect_cors_bypass.html create mode 100644 toolkit/components/extensions/test/mochitest/file_redirect_data_uri.html create mode 100644 toolkit/components/extensions/test/mochitest/file_remote_frame.html create mode 100644 toolkit/components/extensions/test/mochitest/file_sample.html create mode 100644 toolkit/components/extensions/test/mochitest/file_sample.txt create mode 100644 toolkit/components/extensions/test/mochitest/file_sample.txt^headers^ create mode 100644 toolkit/components/extensions/test/mochitest/file_script_bad.js create mode 100644 toolkit/components/extensions/test/mochitest/file_script_good.js create mode 100644 toolkit/components/extensions/test/mochitest/file_script_redirect.js create mode 100644 toolkit/components/extensions/test/mochitest/file_script_xhr.js create mode 100644 toolkit/components/extensions/test/mochitest/file_serviceWorker.html create mode 100644 toolkit/components/extensions/test/mochitest/file_simple_sandboxed_frame.html create mode 100644 toolkit/components/extensions/test/mochitest/file_simple_sandboxed_subframe.html create mode 100644 toolkit/components/extensions/test/mochitest/file_simple_xhr.html create mode 100644 toolkit/components/extensions/test/mochitest/file_simple_xhr_frame.html create mode 100644 toolkit/components/extensions/test/mochitest/file_simple_xhr_frame2.html create mode 100644 toolkit/components/extensions/test/mochitest/file_streamfilter.txt create mode 100644 toolkit/components/extensions/test/mochitest/file_style_bad.css create mode 100644 toolkit/components/extensions/test/mochitest/file_style_good.css create mode 100644 toolkit/components/extensions/test/mochitest/file_style_redirect.css create mode 100644 toolkit/components/extensions/test/mochitest/file_tabs_permission_page1.html create mode 100644 toolkit/components/extensions/test/mochitest/file_tabs_permission_page2.html create mode 100644 toolkit/components/extensions/test/mochitest/file_third_party.html create mode 100644 toolkit/components/extensions/test/mochitest/file_to_drawWindow.html create mode 100644 toolkit/components/extensions/test/mochitest/file_webNavigation_clientRedirect.html create mode 100644 toolkit/components/extensions/test/mochitest/file_webNavigation_clientRedirect_httpHeaders.html create mode 100644 toolkit/components/extensions/test/mochitest/file_webNavigation_clientRedirect_httpHeaders.html^headers^ create mode 100644 toolkit/components/extensions/test/mochitest/file_webNavigation_frameClientRedirect.html create mode 100644 toolkit/components/extensions/test/mochitest/file_webNavigation_frameRedirect.html create mode 100644 toolkit/components/extensions/test/mochitest/file_webNavigation_manualSubframe.html create mode 100644 toolkit/components/extensions/test/mochitest/file_webNavigation_manualSubframe_page1.html create mode 100644 toolkit/components/extensions/test/mochitest/file_webNavigation_manualSubframe_page2.html create mode 100644 toolkit/components/extensions/test/mochitest/file_with_about_blank.html create mode 100644 toolkit/components/extensions/test/mochitest/file_with_images.html create mode 100644 toolkit/components/extensions/test/mochitest/file_with_xorigin_frame.html create mode 100644 toolkit/components/extensions/test/mochitest/head.js create mode 100644 toolkit/components/extensions/test/mochitest/head_cookies.js create mode 100644 toolkit/components/extensions/test/mochitest/head_notifications.js create mode 100644 toolkit/components/extensions/test/mochitest/head_unlimitedStorage.js create mode 100644 toolkit/components/extensions/test/mochitest/head_webrequest.js create mode 100644 toolkit/components/extensions/test/mochitest/hsts.sjs create mode 100644 toolkit/components/extensions/test/mochitest/mochitest-common.ini create mode 100644 toolkit/components/extensions/test/mochitest/mochitest-remote.ini create mode 100644 toolkit/components/extensions/test/mochitest/mochitest.ini create mode 100644 toolkit/components/extensions/test/mochitest/mochitest_console.js create mode 100644 toolkit/components/extensions/test/mochitest/oauth.html create mode 100644 toolkit/components/extensions/test/mochitest/redirect_auto.sjs create mode 100644 toolkit/components/extensions/test/mochitest/redirection.sjs create mode 100644 toolkit/components/extensions/test/mochitest/return_headers.sjs create mode 100644 toolkit/components/extensions/test/mochitest/serviceWorker.js create mode 100644 toolkit/components/extensions/test/mochitest/slow_response.sjs create mode 100644 toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_data_uri.html create mode 100644 toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_telemetry.html create mode 100644 toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_unrecognizedprop_warning.html create mode 100644 toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_open.html create mode 100644 toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html create mode 100644 toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_uniquify.html create mode 100644 toolkit/components/extensions/test/mochitest/test_chrome_ext_permissions.html create mode 100644 toolkit/components/extensions/test/mochitest/test_chrome_ext_trackingprotection.html create mode 100644 toolkit/components/extensions/test/mochitest/test_chrome_ext_webnavigation_resolved_urls.html create mode 100644 toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_background_events.html create mode 100644 toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_host_permissions.html create mode 100644 toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_mozextension.html create mode 100644 toolkit/components/extensions/test/mochitest/test_chrome_native_messaging_paths.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_activityLog.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_all_apis.js create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_async_clipboard.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_background_canvas.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_background_page.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_browsingData_indexedDB.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_browsingData_localStorage.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_browsingData_pluginData.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_browsingData_serviceWorkers.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_browsingData_settings.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_canvas_resistFingerprinting.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_clipboard.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_clipboard_image.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_contentscript_about_blank.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_contentscript_activeTab.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_contentscript_cache.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_contentscript_canvas.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_contentscript_devtools_metadata.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_contentscript_fission_frame.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_contentscript_incognito.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_contentscript_permission.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_cookies.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_cookies_containers.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_cookies_expiry.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_cookies_first_party.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_cookies_incognito.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions_bad.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions_good.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_downloads_download.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_embeddedimg_iframe_frameAncestors.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_exclude_include_globs.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_external_messaging.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_generate.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_geolocation.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_identity.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_idle.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_inIncognitoContext_window.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_listener_proxies.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_new_tab_processType.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_notifications.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_protocolHandlers.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_redirect_jar.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_request_urlClassification.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_runtime_connect.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_runtime_connect2.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_runtime_connect_twoway.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_runtime_disconnect.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_sendmessage_doublereply.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_sendmessage_frameId.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_sendmessage_no_receiver.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply2.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_storage_cleanup.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_storage_manager_capabilities.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_storage_smoke_test.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_streamfilter_multiple.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_streamfilter_processswitch.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_subframes_privileges.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_tabs_captureTab.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_tabs_permissions.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_tabs_query_popup.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_tabs_sendMessage.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_test.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_unlimitedStorage.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_unlimitedStorage_legacy_persistent_indexedDB.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_web_accessible_incognito.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_webnavigation.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_webnavigation_filters.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_webnavigation_incognito.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_webrequest_and_proxy_filter.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_webrequest_auth.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_webrequest_basic.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_webrequest_errors.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_webrequest_filter.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_webrequest_frameId.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_webrequest_hsts.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_webrequest_redirect_bypass_cors.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_webrequest_redirect_data_uri.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_webrequest_upgrade.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_webrequest_upload.html create mode 100644 toolkit/components/extensions/test/mochitest/test_ext_window_postMessage.html create mode 100644 toolkit/components/extensions/test/mochitest/test_verify_non_remote_mode.html create mode 100644 toolkit/components/extensions/test/mochitest/test_verify_remote_mode.html create mode 100644 toolkit/components/extensions/test/mochitest/webrequest_chromeworker.js create mode 100644 toolkit/components/extensions/test/mochitest/webrequest_test.jsm create mode 100644 toolkit/components/extensions/test/mochitest/webrequest_worker.js create mode 100644 toolkit/components/extensions/test/xpcshell/.eslintrc.js create mode 100644 toolkit/components/extensions/test/xpcshell/data/dummy_page.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/empty_file_download.txt create mode 100644 toolkit/components/extensions/test/xpcshell/data/file download.txt create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_WebRequest_page2.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_original.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_original.js create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_redirected.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_redirected.js create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_csp.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_csp.html^headers^ create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_do_load_script_subresource.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_document_open.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_document_write.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_download.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_download.txt create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_iframe.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_image_bad.png create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_image_good.png create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_image_redirect.png create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_page_xhr.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_permission_xhr.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_privilege_escalation.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_sample.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_sample_registered_styles.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_script.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_script_bad.js create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_script_good.js create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_script_redirect.js create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_script_xhr.js create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_shadowdom.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_style_bad.css create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_style_good.css create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_style_redirect.css create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_stylesheet_cache.css create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_stylesheet_cache.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_stylesheet_cache_2.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_toplevel.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/file_with_xorigin_frame.html create mode 100644 toolkit/components/extensions/test/xpcshell/data/lorem.html.gz create mode 100644 toolkit/components/extensions/test/xpcshell/data/pixel_green.gif create mode 100644 toolkit/components/extensions/test/xpcshell/data/pixel_red.gif create mode 100644 toolkit/components/extensions/test/xpcshell/head.js create mode 100644 toolkit/components/extensions/test/xpcshell/head_e10s.js create mode 100644 toolkit/components/extensions/test/xpcshell/head_legacy_ep.js create mode 100644 toolkit/components/extensions/test/xpcshell/head_native_messaging.js create mode 100644 toolkit/components/extensions/test/xpcshell/head_remote.js create mode 100644 toolkit/components/extensions/test/xpcshell/head_storage.js create mode 100644 toolkit/components/extensions/test/xpcshell/head_sync.js create mode 100644 toolkit/components/extensions/test/xpcshell/head_telemetry.js create mode 100644 toolkit/components/extensions/test/xpcshell/native_messaging.ini create mode 100644 toolkit/components/extensions/test/xpcshell/test_ExtensionStorageSync_migration_kinto.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_MatchPattern.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_StorageSyncService.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_WebExtensionContentScript.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_WebExtensionPolicy.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_change_remote_mode.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_csp_custom_policies.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_csp_validator.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_MessageManagerProxy.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_activityLog.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_private_field_xrays.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_xrays.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_alarms.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_alarms_does_not_fire.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_alarms_periodic.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_alarms_replaces.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_api_permissions.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_background_api_injection.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_background_early_shutdown.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_background_generated_load_events.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_background_generated_reload.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_background_global_history.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_background_private_browsing.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_background_runtime_connect_params.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_background_sub_windows.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_background_teardown.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_background_telemetry.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_background_window_properties.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_brokenlinks.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_browserSettings.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_browserSettings_homepage.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_browsingData.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_browsingData_cookies_cache.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_browsingData_cookies_cookieStoreId.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_captivePortal.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_captivePortal_url.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentScripts_register.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_content_security_policy.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_about_blank_start.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_api_injection.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_async_loading.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_canvas_tainting.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_context.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_context_isolation.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_create_iframe.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_csp.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_css.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_exporthelpers.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_in_background.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_perf_observers.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_restrictSchemes.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_scriptCreated.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_teardown.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_unregister_during_loadContentScript.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_xml_prettyprint.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_xorigin_frame.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contentscript_xrays.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contexts.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contexts_gc.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_cookieBehaviors.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_cookies_firstParty.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_cookies_samesite.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_debugging_utils.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_dns.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_downloads.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_downloads_cookies.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_downloads_misc.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_downloads_private.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_downloads_urlencoded.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_error_location.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_eventpage_warning.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_experiments.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_extension.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_extensionPreferencesManager.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_extensionSettingsStore.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_extension_content_telemetry.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_extension_startup_failure.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_extension_startup_telemetry.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_file_access.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_geckoProfiler_control.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_geckoProfiler_schema.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_geturl.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_i18n.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_i18n_css.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_idle.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_incognito.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_indexedDB_principal.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_ipcBlob.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_json_parser.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_l10n.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_localStorage.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_management.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_management_uninstall_self.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_manifest.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_manifest_content_security_policy.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_manifest_incognito.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_manifest_minimum_chrome_version.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_manifest_minimum_opera_version.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_manifest_themes.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_messaging_startup.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_native_messaging_perf.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_native_messaging_unresponsive.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_networkStatus.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_notifications_incognito.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_notifications_unsupported.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_onmessage_removelistener.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_performance_counters.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_permission_warnings.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_permission_xhr.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_permissions.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_permissions_api.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_permissions_migrate.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_permissions_uninstall.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_persistent_events.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_privacy.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_privacy_disable.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_privacy_update.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_proxy_authorization_via_proxyinfo.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_proxy_config.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_proxy_onauthrequired.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_proxy_settings.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_proxy_socks.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_proxy_speculative.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_proxy_startup.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_redirects.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_runtime_connect_no_receiver.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_runtime_getBrowserInfo.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_runtime_getPlatformInfo.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_runtime_id.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_runtime_messaging_self.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_runtime_ports.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_runtime_ports_gc.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_args.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_errors.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_multiple.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_no_receiver.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_same_site_cookies.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_same_site_redirects.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_sandbox_var.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_schema.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_schemas.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_schemas_allowed_contexts.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_schemas_async.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_schemas_interactive.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_schemas_manifest_permissions.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_schemas_privileged.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_schemas_revoke.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_schemas_roots.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_shadowdom.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_shared_workers.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_shutdown_cleanup.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_simple.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_startupData.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_startup_cache.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_startup_perf.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_startup_request_handler.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_storage_content_local.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_storage_content_sync.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_storage_content_sync_kinto.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_storage_idb_data_migration.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_storage_local.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_storage_managed.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_storage_managed_policy.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_storage_quota_exceeded_errors.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_storage_sanitizer.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_storage_sync.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_storage_sync_kinto.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_storage_sync_kinto_crypto.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_storage_tab.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_storage_telemetry.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_tab_teardown.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_telemetry.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_test_mock.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_test_wrapper.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_trustworthy_origin.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_unknown_permissions.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_unlimitedStorage.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_unload_frame.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_userScripts.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_userScripts_exports.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_userScripts_telemetry.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_auth.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_cached.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_cancelWithReason.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_download.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterTypes.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filter_urls.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_from_extension_page.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_host.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_incognito.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_mergecsp.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_permission.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_redirect_StreamFilter.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_redirect_mozextension.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_requestSize.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_responseBody.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_set_cookie.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_startup.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_startup_StreamFilter.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_style_cache.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_suspend.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_urlclassification.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_userContextId.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_viewsource.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_viewsource_StreamFilter.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_webSocket.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_web_accessible_resources.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_xhr_capabilities.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_extension_permissions_migration.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_load_all_api_modules.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_locale_converter.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_locale_data.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_native_manifests.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_proxy_incognito.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_proxy_info_results.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_proxy_listener.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_proxy_userContextId.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_webRequest_ancestors.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_webRequest_cookies.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_webRequest_filtering.js create mode 100644 toolkit/components/extensions/test/xpcshell/xpcshell-common-e10s.ini create mode 100644 toolkit/components/extensions/test/xpcshell/xpcshell-common.ini create mode 100644 toolkit/components/extensions/test/xpcshell/xpcshell-content.ini create mode 100644 toolkit/components/extensions/test/xpcshell/xpcshell-e10s.ini create mode 100644 toolkit/components/extensions/test/xpcshell/xpcshell-legacy-ep.ini create mode 100644 toolkit/components/extensions/test/xpcshell/xpcshell-remote.ini create mode 100644 toolkit/components/extensions/test/xpcshell/xpcshell.ini (limited to 'toolkit/components/extensions/test') diff --git a/toolkit/components/extensions/test/browser/.eslintrc.js b/toolkit/components/extensions/test/browser/.eslintrc.js new file mode 100644 index 0000000000..ef228570e3 --- /dev/null +++ b/toolkit/components/extensions/test/browser/.eslintrc.js @@ -0,0 +1,11 @@ +"use strict"; + +module.exports = { + env: { + webextensions: true, + }, + + rules: { + "no-shadow": "off", + }, +}; diff --git a/toolkit/components/extensions/test/browser/browser-serviceworker.ini b/toolkit/components/extensions/test/browser/browser-serviceworker.ini new file mode 100644 index 0000000000..58e9082f7b --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser-serviceworker.ini @@ -0,0 +1,9 @@ +[DEFAULT] +support-files = + head_serviceworker.js + data/** + +prefs = + extensions.backgroundServiceWorker.enabled=true + +[browser_ext_background_serviceworker.js] diff --git a/toolkit/components/extensions/test/browser/browser.ini b/toolkit/components/extensions/test/browser/browser.ini new file mode 100644 index 0000000000..0cfb5fcd89 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser.ini @@ -0,0 +1,50 @@ +[DEFAULT] +support-files = + head.js + data/** + +[browser_ext_background_serviceworker_pref_disabled.js] +[browser_ext_downloads_filters.js] +[browser_ext_downloads_referrer.js] +[browser_ext_management_themes.js] +skip-if = verify +[browser_ext_test_mock.js] +[browser_ext_themes_additional_backgrounds_alignment.js] +[browser_ext_themes_alpha_accentcolor.js] +[browser_ext_themes_arrowpanels.js] +[browser_ext_themes_autocomplete_popup.js] +[browser_ext_themes_chromeparity.js] +[browser_ext_themes_dynamic_getCurrent.js] +[browser_ext_themes_dynamic_onUpdated.js] +[browser_ext_themes_dynamic_updates.js] +[browser_ext_themes_experiment.js] +[browser_ext_themes_findbar.js] +[browser_ext_themes_getCurrent_differentExt.js] +[browser_ext_themes_highlight.js] +[browser_ext_themes_incognito.js] +[browser_ext_themes_lwtsupport.js] +[browser_ext_themes_multiple_backgrounds.js] +[browser_ext_themes_ntp_colors.js] +[browser_ext_themes_ntp_colors_perwindow.js] +[browser_ext_themes_persistence.js] +[browser_ext_themes_reset.js] +[browser_ext_themes_sanitization.js] +[browser_ext_themes_separators.js] +[browser_ext_themes_sidebars.js] +[browser_ext_themes_static_onUpdated.js] +[browser_ext_themes_tab_line.js] +[browser_ext_themes_tab_loading.js] +[browser_ext_themes_tab_selected.js] +[browser_ext_themes_tab_separators.js] +[browser_ext_themes_tab_text.js] +[browser_ext_themes_toolbar_fields_focus.js] +[browser_ext_themes_toolbar_fields.js] +[browser_ext_themes_toolbarbutton_colors.js] +[browser_ext_themes_toolbarbutton_icons.js] +[browser_ext_themes_toolbars.js] +[browser_ext_themes_theme_transition.js] +[browser_ext_themes_warnings.js] +[browser_ext_thumbnails_bg_extension.js] +support-files = !/toolkit/components/thumbnails/test/head.js +[browser_ext_webRequest_redirect_mozextension.js] +[browser_ext_windows_popup_title.js] diff --git a/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker.js b/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker.js new file mode 100644 index 0000000000..a43a49cc0a --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker.js @@ -0,0 +1,292 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* globals getBackgroundServiceWorkerRegistration, waitForServiceWorkerTerminated */ + +Services.scriptloader.loadSubScript( + new URL("head_serviceworker.js", gTestPath).href, + this +); + +add_task(assert_background_serviceworker_pref_enabled); + +add_task(async function test_serviceWorker_register_guarded_by_pref() { + // Test with backgroundServiceWorkeEnable set to true and the + // extensions.serviceWorkerRegist.allowed pref set to false. + // NOTE: the scenario with backgroundServiceWorkeEnable set to false + // is part of "browser_ext_background_serviceworker_pref_disabled.js". + + await SpecialPowers.pushPrefEnv({ + set: [["extensions.serviceWorkerRegister.allowed", false]], + }); + + let extensionData = { + files: { + "page.html": "", + "page.js": async function() { + try { + await navigator.serviceWorker.register("sw.js"); + browser.test.fail( + `An extension page should not be able to register a serviceworker successfully` + ); + } catch (err) { + browser.test.assertEq( + String(err), + "SecurityError: The operation is insecure.", + "Got the expected error on registering a service worker from a script" + ); + } + browser.test.sendMessage("test-serviceWorker-register-disallowed"); + }, + "sw.js": "", + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + + // Verify that an extension page can't register a moz-extension url + // as a service worker. + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: `moz-extension://${extension.uuid}/page.html`, + }, + async () => { + await extension.awaitMessage("test-serviceWorker-register-disallowed"); + } + ); + + await extension.unload(); + + await SpecialPowers.popPrefEnv(); + + // Test again with the pref set to true. + + await SpecialPowers.pushPrefEnv({ + set: [["extensions.serviceWorkerRegister.allowed", true]], + }); + + extension = ExtensionTestUtils.loadExtension({ + files: { + ...extensionData.files, + "page.js": async function() { + try { + await navigator.serviceWorker.register("sw.js"); + } catch (err) { + browser.test.fail( + `Unexpected error on registering a service worker: ${err}` + ); + throw err; + } finally { + browser.test.sendMessage("test-serviceworker-register-allowed"); + } + }, + }, + }); + await extension.startup(); + + // Verify that an extension page can register a moz-extension url + // as a service worker if enabled by the related pref. + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: `moz-extension://${extension.uuid}/page.html`, + }, + async () => { + await extension.awaitMessage("test-serviceworker-register-allowed"); + } + ); + + await extension.unload(); + + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function test_cache_api_allowed() { + // Verify that Cache API support for moz-extension url availability is also + // conditioned by the extensions.backgroundServiceWorker.enabled pref. + // NOTE: the scenario with backgroundServiceWorkeEnable set to false + // is part of "browser_ext_background_serviceworker_pref_disabled.js". + const extension = ExtensionTestUtils.loadExtension({ + async background() { + try { + let cache = await window.caches.open("test-cache-api"); + browser.test.assertTrue( + await window.caches.has("test-cache-api"), + "CacheStorage.has should resolve to true" + ); + + // Test that adding and requesting cached moz-extension urls + // works as well. + let url = browser.runtime.getURL("file.txt"); + await cache.add(url); + const content = await cache.match(url).then(res => res.text()); + browser.test.assertEq( + "file content", + content, + "Got the expected content from the cached moz-extension url" + ); + + // Test that deleting the cache storage works as expected. + browser.test.assertTrue( + await window.caches.delete("test-cache-api"), + "Cache deleted successfully" + ); + browser.test.assertTrue( + !(await window.caches.has("test-cache-api")), + "CacheStorage.has should resolve to false" + ); + } catch (err) { + browser.test.fail(`Unexpected error on using Cache API: ${err}`); + throw err; + } finally { + browser.test.sendMessage("test-cache-api-allowed"); + } + }, + files: { + "file.txt": "file content", + }, + }); + + await extension.startup(); + await extension.awaitMessage("test-cache-api-allowed"); + await extension.unload(); +}); + +function createTestSWScript({ postMessageReply }) { + return ` + self.onmessage = msg => { + dump("Background ServiceWorker - onmessage handler\\n"); + msg.ports[0].postMessage("${postMessageReply}"); + dump("Background ServiceWorker - postMessage\\n"); + }; + dump("Background ServiceWorker - executed\\n"); + `; +} + +async function testServiceWorker({ extension, expectMessageReply }) { + // Verify that the WebExtensions framework has successfully registered the + // background service worker declared in the extension manifest. + const swRegInfo = getBackgroundServiceWorkerRegistration(extension); + + // Activate the background service worker by exchanging a message + // with it. + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: `moz-extension://${extension.uuid}/page.html`, + }, + async browser => { + let msgFromV1 = await SpecialPowers.spawn( + browser, + [swRegInfo.scriptURL], + async url => { + const { active } = await content.navigator.serviceWorker.ready; + const { port1, port2 } = new content.MessageChannel(); + + return new Promise(resolve => { + port1.onmessage = msg => resolve(msg.data); + active.postMessage("test", [port2]); + }); + } + ); + + Assert.deepEqual( + msgFromV1, + expectMessageReply, + "Got the expected reply from the extension service worker" + ); + } + ); +} + +function loadTestExtension({ version }) { + const postMessageReply = `reply:sw-v${version}`; + + return ExtensionTestUtils.loadExtension({ + useAddonManager: "temporary", + manifest: { + version, + background: { + service_worker: "sw.js", + }, + applications: { gecko: { id: "test-bg-sw@mochi.test" } }, + }, + files: { + "page.html": "", + "sw.js": createTestSWScript({ postMessageReply }), + }, + }); +} + +async function assertWorkerIsRunningInExtensionProcess(extension) { + // Activate the background service worker by exchanging a message + // with it. + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: `moz-extension://${extension.uuid}/page.html`, + }, + async browser => { + const workerScriptURL = `moz-extension://${extension.uuid}/sw.js`; + const workerDebuggerURLs = await SpecialPowers.spawn( + browser, + [workerScriptURL], + async url => { + await content.navigator.serviceWorker.ready; + const wdm = Cc[ + "@mozilla.org/dom/workers/workerdebuggermanager;1" + ].getService(Ci.nsIWorkerDebuggerManager); + + return Array.from(wdm.getWorkerDebuggerEnumerator()) + .map(wd => { + return wd.url; + }) + .filter(swURL => swURL == url); + } + ); + + Assert.deepEqual( + workerDebuggerURLs, + [workerScriptURL], + "The worker should be running in the extension child process" + ); + } + ); +} + +add_task(async function test_background_serviceworker_with_no_ext_apis() { + const extensionV1 = loadTestExtension({ version: "1" }); + await extensionV1.startup(); + + const swRegInfo = getBackgroundServiceWorkerRegistration(extensionV1); + const { uuid } = extensionV1; + + await assertWorkerIsRunningInExtensionProcess(extensionV1); + await testServiceWorker({ + extension: extensionV1, + expectMessageReply: "reply:sw-v1", + }); + + // Load a new version of the same addon and verify that the + // expected worker script is being executed. + const extensionV2 = loadTestExtension({ version: "2" }); + await extensionV2.startup(); + is(extensionV2.uuid, uuid, "The extension uuid did not change as expected"); + + await testServiceWorker({ + extension: extensionV2, + expectMessageReply: "reply:sw-v2", + }); + + await Promise.all([ + extensionV2.unload(), + // test extension v1 wrapper has to be unloaded explicitly, otherwise + // will be detected as a failure by the test harness. + extensionV1.unload(), + ]); + await waitForServiceWorkerTerminated(swRegInfo); + await waitForServiceWorkerRegistrationsRemoved(extensionV2); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker_pref_disabled.js b/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker_pref_disabled.js new file mode 100644 index 0000000000..a2d9004801 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker_pref_disabled.js @@ -0,0 +1,122 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(async function assert_background_serviceworker_pref_disabled() { + is( + WebExtensionPolicy.backgroundServiceWorkerEnabled, + false, + "Expect extensions.backgroundServiceWorker.enabled to be false" + ); +}); + +add_task(async function test_background_serviceworker_disallowed() { + const id = "test-disallowed-worker@test"; + + const extensionData = { + manifest: { + background: { + service_worker: "sw.js", + }, + applicantions: { gecko: { id } }, + useAddonManager: "temporary", + }, + }; + + SimpleTest.waitForExplicitFinish(); + let waitForConsole = new Promise(resolve => { + SimpleTest.monitorConsole(resolve, [ + { + message: /Reading manifest: Error processing background: background.service_worker is currently disabled/, + }, + ]); + }); + + const extension = ExtensionTestUtils.loadExtension(extensionData); + await Assert.rejects( + extension.startup(), + /startup failed/, + "Startup failed with background.service_worker while disabled by pref" + ); + + SimpleTest.endMonitorConsole(); + await waitForConsole; +}); + +add_task(async function test_serviceWorker_register_disallowed() { + // Verify that setting extensions.serviceWorkerRegist.allowed pref to false + // doesn't allow serviceWorker.register if backgroundServiceWorkeEnable is + // set to false + + await SpecialPowers.pushPrefEnv({ + set: [["extensions.serviceWorkerRegister.allowed", true]], + }); + + let extensionData = { + files: { + "page.html": "", + "page.js": async function() { + try { + await navigator.serviceWorker.register("sw.js"); + browser.test.fail( + `An extension page should not be able to register a serviceworker successfully` + ); + } catch (err) { + browser.test.assertEq( + String(err), + "SecurityError: The operation is insecure.", + "Got the expected error on registering a service worker from a script" + ); + } + browser.test.sendMessage("test-serviceWorker-register-disallowed"); + }, + "sw.js": "", + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + + // Verify that an extension page can't register a moz-extension url + // as a service worker. + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: `moz-extension://${extension.uuid}/page.html`, + }, + async () => { + await extension.awaitMessage("test-serviceWorker-register-disallowed"); + } + ); + + await extension.unload(); + + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function test_cache_api_disallowed() { + // Verify that Cache API support for moz-extension url availability is also + // conditioned by the extensions.backgroundServiceWorker.enabled pref. + const extension = ExtensionTestUtils.loadExtension({ + async background() { + try { + await window.caches.open("test-cache-api"); + browser.test.fail( + `An extension page should not be allowed to use the Cache API successfully` + ); + } catch (err) { + browser.test.assertEq( + String(err), + "SecurityError: The operation is insecure.", + "Got the expected error on registering a service worker from a script" + ); + } finally { + browser.test.sendMessage("test-cache-api-disallowed"); + } + }, + }); + + await extension.startup(); + await extension.awaitMessage("test-cache-api-disallowed"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_downloads_filters.js b/toolkit/components/extensions/test/browser/browser_ext_downloads_filters.js new file mode 100644 index 0000000000..f8672597cd --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_downloads_filters.js @@ -0,0 +1,138 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ + +"use strict"; + +async function testAppliedFilters(ext, expectedFilter, expectedFilterCount) { + let tempDir = FileUtils.getDir( + "TmpD", + [`testDownloadDir-${Math.random()}`], + true + ); + + let filterCount = 0; + + let MockFilePicker = SpecialPowers.MockFilePicker; + MockFilePicker.init(window); + MockFilePicker.displayDirectory = tempDir; + MockFilePicker.returnValue = MockFilePicker.returnCancel; + MockFilePicker.appendFiltersCallback = function(fp, val) { + const hexstr = "0x" + ("000" + val.toString(16)).substr(-3); + filterCount++; + if (filterCount < expectedFilterCount) { + is(val, expectedFilter, "Got expected filter: " + hexstr); + } else if (filterCount == expectedFilterCount) { + is(val, MockFilePicker.filterAll, "Got all files filter: " + hexstr); + } else { + is(val, null, "Got unexpected filter: " + hexstr); + } + }; + MockFilePicker.showCallback = function(fp) { + const filename = fp.defaultString; + info("MockFilePicker - save as: " + filename); + }; + + let manifest = { + description: ext, + permissions: ["downloads"], + }; + + const extension = ExtensionTestUtils.loadExtension({ + manifest: manifest, + + background: async function() { + let ext = chrome.runtime.getManifest().description; + await browser.test.assertRejects( + browser.downloads.download({ + url: "http://any-origin/any-path/any-resource", + filename: "any-file" + ext, + saveAs: true, + }), + "Download canceled by the user", + "expected request to be canceled" + ); + browser.test.sendMessage("canceled"); + }, + }); + + await extension.startup(); + await extension.awaitMessage("canceled"); + await extension.unload(); + + is( + filterCount, + expectedFilterCount, + "Got correct number of filters: " + filterCount + ); + + MockFilePicker.cleanup(); + + tempDir.remove(true); +} + +// Missing extension +add_task(async function testDownload_missing_All() { + await testAppliedFilters("", null, 1); +}); + +// Unrecognized extension +add_task(async function testDownload_unrecognized_All() { + await testAppliedFilters(".xxx", null, 1); +}); + +// Recognized extensions +add_task(async function testDownload_html_HTML() { + await testAppliedFilters(".html", Ci.nsIFilePicker.filterHTML, 2); +}); + +add_task(async function testDownload_xhtml_HTML() { + await testAppliedFilters(".xhtml", Ci.nsIFilePicker.filterHTML, 2); +}); + +add_task(async function testDownload_txt_Text() { + await testAppliedFilters(".txt", Ci.nsIFilePicker.filterText, 2); +}); + +add_task(async function testDownload_text_Text() { + await testAppliedFilters(".text", Ci.nsIFilePicker.filterText, 2); +}); + +add_task(async function testDownload_jpe_Images() { + await testAppliedFilters(".jpe", Ci.nsIFilePicker.filterImages, 2); +}); + +add_task(async function testDownload_tif_Images() { + await testAppliedFilters(".tif", Ci.nsIFilePicker.filterImages, 2); +}); + +add_task(async function testDownload_webp_Images() { + await testAppliedFilters(".webp", Ci.nsIFilePicker.filterImages, 2); +}); + +add_task(async function testDownload_xml_XML() { + await testAppliedFilters(".xml", Ci.nsIFilePicker.filterXML, 2); +}); + +add_task(async function testDownload_aac_Audio() { + await testAppliedFilters(".aac", Ci.nsIFilePicker.filterAudio, 2); +}); + +add_task(async function testDownload_mp3_Audio() { + await testAppliedFilters(".mp3", Ci.nsIFilePicker.filterAudio, 2); +}); + +add_task(async function testDownload_wma_Audio() { + await testAppliedFilters(".wma", Ci.nsIFilePicker.filterAudio, 2); +}); + +add_task(async function testDownload_avi_Video() { + await testAppliedFilters(".avi", Ci.nsIFilePicker.filterVideo, 2); +}); + +add_task(async function testDownload_mp4_Video() { + await testAppliedFilters(".mp4", Ci.nsIFilePicker.filterVideo, 2); +}); + +add_task(async function testDownload_xvid_Video() { + await testAppliedFilters(".xvid", Ci.nsIFilePicker.filterVideo, 2); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_downloads_referrer.js b/toolkit/components/extensions/test/browser/browser_ext_downloads_referrer.js new file mode 100644 index 0000000000..b2931e0b6f --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_downloads_referrer.js @@ -0,0 +1,82 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ + +"use strict"; + +const { BrowserTestUtils } = ChromeUtils.import( + "resource://testing-common/BrowserTestUtils.jsm" +); + +const URL_PATH = "browser/toolkit/components/extensions/test/browser/data"; +const TEST_URL = `http://example.com/${URL_PATH}/test_downloads_referrer.html`; +const DOWNLOAD_URL = `http://example.com/${URL_PATH}/test-download.txt`; + +async function triggerSaveAs({ selector }) { + await BrowserTestUtils.synthesizeMouseAtCenter( + selector, + { type: "contextmenu", button: 2 }, + gBrowser.selectedBrowser + ); + let saveLinkCommand = window.document.getElementById("context-savelink"); + saveLinkCommand.doCommand(); +} + +add_task(function test_setup() { + const tempDir = Services.dirsvc.get("TmpD", Ci.nsIFile); + tempDir.append("test-download-dir"); + if (!tempDir.exists()) { + tempDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + } + + let MockFilePicker = SpecialPowers.MockFilePicker; + MockFilePicker.init(window); + registerCleanupFunction(function() { + MockFilePicker.cleanup(); + + if (tempDir.exists()) { + tempDir.remove(true); + } + }); + + MockFilePicker.displayDirectory = tempDir; + MockFilePicker.showCallback = function(fp) { + info("MockFilePicker: shown"); + const filename = fp.defaultString; + info("MockFilePicker: save as " + filename); + const destFile = tempDir.clone(); + destFile.append(filename); + MockFilePicker.setFiles([destFile]); + info("MockFilePicker: showCallback done"); + }; +}); + +add_task(async function test_download_item_referrer_info() { + const extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["downloads"], + }, + async background() { + browser.downloads.onCreated.addListener(async downloadInfo => { + browser.test.sendMessage("download-on-created", downloadInfo); + }); + + // Call an API method implemented in the parent process to make sure + // registering the downloas.onCreated event listener has been completed. + await browser.runtime.getBrowserInfo(); + + browser.test.sendMessage("bg-page:ready"); + }, + }); + + await extension.startup(); + await extension.awaitMessage("bg-page:ready"); + + await BrowserTestUtils.withNewTab({ gBrowser, url: TEST_URL }, async () => { + await triggerSaveAs({ selector: "a.test-link" }); + const downloadInfo = await extension.awaitMessage("download-on-created"); + is(downloadInfo.url, DOWNLOAD_URL, "Got the expected download url"); + is(downloadInfo.referrer, TEST_URL, "Got the expected referrer"); + }); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_management_themes.js b/toolkit/components/extensions/test/browser/browser_ext_management_themes.js new file mode 100644 index 0000000000..f74f418ace --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_management_themes.js @@ -0,0 +1,149 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/PromiseTestUtils.jsm" +); +PromiseTestUtils.allowMatchingRejectionsGlobally( + /Message manager disconnected/ +); + +add_task(async function test_management_themes() { + const TEST_ID = "test_management_themes@tests.mozilla.com"; + + let theme = ExtensionTestUtils.loadExtension({ + manifest: { + name: "Simple theme test", + version: "1.0", + description: "test theme", + theme: { + images: { + theme_frame: "image1.png", + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + useAddonManager: "temporary", + }); + + async function background(TEST_ID) { + browser.management.onInstalled.addListener(info => { + if (info.name == TEST_ID) { + return; + } + browser.test.log(`${info.name} was installed`); + browser.test.assertEq(info.type, "theme", "addon is theme"); + browser.test.sendMessage("onInstalled", info.name); + }); + browser.management.onDisabled.addListener(info => { + browser.test.log(`${info.name} was disabled`); + browser.test.assertEq(info.type, "theme", "addon is theme"); + browser.test.sendMessage("onDisabled", info.name); + }); + browser.management.onEnabled.addListener(info => { + browser.test.log(`${info.name} was enabled`); + browser.test.assertEq(info.type, "theme", "addon is theme"); + browser.test.sendMessage("onEnabled", info.name); + }); + browser.management.onUninstalled.addListener(info => { + browser.test.log(`${info.name} was uninstalled`); + browser.test.assertEq(info.type, "theme", "addon is theme"); + browser.test.sendMessage("onUninstalled", info.name); + }); + + async function getAddon(type) { + let addons = await browser.management.getAll(); + let themes = addons.filter(addon => addon.type === "theme"); + // We get the 4 built-in themes plus the lwt and our addon. + browser.test.assertEq(5, themes.length, "got expected addons"); + // We should also get our test extension. + let testExtension = addons.find(addon => { + return addon.id === TEST_ID; + }); + browser.test.assertTrue( + !!testExtension, + `The extension with id ${TEST_ID} was returned by getAll.` + ); + let found; + for (let addon of themes) { + browser.test.assertEq(addon.type, "theme", "addon is theme"); + if (type == "theme" && addon.id.includes("temporary-addon")) { + found = addon; + } else if (type == "enabled" && addon.enabled) { + found = addon; + } + } + return found; + } + + browser.test.onMessage.addListener(async msg => { + let theme = await getAddon("theme"); + browser.test.assertEq( + theme.description, + "test theme", + "description is correct" + ); + browser.test.assertTrue(theme.enabled, "theme is enabled"); + await browser.management.setEnabled(theme.id, false); + + theme = await getAddon("theme"); + + browser.test.assertTrue(!theme.enabled, "theme is disabled"); + let addon = getAddon("enabled"); + browser.test.assertTrue(addon, "another theme was enabled"); + + await browser.management.setEnabled(theme.id, true); + theme = await getAddon("theme"); + addon = await getAddon("enabled"); + browser.test.assertEq(theme.id, addon.id, "theme is enabled"); + + browser.test.sendMessage("done"); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + applications: { + gecko: { + id: TEST_ID, + }, + }, + name: TEST_ID, + permissions: ["management"], + }, + background: `(${background})("${TEST_ID}")`, + useAddonManager: "temporary", + }); + await extension.startup(); + + await theme.startup(); + is( + await extension.awaitMessage("onInstalled"), + "Simple theme test", + "webextension theme installed" + ); + is(await extension.awaitMessage("onDisabled"), "Default", "default disabled"); + + extension.sendMessage("test"); + is(await extension.awaitMessage("onEnabled"), "Default", "default enabled"); + is( + await extension.awaitMessage("onDisabled"), + "Simple theme test", + "addon disabled" + ); + is( + await extension.awaitMessage("onEnabled"), + "Simple theme test", + "addon enabled" + ); + is(await extension.awaitMessage("onDisabled"), "Default", "default disabled"); + await extension.awaitMessage("done"); + + await Promise.all([theme.unload(), extension.awaitMessage("onUninstalled")]); + + is(await extension.awaitMessage("onEnabled"), "Default", "default enabled"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_test_mock.js b/toolkit/components/extensions/test/browser/browser_ext_test_mock.js new file mode 100644 index 0000000000..fc71cacc66 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_test_mock.js @@ -0,0 +1,45 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +// This test verifies that the extension mocks behave consistently, regardless +// of test type (xpcshell vs browser test). +// See also toolkit/components/extensions/test/xpcshell/test_ext_test_mock.js + +// Check the state of the extension object. This should be consistent between +// browser tests and xpcshell tests. +async function checkExtensionStartupAndUnload(ext) { + await ext.startup(); + Assert.ok(ext.id, "Extension ID should be available"); + Assert.ok(ext.uuid, "Extension UUID should be available"); + await ext.unload(); + // Once set nothing clears the UUID. + Assert.ok(ext.uuid, "Extension UUID exists after unload"); +} + +add_task(async function test_MockExtension() { + // When "useAddonManager" is set, a MockExtension is created in the main + // process, which does not necessarily behave identically to an Extension. + let ext = ExtensionTestUtils.loadExtension({ + // xpcshell/test_ext_test_mock.js tests "temporary", so here we use + // "permanent" to have even more test coverage. + useAddonManager: "permanent", + manifest: { applications: { gecko: { id: "@permanent-mock-extension" } } }, + }); + + Assert.ok(!ext.id, "Extension ID is initially unavailable"); + Assert.ok(!ext.uuid, "Extension UUID is initially unavailable"); + await checkExtensionStartupAndUnload(ext); + Assert.ok(ext.id, "Extension ID exists after unload"); +}); + +add_task(async function test_generated_Extension() { + let ext = ExtensionTestUtils.loadExtension({ + manifest: {}, + }); + + Assert.ok(!ext.id, "Extension ID is initially unavailable"); + Assert.ok(!ext.uuid, "Extension UUID is initially unavailable"); + await checkExtensionStartupAndUnload(ext); + Assert.ok(ext.id, "Extension ID exists after unload"); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_additional_backgrounds_alignment.js b/toolkit/components/extensions/test/browser/browser_ext_themes_additional_backgrounds_alignment.js new file mode 100644 index 0000000000..f265a724e5 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_additional_backgrounds_alignment.js @@ -0,0 +1,102 @@ +"use strict"; + +// Case 1 - When there is a theme_frame image and additional_backgrounds_alignment is not specified. +// So background-position should default to "right top" +add_task(async function test_default_additional_backgrounds_alignment() { + const RIGHT_TOP = "100% 0%"; + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + additional_backgrounds: ["image1.png", "image1.png"], + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + let docEl = document.documentElement; + let rootCS = window.getComputedStyle(docEl); + + Assert.equal( + rootCS.getPropertyValue("background-position"), + RIGHT_TOP, + "root only contains theme_frame alignment property" + ); + + let toolbox = document.querySelector("#navigator-toolbox"); + let toolboxCS = window.getComputedStyle(toolbox); + + Assert.equal( + toolboxCS.getPropertyValue("background-position"), + RIGHT_TOP, + toolbox.id + + " only contains default additional backgrounds alignment property" + ); + + await extension.unload(); +}); + +// Case 2 - When there is a theme_frame image and additional_backgrounds_alignment is specified. +add_task(async function test_additional_backgrounds_alignment() { + const LEFT_BOTTOM = "0% 100%"; + const CENTER_CENTER = "50% 50%"; + const RIGHT_TOP = "100% 0%"; + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + additional_backgrounds: ["image1.png", "image1.png", "image1.png"], + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + }, + properties: { + additional_backgrounds_alignment: [ + "left bottom", + "center center", + "right top", + ], + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + let docEl = document.documentElement; + let rootCS = window.getComputedStyle(docEl); + + Assert.equal( + rootCS.getPropertyValue("background-position"), + RIGHT_TOP, + "root only contains theme_frame alignment property" + ); + + let toolbox = document.querySelector("#navigator-toolbox"); + let toolboxCS = window.getComputedStyle(toolbox); + + Assert.equal( + toolboxCS.getPropertyValue("background-position"), + LEFT_BOTTOM + ", " + CENTER_CENTER + ", " + RIGHT_TOP, + toolbox.id + " contains additional backgrounds alignment properties" + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_alpha_accentcolor.js b/toolkit/components/extensions/test/browser/browser_ext_themes_alpha_accentcolor.js new file mode 100644 index 0000000000..65e3a6c9bf --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_alpha_accentcolor.js @@ -0,0 +1,34 @@ +"use strict"; + +add_task(async function test_alpha_frame_color() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: "rgba(230, 128, 0, 0.1)", + tab_background_text: TEXT_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + // Add the event listener before loading the extension + let docEl = window.document.documentElement; + let style = window.getComputedStyle(docEl); + + Assert.equal( + style.backgroundColor, + "rgb(230, 128, 0)", + "Window background color should be opaque" + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js b/toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js new file mode 100644 index 0000000000..e7024b0479 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js @@ -0,0 +1,99 @@ +"use strict"; + +function openIdentityPopup() { + let promise = BrowserTestUtils.waitForEvent( + window, + "popupshown", + true, + event => event.target == gIdentityHandler._identityPopup + ); + gIdentityHandler._identityBox.click(); + return promise; +} + +function closeIdentityPopup() { + let promise = BrowserTestUtils.waitForEvent( + gIdentityHandler._identityPopup, + "popuphidden" + ); + gIdentityHandler._identityPopup.hidePopup(); + return promise; +} + +// This test checks applied WebExtension themes that attempt to change +// popup properties + +add_task(async function test_popup_styling(browser, accDoc) { + const POPUP_BACKGROUND_COLOR = "#FF0000"; + const POPUP_TEXT_COLOR = "#008000"; + const POPUP_BORDER_COLOR = "#0000FF"; + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + popup: POPUP_BACKGROUND_COLOR, + popup_text: POPUP_TEXT_COLOR, + popup_border: POPUP_BORDER_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "https://example.com" }, + async function(browser) { + await extension.startup(); + + // Open the information arrow panel + await openIdentityPopup(); + + let arrowContent = gIdentityHandler._identityPopup.shadowRoot.querySelector( + ".panel-arrowcontent" + ); + let arrowContentComputedStyle = window.getComputedStyle(arrowContent); + // Ensure popup background color was set properly + Assert.equal( + arrowContentComputedStyle.getPropertyValue("background-color"), + `rgb(${hexToRGB(POPUP_BACKGROUND_COLOR).join(", ")})`, + "Popup background color should have been themed" + ); + + // Ensure popup text color was set properly + Assert.equal( + arrowContentComputedStyle.getPropertyValue("color"), + `rgb(${hexToRGB(POPUP_TEXT_COLOR).join(", ")})`, + "Popup text color should have been themed" + ); + + Assert.equal( + arrowContentComputedStyle.getPropertyValue("--panel-description-color"), + `rgba(${hexToRGB(POPUP_TEXT_COLOR).join(", ")}, 0.65)`, + "Popup text description color should have been themed" + ); + + // Ensure popup border color was set properly + if (AppConstants.platform == "macosx") { + Assert.ok( + arrowContentComputedStyle + .getPropertyValue("box-shadow") + .includes(`rgb(${hexToRGB(POPUP_BORDER_COLOR).join(", ")})`), + "Popup border color should be set" + ); + } else { + testBorderColor(arrowContent, POPUP_BORDER_COLOR); + } + + await closeIdentityPopup(); + await extension.unload(); + } + ); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js b/toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js new file mode 100644 index 0000000000..2c5a0123f4 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js @@ -0,0 +1,170 @@ +"use strict"; + +// This test checks whether applied WebExtension themes that attempt to change +// popup properties are applied correctly to the autocomplete bar. +const POPUP_COLOR = "#85A400"; +const POPUP_TEXT_COLOR_DARK = "#000000"; +const POPUP_TEXT_COLOR_BRIGHT = "#ffffff"; +const POPUP_SELECTED_COLOR = "#9400ff"; +const POPUP_SELECTED_TEXT_COLOR = "#09b9a6"; + +const POPUP_URL_COLOR_DARK = "#1c78d4"; +const POPUP_ACTION_COLOR_DARK = "#008f8a"; +const POPUP_URL_COLOR_BRIGHT = "#74c0ff"; +const POPUP_ACTION_COLOR_BRIGHT = "#30e60b"; + +const SEARCH_TERM = "urlbar-reflows-" + Date.now(); + +XPCOMUtils.defineLazyModuleGetters(this, { + PlacesTestUtils: "resource://testing-common/PlacesTestUtils.jsm", + UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.jsm", +}); + +add_task(async function setup() { + await PlacesUtils.history.clear(); + const NUM_VISITS = 10; + let visits = []; + + for (let i = 0; i < NUM_VISITS; ++i) { + visits.push({ + uri: `http://example.com/urlbar-reflows-${i}`, + title: `Reflow test for URL bar entry #${i} - ${SEARCH_TERM}`, + }); + } + + await PlacesTestUtils.addVisits(visits); + + registerCleanupFunction(async function() { + await PlacesUtils.history.clear(); + }); +}); + +add_task(async function test_popup_url() { + // Load extension with brighttext not set + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + toolbar_field_focus: POPUP_COLOR, + toolbar_field_text_focus: POPUP_TEXT_COLOR_DARK, + popup_highlight: POPUP_SELECTED_COLOR, + popup_highlight_text: POPUP_SELECTED_TEXT_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + let maxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults"); + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:mozilla" + ); + registerCleanupFunction(async function() { + await PlacesUtils.history.clear(); + await BrowserTestUtils.removeTab(tab); + }); + + let visits = []; + + for (let i = 0; i < maxResults; i++) { + visits.push({ uri: makeURI("http://example.com/autocomplete/?" + i) }); + } + + await PlacesTestUtils.addVisits(visits); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + waitForFocus, + value: "example.com/autocomplete", + }); + await UrlbarTestUtils.waitForAutocompleteResultAt(window, maxResults - 1); + + Assert.equal( + UrlbarTestUtils.getResultCount(window), + maxResults, + "Should get maxResults=" + maxResults + " results" + ); + + // Set the selected attribute to true to test the highlight popup properties + UrlbarTestUtils.setSelectedRowIndex(window, 1); + let actionResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0); + let urlResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 1); + let resultCS = window.getComputedStyle(urlResult.element.row._content); + + Assert.equal( + resultCS.backgroundColor, + `rgb(${hexToRGB(POPUP_SELECTED_COLOR).join(", ")})`, + `Popup highlight background color should be set to ${POPUP_SELECTED_COLOR}` + ); + + Assert.equal( + resultCS.color, + `rgb(${hexToRGB(POPUP_SELECTED_TEXT_COLOR).join(", ")})`, + `Popup highlight color should be set to ${POPUP_SELECTED_TEXT_COLOR}` + ); + + // Now set the index to somewhere not on the first two, so that we can test both + // url and action text colors. + UrlbarTestUtils.setSelectedRowIndex(window, 2); + + Assert.equal( + window.getComputedStyle(urlResult.element.url).color, + `rgb(${hexToRGB(POPUP_URL_COLOR_DARK).join(", ")})`, + `Urlbar popup url color should be set to ${POPUP_URL_COLOR_DARK}` + ); + + Assert.equal( + window.getComputedStyle(actionResult.element.action).color, + `rgb(${hexToRGB(POPUP_ACTION_COLOR_DARK).join(", ")})`, + `Urlbar popup action color should be set to ${POPUP_ACTION_COLOR_DARK}` + ); + + await extension.unload(); + + // Load a manifest with popup_text being bright. Test for bright text properties. + extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + toolbar_field_focus: POPUP_COLOR, + toolbar_field_text_focus: POPUP_TEXT_COLOR_BRIGHT, + popup_highlight: POPUP_SELECTED_COLOR, + popup_highlight_text: POPUP_SELECTED_TEXT_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + Assert.equal( + window.getComputedStyle(urlResult.element.url).color, + `rgb(${hexToRGB(POPUP_URL_COLOR_BRIGHT).join(", ")})`, + `Urlbar popup url color should be set to ${POPUP_URL_COLOR_BRIGHT}` + ); + + Assert.equal( + window.getComputedStyle(actionResult.element.action).color, + `rgb(${hexToRGB(POPUP_ACTION_COLOR_BRIGHT).join(", ")})`, + `Urlbar popup action color should be set to ${POPUP_ACTION_COLOR_BRIGHT}` + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_chromeparity.js b/toolkit/components/extensions/test/browser/browser_ext_themes_chromeparity.js new file mode 100644 index 0000000000..4366764a20 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_chromeparity.js @@ -0,0 +1,161 @@ +"use strict"; + +add_task(async function test_support_theme_frame() { + const FRAME_COLOR = [71, 105, 91]; + const TAB_TEXT_COLOR = [0, 0, 0]; + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "face.png", + }, + colors: { + frame: FRAME_COLOR, + tab_background_text: TAB_TEXT_COLOR, + }, + }, + }, + files: { + "face.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA), + }, + }); + + await extension.startup(); + + let docEl = window.document.documentElement; + Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set"); + + Assert.ok( + docEl.hasAttribute("lwtheme-image"), + "LWT image attribute should be set" + ); + + Assert.equal( + docEl.getAttribute("lwthemetextcolor"), + "dark", + "LWT text color attribute should be set" + ); + + let style = window.getComputedStyle(docEl); + Assert.ok( + style.backgroundImage.includes("face.png"), + `The backgroundImage should use face.png. Actual value is: ${style.backgroundImage}` + ); + Assert.equal( + style.backgroundColor, + "rgb(" + FRAME_COLOR.join(", ") + ")", + "Expected correct background color" + ); + Assert.equal( + style.color, + "rgb(" + TAB_TEXT_COLOR.join(", ") + ")", + "Expected correct text color" + ); + + await extension.unload(); + + Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set"); + + Assert.ok( + !docEl.hasAttribute("lwtheme-image"), + "LWT image attribute should not be set" + ); + + Assert.ok( + !docEl.hasAttribute("lwthemetextcolor"), + "LWT text color attribute should not be set" + ); +}); + +add_task(async function test_support_theme_frame_inactive() { + const FRAME_COLOR = [71, 105, 91]; + const FRAME_COLOR_INACTIVE = [255, 0, 0]; + const TAB_TEXT_COLOR = [207, 221, 192]; + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: FRAME_COLOR, + frame_inactive: FRAME_COLOR_INACTIVE, + tab_background_text: TAB_TEXT_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + let docEl = window.document.documentElement; + let style = window.getComputedStyle(docEl); + + Assert.equal( + style.backgroundColor, + "rgb(" + FRAME_COLOR.join(", ") + ")", + "Window background is set to the colors.frame property" + ); + + // Now we'll open a new window to see if the inactive browser accent color changed + let window2 = await BrowserTestUtils.openNewBrowserWindow(); + Assert.equal( + style.backgroundColor, + "rgb(" + FRAME_COLOR_INACTIVE.join(", ") + ")", + `Inactive window background color should be ${FRAME_COLOR_INACTIVE}` + ); + + await BrowserTestUtils.closeWindow(window2); + await extension.unload(); + + Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set"); +}); + +add_task(async function test_lack_of_theme_frame_inactive() { + const FRAME_COLOR = [71, 105, 91]; + const TAB_TEXT_COLOR = [207, 221, 192]; + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: FRAME_COLOR, + tab_background_text: TAB_TEXT_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + let docEl = window.document.documentElement; + let style = window.getComputedStyle(docEl); + + Assert.equal( + style.backgroundColor, + "rgb(" + FRAME_COLOR.join(", ") + ")", + "Window background is set to the colors.frame property" + ); + + // Now we'll open a new window to make sure the inactive browser accent color stayed the same + let window2 = await BrowserTestUtils.openNewBrowserWindow(); + + Assert.equal( + style.backgroundColor, + "rgb(" + FRAME_COLOR.join(", ") + ")", + "Inactive window background should not change if colors.frame_inactive isn't set" + ); + + await BrowserTestUtils.closeWindow(window2); + await extension.unload(); + + Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set"); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_getCurrent.js b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_getCurrent.js new file mode 100644 index 0000000000..4a379edfbf --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_getCurrent.js @@ -0,0 +1,203 @@ +"use strict"; + +// This test checks whether browser.theme.getCurrent() works correctly in different +// configurations and with different parameter. + +// PNG image data for a simple red dot. +const BACKGROUND_1 = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; +// PNG image data for the Mozilla dino head. +const BACKGROUND_2 = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="; + +add_task(async function test_get_current() { + let extension = ExtensionTestUtils.loadExtension({ + async background() { + const ACCENT_COLOR_1 = "#a14040"; + const TEXT_COLOR_1 = "#fac96e"; + + const ACCENT_COLOR_2 = "#03fe03"; + const TEXT_COLOR_2 = "#0ef325"; + + const theme1 = { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR_1, + tab_background_text: TEXT_COLOR_1, + }, + }; + + const theme2 = { + images: { + theme_frame: "image2.png", + }, + colors: { + frame: ACCENT_COLOR_2, + tab_background_text: TEXT_COLOR_2, + }, + }; + + function ensureWindowFocused(winId) { + browser.test.log("Waiting for focused window to be " + winId); + // eslint-disable-next-line no-async-promise-executor + return new Promise(async resolve => { + let listener = windowId => { + if (windowId === winId) { + browser.windows.onFocusChanged.removeListener(listener); + resolve(); + } + }; + // We first add a listener and then check whether the window is + // focused using .get(), because the .get() Promise resolving + // could race with the listener running, in which case we'd + // never be notified. + browser.windows.onFocusChanged.addListener(listener); + let { focused } = await browser.windows.get(winId); + if (focused) { + browser.windows.onFocusChanged.removeListener(listener); + resolve(); + } + }); + } + + function testTheme1(returnedTheme) { + browser.test.assertTrue( + returnedTheme.images.theme_frame.includes("image1.png"), + "Theme 1 theme_frame image should be applied" + ); + browser.test.assertEq( + ACCENT_COLOR_1, + returnedTheme.colors.frame, + "Theme 1 frame color should be applied" + ); + browser.test.assertEq( + TEXT_COLOR_1, + returnedTheme.colors.tab_background_text, + "Theme 1 tab_background_text color should be applied" + ); + } + + function testTheme2(returnedTheme) { + browser.test.assertTrue( + returnedTheme.images.theme_frame.includes("image2.png"), + "Theme 2 theme_frame image should be applied" + ); + browser.test.assertEq( + ACCENT_COLOR_2, + returnedTheme.colors.frame, + "Theme 2 frame color should be applied" + ); + browser.test.assertEq( + TEXT_COLOR_2, + returnedTheme.colors.tab_background_text, + "Theme 2 tab_background_text color should be applied" + ); + } + + function testEmptyTheme(returnedTheme) { + browser.test.assertEq( + JSON.stringify({ colors: null, images: null, properties: null }), + JSON.stringify(returnedTheme), + JSON.stringify(returnedTheme, null, 2) + ); + } + + browser.test.log("Testing getCurrent() with initial unthemed window"); + const firstWin = await browser.windows.getCurrent(); + testEmptyTheme(await browser.theme.getCurrent()); + testEmptyTheme(await browser.theme.getCurrent(firstWin.id)); + + browser.test.log("Testing getCurrent() with after theme.update()"); + await browser.theme.update(theme1); + testTheme1(await browser.theme.getCurrent()); + testTheme1(await browser.theme.getCurrent(firstWin.id)); + + browser.test.log( + "Testing getCurrent() with after theme.update(windowId)" + ); + const secondWin = await browser.windows.create(); + await ensureWindowFocused(secondWin.id); + await browser.theme.update(secondWin.id, theme2); + testTheme2(await browser.theme.getCurrent()); + testTheme1(await browser.theme.getCurrent(firstWin.id)); + testTheme2(await browser.theme.getCurrent(secondWin.id)); + + browser.test.log("Testing getCurrent() after window focus change"); + let focusChanged = ensureWindowFocused(firstWin.id); + await browser.windows.update(firstWin.id, { focused: true }); + await focusChanged; + testTheme1(await browser.theme.getCurrent()); + testTheme1(await browser.theme.getCurrent(firstWin.id)); + testTheme2(await browser.theme.getCurrent(secondWin.id)); + + browser.test.log( + "Testing getCurrent() after another window focus change" + ); + focusChanged = ensureWindowFocused(secondWin.id); + await browser.windows.update(secondWin.id, { focused: true }); + await focusChanged; + testTheme2(await browser.theme.getCurrent()); + testTheme1(await browser.theme.getCurrent(firstWin.id)); + testTheme2(await browser.theme.getCurrent(secondWin.id)); + + browser.test.log("Testing getCurrent() after theme.reset(windowId)"); + await browser.theme.reset(firstWin.id); + testTheme2(await browser.theme.getCurrent()); + testTheme1(await browser.theme.getCurrent(firstWin.id)); + testTheme2(await browser.theme.getCurrent(secondWin.id)); + + browser.test.log( + "Testing getCurrent() after reset and window focus change" + ); + focusChanged = ensureWindowFocused(firstWin.id); + await browser.windows.update(firstWin.id, { focused: true }); + await focusChanged; + testTheme1(await browser.theme.getCurrent()); + testTheme1(await browser.theme.getCurrent(firstWin.id)); + testTheme2(await browser.theme.getCurrent(secondWin.id)); + + browser.test.log("Testing getCurrent() after theme.update(windowId)"); + await browser.theme.update(firstWin.id, theme1); + testTheme1(await browser.theme.getCurrent()); + testTheme1(await browser.theme.getCurrent(firstWin.id)); + testTheme2(await browser.theme.getCurrent(secondWin.id)); + + browser.test.log("Testing getCurrent() after theme.reset()"); + await browser.theme.reset(); + testEmptyTheme(await browser.theme.getCurrent()); + testEmptyTheme(await browser.theme.getCurrent(firstWin.id)); + testEmptyTheme(await browser.theme.getCurrent(secondWin.id)); + + browser.test.log("Testing getCurrent() after closing a window"); + await browser.windows.remove(secondWin.id); + testEmptyTheme(await browser.theme.getCurrent()); + testEmptyTheme(await browser.theme.getCurrent(firstWin.id)); + + browser.test.log("Testing update calls with invalid window ID"); + await browser.test.assertRejects( + browser.theme.reset(secondWin.id), + /Invalid window/, + "Invalid window should throw" + ); + await browser.test.assertRejects( + browser.theme.update(secondWin.id, theme2), + /Invalid window/, + "Invalid window should throw" + ); + browser.test.notifyPass("get_current"); + }, + manifest: { + permissions: ["theme"], + }, + files: { + "image1.png": BACKGROUND_1, + "image2.png": BACKGROUND_2, + }, + }); + + await extension.startup(); + await extension.awaitFinish("get_current"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_onUpdated.js b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_onUpdated.js new file mode 100644 index 0000000000..34c7162810 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_onUpdated.js @@ -0,0 +1,154 @@ +"use strict"; + +// This test checks whether browser.theme.onUpdated works correctly with different +// types of dynamic theme updates. + +// PNG image data for a simple red dot. +const BACKGROUND_1 = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; +// PNG image data for the Mozilla dino head. +const BACKGROUND_2 = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="; + +add_task(async function test_on_updated() { + let extension = ExtensionTestUtils.loadExtension({ + async background() { + const ACCENT_COLOR_1 = "#a14040"; + const TEXT_COLOR_1 = "#fac96e"; + + const ACCENT_COLOR_2 = "#03fe03"; + const TEXT_COLOR_2 = "#0ef325"; + + const theme1 = { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR_1, + tab_background_text: TEXT_COLOR_1, + }, + }; + + const theme2 = { + images: { + theme_frame: "image2.png", + }, + colors: { + frame: ACCENT_COLOR_2, + tab_background_text: TEXT_COLOR_2, + }, + }; + + function testTheme1(returnedTheme) { + browser.test.assertTrue( + returnedTheme.images.theme_frame.includes("image1.png"), + "Theme 1 theme_frame image should be applied" + ); + browser.test.assertEq( + ACCENT_COLOR_1, + returnedTheme.colors.frame, + "Theme 1 frame color should be applied" + ); + browser.test.assertEq( + TEXT_COLOR_1, + returnedTheme.colors.tab_background_text, + "Theme 1 tab_background_text color should be applied" + ); + } + + function testTheme2(returnedTheme) { + browser.test.assertTrue( + returnedTheme.images.theme_frame.includes("image2.png"), + "Theme 2 theme_frame image should be applied" + ); + browser.test.assertEq( + ACCENT_COLOR_2, + returnedTheme.colors.frame, + "Theme 2 frame color should be applied" + ); + browser.test.assertEq( + TEXT_COLOR_2, + returnedTheme.colors.tab_background_text, + "Theme 2 tab_background_text color should be applied" + ); + } + + const firstWin = await browser.windows.getCurrent(); + const secondWin = await browser.windows.create(); + + const onceThemeUpdated = () => + new Promise(resolve => { + const listener = updateInfo => { + browser.theme.onUpdated.removeListener(listener); + resolve(updateInfo); + }; + browser.theme.onUpdated.addListener(listener); + }); + + browser.test.log("Testing update with no windowId parameter"); + let updateInfo1 = onceThemeUpdated(); + await browser.theme.update(theme1); + updateInfo1 = await updateInfo1; + testTheme1(updateInfo1.theme); + browser.test.assertTrue( + !updateInfo1.windowId, + "No window id on first update" + ); + + browser.test.log("Testing update with windowId parameter"); + let updateInfo2 = onceThemeUpdated(); + await browser.theme.update(secondWin.id, theme2); + updateInfo2 = await updateInfo2; + testTheme2(updateInfo2.theme); + browser.test.assertEq( + secondWin.id, + updateInfo2.windowId, + "window id on second update" + ); + + browser.test.log("Testing reset with windowId parameter"); + let updateInfo3 = onceThemeUpdated(); + await browser.theme.reset(firstWin.id); + updateInfo3 = await updateInfo3; + browser.test.assertEq( + 0, + Object.keys(updateInfo3.theme).length, + "Empty theme given on reset" + ); + browser.test.assertEq( + firstWin.id, + updateInfo3.windowId, + "window id on third update" + ); + + browser.test.log("Testing reset with no windowId parameter"); + let updateInfo4 = onceThemeUpdated(); + await browser.theme.reset(); + updateInfo4 = await updateInfo4; + browser.test.assertEq( + 0, + Object.keys(updateInfo4.theme).length, + "Empty theme given on reset" + ); + browser.test.assertTrue( + !updateInfo4.windowId, + "no window id on fourth update" + ); + + browser.test.log("Cleaning up test"); + await browser.windows.remove(secondWin.id); + browser.test.notifyPass("onUpdated"); + }, + manifest: { + permissions: ["theme"], + }, + files: { + "image1.png": BACKGROUND_1, + "image2.png": BACKGROUND_2, + }, + }); + + await extension.startup(); + await extension.awaitFinish("onUpdated"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js new file mode 100644 index 0000000000..34e719262d --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js @@ -0,0 +1,185 @@ +"use strict"; + +// PNG image data for a simple red dot. +const BACKGROUND_1 = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; +const ACCENT_COLOR_1 = "#a14040"; +const TEXT_COLOR_1 = "#fac96e"; + +// PNG image data for the Mozilla dino head. +const BACKGROUND_2 = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="; +const ACCENT_COLOR_2 = "#03fe03"; +const TEXT_COLOR_2 = "#0ef325"; + +function hexToRGB(hex) { + hex = parseInt(hex.indexOf("#") > -1 ? hex.substring(1) : hex, 16); + return ( + "rgb(" + [hex >> 16, (hex & 0x00ff00) >> 8, hex & 0x0000ff].join(", ") + ")" + ); +} + +function validateTheme(backgroundImage, accentColor, textColor, isLWT) { + let docEl = window.document.documentElement; + let style = window.getComputedStyle(docEl); + + if (isLWT) { + Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set"); + Assert.equal( + docEl.getAttribute("lwthemetextcolor"), + "bright", + "LWT text color attribute should be set" + ); + } + + Assert.ok( + style.backgroundImage.includes(backgroundImage), + "Expected correct background image" + ); + if (accentColor.startsWith("#")) { + accentColor = hexToRGB(accentColor); + } + if (textColor.startsWith("#")) { + textColor = hexToRGB(textColor); + } + Assert.equal( + style.backgroundColor, + accentColor, + "Expected correct accent color" + ); + Assert.equal(style.color, textColor, "Expected correct text color"); +} + +add_task(async function test_dynamic_theme_updates() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["theme"], + }, + files: { + "image1.png": BACKGROUND_1, + "image2.png": BACKGROUND_2, + }, + background() { + browser.test.onMessage.addListener((msg, details) => { + if (msg === "update-theme") { + browser.theme.update(details).then(() => { + browser.test.sendMessage("theme-updated"); + }); + } else { + browser.theme.reset().then(() => { + browser.test.sendMessage("theme-reset"); + }); + } + }); + }, + }); + + let defaultStyle = window.getComputedStyle(window.document.documentElement); + await extension.startup(); + + extension.sendMessage("update-theme", { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR_1, + tab_background_text: TEXT_COLOR_1, + }, + }); + + await extension.awaitMessage("theme-updated"); + + validateTheme("image1.png", ACCENT_COLOR_1, TEXT_COLOR_1, true); + + // Check with the LWT aliases (to update on Firefox 69, because the + // LWT aliases are going to be removed). + extension.sendMessage("update-theme", { + images: { + theme_frame: "image2.png", + }, + colors: { + frame: ACCENT_COLOR_2, + tab_background_text: TEXT_COLOR_2, + }, + }); + + await extension.awaitMessage("theme-updated"); + + validateTheme("image2.png", ACCENT_COLOR_2, TEXT_COLOR_2, true); + + extension.sendMessage("reset-theme"); + + await extension.awaitMessage("theme-reset"); + + let { backgroundImage, backgroundColor, color } = defaultStyle; + validateTheme(backgroundImage, backgroundColor, color, false); + + await extension.unload(); + + let docEl = window.document.documentElement; + Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set"); +}); + +add_task(async function test_dynamic_theme_updates_with_data_url() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["theme"], + }, + background() { + browser.test.onMessage.addListener((msg, details) => { + if (msg === "update-theme") { + browser.theme.update(details).then(() => { + browser.test.sendMessage("theme-updated"); + }); + } else { + browser.theme.reset().then(() => { + browser.test.sendMessage("theme-reset"); + }); + } + }); + }, + }); + + let defaultStyle = window.getComputedStyle(window.document.documentElement); + await extension.startup(); + + extension.sendMessage("update-theme", { + images: { + theme_frame: BACKGROUND_1, + }, + colors: { + frame: ACCENT_COLOR_1, + tab_background_text: TEXT_COLOR_1, + }, + }); + + await extension.awaitMessage("theme-updated"); + + validateTheme(BACKGROUND_1, ACCENT_COLOR_1, TEXT_COLOR_1, true); + + extension.sendMessage("update-theme", { + images: { + theme_frame: BACKGROUND_2, + }, + colors: { + frame: ACCENT_COLOR_2, + tab_background_text: TEXT_COLOR_2, + }, + }); + + await extension.awaitMessage("theme-updated"); + + validateTheme(BACKGROUND_2, ACCENT_COLOR_2, TEXT_COLOR_2, true); + + extension.sendMessage("reset-theme"); + + await extension.awaitMessage("theme-reset"); + + let { backgroundImage, backgroundColor, color } = defaultStyle; + validateTheme(backgroundImage, backgroundColor, color, false); + + await extension.unload(); + + let docEl = window.document.documentElement; + Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set"); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_experiment.js b/toolkit/components/extensions/test/browser/browser_ext_themes_experiment.js new file mode 100644 index 0000000000..7b362498e7 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_experiment.js @@ -0,0 +1,401 @@ +"use strict"; + +const { AddonSettings } = ChromeUtils.import( + "resource://gre/modules/addons/AddonSettings.jsm" +); + +// This test checks whether the theme experiments work +add_task(async function test_experiment_static_theme() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + some_color_property: "#ff00ff", + }, + images: { + some_image_property: "background.jpg", + }, + properties: { + some_random_property: "no-repeat", + }, + }, + theme_experiment: { + colors: { + some_color_property: "--some-color-property", + }, + images: { + some_image_property: "--some-image-property", + }, + properties: { + some_random_property: "--some-random-property", + }, + }, + }, + }); + + const root = window.document.documentElement; + + is( + root.style.getPropertyValue("--some-color-property"), + "", + "Color property should be unset" + ); + is( + root.style.getPropertyValue("--some-image-property"), + "", + "Image property should be unset" + ); + is( + root.style.getPropertyValue("--some-random-property"), + "", + "Generic Property should be unset." + ); + + await extension.startup(); + + const testExperimentApplied = rootEl => { + if (AddonSettings.EXPERIMENTS_ENABLED) { + is( + rootEl.style.getPropertyValue("--some-color-property"), + hexToCSS("#ff00ff"), + "Color property should be parsed and set." + ); + ok( + rootEl.style + .getPropertyValue("--some-image-property") + .startsWith("url("), + "Image property should be parsed." + ); + ok( + rootEl.style + .getPropertyValue("--some-image-property") + .endsWith("background.jpg)"), + "Image property should be set." + ); + is( + rootEl.style.getPropertyValue("--some-random-property"), + "no-repeat", + "Generic Property should be set." + ); + } else { + is( + rootEl.style.getPropertyValue("--some-color-property"), + "", + "Color property should be unset" + ); + is( + rootEl.style.getPropertyValue("--some-image-property"), + "", + "Image property should be unset" + ); + is( + rootEl.style.getPropertyValue("--some-random-property"), + "", + "Generic Property should be unset." + ); + } + }; + + info("Testing that current window updated with the experiment applied"); + testExperimentApplied(root); + + info("Testing that new window initialized with the experiment applied"); + const newWindow = await BrowserTestUtils.openNewBrowserWindow(); + const newWindowRoot = newWindow.document.documentElement; + testExperimentApplied(newWindowRoot); + + await extension.unload(); + + info("Testing that both windows unapplied the experiment"); + for (const rootEl of [root, newWindowRoot]) { + is( + rootEl.style.getPropertyValue("--some-color-property"), + "", + "Color property should be unset" + ); + is( + rootEl.style.getPropertyValue("--some-image-property"), + "", + "Image property should be unset" + ); + is( + rootEl.style.getPropertyValue("--some-random-property"), + "", + "Generic Property should be unset." + ); + } + await BrowserTestUtils.closeWindow(newWindow); +}); + +add_task(async function test_experiment_dynamic_theme() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["theme"], + theme_experiment: { + colors: { + some_color_property: "--some-color-property", + }, + images: { + some_image_property: "--some-image-property", + }, + properties: { + some_random_property: "--some-random-property", + }, + }, + }, + background() { + const theme = { + colors: { + some_color_property: "#ff00ff", + }, + images: { + some_image_property: "background.jpg", + }, + properties: { + some_random_property: "no-repeat", + }, + }; + browser.test.onMessage.addListener(msg => { + if (msg === "update-theme") { + browser.theme.update(theme).then(() => { + browser.test.sendMessage("theme-updated"); + }); + } else { + browser.theme.reset().then(() => { + browser.test.sendMessage("theme-reset"); + }); + } + }); + }, + }); + + await extension.startup(); + + const root = window.document.documentElement; + + is( + root.style.getPropertyValue("--some-color-property"), + "", + "Color property should be unset" + ); + is( + root.style.getPropertyValue("--some-image-property"), + "", + "Image property should be unset" + ); + is( + root.style.getPropertyValue("--some-random-property"), + "", + "Generic Property should be unset." + ); + + extension.sendMessage("update-theme"); + await extension.awaitMessage("theme-updated"); + + const testExperimentApplied = rootEl => { + if (AddonSettings.EXPERIMENTS_ENABLED) { + is( + rootEl.style.getPropertyValue("--some-color-property"), + hexToCSS("#ff00ff"), + "Color property should be parsed and set." + ); + ok( + rootEl.style + .getPropertyValue("--some-image-property") + .startsWith("url("), + "Image property should be parsed." + ); + ok( + rootEl.style + .getPropertyValue("--some-image-property") + .endsWith("background.jpg)"), + "Image property should be set." + ); + is( + rootEl.style.getPropertyValue("--some-random-property"), + "no-repeat", + "Generic Property should be set." + ); + } else { + is( + rootEl.style.getPropertyValue("--some-color-property"), + "", + "Color property should be unset" + ); + is( + rootEl.style.getPropertyValue("--some-image-property"), + "", + "Image property should be unset" + ); + is( + rootEl.style.getPropertyValue("--some-random-property"), + "", + "Generic Property should be unset." + ); + } + }; + testExperimentApplied(root); + + const newWindow = await BrowserTestUtils.openNewBrowserWindow(); + const newWindowRoot = newWindow.document.documentElement; + + testExperimentApplied(newWindowRoot); + + extension.sendMessage("reset-theme"); + await extension.awaitMessage("theme-reset"); + + for (const rootEl of [root, newWindowRoot]) { + is( + rootEl.style.getPropertyValue("--some-color-property"), + "", + "Color property should be unset" + ); + is( + rootEl.style.getPropertyValue("--some-image-property"), + "", + "Image property should be unset" + ); + is( + rootEl.style.getPropertyValue("--some-random-property"), + "", + "Generic Property should be unset." + ); + } + + extension.sendMessage("update-theme"); + await extension.awaitMessage("theme-updated"); + + testExperimentApplied(root); + testExperimentApplied(newWindowRoot); + + await extension.unload(); + + for (const rootEl of [root, newWindowRoot]) { + is( + rootEl.style.getPropertyValue("--some-color-property"), + "", + "Color property should be unset" + ); + is( + rootEl.style.getPropertyValue("--some-image-property"), + "", + "Image property should be unset" + ); + is( + rootEl.style.getPropertyValue("--some-random-property"), + "", + "Generic Property should be unset." + ); + } + + await BrowserTestUtils.closeWindow(newWindow); +}); + +add_task(async function test_experiment_stylesheet() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + menu_button_background: "#ff00ff", + }, + }, + theme_experiment: { + stylesheet: "experiment.css", + colors: { + menu_button_background: "--menu-button-background", + }, + }, + }, + files: { + "experiment.css": `#PanelUI-menu-button { + background-color: var(--menu-button-background); + fill: white; + }`, + }, + }); + + const root = window.document.documentElement; + const menuButton = document.getElementById("PanelUI-menu-button"); + const computedStyle = window.getComputedStyle(menuButton); + const expectedColor = hexToCSS("#ff00ff"); + const expectedFill = hexToCSS("#ffffff"); + + is( + root.style.getPropertyValue("--menu-button-background"), + "", + "Variable should be unset" + ); + isnot( + computedStyle.backgroundColor, + expectedColor, + "Menu button should not have custom background" + ); + isnot( + computedStyle.fill, + expectedFill, + "Menu button should not have stylesheet fill" + ); + + await extension.startup(); + + if (AddonSettings.EXPERIMENTS_ENABLED) { + // Wait for stylesheet load. + await BrowserTestUtils.waitForCondition( + () => computedStyle.fill === expectedFill + ); + + is( + root.style.getPropertyValue("--menu-button-background"), + expectedColor, + "Variable should be parsed and set." + ); + is( + computedStyle.backgroundColor, + expectedColor, + "Menu button should be have correct background" + ); + is( + computedStyle.fill, + expectedFill, + "Menu button should be have correct fill" + ); + } else { + is( + root.style.getPropertyValue("--menu-button-background"), + "", + "Variable should be unset" + ); + isnot( + computedStyle.backgroundColor, + expectedColor, + "Menu button should not have custom background" + ); + isnot( + computedStyle.fill, + expectedFill, + "Menu button should not have stylesheet fill" + ); + } + + await extension.unload(); + + is( + root.style.getPropertyValue("--menu-button-background"), + "", + "Variable should be unset" + ); + isnot( + computedStyle.backgroundColor, + expectedColor, + "Menu button should not have custom background" + ); + isnot( + computedStyle.fill, + expectedFill, + "Menu button should not have stylesheet fill" + ); +}); + +add_task(async function cleanup() { + await SpecialPowers.popPrefEnv(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_findbar.js b/toolkit/components/extensions/test/browser/browser_ext_themes_findbar.js new file mode 100644 index 0000000000..fe9689bc74 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_findbar.js @@ -0,0 +1,217 @@ +"use strict"; + +// This test checks whether applied WebExtension themes that attempt to change +// the toolbar and toolbar_field properties also theme the findbar. + +add_task(async function test_support_toolbar_properties_on_findbar() { + const TOOLBAR_COLOR = "#ff00ff"; + const TOOLBAR_TEXT_COLOR = "#9400ff"; + const ACCENT_COLOR_INACTIVE = "#ffff00"; + // The TabContextMenu initializes its strings only on a focus or mouseover event. + // Calls focus event on the TabContextMenu early in the test. + gBrowser.selectedTab.focus(); + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: ACCENT_COLOR, + frame_inactive: ACCENT_COLOR_INACTIVE, + tab_background_text: TEXT_COLOR, + toolbar: TOOLBAR_COLOR, + bookmark_text: TOOLBAR_TEXT_COLOR, + }, + }, + }, + }); + + await extension.startup(); + await gBrowser.getFindBar(); + + let findbar_button = gFindBar.getElement("highlight"); + + info("Checking findbar background is set as toolbar color"); + Assert.equal( + window.getComputedStyle(gFindBar).backgroundColor, + hexToCSS(ACCENT_COLOR), + "Findbar background color should be the same as toolbar background color." + ); + + info("Checking findbar and button text color is set as toolbar text color"); + Assert.equal( + window.getComputedStyle(gFindBar).color, + hexToCSS(TOOLBAR_TEXT_COLOR), + "Findbar text color should be the same as toolbar text color." + ); + Assert.equal( + window.getComputedStyle(findbar_button).color, + hexToCSS(TOOLBAR_TEXT_COLOR), + "Findbar button text color should be the same as toolbar text color." + ); + + // Open a new window to check frame_inactive + let window2 = await BrowserTestUtils.openNewBrowserWindow(); + Assert.equal( + window.getComputedStyle(gFindBar).backgroundColor, + hexToCSS(ACCENT_COLOR_INACTIVE), + "Findbar background changed in inactive window." + ); + await BrowserTestUtils.closeWindow(window2); + + await extension.unload(); +}); + +add_task(async function test_support_toolbar_field_properties_on_findbar() { + const TOOLBAR_FIELD_COLOR = "#ff00ff"; + const TOOLBAR_FIELD_TEXT_COLOR = "#9400ff"; + const TOOLBAR_FIELD_BORDER_COLOR = "#ffffff"; + // The TabContextMenu initializes its strings only on a focus or mouseover event. + // Calls focus event on the TabContextMenu early in the test. + gBrowser.selectedTab.focus(); + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + toolbar_field: TOOLBAR_FIELD_COLOR, + toolbar_field_text: TOOLBAR_FIELD_TEXT_COLOR, + toolbar_field_border: TOOLBAR_FIELD_BORDER_COLOR, + }, + }, + }, + }); + + await extension.startup(); + await gBrowser.getFindBar(); + + let findbar_textbox = gFindBar.getElement("findbar-textbox"); + + let findbar_prev_button = gFindBar.getElement("find-previous"); + + let findbar_next_button = gFindBar.getElement("find-next"); + + info( + "Checking findbar textbox background is set as toolbar field background color" + ); + Assert.equal( + window.getComputedStyle(findbar_textbox).backgroundColor, + hexToCSS(TOOLBAR_FIELD_COLOR), + "Findbar textbox background color should be the same as toolbar field color." + ); + + info("Checking findbar textbox color is set as toolbar field text color"); + Assert.equal( + window.getComputedStyle(findbar_textbox).color, + hexToCSS(TOOLBAR_FIELD_TEXT_COLOR), + "Findbar textbox text color should be the same as toolbar field text color." + ); + testBorderColor(findbar_textbox, TOOLBAR_FIELD_BORDER_COLOR); + testBorderColor(findbar_prev_button, TOOLBAR_FIELD_BORDER_COLOR); + testBorderColor(findbar_next_button, TOOLBAR_FIELD_BORDER_COLOR); + + await extension.unload(); +}); + +// Test that theme properties are *not* applied with a theme_frame (see bug 1506913) +add_task(async function test_toolbar_properties_on_findbar_with_theme_frame() { + const TOOLBAR_COLOR = "#ff00ff"; + const TOOLBAR_TEXT_COLOR = "#9400ff"; + // The TabContextMenu initializes its strings only on a focus or mouseover event. + // Calls focus event on the TabContextMenu early in the test. + gBrowser.selectedTab.focus(); + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + toolbar: TOOLBAR_COLOR, + bookmark_text: TOOLBAR_TEXT_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + await gBrowser.getFindBar(); + + let findbar_button = gFindBar.getElement("highlight"); + + info("Checking findbar background is *not* set as toolbar color"); + Assert.notEqual( + window.getComputedStyle(gFindBar).backgroundColor, + hexToCSS(ACCENT_COLOR), + "Findbar background color should not be set by theme." + ); + + info( + "Checking findbar and button text color is *not* set as toolbar text color" + ); + Assert.notEqual( + window.getComputedStyle(gFindBar).color, + hexToCSS(TOOLBAR_TEXT_COLOR), + "Findbar text color should not be set by theme." + ); + Assert.notEqual( + window.getComputedStyle(findbar_button).color, + hexToCSS(TOOLBAR_TEXT_COLOR), + "Findbar button text color should not be set by theme." + ); + + await extension.unload(); +}); + +add_task( + async function test_toolbar_field_properties_on_findbar_with_theme_frame() { + const TOOLBAR_FIELD_COLOR = "#ff00ff"; + const TOOLBAR_FIELD_TEXT_COLOR = "#9400ff"; + const TOOLBAR_FIELD_BORDER_COLOR = "#ffffff"; + // The TabContextMenu initializes its strings only on a focus or mouseover event. + // Calls focus event on the TabContextMenu early in the test. + gBrowser.selectedTab.focus(); + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + toolbar_field: TOOLBAR_FIELD_COLOR, + toolbar_field_text: TOOLBAR_FIELD_TEXT_COLOR, + toolbar_field_border: TOOLBAR_FIELD_BORDER_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + await gBrowser.getFindBar(); + + let findbar_textbox = gFindBar.getElement("findbar-textbox"); + + Assert.notEqual( + window.getComputedStyle(findbar_textbox).backgroundColor, + hexToCSS(TOOLBAR_FIELD_COLOR), + "Findbar textbox background color should not be set by theme." + ); + + Assert.notEqual( + window.getComputedStyle(findbar_textbox).color, + hexToCSS(TOOLBAR_FIELD_TEXT_COLOR), + "Findbar textbox text color should not be set by theme." + ); + + await extension.unload(); + } +); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_getCurrent_differentExt.js b/toolkit/components/extensions/test/browser/browser_ext_themes_getCurrent_differentExt.js new file mode 100644 index 0000000000..981f32d7fb --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_getCurrent_differentExt.js @@ -0,0 +1,66 @@ +"use strict"; + +// This test checks whether browser.theme.getCurrent() works correctly when theme +// does not originate from extension querying the theme. + +add_task(async function test_getcurrent() { + const theme = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + const extension = ExtensionTestUtils.loadExtension({ + background() { + browser.theme.onUpdated.addListener(() => { + browser.theme.getCurrent().then(theme => { + browser.test.sendMessage("theme-updated", theme); + }); + }); + }, + }); + + await extension.startup(); + + info("Testing getCurrent after static theme startup"); + let updatedPromise = extension.awaitMessage("theme-updated"); + await theme.startup(); + let receivedTheme = await updatedPromise; + Assert.ok( + receivedTheme.images.theme_frame.includes("image1.png"), + "getCurrent returns correct theme_frame image" + ); + Assert.equal( + receivedTheme.colors.frame, + ACCENT_COLOR, + "getCurrent returns correct frame color" + ); + Assert.equal( + receivedTheme.colors.tab_background_text, + TEXT_COLOR, + "getCurrent returns correct tab_background_text color" + ); + + info("Testing getCurrent after static theme unload"); + updatedPromise = extension.awaitMessage("theme-updated"); + await theme.unload(); + receivedTheme = await updatedPromise; + Assert.equal( + JSON.stringify({ colors: null, images: null, properties: null }), + JSON.stringify(receivedTheme), + "getCurrent returns empty theme" + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_highlight.js b/toolkit/components/extensions/test/browser/browser_ext_themes_highlight.js new file mode 100644 index 0000000000..083eb85486 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_highlight.js @@ -0,0 +1,61 @@ +"use strict"; + +// This test checks whether applied WebExtension themes that attempt to change +// the color of the font and background in a selection are applied properly. +ChromeUtils.import( + "resource://testing-common/CustomizableUITestUtils.jsm", + this +); +let gCUITestUtils = new CustomizableUITestUtils(window); +add_task(async function setup() { + await gCUITestUtils.addSearchBar(); + registerCleanupFunction(() => { + gCUITestUtils.removeSearchBar(); + }); +}); + +add_task(async function test_support_selection() { + const HIGHLIGHT_TEXT_COLOR = "#9400FF"; + const HIGHLIGHT_COLOR = "#F89919"; + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + toolbar_field_highlight: HIGHLIGHT_COLOR, + toolbar_field_highlight_text: HIGHLIGHT_TEXT_COLOR, + }, + }, + }, + }); + + await extension.startup(); + + let fields = [ + gURLBar.inputField, + document.querySelector("#searchbar .searchbar-textbox"), + ].filter(field => { + let bounds = field.getBoundingClientRect(); + return bounds.width > 0 && bounds.height > 0; + }); + + Assert.equal(fields.length, 2, "Should be testing two elements"); + + info( + `Checking background colors and colors for ${fields.length} toolbar input fields.` + ); + for (let field of fields) { + info(`Testing ${field.id || field.className}`); + Assert.equal( + window.getComputedStyle(field, "::selection").backgroundColor, + hexToCSS(HIGHLIGHT_COLOR), + "Input selection background should be set." + ); + Assert.equal( + window.getComputedStyle(field, "::selection").color, + hexToCSS(HIGHLIGHT_TEXT_COLOR), + "Input selection color should be set." + ); + } + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_incognito.js b/toolkit/components/extensions/test/browser/browser_ext_themes_incognito.js new file mode 100644 index 0000000000..4917f6f830 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_incognito.js @@ -0,0 +1,81 @@ +"use strict"; + +add_task(async function test_theme_incognito_not_allowed() { + await SpecialPowers.pushPrefEnv({ + set: [["extensions.allowPrivateBrowsingByDefault", false]], + }); + + let windowExtension = ExtensionTestUtils.loadExtension({ + incognitoOverride: "spanning", + async background() { + const theme = { + colors: { + frame: "black", + tab_background_text: "black", + }, + }; + let window = await browser.windows.create({ incognito: true }); + browser.test.onMessage.addListener(async message => { + if (message == "update") { + browser.theme.update(window.id, theme); + return; + } + await browser.windows.remove(window.id); + browser.test.sendMessage("done"); + }); + browser.test.sendMessage("ready", window.id); + }, + manifest: { + permissions: ["theme"], + }, + }); + await windowExtension.startup(); + let wId = await windowExtension.awaitMessage("ready"); + + async function background(windowId) { + const theme = { + colors: { + frame: "black", + tab_background_text: "black", + }, + }; + + browser.theme.onUpdated.addListener(info => { + browser.test.log("got theme onChanged"); + browser.test.fail("theme"); + }); + await browser.test.assertRejects( + browser.theme.getCurrent(windowId), + /Invalid window ID/, + "API should reject getting window theme" + ); + await browser.test.assertRejects( + browser.theme.update(windowId, theme), + /Invalid window ID/, + "API should reject updating theme" + ); + await browser.test.assertRejects( + browser.theme.reset(windowId), + /Invalid window ID/, + "API should reject reseting theme on window" + ); + + browser.test.sendMessage("start"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${background})(${wId})`, + manifest: { + permissions: ["theme"], + }, + }); + + await extension.startup(); + await extension.awaitMessage("start"); + windowExtension.sendMessage("update"); + + windowExtension.sendMessage("close"); + await windowExtension.awaitMessage("done"); + await windowExtension.unload(); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_lwtsupport.js b/toolkit/components/extensions/test/browser/browser_ext_themes_lwtsupport.js new file mode 100644 index 0000000000..af2eef6ffb --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_lwtsupport.js @@ -0,0 +1,57 @@ +"use strict"; + +const DEFAULT_THEME_BG_COLOR = "rgb(255, 255, 255)"; +const DEFAULT_THEME_TEXT_COLOR = "rgb(0, 0, 0)"; + +add_task(async function test_deprecated_LWT_properties_ignored() { + // This test uses deprecated theme properties, so warnings are expected. + ExtensionTestUtils.failOnSchemaWarnings(false); + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + headerURL: "image1.png", + }, + colors: { + accentcolor: ACCENT_COLOR, + textcolor: TEXT_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + let docEl = window.document.documentElement; + let style = window.getComputedStyle(docEl); + + Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set"); + Assert.ok( + !docEl.hasAttribute("lwtheme-image"), + "LWT image attribute should not be set on deprecated headerURL alias" + ); + Assert.equal( + docEl.getAttribute("lwthemetextcolor"), + "dark", + "LWT text color attribute should not be set on deprecated textcolor alias" + ); + + Assert.equal( + style.backgroundColor, + DEFAULT_THEME_BG_COLOR, + "Expected default theme background color" + ); + Assert.equal( + style.color, + DEFAULT_THEME_TEXT_COLOR, + "Expected default theme text color" + ); + + await extension.unload(); + + Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set"); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js b/toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js new file mode 100644 index 0000000000..1395647683 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js @@ -0,0 +1,216 @@ +"use strict"; + +add_task(async function test_support_backgrounds_position() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "face1.png", + additional_backgrounds: ["face2.png", "face2.png", "face2.png"], + }, + colors: { + frame: `rgb(${FRAME_COLOR.join(",")})`, + tab_background_text: `rgb(${TAB_BACKGROUND_TEXT_COLOR.join(",")})`, + }, + properties: { + additional_backgrounds_alignment: [ + "left top", + "center top", + "right bottom", + ], + }, + }, + }, + files: { + "face1.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA), + "face2.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA), + }, + }); + + await extension.startup(); + + let docEl = window.document.documentElement; + let toolbox = document.querySelector("#navigator-toolbox"); + + Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set"); + Assert.equal( + docEl.getAttribute("lwthemetextcolor"), + "bright", + "LWT text color attribute should be set" + ); + + let toolboxCS = window.getComputedStyle(toolbox); + let rootCS = window.getComputedStyle(docEl); + let rootBgImage = rootCS.backgroundImage.split(",")[0].trim(); + let bgImage = toolboxCS.backgroundImage.split(",")[0].trim(); + Assert.ok( + rootBgImage.includes("face1.png"), + `The backgroundImage should use face1.png. Actual value is: ${rootBgImage}` + ); + Assert.equal( + toolboxCS.backgroundImage, + Array(3) + .fill(bgImage) + .join(", "), + "The backgroundImage should use face2.png three times." + ); + Assert.equal( + toolboxCS.backgroundPosition, + "0% 0%, 50% 0%, 100% 100%", + "The backgroundPosition should use the three values provided." + ); + Assert.equal( + toolboxCS.backgroundRepeat, + "no-repeat", + "The backgroundPosition should use the default value." + ); + + await extension.unload(); + + Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set"); + toolboxCS = window.getComputedStyle(toolbox); + + // Styles should've reverted to their initial values. + Assert.equal(rootCS.backgroundImage, "none"); + Assert.equal(rootCS.backgroundPosition, "0% 0%"); + Assert.equal(rootCS.backgroundRepeat, "repeat"); + Assert.equal(toolboxCS.backgroundImage, "none"); + Assert.equal(toolboxCS.backgroundPosition, "0% 0%"); + Assert.equal(toolboxCS.backgroundRepeat, "repeat"); +}); + +add_task(async function test_support_backgrounds_repeat() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "face0.png", + additional_backgrounds: ["face1.png", "face2.png", "face3.png"], + }, + colors: { + frame: FRAME_COLOR, + tab_background_text: TAB_BACKGROUND_TEXT_COLOR, + }, + properties: { + additional_backgrounds_tiling: ["repeat-x", "repeat-y", "repeat"], + }, + }, + }, + files: { + "face0.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA), + "face1.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA), + "face2.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA), + "face3.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA), + }, + }); + + await extension.startup(); + + let docEl = window.document.documentElement; + let toolbox = document.querySelector("#navigator-toolbox"); + + Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set"); + Assert.equal( + docEl.getAttribute("lwthemetextcolor"), + "bright", + "LWT text color attribute should be set" + ); + + let rootCS = window.getComputedStyle(docEl); + let toolboxCS = window.getComputedStyle(toolbox); + let bgImage = rootCS.backgroundImage.split(",")[0].trim(); + Assert.ok( + bgImage.includes("face0.png"), + `The backgroundImage should use face.png. Actual value is: ${bgImage}` + ); + Assert.equal( + [1, 2, 3].map(num => bgImage.replace(/face[\d]*/, `face${num}`)).join(", "), + toolboxCS.backgroundImage, + "The backgroundImage should use face.png three times." + ); + Assert.equal( + rootCS.backgroundPosition, + "100% 0%", + "The backgroundPosition should use the default value for root." + ); + Assert.equal( + toolboxCS.backgroundPosition, + "100% 0%", + "The backgroundPosition should use the default value for navigator-toolbox." + ); + Assert.equal( + rootCS.backgroundRepeat, + "no-repeat", + "The backgroundRepeat should use the default values for root." + ); + Assert.equal( + toolboxCS.backgroundRepeat, + "repeat-x, repeat-y, repeat", + "The backgroundRepeat should use the three values provided for navigator-toolbox." + ); + + await extension.unload(); + + Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set"); +}); + +add_task(async function test_additional_images_check() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "face.png", + }, + colors: { + frame: FRAME_COLOR, + tab_background_text: TAB_BACKGROUND_TEXT_COLOR, + }, + properties: { + additional_backgrounds_tiling: ["repeat-x", "repeat-y", "repeat"], + }, + }, + }, + files: { + "face.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA), + }, + }); + + await extension.startup(); + + let docEl = window.document.documentElement; + let toolbox = document.querySelector("#navigator-toolbox"); + + Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set"); + Assert.equal( + docEl.getAttribute("lwthemetextcolor"), + "bright", + "LWT text color attribute should be set" + ); + + let rootCS = window.getComputedStyle(docEl); + let toolboxCS = window.getComputedStyle(toolbox); + let bgImage = rootCS.backgroundImage.split(",")[0]; + Assert.ok( + bgImage.includes("face.png"), + `The backgroundImage should use face.png. Actual value is: ${bgImage}` + ); + Assert.equal( + "none", + toolboxCS.backgroundImage, + "The backgroundImage should not use face.png." + ); + Assert.equal( + rootCS.backgroundPosition, + "100% 0%", + "The backgroundPosition should use the default value." + ); + Assert.equal( + rootCS.backgroundRepeat, + "no-repeat", + "The backgroundPosition should use only one (default) value." + ); + + await extension.unload(); + + Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set"); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js new file mode 100644 index 0000000000..3e5d789709 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js @@ -0,0 +1,157 @@ +"use strict"; + +// This test checks whether the new tab page color properties work. + +/** + * Test whether the selected browser has the new tab page theme applied + * @param {Object} theme that is applied + * @param {boolean} isBrightText whether the brighttext attribute should be set + */ +async function test_ntp_theme(theme, isBrightText) { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme, + }, + }); + + let browser = gBrowser.selectedBrowser; + + let { originalBackground, originalColor } = await SpecialPowers.spawn( + browser, + [], + function() { + let doc = content.document; + ok( + !doc.body.hasAttribute("lwt-newtab"), + "New tab page should not have lwt-newtab attribute" + ); + ok( + !doc.body.hasAttribute("lwt-newtab-brighttext"), + `New tab page should not have lwt-newtab-brighttext attribute` + ); + + return { + originalBackground: content.getComputedStyle(doc.body).backgroundColor, + originalColor: content.getComputedStyle( + doc.querySelector(".outer-wrapper") + ).color, + }; + } + ); + + await extension.startup(); + + Services.ppmm.sharedData.flush(); + + await SpecialPowers.spawn( + browser, + [ + { + isBrightText, + background: hexToCSS(theme.colors.ntp_background), + color: hexToCSS(theme.colors.ntp_text), + }, + ], + function({ isBrightText, background, color }) { + let doc = content.document; + ok( + doc.body.hasAttribute("lwt-newtab"), + "New tab page should have lwt-newtab attribute" + ); + is( + doc.body.hasAttribute("lwt-newtab-brighttext"), + isBrightText, + `New tab page should${ + !isBrightText ? " not" : "" + } have lwt-newtab-brighttext attribute` + ); + + is( + content.getComputedStyle(doc.body).backgroundColor, + background, + "New tab page background should be set." + ); + is( + content.getComputedStyle(doc.querySelector(".outer-wrapper")).color, + color, + "New tab page text color should be set." + ); + } + ); + + await extension.unload(); + + Services.ppmm.sharedData.flush(); + + await SpecialPowers.spawn( + browser, + [ + { + originalBackground, + originalColor, + }, + ], + function({ originalBackground, originalColor }) { + let doc = content.document; + ok( + !doc.body.hasAttribute("lwt-newtab"), + "New tab page should not have lwt-newtab attribute" + ); + ok( + !doc.body.hasAttribute("lwt-newtab-brighttext"), + `New tab page should not have lwt-newtab-brighttext attribute` + ); + + is( + content.getComputedStyle(doc.body).backgroundColor, + originalBackground, + "New tab page background should be reset." + ); + is( + content.getComputedStyle(doc.querySelector(".outer-wrapper")).color, + originalColor, + "New tab page text color should be reset." + ); + } + ); +} + +add_task(async function test_support_ntp_colors() { + // BrowserTestUtils.withNewTab waits for about:newtab to load + // so we disable preloading before running the test. + SpecialPowers.setBoolPref("browser.newtab.preload", false); + registerCleanupFunction(() => { + SpecialPowers.clearUserPref("browser.newtab.preload"); + }); + NewTabPagePreloading.removePreloadedBrowser(window); + for (let url of ["about:newtab", "about:home", "about:welcome"]) { + info("Opening url: " + url); + await BrowserTestUtils.withNewTab({ gBrowser, url }, async browser => { + await test_ntp_theme( + { + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + ntp_background: "#add8e6", + ntp_text: "#00008b", + }, + }, + false, + url + ); + + await test_ntp_theme( + { + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + ntp_background: "#00008b", + ntp_text: "#add8e6", + }, + }, + true, + url + ); + }); + } +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js new file mode 100644 index 0000000000..bf204632ec --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js @@ -0,0 +1,249 @@ +"use strict"; + +// This test checks whether the new tab page color properties work per-window. + +/** + * Test whether a given browser has the new tab page theme applied + * @param {Object} browser to test against + * @param {Object} theme that is applied + * @param {boolean} isBrightText whether the brighttext attribute should be set + * @returns {Promise} The task as a promise + */ +function test_ntp_theme(browser, theme, isBrightText) { + Services.ppmm.sharedData.flush(); + return SpecialPowers.spawn( + browser, + [ + { + isBrightText, + background: hexToCSS(theme.colors.ntp_background), + color: hexToCSS(theme.colors.ntp_text), + }, + ], + function({ isBrightText, background, color }) { + let doc = content.document; + ok( + doc.body.hasAttribute("lwt-newtab"), + "New tab page should have lwt-newtab attribute" + ); + is( + doc.body.hasAttribute("lwt-newtab-brighttext"), + isBrightText, + `New tab page should${ + !isBrightText ? " not" : "" + } have lwt-newtab-brighttext attribute` + ); + + is( + content.getComputedStyle(doc.body).backgroundColor, + background, + "New tab page background should be set." + ); + is( + content.getComputedStyle(doc.querySelector(".outer-wrapper")).color, + color, + "New tab page text color should be set." + ); + } + ); +} + +/** + * Test whether a given browser has the default theme applied + * @param {Object} browser to test against + * @param {string} url being tested + * @returns {Promise} The task as a promise + */ +function test_ntp_default_theme(browser, url) { + Services.ppmm.sharedData.flush(); + if (url === "about:welcome") { + return SpecialPowers.spawn( + browser, + [ + { + background: hexToCSS("#EDEDF0"), + color: hexToCSS("#0C0C0D"), + }, + ], + function({ background, color }) { + let doc = content.document; + ok( + !doc.body.hasAttribute("lwt-newtab"), + "About:welcome page should not have lwt-newtab attribute" + ); + ok( + !doc.body.hasAttribute("lwt-newtab-brighttext"), + `About:welcome page should not have lwt-newtab-brighttext attribute` + ); + + is( + content.getComputedStyle(doc.body).backgroundColor, + background, + "About:welcome page background should be reset." + ); + is( + content.getComputedStyle(doc.querySelector(".outer-wrapper")).color, + color, + "About:welcome page text color should be reset." + ); + } + ); + } + return SpecialPowers.spawn( + browser, + [ + { + background: hexToCSS("#F9F9FA"), + color: hexToCSS("#0C0C0D"), + }, + ], + function({ background, color }) { + let doc = content.document; + ok( + !doc.body.hasAttribute("lwt-newtab"), + "New tab page should not have lwt-newtab attribute" + ); + ok( + !doc.body.hasAttribute("lwt-newtab-brighttext"), + `New tab page should not have lwt-newtab-brighttext attribute` + ); + + is( + content.getComputedStyle(doc.body).backgroundColor, + background, + "New tab page background should be reset." + ); + is( + content.getComputedStyle(doc.querySelector(".outer-wrapper")).color, + color, + "New tab page text color should be reset." + ); + } + ); +} + +add_task(async function test_per_window_ntp_theme() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["theme"], + }, + async background() { + function promiseWindowChecked() { + return new Promise(resolve => { + let listener = msg => { + if (msg == "checked-window") { + browser.test.onMessage.removeListener(listener); + resolve(); + } + }; + browser.test.onMessage.addListener(listener); + }); + } + + function removeWindow(winId) { + return new Promise(resolve => { + let listener = removedWinId => { + if (removedWinId == winId) { + browser.windows.onRemoved.removeListener(listener); + resolve(); + } + }; + browser.windows.onRemoved.addListener(listener); + browser.windows.remove(winId); + }); + } + + async function checkWindow(theme, isBrightText, winId) { + let windowChecked = promiseWindowChecked(); + browser.test.sendMessage("check-window", { + theme, + isBrightText, + winId, + }); + await windowChecked; + } + + const darkTextTheme = { + colors: { + frame: "#add8e6", + tab_background_text: "#000", + ntp_background: "#add8e6", + ntp_text: "#000", + }, + }; + + const brightTextTheme = { + colors: { + frame: "#00008b", + tab_background_text: "#add8e6", + ntp_background: "#00008b", + ntp_text: "#add8e6", + }, + }; + + let { id: winId } = await browser.windows.getCurrent(); + // We are opening about:blank instead of the default homepage, + // because using the default homepage results in intermittent + // test failures on debug builds due to browser window leaks. + let { id: secondWinId } = await browser.windows.create({ + url: "about:blank", + }); + + browser.test.log("Test that single window update works"); + await browser.theme.update(winId, darkTextTheme); + await checkWindow(darkTextTheme, false, winId); + await checkWindow(null, false, secondWinId); + + browser.test.log("Test that applying different themes on both windows"); + await browser.theme.update(secondWinId, brightTextTheme); + await checkWindow(darkTextTheme, false, winId); + await checkWindow(brightTextTheme, true, secondWinId); + + browser.test.log("Test resetting the theme on one window"); + await browser.theme.reset(winId); + await checkWindow(null, false, winId); + await checkWindow(brightTextTheme, true, secondWinId); + + await removeWindow(secondWinId); + await checkWindow(null, false, winId); + browser.test.notifyPass("perwindow-ntp-theme"); + }, + }); + + extension.onMessage( + "check-window", + async ({ theme, isBrightText, winId }) => { + let win = Services.wm.getOuterWindowWithId(winId); + win.NewTabPagePreloading.removePreloadedBrowser(win); + // These pages were initially chosen because LightweightThemeChild.jsm + // treats them specially. + for (let url of ["about:newtab", "about:home", "about:welcome"]) { + info("Opening url: " + url); + await BrowserTestUtils.withNewTab( + { gBrowser: win.gBrowser, url }, + async browser => { + if (theme) { + await test_ntp_theme(browser, theme, isBrightText); + } else { + await test_ntp_default_theme(browser, url); + } + } + ); + } + extension.sendMessage("checked-window"); + } + ); + + // BrowserTestUtils.withNewTab waits for about:newtab to load + // so we disable preloading before running the test. + await SpecialPowers.setBoolPref("browser.newtab.preload", false); + await SpecialPowers.setBoolPref("browser.aboutwelcome.enabled", true); + registerCleanupFunction(() => { + SpecialPowers.clearUserPref("browser.newtab.preload"); + SpecialPowers.clearUserPref("browser.aboutwelcome.enabled"); + }); + + await extension.startup(); + await extension.awaitFinish("perwindow-ntp-theme"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_persistence.js b/toolkit/components/extensions/test/browser/browser_ext_themes_persistence.js new file mode 100644 index 0000000000..bc72609acd --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_persistence.js @@ -0,0 +1,58 @@ +"use strict"; + +// This test checks whether applied WebExtension themes are persisted and applied +// on newly opened windows. + +add_task(async function test_multiple_windows() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + let docEl = window.document.documentElement; + let style = window.getComputedStyle(docEl); + + Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set"); + Assert.equal( + docEl.getAttribute("lwthemetextcolor"), + "bright", + "LWT text color attribute should be set" + ); + Assert.ok( + style.backgroundImage.includes("image1.png"), + "Expected background image" + ); + + // Now we'll open a new window to see if the theme is also applied there. + let window2 = await BrowserTestUtils.openNewBrowserWindow(); + docEl = window2.document.documentElement; + style = window2.getComputedStyle(docEl); + + Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set"); + Assert.equal( + docEl.getAttribute("lwthemetextcolor"), + "bright", + "LWT text color attribute should be set" + ); + Assert.ok( + style.backgroundImage.includes("image1.png"), + "Expected background image" + ); + + await BrowserTestUtils.closeWindow(window2); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_reset.js b/toolkit/components/extensions/test/browser/browser_ext_themes_reset.js new file mode 100644 index 0000000000..d8b3b14073 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_reset.js @@ -0,0 +1,112 @@ +"use strict"; + +add_task(async function theme_reset_global_static_theme() { + let global_theme_extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: "#123456", + tab_background_text: "#fedcba", + }, + }, + }, + }); + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["theme"], + }, + async background() { + await browser.theme.reset(); + let theme_after_reset = await browser.theme.getCurrent(); + + browser.test.assertEq( + "#123456", + theme_after_reset.colors.frame, + "Theme from other extension should not be cleared upon reset()" + ); + + let theme = { + colors: { + frame: "#CF723F", + }, + }; + + await browser.theme.update(theme); + await browser.theme.reset(); + let final_reset_theme = await browser.theme.getCurrent(); + + browser.test.assertEq( + JSON.stringify({ colors: null, images: null, properties: null }), + JSON.stringify(final_reset_theme), + "Should reset when extension had replaced the global theme" + ); + browser.test.sendMessage("done"); + }, + }); + await global_theme_extension.startup(); + await extension.startup(); + await extension.awaitMessage("done"); + + await global_theme_extension.unload(); + await extension.unload(); +}); + +add_task(async function theme_reset_by_windowId() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["theme"], + }, + async background() { + let theme = { + colors: { + frame: "#CF723F", + }, + }; + + let { id: winId } = await browser.windows.getCurrent(); + await browser.theme.update(winId, theme); + let update_theme = await browser.theme.getCurrent(winId); + + browser.test.onMessage.addListener(async () => { + let current_theme = await browser.theme.getCurrent(winId); + browser.test.assertEq( + update_theme.colors.frame, + current_theme.colors.frame, + "Should not be reset by a reset(windowId) call from another extension" + ); + + browser.test.sendMessage("done"); + }); + + browser.test.sendMessage("ready", winId); + }, + }); + + let anotherExtension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["theme"], + }, + background() { + browser.test.onMessage.addListener(async winId => { + await browser.theme.reset(winId); + browser.test.sendMessage("done"); + }); + }, + }); + + await extension.startup(); + let winId = await extension.awaitMessage("ready"); + + await anotherExtension.startup(); + + // theme.reset should be ignored if the theme was set by another extension. + anotherExtension.sendMessage(winId); + await anotherExtension.awaitMessage("done"); + + extension.sendMessage(); + await extension.awaitMessage("done"); + + await anotherExtension.unload(); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js b/toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js new file mode 100644 index 0000000000..36658cd5b4 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js @@ -0,0 +1,175 @@ +"use strict"; + +// This test checks color sanitization in various situations + +add_task(async function test_sanitization_invalid() { + // This test checks that invalid values are sanitized + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + bookmark_text: "ntimsfavoriteblue", + }, + }, + }, + }); + + await extension.startup(); + + let navbar = document.querySelector("#nav-bar"); + Assert.equal( + window.getComputedStyle(navbar).color, + "rgb(0, 0, 0)", + "All invalid values should always compute to black." + ); + + await extension.unload(); +}); + +add_task(async function test_sanitization_css_variables() { + // This test checks that CSS variables are sanitized + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + bookmark_text: "var(--arrowpanel-dimmed)", + }, + }, + }, + }); + + await extension.startup(); + + let navbar = document.querySelector("#nav-bar"); + Assert.equal( + window.getComputedStyle(navbar).color, + "rgb(0, 0, 0)", + "All CSS variables should always compute to black." + ); + + await extension.unload(); +}); + +add_task(async function test_sanitization_important() { + // This test checks that the sanitizer cannot be fooled with !important + let stylesheetAttr = `href="data:text/css,*{color:red!important}" type="text/css"`; + let stylesheet = document.createProcessingInstruction( + "xml-stylesheet", + stylesheetAttr + ); + let load = BrowserTestUtils.waitForEvent(stylesheet, "load"); + document.insertBefore(stylesheet, document.documentElement); + await load; + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + bookmark_text: "green", + }, + }, + }, + }); + + await extension.startup(); + + let navbar = document.querySelector("#nav-bar"); + Assert.equal( + window.getComputedStyle(navbar).color, + "rgb(255, 0, 0)", + "Sheet applies" + ); + + stylesheet.remove(); + + Assert.equal( + window.getComputedStyle(navbar).color, + "rgb(0, 128, 0)", + "Shouldn't be able to fool the color sanitizer with !important" + ); + + await extension.unload(); +}); + +add_task(async function test_sanitization_transparent() { + // This test checks whether transparent values are applied properly + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + toolbar_top_separator: "transparent", + }, + }, + }, + }); + + await extension.startup(); + + let navbar = document.querySelector("#nav-bar"); + Assert.ok( + window.getComputedStyle(navbar).boxShadow.includes("rgba(0, 0, 0, 0)"), + "Top separator should be transparent" + ); + + await extension.unload(); +}); + +add_task(async function test_sanitization_transparent_frame_color() { + // This test checks whether transparent frame color falls back to white. + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: "transparent", + tab_background_text: TEXT_COLOR, + }, + }, + }, + }); + + await extension.startup(); + + let docEl = document.documentElement; + Assert.equal( + window.getComputedStyle(docEl).backgroundColor, + "rgb(255, 255, 255)", + "Accent color should be white" + ); + + await extension.unload(); +}); + +add_task( + async function test_sanitization_transparent_tab_background_text_color() { + // This test checks whether transparent textcolor falls back to black. + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: ACCENT_COLOR, + tab_background_text: "transparent", + }, + }, + }, + }); + + await extension.startup(); + + let docEl = document.documentElement; + Assert.equal( + window.getComputedStyle(docEl).color, + "rgb(0, 0, 0)", + "Text color should be black" + ); + + await extension.unload(); + } +); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js b/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js new file mode 100644 index 0000000000..4266a982d8 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js @@ -0,0 +1,69 @@ +"use strict"; + +// This test checks whether applied WebExtension themes that attempt to change +// the separator colors are applied properly. + +add_task(async function test_support_separator_properties() { + const SEPARATOR_TOP_COLOR = "#ff00ff"; + const SEPARATOR_VERTICAL_COLOR = "#f0000f"; + const SEPARATOR_FIELD_COLOR = "#9400ff"; + const SEPARATOR_BOTTOM_COLOR = "#3366cc"; + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + toolbar_top_separator: SEPARATOR_TOP_COLOR, + toolbar_vertical_separator: SEPARATOR_VERTICAL_COLOR, + toolbar_field_separator: SEPARATOR_FIELD_COLOR, + toolbar_bottom_separator: SEPARATOR_BOTTOM_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + let navbar = document.querySelector("#nav-bar"); + Assert.ok( + window + .getComputedStyle(navbar) + .boxShadow.includes(`rgb(${hexToRGB(SEPARATOR_TOP_COLOR).join(", ")})`), + "Top separator color properly set" + ); + + let mainWin = document.querySelector("#main-window"); + Assert.equal( + window + .getComputedStyle(mainWin) + .getPropertyValue("--urlbar-separator-color"), + `rgb(${hexToRGB(SEPARATOR_FIELD_COLOR).join(", ")})`, + "Toolbar field separator color properly set" + ); + + let panelUIButton = document.querySelector("#PanelUI-button"); + Assert.ok( + window + .getComputedStyle(panelUIButton) + .getPropertyValue("border-image-source") + .includes(`rgb(${hexToRGB(SEPARATOR_VERTICAL_COLOR).join(", ")})`), + "Vertical separator color properly set" + ); + + let toolbox = document.querySelector("#navigator-toolbox"); + Assert.equal( + window.getComputedStyle(toolbox).borderBottomColor, + `rgb(${hexToRGB(SEPARATOR_BOTTOM_COLOR).join(", ")})`, + "Bottom separator color properly set" + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_sidebars.js b/toolkit/components/extensions/test/browser/browser_ext_themes_sidebars.js new file mode 100644 index 0000000000..3d814d1082 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_sidebars.js @@ -0,0 +1,274 @@ +"use strict"; + +// This test checks whether the sidebar color properties work. + +/** + * Test whether the selected browser has the sidebar theme applied + * @param {Object} theme that is applied + * @param {boolean} isBrightText whether the brighttext attribute should be set + */ +async function test_sidebar_theme(theme, isBrightText) { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme, + }, + }); + + const sidebarBox = document.getElementById("sidebar-box"); + const content = SidebarUI.browser.contentWindow; + const root = content.document.documentElement; + + ok( + !sidebarBox.hasAttribute("lwt-sidebar"), + "Sidebar box should not have lwt-sidebar attribute" + ); + ok( + !root.hasAttribute("lwt-sidebar"), + "Sidebar should not have lwt-sidebar attribute" + ); + ok( + !root.hasAttribute("lwt-sidebar-brighttext"), + "Sidebar should not have lwt-sidebar-brighttext attribute" + ); + ok( + !root.hasAttribute("lwt-sidebar-highlight"), + "Sidebar should not have lwt-sidebar-highlight attribute" + ); + + const rootCS = content.getComputedStyle(root); + const originalBackground = rootCS.backgroundColor; + const originalColor = rootCS.color; + + // ::-moz-tree-row(selected, focus) computed style can't be accessed, so we create a fake one. + const highlightCS = { + get backgroundColor() { + // Standardize to rgb like other computed style. + let color = rootCS.getPropertyValue( + "--lwt-sidebar-highlight-background-color" + ); + let [r, g, b] = color + .replace("rgba(", "") + .split(",") + .map(channel => parseInt(channel, 10)); + return `rgb(${r}, ${g}, ${b})`; + }, + + get color() { + let color = rootCS.getPropertyValue("--lwt-sidebar-highlight-text-color"); + let [r, g, b] = color + .replace("rgba(", "") + .split(",") + .map(channel => parseInt(channel, 10)); + return `rgb(${r}, ${g}, ${b})`; + }, + }; + const originalHighlightBackground = highlightCS.backgroundColor; + const originalHighlightColor = highlightCS.color; + + await extension.startup(); + + Services.ppmm.sharedData.flush(); + + const actualBackground = hexToCSS(theme.colors.sidebar) || originalBackground; + const actualColor = hexToCSS(theme.colors.sidebar_text) || originalColor; + const actualHighlightBackground = + hexToCSS(theme.colors.sidebar_highlight) || originalHighlightBackground; + const actualHighlightColor = + hexToCSS(theme.colors.sidebar_highlight_text) || originalHighlightColor; + const isCustomHighlight = !!theme.colors.sidebar_highlight_text; + const isCustomSidebar = !!theme.colors.sidebar_text; + + is( + sidebarBox.hasAttribute("lwt-sidebar"), + isCustomSidebar, + `Sidebar box should${ + !isCustomSidebar ? " not" : "" + } have lwt-sidebar attribute` + ); + is( + root.hasAttribute("lwt-sidebar"), + isCustomSidebar, + `Sidebar should${!isCustomSidebar ? " not" : ""} have lwt-sidebar attribute` + ); + is( + root.hasAttribute("lwt-sidebar-brighttext"), + isBrightText, + `Sidebar should${ + !isBrightText ? " not" : "" + } have lwt-sidebar-brighttext attribute` + ); + is( + root.hasAttribute("lwt-sidebar-highlight"), + isCustomHighlight, + `Sidebar should${ + !isCustomHighlight ? " not" : "" + } have lwt-sidebar-highlight attribute` + ); + + if (isCustomSidebar) { + const sidebarBoxCS = window.getComputedStyle(sidebarBox); + is( + sidebarBoxCS.backgroundColor, + actualBackground, + "Sidebar box background should be set." + ); + is( + sidebarBoxCS.color, + actualColor, + "Sidebar box text color should be set." + ); + } + + is( + rootCS.backgroundColor, + actualBackground, + "Sidebar background should be set." + ); + is(rootCS.color, actualColor, "Sidebar text color should be set."); + + is( + highlightCS.backgroundColor, + actualHighlightBackground, + "Sidebar highlight background color should be set." + ); + is( + highlightCS.color, + actualHighlightColor, + "Sidebar highlight text color should be set." + ); + + await extension.unload(); + + Services.ppmm.sharedData.flush(); + + ok( + !sidebarBox.hasAttribute("lwt-sidebar"), + "Sidebar box should not have lwt-sidebar attribute" + ); + ok( + !root.hasAttribute("lwt-sidebar"), + "Sidebar should not have lwt-sidebar attribute" + ); + ok( + !root.hasAttribute("lwt-sidebar-brighttext"), + "Sidebar should not have lwt-sidebar-brighttext attribute" + ); + ok( + !root.hasAttribute("lwt-sidebar-highlight"), + "Sidebar should not have lwt-sidebar-highlight attribute" + ); + + is( + rootCS.backgroundColor, + originalBackground, + "Sidebar background should be reset." + ); + is(rootCS.color, originalColor, "Sidebar text color should be reset."); + is( + highlightCS.backgroundColor, + originalHighlightBackground, + "Sidebar highlight background color should be reset." + ); + is( + highlightCS.color, + originalHighlightColor, + "Sidebar highlight text color should be reset." + ); +} + +add_task(async function test_support_sidebar_colors() { + for (let command of ["viewBookmarksSidebar", "viewHistorySidebar"]) { + info("Executing command: " + command); + + await SidebarUI.show(command); + + await test_sidebar_theme( + { + colors: { + sidebar: "#fafad2", // lightgoldenrodyellow + sidebar_text: "#2f4f4f", // darkslategrey + }, + }, + false + ); + + await test_sidebar_theme( + { + colors: { + sidebar: "#8b4513", // saddlebrown + sidebar_text: "#ffa07a", // lightsalmon + }, + }, + true + ); + + await test_sidebar_theme( + { + colors: { + sidebar: "#fffafa", // snow + sidebar_text: "#663399", // rebeccapurple + sidebar_highlight: "#7cfc00", // lawngreen + sidebar_highlight_text: "#ffefd5", // papayawhip + }, + }, + false + ); + + await test_sidebar_theme( + { + colors: { + sidebar_highlight: "#a0522d", // sienna + sidebar_highlight_text: "#fff5ee", // seashell + }, + }, + false + ); + } +}); + +add_task(async function test_support_sidebar_border_color() { + const LIGHT_SALMON = "#ffa07a"; + const extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + sidebar_border: LIGHT_SALMON, + }, + }, + }, + }); + + await extension.startup(); + + const sidebarHeader = document.getElementById("sidebar-header"); + const sidebarHeaderCS = window.getComputedStyle(sidebarHeader); + + is( + sidebarHeaderCS.borderBottomColor, + hexToCSS(LIGHT_SALMON), + "Sidebar header border should be colored properly" + ); + + if (AppConstants.platform !== "linux") { + const sidebarSplitter = document.getElementById("sidebar-splitter"); + const sidebarSplitterCS = window.getComputedStyle(sidebarSplitter); + + is( + sidebarSplitterCS.borderInlineEndColor, + hexToCSS(LIGHT_SALMON), + "Sidebar splitter should be colored properly" + ); + + SidebarUI.reversePosition(); + + is( + sidebarSplitterCS.borderInlineStartColor, + hexToCSS(LIGHT_SALMON), + "Sidebar splitter should be colored properly after switching sides" + ); + + SidebarUI.reversePosition(); + } + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_static_onUpdated.js b/toolkit/components/extensions/test/browser/browser_ext_themes_static_onUpdated.js new file mode 100644 index 0000000000..081322faa3 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_static_onUpdated.js @@ -0,0 +1,66 @@ +"use strict"; + +// This test checks whether browser.theme.onUpdated works +// when a static theme is applied + +add_task(async function test_on_updated() { + const theme = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + const extension = ExtensionTestUtils.loadExtension({ + background() { + browser.theme.onUpdated.addListener(updateInfo => { + browser.test.sendMessage("theme-updated", updateInfo); + }); + }, + }); + + await extension.startup(); + + info("Testing update event on static theme startup"); + let updatedPromise = extension.awaitMessage("theme-updated"); + await theme.startup(); + const { theme: receivedTheme, windowId } = await updatedPromise; + Assert.ok(!windowId, "No window id in static theme update event"); + Assert.ok( + receivedTheme.images.theme_frame.includes("image1.png"), + "Theme theme_frame image should be applied" + ); + Assert.equal( + receivedTheme.colors.frame, + ACCENT_COLOR, + "Theme frame color should be applied" + ); + Assert.equal( + receivedTheme.colors.tab_background_text, + TEXT_COLOR, + "Theme tab_background_text color should be applied" + ); + + info("Testing update event on static theme unload"); + updatedPromise = extension.awaitMessage("theme-updated"); + await theme.unload(); + const updateInfo = await updatedPromise; + Assert.ok(!windowId, "No window id in static theme update event on unload"); + Assert.equal( + Object.keys(updateInfo.theme), + 0, + "unloading theme sends empty theme in update event" + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_line.js b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_line.js new file mode 100644 index 0000000000..928fa4edee --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_line.js @@ -0,0 +1,50 @@ +"use strict"; + +// This test checks whether applied WebExtension themes that attempt to change +// the color of the tab line are applied properly. + +add_task(async function test_support_tab_line() { + for (let protonTabsEnabled of [true, false]) { + SpecialPowers.pushPrefEnv({ + set: [["browser.proton.tabs.enabled", protonTabsEnabled]], + }); + let newWin = await BrowserTestUtils.openNewWindowWithFlushedXULCacheForMozSupports(); + + const TAB_LINE_COLOR = "#9400ff"; + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + tab_line: TAB_LINE_COLOR, + }, + }, + }, + }); + + await extension.startup(); + + info("Checking selected tab line color"); + let selectedTab = newWin.document.querySelector( + ".tabbrowser-tab[selected]" + ); + let line = selectedTab.querySelector(".tab-line"); + if (protonTabsEnabled) { + Assert.equal( + newWin.getComputedStyle(line).display, + "none", + "Tab line should not be displayed when Proton is enabled" + ); + } else { + Assert.equal( + newWin.getComputedStyle(line).backgroundColor, + `rgb(${hexToRGB(TAB_LINE_COLOR).join(", ")})`, + "Tab line should have theme color" + ); + } + + await extension.unload(); + await BrowserTestUtils.closeWindow(newWin); + } +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_loading.js b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_loading.js new file mode 100644 index 0000000000..1e402dbcc6 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_loading.js @@ -0,0 +1,51 @@ +"use strict"; + +add_task(async function test_support_tab_loading_filling() { + const TAB_LOADING_COLOR = "#FF0000"; + + // Make sure we use the animating loading icon + await SpecialPowers.pushPrefEnv({ + set: [["ui.prefersReducedMotion", 0]], + }); + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: "#000", + toolbar: "#124455", + tab_background_text: "#9400ff", + tab_loading: TAB_LOADING_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + info("Checking selected tab loading indicator colors"); + + let selectedTab = document.querySelector( + ".tabbrowser-tab[visuallyselected=true]" + ); + + selectedTab.setAttribute("busy", "true"); + selectedTab.setAttribute("progress", "true"); + + let throbber = selectedTab.throbber; + Assert.equal( + window.getComputedStyle(throbber, "::before").fill, + `rgb(${hexToRGB(TAB_LOADING_COLOR).join(", ")})`, + "Throbber is filled with theme color" + ); + + selectedTab.removeAttribute("busy"); + selectedTab.removeAttribute("progress"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_selected.js b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_selected.js new file mode 100644 index 0000000000..21f3c6d38b --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_selected.js @@ -0,0 +1,54 @@ +"use strict"; + +// This test checks whether applied WebExtension themes that attempt to change +// the background color of selected tab are applied correctly. + +add_task(async function test_tab_background_color_property() { + const TAB_BACKGROUND_COLOR = "#9400ff"; + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + tab_selected: TAB_BACKGROUND_COLOR, + }, + }, + }, + }); + + await extension.startup(); + + info("Checking selected tab color"); + + let openTab = document.querySelector( + ".tabbrowser-tab[visuallyselected=true]" + ); + let openTabBackground = openTab.querySelector(".tab-background"); + + let selectedTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:blank" + ); + let selectedTabBackground = selectedTab.querySelector(".tab-background"); + + let openTabGradient = window + .getComputedStyle(openTabBackground) + .getPropertyValue("background-image"); + let selectedTabGradient = window + .getComputedStyle(selectedTabBackground) + .getPropertyValue("background-image"); + + let rgbRegex = /rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)/g; + let selectedTabColors = selectedTabGradient.match(rgbRegex); + + Assert.equal( + selectedTabColors[0], + "rgb(" + hexToRGB(TAB_BACKGROUND_COLOR).join(", ") + ")", + "Selected tab background color should be set." + ); + Assert.equal(openTabGradient, "none"); + + gBrowser.removeTab(selectedTab); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_separators.js b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_separators.js new file mode 100644 index 0000000000..722c7dd99c --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_separators.js @@ -0,0 +1,38 @@ +"use strict"; + +add_task(async function test_support_tab_separators() { + const TAB_SEPARATOR_COLOR = "#FF0000"; + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: "#000", + tab_background_text: "#9400ff", + tab_background_separator: TAB_SEPARATOR_COLOR, + }, + }, + }, + }); + await extension.startup(); + + info("Checking background tab separator color"); + + let tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + + Assert.equal( + window.getComputedStyle(tab, "::before").borderLeftColor, + `rgb(${hexToRGB(TAB_SEPARATOR_COLOR).join(", ")})`, + "Left separator has right color." + ); + + Assert.equal( + window.getComputedStyle(tab, "::after").borderLeftColor, + `rgb(${hexToRGB(TAB_SEPARATOR_COLOR).join(", ")})`, + "Right separator has right color." + ); + + gBrowser.removeTab(tab); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_text.js b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_text.js new file mode 100644 index 0000000000..d819f3a5f1 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_text.js @@ -0,0 +1,70 @@ +"use strict"; + +// This test checks whether applied WebExtension themes that attempt to change +// the text color of the selected tab are applied properly. + +add_task(async function test_support_tab_text_property_css_color() { + const TAB_TEXT_COLOR = "#9400ff"; + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + tab_text: TAB_TEXT_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + info("Checking selected tab colors"); + let selectedTab = document.querySelector(".tabbrowser-tab[selected]"); + Assert.equal( + window.getComputedStyle(selectedTab).color, + "rgb(" + hexToRGB(TAB_TEXT_COLOR).join(", ") + ")", + "Selected tab text color should be set." + ); + + await extension.unload(); +}); + +add_task(async function test_support_tab_text_chrome_array() { + const TAB_TEXT_COLOR = [148, 0, 255]; + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: FRAME_COLOR, + tab_background_text: TAB_BACKGROUND_TEXT_COLOR, + tab_text: TAB_TEXT_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + info("Checking selected tab colors"); + let selectedTab = document.querySelector(".tabbrowser-tab[selected]"); + Assert.equal( + window.getComputedStyle(selectedTab).color, + "rgb(" + TAB_TEXT_COLOR.join(", ") + ")", + "Selected tab text color should be set." + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_theme_transition.js b/toolkit/components/extensions/test/browser/browser_ext_themes_theme_transition.js new file mode 100644 index 0000000000..39934200ac --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_theme_transition.js @@ -0,0 +1,48 @@ +"use strict"; + +// This test checks whether the applied theme transition effects are applied +// correctly. + +add_task(async function test_theme_transition_effects() { + const TOOLBAR = "#f27489"; + const TEXT_COLOR = "#000000"; + const TRANSITION_PROPERTY = "background-color"; + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + tab_background_text: TEXT_COLOR, + toolbar: TOOLBAR, + bookmark_text: TEXT_COLOR, + }, + }, + }, + }); + + await extension.startup(); + + // check transition effect for toolbars + let navbar = document.querySelector("#nav-bar"); + let navbarCS = window.getComputedStyle(navbar); + + Assert.ok( + navbarCS + .getPropertyValue("transition-property") + .includes(TRANSITION_PROPERTY), + "Transition property set for #nav-bar" + ); + + let bookmarksBar = document.querySelector("#PersonalToolbar"); + setToolbarVisibility(bookmarksBar, true, false, true); + let bookmarksBarCS = window.getComputedStyle(bookmarksBar); + + Assert.ok( + bookmarksBarCS + .getPropertyValue("transition-property") + .includes(TRANSITION_PROPERTY), + "Transition property set for #PersonalToolbar" + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields.js b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields.js new file mode 100644 index 0000000000..cd4d08c38f --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields.js @@ -0,0 +1,145 @@ +"use strict"; + +// This test checks whether applied WebExtension themes that attempt to change +// the background color and the color of the navbar text fields are applied properly. + +ChromeUtils.import( + "resource://testing-common/CustomizableUITestUtils.jsm", + this +); +let gCUITestUtils = new CustomizableUITestUtils(window); + +add_task(async function setup() { + await gCUITestUtils.addSearchBar(); + registerCleanupFunction(() => { + gCUITestUtils.removeSearchBar(); + }); +}); + +add_task(async function test_support_toolbar_field_properties() { + const TOOLBAR_FIELD_BACKGROUND = "#ff00ff"; + const TOOLBAR_FIELD_COLOR = "#00ff00"; + const TOOLBAR_FIELD_BORDER = "#aaaaff"; + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + toolbar_field: TOOLBAR_FIELD_BACKGROUND, + toolbar_field_text: TOOLBAR_FIELD_COLOR, + toolbar_field_border: TOOLBAR_FIELD_BORDER, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + let root = document.documentElement; + // Remove the `remotecontrol` attribute since it interferes with the urlbar styling. + root.removeAttribute("remotecontrol"); + registerCleanupFunction(() => { + root.setAttribute("remotecontrol", "true"); + }); + + let fields = [ + document.querySelector("#urlbar-background"), + BrowserSearch.searchBar, + ].filter(field => { + let bounds = field.getBoundingClientRect(); + return bounds.width > 0 && bounds.height > 0; + }); + + Assert.equal(fields.length, 2, "Should be testing two elements"); + + info( + `Checking toolbar background colors and colors for ${fields.length} toolbar fields.` + ); + for (let field of fields) { + info(`Testing ${field.id || field.className}`); + Assert.equal( + window.getComputedStyle(field).backgroundColor, + hexToCSS(TOOLBAR_FIELD_BACKGROUND), + "Field background should be set." + ); + Assert.equal( + window.getComputedStyle(field).color, + hexToCSS(TOOLBAR_FIELD_COLOR), + "Field color should be set." + ); + testBorderColor(field, TOOLBAR_FIELD_BORDER); + } + + await extension.unload(); +}); + +add_task(async function test_support_toolbar_field_brighttext() { + let root = document.documentElement; + // Remove the `remotecontrol` attribute since it interferes with the urlbar styling. + root.removeAttribute("remotecontrol"); + registerCleanupFunction(() => { + root.setAttribute("remotecontrol", "true"); + }); + let urlbar = gURLBar.textbox; + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + toolbar_field: "#fff", + toolbar_field_text: "#000", + }, + }, + }, + }); + + await extension.startup(); + + Assert.equal( + window.getComputedStyle(urlbar).color, + hexToCSS("#000000"), + "Color has been set" + ); + Assert.ok( + !root.hasAttribute("lwt-toolbar-field-brighttext"), + "Brighttext attribute should not be set" + ); + + await extension.unload(); + + extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + toolbar_field: "#000", + toolbar_field_text: "#fff", + }, + }, + }, + }); + + await extension.startup(); + + Assert.equal( + window.getComputedStyle(urlbar).color, + hexToCSS("#ffffff"), + "Color has been set" + ); + Assert.ok( + root.hasAttribute("lwt-toolbar-field-brighttext"), + "Brighttext attribute should be set" + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields_focus.js b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields_focus.js new file mode 100644 index 0000000000..05b6a186d2 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields_focus.js @@ -0,0 +1,102 @@ +"use strict"; + +add_task(async function setup() { + // Remove the `remotecontrol` attribute since it interferes with the urlbar styling. + document.documentElement.removeAttribute("remotecontrol"); + registerCleanupFunction(() => { + document.documentElement.setAttribute("remotecontrol", "true"); + }); +}); + +add_task(async function test_toolbar_field_focus() { + const TOOLBAR_FIELD_BACKGROUND = "#FF00FF"; + const TOOLBAR_FIELD_COLOR = "#00FF00"; + const TOOLBAR_FOCUS_BACKGROUND = "#FF0000"; + const TOOLBAR_FOCUS_TEXT = "#9400FF"; + const TOOLBAR_FOCUS_BORDER = "#FFFFFF"; + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: "#FF0000", + tab_background_color: "#ffffff", + toolbar_field: TOOLBAR_FIELD_BACKGROUND, + toolbar_field_text: TOOLBAR_FIELD_COLOR, + toolbar_field_focus: TOOLBAR_FOCUS_BACKGROUND, + toolbar_field_text_focus: TOOLBAR_FOCUS_TEXT, + toolbar_field_border_focus: TOOLBAR_FOCUS_BORDER, + }, + }, + }, + }); + + await extension.startup(); + + info("Checking toolbar field's focus color"); + + let urlBar = document.querySelector("#urlbar-background"); + gURLBar.textbox.setAttribute("focused", "true"); + + Assert.equal( + window.getComputedStyle(urlBar).backgroundColor, + `rgb(${hexToRGB(TOOLBAR_FOCUS_BACKGROUND).join(", ")})`, + "Background Color is changed" + ); + Assert.equal( + window.getComputedStyle(urlBar).color, + `rgb(${hexToRGB(TOOLBAR_FOCUS_TEXT).join(", ")})`, + "Text Color is changed" + ); + testBorderColor(urlBar, TOOLBAR_FOCUS_BORDER); + + gURLBar.textbox.removeAttribute("focused"); + + Assert.equal( + window.getComputedStyle(urlBar).backgroundColor, + `rgb(${hexToRGB(TOOLBAR_FIELD_BACKGROUND).join(", ")})`, + "Background Color is set back to initial" + ); + Assert.equal( + window.getComputedStyle(urlBar).color, + `rgb(${hexToRGB(TOOLBAR_FIELD_COLOR).join(", ")})`, + "Text Color is set back to initial" + ); + await extension.unload(); +}); + +add_task(async function test_toolbar_field_focus_low_alpha() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: "#FF0000", + tab_background_color: "#ffffff", + toolbar_field: "#FF00FF", + toolbar_field_text: "#00FF00", + toolbar_field_focus: "rgba(0, 0, 255, 0.4)", + toolbar_field_text_focus: "red", + toolbar_field_border_focus: "#FFFFFF", + }, + }, + }, + }); + + await extension.startup(); + gURLBar.textbox.setAttribute("focused", "true"); + + let urlBar = document.querySelector("#urlbar-background"); + Assert.equal( + window.getComputedStyle(urlBar).backgroundColor, + `rgba(0, 0, 255, 0.9)`, + "Background color has minimum opacity enforced" + ); + Assert.equal( + window.getComputedStyle(urlBar).color, + `rgb(255, 255, 255)`, + "Text color has been overridden to match background" + ); + + gURLBar.textbox.removeAttribute("focused"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_colors.js b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_colors.js new file mode 100644 index 0000000000..f31e0fce8a --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_colors.js @@ -0,0 +1,56 @@ +"use strict"; + +/* globals InspectorUtils */ + +// This test checks whether applied WebExtension themes that attempt to change +// the button background color properties are applied correctly. + +add_task(async function test_button_background_properties() { + const BUTTON_BACKGROUND_ACTIVE = "#FFFFFF"; + const BUTTON_BACKGROUND_HOVER = "#59CBE8"; + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + button_background_active: BUTTON_BACKGROUND_ACTIVE, + button_background_hover: BUTTON_BACKGROUND_HOVER, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + let toolbarButton = document.querySelector("#home-button"); + let toolbarButtonIcon = toolbarButton.icon; + let toolbarButtonIconCS = window.getComputedStyle(toolbarButtonIcon); + + InspectorUtils.addPseudoClassLock(toolbarButton, ":hover"); + + Assert.equal( + toolbarButtonIconCS.getPropertyValue("background-color"), + `rgb(${hexToRGB(BUTTON_BACKGROUND_HOVER).join(", ")})`, + "Toolbar button hover background is set." + ); + + InspectorUtils.addPseudoClassLock(toolbarButton, ":active"); + + Assert.equal( + toolbarButtonIconCS.getPropertyValue("background-color"), + `rgb(${hexToRGB(BUTTON_BACKGROUND_ACTIVE).join(", ")})`, + "Toolbar button active background is set!" + ); + + InspectorUtils.clearPseudoClassLocks(toolbarButton); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_icons.js b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_icons.js new file mode 100644 index 0000000000..11643412dd --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_icons.js @@ -0,0 +1,107 @@ +"use strict"; + +// This test checks applied WebExtension themes that attempt to change +// icon color properties + +add_task(async function test_icons_properties() { + const ICONS_COLOR = "#001b47"; + const ICONS_ATTENTION_COLOR = "#44ba77"; + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + icons: ICONS_COLOR, + icons_attention: ICONS_ATTENTION_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + let toolbarbutton = document.querySelector("#home-button"); + Assert.equal( + window.getComputedStyle(toolbarbutton).getPropertyValue("fill"), + `rgb(${hexToRGB(ICONS_COLOR).join(", ")})`, + "Buttons fill color set!" + ); + + let starButton = document.querySelector("#star-button"); + starButton.setAttribute("starred", "true"); + + let starComputedStyle = window.getComputedStyle(starButton); + Assert.equal( + starComputedStyle.getPropertyValue( + "--lwt-toolbarbutton-icon-fill-attention" + ), + `rgb(${hexToRGB(ICONS_ATTENTION_COLOR).join(", ")})`, + "Variable is properly set" + ); + Assert.equal( + starComputedStyle.getPropertyValue("fill"), + `rgb(${hexToRGB(ICONS_ATTENTION_COLOR).join(", ")})`, + "Starred icon fill is properly set" + ); + + starButton.removeAttribute("starred"); + + await extension.unload(); +}); + +add_task(async function test_no_icons_properties() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + images: { + theme_frame: "image1.png", + }, + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + }, + }, + }, + files: { + "image1.png": BACKGROUND, + }, + }); + + await extension.startup(); + + let toolbarbutton = document.querySelector("#home-button"); + let toolbarbuttonCS = window.getComputedStyle(toolbarbutton); + Assert.equal( + toolbarbuttonCS.getPropertyValue("--lwt-toolbarbutton-icon-fill"), + "", + "Icon fill should not be set when the value is not specified in the manifest." + ); + let currentColor = toolbarbuttonCS.getPropertyValue("color"); + Assert.equal( + window.getComputedStyle(toolbarbutton).getPropertyValue("fill"), + currentColor, + "Button fill color should be currentColor when no icon color specified." + ); + + let starButton = document.querySelector("#star-button"); + starButton.setAttribute("starred", "true"); + let starComputedStyle = window.getComputedStyle(starButton); + Assert.equal( + starComputedStyle.getPropertyValue( + "--lwt-toolbarbutton-icon-fill-attention" + ), + "", + "Icon attention fill should not be set when the value is not specified in the manifest." + ); + starButton.removeAttribute("starred"); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbars.js b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbars.js new file mode 100644 index 0000000000..ee31d80888 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbars.js @@ -0,0 +1,105 @@ +"use strict"; + +// This test checks whether applied WebExtension themes that attempt to change +// the background color of toolbars are applied properly. + +add_task(async function test_support_toolbar_property() { + const TOOLBAR_COLOR = "#ff00ff"; + const TOOLBAR_TEXT_COLOR = "#9400ff"; + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + toolbar: TOOLBAR_COLOR, + toolbar_text: TOOLBAR_TEXT_COLOR, + }, + }, + }, + }); + + let toolbox = document.querySelector("#navigator-toolbox"); + let toolbars = [ + ...toolbox.querySelectorAll("toolbar:not(#TabsToolbar)"), + ].filter(toolbar => { + let bounds = toolbar.getBoundingClientRect(); + return bounds.width > 0 && bounds.height > 0; + }); + + let transitionPromise = waitForTransition(toolbars[0], "background-color"); + await extension.startup(); + await transitionPromise; + + info(`Checking toolbar colors for ${toolbars.length} toolbars.`); + for (let toolbar of toolbars) { + info(`Testing ${toolbar.id}`); + Assert.equal( + window.getComputedStyle(toolbar).backgroundColor, + hexToCSS(TOOLBAR_COLOR), + "Toolbar background color should be set." + ); + Assert.equal( + window.getComputedStyle(toolbar).color, + hexToCSS(TOOLBAR_TEXT_COLOR), + "Toolbar text color should be set." + ); + } + + info("Checking selected tab colors"); + let selectedTab = document.querySelector(".tabbrowser-tab[selected]"); + Assert.equal( + window.getComputedStyle(selectedTab).color, + hexToCSS(TOOLBAR_TEXT_COLOR), + "Selected tab text color should be set." + ); + + await extension.unload(); +}); + +add_task(async function test_bookmark_text_property() { + const TOOLBAR_COLOR = [255, 0, 255]; + const TOOLBAR_TEXT_COLOR = [48, 0, 255]; + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + colors: { + frame: ACCENT_COLOR, + tab_background_text: TEXT_COLOR, + toolbar: TOOLBAR_COLOR, + bookmark_text: TOOLBAR_TEXT_COLOR, + }, + }, + }, + }); + + await extension.startup(); + + let toolbox = document.querySelector("#navigator-toolbox"); + let toolbars = [ + ...toolbox.querySelectorAll("toolbar:not(#TabsToolbar)"), + ].filter(toolbar => { + let bounds = toolbar.getBoundingClientRect(); + return bounds.width > 0 && bounds.height > 0; + }); + + info(`Checking toolbar colors for ${toolbars.length} toolbars.`); + for (let toolbar of toolbars) { + info(`Testing ${toolbar.id}`); + Assert.equal( + window.getComputedStyle(toolbar).color, + rgbToCSS(TOOLBAR_TEXT_COLOR), + "bookmark_text should be an alias for toolbar_text" + ); + } + + info("Checking selected tab colors"); + let selectedTab = document.querySelector(".tabbrowser-tab[selected]"); + Assert.equal( + window.getComputedStyle(selectedTab).color, + rgbToCSS(TOOLBAR_TEXT_COLOR), + "Selected tab text color should be set." + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_warnings.js b/toolkit/components/extensions/test/browser/browser_ext_themes_warnings.js new file mode 100644 index 0000000000..64155006d9 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_themes_warnings.js @@ -0,0 +1,143 @@ +"use strict"; + +const { AddonSettings } = ChromeUtils.import( + "resource://gre/modules/addons/AddonSettings.jsm" +); + +// This test checks that theme warnings are properly emitted. + +function waitForConsole(task, message) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async resolve => { + SimpleTest.monitorConsole(resolve, [ + { + message: new RegExp(message), + }, + ]); + await task(); + SimpleTest.endMonitorConsole(); + }); +} + +add_task(async function setup() { + SimpleTest.waitForExplicitFinish(); +}); + +add_task(async function test_static_theme() { + for (const property of ["colors", "images", "properties"]) { + const extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + [property]: { + such_property: "much_wow", + }, + }, + }, + }); + await waitForConsole( + extension.startup, + `Unrecognized theme property found: ${property}.such_property` + ); + await extension.unload(); + } +}); + +add_task(async function test_dynamic_theme() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["theme"], + }, + background() { + browser.test.onMessage.addListener((msg, details) => { + if (msg === "update-theme") { + browser.theme.update(details).then(() => { + browser.test.sendMessage("theme-updated"); + }); + } else { + browser.theme.reset().then(() => { + browser.test.sendMessage("theme-reset"); + }); + } + }); + }, + }); + + await extension.startup(); + + for (const property of ["colors", "images", "properties"]) { + extension.sendMessage("update-theme", { + [property]: { + such_property: "much_wow", + }, + }); + await waitForConsole( + () => extension.awaitMessage("theme-updated"), + `Unrecognized theme property found: ${property}.such_property` + ); + } + + await extension.unload(); +}); + +add_task(async function test_experiments_enabled() { + info("Testing that experiments are handled correctly on nightly and deved"); + + const extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + properties: { + such_property: "much_wow", + unknown_property: "very_unknown", + }, + }, + theme_experiment: { + properties: { + such_property: "--such-property", + }, + }, + }, + }); + if (!AddonSettings.EXPERIMENTS_ENABLED) { + await waitForConsole( + extension.startup, + "This extension is not allowed to run theme experiments" + ); + } else { + await waitForConsole( + extension.startup, + "Unrecognized theme property found: properties.unknown_property" + ); + } + await extension.unload(); +}); + +add_task(async function test_experiments_disabled() { + await SpecialPowers.pushPrefEnv({ + set: [["extensions.experiments.enabled", false]], + }); + + info( + "Testing that experiments are handled correctly when experiements pref is disabled" + ); + + const extension = ExtensionTestUtils.loadExtension({ + manifest: { + theme: { + properties: { + such_property: "much_wow", + }, + }, + theme_experiment: { + properties: { + such_property: "--such-property", + }, + }, + }, + }); + await waitForConsole( + extension.startup, + "This extension is not allowed to run theme experiments" + ); + await extension.unload(); + await SpecialPowers.popPrefEnv(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_thumbnails_bg_extension.js b/toolkit/components/extensions/test/browser/browser_ext_thumbnails_bg_extension.js new file mode 100644 index 0000000000..96a2216067 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_thumbnails_bg_extension.js @@ -0,0 +1,94 @@ +"use strict"; + +/* import-globals-from ../../../thumbnails/test/head.js */ +loadTestSubscript("../../../thumbnails/test/head.js"); + +// The service that creates thumbnails of webpages in the background loads a +// web page in the background (with several features disabled). Extensions +// should be able to observe requests, but not run content scripts. +add_task(async function test_thumbnails_background_visibility_to_extensions() { + const iframeUrl = "http://example.com/?iframe"; + const testPageUrl = bgTestPageURL({ iframe: iframeUrl }); + // ^ testPageUrl is http://mochi.test:8888/.../thumbnails_background.sjs?... + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + content_scripts: [ + { + // ":8888" omitted due to bug 1362809. + matches: [ + "http://mochi.test/*/thumbnails_background.sjs*", + "http://example.com/?iframe*", + ], + js: ["contentscript.js"], + run_at: "document_start", + all_frames: true, + }, + ], + permissions: [ + "webRequest", + "webRequestBlocking", + "http://example.com/*", + "http://mochi.test/*", + ], + }, + files: { + "contentscript.js": () => { + // Content scripts are not expected to be run in the page of the + // thumbnail service, so this should never execute. + new Image().src = "http://example.com/?unexpected-content-script"; + browser.test.fail("Content script ran in thumbs, unexpectedly."); + }, + }, + background() { + let requests = []; + browser.webRequest.onBeforeRequest.addListener( + ({ url, tabId, frameId, type }) => { + browser.test.assertEq(-1, tabId, "Thumb page is not a tab"); + // We want to know if frameId is 0 or non-negative (or possibly -1). + if (type === "sub_frame") { + browser.test.assertTrue(frameId > 0, `frame ${frameId} for ${url}`); + } else { + browser.test.assertEq(0, frameId, `frameId for ${type} ${url}`); + } + requests.push({ type, url }); + }, + { + types: ["main_frame", "sub_frame", "image"], + urls: ["*://*/*"], + }, + ["blocking"] + ); + browser.test.onMessage.addListener(msg => { + browser.test.assertEq("get-results", msg, "expected message"); + browser.test.sendMessage("webRequest-results", requests); + }); + }, + }); + + await extension.startup(); + + ok(!thumbnailExists(testPageUrl), "Thumbnail should not be cached yet."); + + await bgCapture(testPageUrl); + ok(thumbnailExists(testPageUrl), "Thumbnail should be cached after capture"); + removeThumbnail(testPageUrl); + + extension.sendMessage("get-results"); + Assert.deepEqual( + await extension.awaitMessage("webRequest-results"), + [ + { + type: "main_frame", + url: testPageUrl, + }, + { + type: "sub_frame", + url: iframeUrl, + }, + ], + "Expected requests via webRequest" + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_webRequest_redirect_mozextension.js b/toolkit/components/extensions/test/browser/browser_ext_webRequest_redirect_mozextension.js new file mode 100644 index 0000000000..674a10a5ef --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_webRequest_redirect_mozextension.js @@ -0,0 +1,48 @@ +"use strict"; + +// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1573456 +add_task(async function test_mozextension_page_loaded_in_extension_process() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: [ + "webRequest", + "webRequestBlocking", + "https://example.com/*", + ], + web_accessible_resources: ["test.html"], + }, + files: { + "test.html": '', + "test.js": () => { + browser.test.assertTrue( + browser.webRequest, + "webRequest API should be available" + ); + + browser.test.sendMessage("test_done"); + }, + }, + background: () => { + browser.webRequest.onBeforeRequest.addListener( + () => { + return { + redirectUrl: browser.runtime.getURL("test.html"), + }; + }, + { urls: ["*://*/redir"] }, + ["blocking"] + ); + }, + }); + await extension.startup(); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.com/redir" + ); + + await extension.awaitMessage("test_done"); + + await extension.unload(); + BrowserTestUtils.removeTab(tab); +}); diff --git a/toolkit/components/extensions/test/browser/browser_ext_windows_popup_title.js b/toolkit/components/extensions/test/browser/browser_ext_windows_popup_title.js new file mode 100644 index 0000000000..21ef6bf460 --- /dev/null +++ b/toolkit/components/extensions/test/browser/browser_ext_windows_popup_title.js @@ -0,0 +1,61 @@ +"use strict"; + +// Check that extension popup windows contain the name of the extension +// as well as the title of the loaded document, but not the URL. +add_task(async function test_popup_title() { + const name = "custom_title_number_9_please"; + const docTitle = "popup-test-title"; + + const extension = ExtensionTestUtils.loadExtension({ + manifest: { + name, + permissions: ["tabs"], + }, + + async background() { + let popup; + + // Called after the popup loads + browser.runtime.onMessage.addListener(async ({ docTitle }) => { + const { id } = await popup; + const { title } = await browser.windows.get(id); + browser.windows.remove(id); + + browser.test.assertTrue( + title.includes(name), + "popup title must include extension name" + ); + browser.test.assertTrue( + title.includes(docTitle), + "popup title must include extension document title" + ); + browser.test.assertFalse( + title.includes("moz-extension:"), + "popup title must not include extension URL" + ); + + browser.test.notifyPass("popup-window-title"); + }); + + popup = browser.windows.create({ + url: "/index.html", + type: "popup", + }); + }, + files: { + "index.html": ` + + ${docTitle}, + + `, + "index.js": `addEventListener( + "load", + () => browser.runtime.sendMessage({docTitle: document.title}) + );`, + }, + }); + + await extension.startup(); + await extension.awaitFinish("popup-window-title"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/browser/data/test-download.txt b/toolkit/components/extensions/test/browser/data/test-download.txt new file mode 100644 index 0000000000..f416e0e291 --- /dev/null +++ b/toolkit/components/extensions/test/browser/data/test-download.txt @@ -0,0 +1 @@ +test download content diff --git a/toolkit/components/extensions/test/browser/data/test_downloads_referrer.html b/toolkit/components/extensions/test/browser/data/test_downloads_referrer.html new file mode 100644 index 0000000000..85410abfcd --- /dev/null +++ b/toolkit/components/extensions/test/browser/data/test_downloads_referrer.html @@ -0,0 +1,10 @@ + + + + + Test downloads referrer + + + test link + + diff --git a/toolkit/components/extensions/test/browser/head.js b/toolkit/components/extensions/test/browser/head.js new file mode 100644 index 0000000000..0dd1a1666c --- /dev/null +++ b/toolkit/components/extensions/test/browser/head.js @@ -0,0 +1,103 @@ +/* exported ACCENT_COLOR, BACKGROUND, ENCODED_IMAGE_DATA, FRAME_COLOR, TAB_TEXT_COLOR, + TEXT_COLOR, TAB_BACKGROUND_TEXT_COLOR, imageBufferFromDataURI, hexToCSS, hexToRGB, testBorderColor, + waitForTransition, loadTestSubscript */ + +"use strict"; + +const BACKGROUND = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0" + + "DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; +const ENCODED_IMAGE_DATA = + "iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0h" + + "STQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAdhwAAHYcBj+XxZQAAB5dJREFUSMd" + + "91vmTlEcZB/Bvd7/vO+/ce83O3gfLDUsC4VgIghBUEo2GM9GCFTaQBEISA1qIEVNQ4aggJDGIgAGTlFUKKcqKQpVHaQyny7FrCMiywp4ze+/Mzs67M/P" + + "O+3a3v5jdWo32H/B86vv0U083weecV3+0C8lkEh6PhzS3tuLkieMSAKo3fW9Mb1eoUtM0jemerukLllzrbGlKheovUpeqkmt113hPfx/27tyFF7+/bbg" + + "e+U9g20s7kEwmMXXGNLrp2fWi4V5z/tFjJ3fWX726INbfU2xx0yelkJAKdJf3Xl5+2QcPTpv2U0JZR+u92+xvly5ygKDm20/hlX17/jvB6VNnIKXEOyd" + + "O0iFh4PLVy0XV1U83Vk54QI7JK+bl+UE5vjRfTCzJ5eWBTFEayBLjisvljKmzwmtWrVkEAPNmVrEZkyfh+fU1n59k//7X4Fbz8MK2DRSAWLNq/Yc36y9" + + "+3UVMsyAYVPMy/MTvdBKvriJhphDq6xa9vf0i1GMwPVhM5s9bsLw/EvtN2kywwnw/nzBuLDZs2z4auXGjHuvWbmBQdT5v7qytn165fLCyyGtXTR6j5GV" + + "kIsvlBCwTVNgQhMKCRDQ2iIbmJv7BpU+Ykl02UFOzdt6gkbzTEQ5Rl2KL3W8eGUE+/ssFXK+rJQ8vWigLgjk5z9ZsvpOniJzVi+ZKTUhCuATTKCjhoLA" + + "hhQAsjrSZBJcm7rZ22O+ev6mMmTLj55eu1T+jU8GOH/kJf2TZCiifIQsXfwEbN2yktxoaeYbf93DKSORMnTOZE0aZaVlQGYVKJCgjEJSCcgLB0xDERjI" + + "NFBUEaXmuB20t95eEutr0xrufpo4eepMAkMPIxx+dx9at25EWQNXsh77q0Bzwen0ShEF32HCrCpjksAWHFAKqokFhgEJt2DKJeFoQv8eDuz3duaseXZY" + + "dixthaQ+NRlRCcKO+FgCweP68wswMF/yZWcTkNpLJFAZEGi6XC07NCUIIoqaNSLQfFALCEpCSEL/bK/wuw+12sKlDQzKs6k5yZt+rI+2aNKUSNdUbSSQ" + + "Wh2mJP46rGPeYrjtkY0M7jFgciUQCiqqgrCAfBTle3G9rR1NHN3SnDq9Lg49QlBQEcbfbQCKZlhQEDkXBih27RpDOrmacfP8YB4CfHT7uNXrCMFM2FdD" + + "BVQ5TE/A5HbDSJoSpQXAbXm8A4b5+gKrwulU4KKEBnwuzHpiQu+n1jQoQsM+9cYQMT9fvf/FLBYTaDqdzbfgft95PKzbPyQqwnlAXGkJtGIgNYnJpMfw" + + "OghLG0GJE0ZdiaOnsQ16OD6XZLkiRROdAgud5sxk8ridsy/pQU1VlOIkZN6QtAGnx0FA0AtXvIA4C5OX4kOWbiLRhQBDApTmgJuLwEonMgBvjgpmgjIE" + + "hhX7DAIVKNeqE05/dJbgEgRy5eOJ1ieXr1gJA7ZNLTrVVlAZLyopLJAUlHsrAMrwwrRQ4t6E5VHgSBExjcGpO0JQNizCE05a41dhOi+cXXVm144e1AHD" + + "1vXfFMOLy+KSHEDoEJLZ8s+ZWKpUusWwpFKiMUQ4jbiAaj8Hp9oExBsMCUpEIfD6JLKZjKJVGV3RIZGdm0qxA5qmz+/cgMhBVuuMRewRRGF7fe4BYHMg" + + "N5LxdV3vhy1EjrrjA5GAyTuKpFHricfS0dSDNCQRPoSyQgSSPI+UBEtwShiWUQEHw5mMvbz4JRcXvDr3B3dBG1sq5X53GlMcX4JWVTyvRQcOumDD2vfK" + + "cjOqiQDZPGBF2ryUEnjRhJlP4d6/BiQ1TABPKiyQhgtzvjPCJlQ/OGRwauqESSUPX68U3Vi4fGeH83Hwc3bYHBWUV0m0k4HB6z7aGu6sznDos00R3exg" + + "l5ZMwc+FMaJoKKxHFnbo6DMYiELBlqLOXDBq8dsvuPTfKALpwdbX42iMLsHjLd0Zv4RNvvY1wZxdZunyVDGZm6D/47sv12RqbmOPVhG5LGnAH4S8sgu7" + + "1oK/pn2BWAoYw0dDbaTd19iqlZROejwzEjqgMSuXUifak8jF49JnNI0kAoGrBfET7+uXOrS+y5ta21JzZsw7faW45XJaXxSvyAtTpkOi483fwtAWP1wt" + + "vrhvd/VFx+26zojr9Les2PnfaTNu4cuGvvKe9BVv3/RgARiNTpk/Hod17MWikxcqzzfhK/+1jL2xc+YQAX1ISDHLV7WTpQQaLcASzPEiB41ZrmEeHkrT" + + "Q49uz/aXn+iilLKXq/MmlS0e/jFcuX4SmaQAAKSXlnIvVy1aQ6EBMFgRyCznDpfGFwdKqirF2tu5SdIeGrkiP+KS5yb7dHtIKsnI++kP9rS8RQvjmxxe" + + "jePxD2HHwwP9FdCllurGhUbx14CAbiMc4Y2qVJqwLbo0qfpdLSilILB4Xg0mT6h7vnSWzZn9RoaynobWF3K6rk1NmzMWZ83/+37+V4a1cVg5JACYF45b" + + "FGVVWOFS2V1HUCjOdBqW0Q9fYb7N9/tcSptnldjpott8rFEXBO+f+NKrWMHL9Wu1nSUAIAaUUa59aAyE43E4X3bD8W6K5K6x1h1snRaMDJDuQf7+vrzf" + + "eG+mgfrcLHh3C79bx6wttGEqERiH/AjPohWMouv2ZAAAAAElFTkSuQmCC"; +const ACCENT_COLOR = "#a14040"; +const TEXT_COLOR = "#fac96e"; +// For testing aliases of the colors above: +const FRAME_COLOR = [71, 105, 91]; +const TAB_BACKGROUND_TEXT_COLOR = [207, 221, 192, 0.9]; + +function hexToRGB(hex) { + if (!hex) { + return null; + } + hex = parseInt(hex.indexOf("#") > -1 ? hex.substring(1) : hex, 16); + return [hex >> 16, (hex & 0x00ff00) >> 8, hex & 0x0000ff]; +} + +function rgbToCSS(rgb) { + return `rgb(${rgb.join(", ")})`; +} + +function hexToCSS(hex) { + if (!hex) { + return null; + } + return rgbToCSS(hexToRGB(hex)); +} + +function imageBufferFromDataURI(encodedImageData) { + let decodedImageData = atob(encodedImageData); + return Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer; +} + +function waitForTransition(element, propertyName) { + return BrowserTestUtils.waitForEvent( + element, + "transitionend", + false, + event => { + return event.target == element && event.propertyName == propertyName; + } + ); +} + +function testBorderColor(element, expected) { + let computedStyle = window.getComputedStyle(element); + Assert.equal( + computedStyle.borderLeftColor, + hexToCSS(expected), + "Element left border color should be set." + ); + Assert.equal( + computedStyle.borderRightColor, + hexToCSS(expected), + "Element right border color should be set." + ); + Assert.equal( + computedStyle.borderTopColor, + hexToCSS(expected), + "Element top border color should be set." + ); + Assert.equal( + computedStyle.borderBottomColor, + hexToCSS(expected), + "Element bottom border color should be set." + ); +} + +function loadTestSubscript(filePath) { + Services.scriptloader.loadSubScript(new URL(filePath, gTestPath).href, this); +} diff --git a/toolkit/components/extensions/test/browser/head_serviceworker.js b/toolkit/components/extensions/test/browser/head_serviceworker.js new file mode 100644 index 0000000000..012dcfe284 --- /dev/null +++ b/toolkit/components/extensions/test/browser/head_serviceworker.js @@ -0,0 +1,123 @@ +"use strict"; + +/* exported assert_background_serviceworker_pref_enabled, + * getBackgroundServiceWorkerRegistration, + * getServiceWorkerInfo, getServiceWorkerState, + * waitForServiceWorkerRegistrationsRemoved, waitForServiceWorkerTerminated + */ + +async function assert_background_serviceworker_pref_enabled() { + is( + WebExtensionPolicy.backgroundServiceWorkerEnabled, + true, + "Expect extensions.backgroundServiceWorker.enabled to be true" + ); +} + +// Return the name of the enum corresponding to the worker's state (ex: "STATE_ACTIVATED") +// because nsIServiceWorkerInfo doesn't currently provide a comparable string-returning getter. +function getServiceWorkerState(workerInfo) { + const map = Object.keys(workerInfo) + .filter(k => k.startsWith("STATE_")) + .reduce((map, name) => { + map.set(workerInfo[name], name); + return map; + }, new Map()); + return map.has(workerInfo.state) + ? map.get(workerInfo.state) + : "state: ${workerInfo.state}"; +} + +function getServiceWorkerInfo(swRegInfo) { + const { + evaluatingWorker, + installingWorker, + waitingWorker, + activeWorker, + } = swRegInfo; + return evaluatingWorker || installingWorker || waitingWorker || activeWorker; +} + +async function waitForServiceWorkerTerminated(swRegInfo) { + info(`Wait all ${swRegInfo.scope} workers to be terminated`); + + try { + await BrowserTestUtils.waitForCondition( + () => !getServiceWorkerInfo(swRegInfo) + ); + } catch (err) { + const workerInfo = getServiceWorkerInfo(swRegInfo); + if (workerInfo) { + ok( + false, + `Error while waiting for workers for scope ${swRegInfo.scope} to be terminated. ` + + `Found a worker in state: ${getServiceWorkerState(workerInfo)}` + ); + return; + } + + throw err; + } +} + +function getBackgroundServiceWorkerRegistration(extension) { + const policy = WebExtensionPolicy.getByHostname(extension.uuid); + const expectedSWScope = policy.getURL("/"); + const expectedScriptURL = policy.extension.backgroundWorkerScript || ""; + + ok( + expectedScriptURL.startsWith(expectedSWScope), + `Extension does include a valid background.service_worker: ${expectedScriptURL}` + ); + + const swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService( + Ci.nsIServiceWorkerManager + ); + + let swReg; + let regs = swm.getAllRegistrations(); + + for (let i = 0; i < regs.length; i++) { + let reg = regs.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo); + if (reg.scriptSpec === expectedScriptURL) { + swReg = reg; + break; + } + } + + ok(swReg, `Found service worker registration for ${expectedScriptURL}`); + + is( + swReg.scope, + expectedSWScope, + "The extension background worker registration has the expected scope URL" + ); + + return swReg; +} + +async function waitForServiceWorkerRegistrationsRemoved(extension) { + info(`Wait ${extension.id} service worker registration to be deleted`); + const swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService( + Ci.nsIServiceWorkerManager + ); + let baseURI = Services.io.newURI(`moz-extension://${extension.uuid}/`); + let principal = Services.scriptSecurityManager.createContentPrincipal( + baseURI, + {} + ); + + await BrowserTestUtils.waitForCondition(() => { + let regs = swm.getAllRegistrations(); + + for (let i = 0; i < regs.length; i++) { + let reg = regs.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo); + if (principal.equals(reg.principal)) { + return false; + } + } + + info(`All ${extension.id} service worker registrations are gone`); + return true; + }, `All ${extension.id} service worker registrations should be deleted`); +} diff --git a/toolkit/components/extensions/test/marionette/data/extension-with-bg-sw/manifest.json b/toolkit/components/extensions/test/marionette/data/extension-with-bg-sw/manifest.json new file mode 100644 index 0000000000..5ed13a1b18 --- /dev/null +++ b/toolkit/components/extensions/test/marionette/data/extension-with-bg-sw/manifest.json @@ -0,0 +1,11 @@ +{ + "manifest_version": 2, + "name": "Test Extension with Background Service Worker", + "version": "1", + "applications": { + "gecko": { "id": "extension-with-bg-sw@test" } + }, + "background": { + "service_worker": "sw.js" + } +} \ No newline at end of file diff --git a/toolkit/components/extensions/test/marionette/data/extension-with-bg-sw/sw.js b/toolkit/components/extensions/test/marionette/data/extension-with-bg-sw/sw.js new file mode 100644 index 0000000000..2282e6a64b --- /dev/null +++ b/toolkit/components/extensions/test/marionette/data/extension-with-bg-sw/sw.js @@ -0,0 +1,3 @@ +"use strict"; + +dump("extension-with-bg-sw: sw.js loaded"); diff --git a/toolkit/components/extensions/test/marionette/manifest.ini b/toolkit/components/extensions/test/marionette/manifest.ini new file mode 100644 index 0000000000..78006ccf1e --- /dev/null +++ b/toolkit/components/extensions/test/marionette/manifest.ini @@ -0,0 +1 @@ +[test_extension_serviceworkers_purged_on_pref_disabled.py] \ No newline at end of file diff --git a/toolkit/components/extensions/test/marionette/test_extension_serviceworkers_purged_on_pref_disabled.py b/toolkit/components/extensions/test/marionette/test_extension_serviceworkers_purged_on_pref_disabled.py new file mode 100644 index 0000000000..64e376adf0 --- /dev/null +++ b/toolkit/components/extensions/test/marionette/test_extension_serviceworkers_purged_on_pref_disabled.py @@ -0,0 +1,82 @@ +# 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/. + +from marionette_driver import Wait +from marionette_driver.addons import Addons +from marionette_harness import MarionetteTestCase + +import os + +EXT_ID = "extension-with-bg-sw@test" +EXT_DIR_PATH = "extension-with-bg-sw" +PREF_BG_SW_ENABLED = "extensions.backgroundServiceWorker.enabled" + + +class PurgeExtensionServiceWorkersOnPrefDisabled(MarionetteTestCase): + def setUp(self): + super(PurgeExtensionServiceWorkersOnPrefDisabled, self).setUp() + self.test_extension_id = EXT_ID + # Flip the "mirror: once" pref and restart Firefox to be able + # to run the extension successfully. + self.marionette.set_pref(PREF_BG_SW_ENABLED, True) + self.marionette.restart(in_app=True) + + def tearDown(self): + self.marionette.restart(clean=True) + super(PurgeExtensionServiceWorkersOnPrefDisabled, self).tearDown() + + def test_unregistering_service_worker_when_clearing_data(self): + self.install_extension_with_service_worker() + + # Flip the pref to false and restart again to verify that the + # service worker registration has been removed as expected. + self.marionette.set_pref(PREF_BG_SW_ENABLED, False) + self.marionette.restart(in_app=True) + self.assertFalse(self.is_extension_service_worker_registered) + + def install_extension_with_service_worker(self): + addons = Addons(self.marionette) + test_extension_path = os.path.join( + os.path.dirname(self.filepath), "data", EXT_DIR_PATH + ) + addons.install(test_extension_path, temp=True) + self.test_extension_base_url = self.get_extension_url() + Wait(self.marionette).until( + lambda _: self.is_extension_service_worker_registered, + message="Wait the extension service worker to be registered", + ) + + def get_extension_url(self, path="/"): + with self.marionette.using_context("chrome"): + return self.marionette.execute_script( + """ + let policy = WebExtensionPolicy.getByID(arguments[0]); + return policy.getURL(arguments[1]) + """, + script_args=(self.test_extension_id, path), + ) + + @property + def is_extension_service_worker_registered(self): + with self.marionette.using_context("chrome"): + return self.marionette.execute_script( + """ + let serviceWorkerManager = Cc["@mozilla.org/serviceworkers/manager;1"].getService( + Ci.nsIServiceWorkerManager + ); + + let serviceWorkers = serviceWorkerManager.getAllRegistrations(); + for (let i = 0; i < serviceWorkers.length; i++) { + let sw = serviceWorkers.queryElementAt( + i, + Ci.nsIServiceWorkerRegistrationInfo + ); + if (sw.scope == arguments[0]) { + return true; + } + } + return false; + """, + script_args=(self.test_extension_base_url,), + ) diff --git a/toolkit/components/extensions/test/mochitest/.eslintrc.js b/toolkit/components/extensions/test/mochitest/.eslintrc.js new file mode 100644 index 0000000000..a776405c9d --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/.eslintrc.js @@ -0,0 +1,12 @@ +"use strict"; + +module.exports = { + env: { + browser: true, + webextensions: true, + }, + + rules: { + "no-shadow": 0, + }, +}; diff --git a/toolkit/components/extensions/test/mochitest/chrome.ini b/toolkit/components/extensions/test/mochitest/chrome.ini new file mode 100644 index 0000000000..209f11b864 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/chrome.ini @@ -0,0 +1,37 @@ +[DEFAULT] +support-files = + chrome_cleanup_script.js + head.js + head_cookies.js + file_image_good.png + file_image_great.png + file_sample.html + file_with_images.html + webrequest_chromeworker.js + webrequest_test.jsm +prefs = + security.mixed_content.upgrade_display_content=false +tags = webextensions in-process-webextensions + +# NO NEW TESTS. mochitest-chrome does not run under e10s, avoid adding new +# tests here unless absolutely necessary. + +[test_chrome_ext_contentscript_data_uri.html] +[test_chrome_ext_contentscript_telemetry.html] +skip-if = (os == 'linux' && bits == 64) #Bug 1393920 +[test_chrome_ext_contentscript_unrecognizedprop_warning.html] +[test_chrome_ext_downloads_open.html] +[test_chrome_ext_downloads_saveAs.html] +skip-if = (verify && !debug && (os == 'win')) || (os == 'android') +[test_chrome_ext_downloads_uniquify.html] +[test_chrome_ext_permissions.html] +skip-if = os == 'android' # Bug 1350559 +[test_chrome_ext_trackingprotection.html] +[test_chrome_ext_webnavigation_resolved_urls.html] +[test_chrome_ext_webrequest_background_events.html] +[test_chrome_ext_webrequest_host_permissions.html] +skip-if = verify +[test_chrome_ext_webrequest_mozextension.html] +skip-if = true # Bug 1404172 +[test_chrome_native_messaging_paths.html] +skip-if = os != "mac" && os != "linux" diff --git a/toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js b/toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js new file mode 100644 index 0000000000..397996b15c --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js @@ -0,0 +1,66 @@ +"use strict"; + +/* global addMessageListener, sendAsyncMessage */ + +const { AppConstants } = ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" +); +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +let listener = msg => { + void (msg instanceof Ci.nsIConsoleMessage); + dump(`Console message: ${msg}\n`); +}; + +Services.console.registerListener(listener); + +let getBrowserApp, getTabBrowser; +if (AppConstants.MOZ_BUILD_APP === "mobile/android") { + getBrowserApp = win => win.BrowserApp; + getTabBrowser = tab => tab.browser; +} else { + getBrowserApp = win => win.gBrowser; + getTabBrowser = tab => tab.linkedBrowser; +} + +function* iterBrowserWindows() { + for (let win of Services.wm.getEnumerator("navigator:browser")) { + if (!win.closed && getBrowserApp(win)) { + yield win; + } + } +} + +let initialTabs = new Map(); +for (let win of iterBrowserWindows()) { + initialTabs.set(win, new Set(getBrowserApp(win).tabs)); +} + +addMessageListener("check-cleanup", extensionId => { + Services.console.unregisterListener(listener); + + let results = { + extraWindows: [], + extraTabs: [], + }; + + for (let win of iterBrowserWindows()) { + if (initialTabs.has(win)) { + let tabs = initialTabs.get(win); + + for (let tab of getBrowserApp(win).tabs) { + if (!tabs.has(tab)) { + results.extraTabs.push(getTabBrowser(tab).currentURI.spec); + } + } + } else { + results.extraWindows.push( + Array.from(win.gBrowser.tabs, tab => getTabBrowser(tab).currentURI.spec) + ); + } + } + + initialTabs = null; + + sendAsyncMessage("cleanup-results", results); +}); diff --git a/toolkit/components/extensions/test/mochitest/chrome_head.js b/toolkit/components/extensions/test/mochitest/chrome_head.js new file mode 100644 index 0000000000..3918c74e44 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/chrome_head.js @@ -0,0 +1 @@ +"use strict"; diff --git a/toolkit/components/extensions/test/mochitest/file_WebNavigation_page1.html b/toolkit/components/extensions/test/mochitest/file_WebNavigation_page1.html new file mode 100644 index 0000000000..663ebc6112 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_WebNavigation_page1.html @@ -0,0 +1,12 @@ + + + + + + + +
+
+ + + diff --git a/toolkit/components/extensions/test/mochitest/file_WebNavigation_page2.html b/toolkit/components/extensions/test/mochitest/file_WebNavigation_page2.html new file mode 100644 index 0000000000..cc1acc83d6 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_WebNavigation_page2.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/file_WebNavigation_page3.html b/toolkit/components/extensions/test/mochitest/file_WebNavigation_page3.html new file mode 100644 index 0000000000..a0a26a2e9d --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_WebNavigation_page3.html @@ -0,0 +1,9 @@ + + + + + +click me + + + diff --git a/toolkit/components/extensions/test/mochitest/file_WebRequest_page3.html b/toolkit/components/extensions/test/mochitest/file_WebRequest_page3.html new file mode 100644 index 0000000000..24c7a42986 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_WebRequest_page3.html @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/file_contains_iframe.html b/toolkit/components/extensions/test/mochitest/file_contains_iframe.html new file mode 100644 index 0000000000..2b9344f463 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_contains_iframe.html @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/file_contains_img.html b/toolkit/components/extensions/test/mochitest/file_contains_img.html new file mode 100644 index 0000000000..c1112acbd8 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_contains_img.html @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/file_contentscript_activeTab.html b/toolkit/components/extensions/test/mochitest/file_contentscript_activeTab.html new file mode 100644 index 0000000000..6c1675cb47 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_contentscript_activeTab.html @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/file_contentscript_activeTab2.html b/toolkit/components/extensions/test/mochitest/file_contentscript_activeTab2.html new file mode 100644 index 0000000000..3b102b3d67 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_contentscript_activeTab2.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/file_contentscript_iframe.html b/toolkit/components/extensions/test/mochitest/file_contentscript_iframe.html new file mode 100644 index 0000000000..dda5169d69 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_contentscript_iframe.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/file_green.html b/toolkit/components/extensions/test/mochitest/file_green.html new file mode 100644 index 0000000000..20755c5b56 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_green.html @@ -0,0 +1,3 @@ + +Super green test page + diff --git a/toolkit/components/extensions/test/mochitest/file_image_bad.png b/toolkit/components/extensions/test/mochitest/file_image_bad.png new file mode 100644 index 0000000000..4c3be50847 Binary files /dev/null and b/toolkit/components/extensions/test/mochitest/file_image_bad.png differ diff --git a/toolkit/components/extensions/test/mochitest/file_image_good.png b/toolkit/components/extensions/test/mochitest/file_image_good.png new file mode 100644 index 0000000000..769c636340 Binary files /dev/null and b/toolkit/components/extensions/test/mochitest/file_image_good.png differ diff --git a/toolkit/components/extensions/test/mochitest/file_image_great.png b/toolkit/components/extensions/test/mochitest/file_image_great.png new file mode 100644 index 0000000000..769c636340 Binary files /dev/null and b/toolkit/components/extensions/test/mochitest/file_image_great.png differ diff --git a/toolkit/components/extensions/test/mochitest/file_image_redirect.png b/toolkit/components/extensions/test/mochitest/file_image_redirect.png new file mode 100644 index 0000000000..4c3be50847 Binary files /dev/null and b/toolkit/components/extensions/test/mochitest/file_image_redirect.png differ diff --git a/toolkit/components/extensions/test/mochitest/file_indexedDB.html b/toolkit/components/extensions/test/mochitest/file_indexedDB.html new file mode 100644 index 0000000000..65b7e0ad2f --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_indexedDB.html @@ -0,0 +1,28 @@ + + + + + + + + This is a test page. + + diff --git a/toolkit/components/extensions/test/mochitest/file_mixed.html b/toolkit/components/extensions/test/mochitest/file_mixed.html new file mode 100644 index 0000000000..f3c7dda580 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_mixed.html @@ -0,0 +1,13 @@ + + + + + + + + +
Sample text
+ + + + diff --git a/toolkit/components/extensions/test/mochitest/file_redirect_cors_bypass.html b/toolkit/components/extensions/test/mochitest/file_redirect_cors_bypass.html new file mode 100644 index 0000000000..b8fda2369a --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_redirect_cors_bypass.html @@ -0,0 +1,30 @@ + + + + 1450965 Skip Cors Check for Early WebExtention Redirects + + +
+    Fetching ...
+  
+ + + diff --git a/toolkit/components/extensions/test/mochitest/file_redirect_data_uri.html b/toolkit/components/extensions/test/mochitest/file_redirect_data_uri.html new file mode 100644 index 0000000000..fe8e5bea44 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_redirect_data_uri.html @@ -0,0 +1,9 @@ + + + + Bug 1434357: Allow Web Request API to redirect to data: URI + + +
foo
+ + diff --git a/toolkit/components/extensions/test/mochitest/file_remote_frame.html b/toolkit/components/extensions/test/mochitest/file_remote_frame.html new file mode 100644 index 0000000000..f1b9240092 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_remote_frame.html @@ -0,0 +1,20 @@ + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/file_sample.html b/toolkit/components/extensions/test/mochitest/file_sample.html new file mode 100644 index 0000000000..a20e49a1f0 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_sample.html @@ -0,0 +1,12 @@ + + + + + + + + +
Sample text
+ + + diff --git a/toolkit/components/extensions/test/mochitest/file_sample.txt b/toolkit/components/extensions/test/mochitest/file_sample.txt new file mode 100644 index 0000000000..c02cd532b1 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_sample.txt @@ -0,0 +1 @@ +Sample \ No newline at end of file diff --git a/toolkit/components/extensions/test/mochitest/file_sample.txt^headers^ b/toolkit/components/extensions/test/mochitest/file_sample.txt^headers^ new file mode 100644 index 0000000000..cb762eff80 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_sample.txt^headers^ @@ -0,0 +1 @@ +Access-Control-Allow-Origin: * diff --git a/toolkit/components/extensions/test/mochitest/file_script_bad.js b/toolkit/components/extensions/test/mochitest/file_script_bad.js new file mode 100644 index 0000000000..c425122c71 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_script_bad.js @@ -0,0 +1,3 @@ +"use strict"; + +window.failure = true; diff --git a/toolkit/components/extensions/test/mochitest/file_script_good.js b/toolkit/components/extensions/test/mochitest/file_script_good.js new file mode 100644 index 0000000000..14e959aa5c --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_script_good.js @@ -0,0 +1,12 @@ +"use strict"; + +window.success = window.success ? window.success + 1 : 1; + +{ + let scripts = document.getElementsByTagName("script"); + let url = new URL(scripts[scripts.length - 1].src); + let flag = url.searchParams.get("q"); + if (flag) { + window.postMessage(flag, "*"); + } +} diff --git a/toolkit/components/extensions/test/mochitest/file_script_redirect.js b/toolkit/components/extensions/test/mochitest/file_script_redirect.js new file mode 100644 index 0000000000..c425122c71 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_script_redirect.js @@ -0,0 +1,3 @@ +"use strict"; + +window.failure = true; diff --git a/toolkit/components/extensions/test/mochitest/file_script_xhr.js b/toolkit/components/extensions/test/mochitest/file_script_xhr.js new file mode 100644 index 0000000000..ad01f74253 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_script_xhr.js @@ -0,0 +1,9 @@ +"use strict"; + +var request = new XMLHttpRequest(); +request.open( + "get", + "http://mochi.test:8888/tests/toolkit/components/extensions/test/mochitest/xhr_resource", + false +); +request.send(); diff --git a/toolkit/components/extensions/test/mochitest/file_serviceWorker.html b/toolkit/components/extensions/test/mochitest/file_serviceWorker.html new file mode 100644 index 0000000000..d2b99769cc --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_serviceWorker.html @@ -0,0 +1,16 @@ + + + + + + + + This is a test page. + + diff --git a/toolkit/components/extensions/test/mochitest/file_simple_sandboxed_frame.html b/toolkit/components/extensions/test/mochitest/file_simple_sandboxed_frame.html new file mode 100644 index 0000000000..909a1f9e36 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_simple_sandboxed_frame.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/file_simple_sandboxed_subframe.html b/toolkit/components/extensions/test/mochitest/file_simple_sandboxed_subframe.html new file mode 100644 index 0000000000..a0a437d0eb --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_simple_sandboxed_subframe.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/file_simple_xhr.html b/toolkit/components/extensions/test/mochitest/file_simple_xhr.html new file mode 100644 index 0000000000..f6ef67277d --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_simple_xhr.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + +
+
+ + + diff --git a/toolkit/components/extensions/test/mochitest/file_webNavigation_frameRedirect.html b/toolkit/components/extensions/test/mochitest/file_webNavigation_frameRedirect.html new file mode 100644 index 0000000000..06dbd43741 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_webNavigation_frameRedirect.html @@ -0,0 +1,12 @@ + + + + + + + +
+
+ + + diff --git a/toolkit/components/extensions/test/mochitest/file_webNavigation_manualSubframe.html b/toolkit/components/extensions/test/mochitest/file_webNavigation_manualSubframe.html new file mode 100644 index 0000000000..307990714b --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_webNavigation_manualSubframe.html @@ -0,0 +1,12 @@ + + + + + + + +
+
+ + + diff --git a/toolkit/components/extensions/test/mochitest/file_webNavigation_manualSubframe_page1.html b/toolkit/components/extensions/test/mochitest/file_webNavigation_manualSubframe_page1.html new file mode 100644 index 0000000000..55bb7aa6ae --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_webNavigation_manualSubframe_page1.html @@ -0,0 +1,8 @@ + + + + +

page1

+ page2 + + diff --git a/toolkit/components/extensions/test/mochitest/file_webNavigation_manualSubframe_page2.html b/toolkit/components/extensions/test/mochitest/file_webNavigation_manualSubframe_page2.html new file mode 100644 index 0000000000..8f589f8bbd --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_webNavigation_manualSubframe_page2.html @@ -0,0 +1,7 @@ + + + + +

page2

+ + diff --git a/toolkit/components/extensions/test/mochitest/file_with_about_blank.html b/toolkit/components/extensions/test/mochitest/file_with_about_blank.html new file mode 100644 index 0000000000..af51c2e52a --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_with_about_blank.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/file_with_images.html b/toolkit/components/extensions/test/mochitest/file_with_images.html new file mode 100644 index 0000000000..6a3c090be2 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_with_images.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/file_with_xorigin_frame.html b/toolkit/components/extensions/test/mochitest/file_with_xorigin_frame.html new file mode 100644 index 0000000000..d0d2f02e2d --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/file_with_xorigin_frame.html @@ -0,0 +1,6 @@ + + + + +Load a cross-origin iframe from example.net

+ diff --git a/toolkit/components/extensions/test/mochitest/head.js b/toolkit/components/extensions/test/mochitest/head.js new file mode 100644 index 0000000000..2d26de34c7 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/head.js @@ -0,0 +1,123 @@ +"use strict"; + +/* exported AppConstants, Assert */ + +var { AppConstants } = SpecialPowers.Cu.import( + "resource://gre/modules/AppConstants.jsm", + {} +); + +let remote = SpecialPowers.getBoolPref("extensions.webextensions.remote"); +if (remote) { + // We don't want to reset this at the end of the test, so that we don't have + // to spawn a new extension child process for each test unit. + SpecialPowers.setIntPref("dom.ipc.keepProcessesAlive.extension", 1); +} + +{ + let chromeScript = SpecialPowers.loadChromeScript( + SimpleTest.getTestFileURL("chrome_cleanup_script.js") + ); + + SimpleTest.registerCleanupFunction(async () => { + await new Promise(resolve => setTimeout(resolve, 0)); + + chromeScript.sendAsyncMessage("check-cleanup"); + + let results = await chromeScript.promiseOneMessage("cleanup-results"); + chromeScript.destroy(); + + if (results.extraWindows.length || results.extraTabs.length) { + ok( + false, + `Test left extra windows or tabs: ${JSON.stringify(results)}\n` + ); + } + }); +} + +let Assert = { + // Cut-down version based on Assert.jsm. Only supports regexp and objects as + // the expected variables. + rejects(promise, expected, msg) { + return promise.then( + () => { + ok(false, msg); + }, + actual => { + let matched = false; + if (Object.prototype.toString.call(expected) == "[object RegExp]") { + if (expected.test(actual)) { + matched = true; + } + } else if (actual instanceof expected) { + matched = true; + } + + if (matched) { + ok(true, msg); + } else { + ok(false, `Unexpected exception for "${msg}": ${actual}`); + } + } + ); + }, +}; + +/* exported waitForLoad */ + +function waitForLoad(win) { + return new Promise(resolve => { + win.addEventListener( + "load", + function() { + resolve(); + }, + { capture: true, once: true } + ); + }); +} + +/* exported loadChromeScript */ +function loadChromeScript(fn) { + let wrapper = ` +const {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); +(${fn.toString()})();`; + + return SpecialPowers.loadChromeScript(new Function(wrapper)); +} + +/* exported consoleMonitor */ +let consoleMonitor = { + start(messages) { + this.chromeScript = SpecialPowers.loadChromeScript( + SimpleTest.getTestFileURL("mochitest_console.js") + ); + this.chromeScript.sendAsyncMessage("consoleStart", messages); + }, + + async finished() { + let done = this.chromeScript.promiseOneMessage("consoleDone").then(done => { + this.chromeScript.destroy(); + return done; + }); + this.chromeScript.sendAsyncMessage("waitForConsole"); + let test = await done; + ok(test.ok, test.message); + }, +}; +/* exported waitForState */ + +function waitForState(sw, state) { + return new Promise(resolve => { + if (sw.state === state) { + return resolve(); + } + sw.addEventListener("statechange", function onStateChange() { + if (sw.state === state) { + sw.removeEventListener("statechange", onStateChange); + resolve(); + } + }); + }); +} diff --git a/toolkit/components/extensions/test/mochitest/head_cookies.js b/toolkit/components/extensions/test/mochitest/head_cookies.js new file mode 100644 index 0000000000..610c800c94 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/head_cookies.js @@ -0,0 +1,287 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* exported testCookies */ +/* import-globals-from head.js */ + +async function testCookies(options) { + // Changing the options object is a bit of a hack, but it allows us to easily + // pass an expiration date to the background script. + options.expiry = Date.now() / 1000 + 3600; + + async function background(backgroundOptions) { + // Ask the parent scope to change some cookies we may or may not have + // permission for. + let awaitChanges = new Promise(resolve => { + browser.test.onMessage.addListener(msg => { + browser.test.assertEq("cookies-changed", msg, "browser.test.onMessage"); + resolve(); + }); + }); + + let changed = []; + browser.cookies.onChanged.addListener(event => { + changed.push(`${event.cookie.name}:${event.cause}`); + }); + browser.test.sendMessage("change-cookies"); + + // Try to access some cookies in various ways. + let { url, domain, secure } = backgroundOptions; + + let failures = 0; + let tallyFailure = error => { + failures++; + }; + + try { + await awaitChanges; + + let cookie = await browser.cookies.get({ url, name: "foo" }); + browser.test.assertEq( + backgroundOptions.shouldPass, + cookie != null, + "should pass == get cookie" + ); + + let cookies = await browser.cookies.getAll({ domain }); + if (backgroundOptions.shouldPass) { + browser.test.assertEq(2, cookies.length, "expected number of cookies"); + } else { + browser.test.assertEq(0, cookies.length, "expected number of cookies"); + } + + await Promise.all([ + browser.cookies + .set({ + url, + domain, + secure, + name: "foo", + value: "baz", + expirationDate: backgroundOptions.expiry, + }) + .catch(tallyFailure), + browser.cookies + .set({ + url, + domain, + secure, + name: "bar", + value: "quux", + expirationDate: backgroundOptions.expiry, + }) + .catch(tallyFailure), + browser.cookies.remove({ url, name: "deleted" }), + ]); + + if (backgroundOptions.shouldPass) { + // The order of eviction events isn't guaranteed, so just check that + // it's there somewhere. + let evicted = changed.indexOf("evicted:evicted"); + if (evicted < 0) { + browser.test.fail("got no eviction event"); + } else { + browser.test.succeed("got eviction event"); + changed.splice(evicted, 1); + } + + browser.test.assertEq( + "x:explicit,x:overwrite,x:explicit,x:explicit,foo:overwrite,foo:explicit,bar:explicit,deleted:explicit", + changed.join(","), + "expected changes" + ); + } else { + browser.test.assertEq("", changed.join(","), "expected no changes"); + } + + if (!(backgroundOptions.shouldPass || backgroundOptions.shouldWrite)) { + browser.test.assertEq(2, failures, "Expected failures"); + } else { + browser.test.assertEq(0, failures, "Expected no failures"); + } + + browser.test.notifyPass("cookie-permissions"); + } catch (error) { + browser.test.fail(`Error: ${error} :: ${error.stack}`); + browser.test.notifyFail("cookie-permissions"); + } + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: options.permissions, + }, + + background: `(${background})(${JSON.stringify(options)})`, + }); + + let stepOne = loadChromeScript(() => { + const { addMessageListener, sendAsyncMessage } = this; + addMessageListener("options", options => { + let domain = options.domain.replace(/^\.?/, "."); + // This will be evicted after we add a fourth cookie. + Services.cookies.add( + domain, + "/", + "evicted", + "bar", + options.secure, + false, + false, + options.expiry, + {}, + Ci.nsICookie.SAMESITE_NONE, + options.url.startsWith("https") + ? Ci.nsICookie.SCHEME_HTTPS + : Ci.nsICookie.SCHEME_HTTP + ); + // This will be modified by the background script. + Services.cookies.add( + domain, + "/", + "foo", + "bar", + options.secure, + false, + false, + options.expiry, + {}, + Ci.nsICookie.SAMESITE_NONE, + options.url.startsWith("https") + ? Ci.nsICookie.SCHEME_HTTPS + : Ci.nsICookie.SCHEME_HTTP + ); + // This will be deleted by the background script. + Services.cookies.add( + domain, + "/", + "deleted", + "bar", + options.secure, + false, + false, + options.expiry, + {}, + Ci.nsICookie.SAMESITE_NONE, + options.url.startsWith("https") + ? Ci.nsICookie.SCHEME_HTTPS + : Ci.nsICookie.SCHEME_HTTP + ); + sendAsyncMessage("done"); + }); + }); + stepOne.sendAsyncMessage("options", options); + await stepOne.promiseOneMessage("done"); + stepOne.destroy(); + + await extension.startup(); + + await extension.awaitMessage("change-cookies"); + + let stepTwo = loadChromeScript(() => { + const { addMessageListener, sendAsyncMessage } = this; + addMessageListener("options", options => { + let domain = options.domain.replace(/^\.?/, "."); + + Services.cookies.add( + domain, + "/", + "x", + "y", + options.secure, + false, + false, + options.expiry, + {}, + Ci.nsICookie.SAMESITE_NONE, + options.url.startsWith("https") + ? Ci.nsICookie.SCHEME_HTTPS + : Ci.nsICookie.SCHEME_HTTP + ); + Services.cookies.add( + domain, + "/", + "x", + "z", + options.secure, + false, + false, + options.expiry, + {}, + Ci.nsICookie.SAMESITE_NONE, + options.url.startsWith("https") + ? Ci.nsICookie.SCHEME_HTTPS + : Ci.nsICookie.SCHEME_HTTP + ); + Services.cookies.remove(domain, "x", "/", {}); + sendAsyncMessage("done"); + }); + }); + stepTwo.sendAsyncMessage("options", options); + await stepTwo.promiseOneMessage("done"); + stepTwo.destroy(); + + extension.sendMessage("cookies-changed"); + + await extension.awaitFinish("cookie-permissions"); + await extension.unload(); + + let stepThree = loadChromeScript(() => { + const { addMessageListener, sendAsyncMessage, assert } = this; + let cookieSvc = Services.cookies; + + function getCookies(host) { + let cookies = []; + for (let cookie of cookieSvc.getCookiesFromHost(host, {})) { + cookies.push(cookie); + } + return cookies.sort((a, b) => a.name.localeCompare(b.name)); + } + + addMessageListener("options", options => { + let cookies = getCookies(options.domain); + + if (options.shouldPass) { + assert.equal(cookies.length, 2, "expected two cookies for host"); + + assert.equal(cookies[0].name, "bar", "correct cookie name"); + assert.equal(cookies[0].value, "quux", "correct cookie value"); + + assert.equal(cookies[1].name, "foo", "correct cookie name"); + assert.equal(cookies[1].value, "baz", "correct cookie value"); + } else if (options.shouldWrite) { + // Note: |shouldWrite| applies only when |shouldPass| is false. + // This is necessary because, unfortunately, websites (and therefore web + // extensions) are allowed to write some cookies which they're not allowed + // to read. + assert.equal(cookies.length, 3, "expected three cookies for host"); + + assert.equal(cookies[0].name, "bar", "correct cookie name"); + assert.equal(cookies[0].value, "quux", "correct cookie value"); + + assert.equal(cookies[1].name, "deleted", "correct cookie name"); + + assert.equal(cookies[2].name, "foo", "correct cookie name"); + assert.equal(cookies[2].value, "baz", "correct cookie value"); + } else { + assert.equal(cookies.length, 2, "expected two cookies for host"); + + assert.equal(cookies[0].name, "deleted", "correct second cookie name"); + + assert.equal(cookies[1].name, "foo", "correct cookie name"); + assert.equal(cookies[1].value, "bar", "correct cookie value"); + } + + for (let cookie of cookies) { + cookieSvc.remove(cookie.host, cookie.name, "/", {}); + } + // Make sure we don't silently poison subsequent tests if something goes wrong. + assert.equal(getCookies(options.domain).length, 0, "cookies cleared"); + sendAsyncMessage("done"); + }); + }); + stepThree.sendAsyncMessage("options", options); + await stepThree.promiseOneMessage("done"); + stepThree.destroy(); +} diff --git a/toolkit/components/extensions/test/mochitest/head_notifications.js b/toolkit/components/extensions/test/mochitest/head_notifications.js new file mode 100644 index 0000000000..0c8cf24350 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/head_notifications.js @@ -0,0 +1,169 @@ +"use strict"; + +/* exported MockAlertsService */ + +function mockServicesChromeScript() { + const MOCK_ALERTS_CID = Components.ID( + "{48068bc2-40ab-4904-8afd-4cdfb3a385f3}" + ); + const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1"; + + const { setTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm", + {} + ); + const registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + + let activeNotifications = Object.create(null); + + const mockAlertsService = { + showPersistentNotification: function(persistentData, alert, alertListener) { + this.showAlert(alert, alertListener); + }, + + showAlert: function(alert, listener) { + activeNotifications[alert.name] = { + listener: listener, + cookie: alert.cookie, + title: alert.title, + }; + + // fake async alert show event + if (listener) { + setTimeout(function() { + listener.observe(null, "alertshow", alert.cookie); + }, 100); + } + }, + + showAlertNotification: function( + imageUrl, + title, + text, + textClickable, + cookie, + alertListener, + name + ) { + this.showAlert( + { + name: name, + cookie: cookie, + title: title, + }, + alertListener + ); + }, + + closeAlert: function(name) { + let alertNotification = activeNotifications[name]; + if (alertNotification) { + if (alertNotification.listener) { + alertNotification.listener.observe( + null, + "alertfinished", + alertNotification.cookie + ); + } + delete activeNotifications[name]; + } + }, + + QueryInterface: ChromeUtils.generateQI(["nsIAlertsService"]), + + createInstance: function(outer, iid) { + if (outer != null) { + throw Components.Exception("", Cr.NS_ERROR_NO_AGGREGATION); + } + return this.QueryInterface(iid); + }, + }; + + registrar.registerFactory( + MOCK_ALERTS_CID, + "alerts service", + ALERTS_SERVICE_CONTRACT_ID, + mockAlertsService + ); + + function clickNotifications(doClose) { + // Until we need to close a specific notification, just click them all. + for (let [name, notification] of Object.entries(activeNotifications)) { + let { listener, cookie } = notification; + listener.observe(null, "alertclickcallback", cookie); + if (doClose) { + mockAlertsService.closeAlert(name); + } + } + } + + function closeAllNotifications() { + for (let alertName of Object.keys(activeNotifications)) { + mockAlertsService.closeAlert(alertName); + } + } + + const { addMessageListener, sendAsyncMessage } = this; + + addMessageListener("mock-alert-service:unregister", () => { + closeAllNotifications(); + activeNotifications = null; + registrar.unregisterFactory(MOCK_ALERTS_CID, mockAlertsService); + sendAsyncMessage("mock-alert-service:unregistered"); + }); + + addMessageListener( + "mock-alert-service:click-notifications", + clickNotifications + ); + + addMessageListener( + "mock-alert-service:close-notifications", + closeAllNotifications + ); + + sendAsyncMessage("mock-alert-service:registered"); +} + +const MockAlertsService = { + async register() { + if (this._chromeScript) { + throw new Error("MockAlertsService already registered"); + } + this._chromeScript = SpecialPowers.loadChromeScript( + mockServicesChromeScript + ); + await this._chromeScript.promiseOneMessage("mock-alert-service:registered"); + }, + async unregister() { + if (!this._chromeScript) { + throw new Error("MockAlertsService not registered"); + } + this._chromeScript.sendAsyncMessage("mock-alert-service:unregister"); + return this._chromeScript + .promiseOneMessage("mock-alert-service:unregistered") + .then(() => { + this._chromeScript.destroy(); + this._chromeScript = null; + }); + }, + async clickNotifications() { + // Most implementations of the nsIAlertsService automatically close upon click. + await this._chromeScript.sendAsyncMessage( + "mock-alert-service:click-notifications", + true + ); + }, + async clickNotificationsWithoutClose() { + // The implementation on macOS does not automatically close the notification. + await this._chromeScript.sendAsyncMessage( + "mock-alert-service:click-notifications", + false + ); + }, + async closeNotifications() { + await this._chromeScript.sendAsyncMessage( + "mock-alert-service:close-notifications" + ); + }, +}; diff --git a/toolkit/components/extensions/test/mochitest/head_unlimitedStorage.js b/toolkit/components/extensions/test/mochitest/head_unlimitedStorage.js new file mode 100644 index 0000000000..73b98b68ae --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/head_unlimitedStorage.js @@ -0,0 +1,50 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* exported checkSitePermissions */ + +const { Services } = SpecialPowers; +const { NetUtil } = SpecialPowers.Cu.import( + "resource://gre/modules/NetUtil.jsm", + {} +); + +function checkSitePermissions(uuid, expectedPermAction, assertMessage) { + if (!uuid) { + throw new Error( + "checkSitePermissions should not be called with an undefined uuid" + ); + } + + const baseURI = NetUtil.newURI(`moz-extension://${uuid}/`); + const principal = Services.scriptSecurityManager.createContentPrincipal( + baseURI, + {} + ); + + const sitePermissions = { + webextUnlimitedStorage: Services.perms.testPermissionFromPrincipal( + principal, + "WebExtensions-unlimitedStorage" + ), + indexedDB: Services.perms.testPermissionFromPrincipal( + principal, + "indexedDB" + ), + persistentStorage: Services.perms.testPermissionFromPrincipal( + principal, + "persistent-storage" + ), + }; + + for (const [sitePermissionName, actualPermAction] of Object.entries( + sitePermissions + )) { + is( + actualPermAction, + expectedPermAction, + `The extension "${sitePermissionName}" SitePermission ${assertMessage} as expected` + ); + } +} diff --git a/toolkit/components/extensions/test/mochitest/head_webrequest.js b/toolkit/components/extensions/test/mochitest/head_webrequest.js new file mode 100644 index 0000000000..f6c6530e41 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/head_webrequest.js @@ -0,0 +1,482 @@ +"use strict"; + +let commonEvents = { + onBeforeRequest: [{ urls: [""] }, ["blocking"]], + onBeforeSendHeaders: [ + { urls: [""] }, + ["blocking", "requestHeaders"], + ], + onSendHeaders: [{ urls: [""] }, ["requestHeaders"]], + onBeforeRedirect: [{ urls: [""] }], + onHeadersReceived: [ + { urls: [""] }, + ["blocking", "responseHeaders"], + ], + // Auth tests will need to set their own events object + // "onAuthRequired": [{urls: [""]}, ["blocking", "responseHeaders"]], + onResponseStarted: [{ urls: [""] }], + onCompleted: [{ urls: [""] }, ["responseHeaders"]], + onErrorOccurred: [{ urls: [""] }], +}; + +function background(events) { + const IP_PATTERN = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; + + let expect; + let ignore; + let defaultOrigin; + let watchAuth = Object.keys(events).includes("onAuthRequired"); + let expectedIp = null; + + browser.test.onMessage.addListener((msg, expected) => { + if (msg !== "set-expected") { + return; + } + expect = expected.expect; + defaultOrigin = expected.origin; + ignore = expected.ignore; + let promises = []; + // Initialize some stuff we'll need in the tests. + for (let entry of Object.values(expect)) { + // a place for the test infrastructure to store some state. + entry.test = {}; + // Each entry in expected gets a Promise that will be resolved in the + // last event for that entry. This will either be onCompleted, or the + // last entry if an events list was provided. + promises.push( + new Promise(resolve => { + entry.test.resolve = resolve; + }) + ); + // If events was left undefined, we're expecting all normal events we're + // listening for, exclude onBeforeRedirect and onErrorOccurred + if (entry.events === undefined) { + entry.events = Object.keys(events).filter( + name => name != "onErrorOccurred" && name != "onBeforeRedirect" + ); + } + if (entry.optional_events === undefined) { + entry.optional_events = []; + } + } + // When every expected entry has finished our test is done. + Promise.all(promises).then(() => { + browser.test.sendMessage("done"); + }); + browser.test.sendMessage("continue"); + }); + + // Retrieve the per-file/test expected values. + function getExpected(details) { + let url = new URL(details.url); + let filename = url.pathname.split("/").pop(); + if (ignore && ignore.includes(filename)) { + return; + } + let expected = expect[filename]; + if (!expected) { + browser.test.fail(`unexpected request ${filename}`); + return; + } + // Save filename for redirect verification. + expected.test.filename = filename; + return expected; + } + + // Process any test header modifications that can happen in request or response phases. + // If a test includes headers, it needs a complete header object, no undefined + // objects even if empty: + // request: { + // add: {"HeaderName": "value",}, + // modify: {"HeaderName": "value",}, + // remove: ["HeaderName",], + // }, + // response: { + // add: {"HeaderName": "value",}, + // modify: {"HeaderName": "value",}, + // remove: ["HeaderName",], + // }, + function processHeaders(phase, expected, details) { + // This should only happen once per phase [request|response]. + browser.test.assertFalse( + !!expected.test[phase], + `First processing of headers for ${phase}` + ); + expected.test[phase] = true; + + let headers = details[`${phase}Headers`]; + browser.test.assertTrue( + Array.isArray(headers), + `${phase}Headers array present` + ); + + let { add, modify, remove } = expected.headers[phase]; + + for (let name in add) { + browser.test.assertTrue( + !headers.find(h => h.name === name), + `header ${name} to be added not present yet in ${phase}Headers` + ); + let header = { name: name }; + if (name.endsWith("-binary")) { + header.binaryValue = Array.from(add[name], c => c.charCodeAt(0)); + } else { + header.value = add[name]; + } + headers.push(header); + } + + let modifiedAny = false; + for (let header of headers) { + if (header.name.toLowerCase() in modify) { + header.value = modify[header.name.toLowerCase()]; + modifiedAny = true; + } + } + browser.test.assertTrue( + modifiedAny, + `at least one ${phase}Headers element to modify` + ); + + let deletedAny = false; + for (let j = headers.length; j-- > 0; ) { + if (remove.includes(headers[j].name.toLowerCase())) { + headers.splice(j, 1); + deletedAny = true; + } + } + browser.test.assertTrue( + deletedAny, + `at least one ${phase}Headers element to delete` + ); + + return headers; + } + + // phase is request or response. + function checkHeaders(phase, expected, details) { + if (!/^https?:/.test(details.url)) { + return; + } + + let headers = details[`${phase}Headers`]; + browser.test.assertTrue( + Array.isArray(headers), + `valid ${phase}Headers array` + ); + + let { add, modify, remove } = expected.headers[phase]; + for (let name in add) { + let value = headers.find(h => h.name.toLowerCase() === name.toLowerCase()) + .value; + browser.test.assertEq( + value, + add[name], + `header ${name} correctly injected in ${phase}Headers` + ); + } + + for (let name in modify) { + let value = headers.find(h => h.name.toLowerCase() === name.toLowerCase()) + .value; + browser.test.assertEq( + value, + modify[name], + `header ${name} matches modified value` + ); + } + + for (let name of remove) { + let found = headers.find( + h => h.name.toLowerCase() === name.toLowerCase() + ); + browser.test.assertFalse( + !!found, + `deleted header ${name} still found in ${phase}Headers` + ); + } + } + + let listeners = { + onBeforeRequest(expected, details, result) { + // Save some values to test request consistency in later events. + browser.test.assertTrue( + details.tabId !== undefined, + `tabId ${details.tabId}` + ); + browser.test.assertTrue( + details.requestId !== undefined, + `requestId ${details.requestId}` + ); + // Validate requestId if it's already set, this happens with redirects. + if (expected.test.requestId !== undefined) { + browser.test.assertEq( + "string", + typeof expected.test.requestId, + `requestid ${expected.test.requestId} is string` + ); + browser.test.assertEq( + "string", + typeof details.requestId, + `requestid ${details.requestId} is string` + ); + browser.test.assertEq( + "number", + typeof parseInt(details.requestId, 10), + "parsed requestid is number" + ); + browser.test.assertEq( + expected.test.requestId, + details.requestId, + "redirects will keep the same requestId" + ); + } else { + // Save any values we want to validate in later events. + expected.test.requestId = details.requestId; + expected.test.tabId = details.tabId; + } + // Tests we don't need to do every event. + browser.test.assertTrue( + details.type.toUpperCase() in browser.webRequest.ResourceType, + `valid resource type ${details.type}` + ); + if (details.type == "main_frame") { + browser.test.assertEq( + 0, + details.frameId, + "frameId is zero when type is main_frame, see bug 1329299" + ); + } + }, + onBeforeSendHeaders(expected, details, result) { + if (expected.headers && expected.headers.request) { + result.requestHeaders = processHeaders("request", expected, details); + } + if (expected.redirect) { + browser.test.log(`${name} redirect request`); + result.redirectUrl = details.url.replace( + expected.test.filename, + expected.redirect + ); + } + }, + onBeforeRedirect() {}, + onSendHeaders(expected, details, result) { + if (expected.headers && expected.headers.request) { + checkHeaders("request", expected, details); + } + }, + onResponseStarted() {}, + onHeadersReceived(expected, details, result) { + let expectedStatus = expected.status || 200; + // If authentication is being requested we don't fail on the status code. + if (watchAuth && [401, 407].includes(details.statusCode)) { + expectedStatus = details.statusCode; + } + browser.test.assertEq( + expectedStatus, + details.statusCode, + `expected HTTP status received for ${details.url} ${details.statusLine}` + ); + if (expected.headers && expected.headers.response) { + result.responseHeaders = processHeaders("response", expected, details); + } + }, + onAuthRequired(expected, details, result) { + result.authCredentials = expected.authInfo; + }, + onCompleted(expected, details, result) { + // If we have already completed a GET request for this url, + // and it was found, we expect for the response to come fromCache. + // expected.cached may be undefined, force boolean. + if (typeof expected.cached === "boolean") { + let expectCached = + expected.cached && + details.method === "GET" && + details.statusCode != 404; + browser.test.assertEq( + expectCached, + details.fromCache, + "fromCache is correct" + ); + } + // We can only tell IPs for non-cached HTTP requests. + if (!details.fromCache && /^https?:/.test(details.url)) { + browser.test.assertTrue( + IP_PATTERN.test(details.ip), + `IP for ${details.url} looks IP-ish: ${details.ip}` + ); + + // We can't easily predict the IP ahead of time, so just make + // sure they're all consistent. + expectedIp = expectedIp || details.ip; + browser.test.assertEq( + expectedIp, + details.ip, + `correct ip for ${details.url}` + ); + } + if (expected.headers && expected.headers.response) { + checkHeaders("response", expected, details); + } + }, + onErrorOccurred(expected, details, result) { + if (expected.error) { + if (Array.isArray(expected.error)) { + browser.test.assertTrue( + expected.error.includes(details.error), + "expected error message received in onErrorOccurred" + ); + } else { + browser.test.assertEq( + expected.error, + details.error, + "expected error message received in onErrorOccurred" + ); + } + } + }, + }; + + function getListener(name) { + return details => { + let result = {}; + browser.test.log(`${name} ${details.requestId} ${details.url}`); + let expected = getExpected(details); + if (!expected) { + return result; + } + let expectedEvent = expected.events[0] == name; + if (expectedEvent) { + expected.events.shift(); + } else { + // e10s vs. non-e10s errors can end with either onCompleted or onErrorOccurred + expectedEvent = expected.optional_events.includes(name); + } + browser.test.assertTrue(expectedEvent, `received ${name}`); + browser.test.assertEq( + expected.type, + details.type, + "resource type is correct" + ); + browser.test.assertEq( + expected.origin || defaultOrigin, + details.originUrl, + "origin is correct" + ); + + if (name != "onBeforeRequest") { + // On events after onBeforeRequest, check the previous values. + browser.test.assertEq( + expected.test.requestId, + details.requestId, + "correct requestId" + ); + browser.test.assertEq( + expected.test.tabId, + details.tabId, + "correct tabId" + ); + } + try { + listeners[name](expected, details, result); + } catch (e) { + browser.test.fail(`unexpected webrequest failure ${name} ${e}`); + } + + if (expected.cancel && expected.cancel == name) { + browser.test.log(`${name} cancel request`); + browser.test.sendMessage("cancelled"); + result.cancel = true; + } + // If we've used up all the events for this test, resolve the promise. + // If something wrong happens and more events come through, there will be + // failures. + if (expected.events.length <= 0) { + expected.test.resolve(); + } + return result; + }; + } + + for (let [name, args] of Object.entries(events)) { + browser.test.log(`adding listener for ${name}`); + try { + browser.webRequest[name].addListener(getListener(name), ...args); + } catch (e) { + browser.test.assertTrue( + /\brequestBody\b/.test(e.message), + "Request body is unsupported" + ); + + // RequestBody is disabled in release builds. + if (!/\brequestBody\b/.test(e.message)) { + throw e; + } + + args.splice(args.indexOf("requestBody"), 1); + browser.webRequest[name].addListener(getListener(name), ...args); + } + } +} + +/* exported makeExtension */ + +function makeExtension(events = commonEvents) { + return ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["webRequest", "webRequestBlocking", ""], + }, + background: `(${background})(${JSON.stringify(events)})`, + }); +} + +/* exported addStylesheet */ + +function addStylesheet(file) { + let link = document.createElement("link"); + link.setAttribute("rel", "stylesheet"); + link.setAttribute("href", file); + document.body.appendChild(link); +} + +/* exported addLink */ + +function addLink(file) { + let a = document.createElement("a"); + a.setAttribute("href", file); + a.setAttribute("target", "_blank"); + a.setAttribute("rel", "opener"); + document.body.appendChild(a); + return a; +} + +/* exported addImage */ + +function addImage(file) { + let img = document.createElement("img"); + img.setAttribute("src", file); + document.body.appendChild(img); +} + +/* exported addScript */ + +function addScript(file) { + let script = document.createElement("script"); + script.setAttribute("type", "text/javascript"); + script.setAttribute("src", file); + document + .getElementsByTagName("head") + .item(0) + .appendChild(script); +} + +/* exported addFrame */ + +function addFrame(file) { + let frame = document.createElement("iframe"); + frame.setAttribute("width", "200"); + frame.setAttribute("height", "200"); + frame.setAttribute("src", file); + document.body.appendChild(frame); +} diff --git a/toolkit/components/extensions/test/mochitest/hsts.sjs b/toolkit/components/extensions/test/mochitest/hsts.sjs new file mode 100644 index 0000000000..636f331882 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/hsts.sjs @@ -0,0 +1,8 @@ +function handleRequest(request, response) { + let page = "

HSTS page

"; + response.setStatusLine(request.httpVersion, "200", "OK"); + response.setHeader("Strict-Transport-Security", "max-age=60"); + response.setHeader("Content-Type", "text/html", false); + response.setHeader("Content-Length", page.length + "", false); + response.write(page); +} diff --git a/toolkit/components/extensions/test/mochitest/mochitest-common.ini b/toolkit/components/extensions/test/mochitest/mochitest-common.ini new file mode 100644 index 0000000000..2e32a951e2 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini @@ -0,0 +1,206 @@ +[DEFAULT] +support-files = + chrome_cleanup_script.js + file_WebNavigation_page1.html + file_WebNavigation_page2.html + file_WebNavigation_page3.html + file_WebRequest_page3.html + file_contains_img.html + file_contains_iframe.html + file_green.html + file_contentscript_activeTab.html + file_contentscript_activeTab2.html + file_contentscript_iframe.html + file_image_bad.png + file_image_good.png + file_image_great.png + file_image_redirect.png + file_indexedDB.html + file_mixed.html + file_remote_frame.html + file_sample.html + file_sample.txt + file_sample.txt^headers^ + file_script_bad.js + file_script_good.js + file_script_redirect.js + file_script_xhr.js + file_serviceWorker.html + file_simple_sandboxed_frame.html + file_simple_sandboxed_subframe.html + file_simple_xhr.html + file_simple_xhr_frame.html + file_simple_xhr_frame2.html + file_streamfilter.txt + file_style_bad.css + file_style_good.css + file_style_redirect.css + file_third_party.html + file_to_drawWindow.html + file_webNavigation_clientRedirect.html + file_webNavigation_clientRedirect_httpHeaders.html + file_webNavigation_clientRedirect_httpHeaders.html^headers^ + file_webNavigation_frameClientRedirect.html + file_webNavigation_frameRedirect.html + file_webNavigation_manualSubframe.html + file_webNavigation_manualSubframe_page1.html + file_webNavigation_manualSubframe_page2.html + file_with_about_blank.html + file_with_xorigin_frame.html + head.js + head_cookies.js + head_notifications.js + head_unlimitedStorage.js + head_webrequest.js + hsts.sjs + mochitest_console.js + oauth.html + redirect_auto.sjs + redirection.sjs + return_headers.sjs + serviceWorker.js + slow_response.sjs + webrequest_worker.js + !/dom/tests/mochitest/geolocation/network_geolocation.sjs + !/toolkit/components/passwordmgr/test/authenticate.sjs + file_redirect_data_uri.html + file_redirect_cors_bypass.html + file_tabs_permission_page1.html + file_tabs_permission_page2.html +prefs = + security.mixed_content.upgrade_display_content=false + browser.chrome.guess_favicon=true + +[test_ext_activityLog.html] +skip-if = + os == 'android' + tsan # Times out on TSan, bug 1612707 + xorigin # Inconsistent pass/fail in opt and debug +[test_ext_async_clipboard.html] +skip-if = toolkit == 'android' || tsan # near-permafail after landing bug 1270059: Bug 1523131. tsan: bug 1612707 +[test_ext_background_canvas.html] +[test_ext_background_page.html] +skip-if = (toolkit == 'android') # android doesn't have devtools +[test_ext_browsingData_indexedDB.html] +[test_ext_browsingData_localStorage.html] +[test_ext_browsingData_pluginData.html] +[test_ext_browsingData_serviceWorkers.html] +[test_ext_browsingData_settings.html] +[test_ext_canvas_resistFingerprinting.html] +[test_ext_clipboard.html] +skip-if = os == 'android' +[test_ext_clipboard_image.html] +skip-if = headless # Bug 1405872 +[test_ext_contentscript_about_blank.html] +skip-if = os == 'android' # bug 1369440 +[test_ext_contentscript_activeTab.html] +skip-if = os == 'android' || fission +[test_ext_contentscript_cache.html] +skip-if = (os == 'linux' && debug) || (toolkit == 'android' && debug) # bug 1348241 +fail-if = xorigin # TypeError: can't access property "staticScripts", ext is undefined - Should not throw any errors +[test_ext_contentscript_canvas.html] +skip-if = (os == 'android') || (verify && debug && (os == 'linux')) # Bug 1617062 +[test_ext_contentscript_devtools_metadata.html] +[test_ext_contentscript_fission_frame.html] +[test_ext_contentscript_incognito.html] +skip-if = os == 'android' # Android does not support multiple windows. +[test_ext_contentscript_permission.html] +skip-if = tsan # Times out on TSan, bug 1612707 +[test_ext_cookies.html] +skip-if = os == 'android' || tsan # Times out on TSan intermittently, bug 1615184; not supported on Android yet +[test_ext_cookies_containers.html] +[test_ext_cookies_expiry.html] +[test_ext_cookies_first_party.html] +[test_ext_cookies_incognito.html] +skip-if = os == 'android' # Bug 1513544 Android does not support multiple windows. +[test_ext_cookies_permissions_bad.html] +[test_ext_cookies_permissions_good.html] +[test_ext_downloads_download.html] +[test_ext_embeddedimg_iframe_frameAncestors.html] +[test_ext_exclude_include_globs.html] +[test_ext_external_messaging.html] +[test_ext_generate.html] +[test_ext_geolocation.html] +skip-if = os == 'android' # Android support Bug 1336194 +[test_ext_identity.html] +skip-if = os == 'android' || tsan # unsupported. tsan: bug 1612707 +[test_ext_idle.html] +skip-if = tsan # Times out on TSan, bug 1612707 +[test_ext_inIncognitoContext_window.html] +skip-if = os == 'android' # Android does not support multiple windows. +[test_ext_listener_proxies.html] +[test_ext_new_tab_processType.html] +skip-if = verify && debug && (os == 'linux' || os == 'mac') +[test_ext_notifications.html] +skip-if = os == 'android' # Not supported on Android yet +[test_ext_protocolHandlers.html] +skip-if = (toolkit == 'android') # bug 1342577 +[test_ext_redirect_jar.html] +skip-if = os == 'win' && (debug || asan) # Bug 1563440 +[test_ext_request_urlClassification.html] +skip-if = os == 'android' # Bug 1615427 +[test_ext_runtime_connect.html] +[test_ext_runtime_connect_twoway.html] +[test_ext_runtime_connect2.html] +[test_ext_runtime_disconnect.html] +[test_ext_sendmessage_doublereply.html] +[test_ext_sendmessage_frameId.html] +[test_ext_sendmessage_no_receiver.html] +[test_ext_sendmessage_reply.html] +[test_ext_sendmessage_reply2.html] +skip-if = os == 'android' +[test_ext_storage_manager_capabilities.html] +skip-if = xorigin # JavaScript Error: "SecurityError: Permission denied to access property "wrappedJSObject" on cross-origin object" {file: "https://example.com/tests/SimpleTest/TestRunner.js" line: 157} +scheme=https +[test_ext_storage_smoke_test.html] +[test_ext_streamfilter_multiple.html] +skip-if = + !debug # Bug 1628642 + os == 'linux' # Bug 1628642 +[test_ext_streamfilter_processswitch.html] +[test_ext_subframes_privileges.html] +skip-if = os == 'android' || verify # bug 1489771 +[test_ext_tabs_captureTab.html] +[test_ext_tabs_query_popup.html] +[test_ext_tabs_permissions.html] +[test_ext_tabs_sendMessage.html] +[test_ext_test.html] +[test_ext_unlimitedStorage.html] +skip-if = os == 'android' +[test_ext_unlimitedStorage_legacy_persistent_indexedDB.html] +# IndexedDB persistent storage mode is not allowed on Fennec from a non-chrome privileged code +# (it has only been enabled for apps and privileged code). See Bug 1119462 for additional info. +skip-if = os == 'android' +[test_ext_web_accessible_resources.html] +skip-if = (os == 'android' && debug) || fission || (os == "linux" && bits == 64) # bug 1397615, bug 1588284, bug 1618231 +[test_ext_web_accessible_incognito.html] +skip-if = (os == 'android') || fission # Crashes intermittently: @ mozilla::dom::BrowsingContext::CreateFromIPC(mozilla::dom::BrowsingContext::IPCInitializer&&, mozilla::dom::BrowsingContextGroup*, mozilla::dom::ContentParent*), bug 1588284, bug 1397615 and bug 1513544 +[test_ext_webnavigation.html] +skip-if = (os == 'android' && debug) # bug 1397615 +[test_ext_webnavigation_filters.html] +skip-if = (os == 'android' && debug) || (verify && (os == 'linux' || os == 'mac')) # bug 1397615 +[test_ext_webnavigation_incognito.html] +skip-if = os == 'android' # bug 1513544 +[test_ext_webrequest_and_proxy_filter.html] +[test_ext_webrequest_auth.html] +skip-if = os == 'android' +[test_ext_webrequest_background_events.html] +[test_ext_webrequest_basic.html] +skip-if = + os == 'android' && debug # bug 1397615 + tsan # bug 1612707 + xorigin # JavaScript Error: "SecurityError: Permission denied to access property "wrappedJSObject" on cross-origin object" {file: "http://mochi.false-test:8888/tests/SimpleTest/TestRunner.js" line: 157}] +[test_ext_webrequest_errors.html] +skip-if = tsan +[test_ext_webrequest_filter.html] +skip-if = os == 'android' && debug || tsan # bug 1452348. tsan: bug 1612707 +[test_ext_webrequest_frameId.html] +skip-if = (webrender && os == 'linux') # Bug 1482983 caused by Bug 1480951 +[test_ext_webrequest_hsts.html] +skip-if = os == 'android' || os == 'linux' || os == 'mac' #Bug 1605515 +[test_ext_webrequest_upgrade.html] +[test_ext_webrequest_upload.html] +skip-if = os == 'android' # Currently fails in emulator tests +[test_ext_webrequest_redirect_bypass_cors.html] +[test_ext_webrequest_redirect_data_uri.html] +[test_ext_window_postMessage.html] diff --git a/toolkit/components/extensions/test/mochitest/mochitest-remote.ini b/toolkit/components/extensions/test/mochitest/mochitest-remote.ini new file mode 100644 index 0000000000..2828eb2182 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/mochitest-remote.ini @@ -0,0 +1,8 @@ +[DEFAULT] +tags = webextensions remote-webextensions +skip-if = !e10s || (os == 'android') # Bug 1620091: disable on android until extension process is done +prefs = + extensions.webextensions.remote=true + +[test_verify_remote_mode.html] +[include:mochitest-common.ini] diff --git a/toolkit/components/extensions/test/mochitest/mochitest.ini b/toolkit/components/extensions/test/mochitest/mochitest.ini new file mode 100644 index 0000000000..4612cac657 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/mochitest.ini @@ -0,0 +1,12 @@ +[DEFAULT] +tags = webextensions in-process-webextensions +prefs = + extensions.webextensions.remote=false +dupe-manifest = true + +[test_verify_non_remote_mode.html] +[test_ext_storage_cleanup.html] +# Bug 1426514 storage_cleanup: clearing localStorage fails with oop + +[include:mochitest-common.ini] +skip-if = os == 'win' # Windows WebExtensions always run OOP diff --git a/toolkit/components/extensions/test/mochitest/mochitest_console.js b/toolkit/components/extensions/test/mochitest/mochitest_console.js new file mode 100644 index 0000000000..e4be8acd69 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/mochitest_console.js @@ -0,0 +1,53 @@ +"use strict"; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { addMessageListener, sendAsyncMessage } = this; + +// Much of the console monitoring code is copied from TestUtils but simplified +// to our needs. +function monitorConsole(msgs) { + function msgMatches(msg, pat) { + for (let k in pat) { + if (!(k in msg)) { + return false; + } + if (pat[k] instanceof RegExp && typeof msg[k] === "string") { + if (!pat[k].test(msg[k])) { + return false; + } + } else if (msg[k] !== pat[k]) { + return false; + } + } + return true; + } + + let counter = 0; + function listener(msg) { + if (msgMatches(msg, msgs[counter])) { + counter++; + } + } + addMessageListener("waitForConsole", () => { + sendAsyncMessage("consoleDone", { + ok: counter >= msgs.length, + message: `monitorConsole | messages left expected at least ${msgs.length} got ${counter}`, + }); + Services.console.unregisterListener(listener); + }); + + Services.console.registerListener(listener); +} + +addMessageListener("consoleStart", messages => { + for (let msg of messages) { + // Message might be a RegExp object from a different compartment, but + // instanceof RegExp will fail. If we have an object, lets just make + // sure. + let message = msg.message; + if (typeof message == "object" && !(message instanceof RegExp)) { + msg.message = new RegExp(message); + } + } + monitorConsole(messages); +}); diff --git a/toolkit/components/extensions/test/mochitest/oauth.html b/toolkit/components/extensions/test/mochitest/oauth.html new file mode 100644 index 0000000000..8b9b1d65ec --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/oauth.html @@ -0,0 +1,26 @@ + + + + + + +
+
+ + diff --git a/toolkit/components/extensions/test/mochitest/redirect_auto.sjs b/toolkit/components/extensions/test/mochitest/redirect_auto.sjs new file mode 100644 index 0000000000..27d249f022 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/redirect_auto.sjs @@ -0,0 +1,21 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +Components.utils.importGlobalProperties(["URLSearchParams", "URL"]); + +function handleRequest(request, response) { + let params = new URLSearchParams(request.queryString); + if (params.has("no_redirect")) { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.write("ok"); + } else { + if (request.method == "POST") { + response.setStatusLine(request.httpVersion, 303, "Redirected"); + } else { + response.setStatusLine(request.httpVersion, 302, "Moved Temporarily"); + } + let url = new URL(params.get("redirect_uri") || params.get("default_redirect")); + url.searchParams.set("access_token", "here ya go"); + response.setHeader("Location", url.href); + } +} diff --git a/toolkit/components/extensions/test/mochitest/redirection.sjs b/toolkit/components/extensions/test/mochitest/redirection.sjs new file mode 100644 index 0000000000..370ecd213f --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/redirection.sjs @@ -0,0 +1,4 @@ +function handleRequest(aRequest, aResponse) { + aResponse.setStatusLine(aRequest.httpVersion, 302); + aResponse.setHeader("Location", "./dummy_page.html"); +} diff --git a/toolkit/components/extensions/test/mochitest/return_headers.sjs b/toolkit/components/extensions/test/mochitest/return_headers.sjs new file mode 100644 index 0000000000..54e2e5fb4d --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/return_headers.sjs @@ -0,0 +1,20 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript sts=2 sw=2 et tw=80: */ +"use strict"; + +/* exported handleRequest */ + +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/plain", false); + + let headers = {}; + // Why on earth is this a nsISimpleEnumerator... + let enumerator = request.headers; + while (enumerator.hasMoreElements()) { + let header = enumerator.getNext().data; + headers[header.toLowerCase()] = request.getHeader(header); + } + + response.write(JSON.stringify(headers)); +} + diff --git a/toolkit/components/extensions/test/mochitest/serviceWorker.js b/toolkit/components/extensions/test/mochitest/serviceWorker.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/toolkit/components/extensions/test/mochitest/slow_response.sjs b/toolkit/components/extensions/test/mochitest/slow_response.sjs new file mode 100644 index 0000000000..290d6ca1de --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/slow_response.sjs @@ -0,0 +1,55 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80 ft=javascript: */ +"use strict"; + +/* eslint-disable no-unused-vars */ + +Cu.import("resource://gre/modules/AppConstants.jsm"); + +const DELAY = AppConstants.DEBUG ? 4000 : 800; + +let nsTimer = Components.Constructor("@mozilla.org/timer;1", "nsITimer", "initWithCallback"); + +let timer; +function delay() { + return new Promise(resolve => { + timer = nsTimer(resolve, DELAY, Ci.nsITimer.TYPE_ONE_SHOT); + }); +} + +const PARTS = [ + ` + + + + + + `, + "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.
", + ` + + `, +]; + +async function handleRequest(request, response) { + response.processAsync(); + + response.setHeader("Content-Type", "text/html", false); + response.setHeader("Cache-Control", "no-cache", false); + + await delay(); + + for (let part of PARTS) { + response.write(`${part}\n`); + await delay(); + } + + response.finish(); +} + diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_data_uri.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_data_uri.html new file mode 100644 index 0000000000..42950c50ec --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_data_uri.html @@ -0,0 +1,104 @@ + + + + Test content script matching a data: URI + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_telemetry.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_telemetry.html new file mode 100644 index 0000000000..198b8e85cf --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_telemetry.html @@ -0,0 +1,64 @@ + + + + Test for telemetry for content script injection + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_unrecognizedprop_warning.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_unrecognizedprop_warning.html new file mode 100644 index 0000000000..40403dea2b --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_unrecognizedprop_warning.html @@ -0,0 +1,80 @@ + + + + Test for content script unrecognized property on manifest + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_open.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_open.html new file mode 100644 index 0000000000..530937c1ac --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_open.html @@ -0,0 +1,114 @@ + + + + Test for permissions + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html new file mode 100644 index 0000000000..64cfcfd289 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html @@ -0,0 +1,257 @@ + + + + Test downloads.download() saveAs option + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_uniquify.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_uniquify.html new file mode 100644 index 0000000000..b5fedee7ec --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_uniquify.html @@ -0,0 +1,116 @@ + + + + Test downloads.download() uniquify option + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_permissions.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_permissions.html new file mode 100644 index 0000000000..47761784b1 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_permissions.html @@ -0,0 +1,176 @@ + + + + Test for permissions + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_trackingprotection.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_trackingprotection.html new file mode 100644 index 0000000000..580ea5e793 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_trackingprotection.html @@ -0,0 +1,98 @@ + + + + Test for simple WebExtension + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_webnavigation_resolved_urls.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webnavigation_resolved_urls.html new file mode 100644 index 0000000000..a06709d807 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webnavigation_resolved_urls.html @@ -0,0 +1,81 @@ + + + + Test for simple WebExtension + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_background_events.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_background_events.html new file mode 100644 index 0000000000..a9dfb0a902 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_background_events.html @@ -0,0 +1,94 @@ + + + + Test for simple WebExtension + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_host_permissions.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_host_permissions.html new file mode 100644 index 0000000000..19c812f59f --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_host_permissions.html @@ -0,0 +1,89 @@ + + + + Test webRequest checks host permissions + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_mozextension.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_mozextension.html new file mode 100644 index 0000000000..4c19359d8b --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_mozextension.html @@ -0,0 +1,193 @@ + + + + Test moz-extension protocol use + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_native_messaging_paths.html b/toolkit/components/extensions/test/mochitest/test_chrome_native_messaging_paths.html new file mode 100644 index 0000000000..78359747ce --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_chrome_native_messaging_paths.html @@ -0,0 +1,56 @@ + + + + WebExtension test + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_activityLog.html b/toolkit/components/extensions/test/mochitest/test_ext_activityLog.html new file mode 100644 index 0000000000..ce4689540d --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_activityLog.html @@ -0,0 +1,390 @@ + + + + WebExtension activityLog test + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js b/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js new file mode 100644 index 0000000000..62933bf008 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js @@ -0,0 +1,181 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +// Tests whether not too many APIs are visible by default. +// This file is used by test_ext_all_apis.html in browser/ and mobile/android/, +// which may modify the following variables to add or remove expected APIs. +/* globals expectedContentApisTargetSpecific */ +/* globals expectedBackgroundApisTargetSpecific */ + +// Generates a list of expectations. +function generateExpectations(list) { + return list + .reduce((allApis, path) => { + return allApis.concat(`browser.${path}`, `chrome.${path}`); + }, []) + .sort(); +} + +let expectedCommonApis = [ + "extension.getURL", + "extension.inIncognitoContext", + "extension.lastError", + "i18n.detectLanguage", + "i18n.getAcceptLanguages", + "i18n.getMessage", + "i18n.getUILanguage", + "runtime.OnInstalledReason", + "runtime.OnRestartRequiredReason", + "runtime.PlatformArch", + "runtime.PlatformOs", + "runtime.RequestUpdateCheckStatus", + "runtime.getManifest", + "runtime.connect", + "runtime.getURL", + "runtime.id", + "runtime.lastError", + "runtime.onConnect", + "runtime.onMessage", + "runtime.sendMessage", + // browser.test is only available in xpcshell or when + // Cu.isInAutomation is true. + "test.assertEq", + "test.assertFalse", + "test.assertRejects", + "test.assertThrows", + "test.assertTrue", + "test.fail", + "test.log", + "test.notifyFail", + "test.notifyPass", + "test.onMessage", + "test.sendMessage", + "test.succeed", + "test.withHandlingUserInput", +]; + +let expectedContentApis = [ + ...expectedCommonApis, + ...expectedContentApisTargetSpecific, +]; + +let expectedBackgroundApis = [ + ...expectedCommonApis, + ...expectedBackgroundApisTargetSpecific, + "contentScripts.register", + "experiments.APIChildScope", + "experiments.APIEvent", + "experiments.APIParentScope", + "extension.ViewType", + "extension.getBackgroundPage", + "extension.getViews", + "extension.isAllowedFileSchemeAccess", + "extension.isAllowedIncognitoAccess", + // Note: extensionTypes is not visible in Chrome. + "extensionTypes.CSSOrigin", + "extensionTypes.ImageFormat", + "extensionTypes.RunAt", + "management.ExtensionDisabledReason", + "management.ExtensionInstallType", + "management.ExtensionType", + "management.getSelf", + "management.uninstallSelf", + "permissions.getAll", + "permissions.contains", + "permissions.request", + "permissions.remove", + "permissions.onAdded", + "permissions.onRemoved", + "runtime.getBackgroundPage", + "runtime.getBrowserInfo", + "runtime.getPlatformInfo", + "runtime.onConnectExternal", + "runtime.onInstalled", + "runtime.onMessageExternal", + "runtime.onStartup", + "runtime.onUpdateAvailable", + "runtime.openOptionsPage", + "runtime.reload", + "runtime.setUninstallURL", + "theme.getCurrent", + "theme.onUpdated", + "types.LevelOfControl", + "types.SettingScope", +]; + +function sendAllApis() { + function isEvent(key, val) { + if (!/^on[A-Z]/.test(key)) { + return false; + } + let eventKeys = []; + for (let prop in val) { + eventKeys.push(prop); + } + eventKeys = eventKeys.sort().join(); + return eventKeys === "addListener,hasListener,removeListener"; + } + function mayRecurse(key, val) { + if (Object.keys(val).filter(k => !/^[A-Z\-0-9_]+$/.test(k)).length === 0) { + // Don't recurse on constants and empty objects. + return false; + } + return !isEvent(key, val); + } + + let results = []; + function diveDeeper(path, obj) { + for (let key in obj) { + let val = obj[key]; + if (typeof val == "object" && val !== null && mayRecurse(key, val)) { + diveDeeper(`${path}.${key}`, val); + } else if (val !== undefined) { + results.push(`${path}.${key}`); + } + } + } + diveDeeper("browser", browser); + diveDeeper("chrome", chrome); + browser.test.sendMessage("allApis", results.sort()); +} + +add_task(async function test_enumerate_content_script_apis() { + let extensionData = { + manifest: { + content_scripts: [ + { + matches: ["http://mochi.test/*/file_sample.html"], + js: ["contentscript.js"], + run_at: "document_start", + }, + ], + }, + files: { + "contentscript.js": sendAllApis, + }, + }; + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + + let win = window.open("file_sample.html"); + let actualApis = await extension.awaitMessage("allApis"); + win.close(); + let expectedApis = generateExpectations(expectedContentApis); + isDeeply(actualApis, expectedApis, "content script APIs"); + + await extension.unload(); +}); + +add_task(async function test_enumerate_background_script_apis() { + let extensionData = { + background: sendAllApis, + }; + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + let actualApis = await extension.awaitMessage("allApis"); + let expectedApis = generateExpectations(expectedBackgroundApis); + isDeeply(actualApis, expectedApis, "background script APIs"); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/mochitest/test_ext_async_clipboard.html b/toolkit/components/extensions/test/mochitest/test_ext_async_clipboard.html new file mode 100644 index 0000000000..ffa421e042 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_async_clipboard.html @@ -0,0 +1,376 @@ + + + + Async Clipboard permissions tests + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_background_canvas.html b/toolkit/components/extensions/test/mochitest/test_ext_background_canvas.html new file mode 100644 index 0000000000..8b6fba25bb --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_background_canvas.html @@ -0,0 +1,50 @@ + + + + Test for background page canvas rendering + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_background_page.html b/toolkit/components/extensions/test/mochitest/test_ext_background_page.html new file mode 100644 index 0000000000..9cafd8a61a --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_background_page.html @@ -0,0 +1,84 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_browsingData_indexedDB.html b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_indexedDB.html new file mode 100644 index 0000000000..f7d36633db --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_indexedDB.html @@ -0,0 +1,161 @@ + + + + + Test browsingData.remove indexedDB + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_browsingData_localStorage.html b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_localStorage.html new file mode 100644 index 0000000000..cf6c420366 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_localStorage.html @@ -0,0 +1,322 @@ + + + + + Test browsingData.remove indexedDB + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_browsingData_pluginData.html b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_pluginData.html new file mode 100644 index 0000000000..ff75ca7b9f --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_pluginData.html @@ -0,0 +1,71 @@ + + + + + Test browsingData.remove indexedDB + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_browsingData_serviceWorkers.html b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_serviceWorkers.html new file mode 100644 index 0000000000..a97a62a0f4 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_serviceWorkers.html @@ -0,0 +1,141 @@ + + + + + Test browsingData.remove indexedDB + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_browsingData_settings.html b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_settings.html new file mode 100644 index 0000000000..3b1d5e1af9 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_settings.html @@ -0,0 +1,67 @@ + + + + + Test browsingData.settings + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_canvas_resistFingerprinting.html b/toolkit/components/extensions/test/mochitest/test_ext_canvas_resistFingerprinting.html new file mode 100644 index 0000000000..7116d03235 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_canvas_resistFingerprinting.html @@ -0,0 +1,64 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_clipboard.html b/toolkit/components/extensions/test/mochitest/test_ext_clipboard.html new file mode 100644 index 0000000000..77ac767391 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_clipboard.html @@ -0,0 +1,210 @@ + + + + Clipboard permissions tests + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_clipboard_image.html b/toolkit/components/extensions/test/mochitest/test_ext_clipboard_image.html new file mode 100644 index 0000000000..b5d5f6764a --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_clipboard_image.html @@ -0,0 +1,262 @@ + + + + Clipboard permissions tests + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_about_blank.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_about_blank.html new file mode 100644 index 0000000000..04946ceeaf --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_about_blank.html @@ -0,0 +1,116 @@ + + + + Test content script match_about_blank option + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_activeTab.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_activeTab.html new file mode 100644 index 0000000000..306f093fe1 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_activeTab.html @@ -0,0 +1,371 @@ + + + + Test for content script + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_cache.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_cache.html new file mode 100644 index 0000000000..e8bb638d95 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_cache.html @@ -0,0 +1,113 @@ + + + + Test for content script caching + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_canvas.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_canvas.html new file mode 100644 index 0000000000..c4b6b5a256 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_canvas.html @@ -0,0 +1,138 @@ + + + + Test content script access to canvas drawWindow() + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_devtools_metadata.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_devtools_metadata.html new file mode 100644 index 0000000000..f2a2de0e05 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_devtools_metadata.html @@ -0,0 +1,77 @@ + + + + Test for Sandbox metadata on WebExtensions ContentScripts + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_fission_frame.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_fission_frame.html new file mode 100644 index 0000000000..702456a798 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_fission_frame.html @@ -0,0 +1,100 @@ + + + Test content script in cross-origin frame + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_incognito.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_incognito.html new file mode 100644 index 0000000000..63dd23b151 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_incognito.html @@ -0,0 +1,105 @@ + + + + Test for content script private browsing ID + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_permission.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_permission.html new file mode 100644 index 0000000000..e6bc48800c --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_permission.html @@ -0,0 +1,61 @@ + + + + Test for content script + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_cookies.html b/toolkit/components/extensions/test/mochitest/test_ext_cookies.html new file mode 100644 index 0000000000..c9dd05a41c --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_cookies.html @@ -0,0 +1,366 @@ + + + + WebExtension test + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_cookies_containers.html b/toolkit/components/extensions/test/mochitest/test_ext_cookies_containers.html new file mode 100644 index 0000000000..db12a97854 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_cookies_containers.html @@ -0,0 +1,97 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_cookies_expiry.html b/toolkit/components/extensions/test/mochitest/test_ext_cookies_expiry.html new file mode 100644 index 0000000000..fa118f5271 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_cookies_expiry.html @@ -0,0 +1,72 @@ + + + + WebExtension cookies test + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_cookies_first_party.html b/toolkit/components/extensions/test/mochitest/test_ext_cookies_first_party.html new file mode 100644 index 0000000000..7e33f4731d --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_cookies_first_party.html @@ -0,0 +1,316 @@ + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_cookies_incognito.html b/toolkit/components/extensions/test/mochitest/test_ext_cookies_incognito.html new file mode 100644 index 0000000000..a7c6931c06 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_cookies_incognito.html @@ -0,0 +1,112 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions_bad.html b/toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions_bad.html new file mode 100644 index 0000000000..0bd2852075 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions_bad.html @@ -0,0 +1,115 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions_good.html b/toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions_good.html new file mode 100644 index 0000000000..bd76f2b9c0 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions_good.html @@ -0,0 +1,89 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_downloads_download.html b/toolkit/components/extensions/test/mochitest/test_ext_downloads_download.html new file mode 100644 index 0000000000..ea163db0de --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_downloads_download.html @@ -0,0 +1,90 @@ + + + + + Downloads Test + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_embeddedimg_iframe_frameAncestors.html b/toolkit/components/extensions/test/mochitest/test_ext_embeddedimg_iframe_frameAncestors.html new file mode 100644 index 0000000000..d6702da4d3 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_embeddedimg_iframe_frameAncestors.html @@ -0,0 +1,94 @@ + + + + Test checking webRequest.onBeforeRequest details object + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_exclude_include_globs.html b/toolkit/components/extensions/test/mochitest/test_ext_exclude_include_globs.html new file mode 100644 index 0000000000..bdf300ec50 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_exclude_include_globs.html @@ -0,0 +1,91 @@ + + + + Test for content script + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_external_messaging.html b/toolkit/components/extensions/test/mochitest/test_ext_external_messaging.html new file mode 100644 index 0000000000..ba91989d9c --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_external_messaging.html @@ -0,0 +1,110 @@ + + + + WebExtension external messaging + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_generate.html b/toolkit/components/extensions/test/mochitest/test_ext_generate.html new file mode 100644 index 0000000000..ba88d16ca3 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_generate.html @@ -0,0 +1,48 @@ + + + + Test for generating WebExtensions + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_geolocation.html b/toolkit/components/extensions/test/mochitest/test_ext_geolocation.html new file mode 100644 index 0000000000..9f326372bb --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_geolocation.html @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_identity.html b/toolkit/components/extensions/test/mochitest/test_ext_identity.html new file mode 100644 index 0000000000..c40578cd40 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_identity.html @@ -0,0 +1,390 @@ + + + + Test for WebExtension Identity + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_idle.html b/toolkit/components/extensions/test/mochitest/test_ext_idle.html new file mode 100644 index 0000000000..381687ee38 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_idle.html @@ -0,0 +1,68 @@ + + + + WebExtension test + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_inIncognitoContext_window.html b/toolkit/components/extensions/test/mochitest/test_ext_inIncognitoContext_window.html new file mode 100644 index 0000000000..5b36902581 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_inIncognitoContext_window.html @@ -0,0 +1,49 @@ + + + + WebExtension test + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_listener_proxies.html b/toolkit/components/extensions/test/mochitest/test_ext_listener_proxies.html new file mode 100644 index 0000000000..cc161f735f --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_listener_proxies.html @@ -0,0 +1,62 @@ + + + + Test for content script + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_new_tab_processType.html b/toolkit/components/extensions/test/mochitest/test_ext_new_tab_processType.html new file mode 100644 index 0000000000..4561ef1a28 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_new_tab_processType.html @@ -0,0 +1,152 @@ + + + + Test for opening links in new tabs from extension frames + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_notifications.html b/toolkit/components/extensions/test/mochitest/test_ext_notifications.html new file mode 100644 index 0000000000..7a91320373 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_notifications.html @@ -0,0 +1,340 @@ + + + + Test for notifications + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_protocolHandlers.html b/toolkit/components/extensions/test/mochitest/test_ext_protocolHandlers.html new file mode 100644 index 0000000000..10305c8ac0 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_protocolHandlers.html @@ -0,0 +1,394 @@ + + + + + Test for protocol handlers + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_redirect_jar.html b/toolkit/components/extensions/test/mochitest/test_ext_redirect_jar.html new file mode 100644 index 0000000000..24dc737982 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_redirect_jar.html @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_request_urlClassification.html b/toolkit/components/extensions/test/mochitest/test_ext_request_urlClassification.html new file mode 100644 index 0000000000..d9a85ad8e4 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_request_urlClassification.html @@ -0,0 +1,129 @@ + + + + Test for WebRequest urlClassification + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect.html b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect.html new file mode 100644 index 0000000000..c4726092ec --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect.html @@ -0,0 +1,82 @@ + + + + WebExtension test + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect2.html b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect2.html new file mode 100644 index 0000000000..13b9029c48 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect2.html @@ -0,0 +1,102 @@ + + + + WebExtension test + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect_twoway.html b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect_twoway.html new file mode 100644 index 0000000000..b671cba23d --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect_twoway.html @@ -0,0 +1,126 @@ + + + + WebExtension test + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_runtime_disconnect.html b/toolkit/components/extensions/test/mochitest/test_ext_runtime_disconnect.html new file mode 100644 index 0000000000..f18190bf8b --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_disconnect.html @@ -0,0 +1,77 @@ + + + + WebExtension test + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_doublereply.html b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_doublereply.html new file mode 100644 index 0000000000..ffdbc90efb --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_doublereply.html @@ -0,0 +1,100 @@ + + + + WebExtension test + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_frameId.html b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_frameId.html new file mode 100644 index 0000000000..ca151b0216 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_frameId.html @@ -0,0 +1,49 @@ + + + Test sendMessage frameId + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_no_receiver.html b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_no_receiver.html new file mode 100644 index 0000000000..970de26528 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_no_receiver.html @@ -0,0 +1,82 @@ + + + + WebExtension test + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply.html b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply.html new file mode 100644 index 0000000000..a7f6314efd --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply.html @@ -0,0 +1,78 @@ + + + + WebExtension test + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply2.html b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply2.html new file mode 100644 index 0000000000..d3227dbcaf --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply2.html @@ -0,0 +1,204 @@ + + + + WebExtension test + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_storage_cleanup.html b/toolkit/components/extensions/test/mochitest/test_ext_storage_cleanup.html new file mode 100644 index 0000000000..e02b016419 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_storage_cleanup.html @@ -0,0 +1,235 @@ + + + + WebExtension test + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_storage_manager_capabilities.html b/toolkit/components/extensions/test/mochitest/test_ext_storage_manager_capabilities.html new file mode 100644 index 0000000000..3a02f3fb63 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_storage_manager_capabilities.html @@ -0,0 +1,126 @@ + + + + Test Storage API + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_storage_smoke_test.html b/toolkit/components/extensions/test/mochitest/test_ext_storage_smoke_test.html new file mode 100644 index 0000000000..b0c7425383 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_storage_smoke_test.html @@ -0,0 +1,110 @@ + + + + WebExtension test + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_streamfilter_multiple.html b/toolkit/components/extensions/test/mochitest/test_ext_streamfilter_multiple.html new file mode 100644 index 0000000000..d1bfbd824b --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_streamfilter_multiple.html @@ -0,0 +1,91 @@ + + + + Test for multiple extensions trying to filterResponseData on the same request + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_streamfilter_processswitch.html b/toolkit/components/extensions/test/mochitest/test_ext_streamfilter_processswitch.html new file mode 100644 index 0000000000..2cf15db4e2 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_streamfilter_processswitch.html @@ -0,0 +1,73 @@ + + + + Test for using filterResponseData to intercept a cross-origin navigation that will involve a process switch with fission + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_subframes_privileges.html b/toolkit/components/extensions/test/mochitest/test_ext_subframes_privileges.html new file mode 100644 index 0000000000..f7389236ab --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_subframes_privileges.html @@ -0,0 +1,340 @@ + + + + + WebExtension test + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_tabs_captureTab.html b/toolkit/components/extensions/test/mochitest/test_ext_tabs_captureTab.html new file mode 100644 index 0000000000..7feb1064ba --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_tabs_captureTab.html @@ -0,0 +1,301 @@ + + + + + Tests tabs.captureTab and tabs.captureVisibleTab + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_tabs_permissions.html b/toolkit/components/extensions/test/mochitest/test_ext_tabs_permissions.html new file mode 100644 index 0000000000..99d8b77f16 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_tabs_permissions.html @@ -0,0 +1,780 @@ + + + + Tabs permissions test + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_tabs_query_popup.html b/toolkit/components/extensions/test/mochitest/test_ext_tabs_query_popup.html new file mode 100644 index 0000000000..6393114c5f --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_tabs_query_popup.html @@ -0,0 +1,95 @@ + + + + Tabs create Test + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_tabs_sendMessage.html b/toolkit/components/extensions/test/mochitest/test_ext_tabs_sendMessage.html new file mode 100644 index 0000000000..293914fe5d --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_tabs_sendMessage.html @@ -0,0 +1,95 @@ + + + + Test tabs.sendMessage + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_test.html b/toolkit/components/extensions/test/mochitest/test_ext_test.html new file mode 100644 index 0000000000..9fef13d8d4 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_test.html @@ -0,0 +1,196 @@ + + + + Testing test + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_unlimitedStorage.html b/toolkit/components/extensions/test/mochitest/test_ext_unlimitedStorage.html new file mode 100644 index 0000000000..b92de8ab4f --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_unlimitedStorage.html @@ -0,0 +1,139 @@ + + + + Test for simple WebExtension + + + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_unlimitedStorage_legacy_persistent_indexedDB.html b/toolkit/components/extensions/test/mochitest/test_ext_unlimitedStorage_legacy_persistent_indexedDB.html new file mode 100644 index 0000000000..fe06d22e8d --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_unlimitedStorage_legacy_persistent_indexedDB.html @@ -0,0 +1,81 @@ + + + + Test for simple WebExtension + + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_incognito.html b/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_incognito.html new file mode 100644 index 0000000000..5c9de814e4 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_incognito.html @@ -0,0 +1,174 @@ + + + + Test the web_accessible_resources incognito + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html b/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html new file mode 100644 index 0000000000..d6ae4358d4 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html @@ -0,0 +1,265 @@ + + + + Test the web_accessible_resources manifest directive + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webnavigation.html b/toolkit/components/extensions/test/mochitest/test_ext_webnavigation.html new file mode 100644 index 0000000000..f471ef6a2f --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webnavigation.html @@ -0,0 +1,611 @@ + + + + Test for simple WebExtension + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webnavigation_filters.html b/toolkit/components/extensions/test/mochitest/test_ext_webnavigation_filters.html new file mode 100644 index 0000000000..60720e9663 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webnavigation_filters.html @@ -0,0 +1,299 @@ + + + + Test for simple WebExtension + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webnavigation_incognito.html b/toolkit/components/extensions/test/mochitest/test_ext_webnavigation_incognito.html new file mode 100644 index 0000000000..6b161d8247 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webnavigation_incognito.html @@ -0,0 +1,109 @@ + + + + Test for simple WebExtension + + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_and_proxy_filter.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_and_proxy_filter.html new file mode 100644 index 0000000000..ea99ec244a --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_and_proxy_filter.html @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_auth.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_auth.html new file mode 100644 index 0000000000..f191e58b56 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_auth.html @@ -0,0 +1,182 @@ + + + + + + + + + + + + + +
Authorization Test
+ + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html new file mode 100644 index 0000000000..86cec62fb4 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html @@ -0,0 +1,120 @@ + + + + Test for simple WebExtension + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_basic.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_basic.html new file mode 100644 index 0000000000..742f048a8a --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_basic.html @@ -0,0 +1,446 @@ + + + + + + + + + + + + + +
Sample text
+ + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_errors.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_errors.html new file mode 100644 index 0000000000..89ef9f4809 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_errors.html @@ -0,0 +1,61 @@ + + + + Test for WebRequest errors + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_filter.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_filter.html new file mode 100644 index 0000000000..62539b54bc --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_filter.html @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_frameId.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_frameId.html new file mode 100644 index 0000000000..1b26a77f2b --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_frameId.html @@ -0,0 +1,214 @@ + + + + + + + + + + + + + +
Sample text
+ + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_hsts.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_hsts.html new file mode 100644 index 0000000000..51ffc1e4f6 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_hsts.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_redirect_bypass_cors.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_redirect_bypass_cors.html new file mode 100644 index 0000000000..457d0508b7 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_redirect_bypass_cors.html @@ -0,0 +1,70 @@ + + + + Bug 1450965: Skip Cors Check for Early WebExtention Redirects + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_redirect_data_uri.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_redirect_data_uri.html new file mode 100644 index 0000000000..5d58549c46 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_redirect_data_uri.html @@ -0,0 +1,83 @@ + + + + Bug 1434357: Allow Web Request API to redirect to data: URI + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_upgrade.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_upgrade.html new file mode 100644 index 0000000000..3d24f5a64d --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_upgrade.html @@ -0,0 +1,89 @@ + + + + Test for simple WebExtension + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_upload.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_upload.html new file mode 100644 index 0000000000..f4f2e66955 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_upload.html @@ -0,0 +1,212 @@ + + + + + + + + + + + + +
+ + + + +
+ +
+ + + +
+ + +
+ + +
+ + + diff --git a/toolkit/components/extensions/test/mochitest/test_ext_window_postMessage.html b/toolkit/components/extensions/test/mochitest/test_ext_window_postMessage.html new file mode 100644 index 0000000000..53b19d0ead --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_window_postMessage.html @@ -0,0 +1,104 @@ + + + + Test for content script + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_verify_non_remote_mode.html b/toolkit/components/extensions/test/mochitest/test_verify_non_remote_mode.html new file mode 100644 index 0000000000..6f46fa8eea --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_verify_non_remote_mode.html @@ -0,0 +1,31 @@ + + + + Verify non-remote mode + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/test_verify_remote_mode.html b/toolkit/components/extensions/test/mochitest/test_verify_remote_mode.html new file mode 100644 index 0000000000..2be0e19179 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_verify_remote_mode.html @@ -0,0 +1,22 @@ + + + + Verify remote mode + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/mochitest/webrequest_chromeworker.js b/toolkit/components/extensions/test/mochitest/webrequest_chromeworker.js new file mode 100644 index 0000000000..6a44fcac2e --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/webrequest_chromeworker.js @@ -0,0 +1,9 @@ +"use strict"; + +/* eslint-env worker */ + +onmessage = function(event) { + fetch("https://example.com/example.txt").then(() => { + postMessage("Done!"); + }); +}; diff --git a/toolkit/components/extensions/test/mochitest/webrequest_test.jsm b/toolkit/components/extensions/test/mochitest/webrequest_test.jsm new file mode 100644 index 0000000000..6fc2fe3d7f --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/webrequest_test.jsm @@ -0,0 +1,22 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["webrequest_test"]; + +Cu.importGlobalProperties(["fetch"]); + +var webrequest_test = { + testFetch(url) { + return fetch(url); + }, + + testXHR(url) { + return new Promise(resolve => { + let xhr = new XMLHttpRequest(); + xhr.open("HEAD", url); + xhr.onload = () => { + resolve(); + }; + xhr.send(); + }); + }, +}; diff --git a/toolkit/components/extensions/test/mochitest/webrequest_worker.js b/toolkit/components/extensions/test/mochitest/webrequest_worker.js new file mode 100644 index 0000000000..dcffd08578 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/webrequest_worker.js @@ -0,0 +1,3 @@ +"use strict"; + +fetch("https://example.com/example.txt"); diff --git a/toolkit/components/extensions/test/xpcshell/.eslintrc.js b/toolkit/components/extensions/test/xpcshell/.eslintrc.js new file mode 100644 index 0000000000..3622fff4f6 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/.eslintrc.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + env: { + // The tests in this folder are testing based on WebExtensions, so lets + // just define the webextensions environment here. + webextensions: true, + }, +}; diff --git a/toolkit/components/extensions/test/xpcshell/data/dummy_page.html b/toolkit/components/extensions/test/xpcshell/data/dummy_page.html new file mode 100644 index 0000000000..c1c9a4e043 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/dummy_page.html @@ -0,0 +1,7 @@ + + + + +

Page

+ + diff --git a/toolkit/components/extensions/test/xpcshell/data/empty_file_download.txt b/toolkit/components/extensions/test/xpcshell/data/empty_file_download.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/toolkit/components/extensions/test/xpcshell/data/file download.txt b/toolkit/components/extensions/test/xpcshell/data/file download.txt new file mode 100644 index 0000000000..6293c7af79 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file download.txt @@ -0,0 +1 @@ +This is a sample file used in download tests. diff --git a/toolkit/components/extensions/test/xpcshell/data/file_WebRequest_page2.html b/toolkit/components/extensions/test/xpcshell/data/file_WebRequest_page2.html new file mode 100644 index 0000000000..b2cf48f9e1 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_WebRequest_page2.html @@ -0,0 +1,25 @@ + + + + + + + + + + + +
Sample text
+ + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_original.html b/toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_original.html new file mode 100644 index 0000000000..f6b5142c4d --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_original.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_original.js b/toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_original.js new file mode 100644 index 0000000000..2981108b64 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_original.js @@ -0,0 +1,2 @@ +"use strict"; +window.testScript = "original"; diff --git a/toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_redirected.html b/toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_redirected.html new file mode 100644 index 0000000000..0979593f7b --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_redirected.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_redirected.js b/toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_redirected.js new file mode 100644 index 0000000000..06fd42aa40 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_WebRequest_permission_redirected.js @@ -0,0 +1,2 @@ +"use strict"; +window.testScript = "redirected"; diff --git a/toolkit/components/extensions/test/xpcshell/data/file_csp.html b/toolkit/components/extensions/test/xpcshell/data/file_csp.html new file mode 100644 index 0000000000..9f5cf92f5a --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_csp.html @@ -0,0 +1,14 @@ + + + + + + + + +
Sample text
+ + + + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_csp.html^headers^ b/toolkit/components/extensions/test/xpcshell/data/file_csp.html^headers^ new file mode 100644 index 0000000000..4c6fa3c26a --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_csp.html^headers^ @@ -0,0 +1 @@ +Content-Security-Policy: default-src 'self' diff --git a/toolkit/components/extensions/test/xpcshell/data/file_do_load_script_subresource.html b/toolkit/components/extensions/test/xpcshell/data/file_do_load_script_subresource.html new file mode 100644 index 0000000000..c74dec5f5a --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_do_load_script_subresource.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_document_open.html b/toolkit/components/extensions/test/xpcshell/data/file_document_open.html new file mode 100644 index 0000000000..dae5e90667 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_document_open.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_document_write.html b/toolkit/components/extensions/test/xpcshell/data/file_document_write.html new file mode 100644 index 0000000000..fbae3d6d76 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_document_write.html @@ -0,0 +1,35 @@ + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_download.html b/toolkit/components/extensions/test/xpcshell/data/file_download.html new file mode 100644 index 0000000000..d970c63259 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_download.html @@ -0,0 +1,12 @@ + + + + + + + + +
Download HTML File
+ + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_download.txt b/toolkit/components/extensions/test/xpcshell/data/file_download.txt new file mode 100644 index 0000000000..6293c7af79 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_download.txt @@ -0,0 +1 @@ +This is a sample file used in download tests. diff --git a/toolkit/components/extensions/test/xpcshell/data/file_iframe.html b/toolkit/components/extensions/test/xpcshell/data/file_iframe.html new file mode 100644 index 0000000000..0cd68be586 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_iframe.html @@ -0,0 +1,9 @@ + + + + + Iframe document + + + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_image_bad.png b/toolkit/components/extensions/test/xpcshell/data/file_image_bad.png new file mode 100644 index 0000000000..4c3be50847 Binary files /dev/null and b/toolkit/components/extensions/test/xpcshell/data/file_image_bad.png differ diff --git a/toolkit/components/extensions/test/xpcshell/data/file_image_good.png b/toolkit/components/extensions/test/xpcshell/data/file_image_good.png new file mode 100644 index 0000000000..769c636340 Binary files /dev/null and b/toolkit/components/extensions/test/xpcshell/data/file_image_good.png differ diff --git a/toolkit/components/extensions/test/xpcshell/data/file_image_redirect.png b/toolkit/components/extensions/test/xpcshell/data/file_image_redirect.png new file mode 100644 index 0000000000..4c3be50847 Binary files /dev/null and b/toolkit/components/extensions/test/xpcshell/data/file_image_redirect.png differ diff --git a/toolkit/components/extensions/test/xpcshell/data/file_page_xhr.html b/toolkit/components/extensions/test/xpcshell/data/file_page_xhr.html new file mode 100644 index 0000000000..387b5285f5 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_page_xhr.html @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_permission_xhr.html b/toolkit/components/extensions/test/xpcshell/data/file_permission_xhr.html new file mode 100644 index 0000000000..6f1bb4648b --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_permission_xhr.html @@ -0,0 +1,61 @@ + + + + + + + + + + + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_privilege_escalation.html b/toolkit/components/extensions/test/xpcshell/data/file_privilege_escalation.html new file mode 100644 index 0000000000..258f7058d9 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_privilege_escalation.html @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_sample.html b/toolkit/components/extensions/test/xpcshell/data/file_sample.html new file mode 100644 index 0000000000..a20e49a1f0 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_sample.html @@ -0,0 +1,12 @@ + + + + + + + + +
Sample text
+ + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_sample_registered_styles.html b/toolkit/components/extensions/test/xpcshell/data/file_sample_registered_styles.html new file mode 100644 index 0000000000..9f5c5d5a6a --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_sample_registered_styles.html @@ -0,0 +1,13 @@ + + + + + + + + +
Registered Extension URL style
+
Registered Extension Text style
+ + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_script.html b/toolkit/components/extensions/test/xpcshell/data/file_script.html new file mode 100644 index 0000000000..8d192b7d8e --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_script.html @@ -0,0 +1,14 @@ + + + + + + + + + + +
Sample text
+ + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_script_bad.js b/toolkit/components/extensions/test/xpcshell/data/file_script_bad.js new file mode 100644 index 0000000000..ff4572865b --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_script_bad.js @@ -0,0 +1,12 @@ +"use strict"; + +window.failure = true; +window.addEventListener( + "load", + () => { + let el = document.createElement("div"); + el.setAttribute("id", "bad"); + document.body.appendChild(el); + }, + { once: true } +); diff --git a/toolkit/components/extensions/test/xpcshell/data/file_script_good.js b/toolkit/components/extensions/test/xpcshell/data/file_script_good.js new file mode 100644 index 0000000000..bf47fb36d2 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_script_good.js @@ -0,0 +1,12 @@ +"use strict"; + +window.success = window.success ? window.success + 1 : 1; +window.addEventListener( + "load", + () => { + let el = document.createElement("div"); + el.setAttribute("id", "good"); + document.body.appendChild(el); + }, + { once: true } +); diff --git a/toolkit/components/extensions/test/xpcshell/data/file_script_redirect.js b/toolkit/components/extensions/test/xpcshell/data/file_script_redirect.js new file mode 100644 index 0000000000..c425122c71 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_script_redirect.js @@ -0,0 +1,3 @@ +"use strict"; + +window.failure = true; diff --git a/toolkit/components/extensions/test/xpcshell/data/file_script_xhr.js b/toolkit/components/extensions/test/xpcshell/data/file_script_xhr.js new file mode 100644 index 0000000000..24a26cb8d1 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_script_xhr.js @@ -0,0 +1,9 @@ +"use strict"; + +var request = new XMLHttpRequest(); +request.open( + "get", + "http://example.com/browser/toolkit/modules/tests/browser/xhr_resource", + false +); +request.send(); diff --git a/toolkit/components/extensions/test/xpcshell/data/file_shadowdom.html b/toolkit/components/extensions/test/xpcshell/data/file_shadowdom.html new file mode 100644 index 0000000000..c4e7db14e7 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_shadowdom.html @@ -0,0 +1,13 @@ + + + + + + +
host
+ + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_style_bad.css b/toolkit/components/extensions/test/xpcshell/data/file_style_bad.css new file mode 100644 index 0000000000..8dbc8dc7a4 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_style_bad.css @@ -0,0 +1,3 @@ +#test { + color: green !important; +} diff --git a/toolkit/components/extensions/test/xpcshell/data/file_style_good.css b/toolkit/components/extensions/test/xpcshell/data/file_style_good.css new file mode 100644 index 0000000000..46f9774b5f --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_style_good.css @@ -0,0 +1,3 @@ +#test { + color: red; +} diff --git a/toolkit/components/extensions/test/xpcshell/data/file_style_redirect.css b/toolkit/components/extensions/test/xpcshell/data/file_style_redirect.css new file mode 100644 index 0000000000..8dbc8dc7a4 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_style_redirect.css @@ -0,0 +1,3 @@ +#test { + color: green !important; +} diff --git a/toolkit/components/extensions/test/xpcshell/data/file_stylesheet_cache.css b/toolkit/components/extensions/test/xpcshell/data/file_stylesheet_cache.css new file mode 100644 index 0000000000..6a9140d97e --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_stylesheet_cache.css @@ -0,0 +1 @@ +:root { color: green; } diff --git a/toolkit/components/extensions/test/xpcshell/data/file_stylesheet_cache.html b/toolkit/components/extensions/test/xpcshell/data/file_stylesheet_cache.html new file mode 100644 index 0000000000..6d6d187a27 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_stylesheet_cache.html @@ -0,0 +1,3 @@ + + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_stylesheet_cache_2.html b/toolkit/components/extensions/test/xpcshell/data/file_stylesheet_cache_2.html new file mode 100644 index 0000000000..07a4324c44 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_stylesheet_cache_2.html @@ -0,0 +1,19 @@ + + + + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_toplevel.html b/toolkit/components/extensions/test/xpcshell/data/file_toplevel.html new file mode 100644 index 0000000000..d93813d0f5 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_toplevel.html @@ -0,0 +1,12 @@ + + + + + Top-level frame document + + + + + + + diff --git a/toolkit/components/extensions/test/xpcshell/data/file_with_xorigin_frame.html b/toolkit/components/extensions/test/xpcshell/data/file_with_xorigin_frame.html new file mode 100644 index 0000000000..199c2ce4d4 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/data/file_with_xorigin_frame.html @@ -0,0 +1,10 @@ + + + + + Document with example.org frame + + + + + diff --git a/toolkit/components/extensions/test/xpcshell/data/lorem.html.gz b/toolkit/components/extensions/test/xpcshell/data/lorem.html.gz new file mode 100644 index 0000000000..9eb8d73d50 Binary files /dev/null and b/toolkit/components/extensions/test/xpcshell/data/lorem.html.gz differ diff --git a/toolkit/components/extensions/test/xpcshell/data/pixel_green.gif b/toolkit/components/extensions/test/xpcshell/data/pixel_green.gif new file mode 100644 index 0000000000..baf8166dae Binary files /dev/null and b/toolkit/components/extensions/test/xpcshell/data/pixel_green.gif differ diff --git a/toolkit/components/extensions/test/xpcshell/data/pixel_red.gif b/toolkit/components/extensions/test/xpcshell/data/pixel_red.gif new file mode 100644 index 0000000000..48f97f74bd Binary files /dev/null and b/toolkit/components/extensions/test/xpcshell/data/pixel_red.gif differ diff --git a/toolkit/components/extensions/test/xpcshell/head.js b/toolkit/components/extensions/test/xpcshell/head.js new file mode 100644 index 0000000000..4608f77bd6 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/head.js @@ -0,0 +1,277 @@ +"use strict"; + +/* exported createHttpServer, cleanupDir, clearCache, promiseConsoleOutput, + promiseQuotaManagerServiceReset, promiseQuotaManagerServiceClear, + runWithPrefs, testEnv, withHandlingUserInput, resetHandlingUserInput */ + +var { AppConstants } = ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" +); +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); +var { + clearInterval, + clearTimeout, + setInterval, + setIntervalWithTarget, + setTimeout, + setTimeoutWithTarget, +} = ChromeUtils.import("resource://gre/modules/Timer.jsm"); +var { AddonTestUtils, MockAsyncShutdown } = ChromeUtils.import( + "resource://testing-common/AddonTestUtils.jsm" +); + +// eslint-disable-next-line no-unused-vars +XPCOMUtils.defineLazyModuleGetters(this, { + ContentTask: "resource://testing-common/ContentTask.jsm", + Extension: "resource://gre/modules/Extension.jsm", + ExtensionData: "resource://gre/modules/Extension.jsm", + ExtensionParent: "resource://gre/modules/ExtensionParent.jsm", + ExtensionTestUtils: "resource://testing-common/ExtensionXPCShellUtils.jsm", + FileUtils: "resource://gre/modules/FileUtils.jsm", + MessageChannel: "resource://gre/modules/MessageChannel.jsm", + NetUtil: "resource://gre/modules/NetUtil.jsm", + PromiseTestUtils: "resource://testing-common/PromiseTestUtils.jsm", + Schemas: "resource://gre/modules/Schemas.jsm", +}); + +PromiseTestUtils.allowMatchingRejectionsGlobally( + /Message manager disconnected/ +); + +// These values may be changed in later head files and tested in check_remote +// below. +Services.prefs.setBoolPref("browser.tabs.remote.autostart", false); +Services.prefs.setBoolPref("extensions.webextensions.remote", false); +const testEnv = { + expectRemote: false, +}; + +add_task(function check_remote() { + Assert.equal( + WebExtensionPolicy.useRemoteWebExtensions, + testEnv.expectRemote, + "useRemoteWebExtensions matches" + ); + Assert.equal( + WebExtensionPolicy.isExtensionProcess, + !testEnv.expectRemote, + "testing from extension process" + ); +}); + +ExtensionTestUtils.init(this); + +var createHttpServer = (...args) => { + AddonTestUtils.maybeInit(this); + return AddonTestUtils.createHttpServer(...args); +}; + +if (AppConstants.platform === "android") { + Services.io.offline = true; +} + +/** + * Clears the HTTP and content image caches. + */ +function clearCache() { + Services.cache2.clear(); + + let imageCache = Cc["@mozilla.org/image/tools;1"] + .getService(Ci.imgITools) + .getImgCacheForDocument(null); + imageCache.clearCache(false); +} + +var promiseConsoleOutput = async function(task) { + const DONE = `=== console listener ${Math.random()} done ===`; + + let listener; + let messages = []; + let awaitListener = new Promise(resolve => { + listener = msg => { + if (msg == DONE) { + resolve(); + } else { + void (msg instanceof Ci.nsIConsoleMessage); + void (msg instanceof Ci.nsIScriptError); + messages.push(msg); + } + }; + }); + + Services.console.registerListener(listener); + try { + let result = await task(); + + Services.console.logStringMessage(DONE); + await awaitListener; + + return { messages, result }; + } finally { + Services.console.unregisterListener(listener); + } +}; + +// Attempt to remove a directory. If the Windows OS is still using the +// file sometimes remove() will fail. So try repeatedly until we can +// remove it or we give up. +function cleanupDir(dir) { + let count = 0; + return new Promise((resolve, reject) => { + function tryToRemoveDir() { + count += 1; + try { + dir.remove(true); + } catch (e) { + // ignore + } + if (!dir.exists()) { + return resolve(); + } + if (count >= 25) { + return reject(`Failed to cleanup directory: ${dir}`); + } + setTimeout(tryToRemoveDir, 100); + } + tryToRemoveDir(); + }); +} + +// Run a test with the specified preferences and then restores their initial values +// right after the test function run (whether it passes or fails). +async function runWithPrefs(prefsToSet, testFn) { + const setPrefs = prefs => { + for (let [pref, value] of prefs) { + if (value === undefined) { + // Clear any pref that didn't have a user value. + info(`Clearing pref "${pref}"`); + Services.prefs.clearUserPref(pref); + continue; + } + + info(`Setting pref "${pref}": ${value}`); + switch (typeof value) { + case "boolean": + Services.prefs.setBoolPref(pref, value); + break; + case "number": + Services.prefs.setIntPref(pref, value); + break; + case "string": + Services.prefs.setStringPref(pref, value); + break; + default: + throw new Error("runWithPrefs doesn't support this pref type yet"); + } + } + }; + + const getPrefs = prefs => { + return prefs.map(([pref, value]) => { + info(`Getting initial pref value for "${pref}"`); + if (!Services.prefs.prefHasUserValue(pref)) { + // Check if the pref doesn't have a user value. + return [pref, undefined]; + } + switch (typeof value) { + case "boolean": + return [pref, Services.prefs.getBoolPref(pref)]; + case "number": + return [pref, Services.prefs.getIntPref(pref)]; + case "string": + return [pref, Services.prefs.getStringPref(pref)]; + default: + throw new Error("runWithPrefs doesn't support this pref type yet"); + } + }); + }; + + let initialPrefsValues = []; + + try { + initialPrefsValues = getPrefs(prefsToSet); + + setPrefs(prefsToSet); + + await testFn(); + } finally { + info("Restoring initial preferences values on exit"); + setPrefs(initialPrefsValues); + } +} + +// "Handling User Input" test helpers. + +let extensionHandlers = new WeakSet(); + +function handlingUserInputFrameScript() { + /* globals content */ + // eslint-disable-next-line no-shadow + const { MessageChannel } = ChromeUtils.import( + "resource://gre/modules/MessageChannel.jsm" + ); + + let handle; + MessageChannel.addListener(this, "ExtensionTest:HandleUserInput", { + receiveMessage({ name, data }) { + if (data) { + handle = content.windowUtils.setHandlingUserInput(true); + } else if (handle) { + handle.destruct(); + handle = null; + } + }, + }); +} + +// If you use withHandlingUserInput then restart the addon manager, +// you need to reset this before using withHandlingUserInput again. +function resetHandlingUserInput() { + extensionHandlers = new WeakSet(); +} + +async function withHandlingUserInput(extension, fn) { + let { messageManager } = extension.extension.groupFrameLoader; + + if (!extensionHandlers.has(extension)) { + messageManager.loadFrameScript( + `data:,(${encodeURI(handlingUserInputFrameScript)}).call(this)`, + false, + true + ); + extensionHandlers.add(extension); + } + + await MessageChannel.sendMessage( + messageManager, + "ExtensionTest:HandleUserInput", + true + ); + await fn(); + await MessageChannel.sendMessage( + messageManager, + "ExtensionTest:HandleUserInput", + false + ); +} + +// QuotaManagerService test helpers. + +function promiseQuotaManagerServiceReset() { + info("Calling QuotaManagerService.reset to enforce new test storage limits"); + return new Promise(resolve => { + Services.qms.reset().callback = resolve; + }); +} + +function promiseQuotaManagerServiceClear() { + info( + "Calling QuotaManagerService.clear to empty the test data and refresh test storage limits" + ); + return new Promise(resolve => { + Services.qms.clear().callback = resolve; + }); +} diff --git a/toolkit/components/extensions/test/xpcshell/head_e10s.js b/toolkit/components/extensions/test/xpcshell/head_e10s.js new file mode 100644 index 0000000000..196afae7c9 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/head_e10s.js @@ -0,0 +1,8 @@ +"use strict"; + +/* globals ExtensionTestUtils */ + +// xpcshell disables e10s by default. Turn it on. +Services.prefs.setBoolPref("browser.tabs.remote.autostart", true); + +ExtensionTestUtils.remoteContentScripts = true; diff --git a/toolkit/components/extensions/test/xpcshell/head_legacy_ep.js b/toolkit/components/extensions/test/xpcshell/head_legacy_ep.js new file mode 100644 index 0000000000..01f16ec54c --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/head_legacy_ep.js @@ -0,0 +1,13 @@ +"use strict"; + +// Bug 1646182: Test the legacy ExtensionPermission backend until we fully +// migrate to rkv + +{ + const { ExtensionPermissions } = ChromeUtils.import( + "resource://gre/modules/ExtensionPermissions.jsm" + ); + + ExtensionPermissions._useLegacyStorageBackend = true; + ExtensionPermissions._uninit(); +} diff --git a/toolkit/components/extensions/test/xpcshell/head_native_messaging.js b/toolkit/components/extensions/test/xpcshell/head_native_messaging.js new file mode 100644 index 0000000000..e0b977c22c --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/head_native_messaging.js @@ -0,0 +1,153 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* globals AppConstants, FileUtils */ +/* exported getSubprocessCount, setupHosts, waitForSubprocessExit */ + +ChromeUtils.defineModuleGetter( + this, + "MockRegistry", + "resource://testing-common/MockRegistry.jsm" +); +ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); + +let { Subprocess, SubprocessImpl } = ChromeUtils.import( + "resource://gre/modules/Subprocess.jsm", + null +); + +// It's important that we use a space in this directory name to make sure we +// correctly handle executing batch files with spaces in their path. +let tmpDir = FileUtils.getDir("TmpD", ["Native Messaging"]); +tmpDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + +const TYPE_SLUG = + AppConstants.platform === "linux" + ? "native-messaging-hosts" + : "NativeMessagingHosts"; +OS.File.makeDir(OS.Path.join(tmpDir.path, TYPE_SLUG)); + +registerCleanupFunction(() => { + tmpDir.remove(true); +}); + +function getPath(filename) { + return OS.Path.join(tmpDir.path, TYPE_SLUG, filename); +} + +const ID = "native@tests.mozilla.org"; + +async function setupHosts(scripts) { + const PERMS = { unixMode: 0o755 }; + + const env = Cc["@mozilla.org/process/environment;1"].getService( + Ci.nsIEnvironment + ); + const pythonPath = await Subprocess.pathSearch(env.get("PYTHON")); + + async function writeManifest(script, scriptPath, path) { + let body = `#!${pythonPath} -u\n${script.script}`; + + await OS.File.writeAtomic(scriptPath, body); + await OS.File.setPermissions(scriptPath, PERMS); + + let manifest = { + name: script.name, + description: script.description, + path, + type: "stdio", + allowed_extensions: [ID], + }; + + let manifestPath = getPath(`${script.name}.json`); + await OS.File.writeAtomic(manifestPath, JSON.stringify(manifest)); + + return manifestPath; + } + + switch (AppConstants.platform) { + case "macosx": + case "linux": + let dirProvider = { + getFile(property) { + if (property == "XREUserNativeManifests") { + return tmpDir.clone(); + } else if (property == "XRESysNativeManifests") { + return tmpDir.clone(); + } + return null; + }, + }; + + Services.dirsvc.registerProvider(dirProvider); + registerCleanupFunction(() => { + Services.dirsvc.unregisterProvider(dirProvider); + }); + + for (let script of scripts) { + let path = getPath(`${script.name}.py`); + + await writeManifest(script, path, path); + } + break; + + case "win": + const REGKEY = String.raw`Software\Mozilla\NativeMessagingHosts`; + + let registry = new MockRegistry(); + registerCleanupFunction(() => { + registry.shutdown(); + }); + + for (let script of scripts) { + let { scriptExtension = "bat" } = script; + + // It's important that we use a space in this filename. See directory + // name comment above. + let batPath = getPath(`batch ${script.name}.${scriptExtension}`); + let scriptPath = getPath(`${script.name}.py`); + + let batBody = `@ECHO OFF\n${pythonPath} -u "${scriptPath}" %*\n`; + await OS.File.writeAtomic(batPath, batBody); + + // Create absolute and relative path versions of the entry. + for (let [name, path] of [ + [script.name, batPath], + [`relative.${script.name}`, OS.Path.basename(batPath)], + ]) { + script.name = name; + let manifestPath = await writeManifest(script, scriptPath, path); + + registry.setValue( + Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + `${REGKEY}\\${script.name}`, + "", + manifestPath + ); + } + } + break; + + default: + ok( + false, + `Native messaging is not supported on ${AppConstants.platform}` + ); + } +} + +function getSubprocessCount() { + return SubprocessImpl.Process.getWorker() + .call("getProcesses", []) + .then(result => result.size); +} +function waitForSubprocessExit() { + return SubprocessImpl.Process.getWorker() + .call("waitForNoProcesses", []) + .then(() => { + // Return to the main event loop to give IO handlers enough time to consume + // their remaining buffered input. + return new Promise(resolve => setTimeout(resolve, 0)); + }); +} diff --git a/toolkit/components/extensions/test/xpcshell/head_remote.js b/toolkit/components/extensions/test/xpcshell/head_remote.js new file mode 100644 index 0000000000..f9c31144c9 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/head_remote.js @@ -0,0 +1,7 @@ +"use strict"; + +Services.prefs.setBoolPref("extensions.webextensions.remote", true); +Services.prefs.setIntPref("dom.ipc.keepProcessesAlive.extension", 1); + +/* globals testEnv */ +testEnv.expectRemote = true; // tested in head_test.js diff --git a/toolkit/components/extensions/test/xpcshell/head_storage.js b/toolkit/components/extensions/test/xpcshell/head_storage.js new file mode 100644 index 0000000000..09a5b45b0e --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/head_storage.js @@ -0,0 +1,1227 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* import-globals-from head.js */ + +const STORAGE_SYNC_PREF = "webextensions.storage.sync.enabled"; + +// Test implementations and utility functions that are used against multiple +// storage areas (eg, a test which is run against browser.storage.local and +// browser.storage.sync, or a test against browser.storage.sync but needs to +// be run against both the kinto and rust implementations.) + +/** + * Utility function to ensure that all supported APIs for getting are + * tested. + * + * @param {string} areaName + * either "local" or "sync" according to what we want to test + * @param {string} prop + * "key" to look up using the storage API + * @param {Object} value + * "value" to compare against + */ +async function checkGetImpl(areaName, prop, value) { + let storage = browser.storage[areaName]; + + let data = await storage.get(); + browser.test.assertEq( + value, + data[prop], + `unspecified getter worked for ${prop} in ${areaName}` + ); + + data = await storage.get(null); + browser.test.assertEq( + value, + data[prop], + `null getter worked for ${prop} in ${areaName}` + ); + + data = await storage.get(prop); + browser.test.assertEq( + value, + data[prop], + `string getter worked for ${prop} in ${areaName}` + ); + browser.test.assertEq( + Object.keys(data).length, + 1, + `string getter should return an object with a single property` + ); + + data = await storage.get([prop]); + browser.test.assertEq( + value, + data[prop], + `array getter worked for ${prop} in ${areaName}` + ); + browser.test.assertEq( + Object.keys(data).length, + 1, + `array getter with a single key should return an object with a single property` + ); + + data = await storage.get({ [prop]: undefined }); + browser.test.assertEq( + value, + data[prop], + `object getter worked for ${prop} in ${areaName}` + ); + browser.test.assertEq( + Object.keys(data).length, + 1, + `object getter with a single key should return an object with a single property` + ); +} + +function test_config_flag_needed() { + async function testFn() { + function background() { + let promises = []; + let apiTests = [ + { method: "get", args: ["foo"] }, + { method: "set", args: [{ foo: "bar" }] }, + { method: "remove", args: ["foo"] }, + { method: "clear", args: [] }, + ]; + apiTests.forEach(testDef => { + promises.push( + browser.test.assertRejects( + browser.storage.sync[testDef.method](...testDef.args), + "Please set webextensions.storage.sync.enabled to true in about:config", + `storage.sync.${testDef.method} is behind a flag` + ) + ); + }); + + Promise.all(promises).then(() => browser.test.notifyPass("flag needed")); + } + + ok( + !Services.prefs.getBoolPref(STORAGE_SYNC_PREF, false), + "The `${STORAGE_SYNC_PREF}` should be set to false" + ); + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["storage"], + }, + background, + }); + + await extension.startup(); + await extension.awaitFinish("flag needed"); + await extension.unload(); + } + + return runWithPrefs([[STORAGE_SYNC_PREF, false]], testFn); +} + +function test_sync_reloading_extensions_works() { + async function testFn() { + // Just some random extension ID that we can re-use + const extensionId = "my-extension-id@1"; + + function loadExtension() { + function background() { + browser.storage.sync.set({ a: "b" }).then(() => { + browser.test.notifyPass("set-works"); + }); + } + + return ExtensionTestUtils.loadExtension( + { + manifest: { + permissions: ["storage"], + }, + background: `(${background})()`, + }, + extensionId + ); + } + + ok( + Services.prefs.getBoolPref(STORAGE_SYNC_PREF, false), + "The `${STORAGE_SYNC_PREF}` should be set to true" + ); + + let extension1 = loadExtension(); + + await extension1.startup(); + await extension1.awaitFinish("set-works"); + await extension1.unload(); + + let extension2 = loadExtension(); + + await extension2.startup(); + await extension2.awaitFinish("set-works"); + await extension2.unload(); + } + + return runWithPrefs([[STORAGE_SYNC_PREF, true]], testFn); +} + +async function test_background_page_storage(testAreaName) { + async function backgroundScript(checkGet) { + let globalChanges, gResolve; + function clearGlobalChanges() { + globalChanges = new Promise(resolve => { + gResolve = resolve; + }); + } + clearGlobalChanges(); + let expectedAreaName; + + browser.storage.onChanged.addListener((changes, areaName) => { + browser.test.assertEq( + expectedAreaName, + areaName, + "Expected area name received by listener" + ); + gResolve(changes); + }); + + async function checkChanges(areaName, changes, message) { + function checkSub(obj1, obj2) { + for (let prop in obj1) { + browser.test.assertTrue( + obj1[prop] !== undefined, + `checkChanges ${areaName} ${prop} is missing (${message})` + ); + browser.test.assertTrue( + obj2[prop] !== undefined, + `checkChanges ${areaName} ${prop} is missing (${message})` + ); + browser.test.assertEq( + obj1[prop].oldValue, + obj2[prop].oldValue, + `checkChanges ${areaName} ${prop} old (${message})` + ); + browser.test.assertEq( + obj1[prop].newValue, + obj2[prop].newValue, + `checkChanges ${areaName} ${prop} new (${message})` + ); + } + } + + const recentChanges = await globalChanges; + checkSub(changes, recentChanges); + checkSub(recentChanges, changes); + clearGlobalChanges(); + } + + // Regression test for https://bugzilla.mozilla.org/show_bug.cgi?id=1645598 + async function testNonExistingKeys(storage, storageAreaDesc) { + let data = await storage.get({ test6: 6 }); + browser.test.assertEq( + `{"test6":6}`, + JSON.stringify(data), + `Use default value when not stored for ${storageAreaDesc}` + ); + + data = await storage.get({ test6: null }); + browser.test.assertEq( + `{"test6":null}`, + JSON.stringify(data), + `Use default value, even if null for ${storageAreaDesc}` + ); + + data = await storage.get("test6"); + browser.test.assertEq( + `{}`, + JSON.stringify(data), + `Empty result if key is not found for ${storageAreaDesc}` + ); + + data = await storage.get(["test6", "test7"]); + browser.test.assertEq( + `{}`, + JSON.stringify(data), + `Empty result if list of keys is not found for ${storageAreaDesc}` + ); + } + + async function testFalseyValues(areaName) { + let storage = browser.storage[areaName]; + const dataInitial = { + "test-falsey-value-bool": false, + "test-falsey-value-string": "", + "test-falsey-value-number": 0, + }; + const dataUpdate = { + "test-falsey-value-bool": true, + "test-falsey-value-string": "non-empty-string", + "test-falsey-value-number": 10, + }; + + // Compute the expected changes. + const onSetInitial = { + "test-falsey-value-bool": { newValue: false }, + "test-falsey-value-string": { newValue: "" }, + "test-falsey-value-number": { newValue: 0 }, + }; + const onRemovedFalsey = { + "test-falsey-value-bool": { oldValue: false }, + "test-falsey-value-string": { oldValue: "" }, + "test-falsey-value-number": { oldValue: 0 }, + }; + const onUpdatedFalsey = { + "test-falsey-value-bool": { newValue: true, oldValue: false }, + "test-falsey-value-string": { + newValue: "non-empty-string", + oldValue: "", + }, + "test-falsey-value-number": { newValue: 10, oldValue: 0 }, + }; + const keys = Object.keys(dataInitial); + + // Test on removing falsey values. + await storage.set(dataInitial); + await checkChanges(areaName, onSetInitial, "set falsey values"); + await storage.remove(keys); + await checkChanges(areaName, onRemovedFalsey, "remove falsey value"); + + // Test on updating falsey values. + await storage.set(dataInitial); + await checkChanges(areaName, onSetInitial, "set falsey values"); + await storage.set(dataUpdate); + await checkChanges(areaName, onUpdatedFalsey, "set non-falsey values"); + + // Clear the storage state. + await testNonExistingKeys(storage, `${areaName} before clearing`); + await storage.clear(); + await testNonExistingKeys(storage, `${areaName} after clearing`); + await globalChanges; + clearGlobalChanges(); + } + + function CustomObj() { + this.testKey1 = "testValue1"; + } + + CustomObj.prototype.toString = function() { + return '{"testKey2":"testValue2"}'; + }; + + CustomObj.prototype.toJSON = function customObjToJSON() { + return { testKey1: "testValue3" }; + }; + + /* eslint-disable dot-notation */ + async function runTests(areaName) { + expectedAreaName = areaName; + let storage = browser.storage[areaName]; + // Set some data and then test getters. + try { + await storage.set({ "test-prop1": "value1", "test-prop2": "value2" }); + await checkChanges( + areaName, + { + "test-prop1": { newValue: "value1" }, + "test-prop2": { newValue: "value2" }, + }, + "set (a)" + ); + + await checkGet(areaName, "test-prop1", "value1"); + await checkGet(areaName, "test-prop2", "value2"); + + let data = await storage.get({ + "test-prop1": undefined, + "test-prop2": undefined, + other: "default", + }); + browser.test.assertEq( + "value1", + data["test-prop1"], + "prop1 correct (a)" + ); + browser.test.assertEq( + "value2", + data["test-prop2"], + "prop2 correct (a)" + ); + browser.test.assertEq("default", data["other"], "other correct"); + + data = await storage.get(["test-prop1", "test-prop2", "other"]); + browser.test.assertEq( + "value1", + data["test-prop1"], + "prop1 correct (b)" + ); + browser.test.assertEq( + "value2", + data["test-prop2"], + "prop2 correct (b)" + ); + browser.test.assertFalse("other" in data, "other correct"); + + // Remove data in various ways. + await storage.remove("test-prop1"); + await checkChanges( + areaName, + { "test-prop1": { oldValue: "value1" } }, + "remove string" + ); + + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertFalse( + "test-prop1" in data, + "prop1 absent (remove string)" + ); + browser.test.assertTrue( + "test-prop2" in data, + "prop2 present (remove string)" + ); + + await storage.set({ "test-prop1": "value1" }); + await checkChanges( + areaName, + { "test-prop1": { newValue: "value1" } }, + "set (c)" + ); + + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertEq( + data["test-prop1"], + "value1", + "prop1 correct (c)" + ); + browser.test.assertEq( + data["test-prop2"], + "value2", + "prop2 correct (c)" + ); + + await storage.remove(["test-prop1", "test-prop2"]); + await checkChanges( + areaName, + { + "test-prop1": { oldValue: "value1" }, + "test-prop2": { oldValue: "value2" }, + }, + "remove array" + ); + + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertFalse( + "test-prop1" in data, + "prop1 absent (remove array)" + ); + browser.test.assertFalse( + "test-prop2" in data, + "prop2 absent (remove array)" + ); + + await testFalseyValues(areaName); + + // test storage.clear + await storage.set({ "test-prop1": "value1", "test-prop2": "value2" }); + // Make sure that set() handler happened before we clear the + // promise again. + await globalChanges; + + clearGlobalChanges(); + await storage.clear(); + + await checkChanges( + areaName, + { + "test-prop1": { oldValue: "value1" }, + "test-prop2": { oldValue: "value2" }, + }, + "clear" + ); + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertFalse("test-prop1" in data, "prop1 absent (clear)"); + browser.test.assertFalse("test-prop2" in data, "prop2 absent (clear)"); + + // Make sure we can store complex JSON data. + // known previous values + await storage.set({ "test-prop1": "value1", "test-prop2": "value2" }); + + // Make sure the set() handler landed. + await globalChanges; + + let date = new Date(0); + + clearGlobalChanges(); + await storage.set({ + "test-prop1": { + str: "hello", + bool: true, + null: null, + undef: undefined, + obj: {}, + nestedObj: { + testKey: {}, + }, + intKeyObj: { + 4: "testValue1", + 3: "testValue2", + 99: "testValue3", + }, + floatKeyObj: { + 1.4: "testValue1", + 5.5: "testValue2", + }, + customObj: new CustomObj(), + arr: [1, 2], + nestedArr: [1, [2, 3]], + date, + regexp: /regexp/, + }, + }); + + await browser.test.assertRejects( + storage.set({ + window, + }), + /DataCloneError|cyclic object value/ + ); + + await browser.test.assertRejects( + storage.set({ "test-prop2": function func() {} }), + /DataCloneError/ + ); + + const recentChanges = await globalChanges; + + browser.test.assertEq( + "value1", + recentChanges["test-prop1"].oldValue, + "oldValue correct" + ); + browser.test.assertEq( + "object", + typeof recentChanges["test-prop1"].newValue, + "newValue is obj" + ); + clearGlobalChanges(); + + data = await storage.get({ + "test-prop1": undefined, + "test-prop2": undefined, + }); + let obj = data["test-prop1"]; + + browser.test.assertEq( + "object", + typeof obj.customObj, + "custom object part correct" + ); + browser.test.assertEq( + 1, + Object.keys(obj.customObj).length, + "customObj keys correct" + ); + + if (areaName === "local") { + browser.test.assertEq( + String(date), + String(obj.date), + "date part correct" + ); + browser.test.assertEq( + "/regexp/", + obj.regexp.toString(), + "regexp part correct" + ); + // storage.local doesn't call toJSON + browser.test.assertEq( + "testValue1", + obj.customObj.testKey1, + "customObj keys correct" + ); + } else { + browser.test.assertEq( + "1970-01-01T00:00:00.000Z", + String(obj.date), + "date part correct" + ); + + browser.test.assertEq( + "object", + typeof obj.regexp, + "regexp part is an object" + ); + browser.test.assertEq( + 0, + Object.keys(obj.regexp).length, + "regexp part is an empty object" + ); + // storage.sync does call toJSON + browser.test.assertEq( + "testValue3", + obj.customObj.testKey1, + "customObj keys correct" + ); + } + + browser.test.assertEq("hello", obj.str, "string part correct"); + browser.test.assertEq(true, obj.bool, "bool part correct"); + browser.test.assertEq(null, obj.null, "null part correct"); + browser.test.assertEq(undefined, obj.undef, "undefined part correct"); + browser.test.assertEq(undefined, obj.window, "window part correct"); + browser.test.assertEq("object", typeof obj.obj, "object part correct"); + browser.test.assertEq( + "object", + typeof obj.nestedObj, + "nested object part correct" + ); + browser.test.assertEq( + "object", + typeof obj.nestedObj.testKey, + "nestedObj.testKey part correct" + ); + browser.test.assertEq( + "object", + typeof obj.intKeyObj, + "int key object part correct" + ); + browser.test.assertEq( + "testValue1", + obj.intKeyObj[4], + "intKeyObj[4] part correct" + ); + browser.test.assertEq( + "testValue2", + obj.intKeyObj[3], + "intKeyObj[3] part correct" + ); + browser.test.assertEq( + "testValue3", + obj.intKeyObj[99], + "intKeyObj[99] part correct" + ); + browser.test.assertEq( + "object", + typeof obj.floatKeyObj, + "float key object part correct" + ); + browser.test.assertEq( + "testValue1", + obj.floatKeyObj[1.4], + "floatKeyObj[1.4] part correct" + ); + browser.test.assertEq( + "testValue2", + obj.floatKeyObj[5.5], + "floatKeyObj[5.5] part correct" + ); + + browser.test.assertTrue(Array.isArray(obj.arr), "array part present"); + browser.test.assertEq(1, obj.arr[0], "arr[0] part correct"); + browser.test.assertEq(2, obj.arr[1], "arr[1] part correct"); + browser.test.assertEq(2, obj.arr.length, "arr.length part correct"); + browser.test.assertTrue( + Array.isArray(obj.nestedArr), + "nested array part present" + ); + browser.test.assertEq( + 2, + obj.nestedArr.length, + "nestedArr.length part correct" + ); + browser.test.assertEq(1, obj.nestedArr[0], "nestedArr[0] part correct"); + browser.test.assertTrue( + Array.isArray(obj.nestedArr[1]), + "nestedArr[1] part present" + ); + browser.test.assertEq( + 2, + obj.nestedArr[1].length, + "nestedArr[1].length part correct" + ); + browser.test.assertEq( + 2, + obj.nestedArr[1][0], + "nestedArr[1][0] part correct" + ); + browser.test.assertEq( + 3, + obj.nestedArr[1][1], + "nestedArr[1][1] part correct" + ); + } catch (e) { + browser.test.fail(`Error: ${e} :: ${e.stack}`); + browser.test.notifyFail("storage"); + } + } + + browser.test.onMessage.addListener(msg => { + let promise; + if (msg === "test-local") { + promise = runTests("local"); + } else if (msg === "test-sync") { + promise = runTests("sync"); + } + promise.then(() => browser.test.sendMessage("test-finished")); + }); + + browser.test.sendMessage("ready"); + } + + let extensionData = { + background: `(${backgroundScript})(${checkGetImpl})`, + manifest: { + permissions: ["storage"], + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + await extension.awaitMessage("ready"); + + extension.sendMessage(`test-${testAreaName}`); + await extension.awaitMessage("test-finished"); + + await extension.unload(); +} + +function test_storage_sync_requires_real_id() { + async function testFn() { + async function background() { + const EXCEPTION_MESSAGE = + "The storage API is not available with a temporary addon ID. " + + "Please add an explicit addon ID to your manifest. " + + "For more information see https://mzl.la/3lPk1aE."; + + await browser.test.assertRejects( + browser.storage.sync.set({ foo: "bar" }), + EXCEPTION_MESSAGE + ); + + browser.test.notifyPass("exception correct"); + } + + let extensionData = { + background, + manifest: { + permissions: ["storage"], + }, + useAddonManager: "temporary", + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + await extension.awaitFinish("exception correct"); + + await extension.unload(); + } + + return runWithPrefs([[STORAGE_SYNC_PREF, true]], testFn); +} + +// Test for storage areas which don't support getBytesInUse() nor QUOTA +// constants. +async function check_storage_area_no_bytes_in_use(area) { + let impl = browser.storage[area]; + + browser.test.assertEq( + typeof impl.getBytesInUse, + "undefined", + "getBytesInUse API method should not be available" + ); + browser.test.sendMessage("test-complete"); +} + +async function test_background_storage_area_no_bytes_in_use(area) { + const EXT_ID = "test-gbiu@mozilla.org"; + + const extensionDef = { + manifest: { + permissions: ["storage"], + applications: { gecko: { id: EXT_ID } }, + }, + background: `(${check_storage_area_no_bytes_in_use})("${area}")`, + }; + + const extension = ExtensionTestUtils.loadExtension(extensionDef); + + await extension.startup(); + await extension.awaitMessage("test-complete"); + await extension.unload(); +} + +async function test_contentscript_storage_area_no_bytes_in_use(area) { + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/data/file_sample.html" + ); + + function contentScript(checkImpl) { + browser.test.onMessage.addListener(msg => { + if (msg === "test-local") { + checkImpl("local"); + } else if (msg === "test-sync") { + checkImpl("sync"); + } else { + browser.test.fail(`Unexpected test message received: ${msg}`); + browser.test.sendMessage("test-complete"); + } + }); + browser.test.sendMessage("ready"); + } + + let extensionData = { + manifest: { + content_scripts: [ + { + matches: ["http://example.com/data/file_sample.html"], + js: ["content_script.js"], + run_at: "document_idle", + }, + ], + + permissions: ["storage"], + }, + + files: { + "content_script.js": `(${contentScript})(${check_storage_area_no_bytes_in_use})`, + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + await extension.awaitMessage("ready"); + + extension.sendMessage(`test-${area}`); + await extension.awaitMessage("test-complete"); + + await extension.unload(); + await contentPage.close(); +} + +// Test for storage areas which do support getBytesInUse() (but which may or may +// not support enforcement of the quota) +async function check_storage_area_with_bytes_in_use(area, expectQuota) { + let impl = browser.storage[area]; + + // QUOTA_* constants aren't currently exposed - see bug 1396810. + // However, the quotas are still enforced, so test them here. + // (Note that an implication of this is that we can't test area other than + // 'sync', because its limits are different - so for completeness...) + browser.test.assertEq( + area, + "sync", + "Running test on storage.sync API as expected" + ); + const QUOTA_BYTES_PER_ITEM = 8192; + const MAX_ITEMS = 512; + + // bytes is counted as "length of key as a string, length of value as + // JSON" - ie, quotes not counted in the key, but are in the value. + let value = "x".repeat(QUOTA_BYTES_PER_ITEM - 3); + + await impl.set({ x: value }); // Shouldn't reject on either kinto or rust-based storage.sync. + browser.test.assertEq(await impl.getBytesInUse(null), QUOTA_BYTES_PER_ITEM); + // kinto does implement getBytesInUse() but doesn't enforce a quota. + if (expectQuota) { + await browser.test.assertRejects( + impl.set({ x: value + "x" }), + /QuotaExceededError/, + "Got a rejection with the expected error message" + ); + // MAX_ITEMS + await impl.clear(); + let ob = {}; + for (let i = 0; i < MAX_ITEMS; i++) { + ob[`key-${i}`] = "x"; + } + await impl.set(ob); // should work. + await browser.test.assertRejects( + impl.set({ straw: "camel's back" }), // exceeds MAX_ITEMS + /QuotaExceededError/, + "Got a rejection with the expected error message" + ); + // QUOTA_BYTES is being already tested for the underlying StorageSyncService + // so we don't duplicate those tests here. + } else { + // Exceeding quota should work on the previous kinto-based storage.sync implementation + await impl.set({ x: value + "x" }); // exceeds quota but should work. + browser.test.assertEq( + await impl.getBytesInUse(null), + QUOTA_BYTES_PER_ITEM + 1, + "Got the expected result from getBytesInUse" + ); + } + browser.test.sendMessage("test-complete"); +} + +async function test_background_storage_area_with_bytes_in_use( + area, + expectQuota +) { + const EXT_ID = "test-gbiu@mozilla.org"; + + const extensionDef = { + manifest: { + permissions: ["storage"], + applications: { gecko: { id: EXT_ID } }, + }, + background: `(${check_storage_area_with_bytes_in_use})("${area}", ${expectQuota})`, + }; + + const extension = ExtensionTestUtils.loadExtension(extensionDef); + + await extension.startup(); + await extension.awaitMessage("test-complete"); + await extension.unload(); +} + +async function test_contentscript_storage_area_with_bytes_in_use( + area, + expectQuota +) { + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/data/file_sample.html" + ); + + function contentScript(checkImpl) { + browser.test.onMessage.addListener(([area, expectQuota]) => { + if ( + !["local", "sync"].includes(area) || + typeof expectQuota !== "boolean" + ) { + browser.test.fail(`Unexpected test message: [${area}, ${expectQuota}]`); + // Let the test to fail immediately instead of wait for a timeout failure. + browser.test.sendMessage("test-complete"); + return; + } + checkImpl(area, expectQuota); + }); + browser.test.sendMessage("ready"); + } + + let extensionData = { + manifest: { + content_scripts: [ + { + matches: ["http://example.com/data/file_sample.html"], + js: ["content_script.js"], + run_at: "document_idle", + }, + ], + + permissions: ["storage"], + }, + + files: { + "content_script.js": `(${contentScript})(${check_storage_area_with_bytes_in_use})`, + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + await extension.awaitMessage("ready"); + + extension.sendMessage([area, expectQuota]); + await extension.awaitMessage("test-complete"); + + await extension.unload(); + await contentPage.close(); +} + +// A couple of common tests for checking content scripts. +async function testStorageContentScript(checkGet) { + let globalChanges, gResolve; + function clearGlobalChanges() { + globalChanges = new Promise(resolve => { + gResolve = resolve; + }); + } + clearGlobalChanges(); + let expectedAreaName; + + browser.storage.onChanged.addListener((changes, areaName) => { + browser.test.assertEq( + expectedAreaName, + areaName, + "Expected area name received by listener" + ); + gResolve(changes); + }); + + async function checkChanges(areaName, changes, message) { + function checkSub(obj1, obj2) { + for (let prop in obj1) { + browser.test.assertTrue( + obj1[prop] !== undefined, + `checkChanges ${areaName} ${prop} is missing (${message})` + ); + browser.test.assertTrue( + obj2[prop] !== undefined, + `checkChanges ${areaName} ${prop} is missing (${message})` + ); + browser.test.assertEq( + obj1[prop].oldValue, + obj2[prop].oldValue, + `checkChanges ${areaName} ${prop} old (${message})` + ); + browser.test.assertEq( + obj1[prop].newValue, + obj2[prop].newValue, + `checkChanges ${areaName} ${prop} new (${message})` + ); + } + } + + const recentChanges = await globalChanges; + checkSub(changes, recentChanges); + checkSub(recentChanges, changes); + clearGlobalChanges(); + } + + /* eslint-disable dot-notation */ + async function runTests(areaName) { + expectedAreaName = areaName; + let storage = browser.storage[areaName]; + // Set some data and then test getters. + try { + await storage.set({ "test-prop1": "value1", "test-prop2": "value2" }); + await checkChanges( + areaName, + { + "test-prop1": { newValue: "value1" }, + "test-prop2": { newValue: "value2" }, + }, + "set (a)" + ); + + await checkGet(areaName, "test-prop1", "value1"); + await checkGet(areaName, "test-prop2", "value2"); + + let data = await storage.get({ + "test-prop1": undefined, + "test-prop2": undefined, + other: "default", + }); + browser.test.assertEq("value1", data["test-prop1"], "prop1 correct (a)"); + browser.test.assertEq("value2", data["test-prop2"], "prop2 correct (a)"); + browser.test.assertEq("default", data["other"], "other correct"); + + data = await storage.get(["test-prop1", "test-prop2", "other"]); + browser.test.assertEq("value1", data["test-prop1"], "prop1 correct (b)"); + browser.test.assertEq("value2", data["test-prop2"], "prop2 correct (b)"); + browser.test.assertFalse("other" in data, "other correct"); + + // Remove data in various ways. + await storage.remove("test-prop1"); + await checkChanges( + areaName, + { "test-prop1": { oldValue: "value1" } }, + "remove string" + ); + + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertFalse( + "test-prop1" in data, + "prop1 absent (remove string)" + ); + browser.test.assertTrue( + "test-prop2" in data, + "prop2 present (remove string)" + ); + + await storage.set({ "test-prop1": "value1" }); + await checkChanges( + areaName, + { "test-prop1": { newValue: "value1" } }, + "set (c)" + ); + + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertEq(data["test-prop1"], "value1", "prop1 correct (c)"); + browser.test.assertEq(data["test-prop2"], "value2", "prop2 correct (c)"); + + await storage.remove(["test-prop1", "test-prop2"]); + await checkChanges( + areaName, + { + "test-prop1": { oldValue: "value1" }, + "test-prop2": { oldValue: "value2" }, + }, + "remove array" + ); + + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertFalse( + "test-prop1" in data, + "prop1 absent (remove array)" + ); + browser.test.assertFalse( + "test-prop2" in data, + "prop2 absent (remove array)" + ); + + // test storage.clear + await storage.set({ "test-prop1": "value1", "test-prop2": "value2" }); + // Make sure that set() handler happened before we clear the + // promise again. + await globalChanges; + + clearGlobalChanges(); + await storage.clear(); + + await checkChanges( + areaName, + { + "test-prop1": { oldValue: "value1" }, + "test-prop2": { oldValue: "value2" }, + }, + "clear" + ); + data = await storage.get(["test-prop1", "test-prop2"]); + browser.test.assertFalse("test-prop1" in data, "prop1 absent (clear)"); + browser.test.assertFalse("test-prop2" in data, "prop2 absent (clear)"); + + // Make sure we can store complex JSON data. + // known previous values + await storage.set({ "test-prop1": "value1", "test-prop2": "value2" }); + + // Make sure the set() handler landed. + await globalChanges; + + let date = new Date(0); + + clearGlobalChanges(); + await storage.set({ + "test-prop1": { + str: "hello", + bool: true, + null: null, + undef: undefined, + obj: {}, + arr: [1, 2], + date: new Date(0), + regexp: /regexp/, + }, + }); + + await browser.test.assertRejects( + storage.set({ + window, + }), + /DataCloneError|cyclic object value/ + ); + + await browser.test.assertRejects( + storage.set({ "test-prop2": function func() {} }), + /DataCloneError/ + ); + + const recentChanges = await globalChanges; + + browser.test.assertEq( + "value1", + recentChanges["test-prop1"].oldValue, + "oldValue correct" + ); + browser.test.assertEq( + "object", + typeof recentChanges["test-prop1"].newValue, + "newValue is obj" + ); + clearGlobalChanges(); + + data = await storage.get({ + "test-prop1": undefined, + "test-prop2": undefined, + }); + let obj = data["test-prop1"]; + + if (areaName === "local") { + browser.test.assertEq( + String(date), + String(obj.date), + "date part correct" + ); + browser.test.assertEq( + "/regexp/", + obj.regexp.toString(), + "regexp part correct" + ); + } else { + browser.test.assertEq( + "1970-01-01T00:00:00.000Z", + String(obj.date), + "date part correct" + ); + + browser.test.assertEq( + "object", + typeof obj.regexp, + "regexp part is an object" + ); + browser.test.assertEq( + 0, + Object.keys(obj.regexp).length, + "regexp part is an empty object" + ); + } + + browser.test.assertEq("hello", obj.str, "string part correct"); + browser.test.assertEq(true, obj.bool, "bool part correct"); + browser.test.assertEq(null, obj.null, "null part correct"); + browser.test.assertEq(undefined, obj.undef, "undefined part correct"); + browser.test.assertEq(undefined, obj.window, "window part correct"); + browser.test.assertEq("object", typeof obj.obj, "object part correct"); + browser.test.assertTrue(Array.isArray(obj.arr), "array part present"); + browser.test.assertEq(1, obj.arr[0], "arr[0] part correct"); + browser.test.assertEq(2, obj.arr[1], "arr[1] part correct"); + browser.test.assertEq(2, obj.arr.length, "arr.length part correct"); + } catch (e) { + browser.test.fail(`Error: ${e} :: ${e.stack}`); + browser.test.notifyFail("storage"); + } + } + + browser.test.onMessage.addListener(msg => { + let promise; + if (msg === "test-local") { + promise = runTests("local"); + } else if (msg === "test-sync") { + promise = runTests("sync"); + } + promise.then(() => browser.test.sendMessage("test-finished")); + }); + + browser.test.sendMessage("ready"); +} + +async function test_contentscript_storage(storageType) { + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/data/file_sample.html" + ); + + let extensionData = { + manifest: { + content_scripts: [ + { + matches: ["http://example.com/data/file_sample.html"], + js: ["content_script.js"], + run_at: "document_idle", + }, + ], + + permissions: ["storage"], + }, + + files: { + "content_script.js": `(${testStorageContentScript})(${checkGetImpl})`, + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + await extension.awaitMessage("ready"); + + extension.sendMessage(`test-${storageType}`); + await extension.awaitMessage("test-finished"); + + await extension.unload(); + await contentPage.close(); +} diff --git a/toolkit/components/extensions/test/xpcshell/head_sync.js b/toolkit/components/extensions/test/xpcshell/head_sync.js new file mode 100644 index 0000000000..691743c696 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/head_sync.js @@ -0,0 +1,65 @@ +/* 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"; + +/* exported withSyncContext */ + +ChromeUtils.import("resource://gre/modules/Services.jsm", this); +ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm", this); + +class KintoExtContext extends ExtensionCommon.BaseContext { + constructor(principal) { + super(); + Object.defineProperty(this, "principal", { + value: principal, + configurable: true, + }); + this.sandbox = Cu.Sandbox(principal, { wantXrays: false }); + this.extension = { id: "test@web.extension" }; + } + + get cloneScope() { + return this.sandbox; + } +} + +/** + * Call the given function with a newly-constructed context. + * Unload the context on the way out. + * + * @param {function} f the function to call + */ +async function withContext(f) { + const ssm = Services.scriptSecurityManager; + const PRINCIPAL1 = ssm.createContentPrincipalFromOrigin( + "http://www.example.org" + ); + const context = new KintoExtContext(PRINCIPAL1); + try { + await f(context); + } finally { + await context.unload(); + } +} + +/** + * Like withContext(), but also turn on the "storage.sync" pref for + * the duration of the function. + * Calls to this function can be replaced with calls to withContext + * once the pref becomes on by default. + * + * @param {function} f the function to call + */ +async function withSyncContext(f) { + const STORAGE_SYNC_PREF = "webextensions.storage.sync.enabled"; + let prefs = Services.prefs; + + try { + prefs.setBoolPref(STORAGE_SYNC_PREF, true); + await withContext(f); + } finally { + prefs.clearUserPref(STORAGE_SYNC_PREF); + } +} diff --git a/toolkit/components/extensions/test/xpcshell/head_telemetry.js b/toolkit/components/extensions/test/xpcshell/head_telemetry.js new file mode 100644 index 0000000000..6492d8f995 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/head_telemetry.js @@ -0,0 +1,110 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* exported IS_OOP, valueSum, clearHistograms, getSnapshots, promiseTelemetryRecorded */ + +ChromeUtils.defineModuleGetter( + this, + "ContentTaskUtils", + "resource://testing-common/ContentTaskUtils.jsm" +); + +const IS_OOP = Services.prefs.getBoolPref("extensions.webextensions.remote"); + +function valueSum(arr) { + return Object.values(arr).reduce((a, b) => a + b, 0); +} + +function clearHistograms() { + Services.telemetry.getSnapshotForHistograms("main", true /* clear */); + Services.telemetry.getSnapshotForKeyedHistograms("main", true /* clear */); +} + +function getSnapshots(process) { + return Services.telemetry.getSnapshotForHistograms("main", false /* clear */)[ + process + ]; +} + +function getKeyedSnapshots(process) { + return Services.telemetry.getSnapshotForKeyedHistograms( + "main", + false /* clear */ + )[process]; +} + +// TODO Bug 1357509: There is no good way to make sure that the parent received +// the histogram entries from the extension and content processes. Let's stick +// to the ugly, spinning the event loop until we have a good approach. +function promiseTelemetryRecorded(id, process, expectedCount) { + let condition = () => { + let snapshot = Services.telemetry.getSnapshotForHistograms( + "main", + false /* clear */ + )[process][id]; + return snapshot && valueSum(snapshot.values) >= expectedCount; + }; + return ContentTaskUtils.waitForCondition(condition); +} + +function promiseKeyedTelemetryRecorded( + id, + process, + expectedKey, + expectedCount +) { + let condition = () => { + let snapshot = Services.telemetry.getSnapshotForKeyedHistograms( + "main", + false /* clear */ + )[process][id]; + return ( + snapshot && + snapshot[expectedKey] && + valueSum(snapshot[expectedKey].values) >= expectedCount + ); + }; + return ContentTaskUtils.waitForCondition(condition); +} + +function assertHistogramSnapshot( + histogramId, + { keyed, processSnapshot, expectedValue }, + msg +) { + let histogram; + + if (keyed) { + histogram = Services.telemetry.getKeyedHistogramById(histogramId); + } else { + histogram = Services.telemetry.getHistogramById(histogramId); + } + + let res = processSnapshot(histogram.snapshot()); + Assert.deepEqual(res, expectedValue, msg); + return res; +} + +function assertHistogramEmpty(histogramId) { + assertHistogramSnapshot( + histogramId, + { + processSnapshot: snapshot => snapshot.sum, + expectedValue: 0, + }, + `No data recorded for histogram: ${histogramId}.` + ); +} + +function assertKeyedHistogramEmpty(histogramId) { + assertHistogramSnapshot( + histogramId, + { + keyed: true, + processSnapshot: snapshot => Object.keys(snapshot).length, + expectedValue: 0, + }, + `No data recorded for histogram: ${histogramId}.` + ); +} diff --git a/toolkit/components/extensions/test/xpcshell/native_messaging.ini b/toolkit/components/extensions/test/xpcshell/native_messaging.ini new file mode 100644 index 0000000000..b64cda83c8 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/native_messaging.ini @@ -0,0 +1,15 @@ +[DEFAULT] +head = head.js head_e10s.js head_native_messaging.js +tail = +firefox-appdir = browser +skip-if = appname == "thunderbird" || os == "android" +subprocess = true +support-files = + data/** +tags = webextensions + +[test_ext_native_messaging.js] +skip-if = (os == "win" && processor == "aarch64") # bug 1530841 +[test_ext_native_messaging_perf.js] +skip-if = tsan # Unreasonably slow, bug 1612707 +[test_ext_native_messaging_unresponsive.js] diff --git a/toolkit/components/extensions/test/xpcshell/test_ExtensionStorageSync_migration_kinto.js b/toolkit/components/extensions/test/xpcshell/test_ExtensionStorageSync_migration_kinto.js new file mode 100644 index 0000000000..ad763cb321 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ExtensionStorageSync_migration_kinto.js @@ -0,0 +1,86 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +// Import the rust-based and kinto-based implementations +const { extensionStorageSync: rustImpl } = ChromeUtils.import( + "resource://gre/modules/ExtensionStorageSync.jsm" +); +const { extensionStorageSync: kintoImpl } = ChromeUtils.import( + "resource://gre/modules/ExtensionStorageSyncKinto.jsm" +); + +Services.prefs.setBoolPref("webextensions.storage.sync.kinto", false); + +add_task(async function test_sync_migration() { + // There's no good reason to perform this test via test extensions - we just + // call the underlying APIs directly. + + // Set some stuff using the kinto-based impl. + let e1 = { id: "test@mozilla.com" }; + let c1 = { extension: e1, callOnClose() {} }; + await kintoImpl.set(e1, { foo: "bar" }, c1); + + let e2 = { id: "test-2@mozilla.com" }; + let c2 = { extension: e2, callOnClose() {} }; + await kintoImpl.set(e2, { second: "2nd" }, c2); + + let e3 = { id: "test-3@mozilla.com" }; + let c3 = { extension: e3, callOnClose() {} }; + + // And all the data should be magically migrated. + Assert.deepEqual(await rustImpl.get(e1, "foo", c1), { foo: "bar" }); + Assert.deepEqual(await rustImpl.get(e2, null, c2), { second: "2nd" }); + + // Sanity check we really are doing what we think we are - set a value in our + // new one, it should not be reflected by kinto. + await rustImpl.set(e3, { third: "3rd" }, c3); + Assert.deepEqual(await rustImpl.get(e3, null, c3), { third: "3rd" }); + Assert.deepEqual(await kintoImpl.get(e3, null, c3), {}); + // cleanup. + await kintoImpl.clear(e1, c1); + await kintoImpl.clear(e2, c2); + await kintoImpl.clear(e3, c3); + await rustImpl.clear(e1, c1); + await rustImpl.clear(e2, c2); + await rustImpl.clear(e3, c3); +}); + +// It would be great to have failure tests, but that seems impossible to have +// in automated tests given the conditions under which we migrate - it would +// basically require us to arrange for zero free disk space or to somehow +// arrange for sqlite to see an io error. Specially crafted "corrupt" +// sqlite files doesn't help because that file must not exist for us to even +// attempt migration. +// +// But - what we can test is that if .migratedOk on the new impl ever goes to +// false we delegate correctly. +add_task(async function test_sync_migration_delgates() { + let e1 = { id: "test@mozilla.com" }; + let c1 = { extension: e1, callOnClose() {} }; + await kintoImpl.set(e1, { foo: "bar" }, c1); + + // We think migration went OK - `get` shouldn't see kinto. + Assert.deepEqual(rustImpl.get(e1, null, c1), {}); + + info( + "Setting migration failure flag to ensure we delegate to kinto implementation" + ); + rustImpl.migrationOk = false; + // get should now be seeing kinto. + Assert.deepEqual(await rustImpl.get(e1, null, c1), { foo: "bar" }); + // check everything else delegates. + + await rustImpl.set(e1, { foo: "foo" }, c1); + Assert.deepEqual(await kintoImpl.get(e1, null, c1), { foo: "foo" }); + + Assert.equal(await rustImpl.getBytesInUse(e1, null, c1), 8); + + await rustImpl.remove(e1, "foo", c1); + Assert.deepEqual(await kintoImpl.get(e1, null, c1), {}); + + await rustImpl.set(e1, { foo: "foo" }, c1); + Assert.deepEqual(await kintoImpl.get(e1, null, c1), { foo: "foo" }); + await rustImpl.clear(e1, c1); + Assert.deepEqual(await kintoImpl.get(e1, null, c1), {}); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_MatchPattern.js b/toolkit/components/extensions/test/xpcshell/test_MatchPattern.js new file mode 100644 index 0000000000..c0aa4254ad --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_MatchPattern.js @@ -0,0 +1,552 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(async function test_MatchPattern_matches() { + function test(url, pattern, normalized = pattern, options = {}, explicit) { + let uri = Services.io.newURI(url); + + pattern = Array.prototype.concat.call(pattern); + normalized = Array.prototype.concat.call(normalized); + + let patterns = pattern.map(pat => new MatchPattern(pat, options)); + + let set = new MatchPatternSet(pattern, options); + let set2 = new MatchPatternSet(patterns, options); + + deepEqual( + set2.patterns, + patterns, + "Patterns in set should equal the input patterns" + ); + + equal( + set.matches(uri, explicit), + set2.matches(uri, explicit), + "Single pattern and pattern set should return the same match" + ); + + for (let [i, pat] of patterns.entries()) { + equal( + pat.pattern, + normalized[i], + "Pattern property should contain correct normalized pattern value" + ); + } + + if (patterns.length == 1) { + equal( + patterns[0].matches(uri, explicit), + set.matches(uri, explicit), + "Single pattern and string set should return the same match" + ); + } + + return set.matches(uri, explicit); + } + + function pass({ url, pattern, normalized, options, explicit }) { + ok( + test(url, pattern, normalized, options, explicit), + `Expected match: ${JSON.stringify(pattern)}, ${url}` + ); + } + + function fail({ url, pattern, normalized, options, explicit }) { + ok( + !test(url, pattern, normalized, options, explicit), + `Expected no match: ${JSON.stringify(pattern)}, ${url}` + ); + } + + function invalid({ pattern }) { + Assert.throws( + () => new MatchPattern(pattern), + /.*/, + `Invalid pattern '${pattern}' should throw` + ); + Assert.throws( + () => new MatchPatternSet([pattern]), + /.*/, + `Invalid pattern '${pattern}' should throw` + ); + } + + // Invalid pattern. + invalid({ pattern: "" }); + + // Pattern must include trailing slash. + invalid({ pattern: "http://mozilla.org" }); + + // Protocol not allowed. + invalid({ pattern: "gopher://wuarchive.wustl.edu/" }); + + pass({ url: "http://mozilla.org", pattern: "http://mozilla.org/" }); + pass({ url: "http://mozilla.org/", pattern: "http://mozilla.org/" }); + + pass({ url: "http://mozilla.org/", pattern: "*://mozilla.org/" }); + pass({ url: "https://mozilla.org/", pattern: "*://mozilla.org/" }); + fail({ url: "file://mozilla.org/", pattern: "*://mozilla.org/" }); + fail({ url: "ftp://mozilla.org/", pattern: "*://mozilla.org/" }); + + fail({ url: "http://mozilla.com", pattern: "http://*mozilla.com*/" }); + fail({ url: "http://mozilla.com", pattern: "http://mozilla.*/" }); + invalid({ pattern: "http:/mozilla.com/" }); + + pass({ url: "http://google.com", pattern: "http://*.google.com/" }); + pass({ url: "http://docs.google.com", pattern: "http://*.google.com/" }); + + pass({ url: "http://mozilla.org:8080", pattern: "http://mozilla.org/" }); + pass({ url: "http://mozilla.org:8080", pattern: "*://mozilla.org/" }); + fail({ url: "http://mozilla.org:8080", pattern: "http://mozilla.org:8080/" }); + + // Now try with * in the path. + pass({ url: "http://mozilla.org", pattern: "http://mozilla.org/*" }); + pass({ url: "http://mozilla.org/", pattern: "http://mozilla.org/*" }); + + pass({ url: "http://mozilla.org/", pattern: "*://mozilla.org/*" }); + pass({ url: "https://mozilla.org/", pattern: "*://mozilla.org/*" }); + fail({ url: "file://mozilla.org/", pattern: "*://mozilla.org/*" }); + fail({ url: "http://mozilla.com", pattern: "http://mozilla.*/*" }); + + pass({ url: "http://google.com", pattern: "http://*.google.com/*" }); + pass({ url: "http://docs.google.com", pattern: "http://*.google.com/*" }); + + // Check path stuff. + fail({ url: "http://mozilla.com/abc/def", pattern: "http://mozilla.com/" }); + pass({ url: "http://mozilla.com/abc/def", pattern: "http://mozilla.com/*" }); + pass({ + url: "http://mozilla.com/abc/def", + pattern: "http://mozilla.com/a*f", + }); + pass({ url: "http://mozilla.com/abc/def", pattern: "http://mozilla.com/a*" }); + pass({ url: "http://mozilla.com/abc/def", pattern: "http://mozilla.com/*f" }); + fail({ url: "http://mozilla.com/abc/def", pattern: "http://mozilla.com/*e" }); + fail({ url: "http://mozilla.com/abc/def", pattern: "http://mozilla.com/*c" }); + + invalid({ pattern: "http:///a.html" }); + pass({ url: "file:///foo", pattern: "file:///foo*" }); + pass({ url: "file:///foo/bar.html", pattern: "file:///foo*" }); + + pass({ url: "http://mozilla.org/a", pattern: "" }); + pass({ url: "https://mozilla.org/a", pattern: "" }); + pass({ url: "ftp://mozilla.org/a", pattern: "" }); + pass({ url: "file:///a", pattern: "" }); + fail({ url: "gopher://wuarchive.wustl.edu/a", pattern: "" }); + + // Multiple patterns. + pass({ url: "http://mozilla.org", pattern: ["http://mozilla.org/"] }); + pass({ + url: "http://mozilla.org", + pattern: ["http://mozilla.org/", "http://mozilla.com/"], + }); + pass({ + url: "http://mozilla.com", + pattern: ["http://mozilla.org/", "http://mozilla.com/"], + }); + fail({ + url: "http://mozilla.biz", + pattern: ["http://mozilla.org/", "http://mozilla.com/"], + }); + + // Match url with fragments. + pass({ + url: "http://mozilla.org/base#some-fragment", + pattern: "http://mozilla.org/base", + }); + + // Match data:-URLs. + pass({ url: "data:text/plain,foo", pattern: ["data:text/plain,foo"] }); + pass({ url: "data:text/plain,foo", pattern: ["data:text/plain,*"] }); + pass({ + url: "data:text/plain;charset=utf-8,foo", + pattern: ["data:text/plain;charset=utf-8,foo"], + }); + fail({ + url: "data:text/plain,foo", + pattern: ["data:text/plain;charset=utf-8,foo"], + }); + fail({ + url: "data:text/plain;charset=utf-8,foo", + pattern: ["data:text/plain,foo"], + }); + + // Privileged matchers: + invalid({ pattern: "about:foo" }); + invalid({ pattern: "resource://foo/*" }); + + pass({ + url: "about:foo", + pattern: ["about:foo", "about:foo*"], + options: { restrictSchemes: false }, + }); + pass({ + url: "about:foo", + pattern: ["about:foo*"], + options: { restrictSchemes: false }, + }); + pass({ + url: "about:foobar", + pattern: ["about:foo*"], + options: { restrictSchemes: false }, + }); + + pass({ + url: "resource://foo/bar", + pattern: ["resource://foo/bar"], + options: { restrictSchemes: false }, + }); + fail({ + url: "resource://fog/bar", + pattern: ["resource://foo/bar"], + options: { restrictSchemes: false }, + }); + fail({ + url: "about:foo", + pattern: ["about:meh"], + options: { restrictSchemes: false }, + }); + + // Matchers for schemes without host should ignore ignorePath. + pass({ + url: "about:reader?http://e.com/", + pattern: ["about:reader*"], + options: { ignorePath: true, restrictSchemes: false }, + }); + pass({ url: "data:,", pattern: ["data:,*"], options: { ignorePath: true } }); + + // Matchers for schems without host should still match even if the explicit (host) flag is set. + pass({ + url: "about:reader?explicit", + pattern: ["about:reader*"], + options: { restrictSchemes: false }, + explicit: true, + }); + pass({ + url: "about:reader?explicit", + pattern: ["about:reader?explicit"], + options: { restrictSchemes: false }, + explicit: true, + }); + pass({ url: "data:,explicit", pattern: ["data:,explicit"], explicit: true }); + pass({ url: "data:,explicit", pattern: ["data:,*"], explicit: true }); + + // Matchers without "//" separator in the pattern. + pass({ url: "data:text/plain;charset=utf-8,foo", pattern: ["data:*"] }); + pass({ + url: "about:blank", + pattern: ["about:*"], + options: { restrictSchemes: false }, + }); + pass({ + url: "view-source:https://example.com", + pattern: ["view-source:*"], + options: { restrictSchemes: false }, + }); + invalid({ pattern: ["chrome:*"], options: { restrictSchemes: false } }); + invalid({ pattern: "http:*" }); + + // Matchers for unrecognized schemes. + invalid({ pattern: "unknown-scheme:*" }); + pass({ + url: "unknown-scheme:foo", + pattern: ["unknown-scheme:foo"], + options: { restrictSchemes: false }, + }); + pass({ + url: "unknown-scheme:foo", + pattern: ["unknown-scheme:*"], + options: { restrictSchemes: false }, + }); + pass({ + url: "unknown-scheme://foo", + pattern: ["unknown-scheme://foo"], + options: { restrictSchemes: false }, + }); + pass({ + url: "unknown-scheme://foo", + pattern: ["unknown-scheme://*"], + options: { restrictSchemes: false }, + }); + pass({ + url: "unknown-scheme://foo", + pattern: ["unknown-scheme:*"], + options: { restrictSchemes: false }, + }); + fail({ + url: "unknown-scheme://foo", + pattern: ["unknown-scheme:foo"], + options: { restrictSchemes: false }, + }); + fail({ + url: "unknown-scheme:foo", + pattern: ["unknown-scheme://foo"], + options: { restrictSchemes: false }, + }); + fail({ + url: "unknown-scheme:foo", + pattern: ["unknown-scheme://*"], + options: { restrictSchemes: false }, + }); + + // Matchers for IPv6 + pass({ url: "http://[::1]/", pattern: ["http://[::1]/"] }); + pass({ + url: "http://[2a03:4000:6:310e:216:3eff:fe53:99b]/", + pattern: ["http://[2a03:4000:6:310e:216:3eff:fe53:99b]/"], + }); + fail({ + url: "http://[2:4:6:3:2:3:f:b]/", + pattern: ["http://[2a03:4000:6:310e:216:3eff:fe53:99b]/"], + }); + + // Before fixing Bug 1529230, the only way to match a specific IPv6 url is by droping the brackets in pattern, + // thus we keep this pattern valid for the sake of backward compatibility + pass({ url: "http://[::1]/", pattern: ["http://::1/"] }); + pass({ + url: "http://[2a03:4000:6:310e:216:3eff:fe53:99b]/", + pattern: ["http://2a03:4000:6:310e:216:3eff:fe53:99b/"], + }); +}); + +add_task(async function test_MatchPattern_overlaps() { + function test(filter, hosts, optional) { + filter = Array.prototype.concat.call(filter); + hosts = Array.prototype.concat.call(hosts); + optional = Array.prototype.concat.call(optional); + + const set = new MatchPatternSet([...hosts, ...optional]); + const pat = new MatchPatternSet(filter); + return set.overlapsAll(pat); + } + + function pass({ filter = [], hosts = [], optional = [] }) { + ok( + test(filter, hosts, optional), + `Expected overlap: ${filter}, ${hosts} (${optional})` + ); + } + + function fail({ filter = [], hosts = [], optional = [] }) { + ok( + !test(filter, hosts, optional), + `Expected no overlap: ${filter}, ${hosts} (${optional})` + ); + } + + // Direct comparison. + pass({ hosts: "http://ab.cd/", filter: "http://ab.cd/" }); + fail({ hosts: "http://ab.cd/", filter: "ftp://ab.cd/" }); + + // Wildcard protocol. + pass({ hosts: "*://ab.cd/", filter: "https://ab.cd/" }); + fail({ hosts: "*://ab.cd/", filter: "ftp://ab.cd/" }); + + // Wildcard subdomain. + pass({ hosts: "http://*.ab.cd/", filter: "http://ab.cd/" }); + pass({ hosts: "http://*.ab.cd/", filter: "http://www.ab.cd/" }); + fail({ hosts: "http://*.ab.cd/", filter: "http://ab.cd.ef/" }); + fail({ hosts: "http://*.ab.cd/", filter: "http://www.cd/" }); + + // Wildcard subsumed. + pass({ hosts: "http://*.ab.cd/", filter: "http://*.cd/" }); + fail({ hosts: "http://*.cd/", filter: "http://*.xy/" }); + + // Subdomain vs substring. + fail({ hosts: "http://*.ab.cd/", filter: "http://fake-ab.cd/" }); + fail({ hosts: "http://*.ab.cd/", filter: "http://*.fake-ab.cd/" }); + + // Wildcard domain. + pass({ hosts: "http://*/", filter: "http://ab.cd/" }); + fail({ hosts: "http://*/", filter: "https://ab.cd/" }); + + // Wildcard wildcards. + pass({ hosts: "", filter: "ftp://ab.cd/" }); + fail({ hosts: "" }); + + // Multiple hosts. + pass({ hosts: ["http://ab.cd/"], filter: ["http://ab.cd/"] }); + pass({ hosts: ["http://ab.cd/", "http://ab.xy/"], filter: "http://ab.cd/" }); + pass({ hosts: ["http://ab.cd/", "http://ab.xy/"], filter: "http://ab.xy/" }); + fail({ hosts: ["http://ab.cd/", "http://ab.xy/"], filter: "http://ab.zz/" }); + + // Multiple Multiples. + pass({ + hosts: ["http://*.ab.cd/"], + filter: ["http://ab.cd/", "http://www.ab.cd/"], + }); + pass({ + hosts: ["http://ab.cd/", "http://ab.xy/"], + filter: ["http://ab.cd/", "http://ab.xy/"], + }); + fail({ + hosts: ["http://ab.cd/", "http://ab.xy/"], + filter: ["http://ab.cd/", "http://ab.zz/"], + }); + + // Optional. + pass({ hosts: [], optional: "http://ab.cd/", filter: "http://ab.cd/" }); + pass({ + hosts: "http://ab.cd/", + optional: "http://ab.xy/", + filter: ["http://ab.cd/", "http://ab.xy/"], + }); + fail({ + hosts: "http://ab.cd/", + optional: "https://ab.xy/", + filter: "http://ab.xy/", + }); +}); + +add_task(async function test_MatchGlob() { + function test(url, pattern) { + let m = new MatchGlob(pattern[0]); + return m.matches(Services.io.newURI(url).spec); + } + + function pass({ url, pattern }) { + ok( + test(url, pattern), + `Expected match: ${JSON.stringify(pattern)}, ${url}` + ); + } + + function fail({ url, pattern }) { + ok( + !test(url, pattern), + `Expected no match: ${JSON.stringify(pattern)}, ${url}` + ); + } + + let moz = "http://mozilla.org"; + + pass({ url: moz, pattern: ["*"] }); + pass({ url: moz, pattern: ["http://*"] }); + pass({ url: moz, pattern: ["*mozilla*"] }); + // pass({url: moz, pattern: ["*example*", "*mozilla*"]}); + + pass({ url: moz, pattern: ["*://*"] }); + pass({ url: "https://mozilla.org", pattern: ["*://*"] }); + + // Documentation example + pass({ + url: "http://www.example.com/foo/bar", + pattern: ["http://???.example.com/foo/*"], + }); + pass({ + url: "http://the.example.com/foo/", + pattern: ["http://???.example.com/foo/*"], + }); + fail({ + url: "http://my.example.com/foo/bar", + pattern: ["http://???.example.com/foo/*"], + }); + fail({ + url: "http://example.com/foo/", + pattern: ["http://???.example.com/foo/*"], + }); + fail({ + url: "http://www.example.com/foo", + pattern: ["http://???.example.com/foo/*"], + }); + + // Matches path + let path = moz + "/abc/def"; + pass({ url: path, pattern: ["*def"] }); + pass({ url: path, pattern: ["*c/d*"] }); + pass({ url: path, pattern: ["*org/abc*"] }); + fail({ url: path + "/", pattern: ["*def"] }); + + // Trailing slash + pass({ url: moz, pattern: ["*.org/"] }); + fail({ url: moz, pattern: ["*.org"] }); + + // Wrong TLD + fail({ url: moz, pattern: ["*oz*.com/"] }); + // Case sensitive + fail({ url: moz, pattern: ["*.ORG/"] }); +}); + +add_task(async function test_MatchPattern_subsumes() { + function test(oldPat, newPat) { + let m = new MatchPatternSet(oldPat); + return m.subsumes(new MatchPattern(newPat)); + } + + function pass({ oldPat, newPat }) { + ok(test(oldPat, newPat), `${JSON.stringify(oldPat)} subsumes "${newPat}"`); + } + + function fail({ oldPat, newPat }) { + ok( + !test(oldPat, newPat), + `${JSON.stringify(oldPat)} doesn't subsume "${newPat}"` + ); + } + + pass({ oldPat: [""], newPat: "*://*/*" }); + pass({ oldPat: [""], newPat: "http://*/*" }); + pass({ oldPat: [""], newPat: "http://*.example.com/*" }); + + pass({ oldPat: ["*://*/*"], newPat: "http://*/*" }); + pass({ oldPat: ["*://*/*"], newPat: "wss://*/*" }); + pass({ oldPat: ["*://*/*"], newPat: "http://*.example.com/*" }); + + pass({ oldPat: ["*://*.example.com/*"], newPat: "http://*.example.com/*" }); + pass({ oldPat: ["*://*.example.com/*"], newPat: "*://sub.example.com/*" }); + + pass({ oldPat: ["https://*/*"], newPat: "https://*.example.com/*" }); + pass({ + oldPat: ["http://*.example.com/*"], + newPat: "http://subdomain.example.com/*", + }); + pass({ + oldPat: ["http://*.sub.example.com/*"], + newPat: "http://sub.example.com/*", + }); + pass({ + oldPat: ["http://*.sub.example.com/*"], + newPat: "http://sec.sub.example.com/*", + }); + pass({ + oldPat: ["http://www.example.com/*"], + newPat: "http://www.example.com/path/*", + }); + pass({ + oldPat: ["http://www.example.com/path/*"], + newPat: "http://www.example.com/*", + }); + + fail({ oldPat: ["*://*/*"], newPat: "" }); + fail({ oldPat: ["*://*/*"], newPat: "ftp://*/*" }); + fail({ oldPat: ["*://*/*"], newPat: "file://*/*" }); + + fail({ oldPat: ["http://example.com/*"], newPat: "*://example.com/*" }); + fail({ oldPat: ["http://example.com/*"], newPat: "https://example.com/*" }); + fail({ + oldPat: ["http://example.com/*"], + newPat: "http://otherexample.com/*", + }); + fail({ oldPat: ["http://example.com/*"], newPat: "http://*.example.com/*" }); + fail({ + oldPat: ["http://example.com/*"], + newPat: "http://subdomain.example.com/*", + }); + + fail({ + oldPat: ["http://subdomain.example.com/*"], + newPat: "http://example.com/*", + }); + fail({ + oldPat: ["http://subdomain.example.com/*"], + newPat: "http://*.example.com/*", + }); + fail({ + oldPat: ["http://sub.example.com/*"], + newPat: "http://*.sub.example.com/*", + }); + + fail({ oldPat: ["ws://example.com/*"], newPat: "wss://example.com/*" }); + fail({ oldPat: ["http://example.com/*"], newPat: "ws://example.com/*" }); + fail({ oldPat: ["https://example.com/*"], newPat: "wss://example.com/*" }); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_StorageSyncService.js b/toolkit/components/extensions/test/xpcshell/test_StorageSyncService.js new file mode 100644 index 0000000000..8b627a0ee9 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_StorageSyncService.js @@ -0,0 +1,286 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const NS_ERROR_DOM_QUOTA_EXCEEDED_ERR = 0x80530016; + +XPCOMUtils.defineLazyServiceGetter( + this, + "StorageSyncService", + "@mozilla.org/extensions/storage/sync;1", + "nsIInterfaceRequestor" +); + +function promisify(func, ...params) { + return new Promise((resolve, reject) => { + let changes = []; + func(...params, { + QueryInterface: ChromeUtils.generateQI([ + "mozIExtensionStorageListener", + "mozIExtensionStorageCallback", + "mozIBridgedSyncEngineCallback", + "mozIBridgedSyncEngineApplyCallback", + ]), + onChanged(extId, json) { + changes.push({ extId, changes: JSON.parse(json) }); + }, + handleSuccess(value) { + resolve({ + changes, + value: typeof value == "string" ? JSON.parse(value) : value, + }); + }, + handleError(code, message) { + reject(Components.Exception(message, code)); + }, + }); + }); +} + +add_task(async function setup_storage_sync() { + // So that we can write to the profile directory. + do_get_profile(); +}); + +add_task(async function test_storage_sync_service() { + const service = StorageSyncService.getInterface(Ci.mozIExtensionStorageArea); + { + let { changes, value } = await promisify( + service.set, + "ext-1", + JSON.stringify({ + hi: "hello! 💖", + bye: "adiós", + }) + ); + deepEqual( + changes, + [ + { + extId: "ext-1", + changes: { + hi: { + newValue: "hello! 💖", + }, + bye: { + newValue: "adiós", + }, + }, + }, + ], + "`set` should notify listeners about changes" + ); + ok(!value, "`set` should not return a value"); + } + + { + let { changes, value } = await promisify( + service.get, + "ext-1", + JSON.stringify(["hi"]) + ); + deepEqual(changes, [], "`get` should not notify listeners"); + deepEqual( + value, + { + hi: "hello! 💖", + }, + "`get` with key should return value" + ); + + let { value: allValues } = await promisify(service.get, "ext-1", "null"); + deepEqual( + allValues, + { + hi: "hello! 💖", + bye: "adiós", + }, + "`get` without a key should return all values" + ); + } + + { + await promisify( + service.set, + "ext-2", + JSON.stringify({ + hi: "hola! 👋", + }) + ); + await promisify(service.clear, "ext-1"); + let { value: allValues } = await promisify(service.get, "ext-1", "null"); + deepEqual(allValues, {}, "clear removed ext-1"); + + let { value: allValues2 } = await promisify(service.get, "ext-2", "null"); + deepEqual(allValues2, { hi: "hola! 👋" }, "clear didn't remove ext-2"); + // We need to clear data for ext-2 too, so later tests don't fail due to + // this data. + await promisify(service.clear, "ext-2"); + } +}); + +add_task(async function test_storage_sync_bridged_engine() { + const area = StorageSyncService.getInterface(Ci.mozIExtensionStorageArea); + const engine = StorageSyncService.getInterface(Ci.mozIBridgedSyncEngine); + + info("Add some local items"); + await promisify(area.set, "ext-1", JSON.stringify({ a: "abc" })); + await promisify(area.set, "ext-2", JSON.stringify({ b: "xyz" })); + + info("Start a sync"); + await promisify(engine.syncStarted); + + info("Store some incoming synced items"); + let incomingEnvelopesAsJSON = [ + { + id: "guidAAA", + modified: 0.1, + cleartext: JSON.stringify({ + id: "guidAAA", + extId: "ext-2", + data: JSON.stringify({ + c: 1234, + }), + }), + }, + { + id: "guidBBB", + modified: 0.1, + cleartext: JSON.stringify({ + id: "guidBBB", + extId: "ext-3", + data: JSON.stringify({ + d: "new! ✨", + }), + }), + }, + ].map(e => JSON.stringify(e)); + await promisify(area.storeIncoming, incomingEnvelopesAsJSON); + + info("Merge"); + // Three levels of JSON wrapping: each outgoing envelope, the cleartext in + // each envelope, and the extension storage data in each cleartext. + let { value: outgoingEnvelopesAsJSON } = await promisify(area.apply); + let outgoingEnvelopes = outgoingEnvelopesAsJSON.map(json => JSON.parse(json)); + let parsedCleartexts = outgoingEnvelopes.map(e => JSON.parse(e.cleartext)); + let parsedData = parsedCleartexts.map(c => JSON.parse(c.data)); + + let { changes } = await promisify( + area.QueryInterface(Ci.mozISyncedExtensionStorageArea) + .fetchPendingSyncChanges + ); + deepEqual( + changes, + [ + { + extId: "ext-2", + changes: { + c: { newValue: 1234 }, + }, + }, + { + extId: "ext-3", + changes: { + d: { newValue: "new! ✨" }, + }, + }, + ], + "Should return pending synced changes for observers" + ); + + // ext-1 doesn't exist remotely yet, so the Rust sync layer will generate + // a GUID for it. We don't know what it is, so we find it by the extension + // ID. + let ext1Index = parsedCleartexts.findIndex(c => c.extId == "ext-1"); + greater(ext1Index, -1, "Should find envelope for ext-1"); + let ext1Guid = outgoingEnvelopes[ext1Index].id; + + // ext-2 has a remote GUID that we set in the test above. + let ext2Index = outgoingEnvelopes.findIndex(c => c.id == "guidAAA"); + greater(ext2Index, -1, "Should find envelope for ext-2"); + + equal(outgoingEnvelopes.length, 2, "Should upload ext-1 and ext-2"); + equal( + ext1Guid, + parsedCleartexts[ext1Index].id, + "ext-1 ID in envelope should match cleartext" + ); + deepEqual( + parsedData[ext1Index], + { + a: "abc", + }, + "Should upload new data for ext-1" + ); + equal( + outgoingEnvelopes[ext2Index].id, + parsedCleartexts[ext2Index].id, + "ext-2 ID in envelope should match cleartext" + ); + deepEqual( + parsedData[ext2Index], + { + b: "xyz", + c: 1234, + }, + "Should merge local and remote data for ext-2" + ); + + info("Mark all extensions as uploaded"); + await promisify(engine.setUploaded, 0, [ext1Guid, "guidAAA"]); + + info("Finish sync"); + await promisify(engine.syncFinished); + + // Try fetching values for the remote-only extension we just synced. + let { value: ext3Value } = await promisify(area.get, "ext-3", "null"); + deepEqual( + ext3Value, + { + d: "new! ✨", + }, + "Should return new keys for ext-3" + ); + + info("Try applying a second time"); + let secondApply = await promisify(area.apply); + deepEqual(secondApply.value, {}, "Shouldn't merge anything on second apply"); + + info("Wipe all items"); + await promisify(engine.wipe); + + for (let extId of ["ext-1", "ext-2", "ext-3"]) { + // `get` always returns an object, even if there are no keys for the + // extension ID. + let { value } = await promisify(area.get, extId, "null"); + deepEqual(value, {}, `Wipe should remove all values for ${extId}`); + } +}); + +add_task(async function test_storage_sync_quota() { + const service = StorageSyncService.getInterface(Ci.mozIExtensionStorageArea); + const engine = StorageSyncService.getInterface(Ci.mozIBridgedSyncEngine); + await promisify(engine.wipe); + await promisify(service.set, "ext-1", JSON.stringify({ x: "hi" })); + await promisify(service.set, "ext-1", JSON.stringify({ longer: "value" })); + + let { value: v1 } = await promisify(service.getBytesInUse, "ext-1", '"x"'); + Assert.equal(v1, 5); // key len without quotes, value len with quotes. + let { value: v2 } = await promisify(service.getBytesInUse, "ext-1", "null"); + // 5 from 'x', plus 'longer' (6 for key, 7 for value = 13) = 18. + Assert.equal(v2, 18); + + // Now set something greater than our quota. + await Assert.rejects( + promisify( + service.set, + "ext-1", + JSON.stringify({ + big: "x".repeat(Ci.mozIExtensionStorageArea.SYNC_QUOTA_BYTES), + }) + ), + ex => ex.result == NS_ERROR_DOM_QUOTA_EXCEEDED_ERR, + "should reject with NS_ERROR_DOM_QUOTA_EXCEEDED_ERR" + ); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_WebExtensionContentScript.js b/toolkit/components/extensions/test/xpcshell/test_WebExtensionContentScript.js new file mode 100644 index 0000000000..78d61d4b29 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_WebExtensionContentScript.js @@ -0,0 +1,209 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const { newURI } = Services.io; + +const server = createHttpServer({ hosts: ["example.com"] }); +server.registerDirectory("/data/", do_get_file("data")); + +let policy = new WebExtensionPolicy({ + id: "foo@bar.baz", + mozExtensionHostname: "88fb51cd-159f-4859-83db-7065485bc9b2", + baseURL: "file:///foo", + + allowedOrigins: new MatchPatternSet([]), + localizeCallback() {}, +}); + +add_task(async function test_WebExtensinonContentScript_url_matching() { + let contentScript = new WebExtensionContentScript(policy, { + matches: new MatchPatternSet(["http://foo.com/bar", "*://bar.com/baz/*"]), + + excludeMatches: new MatchPatternSet(["*://bar.com/baz/quux"]), + + includeGlobs: ["*flerg*", "*.com/bar", "*/quux"].map( + glob => new MatchGlob(glob) + ), + + excludeGlobs: ["*glorg*"].map(glob => new MatchGlob(glob)), + }); + + ok( + contentScript.matchesURI(newURI("http://foo.com/bar")), + "Simple matches include should match" + ); + + ok( + contentScript.matchesURI(newURI("https://bar.com/baz/xflergx")), + "Simple matches include should match" + ); + + ok( + !contentScript.matchesURI(newURI("https://bar.com/baz/xx")), + "Failed includeGlobs match pattern should not match" + ); + + ok( + !contentScript.matchesURI(newURI("https://bar.com/baz/quux")), + "Excluded match pattern should not match" + ); + + ok( + !contentScript.matchesURI(newURI("https://bar.com/baz/xflergxglorgx")), + "Excluded match glob should not match" + ); +}); + +async function loadURL(url) { + let requests = new Map(); + + function requestObserver(request) { + request.QueryInterface(Ci.nsIChannel); + if (request.isDocument) { + requests.set(request.name, request); + } + } + + Services.obs.addObserver(requestObserver, "http-on-examine-response"); + + let contentPage = await ExtensionTestUtils.loadContentPage(url); + + Services.obs.removeObserver(requestObserver, "http-on-examine-response"); + + return { contentPage, requests }; +} + +add_task(async function test_WebExtensinonContentScript_frame_matching() { + if (AppConstants.platform == "linux") { + // The windowless browser currently does not load correctly on Linux on + // infra. + return; + } + + let baseURL = `http://example.com/data`; + let urls = { + topLevel: `${baseURL}/file_toplevel.html`, + iframe: `${baseURL}/file_iframe.html`, + srcdoc: "about:srcdoc", + aboutBlank: "about:blank", + }; + + let { contentPage, requests } = await loadURL(urls.topLevel); + + let tests = [ + { + matches: ["http://example.com/data/*"], + contentScript: {}, + topLevel: true, + iframe: false, + aboutBlank: false, + srcdoc: false, + }, + + { + matches: ["http://example.com/data/*"], + contentScript: { + frameID: 0, + }, + topLevel: true, + iframe: false, + aboutBlank: false, + srcdoc: false, + }, + + { + matches: ["http://example.com/data/*"], + contentScript: { + allFrames: true, + }, + topLevel: true, + iframe: true, + aboutBlank: false, + srcdoc: false, + }, + + { + matches: ["http://example.com/data/*"], + contentScript: { + allFrames: true, + matchAboutBlank: true, + }, + topLevel: true, + iframe: true, + aboutBlank: true, + srcdoc: true, + }, + + { + matches: ["http://foo.com/data/*"], + contentScript: { + allFrames: true, + matchAboutBlank: true, + }, + topLevel: false, + iframe: false, + aboutBlank: false, + srcdoc: false, + }, + ]; + + // matchesWindowGlobal tests against content frames + await contentPage.spawn({ tests, urls }, args => { + this.windows = new Map(); + this.windows.set(this.content.location.href, this.content); + for (let c of Array.from(this.content.frames)) { + this.windows.set(c.location.href, c); + } + this.policy = new WebExtensionPolicy({ + id: "foo@bar.baz", + mozExtensionHostname: "88fb51cd-159f-4859-83db-7065485bc9b2", + baseURL: "file:///foo", + + allowedOrigins: new MatchPatternSet([]), + localizeCallback() {}, + }); + + let tests = args.tests.map(t => { + t.contentScript.matches = new MatchPatternSet(t.matches); + t.script = new WebExtensionContentScript(this.policy, t.contentScript); + return t; + }); + for (let [i, test] of tests.entries()) { + for (let [frame, url] of Object.entries(args.urls)) { + let should = test[frame] ? "should" : "should not"; + let wgc = this.windows.get(url).windowGlobalChild; + Assert.equal( + test.script.matchesWindowGlobal(wgc), + test[frame], + `Script ${i} ${should} match the ${frame} frame` + ); + } + } + }); + + // Parent tests against loadInfo + tests = tests.map(t => { + t.contentScript.matches = new MatchPatternSet(t.matches); + t.script = new WebExtensionContentScript(policy, t.contentScript); + return t; + }); + + for (let [i, test] of tests.entries()) { + for (let [frame, url] of Object.entries(urls)) { + let should = test[frame] ? "should" : "should not"; + + if (url.startsWith("http")) { + let request = requests.get(url); + + equal( + test.script.matchesLoadInfo(request.URI, request.loadInfo), + test[frame], + `Script ${i} ${should} match the request LoadInfo for ${frame} frame` + ); + } + } + } + + await contentPage.close(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_WebExtensionPolicy.js b/toolkit/components/extensions/test/xpcshell/test_WebExtensionPolicy.js new file mode 100644 index 0000000000..75c6edd9c4 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_WebExtensionPolicy.js @@ -0,0 +1,376 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const { newURI } = Services.io; + +add_task(async function test_WebExtensionPolicy() { + const id = "foo@bar.baz"; + const uuid = "ca9d3f23-125c-4b24-abfc-1ca2692b0610"; + + const baseURL = "file:///foo/"; + const mozExtURL = `moz-extension://${uuid}/`; + const mozExtURI = newURI(mozExtURL); + + let policy = new WebExtensionPolicy({ + id, + mozExtensionHostname: uuid, + baseURL, + + localizeCallback(str) { + return `<${str}>`; + }, + + allowedOrigins: new MatchPatternSet(["http://foo.bar/", "*://*.baz/"], { + ignorePath: true, + }), + permissions: [""], + webAccessibleResources: ["/foo/*", "/bar.baz"].map( + glob => new MatchGlob(glob) + ), + }); + + equal(policy.active, false, "Active attribute should initially be false"); + + // GetURL + + equal( + policy.getURL(), + mozExtURL, + "getURL() should return the correct root URL" + ); + equal( + policy.getURL("path/foo.html"), + `${mozExtURL}path/foo.html`, + "getURL(path) should return the correct URL" + ); + + // Permissions + + deepEqual( + policy.permissions, + [""], + "Initial permissions should be correct" + ); + + ok( + policy.hasPermission(""), + "hasPermission should match existing permission" + ); + ok( + !policy.hasPermission("history"), + "hasPermission should not match nonexistent permission" + ); + + Assert.throws( + () => { + policy.permissions[0] = "foo"; + }, + TypeError, + "Permissions array should be frozen" + ); + + policy.permissions = ["history"]; + deepEqual( + policy.permissions, + ["history"], + "Permissions should be updateable as a set" + ); + + ok( + policy.hasPermission("history"), + "hasPermission should match existing permission" + ); + ok( + !policy.hasPermission(""), + "hasPermission should not match nonexistent permission" + ); + + // Origins + + ok( + policy.canAccessURI(newURI("http://foo.bar/quux")), + "Should be able to access permitted URI" + ); + ok( + policy.canAccessURI(newURI("https://x.baz/foo")), + "Should be able to access permitted URI" + ); + + ok( + !policy.canAccessURI(newURI("https://foo.bar/quux")), + "Should not be able to access non-permitted URI" + ); + + policy.allowedOrigins = new MatchPatternSet(["https://foo.bar/"], { + ignorePath: true, + }); + + ok( + policy.canAccessURI(newURI("https://foo.bar/quux")), + "Should be able to access updated permitted URI" + ); + ok( + !policy.canAccessURI(newURI("https://x.baz/foo")), + "Should not be able to access removed permitted URI" + ); + + // Web-accessible resources + + ok( + policy.isPathWebAccessible("/foo/bar"), + "Web-accessible glob should be web-accessible" + ); + ok( + policy.isPathWebAccessible("/bar.baz"), + "Web-accessible path should be web-accessible" + ); + ok( + !policy.isPathWebAccessible("/bar.baz/quux"), + "Non-web-accessible path should not be web-accessible" + ); + + // Localization + + equal( + policy.localize("foo"), + "", + "Localization callback should work as expected" + ); + + // Protocol and lookups. + + let proto = Services.io + .getProtocolHandler("moz-extension", uuid) + .QueryInterface(Ci.nsISubstitutingProtocolHandler); + + deepEqual( + WebExtensionPolicy.getActiveExtensions(), + [], + "Should have no active extensions" + ); + equal( + WebExtensionPolicy.getByID(id), + null, + "ID lookup should not return extension when not active" + ); + equal( + WebExtensionPolicy.getByHostname(uuid), + null, + "Hostname lookup should not return extension when not active" + ); + Assert.throws( + () => proto.resolveURI(mozExtURI), + /NS_ERROR_NOT_AVAILABLE/, + "URL should not resolve when not active" + ); + + policy.active = true; + equal(policy.active, true, "Active attribute should be updated"); + + let exts = WebExtensionPolicy.getActiveExtensions(); + equal(exts.length, 1, "Should have one active extension"); + equal(exts[0], policy, "Should have the correct active extension"); + + equal( + WebExtensionPolicy.getByID(id), + policy, + "ID lookup should return extension when active" + ); + equal( + WebExtensionPolicy.getByHostname(uuid), + policy, + "Hostname lookup should return extension when active" + ); + + equal( + proto.resolveURI(mozExtURI), + baseURL, + "URL should resolve correctly while active" + ); + + policy.active = false; + equal(policy.active, false, "Active attribute should be updated"); + + deepEqual( + WebExtensionPolicy.getActiveExtensions(), + [], + "Should have no active extensions" + ); + equal( + WebExtensionPolicy.getByID(id), + null, + "ID lookup should not return extension when not active" + ); + equal( + WebExtensionPolicy.getByHostname(uuid), + null, + "Hostname lookup should not return extension when not active" + ); + Assert.throws( + () => proto.resolveURI(mozExtURI), + /NS_ERROR_NOT_AVAILABLE/, + "URL should not resolve when not active" + ); + + // Conflicting policies. + + // This asserts in debug builds, so only test in non-debug builds. + if (!AppConstants.DEBUG) { + policy.active = true; + + let attrs = [ + { id, uuid }, + { id, uuid: "d916886c-cfdf-482e-b7b1-d7f5b0facfa5" }, + { id: "foo@quux", uuid }, + ]; + + // eslint-disable-next-line no-shadow + for (let { id, uuid } of attrs) { + let policy2 = new WebExtensionPolicy({ + id, + mozExtensionHostname: uuid, + baseURL: "file://bar/", + + localizeCallback() {}, + + allowedOrigins: new MatchPatternSet([]), + }); + + Assert.throws( + () => { + policy2.active = true; + }, + /NS_ERROR_UNEXPECTED/, + `Should not be able to activate conflicting policy: ${id} ${uuid}` + ); + } + + policy.active = false; + } +}); + +add_task(async function test_WebExtensionPolicy_registerContentScripts() { + const id = "foo@bar.baz"; + const uuid = "77a7b9d3-e73c-4cf3-97fb-1824868fe00f"; + + const id2 = "foo-2@bar.baz"; + const uuid2 = "89383c45-7db4-4999-83f7-f4cc246372cd"; + + const baseURL = "file:///foo/"; + + const mozExtURL = `moz-extension://${uuid}/`; + const mozExtURL2 = `moz-extension://${uuid2}/`; + + let policy = new WebExtensionPolicy({ + id, + mozExtensionHostname: uuid, + baseURL, + localizeCallback() {}, + allowedOrigins: new MatchPatternSet([]), + permissions: [""], + }); + + let policy2 = new WebExtensionPolicy({ + id: id2, + mozExtensionHostname: uuid2, + baseURL, + localizeCallback() {}, + allowedOrigins: new MatchPatternSet([]), + permissions: [""], + }); + + let script1 = new WebExtensionContentScript(policy, { + run_at: "document_end", + js: [`${mozExtURL}/registered-content-script.js`], + matches: new MatchPatternSet(["http://localhost/data/*"]), + }); + + let script2 = new WebExtensionContentScript(policy, { + run_at: "document_end", + css: [`${mozExtURL}/registered-content-style.css`], + matches: new MatchPatternSet(["http://localhost/data/*"]), + }); + + let script3 = new WebExtensionContentScript(policy2, { + run_at: "document_end", + css: [`${mozExtURL2}/registered-content-style.css`], + matches: new MatchPatternSet(["http://localhost/data/*"]), + }); + + deepEqual( + policy.contentScripts, + [], + "The policy contentScripts is initially empty" + ); + + policy.registerContentScript(script1); + + deepEqual( + policy.contentScripts, + [script1], + "script1 has been added to the policy contentScripts" + ); + + Assert.throws( + () => policy.registerContentScript(script1), + e => e.result == Cr.NS_ERROR_ILLEGAL_VALUE, + "Got the expected NS_ERROR_ILLEGAL_VALUE when trying to register a script more than once" + ); + + Assert.throws( + () => policy.registerContentScript(script3), + e => e.result == Cr.NS_ERROR_ILLEGAL_VALUE, + "Got the expected NS_ERROR_ILLEGAL_VALUE when trying to register a script related to " + + "a different extension" + ); + + Assert.throws( + () => policy.unregisterContentScript(script3), + e => e.result == Cr.NS_ERROR_ILLEGAL_VALUE, + "Got the expected NS_ERROR_ILLEGAL_VALUE when trying to unregister a script related to " + + "a different extension" + ); + + deepEqual( + policy.contentScripts, + [script1], + "script1 has not been added twice" + ); + + policy.registerContentScript(script2); + + deepEqual( + policy.contentScripts, + [script1, script2], + "script2 has the last item of the policy contentScripts array" + ); + + policy.unregisterContentScript(script1); + + deepEqual( + policy.contentScripts, + [script2], + "script1 has been removed from the policy contentscripts" + ); + + Assert.throws( + () => policy.unregisterContentScript(script1), + e => e.result == Cr.NS_ERROR_ILLEGAL_VALUE, + "Got the expected NS_ERROR_ILLEGAL_VALUE when trying to unregister a script more than once" + ); + + deepEqual( + policy.contentScripts, + [script2], + "the policy contentscripts is unmodified when unregistering an unknown contentScript" + ); + + policy.unregisterContentScript(script2); + + deepEqual( + policy.contentScripts, + [], + "script2 has been removed from the policy contentScripts" + ); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_change_remote_mode.js b/toolkit/components/extensions/test/xpcshell/test_change_remote_mode.js new file mode 100644 index 0000000000..a6d22e8703 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_change_remote_mode.js @@ -0,0 +1,20 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(async function change_remote() { + let remote = Services.prefs.getBoolPref("extensions.webextensions.remote"); + Assert.equal( + WebExtensionPolicy.useRemoteWebExtensions, + remote, + "value of useRemoteWebExtensions matches the pref" + ); + + Services.prefs.setBoolPref("extensions.webextensions.remote", !remote); + + Assert.equal( + WebExtensionPolicy.useRemoteWebExtensions, + remote, + "value of useRemoteWebExtensions is still the same after changing the pref" + ); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_csp_custom_policies.js b/toolkit/components/extensions/test/xpcshell/test_csp_custom_policies.js new file mode 100644 index 0000000000..0b24cc4c50 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_csp_custom_policies.js @@ -0,0 +1,278 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const { Preferences } = ChromeUtils.import( + "resource://gre/modules/Preferences.jsm" +); + +const ADDON_ID = "test@web.extension"; + +const aps = Cc["@mozilla.org/addons/policy-service;1"].getService( + Ci.nsIAddonPolicyService +); + +const v2_csp = Preferences.get( + "extensions.webextensions.base-content-security-policy" +); +const v3_csp = Preferences.get( + "extensions.webextensions.base-content-security-policy.v3" +); + +add_task(async function test_invalid_addon_csp() { + await Assert.throws( + () => aps.getBaseCSP("invalid@missing"), + /NS_ERROR_ILLEGAL_VALUE/, + "no base csp for non-existent addon" + ); + await Assert.throws( + () => aps.getExtensionPageCSP("invalid@missing"), + /NS_ERROR_ILLEGAL_VALUE/, + "no extension page csp for non-existent addon" + ); +}); + +add_task(async function test_policy_csp() { + equal( + aps.defaultCSP, + Preferences.get("extensions.webextensions.default-content-security-policy"), + "Expected default CSP value" + ); + + const CUSTOM_POLICY = + "script-src: 'self' https://xpcshell.test.custom.csp; object-src: 'none'"; + + let tests = [ + { + name: "manifest version 2, no custom policy", + policyData: {}, + expectedPolicy: aps.defaultCSP, + }, + { + name: "manifest version 2, no custom policy", + policyData: { + manifestVersion: 2, + }, + expectedPolicy: aps.defaultCSP, + }, + { + name: "version 2 custom extension policy", + policyData: { + extensionPageCSP: CUSTOM_POLICY, + }, + expectedPolicy: CUSTOM_POLICY, + }, + { + name: "manifest version 2 set, custom extension policy", + policyData: { + manifestVersion: 2, + extensionPageCSP: CUSTOM_POLICY, + }, + expectedPolicy: CUSTOM_POLICY, + }, + { + name: "manifest version 3, no custom policy", + policyData: { + manifestVersion: 3, + }, + expectedPolicy: aps.defaultCSP, + }, + { + name: "manifest 3 version set, custom extensionPage policy", + policyData: { + manifestVersion: 3, + extensionPageCSP: CUSTOM_POLICY, + }, + expectedPolicy: CUSTOM_POLICY, + }, + ]; + + let policy = null; + + function setExtensionCSP({ manifestVersion, extensionPageCSP }) { + if (policy) { + policy.active = false; + } + + policy = new WebExtensionPolicy({ + id: ADDON_ID, + mozExtensionHostname: ADDON_ID, + baseURL: "file:///", + + allowedOrigins: new MatchPatternSet([]), + localizeCallback() {}, + + manifestVersion, + extensionPageCSP, + }); + + policy.active = true; + } + + for (let test of tests) { + info(test.name); + setExtensionCSP(test.policyData); + equal( + aps.getBaseCSP(ADDON_ID), + test.policyData.manifestVersion == 3 ? v3_csp : v2_csp, + "baseCSP is correct" + ); + equal( + aps.getExtensionPageCSP(ADDON_ID), + test.expectedPolicy, + "extensionPageCSP is correct" + ); + } +}); + +add_task(async function test_extension_csp() { + Services.prefs.setBoolPref("extensions.manifestV3.enabled", true); + + ExtensionTestUtils.failOnSchemaWarnings(false); + + let extension_pages = "script-src 'self'; object-src 'none'; img-src 'none'"; + + let tests = [ + { + name: "manifest_v2 invalid csp results in default csp used", + manifest: { + content_security_policy: `script-src 'none'`, + }, + expectedPolicy: aps.defaultCSP, + }, + { + name: "manifest_v2 allows https protocol", + manifest: { + manifest_version: 3, + content_security_policy: { + extension_pages: `script-src 'self' https://*; object-src 'self'`, + }, + }, + expectedPolicy: aps.defaultCSP, + }, + { + name: "manifest_v2 allows unsafe-eval", + manifest: { + manifest_version: 3, + content_security_policy: { + extension_pages: `script-src 'self' 'unsafe-eval'; object-src 'self'`, + }, + }, + expectedPolicy: aps.defaultCSP, + }, + { + name: "manifest_v3 invalid csp results in default csp used", + manifest: { + manifest_version: 3, + content_security_policy: { + extension_pages: `script-src 'none'`, + }, + }, + expectedPolicy: aps.defaultCSP, + }, + { + name: "manifest_v3 forbidden protocol results in default csp used", + manifest: { + manifest_version: 3, + content_security_policy: { + extension_pages: `script-src 'self' https://*; object-src 'self'`, + }, + }, + expectedPolicy: aps.defaultCSP, + }, + { + name: "manifest_v3 forbidden eval results in default csp used", + manifest: { + manifest_version: 3, + content_security_policy: { + extension_pages: `script-src 'self' 'unsafe-eval'; object-src 'self'`, + }, + }, + expectedPolicy: aps.defaultCSP, + }, + { + name: "manifest_v3 allows localhost", + manifest: { + manifest_version: 3, + content_security_policy: { + extension_pages: `script-src 'self' https://localhost; object-src 'self'`, + }, + }, + expectedPolicy: `script-src 'self' https://localhost; object-src 'self'`, + }, + { + name: "manifest_v3 allows 127.0.0.1", + manifest: { + manifest_version: 3, + content_security_policy: { + extension_pages: `script-src 'self' https://127.0.0.1; object-src 'self'`, + }, + }, + expectedPolicy: `script-src 'self' https://127.0.0.1; object-src 'self'`, + }, + { + name: "manifest_v2 csp", + manifest: { + manifest_version: 2, + content_security_policy: extension_pages, + }, + expectedPolicy: extension_pages, + }, + { + name: "manifest_v2 with no csp, expect default", + manifest: { + manifest_version: 2, + }, + expectedPolicy: aps.defaultCSP, + }, + { + name: "manifest_v3 used with no csp, expect default", + manifest: { + manifest_version: 3, + }, + expectedPolicy: aps.defaultCSP, + }, + { + name: "manifest_v3 used with v2 syntax", + manifest: { + manifest_version: 3, + content_security_policy: extension_pages, + }, + expectedPolicy: extension_pages, + }, + { + name: "manifest_v3 syntax used", + manifest: { + manifest_version: 3, + content_security_policy: { + extension_pages, + }, + }, + expectedPolicy: extension_pages, + }, + ]; + + for (let test of tests) { + info(test.name); + let extension = ExtensionTestUtils.loadExtension({ + manifest: test.manifest, + }); + await extension.startup(); + let policy = WebExtensionPolicy.getByID(extension.id); + equal( + policy.baseCSP, + test.manifest.manifest_version == 3 ? v3_csp : v2_csp, + "baseCSP is correct" + ); + equal( + policy.extensionPageCSP, + test.expectedPolicy, + "extensionPageCSP is correct." + ); + await extension.unload(); + } + + ExtensionTestUtils.failOnSchemaWarnings(true); + + Services.prefs.clearUserPref("extensions.manifestV3.enabled"); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_csp_validator.js b/toolkit/components/extensions/test/xpcshell/test_csp_validator.js new file mode 100644 index 0000000000..fb494f3da2 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_csp_validator.js @@ -0,0 +1,298 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const cps = Cc["@mozilla.org/addons/content-policy;1"].getService( + Ci.nsIAddonContentPolicy +); + +add_task(async function test_csp_validator_flags() { + let checkPolicy = (policy, flags, expectedResult, message = null) => { + info(`Checking policy: ${policy}`); + + let result = cps.validateAddonCSP(policy, flags); + equal(result, expectedResult); + }; + + let flags = Ci.nsIAddonContentPolicy; + + checkPolicy( + "default-src 'self'; script-src 'self' http://localhost", + 0, + "\u2018script-src\u2019 directive contains a forbidden http: protocol source", + "localhost disallowed" + ); + checkPolicy( + "default-src 'self'; script-src 'self' http://localhost", + flags.CSP_ALLOW_LOCALHOST, + null, + "localhost allowed" + ); + + checkPolicy( + "default-src 'self'; script-src 'self' 'unsafe-eval'", + 0, + "\u2018script-src\u2019 directive contains a forbidden 'unsafe-eval' keyword", + "eval disallowed" + ); + checkPolicy( + "default-src 'self'; script-src 'self' 'unsafe-eval'", + flags.CSP_ALLOW_EVAL, + null, + "eval allowed" + ); + + checkPolicy( + "default-src 'self'; script-src 'self' https://example.com", + 0, + "\u2018script-src\u2019 directive contains a forbidden https: protocol source", + "remote disallowed" + ); + checkPolicy( + "default-src 'self'; script-src 'self' https://example.com", + flags.CSP_ALLOW_REMOTE, + null, + "remote allowed" + ); +}); + +add_task(async function test_csp_validator() { + let checkPolicy = (policy, expectedResult, message = null) => { + info(`Checking policy: ${policy}`); + + let result = cps.validateAddonCSP( + policy, + Ci.nsIAddonContentPolicy.CSP_ALLOW_ANY + ); + equal(result, expectedResult); + }; + + checkPolicy("script-src 'self'; object-src 'self';", null); + + let hash = + "'sha256-NjZhMDQ1YjQ1MjEwMmM1OWQ4NDBlYzA5N2Q1OWQ5NDY3ZTEzYTNmMzRmNjQ5NGU1MzlmZmQzMmMxYmIzNWYxOCAgLQo='"; + + checkPolicy( + `script-src 'self' https://com https://*.example.com moz-extension://09abcdef blob: filesystem: ${hash} 'unsafe-eval'; ` + + `object-src 'self' https://com https://*.example.com moz-extension://09abcdef blob: filesystem: ${hash}`, + null + ); + + checkPolicy( + "", + "Policy is missing a required \u2018script-src\u2019 directive" + ); + + checkPolicy( + "object-src 'none';", + "Policy is missing a required \u2018script-src\u2019 directive" + ); + + checkPolicy( + "default-src 'self'", + null, + "A valid default-src should count as a valid script-src or object-src" + ); + + checkPolicy( + "default-src 'self'; script-src 'self'", + null, + "A valid default-src should count as a valid script-src or object-src" + ); + + checkPolicy( + "default-src 'self'; object-src 'self'", + null, + "A valid default-src should count as a valid script-src or object-src" + ); + + checkPolicy( + "default-src 'self'; script-src http://example.com", + "\u2018script-src\u2019 directive contains a forbidden http: protocol source", + "A valid default-src should not allow an invalid script-src directive" + ); + + checkPolicy( + "default-src 'self'; object-src http://example.com", + "\u2018object-src\u2019 directive contains a forbidden http: protocol source", + "A valid default-src should not allow an invalid object-src directive" + ); + + checkPolicy( + "script-src 'self';", + "Policy is missing a required \u2018object-src\u2019 directive" + ); + + checkPolicy( + "script-src 'none'; object-src 'none'", + "\u2018script-src\u2019 must include the source 'self'" + ); + + checkPolicy("script-src 'self'; object-src 'none';", null); + + checkPolicy( + "script-src 'self' 'unsafe-inline'; object-src 'self';", + "\u2018script-src\u2019 directive contains a forbidden 'unsafe-inline' keyword" + ); + + // Localhost is always valid + for (let src of [ + "http://localhost", + "https://localhost", + "http://127.0.0.1", + "https://127.0.0.1", + ]) { + checkPolicy(`script-src 'self' ${src}; object-src 'none';`, null); + } + + let directives = ["script-src", "object-src"]; + + for (let [directive, other] of [directives, directives.slice().reverse()]) { + for (let src of ["https://*", "https://*.blogspot.com", "https://*"]) { + checkPolicy( + `${directive} 'self' ${src}; ${other} 'self';`, + `https: wildcard sources in \u2018${directive}\u2019 directives must include at least one non-generic sub-domain (e.g., *.example.com rather than *.com)` + ); + } + + for (let protocol of ["http", "https"]) { + checkPolicy( + `${directive} 'self' ${protocol}:; ${other} 'self';`, + `${protocol}: protocol requires a host in \u2018${directive}\u2019 directives` + ); + } + + checkPolicy( + `${directive} 'self' http://example.com; ${other} 'self';`, + `\u2018${directive}\u2019 directive contains a forbidden http: protocol source` + ); + + for (let protocol of ["ftp", "meh"]) { + checkPolicy( + `${directive} 'self' ${protocol}:; ${other} 'self';`, + `\u2018${directive}\u2019 directive contains a forbidden ${protocol}: protocol source` + ); + } + + checkPolicy( + `${directive} 'self' 'nonce-01234'; ${other} 'self';`, + `\u2018${directive}\u2019 directive contains a forbidden 'nonce-*' keyword` + ); + } +}); + +add_task(async function test_csp_validator_extension_pages() { + let checkPolicy = (policy, expectedResult, message = null) => { + info(`Checking policy: ${policy}`); + + let result = cps.validateAddonCSP( + policy, + Ci.nsIAddonContentPolicy.CSP_ALLOW_LOCALHOST + ); + equal(result, expectedResult); + }; + + checkPolicy("script-src 'self'; object-src 'self';", null); + checkPolicy("script-src 'self'; object-src 'self'; worker-src 'none'", null); + checkPolicy("script-src 'self'; object-src 'none'; worker-src 'self'", null); + + let hash = + "'sha256-NjZhMDQ1YjQ1MjEwMmM1OWQ4NDBlYzA5N2Q1OWQ5NDY3ZTEzYTNmMzRmNjQ5NGU1MzlmZmQzMmMxYmIzNWYxOCAgLQo='"; + + checkPolicy( + `script-src 'self' moz-extension://09abcdef blob: filesystem: ${hash}; ` + + `object-src 'self' moz-extension://09abcdef blob: filesystem: ${hash}`, + null + ); + + for (let policy of ["", "object-src 'none';", "worker-src 'none';"]) { + checkPolicy( + policy, + "Policy is missing a required \u2018script-src\u2019 directive" + ); + } + + checkPolicy( + "default-src 'self'", + null, + "A valid default-src should count as a valid script-src or object-src" + ); + + for (let directive of ["script-src", "object-src", "worker-src"]) { + checkPolicy( + `default-src 'self'; ${directive} 'self'`, + null, + `A valid default-src should count as a valid ${directive}` + ); + checkPolicy( + `default-src 'self'; ${directive} http://example.com`, + `\u2018${directive}\u2019 directive contains a forbidden http: protocol source`, + `A valid default-src should not allow an invalid ${directive} directive` + ); + } + + checkPolicy( + "script-src 'self';", + "Policy is missing a required \u2018object-src\u2019 directive" + ); + + checkPolicy( + "script-src 'none'; object-src 'none'", + "\u2018script-src\u2019 must include the source 'self'" + ); + + checkPolicy("script-src 'self'; object-src 'none';", null); + + checkPolicy( + "script-src 'self' 'unsafe-inline'; object-src 'self';", + "\u2018script-src\u2019 directive contains a forbidden 'unsafe-inline' keyword" + ); + + checkPolicy( + "script-src 'self' 'unsafe-eval'; object-src 'self';", + "\u2018script-src\u2019 directive contains a forbidden 'unsafe-eval' keyword" + ); + + // Localhost is always valid + for (let src of [ + "http://localhost", + "https://localhost", + "http://127.0.0.1", + "https://127.0.0.1", + ]) { + checkPolicy(`script-src 'self' ${src}; object-src 'none';`, null); + } + + let directives = ["script-src", "object-src"]; + + for (let [directive, other] of [directives, directives.slice().reverse()]) { + for (let protocol of ["http", "https"]) { + checkPolicy( + `${directive} 'self' ${protocol}:; ${other} 'self';`, + `${protocol}: protocol requires a host in \u2018${directive}\u2019 directives` + ); + } + + checkPolicy( + `${directive} 'self' https://example.com; ${other} 'self';`, + `\u2018${directive}\u2019 directive contains a forbidden https: protocol source` + ); + + checkPolicy( + `${directive} 'self' http://example.com; ${other} 'self';`, + `\u2018${directive}\u2019 directive contains a forbidden http: protocol source` + ); + + for (let protocol of ["ftp", "meh"]) { + checkPolicy( + `${directive} 'self' ${protocol}:; ${other} 'self';`, + `\u2018${directive}\u2019 directive contains a forbidden ${protocol}: protocol source` + ); + } + + checkPolicy( + `${directive} 'self' 'nonce-01234'; ${other} 'self';`, + `\u2018${directive}\u2019 directive contains a forbidden 'nonce-*' keyword` + ); + } +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_MessageManagerProxy.js b/toolkit/components/extensions/test/xpcshell/test_ext_MessageManagerProxy.js new file mode 100644 index 0000000000..20ffb71d18 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_MessageManagerProxy.js @@ -0,0 +1,80 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const { MessageManagerProxy } = ChromeUtils.import( + "resource://gre/modules/MessageManagerProxy.jsm" +); +const { PromiseUtils } = ChromeUtils.import( + "resource://gre/modules/PromiseUtils.jsm" +); + +class TestMessageManagerProxy extends MessageManagerProxy { + constructor(contentPage, identifier) { + super(contentPage.browser); + this.identifier = identifier; + this.contentPage = contentPage; + this.deferred = null; + } + + // Registers message listeners. Call dispose() once you've finished. + async setupPingPongListeners() { + await this.contentPage.loadFrameScript(`() => { + this.addMessageListener("test:MessageManagerProxy:Ping", ({data}) => { + this.sendAsyncMessage("test:MessageManagerProxy:Pong", "${this.identifier}:" + data); + }); + }`); + + // Register the listener here instead of during testPingPong, to make sure + // that the listener is correctly registered during the whole test. + this.addMessageListener("test:MessageManagerProxy:Pong", event => { + ok( + this.deferred, + `[${this.identifier}] expected to be waiting for ping-pong` + ); + this.deferred.resolve(event.data); + this.deferred = null; + }); + } + + async testPingPong(description) { + equal(this.deferred, null, "should not be waiting for a message"); + this.deferred = PromiseUtils.defer(); + this.sendAsyncMessage("test:MessageManagerProxy:Ping", description); + let result = await this.deferred.promise; + equal(result, `${this.identifier}:${description}`, "Expected ping-pong"); + } +} + +// Tests that MessageManagerProxy continues to proxy messages after docshells +// have been swapped. +add_task(async function test_message_after_swapdocshells() { + let page1 = await ExtensionTestUtils.loadContentPage("about:blank"); + let page2 = await ExtensionTestUtils.loadContentPage("about:blank"); + + let testProxyOne = new TestMessageManagerProxy(page1, "page1"); + let testProxyTwo = new TestMessageManagerProxy(page2, "page2"); + + await testProxyOne.setupPingPongListeners(); + await testProxyTwo.setupPingPongListeners(); + + await testProxyOne.testPingPong("after setup (to 1)"); + await testProxyTwo.testPingPong("after setup (to 2)"); + + page1.browser.swapDocShells(page2.browser); + + await testProxyOne.testPingPong("after docshell swap (to 1)"); + await testProxyTwo.testPingPong("after docshell swap (to 2)"); + + // Swap again to verify that listeners are repeatedly moved when needed. + page1.browser.swapDocShells(page2.browser); + + await testProxyOne.testPingPong("after another docshell swap (to 1)"); + await testProxyTwo.testPingPong("after another docshell swap (to 2)"); + + // Verify that dispose() works regardless of the browser's validity. + await testProxyOne.dispose(); + await page1.close(); + await page2.close(); + await testProxyTwo.dispose(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_activityLog.js b/toolkit/components/extensions/test/xpcshell/test_ext_activityLog.js new file mode 100644 index 0000000000..4a23b65264 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_activityLog.js @@ -0,0 +1,21 @@ +"use strict"; + +add_task(async function test_api_restricted() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + applications: { + gecko: { id: "activityLog-permission@tests.mozilla.org" }, + }, + permissions: ["activityLog"], + }, + async background() { + browser.test.assertEq( + undefined, + browser.activityLog, + "activityLog is privileged" + ); + }, + }); + await extension.startup(); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_private_field_xrays.js b/toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_private_field_xrays.js new file mode 100644 index 0000000000..bc4e0409cb --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_private_field_xrays.js @@ -0,0 +1,160 @@ +"use strict"; + +// ExtensionContent.jsm needs to know when it's running from xpcshell, +// to use the right timeout for content scripts executed at document_idle. +ExtensionTestUtils.mockAppInfo(); +const server = createHttpServer(); +server.registerDirectory("/data/", do_get_file("data")); + +const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`; + +add_task(async function test_contentscript_private_field_xrays() { + async function contentScript() { + let node = window.document.createElement("div"); + + class Base { + constructor(o) { + return o; + } + } + + class A extends Base { + #x = 5; + static gx(o) { + return o.#x; + } + static sx(o, v) { + o.#x = v; + } + } + + browser.test.log(A.toString()); + + // Stamp node with A's private field. + new A(node); + + browser.test.log("stamped"); + + browser.test.assertEq( + A.gx(node), + 5, + "We should be able to see our expando private field" + ); + browser.test.log("Read"); + browser.test.assertThrows( + () => A.gx(node.wrappedJSObject), + /Trying to read undeclared field/, + "Underlying object should not have our private field" + ); + + browser.test.log("threw"); + window.frames[0].document.adoptNode(node); + browser.test.log("adopted"); + browser.test.assertEq( + A.gx(node), + 5, + "Adoption should not change expando private field" + ); + browser.test.log("read"); + browser.test.assertThrows( + () => A.gx(node.wrappedJSObject), + /Trying to read undeclared field/, + "Adoption should really not change expandos private fields" + ); + browser.test.log("threw2"); + + // Repeat but now with an object that has a reference from the + // window it's being cloned into. + node = window.document.createElement("div"); + // Stamp node with A's private field. + new A(node); + A.sx(node, 6); + + browser.test.assertEq( + A.gx(node), + 6, + "We should be able to see our expando (2)" + ); + browser.test.assertThrows( + () => A.gx(node.wrappedJSObject), + /Trying to read undeclared field/, + "Underlying object should not have exxpando. (2)" + ); + + window.frames[0].wrappedJSObject.incoming = node.wrappedJSObject; + window.frames[0].document.adoptNode(node); + + browser.test.assertEq( + A.gx(node), + 6, + "We should be able to see our expando (3)" + ); + browser.test.assertThrows( + () => A.gx(node.wrappedJSObject), + /Trying to read undeclared field/, + "Underlying object should not have exxpando. (3)" + ); + + // Repeat once more, now with an expando that refers to the object itself + node = window.document.createElement("div"); + new A(node); + A.sx(node, node); + + browser.test.assertEq( + A.gx(node), + node, + "We should be able to see our self-referential expando (4)" + ); + browser.test.assertThrows( + () => A.gx(node.wrappedJSObject), + /Trying to read undeclared field/, + "Underlying object should not have exxpando. (4)" + ); + + window.frames[0].document.adoptNode(node); + + browser.test.assertEq( + A.gx(node), + node, + "Adoption should not change our self-referential expando (4)" + ); + browser.test.assertThrows( + () => A.gx(node.wrappedJSObject), + /Trying to read undeclared field/, + "Adoption should not change underlying object. (4)" + ); + + // And test what happens if we now set document.domain and cause + // wrapper remapping. + let doc = window.frames[0].document; + // eslint-disable-next-line no-self-assign + doc.domain = doc.domain; + + browser.test.notifyPass("privateFieldXRayAdoption"); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + content_scripts: [ + { + matches: ["http://*/*/file_toplevel.html"], + js: ["content_script.js"], + }, + ], + }, + + files: { + "content_script.js": contentScript, + }, + }); + + await extension.startup(); + let contentPage = await ExtensionTestUtils.loadContentPage( + `${BASE_URL}/file_toplevel.html` + ); + + await extension.awaitFinish("privateFieldXRayAdoption"); + + await contentPage.close(); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_xrays.js b/toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_xrays.js new file mode 100644 index 0000000000..9655c157d1 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_adoption_with_xrays.js @@ -0,0 +1,129 @@ +"use strict"; + +// ExtensionContent.jsm needs to know when it's running from xpcshell, +// to use the right timeout for content scripts executed at document_idle. +ExtensionTestUtils.mockAppInfo(); +const server = createHttpServer(); +server.registerDirectory("/data/", do_get_file("data")); + +const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`; + +add_task(async function test_contentscript_xrays() { + async function contentScript() { + let node = window.document.createElement("div"); + node.expando = 5; + + browser.test.assertEq( + node.expando, + 5, + "We should be able to see our expando" + ); + browser.test.assertEq( + node.wrappedJSObject.expando, + undefined, + "Underlying object should not have our expando" + ); + + window.frames[0].document.adoptNode(node); + browser.test.assertEq( + node.expando, + 5, + "Adoption should not change expandos" + ); + browser.test.assertEq( + node.wrappedJSObject.expando, + undefined, + "Adoption should really not change expandos" + ); + + // Repeat but now with an object that has a reference from the + // window it's being cloned into. + node = window.document.createElement("div"); + node.expando = 6; + + browser.test.assertEq( + node.expando, + 6, + "We should be able to see our expando (2)" + ); + browser.test.assertEq( + node.wrappedJSObject.expando, + undefined, + "Underlying object should not have our expando (2)" + ); + + window.frames[0].wrappedJSObject.incoming = node.wrappedJSObject; + + window.frames[0].document.adoptNode(node); + browser.test.assertEq( + node.expando, + 6, + "Adoption should not change expandos (2)" + ); + browser.test.assertEq( + node.wrappedJSObject.expando, + undefined, + "Adoption should really not change expandos (2)" + ); + + // Repeat once more, now with an expando that refers to the object itself. + node = window.document.createElement("div"); + node.expando = node; + + browser.test.assertEq( + node.expando, + node, + "We should be able to see our self-referential expando (3)" + ); + browser.test.assertEq( + node.wrappedJSObject.expando, + undefined, + "Underlying object should not have our self-referential expando (3)" + ); + + window.frames[0].document.adoptNode(node); + browser.test.assertEq( + node.expando, + node, + "Adoption should not change self-referential expando (3)" + ); + browser.test.assertEq( + node.wrappedJSObject.expando, + undefined, + "Adoption should really not change self-referential expando (3)" + ); + + // And test what happens if we now set document.domain and cause + // wrapper remapping. + let doc = window.frames[0].document; + // eslint-disable-next-line no-self-assign + doc.domain = doc.domain; + + browser.test.notifyPass("contentScriptAdoptionWithXrays"); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + content_scripts: [ + { + matches: ["http://*/*/file_toplevel.html"], + js: ["content_script.js"], + }, + ], + }, + + files: { + "content_script.js": contentScript, + }, + }); + + await extension.startup(); + let contentPage = await ExtensionTestUtils.loadContentPage( + `${BASE_URL}/file_toplevel.html` + ); + + await extension.awaitFinish("contentScriptAdoptionWithXrays"); + + await contentPage.close(); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_alarms.js b/toolkit/components/extensions/test/xpcshell/test_ext_alarms.js new file mode 100644 index 0000000000..0751f7d573 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_alarms.js @@ -0,0 +1,219 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +"use strict"; + +add_task(async function test_alarm_without_permissions() { + function backgroundScript() { + browser.test.assertTrue( + !browser.alarms, + "alarm API is not available when the alarm permission is not required" + ); + browser.test.notifyPass("alarms_permission"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: [], + }, + }); + + await extension.startup(); + await extension.awaitFinish("alarms_permission"); + await extension.unload(); +}); + +add_task(async function test_alarm_clear_non_matching_name() { + async function backgroundScript() { + let ALARM_NAME = "test_ext_alarms"; + + browser.alarms.create(ALARM_NAME, { when: Date.now() + 2000000 }); + + let wasCleared = await browser.alarms.clear(ALARM_NAME + "1"); + browser.test.assertFalse(wasCleared, "alarm was not cleared"); + + let alarms = await browser.alarms.getAll(); + browser.test.assertEq(1, alarms.length, "alarm was not removed"); + browser.test.notifyPass("alarm-clear"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: ["alarms"], + }, + }); + + await extension.startup(); + await extension.awaitFinish("alarm-clear"); + await extension.unload(); +}); + +add_task(async function test_alarm_get_and_clear_single_argument() { + async function backgroundScript() { + browser.alarms.create({ when: Date.now() + 2000000 }); + + let alarm = await browser.alarms.get(); + browser.test.assertEq("", alarm.name, "expected alarm returned"); + + let wasCleared = await browser.alarms.clear(); + browser.test.assertTrue(wasCleared, "alarm was cleared"); + + let alarms = await browser.alarms.getAll(); + browser.test.assertEq(0, alarms.length, "alarm was removed"); + + browser.test.notifyPass("alarm-single-arg"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: ["alarms"], + }, + }); + + await extension.startup(); + await extension.awaitFinish("alarm-single-arg"); + await extension.unload(); +}); + +add_task(async function test_get_get_all_clear_all_alarms() { + async function backgroundScript() { + const ALARM_NAME = "test_alarm"; + + let suffixes = [0, 1, 2]; + + for (let suffix of suffixes) { + browser.alarms.create(ALARM_NAME + suffix, { + when: Date.now() + (suffix + 1) * 10000, + }); + } + + let alarms = await browser.alarms.getAll(); + browser.test.assertEq( + suffixes.length, + alarms.length, + "expected number of alarms were found" + ); + alarms.forEach((alarm, index) => { + browser.test.assertEq( + ALARM_NAME + index, + alarm.name, + "alarm has the expected name" + ); + }); + + for (let suffix of suffixes) { + let alarm = await browser.alarms.get(ALARM_NAME + suffix); + browser.test.assertEq( + ALARM_NAME + suffix, + alarm.name, + "alarm has the expected name" + ); + browser.test.sendMessage(`get-${suffix}`); + } + + let wasCleared = await browser.alarms.clear(ALARM_NAME + suffixes[0]); + browser.test.assertTrue(wasCleared, "alarm was cleared"); + + alarms = await browser.alarms.getAll(); + browser.test.assertEq(2, alarms.length, "alarm was removed"); + + let alarm = await browser.alarms.get(ALARM_NAME + suffixes[0]); + browser.test.assertEq(undefined, alarm, "non-existent alarm is undefined"); + browser.test.sendMessage(`get-invalid`); + + wasCleared = await browser.alarms.clearAll(); + browser.test.assertTrue(wasCleared, "alarms were cleared"); + + alarms = await browser.alarms.getAll(); + browser.test.assertEq(0, alarms.length, "no alarms exist"); + browser.test.sendMessage("clearAll"); + browser.test.sendMessage("clear"); + browser.test.sendMessage("getAll"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: ["alarms"], + }, + }); + + await Promise.all([ + extension.startup(), + extension.awaitMessage("getAll"), + extension.awaitMessage("get-0"), + extension.awaitMessage("get-1"), + extension.awaitMessage("get-2"), + extension.awaitMessage("clear"), + extension.awaitMessage("get-invalid"), + extension.awaitMessage("clearAll"), + ]); + await extension.unload(); +}); + +async function test_alarm_fires_with_options(alarmCreateOptions) { + info( + `Test alarms.create fires with options: ${JSON.stringify( + alarmCreateOptions + )}` + ); + + function backgroundScript(createOptions) { + let ALARM_NAME = "test_ext_alarms"; + let timer; + + browser.alarms.onAlarm.addListener(alarm => { + browser.test.assertEq( + ALARM_NAME, + alarm.name, + "alarm has the expected name" + ); + clearTimeout(timer); + browser.test.notifyPass("alarms-create-with-options"); + }); + + browser.alarms.create(ALARM_NAME, createOptions); + + timer = setTimeout(async () => { + browser.test.fail("alarm fired within expected time"); + let wasCleared = await browser.alarms.clear(ALARM_NAME); + browser.test.assertTrue(wasCleared, "alarm was cleared"); + browser.test.notifyFail("alarms-create-with-options"); + }, 10000); + } + + let extension = ExtensionTestUtils.loadExtension({ + // Pass the alarms.create options to the background page. + background: `(${backgroundScript})(${JSON.stringify(alarmCreateOptions)})`, + manifest: { + permissions: ["alarms"], + }, + }); + + await extension.startup(); + await extension.awaitFinish("alarms-create-with-options"); + + // Defer unloading the extension so the asynchronous event listener + // reply finishes. + await new Promise(resolve => setTimeout(resolve, 0)); + await extension.unload(); +} + +add_task(async function test_alarm_fires() { + Services.prefs.setBoolPref( + "privacy.resistFingerprinting.reduceTimerPrecision.jitter", + false + ); + + await test_alarm_fires_with_options({ delayInMinutes: 0.01 }); + await test_alarm_fires_with_options({ when: Date.now() + 1000 }); + await test_alarm_fires_with_options({ delayInMinutes: -10 }); + await test_alarm_fires_with_options({ when: Date.now() - 1000 }); + + Services.prefs.clearUserPref( + "privacy.resistFingerprinting.reduceTimerPrecision.jitter" + ); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_alarms_does_not_fire.js b/toolkit/components/extensions/test/xpcshell/test_ext_alarms_does_not_fire.js new file mode 100644 index 0000000000..fe385004ba --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_alarms_does_not_fire.js @@ -0,0 +1,34 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +"use strict"; + +add_task(async function test_cleared_alarm_does_not_fire() { + async function backgroundScript() { + let ALARM_NAME = "test_ext_alarms"; + + browser.alarms.onAlarm.addListener(alarm => { + browser.test.fail("cleared alarm does not fire"); + browser.test.notifyFail("alarm-cleared"); + }); + browser.alarms.create(ALARM_NAME, { when: Date.now() + 1000 }); + + let wasCleared = await browser.alarms.clear(ALARM_NAME); + browser.test.assertTrue(wasCleared, "alarm was cleared"); + + await new Promise(resolve => setTimeout(resolve, 2000)); + + browser.test.notifyPass("alarm-cleared"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: ["alarms"], + }, + }); + + await extension.startup(); + await extension.awaitFinish("alarm-cleared"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_alarms_periodic.js b/toolkit/components/extensions/test/xpcshell/test_ext_alarms_periodic.js new file mode 100644 index 0000000000..b78d6da649 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_alarms_periodic.js @@ -0,0 +1,50 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +"use strict"; + +add_task(async function test_periodic_alarm_fires() { + function backgroundScript() { + const ALARM_NAME = "test_ext_alarms"; + let count = 0; + let timer; + + browser.alarms.onAlarm.addListener(alarm => { + browser.test.assertEq( + alarm.name, + ALARM_NAME, + "alarm has the expected name" + ); + if (count++ === 3) { + clearTimeout(timer); + browser.alarms.clear(ALARM_NAME).then(wasCleared => { + browser.test.assertTrue(wasCleared, "alarm was cleared"); + + browser.test.notifyPass("alarm-periodic"); + }); + } + }); + + browser.alarms.create(ALARM_NAME, { periodInMinutes: 0.02 }); + + timer = setTimeout(async () => { + browser.test.fail("alarm fired expected number of times"); + + let wasCleared = await browser.alarms.clear(ALARM_NAME); + browser.test.assertTrue(wasCleared, "alarm was cleared"); + + browser.test.notifyFail("alarm-periodic"); + }, 30000); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: ["alarms"], + }, + }); + + await extension.startup(); + await extension.awaitFinish("alarm-periodic"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_alarms_replaces.js b/toolkit/components/extensions/test/xpcshell/test_ext_alarms_replaces.js new file mode 100644 index 0000000000..0d7597fa5a --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_alarms_replaces.js @@ -0,0 +1,56 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(async function test_duplicate_alarm_name_replaces_alarm() { + function backgroundScript() { + let count = 0; + + browser.alarms.onAlarm.addListener(async alarm => { + browser.test.assertEq( + "replaced alarm", + alarm.name, + "Expected last alarm" + ); + browser.test.assertEq( + 0, + count++, + "duplicate named alarm replaced existing alarm" + ); + let results = await browser.alarms.getAll(); + + // "replaced alarm" is expected to be replaced with a non-repeating + // alarm, so it should not appear in the list of alarms. + browser.test.assertEq(1, results.length, "exactly one alarms exists"); + browser.test.assertEq( + "unrelated alarm", + results[0].name, + "remaining alarm has the expected name" + ); + + browser.test.notifyPass("alarm-duplicate"); + }); + + // Alarm that is so far in the future that it is never triggered. + browser.alarms.create("unrelated alarm", { delayInMinutes: 60 }); + // Alarm that repeats. + browser.alarms.create("replaced alarm", { + delayInMinutes: 1 / 60, + periodInMinutes: 1 / 60, + }); + // Before the repeating alarm is triggered, it is immediately replaced with + // a non-repeating alarm. + browser.alarms.create("replaced alarm", { delayInMinutes: 3 / 60 }); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: `(${backgroundScript})()`, + manifest: { + permissions: ["alarms"], + }, + }); + + await extension.startup(); + await extension.awaitFinish("alarm-duplicate"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_api_permissions.js b/toolkit/components/extensions/test/xpcshell/test_ext_api_permissions.js new file mode 100644 index 0000000000..4be29dc848 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_api_permissions.js @@ -0,0 +1,76 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +let { Management } = ChromeUtils.import( + "resource://gre/modules/Extension.jsm", + null +); +function getNextContext() { + return new Promise(resolve => { + Management.on("proxy-context-load", function listener(type, context) { + Management.off("proxy-context-load", listener); + resolve(context); + }); + }); +} + +add_task(async function test_storage_api_without_permissions() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + // Force API initialization. + try { + browser.storage.onChanged.addListener(() => {}); + } catch (e) { + // Ignore. + } + }, + + manifest: { + permissions: [], + }, + }); + + let contextPromise = getNextContext(); + await extension.startup(); + + let context = await contextPromise; + + // Force API initialization. + void context.apiObj; + + ok( + !("storage" in context.apiObj), + "The storage API should not be initialized" + ); + + await extension.unload(); +}); + +add_task(async function test_storage_api_with_permissions() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.storage.onChanged.addListener(() => {}); + }, + + manifest: { + permissions: ["storage"], + }, + }); + + let contextPromise = getNextContext(); + await extension.startup(); + + let context = await contextPromise; + + // Force API initialization. + void context.apiObj; + + equal( + typeof context.apiObj.storage, + "object", + "The storage API should be initialized" + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_api_injection.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_api_injection.js new file mode 100644 index 0000000000..a603b03a29 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_api_injection.js @@ -0,0 +1,35 @@ +"use strict"; + +const server = createHttpServer({ hosts: ["example.com"] }); +server.registerDirectory("/data/", do_get_file("data")); + +add_task(async function testBackgroundWindow() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.test.log("background script executed"); + window.location = + "http://example.com/data/file_privilege_escalation.html"; + }, + }); + + let awaitConsole = new Promise(resolve => { + Services.console.registerListener(function listener(message) { + if (/WebExt Privilege Escalation/.test(message.message)) { + Services.console.unregisterListener(listener); + resolve(message); + } + }); + }); + + await extension.startup(); + + let message = await awaitConsole; + ok( + message.message.includes( + "WebExt Privilege Escalation: typeof(browser) = undefined" + ), + "Document does not have `browser` APIs." + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_early_shutdown.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_early_shutdown.js new file mode 100644 index 0000000000..ec9d9a6c43 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_early_shutdown.js @@ -0,0 +1,195 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const { BrowserTestUtils } = ChromeUtils.import( + "resource://testing-common/BrowserTestUtils.jsm" +); + +AddonTestUtils.init(this); +AddonTestUtils.overrideCertDB(); +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "1", + "43" +); + +let { + promiseRestartManager, + promiseShutdownManager, + promiseStartupManager, +} = AddonTestUtils; + +Services.prefs.setBoolPref( + "extensions.webextensions.background-delayed-startup", + true +); + +let { Management } = ChromeUtils.import( + "resource://gre/modules/Extension.jsm", + null +); + +// Crashes a 's remote process. +// Based on BrowserTestUtils.crashFrame. +function crashFrame(browser) { + if (!browser.isRemoteBrowser) { + // The browser should be remote, or the test runner would be killed. + throw new Error(" must be remote"); + } + + // Trigger crash by sending a message to BrowserTestUtils actor. + BrowserTestUtils.sendAsyncMessage( + browser.browsingContext, + "BrowserTestUtils:CrashFrame", + {} + ); +} + +// Verifies that a delayed background page is not loaded when an extension is +// shut down during startup. +add_task(async function test_unload_extension_before_background_page_startup() { + await promiseStartupManager(); + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + background() { + browser.test.sendMessage("background_startup_observed"); + }, + }); + + // Delayed startup are only enabled for browser (re)starts, so we need to + // install the extension first, and then unload it. + + await extension.startup(); + await extension.awaitMessage("background_startup_observed"); + + // Now the actual test: Unloading an extension before the startup has + // finished should interrupt the start-up and abort pending delayed loads. + info("Starting extension whose startup will be interrupted"); + ExtensionParent._resetStartupPromises(); + await promiseRestartManager(); + await extension.awaitStartup(); + + let extensionBrowserInsertions = 0; + let onExtensionBrowserInserted = () => ++extensionBrowserInsertions; + Management.on("extension-browser-inserted", onExtensionBrowserInserted); + + info("Unloading extension before the delayed background page starts loading"); + await extension.addon.disable(); + + // Re-enable the add-on to let enough time pass to load a whole background + // page. If at the end of this the original background page hasn't loaded, + // we can consider the test successful. + await extension.addon.enable(); + + // Trigger the notification that would load a background page. + info("Forcing pending delayed background page to load"); + Services.obs.notifyObservers(null, "sessionstore-windows-restored"); + + // This is the expected message from the re-enabled add-on. + await extension.awaitMessage("background_startup_observed"); + await extension.unload(); + + await promiseShutdownManager(); + ExtensionParent._resetStartupPromises(); + + Management.off("extension-browser-inserted", onExtensionBrowserInserted); + Assert.equal( + extensionBrowserInsertions, + 1, + "Extension browser should have been inserted only once" + ); +}); + +// Verifies that the "build" method of BackgroundPage in ext-backgroundPage.js +// does not deadlock when startup is interrupted by extension shutdown. +add_task(async function test_unload_extension_during_background_page_startup() { + await promiseStartupManager(); + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + background() { + browser.test.sendMessage("background_starting"); + }, + }); + + // Delayed startup are only enabled for browser (re)starts, so we need to + // install the extension first, and then reload it. + await extension.startup(); + await extension.awaitMessage("background_starting"); + + ExtensionParent._resetStartupPromises(); + await promiseRestartManager(); + await extension.awaitStartup(); + + let bgStartupPromise = new Promise(resolve => { + function onBackgroundPageDone(eventName) { + extension.extension.off("background-page-started", onBackgroundPageDone); + extension.extension.off("background-page-aborted", onBackgroundPageDone); + + if (eventName === "background-page-aborted") { + info("Background page startup was interrupted"); + resolve("bg_aborted"); + } else { + info("Background page startup finished normally"); + resolve("bg_fully_loaded"); + } + } + extension.extension.on("background-page-started", onBackgroundPageDone); + extension.extension.on("background-page-aborted", onBackgroundPageDone); + }); + + let bgStartingPromise = new Promise(resolve => { + let backgroundLoadCount = 0; + let backgroundPageUrl = extension.extension.baseURI.resolve( + "_generated_background_page.html" + ); + + // Prevent the background page from actually loading. + Management.once("extension-browser-inserted", (eventName, browser) => { + // Intercept background page load. + let browserLoadURI = browser.loadURI; + browser.loadURI = function() { + Assert.equal(++backgroundLoadCount, 1, "loadURI should be called once"); + Assert.equal( + arguments[0], + backgroundPageUrl, + "Expected background page" + ); + // Reset to "about:blank" to not load the actual background page. + arguments[0] = "about:blank"; + browserLoadURI.apply(this, arguments); + + // And force the extension process to crash. + if (browser.isRemote) { + crashFrame(browser); + } else { + // If extensions are not running in out-of-process mode, then the + // non-remote process should not be killed (or the test runner dies). + // Remove instead, to simulate the immediate disconnection + // of the message manager (that would happen if the process crashed). + browser.remove(); + } + resolve(); + }; + }); + }); + + // Force background page to initialize. + Services.obs.notifyObservers(null, "sessionstore-windows-restored"); + await bgStartingPromise; + + await extension.unload(); + await promiseShutdownManager(); + + // This part is the regression test for bug 1501375. It verifies that the + // background building completes eventually. + // If it does not, then the next line will cause a timeout. + info("Waiting for background builder to finish"); + let bgLoadState = await bgStartupPromise; + Assert.equal(bgLoadState, "bg_aborted", "Startup should be interrupted"); + + ExtensionParent._resetStartupPromises(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_generated_load_events.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_generated_load_events.js new file mode 100644 index 0000000000..cac574b8ca --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_generated_load_events.js @@ -0,0 +1,23 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* eslint-disable mozilla/balanced-listeners */ + +add_task(async function test_DOMContentLoaded_in_generated_background_page() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + function reportListener(event) { + browser.test.sendMessage("eventname", event.type); + } + document.addEventListener("DOMContentLoaded", reportListener); + window.addEventListener("load", reportListener); + }, + }); + + await extension.startup(); + equal("DOMContentLoaded", await extension.awaitMessage("eventname")); + equal("load", await extension.awaitMessage("eventname")); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_generated_reload.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_generated_reload.js new file mode 100644 index 0000000000..a22db9d582 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_generated_reload.js @@ -0,0 +1,24 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(async function test_reload_generated_background_page() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + if (location.hash !== "#firstrun") { + browser.test.sendMessage("first run"); + location.hash = "#firstrun"; + browser.test.assertEq("#firstrun", location.hash); + location.reload(); + } else { + browser.test.notifyPass("second run"); + } + }, + }); + + await extension.startup(); + await extension.awaitMessage("first run"); + await extension.awaitFinish("second run"); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_global_history.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_global_history.js new file mode 100644 index 0000000000..eaf20827e5 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_global_history.js @@ -0,0 +1,24 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const { PlacesTestUtils } = ChromeUtils.import( + "resource://testing-common/PlacesTestUtils.jsm" +); + +add_task(async function test_global_history() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.test.sendMessage("background-loaded", location.href); + }, + }); + + await extension.startup(); + + let backgroundURL = await extension.awaitMessage("background-loaded"); + + await extension.unload(); + + let exists = await PlacesTestUtils.isPageInDB(backgroundURL); + ok(!exists, "Background URL should not be in history database"); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_private_browsing.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_private_browsing.js new file mode 100644 index 0000000000..5075e643be --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_private_browsing.js @@ -0,0 +1,46 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(async function test_background_incognito() { + info( + "Test background page incognito value with permanent private browsing enabled" + ); + + Services.prefs.setBoolPref("extensions.allowPrivateBrowsingByDefault", false); + Services.prefs.setBoolPref("browser.privatebrowsing.autostart", true); + registerCleanupFunction(() => { + Services.prefs.clearUserPref("browser.privatebrowsing.autostart"); + Services.prefs.clearUserPref("extensions.allowPrivateBrowsingByDefault"); + }); + + let extension = ExtensionTestUtils.loadExtension({ + incognitoOverride: "spanning", + async background() { + browser.test.assertEq( + window, + browser.extension.getBackgroundPage(), + "Caller should be able to access itself as a background page" + ); + browser.test.assertEq( + window, + await browser.runtime.getBackgroundPage(), + "Caller should be able to access itself as a background page" + ); + + browser.test.assertEq( + browser.extension.inIncognitoContext, + true, + "inIncognitoContext is true for permanent private browsing" + ); + + browser.test.notifyPass("incognito"); + }, + }); + + await extension.startup(); + + await extension.awaitFinish("incognito"); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_runtime_connect_params.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_runtime_connect_params.js new file mode 100644 index 0000000000..aa0976434b --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_runtime_connect_params.js @@ -0,0 +1,88 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +function backgroundScript() { + let received_ports_number = 0; + + const expected_received_ports_number = 1; + + function countReceivedPorts(port) { + received_ports_number++; + + if (port.name == "check-results") { + browser.runtime.onConnect.removeListener(countReceivedPorts); + + browser.test.assertEq( + expected_received_ports_number, + received_ports_number, + "invalid connect should not create a port" + ); + + browser.test.notifyPass("runtime.connect invalid params"); + } + } + + browser.runtime.onConnect.addListener(countReceivedPorts); + + let childFrame = document.createElement("iframe"); + childFrame.src = "extensionpage.html"; + document.body.appendChild(childFrame); +} + +function senderScript() { + let detected_invalid_connect_params = 0; + + const invalid_connect_params = [ + // too many params + [ + "fake-extensions-id", + { name: "fake-conn-name" }, + "unexpected third params", + ], + // invalid params format + [{}, {}], + ["fake-extensions-id", "invalid-connect-info-format"], + ]; + const expected_detected_invalid_connect_params = + invalid_connect_params.length; + + function assertInvalidConnectParamsException(params) { + try { + browser.runtime.connect(...params); + } catch (e) { + detected_invalid_connect_params++; + browser.test.assertTrue( + e.toString().includes("Incorrect argument types for runtime.connect."), + "exception message is correct" + ); + } + } + for (let params of invalid_connect_params) { + assertInvalidConnectParamsException(params); + } + browser.test.assertEq( + expected_detected_invalid_connect_params, + detected_invalid_connect_params, + "all invalid runtime.connect params detected" + ); + + browser.runtime.connect(browser.runtime.id, { name: "check-results" }); +} + +let extensionData = { + background: backgroundScript, + files: { + "senderScript.js": senderScript, + "extensionpage.html": ``, + }, +}; + +add_task(async function test_backgroundRuntimeConnectParams() { + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + + await extension.awaitFinish("runtime.connect invalid params"); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_sub_windows.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_sub_windows.js new file mode 100644 index 0000000000..1c3180b1b6 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_sub_windows.js @@ -0,0 +1,46 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(async function testBackgroundWindow() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.test.log("background script executed"); + + browser.test.sendMessage("background-script-load"); + + let img = document.createElement("img"); + img.src = + "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; + document.body.appendChild(img); + + img.onload = () => { + browser.test.log("image loaded"); + + let iframe = document.createElement("iframe"); + iframe.src = "about:blank?1"; + + iframe.onload = () => { + browser.test.log("iframe loaded"); + setTimeout(() => { + browser.test.notifyPass("background sub-window test done"); + }, 0); + }; + document.body.appendChild(iframe); + }; + }, + }); + + let loadCount = 0; + extension.onMessage("background-script-load", () => { + loadCount++; + }); + + await extension.startup(); + + await extension.awaitFinish("background sub-window test done"); + + equal(loadCount, 1, "background script loaded only once"); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_teardown.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_teardown.js new file mode 100644 index 0000000000..013a68726c --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_teardown.js @@ -0,0 +1,99 @@ +"use strict"; + +add_task(async function test_background_reload_and_unload() { + let events = []; + { + let { Management } = ChromeUtils.import( + "resource://gre/modules/Extension.jsm", + null + ); + let record = (type, extensionContext) => { + let eventType = type == "proxy-context-load" ? "load" : "unload"; + let url = extensionContext.uri.spec; + let extensionId = extensionContext.extension.id; + events.push({ eventType, url, extensionId }); + }; + + Management.on("proxy-context-load", record); + Management.on("proxy-context-unload", record); + registerCleanupFunction(() => { + Management.off("proxy-context-load", record); + Management.off("proxy-context-unload", record); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.test.onMessage.addListener(msg => { + browser.test.assertEq("reload-background", msg); + location.reload(); + }); + browser.test.sendMessage("background-url", location.href); + }, + }); + + await extension.startup(); + let backgroundUrl = await extension.awaitMessage("background-url"); + + let contextEvents = events.splice(0); + equal( + contextEvents.length, + 1, + "ExtensionContext state change after loading an extension" + ); + equal(contextEvents[0].eventType, "load"); + equal( + contextEvents[0].url, + backgroundUrl, + "The ExtensionContext should be the background page" + ); + + extension.sendMessage("reload-background"); + await extension.awaitMessage("background-url"); + + contextEvents = events.splice(0); + equal( + contextEvents.length, + 2, + "ExtensionContext state changes after reloading the background page" + ); + equal( + contextEvents[0].eventType, + "unload", + "Unload ExtensionContext of background page" + ); + equal( + contextEvents[0].url, + backgroundUrl, + "ExtensionContext URL = background" + ); + equal( + contextEvents[1].eventType, + "load", + "Create new ExtensionContext for background page" + ); + equal( + contextEvents[1].url, + backgroundUrl, + "ExtensionContext URL = background" + ); + + await extension.unload(); + + contextEvents = events.splice(0); + equal( + contextEvents.length, + 1, + "ExtensionContext state change after unloading the extension" + ); + equal( + contextEvents[0].eventType, + "unload", + "Unload ExtensionContext for background page after extension unloads" + ); + equal( + contextEvents[0].url, + backgroundUrl, + "ExtensionContext URL = background" + ); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_telemetry.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_telemetry.js new file mode 100644 index 0000000000..8ca76ea3c2 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_telemetry.js @@ -0,0 +1,104 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const HISTOGRAM = "WEBEXT_BACKGROUND_PAGE_LOAD_MS"; +const HISTOGRAM_KEYED = "WEBEXT_BACKGROUND_PAGE_LOAD_MS_BY_ADDONID"; + +add_task(async function test_telemetry() { + Services.prefs.setBoolPref( + "toolkit.telemetry.testing.overrideProductsCheck", + true + ); + + let extension1 = ExtensionTestUtils.loadExtension({ + background() { + browser.test.sendMessage("loaded"); + }, + }); + + let extension2 = ExtensionTestUtils.loadExtension({ + background() { + browser.test.sendMessage("loaded"); + }, + }); + + clearHistograms(); + + assertHistogramEmpty(HISTOGRAM); + assertKeyedHistogramEmpty(HISTOGRAM_KEYED); + + await extension1.startup(); + await extension1.awaitMessage("loaded"); + + const processSnapshot = snapshot => { + return snapshot.sum > 0; + }; + + const processKeyedSnapshot = snapshot => { + let res = {}; + for (let key of Object.keys(snapshot)) { + res[key] = snapshot[key].sum > 0; + } + return res; + }; + + assertHistogramSnapshot( + HISTOGRAM, + { processSnapshot, expectedValue: true }, + `Data recorded for first extension for histogram: ${HISTOGRAM}.` + ); + + assertHistogramSnapshot( + HISTOGRAM_KEYED, + { + keyed: true, + processSnapshot: processKeyedSnapshot, + expectedValue: { + [extension1.extension.id]: true, + }, + }, + `Data recorded for first extension for histogram ${HISTOGRAM_KEYED}` + ); + + let histogram = Services.telemetry.getHistogramById(HISTOGRAM); + let histogramKeyed = Services.telemetry.getKeyedHistogramById( + HISTOGRAM_KEYED + ); + let histogramSum = histogram.snapshot().sum; + let histogramSumExt1 = histogramKeyed.snapshot()[extension1.extension.id].sum; + + await extension2.startup(); + await extension2.awaitMessage("loaded"); + + assertHistogramSnapshot( + HISTOGRAM, + { + processSnapshot: snapshot => snapshot.sum > histogramSum, + expectedValue: true, + }, + `Data recorded for second extension for histogram: ${HISTOGRAM}.` + ); + + assertHistogramSnapshot( + HISTOGRAM_KEYED, + { + keyed: true, + processSnapshot: processKeyedSnapshot, + expectedValue: { + [extension1.extension.id]: true, + [extension2.extension.id]: true, + }, + }, + `Data recorded for second extension for histogram ${HISTOGRAM_KEYED}` + ); + + equal( + histogramKeyed.snapshot()[extension1.extension.id].sum, + histogramSumExt1, + `Data recorder for first extension is unchanged on the keyed histogram ${HISTOGRAM_KEYED}` + ); + + await extension1.unload(); + await extension2.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_background_window_properties.js b/toolkit/components/extensions/test/xpcshell/test_ext_background_window_properties.js new file mode 100644 index 0000000000..fb2ca27482 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_window_properties.js @@ -0,0 +1,41 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(async function testBackgroundWindowProperties() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + let expectedValues = { + screenX: 0, + screenY: 0, + outerWidth: 0, + outerHeight: 0, + }; + + for (let k in window) { + try { + if (k in expectedValues) { + browser.test.assertEq( + expectedValues[k], + window[k], + `should return the expected value for window property: ${k}` + ); + } else { + void window[k]; + } + } catch (e) { + browser.test.assertEq( + null, + e, + `unexpected exception accessing window property: ${k}` + ); + } + } + + browser.test.notifyPass("background.testWindowProperties.done"); + }, + }); + await extension.startup(); + await extension.awaitFinish("background.testWindowProperties.done"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_brokenlinks.js b/toolkit/components/extensions/test/xpcshell/test_ext_brokenlinks.js new file mode 100644 index 0000000000..c066147268 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_brokenlinks.js @@ -0,0 +1,54 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* + * This test extension has a background script 'missing.js' that is missing + * from the XPI. Such an extension should install/uninstall cleanly without + * causing timeouts. + */ +add_task(async function testXPIMissingBackGroundScript() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + background: { + scripts: ["missing.js"], + }, + }, + }); + + await extension.startup(); + await extension.unload(); + ok(true, "load/unload completed without timing out"); +}); + +/* + * This test extension includes a page with a missing script. The + * extension should install/uninstall cleanly without causing hangs. + */ +add_task(async function testXPIMissingPageScript() { + async function pageScript() { + browser.test.sendMessage("pageReady"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.test.sendMessage("ready", browser.runtime.getURL("page.html")); + }, + files: { + "page.html": ` + + + `, + "page.js": pageScript, + }, + }); + + await extension.startup(); + let url = await extension.awaitMessage("ready"); + let contentPage = await ExtensionTestUtils.loadContentPage(url); + await extension.awaitMessage("pageReady"); + await extension.unload(); + await contentPage.close(); + + ok(true, "load/unload completed without timing out"); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_browserSettings.js b/toolkit/components/extensions/test/xpcshell/test_ext_browserSettings.js new file mode 100644 index 0000000000..ed4eb8a664 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_browserSettings.js @@ -0,0 +1,454 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +ChromeUtils.defineModuleGetter( + this, + "Preferences", + "resource://gre/modules/Preferences.jsm" +); + +ChromeUtils.defineModuleGetter( + this, + "AddonManager", + "resource://gre/modules/AddonManager.jsm" +); + +// The test extension uses an insecure update url. +Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); + +const SETTINGS_ID = "test_settings_staged_restart_webext@tests.mozilla.org"; + +const { + createAppInfo, + promiseShutdownManager, + promiseStartupManager, +} = AddonTestUtils; + +AddonTestUtils.init(this); +AddonTestUtils.overrideCertDB(); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42", "42"); + +add_task(async function test_browser_settings() { + const PERM_DENY_ACTION = Services.perms.DENY_ACTION; + const PERM_UNKNOWN_ACTION = Services.perms.UNKNOWN_ACTION; + + // Create an object to hold the values to which we will initialize the prefs. + const PREFS = { + "browser.cache.disk.enable": true, + "browser.cache.memory.enable": true, + "dom.popup_allowed_events": Preferences.get("dom.popup_allowed_events"), + "image.animation_mode": "none", + "permissions.default.desktop-notification": PERM_UNKNOWN_ACTION, + "ui.context_menus.after_mouseup": false, + "browser.tabs.closeTabByDblclick": false, + "browser.tabs.loadBookmarksInTabs": false, + "browser.search.openintab": false, + "browser.tabs.insertRelatedAfterCurrent": true, + "browser.tabs.insertAfterCurrent": false, + "browser.display.document_color_use": 1, + "browser.display.use_document_fonts": 1, + "browser.zoom.full": true, + "browser.zoom.siteSpecific": true, + }; + + async function background() { + let listeners = new Set([]); + browser.test.onMessage.addListener(async (msg, apiName, value) => { + let apiObj = browser.browserSettings[apiName]; + // Don't add more than one listner per apiName. We leave the + // listener to ensure we do not get more calls than we expect. + if (!listeners.has(apiName)) { + apiObj.onChange.addListener(details => { + browser.test.sendMessage("onChange", { + details, + setting: apiName, + }); + }); + listeners.add(apiName); + } + let result = await apiObj.set({ value }); + if (msg === "set") { + browser.test.assertTrue(result, "set returns true."); + browser.test.sendMessage("settingData", await apiObj.get({})); + } else { + browser.test.assertFalse(result, "set returns false for a no-op."); + browser.test.sendMessage("no-op set"); + } + }); + } + + // Set prefs to our initial values. + for (let pref in PREFS) { + Preferences.set(pref, PREFS[pref]); + } + + registerCleanupFunction(() => { + // Reset the prefs. + for (let pref in PREFS) { + Preferences.reset(pref); + } + }); + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["browserSettings"], + }, + useAddonManager: "temporary", + }); + + await promiseStartupManager(); + await extension.startup(); + + async function testSetting(setting, value, expected, expectedValue = value) { + extension.sendMessage("set", setting, value); + let data = await extension.awaitMessage("settingData"); + let dataChange = await extension.awaitMessage("onChange"); + equal(setting, dataChange.setting, "onChange fired"); + equal( + data.value, + dataChange.details.value, + "onChange fired with correct value" + ); + deepEqual( + data.value, + expectedValue, + `The ${setting} setting has the expected value.` + ); + equal( + data.levelOfControl, + "controlled_by_this_extension", + `The ${setting} setting has the expected levelOfControl.` + ); + for (let pref in expected) { + equal( + Preferences.get(pref), + expected[pref], + `${pref} set correctly for ${value}` + ); + } + } + + async function testNoOpSetting(setting, value, expected) { + extension.sendMessage("setNoOp", setting, value); + await extension.awaitMessage("no-op set"); + for (let pref in expected) { + equal( + Preferences.get(pref), + expected[pref], + `${pref} set correctly for ${value}` + ); + } + } + + await testSetting("cacheEnabled", false, { + "browser.cache.disk.enable": false, + "browser.cache.memory.enable": false, + }); + await testSetting("cacheEnabled", true, { + "browser.cache.disk.enable": true, + "browser.cache.memory.enable": true, + }); + + await testSetting("allowPopupsForUserEvents", false, { + "dom.popup_allowed_events": "", + }); + await testSetting("allowPopupsForUserEvents", true, { + "dom.popup_allowed_events": PREFS["dom.popup_allowed_events"], + }); + + for (let value of ["normal", "none", "once"]) { + await testSetting("imageAnimationBehavior", value, { + "image.animation_mode": value, + }); + } + + await testSetting("webNotificationsDisabled", true, { + "permissions.default.desktop-notification": PERM_DENY_ACTION, + }); + await testSetting("webNotificationsDisabled", false, { + // This pref is not defaulted on Android. + "permissions.default.desktop-notification": + AppConstants.MOZ_BUILD_APP !== "browser" + ? undefined + : PERM_UNKNOWN_ACTION, + }); + + // This setting is a no-op on Android. + if (AppConstants.platform === "android") { + await testNoOpSetting("contextMenuShowEvent", "mouseup", { + "ui.context_menus.after_mouseup": false, + }); + } else { + await testSetting("contextMenuShowEvent", "mouseup", { + "ui.context_menus.after_mouseup": true, + }); + } + + // "mousedown" is also a no-op on Windows. + if (["android", "win"].includes(AppConstants.platform)) { + await testNoOpSetting("contextMenuShowEvent", "mousedown", { + "ui.context_menus.after_mouseup": AppConstants.platform === "win", + }); + } else { + await testSetting("contextMenuShowEvent", "mousedown", { + "ui.context_menus.after_mouseup": false, + }); + } + + if (AppConstants.platform !== "android") { + await testSetting("closeTabsByDoubleClick", true, { + "browser.tabs.closeTabByDblclick": true, + }); + await testSetting("closeTabsByDoubleClick", false, { + "browser.tabs.closeTabByDblclick": false, + }); + } + + await testSetting("ftpProtocolEnabled", false, { + "network.ftp.enabled": false, + }); + await testSetting("ftpProtocolEnabled", true, { + "network.ftp.enabled": true, + }); + + await testSetting("newTabPosition", "afterCurrent", { + "browser.tabs.insertRelatedAfterCurrent": false, + "browser.tabs.insertAfterCurrent": true, + }); + await testSetting("newTabPosition", "atEnd", { + "browser.tabs.insertRelatedAfterCurrent": false, + "browser.tabs.insertAfterCurrent": false, + }); + await testSetting("newTabPosition", "relatedAfterCurrent", { + "browser.tabs.insertRelatedAfterCurrent": true, + "browser.tabs.insertAfterCurrent": false, + }); + + await testSetting("openBookmarksInNewTabs", true, { + "browser.tabs.loadBookmarksInTabs": true, + }); + await testSetting("openBookmarksInNewTabs", false, { + "browser.tabs.loadBookmarksInTabs": false, + }); + + await testSetting("openSearchResultsInNewTabs", true, { + "browser.search.openintab": true, + }); + await testSetting("openSearchResultsInNewTabs", false, { + "browser.search.openintab": false, + }); + + await testSetting("openUrlbarResultsInNewTabs", true, { + "browser.urlbar.openintab": true, + }); + await testSetting("openUrlbarResultsInNewTabs", false, { + "browser.urlbar.openintab": false, + }); + + await testSetting("overrideDocumentColors", "high-contrast-only", { + "browser.display.document_color_use": 0, + }); + await testSetting("overrideDocumentColors", "never", { + "browser.display.document_color_use": 1, + }); + await testSetting("overrideDocumentColors", "always", { + "browser.display.document_color_use": 2, + }); + + await testSetting("useDocumentFonts", false, { + "browser.display.use_document_fonts": 0, + }); + await testSetting("useDocumentFonts", true, { + "browser.display.use_document_fonts": 1, + }); + + await testSetting("zoomFullPage", true, { + "browser.zoom.full": true, + }); + await testSetting("zoomFullPage", false, { + "browser.zoom.full": false, + }); + + await testSetting("zoomSiteSpecific", true, { + "browser.zoom.siteSpecific": true, + }); + await testSetting("zoomSiteSpecific", false, { + "browser.zoom.siteSpecific": false, + }); + + await extension.unload(); + await promiseShutdownManager(); +}); + +add_task(async function test_bad_value() { + async function background() { + await browser.test.assertRejects( + browser.browserSettings.contextMenuShowEvent.set({ value: "bad" }), + /bad is not a valid value for contextMenuShowEvent/, + "contextMenuShowEvent.set rejects with an invalid value." + ); + + await browser.test.assertRejects( + browser.browserSettings.overrideDocumentColors.set({ value: 2 }), + /2 is not a valid value for overrideDocumentColors/, + "overrideDocumentColors.set rejects with an invalid value." + ); + + await browser.test.assertRejects( + browser.browserSettings.overrideDocumentColors.set({ value: "bad" }), + /bad is not a valid value for overrideDocumentColors/, + "overrideDocumentColors.set rejects with an invalid value." + ); + + browser.test.sendMessage("done"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["browserSettings"], + }, + }); + + await extension.startup(); + await extension.awaitMessage("done"); + await extension.unload(); +}); + +add_task(async function test_bad_value_android() { + if (AppConstants.platform !== "android") { + return; + } + + async function background() { + await browser.test.assertRejects( + browser.browserSettings.closeTabsByDoubleClick.set({ value: true }), + /android is not a supported platform for the closeTabsByDoubleClick setting/, + "closeTabsByDoubleClick.set rejects on Android." + ); + + await browser.test.assertRejects( + browser.browserSettings.closeTabsByDoubleClick.get({}), + /android is not a supported platform for the closeTabsByDoubleClick setting/, + "closeTabsByDoubleClick.get rejects on Android." + ); + + await browser.test.assertRejects( + browser.browserSettings.closeTabsByDoubleClick.clear({}), + /android is not a supported platform for the closeTabsByDoubleClick setting/, + "closeTabsByDoubleClick.clear rejects on Android." + ); + + browser.test.sendMessage("done"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["browserSettings"], + }, + }); + + await extension.startup(); + await extension.awaitMessage("done"); + await extension.unload(); +}); + +// Verifies settings remain after a staged update on restart. +add_task(async function delay_updates_settings_after_restart() { + let server = AddonTestUtils.createHttpServer({ hosts: ["example.com"] }); + AddonTestUtils.registerJSON(server, "/test_update.json", { + addons: { + "test_settings_staged_restart_webext@tests.mozilla.org": { + updates: [ + { + version: "2.0", + update_link: + "http://example.com/addons/test_settings_staged_restart_v2.xpi", + }, + ], + }, + }, + }); + const update_xpi = AddonTestUtils.createTempXPIFile({ + "manifest.json": { + manifest_version: 2, + name: "Delay Upgrade", + version: "2.0", + applications: { + gecko: { id: SETTINGS_ID }, + }, + permissions: ["browserSettings"], + }, + }); + server.registerFile( + `/addons/test_settings_staged_restart_v2.xpi`, + update_xpi + ); + + await AddonTestUtils.promiseStartupManager(); + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "permanent", + manifest: { + version: "1.0", + applications: { + gecko: { + id: SETTINGS_ID, + update_url: `http://example.com/test_update.json`, + }, + }, + permissions: ["browserSettings"], + }, + background() { + browser.runtime.onUpdateAvailable.addListener(async details => { + if (details) { + await browser.browserSettings.webNotificationsDisabled.set({ + value: true, + }); + if (details.version) { + // This should be the version of the pending update. + browser.test.assertEq("2.0", details.version, "correct version"); + browser.test.notifyPass("delay"); + } + } else { + browser.test.fail("no details object passed"); + } + }); + browser.test.sendMessage("ready"); + }, + }); + + await Promise.all([extension.startup(), extension.awaitMessage("ready")]); + + let prefname = "permissions.default.desktop-notification"; + let val = Services.prefs.getIntPref(prefname); + Assert.notEqual(val, 2, "webNotificationsDisabled pref not set"); + + let update = await AddonTestUtils.promiseFindAddonUpdates(extension.addon); + let install = update.updateAvailable; + Assert.ok(install, `install is available ${update.error}`); + + await AddonTestUtils.promiseCompleteAllInstalls([install]); + + Assert.equal(install.state, AddonManager.STATE_POSTPONED); + await extension.awaitFinish("delay"); + + // restarting allows upgrade to proceed + await AddonTestUtils.promiseRestartManager(); + + await extension.awaitStartup(); + + // If an update is not handled correctly we would fail here. Bug 1639705. + val = Services.prefs.getIntPref(prefname); + Assert.equal(val, 2, "webNotificationsDisabled pref set"); + + await extension.unload(); + await AddonTestUtils.promiseShutdownManager(); + + val = Services.prefs.getIntPref(prefname); + Assert.notEqual(val, 2, "webNotificationsDisabled pref not set"); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_browserSettings_homepage.js b/toolkit/components/extensions/test/xpcshell/test_ext_browserSettings_homepage.js new file mode 100644 index 0000000000..8d1d16c743 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_browserSettings_homepage.js @@ -0,0 +1,36 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(async function test_homepage_get_without_set() { + async function background() { + let homepage = await browser.browserSettings.homepageOverride.get({}); + browser.test.sendMessage("homepage", homepage); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["browserSettings"], + }, + }); + + let defaultHomepage = Services.prefs.getStringPref( + "browser.startup.homepage" + ); + + await extension.startup(); + let homepage = await extension.awaitMessage("homepage"); + equal( + homepage.value, + defaultHomepage, + "The homepageOverride setting has the expected value." + ); + equal( + homepage.levelOfControl, + "not_controllable", + "The homepageOverride setting has the expected levelOfControl." + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_browsingData.js b/toolkit/components/extensions/test/xpcshell/test_ext_browsingData.js new file mode 100644 index 0000000000..1df5e60478 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_browsingData.js @@ -0,0 +1,48 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(async function testInvalidArguments() { + async function background() { + const UNSUPPORTED_DATA_TYPES = ["appcache", "fileSystems", "webSQL"]; + + await browser.test.assertRejects( + browser.browsingData.remove( + { originTypes: { protectedWeb: true } }, + { cookies: true } + ), + "Firefox does not support protectedWeb or extension as originTypes.", + "Expected error received when using protectedWeb originType." + ); + + await browser.test.assertRejects( + browser.browsingData.removeCookies({ originTypes: { extension: true } }), + "Firefox does not support protectedWeb or extension as originTypes.", + "Expected error received when using extension originType." + ); + + for (let dataType of UNSUPPORTED_DATA_TYPES) { + let dataTypes = {}; + dataTypes[dataType] = true; + browser.test.assertThrows( + () => browser.browsingData.remove({}, dataTypes), + /Type error for parameter dataToRemove/, + `Expected error received when using ${dataType} dataType.` + ); + } + + browser.test.notifyPass("invalidArguments"); + } + + let extensionData = { + background: background, + manifest: { + permissions: ["browsingData"], + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + await extension.awaitFinish("invalidArguments"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_browsingData_cookies_cache.js b/toolkit/components/extensions/test/xpcshell/test_ext_browsingData_cookies_cache.js new file mode 100644 index 0000000000..612f2dd0f3 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_browsingData_cookies_cache.js @@ -0,0 +1,456 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +"use strict"; + +const { SiteDataTestUtils } = ChromeUtils.import( + "resource://testing-common/SiteDataTestUtils.jsm" +); + +const COOKIE = { + host: "example.com", + name: "test_cookie", + path: "/", +}; +const COOKIE_NET = { + host: "example.net", + name: "test_cookie", + path: "/", +}; +const COOKIE_ORG = { + host: "example.org", + name: "test_cookie", + path: "/", +}; +let since, oldCookie; + +function addCookie(cookie) { + Services.cookies.add( + cookie.host, + cookie.path, + cookie.name, + "test", + false, + false, + false, + Date.now() / 1000 + 10000, + {}, + Ci.nsICookie.SAMESITE_NONE, + Ci.nsICookie.SCHEME_HTTPS + ); + ok( + Services.cookies.cookieExists(cookie.host, cookie.path, cookie.name, {}), + `Cookie ${cookie.name} was created.` + ); +} + +async function setUpCookies() { + Services.cookies.removeAll(); + + // Add a cookie which will end up with an older creationTime. + oldCookie = Object.assign({}, COOKIE, { name: Date.now() }); + addCookie(oldCookie); + await new Promise(resolve => setTimeout(resolve, 10)); + since = Date.now(); + await new Promise(resolve => setTimeout(resolve, 10)); + + // Add a cookie which will end up with a more recent creationTime. + addCookie(COOKIE); + + // Add cookies for different domains. + addCookie(COOKIE_NET); + addCookie(COOKIE_ORG); +} + +async function setUpCache() { + Services.cache2.clear(); + + // Add cache entries for different domains. + for (const domain of ["example.net", "example.org", "example.com"]) { + await SiteDataTestUtils.addCacheEntry(`http://${domain}/`, "disk"); + await SiteDataTestUtils.addCacheEntry(`http://${domain}/`, "memory"); + } +} + +function hasCacheEntry(domain) { + const disk = SiteDataTestUtils.hasCacheEntry(`http://${domain}/`, "disk"); + const memory = SiteDataTestUtils.hasCacheEntry(`http://${domain}/`, "memory"); + + equal( + disk, + memory, + `For ${domain} either either both or neither caches need to exists.` + ); + return disk; +} + +add_task(async function testCache() { + function background() { + browser.test.onMessage.addListener(async msg => { + if (msg == "removeCache") { + await browser.browsingData.removeCache({}); + } else { + await browser.browsingData.remove({}, { cache: true }); + } + browser.test.sendMessage("cacheRemoved"); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["browsingData"], + }, + }); + + async function testRemovalMethod(method) { + await setUpCache(); + + extension.sendMessage(method); + await extension.awaitMessage("cacheRemoved"); + + ok(!hasCacheEntry("example.net"), "example.net cache was removed"); + ok(!hasCacheEntry("example.org"), "example.org cache was removed"); + ok(!hasCacheEntry("example.com"), "example.com cache was removed"); + } + + await extension.startup(); + + await testRemovalMethod("removeCache"); + await testRemovalMethod("remove"); + + await extension.unload(); +}); + +add_task(async function testCookies() { + // Above in setUpCookies we create an 'old' cookies, wait 10ms, then log a timestamp. + // Here we ask the browser to delete all cookies after the timestamp, with the intention + // that the 'old' cookie is not removed. The issue arises when the timer precision is + // low enough such that the timestamp that gets logged is the same as the 'old' cookie. + // We hardcode a precision value to ensure that there is time between the 'old' cookie + // and the timestamp generation. + Services.prefs.setBoolPref("privacy.reduceTimerPrecision", true); + Services.prefs.setIntPref( + "privacy.resistFingerprinting.reduceTimerPrecision.microseconds", + 2000 + ); + + registerCleanupFunction(function() { + Services.prefs.clearUserPref("privacy.reduceTimerPrecision"); + Services.prefs.clearUserPref( + "privacy.resistFingerprinting.reduceTimerPrecision.microseconds" + ); + }); + + function background() { + browser.test.onMessage.addListener(async (msg, options) => { + if (msg == "removeCookies") { + await browser.browsingData.removeCookies(options); + } else { + await browser.browsingData.remove(options, { cookies: true }); + } + browser.test.sendMessage("cookiesRemoved"); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["browsingData"], + }, + }); + + async function testRemovalMethod(method) { + // Clear cookies with a recent since value. + await setUpCookies(); + extension.sendMessage(method, { since }); + await extension.awaitMessage("cookiesRemoved"); + + ok( + Services.cookies.cookieExists( + oldCookie.host, + oldCookie.path, + oldCookie.name, + {} + ), + "Old cookie was not removed." + ); + ok( + !Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), + "Recent cookie was removed." + ); + + // Clear cookies with an old since value. + await setUpCookies(); + addCookie(COOKIE); + extension.sendMessage(method, { since: since - 100000 }); + await extension.awaitMessage("cookiesRemoved"); + + ok( + !Services.cookies.cookieExists( + oldCookie.host, + oldCookie.path, + oldCookie.name, + {} + ), + "Old cookie was removed." + ); + ok( + !Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), + "Recent cookie was removed." + ); + + // Clear cookies with no since value and valid originTypes. + await setUpCookies(); + extension.sendMessage(method, { + originTypes: { unprotectedWeb: true, protectedWeb: false }, + }); + await extension.awaitMessage("cookiesRemoved"); + + ok( + !Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), + `Cookie ${COOKIE.name} was removed.` + ); + ok( + !Services.cookies.cookieExists( + oldCookie.host, + oldCookie.path, + oldCookie.name, + {} + ), + `Cookie ${oldCookie.name} was removed.` + ); + } + + await extension.startup(); + + await testRemovalMethod("removeCookies"); + await testRemovalMethod("remove"); + + await extension.unload(); +}); + +add_task(async function testCacheAndCookies() { + function background() { + browser.test.onMessage.addListener(async options => { + await browser.browsingData.remove(options, { + cache: true, + cookies: true, + }); + browser.test.sendMessage("cacheAndCookiesRemoved"); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["browsingData"], + }, + }); + + await extension.startup(); + + // Clear cache and cookies with a recent since value. + await setUpCookies(); + await setUpCache(); + extension.sendMessage({ since }); + await extension.awaitMessage("cacheAndCookiesRemoved"); + + ok( + Services.cookies.cookieExists( + oldCookie.host, + oldCookie.path, + oldCookie.name, + {} + ), + "Old cookie was not removed." + ); + ok( + !Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), + "Recent cookie was removed." + ); + + // Cache does not support |since| and deletes everything! + ok(!hasCacheEntry("example.net"), "example.net cache was removed"); + ok(!hasCacheEntry("example.org"), "example.org cache was removed"); + ok(!hasCacheEntry("example.com"), "example.com cache was removed"); + + // Clear cache and cookies with an old since value. + await setUpCookies(); + await setUpCache(); + extension.sendMessage({ since: since - 100000 }); + await extension.awaitMessage("cacheAndCookiesRemoved"); + + // Cache does not support |since| and deletes everything! + ok(!hasCacheEntry("example.net"), "example.net cache was removed"); + ok(!hasCacheEntry("example.org"), "example.org cache was removed"); + ok(!hasCacheEntry("example.com"), "example.com cache was removed"); + + ok( + !Services.cookies.cookieExists( + oldCookie.host, + oldCookie.path, + oldCookie.name, + {} + ), + "Old cookie was removed." + ); + ok( + !Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), + "Recent cookie was removed." + ); + + // Clear cache and cookies with hostnames value. + await setUpCookies(); + await setUpCache(); + extension.sendMessage({ + hostnames: ["example.net", "example.org", "unknown.com"], + }); + await extension.awaitMessage("cacheAndCookiesRemoved"); + + ok( + Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), + `Cookie ${COOKIE.name} was not removed.` + ); + ok( + !Services.cookies.cookieExists( + COOKIE_NET.host, + COOKIE_NET.path, + COOKIE_NET.name, + {} + ), + `Cookie ${COOKIE_NET.name} was removed.` + ); + ok( + !Services.cookies.cookieExists( + COOKIE_ORG.host, + COOKIE_ORG.path, + COOKIE_ORG.name, + {} + ), + `Cookie ${COOKIE_ORG.name} was removed.` + ); + + ok(!hasCacheEntry("example.net"), "example.net cache was removed"); + ok(!hasCacheEntry("example.org"), "example.org cache was removed"); + ok(hasCacheEntry("example.com"), "example.com cache was not removed"); + + // Clear cache and cookies with (empty) hostnames value. + await setUpCookies(); + await setUpCache(); + extension.sendMessage({ hostnames: [] }); + await extension.awaitMessage("cacheAndCookiesRemoved"); + + ok( + Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), + `Cookie ${COOKIE.name} was not removed.` + ); + ok( + Services.cookies.cookieExists( + COOKIE_NET.host, + COOKIE_NET.path, + COOKIE_NET.name, + {} + ), + `Cookie ${COOKIE_NET.name} was not removed.` + ); + ok( + Services.cookies.cookieExists( + COOKIE_ORG.host, + COOKIE_ORG.path, + COOKIE_ORG.name, + {} + ), + `Cookie ${COOKIE_ORG.name} was not removed.` + ); + + ok(hasCacheEntry("example.net"), "example.net cache was not removed"); + ok(hasCacheEntry("example.org"), "example.org cache was not removed"); + ok(hasCacheEntry("example.com"), "example.com cache was not removed"); + + // Clear cache and cookies with both hostnames and since values. + await setUpCache(); + await setUpCookies(); + extension.sendMessage({ hostnames: ["example.com"], since }); + await extension.awaitMessage("cacheAndCookiesRemoved"); + + ok( + Services.cookies.cookieExists( + oldCookie.host, + oldCookie.path, + oldCookie.name, + {} + ), + "Old cookie was not removed." + ); + ok( + !Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), + "Recent cookie was removed." + ); + ok( + Services.cookies.cookieExists( + COOKIE_NET.host, + COOKIE_NET.path, + COOKIE_NET.name, + {} + ), + "Cookie with different hostname was not removed" + ); + ok( + Services.cookies.cookieExists( + COOKIE_ORG.host, + COOKIE_ORG.path, + COOKIE_ORG.name, + {} + ), + "Cookie with different hostname was not removed" + ); + + ok(hasCacheEntry("example.net"), "example.net cache was not removed"); + ok(hasCacheEntry("example.org"), "example.org cache was not removed"); + ok(!hasCacheEntry("example.com"), "example.com cache was removed"); + + // Clear cache and cookies with no since or hostnames value. + await setUpCache(); + await setUpCookies(); + extension.sendMessage({}); + await extension.awaitMessage("cacheAndCookiesRemoved"); + + ok( + !Services.cookies.cookieExists(COOKIE.host, COOKIE.path, COOKIE.name, {}), + `Cookie ${COOKIE.name} was removed.` + ); + ok( + !Services.cookies.cookieExists( + oldCookie.host, + oldCookie.path, + oldCookie.name, + {} + ), + `Cookie ${oldCookie.name} was removed.` + ); + ok( + !Services.cookies.cookieExists( + COOKIE_NET.host, + COOKIE_NET.path, + COOKIE_NET.name, + {} + ), + `Cookie ${COOKIE_NET.name} was removed.` + ); + ok( + !Services.cookies.cookieExists( + COOKIE_ORG.host, + COOKIE_ORG.path, + COOKIE_ORG.name, + {} + ), + `Cookie ${COOKIE_ORG.name} was removed.` + ); + + ok(!hasCacheEntry("example.net"), "example.net cache was removed"); + ok(!hasCacheEntry("example.org"), "example.org cache was removed"); + ok(!hasCacheEntry("example.com"), "example.com cache was removed"); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_browsingData_cookies_cookieStoreId.js b/toolkit/components/extensions/test/xpcshell/test_ext_browsingData_cookies_cookieStoreId.js new file mode 100644 index 0000000000..d3d066efd2 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_browsingData_cookies_cookieStoreId.js @@ -0,0 +1,192 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +"use strict"; + +// "Normal" cookie +const COOKIE_NORMAL = { + host: "example.com", + name: "test_cookie", + path: "/", + originAttributes: {}, +}; +// Private browsing cookie +const COOKIE_PRIVATE = { + host: "example.net", + name: "test_cookie", + path: "/", + originAttributes: { + privateBrowsingId: 1, + }, +}; +// "firefox-container-1" cookie +const COOKIE_CONTAINER = { + host: "example.org", + name: "test_cookie", + path: "/", + originAttributes: { + userContextId: 1, + }, +}; + +function cookieExists(cookie) { + return Services.cookies.cookieExists( + cookie.host, + cookie.path, + cookie.name, + cookie.originAttributes + ); +} + +function addCookie(cookie) { + const THE_FUTURE = Date.now() + 5 * 60; + + Services.cookies.add( + cookie.host, + cookie.path, + cookie.name, + "test", + false, + false, + false, + THE_FUTURE, + cookie.originAttributes, + Ci.nsICookie.SAMESITE_NONE, + Ci.nsICookie.SCHEME_HTTPS + ); + + ok(cookieExists(cookie), `Cookie ${cookie.name} was created.`); +} + +async function setUpCookies() { + Services.cookies.removeAll(); + + addCookie(COOKIE_NORMAL); + addCookie(COOKIE_PRIVATE); + addCookie(COOKIE_CONTAINER); +} + +add_task(async function testCookies() { + Services.prefs.setBoolPref("privacy.userContext.enabled", true); + + function background() { + browser.test.onMessage.addListener(async (msg, options) => { + if (msg == "removeCookies") { + await browser.browsingData.removeCookies(options); + } else { + await browser.browsingData.remove(options, { cookies: true }); + } + browser.test.sendMessage("cookiesRemoved"); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["browsingData"], + }, + }); + + async function testRemovalMethod(method) { + // Clear only "normal"/default cookies. + await setUpCookies(); + + extension.sendMessage(method, { cookieStoreId: "firefox-default" }); + await extension.awaitMessage("cookiesRemoved"); + + ok(!cookieExists(COOKIE_NORMAL), "Normal cookie was removed"); + ok(cookieExists(COOKIE_PRIVATE), "Private cookie was not removed"); + ok(cookieExists(COOKIE_CONTAINER), "Container cookie was not removed"); + + // Clear container cookie + await setUpCookies(); + + extension.sendMessage(method, { cookieStoreId: "firefox-container-1" }); + await extension.awaitMessage("cookiesRemoved"); + + ok(cookieExists(COOKIE_NORMAL), "Normal cookie was not removed"); + ok(cookieExists(COOKIE_PRIVATE), "Private cookie was not removed"); + ok(!cookieExists(COOKIE_CONTAINER), "Container cookie was removed"); + + // Clear private cookie + await setUpCookies(); + + extension.sendMessage(method, { cookieStoreId: "firefox-private" }); + await extension.awaitMessage("cookiesRemoved"); + + ok(cookieExists(COOKIE_NORMAL), "Normal cookie was not removed"); + ok(!cookieExists(COOKIE_PRIVATE), "Private cookie was removed"); + ok(cookieExists(COOKIE_CONTAINER), "Container cookie was not removed"); + + // Clear container cookie with correct hostname + await setUpCookies(); + + extension.sendMessage(method, { + cookieStoreId: "firefox-container-1", + hostnames: ["example.org"], + }); + await extension.awaitMessage("cookiesRemoved"); + + ok(cookieExists(COOKIE_NORMAL), "Normal cookie was not removed"); + ok(cookieExists(COOKIE_PRIVATE), "Private cookie was not removed"); + ok(!cookieExists(COOKIE_CONTAINER), "Container cookie was removed"); + + // Clear container cookie with incorrect hostname; nothing is removed + await setUpCookies(); + + extension.sendMessage(method, { + cookieStoreId: "firefox-container-1", + hostnames: ["example.com"], + }); + await extension.awaitMessage("cookiesRemoved"); + + ok(cookieExists(COOKIE_NORMAL), "Normal cookie was not removed"); + ok(cookieExists(COOKIE_PRIVATE), "Private cookie was not removed"); + ok(cookieExists(COOKIE_CONTAINER), "Container cookie was not removed"); + + // Clear private cookie with correct hostname + await setUpCookies(); + + extension.sendMessage(method, { + cookieStoreId: "firefox-private", + hostnames: ["example.net"], + }); + await extension.awaitMessage("cookiesRemoved"); + + ok(cookieExists(COOKIE_NORMAL), "Normal cookie was not removed"); + ok(!cookieExists(COOKIE_PRIVATE), "Private cookie was removed"); + ok(cookieExists(COOKIE_CONTAINER), "Container cookie was not removed"); + + // Clear private cookie with incorrect hostname; nothing is removed + await setUpCookies(); + + extension.sendMessage(method, { + cookieStoreId: "firefox-private", + hostnames: ["example.com"], + }); + await extension.awaitMessage("cookiesRemoved"); + + ok(cookieExists(COOKIE_NORMAL), "Normal cookie was not removed"); + ok(cookieExists(COOKIE_PRIVATE), "Private cookie was not removed"); + ok(cookieExists(COOKIE_CONTAINER), "Container cookie was not removed"); + + // Clear private cookie by hostname + await setUpCookies(); + + extension.sendMessage(method, { + hostnames: ["example.net"], + }); + await extension.awaitMessage("cookiesRemoved"); + + ok(cookieExists(COOKIE_NORMAL), "Normal cookie was not removed"); + ok(!cookieExists(COOKIE_PRIVATE), "Private cookie was removed"); + ok(cookieExists(COOKIE_CONTAINER), "Container cookie was not removed"); + } + + await extension.startup(); + + await testRemovalMethod("removeCookies"); + await testRemovalMethod("remove"); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_captivePortal.js b/toolkit/components/extensions/test/xpcshell/test_ext_captivePortal.js new file mode 100644 index 0000000000..45c6a122fd --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_captivePortal.js @@ -0,0 +1,109 @@ +"use strict"; + +/** + * This duplicates the test from netwerk/test/unit/test_captive_portal_service.js + * however using an extension to gather the captive portal information. + */ + +const PREF_CAPTIVE_ENABLED = "network.captive-portal-service.enabled"; +const PREF_CAPTIVE_TESTMODE = "network.captive-portal-service.testMode"; +const PREF_CAPTIVE_MINTIME = "network.captive-portal-service.minInterval"; +const PREF_CAPTIVE_ENDPOINT = "captivedetect.canonicalURL"; +const PREF_DNS_NATIVE_IS_LOCALHOST = "network.dns.native-is-localhost"; + +const SUCCESS_STRING = "success\n"; +let cpResponse = SUCCESS_STRING; + +const httpserver = createHttpServer(); +httpserver.registerPathHandler("/captive.txt", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/plain"); + response.write(cpResponse); +}); + +registerCleanupFunction(() => { + Services.prefs.clearUserPref(PREF_CAPTIVE_ENABLED); + Services.prefs.clearUserPref(PREF_CAPTIVE_TESTMODE); + Services.prefs.clearUserPref(PREF_CAPTIVE_ENDPOINT); + Services.prefs.clearUserPref(PREF_CAPTIVE_MINTIME); + Services.prefs.clearUserPref(PREF_DNS_NATIVE_IS_LOCALHOST); +}); + +add_task(function setup() { + Services.prefs.setCharPref( + PREF_CAPTIVE_ENDPOINT, + `http://localhost:${httpserver.identity.primaryPort}/captive.txt` + ); + Services.prefs.setBoolPref(PREF_CAPTIVE_TESTMODE, true); + Services.prefs.setIntPref(PREF_CAPTIVE_MINTIME, 0); + Services.prefs.setBoolPref(PREF_DNS_NATIVE_IS_LOCALHOST, true); +}); + +add_task(async function test_captivePortal_basic() { + let cps = Cc["@mozilla.org/network/captive-portal-service;1"].getService( + Ci.nsICaptivePortalService + ); + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["captivePortal"], + }, + isPrivileged: true, + async background() { + browser.captivePortal.onConnectivityAvailable.addListener(details => { + browser.test.log( + `onConnectivityAvailable received ${JSON.stringify(details)}` + ); + browser.test.sendMessage("connectivity", details); + }); + + browser.captivePortal.onStateChanged.addListener(details => { + browser.test.log(`onStateChanged received ${JSON.stringify(details)}`); + browser.test.sendMessage("state", details); + }); + + browser.test.onMessage.addListener(async msg => { + if (msg == "getstate") { + browser.test.sendMessage( + "getstate", + await browser.captivePortal.getState() + ); + } + }); + browser.test.assertEq( + "unknown", + await browser.captivePortal.getState(), + "initial state unknown" + ); + }, + }); + await extension.startup(); + + // The captive portal service is started by nsIOService when the pref becomes true, so we + // toggle the pref. We cannot set to false before the extension loads above. + Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false); + Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true); + + let details = await extension.awaitMessage("connectivity"); + equal(details.status, "clear", "initial connectivity"); + extension.sendMessage("getstate"); + details = await extension.awaitMessage("getstate"); + equal(details, "not_captive", "initial state"); + + info("REFRESH to other"); + cpResponse = "other"; + cps.recheckCaptivePortal(); + details = await extension.awaitMessage("state"); + equal(details.state, "locked_portal", "state in portal"); + + info("REFRESH to success"); + cpResponse = SUCCESS_STRING; + cps.recheckCaptivePortal(); + details = await extension.awaitMessage("connectivity"); + equal(details.status, "captive", "final connectivity"); + + details = await extension.awaitMessage("state"); + equal(details.state, "unlocked_portal", "state after unlocking portal"); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_captivePortal_url.js b/toolkit/components/extensions/test/xpcshell/test_ext_captivePortal_url.js new file mode 100644 index 0000000000..7bd83b0572 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_captivePortal_url.js @@ -0,0 +1,53 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +add_task(async function test_url_get_without_set() { + async function background() { + browser.captivePortal.canonicalURL.onChange.addListener(details => { + browser.test.sendMessage("url", details); + }); + let url = await browser.captivePortal.canonicalURL.get({}); + browser.test.sendMessage("url", url); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + permissions: ["captivePortal"], + }, + }); + + let defaultURL = Services.prefs.getStringPref("captivedetect.canonicalURL"); + + await extension.startup(); + let url = await extension.awaitMessage("url"); + equal( + url.value, + defaultURL, + "The canonicalURL setting has the expected value." + ); + equal( + url.levelOfControl, + "not_controllable", + "The canonicalURL setting has the expected levelOfControl." + ); + + Services.prefs.setStringPref( + "captivedetect.canonicalURL", + "http://example.com" + ); + url = await extension.awaitMessage("url"); + equal( + url.value, + "http://example.com", + "The canonicalURL setting has the expected value." + ); + equal( + url.levelOfControl, + "not_controllable", + "The canonicalURL setting has the expected levelOfControl." + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentScripts_register.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentScripts_register.js new file mode 100644 index 0000000000..71174716fd --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentScripts_register.js @@ -0,0 +1,591 @@ +"use strict"; + +const { createAppInfo } = AddonTestUtils; + +AddonTestUtils.init(this); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "49"); + +const server = createHttpServer(); +server.registerDirectory("/data/", do_get_file("data")); + +const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`; + +function check_applied_styles() { + const urlElStyle = getComputedStyle( + document.querySelector("#registered-extension-url-style") + ); + const blobElStyle = getComputedStyle( + document.querySelector("#registered-extension-text-style") + ); + + browser.test.sendMessage("registered-styles-results", { + registeredExtensionUrlStyleBG: urlElStyle["background-color"], + registeredExtensionBlobStyleBG: blobElStyle["background-color"], + }); +} + +add_task(async function test_contentscripts_register_css() { + async function background() { + let cssCode = ` + #registered-extension-text-style { + background-color: blue; + } + `; + + const matches = ["http://localhost/*/file_sample_registered_styles.html"]; + + browser.test.assertThrows( + () => { + browser.contentScripts.register({ + matches, + unknownParam: "unexpected property", + }); + }, + /Unexpected property "unknownParam"/, + "contentScripts.register throws on unexpected properties" + ); + + let fileScript = await browser.contentScripts.register({ + css: [{ file: "registered_ext_style.css" }], + matches, + runAt: "document_start", + }); + + let textScript = await browser.contentScripts.register({ + css: [{ code: cssCode }], + matches, + runAt: "document_start", + }); + + browser.test.onMessage.addListener(async msg => { + switch (msg) { + case "unregister-text": + await textScript.unregister().catch(err => { + browser.test.fail( + `Unexpected exception while unregistering text style: ${err}` + ); + }); + + await browser.test.assertRejects( + textScript.unregister(), + /Content script already unregistered/, + "Got the expected rejection on calling script.unregister() multiple times" + ); + + browser.test.sendMessage("unregister-text:done"); + break; + case "unregister-file": + await fileScript.unregister().catch(err => { + browser.test.fail( + `Unexpected exception while unregistering url style: ${err}` + ); + }); + + await browser.test.assertRejects( + fileScript.unregister(), + /Content script already unregistered/, + "Got the expected rejection on calling script.unregister() multiple times" + ); + + browser.test.sendMessage("unregister-file:done"); + break; + default: + browser.test.fail(`Unexpected test message received: ${msg}`); + } + }); + + browser.test.sendMessage("background_ready"); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: [ + "http://localhost/*/file_sample_registered_styles.html", + "", + ], + content_scripts: [ + { + matches: ["http://localhost/*/file_sample_registered_styles.html"], + run_at: "document_idle", + js: ["check_applied_styles.js"], + }, + ], + }, + background, + + files: { + "check_applied_styles.js": check_applied_styles, + "registered_ext_style.css": ` + #registered-extension-url-style { + background-color: red; + } + `, + }, + }); + + await extension.startup(); + + await extension.awaitMessage("background_ready"); + + // Ensure that a content page running in a content process and which has been + // started after the content scripts has been registered, it still receives + // and registers the expected content scripts. + let contentPage = await ExtensionTestUtils.loadContentPage(`about:blank`); + + await contentPage.loadURL(`${BASE_URL}/file_sample_registered_styles.html`); + + const registeredStylesResults = await extension.awaitMessage( + "registered-styles-results" + ); + + equal( + registeredStylesResults.registeredExtensionUrlStyleBG, + "rgb(255, 0, 0)", + "The expected style has been applied from the registered extension url style" + ); + equal( + registeredStylesResults.registeredExtensionBlobStyleBG, + "rgb(0, 0, 255)", + "The expected style has been applied from the registered extension blob style" + ); + + extension.sendMessage("unregister-file"); + await extension.awaitMessage("unregister-file:done"); + + await contentPage.loadURL(`${BASE_URL}/file_sample_registered_styles.html`); + + const unregisteredURLStylesResults = await extension.awaitMessage( + "registered-styles-results" + ); + + equal( + unregisteredURLStylesResults.registeredExtensionUrlStyleBG, + "rgba(0, 0, 0, 0)", + "The expected style has been applied once extension url style has been unregistered" + ); + equal( + unregisteredURLStylesResults.registeredExtensionBlobStyleBG, + "rgb(0, 0, 255)", + "The expected style has been applied from the registered extension blob style" + ); + + extension.sendMessage("unregister-text"); + await extension.awaitMessage("unregister-text:done"); + + await contentPage.loadURL(`${BASE_URL}/file_sample_registered_styles.html`); + + const unregisteredBlobStylesResults = await extension.awaitMessage( + "registered-styles-results" + ); + + equal( + unregisteredBlobStylesResults.registeredExtensionUrlStyleBG, + "rgba(0, 0, 0, 0)", + "The expected style has been applied once extension url style has been unregistered" + ); + equal( + unregisteredBlobStylesResults.registeredExtensionBlobStyleBG, + "rgba(0, 0, 0, 0)", + "The expected style has been applied once extension blob style has been unregistered" + ); + + await contentPage.close(); + await extension.unload(); +}); + +add_task(async function test_contentscripts_unregister_on_context_unload() { + async function background() { + const frame = document.createElement("iframe"); + frame.setAttribute("src", "/background-frame.html"); + + document.body.appendChild(frame); + + browser.test.onMessage.addListener(msg => { + switch (msg) { + case "unload-frame": + frame.remove(); + browser.test.sendMessage("unload-frame:done"); + break; + default: + browser.test.fail(`Unexpected test message received: ${msg}`); + } + }); + + browser.test.sendMessage("background_ready"); + } + + async function background_frame() { + await browser.contentScripts.register({ + css: [{ file: "registered_ext_style.css" }], + matches: ["http://localhost/*/file_sample_registered_styles.html"], + runAt: "document_start", + }); + + browser.test.sendMessage("background_frame_ready"); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["http://localhost/*/file_sample_registered_styles.html"], + content_scripts: [ + { + matches: ["http://localhost/*/file_sample_registered_styles.html"], + run_at: "document_idle", + js: ["check_applied_styles.js"], + }, + ], + }, + background, + + files: { + "background-frame.html": ` + + + + + + + + `, + "background-frame.js": background_frame, + "check_applied_styles.js": check_applied_styles, + "registered_ext_style.css": ` + #registered-extension-url-style { + background-color: red; + } + `, + }, + }); + + await extension.startup(); + + await extension.awaitMessage("background_ready"); + + // Wait the background frame to have been loaded and its script + // executed. + await extension.awaitMessage("background_frame_ready"); + + // Ensure that a content page running in a content process and which has been + // started after the content scripts has been registered, it still receives + // and registers the expected content scripts. + let contentPage = await ExtensionTestUtils.loadContentPage(`about:blank`); + + await contentPage.loadURL(`${BASE_URL}/file_sample_registered_styles.html`); + + const registeredStylesResults = await extension.awaitMessage( + "registered-styles-results" + ); + + equal( + registeredStylesResults.registeredExtensionUrlStyleBG, + "rgb(255, 0, 0)", + "The expected style has been applied from the registered extension url style" + ); + + extension.sendMessage("unload-frame"); + await extension.awaitMessage("unload-frame:done"); + + await contentPage.loadURL(`${BASE_URL}/file_sample_registered_styles.html`); + + const unregisteredURLStylesResults = await extension.awaitMessage( + "registered-styles-results" + ); + + equal( + unregisteredURLStylesResults.registeredExtensionUrlStyleBG, + "rgba(0, 0, 0, 0)", + "The expected style has been applied once extension url style has been unregistered" + ); + + await contentPage.close(); + await extension.unload(); +}); + +add_task(async function test_contentscripts_register_js() { + async function background() { + browser.runtime.onMessage.addListener( + ([msg, expectedStates, readyState], sender) => { + if (msg == "chrome-namespace-ok") { + browser.test.sendMessage(msg); + return; + } + + browser.test.assertEq("script-run", msg, "message type is correct"); + browser.test.assertTrue( + expectedStates.includes(readyState), + `readyState "${readyState}" is one of [${expectedStates}]` + ); + browser.test.sendMessage("script-run-" + expectedStates[0]); + } + ); + + // Raise an exception when the content script cannot be registered + // because the extension has no permission to access the specified origin. + + await browser.test.assertRejects( + browser.contentScripts.register({ + matches: ["http://*/*"], + js: [ + { + code: + 'browser.test.fail("content script with wrong matches should not run")', + }, + ], + }), + /Permission denied to register a content script for/, + "The reject contains the expected error message" + ); + + // Register a content script from a JS code string. + + function textScriptCodeStart() { + browser.runtime.sendMessage([ + "script-run", + ["loading"], + document.readyState, + ]); + } + function textScriptCodeEnd() { + browser.runtime.sendMessage([ + "script-run", + ["interactive", "complete"], + document.readyState, + ]); + } + function textScriptCodeIdle() { + browser.runtime.sendMessage([ + "script-run", + ["complete"], + document.readyState, + ]); + } + + // Register content scripts from both extension URLs and plain JS code strings. + + const content_scripts = [ + // Plain JS code strings. + { + matches: ["http://localhost/*/file_sample.html"], + js: [{ code: `(${textScriptCodeStart})()` }], + runAt: "document_start", + }, + { + matches: ["http://localhost/*/file_sample.html"], + js: [{ code: `(${textScriptCodeEnd})()` }], + runAt: "document_end", + }, + { + matches: ["http://localhost/*/file_sample.html"], + js: [{ code: `(${textScriptCodeIdle})()` }], + runAt: "document_idle", + }, + // Extension URLs. + { + matches: ["http://localhost/*/file_sample.html"], + js: [{ file: "content_script_start.js" }], + runAt: "document_start", + }, + { + matches: ["http://localhost/*/file_sample.html"], + js: [{ file: "content_script_end.js" }], + runAt: "document_end", + }, + { + matches: ["http://localhost/*/file_sample.html"], + js: [{ file: "content_script_idle.js" }], + runAt: "document_idle", + }, + { + matches: ["http://localhost/*/file_sample.html"], + js: [{ file: "content_script.js" }], + // "runAt" is not specified here to ensure that it defaults to document_idle when missing. + }, + ]; + + const expectedAPIs = ["unregister"]; + + for (const scriptOptions of content_scripts) { + const script = await browser.contentScripts.register(scriptOptions); + const actualAPIs = Object.keys(script); + + browser.test.assertEq( + JSON.stringify(expectedAPIs), + JSON.stringify(actualAPIs), + `Got a script API object for ${scriptOptions.js[0]}` + ); + } + + browser.test.sendMessage("background-ready"); + } + + function contentScriptStart() { + browser.runtime.sendMessage([ + "script-run", + ["loading"], + document.readyState, + ]); + } + function contentScriptEnd() { + browser.runtime.sendMessage([ + "script-run", + ["interactive", "complete"], + document.readyState, + ]); + } + function contentScriptIdle() { + browser.runtime.sendMessage([ + "script-run", + ["complete"], + document.readyState, + ]); + } + + function contentScript() { + let manifest = browser.runtime.getManifest(); + void manifest.permissions; + browser.runtime.sendMessage(["chrome-namespace-ok"]); + } + + let extensionData = { + manifest: { + permissions: ["http://localhost/*/file_sample.html"], + }, + background, + + files: { + "content_script_start.js": contentScriptStart, + "content_script_end.js": contentScriptEnd, + "content_script_idle.js": contentScriptIdle, + "content_script.js": contentScript, + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + + let loadingCount = 0; + let interactiveCount = 0; + let completeCount = 0; + extension.onMessage("script-run-loading", () => { + loadingCount++; + }); + extension.onMessage("script-run-interactive", () => { + interactiveCount++; + }); + + let completePromise = new Promise(resolve => { + extension.onMessage("script-run-complete", () => { + completeCount++; + resolve(); + }); + }); + + let chromeNamespacePromise = extension.awaitMessage("chrome-namespace-ok"); + + // Ensure that a content page running in a content process and which has been + // already loaded when the content scripts has been registered, it has received + // and registered the expected content scripts. + let contentPage = await ExtensionTestUtils.loadContentPage(`about:blank`); + + await extension.startup(); + await extension.awaitMessage("background-ready"); + + await contentPage.loadURL(`${BASE_URL}/file_sample.html`); + + await Promise.all([completePromise, chromeNamespacePromise]); + + await contentPage.close(); + + // Expect two content scripts to run (one registered using an extension URL + // and one registered from plain JS code). + equal(loadingCount, 2, "document_start script ran exactly twice"); + equal(interactiveCount, 2, "document_end script ran exactly twice"); + equal(completeCount, 2, "document_idle script ran exactly twice"); + + await extension.unload(); +}); + +// Test that the contentScript.register options are correctly translated +// into the expected WebExtensionContentScript properties. +add_task(async function test_contentscripts_register_all_options() { + async function background() { + await browser.contentScripts.register({ + js: [{ file: "content_script.js" }], + css: [{ file: "content_style.css" }], + matches: ["http://localhost/*"], + excludeMatches: ["http://localhost/exclude/*"], + excludeGlobs: ["*_exclude.html"], + includeGlobs: ["*_include.html"], + allFrames: true, + matchAboutBlank: true, + runAt: "document_start", + }); + + browser.test.sendMessage("background-ready", window.location.origin); + } + + const extensionData = { + manifest: { + permissions: ["http://localhost/*"], + }, + background, + + files: { + "content_script.js": "", + "content_style.css": "", + }, + }; + + const extension = ExtensionTestUtils.loadExtension(extensionData); + + await extension.startup(); + + const baseExtURL = await extension.awaitMessage("background-ready"); + + const policy = WebExtensionPolicy.getByID(extension.id); + + ok(policy, "Got the WebExtensionPolicy for the test extension"); + equal( + policy.contentScripts.length, + 1, + "Got the expected number of registered content scripts" + ); + + const script = policy.contentScripts[0]; + let { allFrames, cssPaths, jsPaths, matchAboutBlank, runAt } = script; + + deepEqual( + { + allFrames, + cssPaths, + jsPaths, + matchAboutBlank, + runAt, + }, + { + allFrames: true, + cssPaths: [`${baseExtURL}/content_style.css`], + jsPaths: [`${baseExtURL}/content_script.js`], + matchAboutBlank: true, + runAt: "document_start", + }, + "Got the expected content script properties" + ); + + ok( + script.matchesURI(Services.io.newURI("http://localhost/ok_include.html")), + "matched and include globs should match" + ); + ok( + !script.matchesURI( + Services.io.newURI("http://localhost/exclude/ok_include.html") + ), + "exclude matches should not match" + ); + ok( + !script.matchesURI(Services.io.newURI("http://localhost/ok_exclude.html")), + "exclude globs should not match" + ); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_content_security_policy.js b/toolkit/components/extensions/test/xpcshell/test_ext_content_security_policy.js new file mode 100644 index 0000000000..47de723f0f --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_content_security_policy.js @@ -0,0 +1,251 @@ +"use strict"; + +Services.prefs.setBoolPref("extensions.manifestV3.enabled", true); + +const server = createHttpServer({ hosts: ["example.com"] }); + +server.registerPathHandler("/dummy", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/html", false); + response.write(""); +}); + +server.registerPathHandler("/worker.js", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/javascript", false); + response.write("let x = true;"); +}); + +const baseCSP = []; +baseCSP[2] = { + "object-src": ["blob:", "filesystem:", "moz-extension:", "'self'"], + "script-src": [ + "'unsafe-eval'", + "'unsafe-inline'", + "blob:", + "filesystem:", + "http://localhost:*", + "http://127.0.0.1:*", + "https://*", + "moz-extension:", + "'self'", + ], +}; +baseCSP[3] = { + "object-src": ["'self'"], + "script-src": ["http://localhost:*", "http://127.0.0.1:*", "'self'"], + "worker-src": ["http://localhost:*", "http://127.0.0.1:*", "'self'"], +}; + +/** + * Tests that content security policies for an add-on are actually applied to * + * documents that belong to it. This tests both the base policies and add-on + * specific policies, and ensures that the parsed policies applied to the + * document's principal match what was specified in the policy string. + * + * @param {number} [manifest_version] + * @param {object} [customCSP] + */ +async function testPolicy(manifest_version = 2, customCSP = null) { + let baseURL; + + let addonCSP = { + "object-src": ["'self'"], + "script-src": ["'self'"], + }; + + let content_security_policy = null; + + if (customCSP) { + for (let key of Object.keys(customCSP)) { + addonCSP[key] = customCSP[key].split(/\s+/); + } + + content_security_policy = Object.keys(customCSP) + .map(key => `${key} ${customCSP[key]}`) + .join("; "); + } + + function checkSource(name, policy, expected) { + // fallback to script-src when comparing worker-src if policy does not include worker-src + let policySrc = + name != "worker-src" || policy[name] + ? policy[name] + : policy["script-src"]; + equal( + JSON.stringify(policySrc.sort()), + JSON.stringify(expected[name].sort()), + `Expected value for ${name}` + ); + } + + function checkCSP(csp, location) { + let policies = csp["csp-policies"]; + + info(`Base policy for ${location}`); + let base = baseCSP[manifest_version]; + + equal(policies[0]["report-only"], false, "Policy is not report-only"); + for (let key in base) { + checkSource(key, policies[0], base); + } + + info(`Add-on policy for ${location}`); + + equal(policies[1]["report-only"], false, "Policy is not report-only"); + for (let key in addonCSP) { + checkSource(key, policies[1], addonCSP); + } + } + + function background() { + browser.test.sendMessage( + "base-url", + browser.extension.getURL("").replace(/\/$/, "") + ); + + browser.test.sendMessage("background-csp", window.getCSP()); + } + + function tabScript() { + browser.test.sendMessage("tab-csp", window.getCSP()); + + const worker = new Worker("worker.js"); + worker.onmessage = event => { + browser.test.sendMessage("worker-csp", event.data); + }; + + worker.postMessage({}); + } + + function testWorker(port) { + this.onmessage = () => { + try { + // eslint-disable-next-line no-undef + importScripts(`http://127.0.0.1:${port}/worker.js`); + postMessage({ loaded: true }); + } catch (e) { + postMessage({ loaded: false }); + } + }; + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + + files: { + "tab.html": ` + + + `, + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + + let awaitConsole = new Promise(resolve => { + Services.console.registerListener(function listener(message) { + if (/WebExt Privilege Escalation/.test(message.message)) { + Services.console.unregisterListener(listener); + resolve(message); + } + }); + }); + + await extension.startup(); + + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/data/file_sample.html" + ); + + let message = await awaitConsole; + ok( + message.message.includes( + "WebExt Privilege Escalation: typeof(browser) = undefined" + ), + "Document does not have `browser` APIs." + ); + + await contentPage.close(); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_async_loading.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_async_loading.js new file mode 100644 index 0000000000..cb9a07142d --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_async_loading.js @@ -0,0 +1,79 @@ +"use strict"; + +const server = createHttpServer({ hosts: ["example.com"] }); + +server.registerPathHandler("/dummy", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/html", false); + response.write(""); +}); + +add_task(async function test_async_loading() { + const adder = `(function add(a = 1) { this.count += a; })();\n`; + + const extension = { + manifest: { + content_scripts: [ + { + run_at: "document_start", + matches: ["http://example.com/dummy"], + js: ["first.js", "second.js"], + }, + { + run_at: "document_end", + matches: ["http://example.com/dummy"], + js: ["third.js"], + }, + ], + }, + files: { + "first.js": ` + this.count = 0; + ${adder.repeat(50000)}; // 2Mb + browser.test.assertEq(this.count, 50000, "A 50k line script"); + + this.order = (this.order || 0) + 1; + browser.test.sendMessage("first", this.order); + `, + "second.js": ` + this.order = (this.order || 0) + 1; + browser.test.sendMessage("second", this.order); + `, + "third.js": ` + this.order = (this.order || 0) + 1; + browser.test.sendMessage("third", this.order); + `, + }, + }; + + async function checkOrder(ext) { + const [first, second, third] = await Promise.all([ + ext.awaitMessage("first"), + ext.awaitMessage("second"), + ext.awaitMessage("third"), + ]); + + equal(first, 1, "first.js finished execution first."); + equal(second, 2, "second.js finished execution second."); + equal(third, 3, "third.js finished execution third."); + } + + info("Test pages observed while extension is running"); + const observed = ExtensionTestUtils.loadExtension(extension); + await observed.startup(); + + const contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/dummy" + ); + await checkOrder(observed); + await observed.unload(); + + info("Test pages already existing on extension startup"); + const existing = ExtensionTestUtils.loadExtension(extension); + + await existing.startup(); + await checkOrder(existing); + await existing.unload(); + + await contentPage.close(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_canvas_tainting.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_canvas_tainting.js new file mode 100644 index 0000000000..4ac22dc700 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_canvas_tainting.js @@ -0,0 +1,128 @@ +"use strict"; + +const server = createHttpServer({ + hosts: ["green.example.com", "red.example.com"], +}); + +server.registerDirectory("/data/", do_get_file("data")); + +server.registerPathHandler("/pixel.html", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/html", false); + response.write(` + + `); +}); + +add_task(async function test_contentscript_canvas_tainting() { + async function contentScript() { + let canvas = document.createElement("canvas"); + let ctx = canvas.getContext("2d"); + document.body.appendChild(canvas); + + function draw(url) { + return new Promise(resolve => { + let img = document.createElement("img"); + img.onload = () => { + ctx.drawImage(img, 0, 0, 1, 1); + resolve(); + }; + img.src = url; + }); + } + + function readByExt() { + let { data } = ctx.getImageData(0, 0, 1, 1); + return data.slice(0, 3).join(); + } + + let readByWeb = window.wrappedJSObject.readByWeb; + + // Test reading after drawing an image from the same origin as the web page. + await draw("http://green.example.com/data/pixel_green.gif"); + browser.test.assertEq( + readByWeb(), + "0,255,0", + "Content can read same-origin image" + ); + browser.test.assertEq( + readByExt(), + "0,255,0", + "Extension can read same-origin image" + ); + + // Test reading after drawing a blue pixel data URI from extension content script. + await draw( + "data:image/gif;base64,R0lGODlhAQABAIABAAAA/wAAACwAAAAAAQABAAACAkQBADs=" + ); + browser.test.assertThrows( + readByWeb, + /operation is insecure/, + "Content can't read extension's image" + ); + browser.test.assertEq( + readByExt(), + "0,0,255", + "Extension can read its own image" + ); + + // Test after tainting the canvas with an image from a third party domain. + await draw("http://red.example.com/data/pixel_red.gif"); + browser.test.assertThrows( + readByWeb, + /operation is insecure/, + "Content can't read third party image" + ); + browser.test.assertThrows( + readByExt, + /operation is insecure/, + "Extension can't read fully tainted" + ); + + // Test canvas is still fully tainted after drawing extension's data: image again. + await draw( + "data:image/gif;base64,R0lGODlhAQABAIABAAAA/wAAACwAAAAAAQABAAACAkQBADs=" + ); + browser.test.assertThrows( + readByWeb, + /operation is insecure/, + "Canvas still fully tainted for content" + ); + browser.test.assertThrows( + readByExt, + /operation is insecure/, + "Canvas still fully tainted for extension" + ); + + browser.test.sendMessage("done"); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + content_scripts: [ + { + matches: ["http://green.example.com/pixel.html"], + js: ["cs.js"], + }, + ], + }, + files: { + "cs.js": contentScript, + }, + }); + + await extension.startup(); + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://green.example.com/pixel.html" + ); + await extension.awaitMessage("done"); + + await contentPage.close(); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_context.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_context.js new file mode 100644 index 0000000000..2bb30f3c90 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_context.js @@ -0,0 +1,348 @@ +"use strict"; + +/* eslint-disable mozilla/balanced-listeners */ + +const server = createHttpServer({ hosts: ["example.com", "example.org"] }); + +server.registerPathHandler("/dummy", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/html", false); + response.write(""); +}); + +function loadExtension() { + function contentScript() { + browser.test.sendMessage("content-script-ready"); + + window.addEventListener( + "pagehide", + () => { + browser.test.sendMessage("content-script-hide"); + }, + true + ); + window.addEventListener("pageshow", () => { + browser.test.sendMessage("content-script-show"); + }); + } + + return ExtensionTestUtils.loadExtension({ + manifest: { + content_scripts: [ + { + matches: ["http://example.com/dummy*"], + js: ["content_script.js"], + run_at: "document_start", + }, + ], + }, + + files: { + "content_script.js": contentScript, + }, + }); +} + +add_task(async function test_contentscript_context() { + let extension = loadExtension(); + await extension.startup(); + + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/dummy" + ); + await extension.awaitMessage("content-script-ready"); + await extension.awaitMessage("content-script-show"); + + // Get the content script context and check that it points to the correct window. + await contentPage.spawn(extension.id, async extensionId => { + let { DocumentManager } = ChromeUtils.import( + "resource://gre/modules/ExtensionContent.jsm", + null + ); + this.context = DocumentManager.getContext(extensionId, this.content); + + Assert.ok(this.context, "Got content script context"); + + Assert.equal( + this.context.contentWindow, + this.content, + "Context's contentWindow property is correct" + ); + + // Navigate so that the content page is hidden in the bfcache. + + this.content.location = "http://example.org/dummy"; + }); + + await extension.awaitMessage("content-script-hide"); + + await contentPage.spawn(null, async () => { + Assert.equal( + this.context.contentWindow, + null, + "Context's contentWindow property is null" + ); + + // Navigate back so the content page is resurrected from the bfcache. + this.content.history.back(); + }); + + await extension.awaitMessage("content-script-show"); + + await contentPage.spawn(null, async () => { + Assert.equal( + this.context.contentWindow, + this.content, + "Context's contentWindow property is correct" + ); + }); + + await contentPage.close(); + await extension.awaitMessage("content-script-hide"); + await extension.unload(); +}); + +async function contentscript_context_incognito_not_allowed_test() { + async function background() { + await browser.contentScripts.register({ + js: [{ file: "registered_script.js" }], + matches: ["http://example.com/dummy"], + runAt: "document_start", + }); + + browser.test.sendMessage("background-ready"); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + content_scripts: [ + { + matches: ["http://example.com/dummy"], + js: ["content_script.js"], + run_at: "document_start", + }, + ], + permissions: ["http://example.com/*"], + }, + background, + files: { + "content_script.js": () => { + browser.test.notifyFail("content_script_loaded"); + }, + "registered_script.js": () => { + browser.test.notifyFail("registered_script_loaded"); + }, + }, + }); + + await extension.startup(); + await extension.awaitMessage("background-ready"); + + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/dummy", + { privateBrowsing: true } + ); + + await contentPage.spawn(extension.id, async extensionId => { + let { DocumentManager } = ChromeUtils.import( + "resource://gre/modules/ExtensionContent.jsm", + null + ); + let context = DocumentManager.getContext(extensionId, this.content); + Assert.equal( + context, + null, + "Extension unable to use content_script in private browsing window" + ); + }); + + await contentPage.close(); + await extension.unload(); +} + +add_task(async function test_contentscript_context_incognito_not_allowed() { + return runWithPrefs( + [["extensions.allowPrivateBrowsingByDefault", false]], + contentscript_context_incognito_not_allowed_test + ); +}); + +add_task(async function test_contentscript_context_unload_while_in_bfcache() { + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/dummy?first" + ); + let extension = loadExtension(); + await extension.startup(); + await extension.awaitMessage("content-script-ready"); + + // Get the content script context and check that it points to the correct window. + await contentPage.spawn(extension.id, async extensionId => { + let { DocumentManager } = ChromeUtils.import( + "resource://gre/modules/ExtensionContent.jsm", + null + ); + // Save context so we can verify that contentWindow is nulled after unload. + this.context = DocumentManager.getContext(extensionId, this.content); + + Assert.equal( + this.context.contentWindow, + this.content, + "Context's contentWindow property is correct" + ); + + this.contextUnloadedPromise = new Promise(resolve => { + this.context.callOnClose({ close: resolve }); + }); + this.pageshownPromise = new Promise(resolve => { + this.content.addEventListener( + "pageshow", + () => { + // Yield to the event loop once more to ensure that all pageshow event + // handlers have been dispatched before fulfilling the promise. + let { setTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm" + ); + setTimeout(resolve, 0); + }, + { once: true, mozSystemGroup: true } + ); + }); + + // Navigate so that the content page is hidden in the bfcache. + this.content.location = "http://example.org/dummy?second"; + }); + + await extension.awaitMessage("content-script-hide"); + + await extension.unload(); + await contentPage.spawn(null, async () => { + await this.contextUnloadedPromise; + Assert.equal(this.context.unloaded, true, "Context has been unloaded"); + + // Normally, when a page is not in the bfcache, context.contentWindow is + // not null when the callOnClose handler is invoked (this is checked by the + // previous subtest). + // Now wait a little bit and check again to ensure that the contentWindow + // property is not somehow restored. + await new Promise(resolve => this.content.setTimeout(resolve, 0)); + Assert.equal( + this.context.contentWindow, + null, + "Context's contentWindow property is null" + ); + + // Navigate back so the content page is resurrected from the bfcache. + this.content.history.back(); + + await this.pageshownPromise; + + Assert.equal( + this.context.contentWindow, + null, + "Context's contentWindow property is null after restore from bfcache" + ); + }); + + await contentPage.close(); +}); + +add_task(async function test_contentscript_context_valid_during_execution() { + // This test does the following: + // - Load page + // - Load extension; inject content script. + // - Navigate page; pagehide triggered. + // - Navigate back; pageshow triggered. + // - Close page; pagehide, unload triggered. + // At each of these last four events, the validity of the context is checked. + + function contentScript() { + browser.test.sendMessage("content-script-ready"); + window.wrappedJSObject.checkContextIsValid("Context is valid on execution"); + + window.addEventListener( + "pagehide", + () => { + window.wrappedJSObject.checkContextIsValid( + "Context is valid on pagehide" + ); + browser.test.sendMessage("content-script-hide"); + }, + true + ); + window.addEventListener("pageshow", () => { + window.wrappedJSObject.checkContextIsValid( + "Context is valid on pageshow" + ); + + // This unload listener is registered after pageshow, to ensure that the + // page can be stored in the bfcache at the previous pagehide. + window.addEventListener("unload", () => { + window.wrappedJSObject.checkContextIsValid( + "Context is valid on unload" + ); + browser.test.sendMessage("content-script-unload"); + }); + + browser.test.sendMessage("content-script-show"); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + content_scripts: [ + { + matches: ["http://example.com/dummy*"], + js: ["content_script.js"], + }, + ], + }, + + files: { + "content_script.js": contentScript, + }, + }); + + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/dummy?first" + ); + await contentPage.spawn(extension.id, async extensionId => { + let context; + let checkContextIsValid = description => { + if (!context) { + let { DocumentManager } = ChromeUtils.import( + "resource://gre/modules/ExtensionContent.jsm", + null + ); + context = DocumentManager.getContext(extensionId, this.content); + } + Assert.equal( + context.contentWindow, + this.content, + `${description}: contentWindow` + ); + Assert.equal(context.active, true, `${description}: active`); + }; + Cu.exportFunction(checkContextIsValid, this.content, { + defineAs: "checkContextIsValid", + }); + }); + await extension.startup(); + await extension.awaitMessage("content-script-ready"); + + await contentPage.spawn(extension.id, async extensionId => { + // Navigate so that the content page is frozen in the bfcache. + this.content.location = "http://example.org/dummy?second"; + }); + + await extension.awaitMessage("content-script-hide"); + await contentPage.spawn(null, async () => { + // Navigate back so the content page is resurrected from the bfcache. + this.content.history.back(); + }); + + await extension.awaitMessage("content-script-show"); + await contentPage.close(); + await extension.awaitMessage("content-script-hide"); + await extension.awaitMessage("content-script-unload"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_context_isolation.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_context_isolation.js new file mode 100644 index 0000000000..1b705e0a53 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_context_isolation.js @@ -0,0 +1,160 @@ +"use strict"; + +/* globals exportFunction */ +/* eslint-disable mozilla/balanced-listeners */ + +const server = createHttpServer({ hosts: ["example.com", "example.org"] }); + +server.registerPathHandler("/dummy", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/html", false); + response.write(""); +}); + +server.registerPathHandler("/bfcachetestpage", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/html;charset=utf-8", false); + response.write(` +`); +}); + +add_task(async function test_contentscript_context_isolation() { + function contentScript() { + browser.test.sendMessage("content-script-ready"); + + exportFunction(browser.test.sendMessage, window, { + defineAs: "browserTestSendMessage", + }); + + window.addEventListener("pageshow", () => { + browser.test.fail( + "pageshow should have been suppressed by stopImmediatePropagation" + ); + }); + window.addEventListener( + "pagehide", + () => { + browser.test.fail( + "pagehide should have been suppressed by stopImmediatePropagation" + ); + }, + true + ); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + content_scripts: [ + { + matches: ["http://example.com/bfcachetestpage"], + js: ["content_script.js"], + }, + ], + }, + + files: { + "content_script.js": contentScript, + }, + }); + + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/bfcachetestpage" + ); + await extension.startup(); + await extension.awaitMessage("content-script-ready"); + + // Get the content script context and check that it points to the correct window. + await contentPage.spawn(extension.id, async extensionId => { + let { DocumentManager } = ChromeUtils.import( + "resource://gre/modules/ExtensionContent.jsm", + null + ); + this.context = DocumentManager.getContext(extensionId, this.content); + + Assert.ok(this.context, "Got content script context"); + + Assert.equal( + this.context.contentWindow, + this.content, + "Context's contentWindow property is correct" + ); + + // Navigate so that the content page is hidden in the bfcache. + + this.content.location = "http://example.org/dummy?noscripthere1"; + }); + + await extension.awaitMessage("content-script-hide"); + + await contentPage.spawn(null, async () => { + Assert.equal( + this.context.contentWindow, + null, + "Context's contentWindow property is null" + ); + Assert.ok(this.context.sandbox, "Context's sandbox exists"); + + // Navigate back so the content page is resurrected from the bfcache. + this.content.history.back(); + }); + + await extension.awaitMessage("content-script-show"); + + await contentPage.spawn(null, async () => { + Assert.equal( + this.context.contentWindow, + this.content, + "Context's contentWindow property is correct" + ); + Assert.ok(this.context.sandbox, "Context's sandbox exists before unload"); + + let contextUnloadedPromise = new Promise(resolve => { + this.context.callOnClose({ close: resolve }); + }); + + // Now add an "unload" event listener, which should prevent a page from entering the bfcache. + await new Promise(resolve => { + this.content.addEventListener("unload", () => { + Assert.equal( + this.context.contentWindow, + this.content, + "Context's contentWindow property should be non-null at unload" + ); + resolve(); + }); + this.content.location = "http://example.org/dummy?noscripthere2"; + }); + + await contextUnloadedPromise; + }); + + await extension.awaitMessage("content-script-unload"); + + await contentPage.spawn(null, async () => { + Assert.equal( + this.context.sandbox, + null, + "Context's sandbox has been destroyed after unload" + ); + }); + + await contentPage.close(); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_create_iframe.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_create_iframe.js new file mode 100644 index 0000000000..ff2622e4fb --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_create_iframe.js @@ -0,0 +1,177 @@ +"use strict"; + +const server = createHttpServer({ hosts: ["example.com"] }); +server.registerDirectory("/data/", do_get_file("data")); + +add_task(async function test_contentscript_create_iframe() { + function background() { + browser.runtime.onMessage.addListener((msg, sender) => { + let { name, availableAPIs, manifest, testGetManifest } = msg; + let hasExtTabsAPI = availableAPIs.indexOf("tabs") > 0; + let hasExtWindowsAPI = availableAPIs.indexOf("windows") > 0; + + browser.test.assertFalse( + hasExtTabsAPI, + "the created iframe should not be able to use privileged APIs (tabs)" + ); + browser.test.assertFalse( + hasExtWindowsAPI, + "the created iframe should not be able to use privileged APIs (windows)" + ); + + let { + applications: { + gecko: { id: expectedManifestGeckoId }, + }, + } = chrome.runtime.getManifest(); + let { + applications: { + gecko: { id: actualManifestGeckoId }, + }, + } = manifest; + + browser.test.assertEq( + actualManifestGeckoId, + expectedManifestGeckoId, + "the add-on manifest should be accessible from the created iframe" + ); + + let { + applications: { + gecko: { id: testGetManifestGeckoId }, + }, + } = testGetManifest; + + browser.test.assertEq( + testGetManifestGeckoId, + expectedManifestGeckoId, + "GET_MANIFEST() returns manifest data before extension unload" + ); + + browser.test.sendMessage(name); + }); + } + + function contentScriptIframe() { + window.GET_MANIFEST = browser.runtime.getManifest.bind(null); + + window.testGetManifestException = () => { + try { + window.GET_MANIFEST(); + } catch (exception) { + return String(exception); + } + }; + + let testGetManifest = window.GET_MANIFEST(); + + let manifest = browser.runtime.getManifest(); + let availableAPIs = Object.keys(browser).filter(key => browser[key]); + + browser.runtime.sendMessage({ + name: "content-script-iframe-loaded", + availableAPIs, + manifest, + testGetManifest, + }); + } + + const ID = "contentscript@tests.mozilla.org"; + let extensionData = { + manifest: { + applications: { gecko: { id: ID } }, + content_scripts: [ + { + matches: ["http://example.com/data/file_sample.html"], + js: ["content_script.js"], + run_at: "document_idle", + }, + ], + web_accessible_resources: ["content_script_iframe.html"], + }, + + background, + + files: { + "content_script.js"() { + let iframe = document.createElement("iframe"); + iframe.src = browser.runtime.getURL("content_script_iframe.html"); + document.body.appendChild(iframe); + }, + "content_script_iframe.html": ` + + + + + + `, + "content_script_iframe.js": contentScriptIframe, + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/data/file_sample.html" + ); + + await extension.awaitMessage("content-script-iframe-loaded"); + + info("testing APIs availability once the extension is unloaded..."); + + await contentPage.spawn(null, () => { + this.iframeWindow = this.content[0]; + + Assert.ok(this.iframeWindow, "content script enabled iframe found"); + Assert.ok( + /content_script_iframe\.html$/.test(this.iframeWindow.location), + "the found iframe has the expected URL" + ); + }); + + await extension.unload(); + + info( + "test content script APIs not accessible from the frame once the extension is unloaded" + ); + + await contentPage.spawn(null, () => { + let win = Cu.waiveXrays(this.iframeWindow); + ok( + !Cu.isDeadWrapper(win.browser), + "the API object should not be a dead object" + ); + + let manifest; + let manifestException; + try { + manifest = win.browser.runtime.getManifest(); + } catch (e) { + manifestException = e; + } + + Assert.ok(!manifest, "manifest should be undefined"); + + Assert.equal( + manifestException.constructor.name, + "TypeError", + "expected exception received" + ); + + Assert.ok( + manifestException.message.endsWith("win.browser.runtime is undefined"), + "expected exception received" + ); + + let getManifestException = win.testGetManifestException(); + + Assert.equal( + getManifestException, + "TypeError: can't access dead object", + "expected exception received" + ); + }); + + await contentPage.close(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_csp.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_csp.js new file mode 100644 index 0000000000..cf770d91b4 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_csp.js @@ -0,0 +1,355 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const { TestUtils } = ChromeUtils.import( + "resource://testing-common/TestUtils.jsm" +); + +Services.prefs.setBoolPref("extensions.manifestV3.enabled", true); + +const server = createHttpServer({ + hosts: ["example.com", "csplog.example.net"], +}); +server.registerDirectory("/data/", do_get_file("data")); + +var gDefaultCSP = `default-src 'self' 'report-sample'; script-src 'self' 'report-sample';`; +var gCSP = gDefaultCSP; +const pageContent = ` + + + + + + + + + `; + +server.registerPathHandler("/plain.html", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/html"); + if (gCSP) { + info(`Content-Security-Policy: ${gCSP}`); + response.setHeader("Content-Security-Policy", gCSP); + } + response.write(pageContent); +}); + +const BASE_URL = `http://example.com`; +const pageURL = `${BASE_URL}/plain.html`; + +const CSP_REPORT_PATH = "/csp-report.sjs"; + +function readUTF8InputStream(stream) { + let buffer = NetUtil.readInputStream(stream, stream.available()); + return new TextDecoder().decode(buffer); +} + +server.registerPathHandler(CSP_REPORT_PATH, (request, response) => { + response.setStatusLine(request.httpVersion, 204, "No Content"); + let data = readUTF8InputStream(request.bodyInputStream); + Services.obs.notifyObservers(null, "extension-test-csp-report", data); +}); + +async function promiseCSPReport(test) { + let res = await TestUtils.topicObserved("extension-test-csp-report", test); + return JSON.parse(res[1]); +} + +// Test functions loaded into extension content script. +function testImage(data = {}) { + return new Promise(resolve => { + let img = window.document.getElementById("testimg"); + img.onload = () => resolve(true); + img.onerror = () => { + browser.test.log(`img error: ${img.src}`); + resolve(false); + }; + img.src = data.image_url; + }); +} + +function testFetch(data = {}) { + let f = data.content ? content.fetch : fetch; + return f(data.url) + .then(() => true) + .catch(e => { + browser.test.assertEq( + e.message, + "NetworkError when attempting to fetch resource.", + "expected fetch failure" + ); + return false; + }); +} + +async function testEval(data = {}) { + try { + // eslint-disable-next-line no-eval + let ev = data.content ? window.eval : eval; + return ev("true"); + } catch (e) { + return false; + } +} + +async function testFunction(data = {}) { + try { + // eslint-disable-next-line no-eval + let fn = data.content ? window.Function : Function; + let sum = new fn("a", "b", "return a + b"); + return sum(1, 1); + } catch (e) { + return 0; + } +} + +function testScriptTag(data) { + return new Promise(resolve => { + let script = document.createElement("script"); + script.src = data.url; + script.onload = () => { + resolve(true); + }; + script.onerror = () => { + resolve(false); + }; + document.body.appendChild(script); + }); +} + +// If the violation source is the extension the securitypolicyviolation event is not fired. +// If the page is the source, the event is fired and both the content script or page scripts +// will receive the event. If we're expecting a moz-extension report we'll fail in the +// event listener if we receive a report. Otherwise we want to resolve in the listener to +// ensure we've received the event for the test. +function contentScript(report) { + return new Promise(resolve => { + if (!report || report["document-uri"] === "moz-extension") { + resolve(); + } + // eslint-disable-next-line mozilla/balanced-listeners + document.addEventListener("securitypolicyviolation", e => { + browser.test.assertTrue( + e.documentURI !== "moz-extension", + `securitypolicyviolation: ${e.violatedDirective} ${e.documentURI}` + ); + resolve(); + }); + }); +} + +let TESTS = [ + // Image Tests + { + description: + "Image from content script using default extension csp. Image is allowed.", + pageCSP: `${gDefaultCSP} img-src 'none';`, + script: testImage, + data: { image_url: `${BASE_URL}/data/file_image_good.png` }, + expect: true, + }, + // Fetch Tests + { + description: "Fetch url in content script uses default extension csp.", + pageCSP: `${gDefaultCSP} connect-src 'none';`, + script: testFetch, + data: { url: `${BASE_URL}/data/file_image_good.png` }, + expect: true, + }, + { + description: "Fetch full url from content script uses page csp.", + pageCSP: `${gDefaultCSP} connect-src 'none';`, + script: testFetch, + data: { + content: true, + url: `${BASE_URL}/data/file_image_good.png`, + }, + expect: false, + report: { + "blocked-uri": `${BASE_URL}/data/file_image_good.png`, + "document-uri": `${BASE_URL}/plain.html`, + "violated-directive": "connect-src", + }, + }, + { + description: "Fetch url from content script uses page csp.", + pageCSP: `${gDefaultCSP} connect-src *;`, + script: testFetch, + version: 3, + data: { + content: true, + url: `${BASE_URL}/data/file_image_good.png`, + }, + expect: true, + }, + + // Eval tests. + { + description: "Eval from content script uses page csp with unsafe-eval.", + pageCSP: `default-src 'none'; script-src 'unsafe-eval';`, + script: testEval, + data: { content: true }, + expect: true, + }, + { + description: "Eval from content script uses page csp.", + pageCSP: `default-src 'self' 'report-sample'; script-src 'self';`, + version: 3, + script: testEval, + data: { content: true }, + expect: false, + report: { + "blocked-uri": "eval", + "document-uri": "http://example.com/plain.html", + "violated-directive": "script-src", + }, + }, + { + description: "Eval in content script allowed by v2 csp.", + pageCSP: `script-src 'self' 'unsafe-eval';`, + script: testEval, + expect: true, + }, + { + description: "Eval in content script disallowed by v3 csp.", + pageCSP: `script-src 'self' 'unsafe-eval';`, + version: 3, + script: testEval, + expect: false, + }, + { + description: "Wrapped Eval in content script uses page csp.", + pageCSP: `script-src 'self' 'unsafe-eval';`, + version: 3, + script: async () => { + return window.wrappedJSObject.eval("true"); + }, + expect: true, + }, + { + description: "Wrapped Eval in content script denied by page csp.", + pageCSP: `script-src 'self';`, + version: 3, + script: async () => { + try { + return window.wrappedJSObject.eval("true"); + } catch (e) { + return false; + } + }, + expect: false, + }, + + { + description: "Function from content script uses page csp.", + pageCSP: `default-src 'self'; script-src 'self' 'unsafe-eval';`, + script: testFunction, + data: { content: true }, + expect: 2, + }, + { + description: "Function from content script uses page csp.", + pageCSP: `default-src 'self' 'report-sample'; script-src 'self';`, + version: 3, + script: testFunction, + data: { content: true }, + expect: 0, + report: { + "blocked-uri": "eval", + "document-uri": "http://example.com/plain.html", + "violated-directive": "script-src", + }, + }, + { + description: "Function in content script uses extension csp.", + pageCSP: `default-src 'self'; script-src 'self' 'unsafe-eval';`, + version: 3, + script: testFunction, + expect: 0, + }, + + // The javascript url tests are not included as we do not execute those, + // aparently even with the urlbar filtering pref flipped. + // (browser.urlbar.filter.javascript) + // https://bugzilla.mozilla.org/show_bug.cgi?id=866522 + + // script tag injection tests + { + description: "remote script in content script passes in v2", + version: 2, + pageCSP: "script-src http://example.com:*;", + script: testScriptTag, + data: { url: `${BASE_URL}/data/file_script_good.js` }, + expect: true, + }, + { + description: "remote script in content script fails in v3", + version: 3, + pageCSP: "script-src http://example.com:*;", + script: testScriptTag, + data: { url: `${BASE_URL}/data/file_script_good.js` }, + expect: false, + }, +]; + +async function runCSPTest(test) { + // Set the CSP for the page loaded into the tab. + gCSP = `${test.pageCSP || gDefaultCSP} report-uri ${CSP_REPORT_PATH}`; + let data = { + manifest: { + manifest_version: test.version || 2, + content_scripts: [ + { + matches: ["http://*/plain.html"], + run_at: "document_idle", + js: ["content_script.js"], + }, + ], + permissions: [""], + }, + + files: { + "content_script.js": ` + (${contentScript})(${JSON.stringify(test.report)}).then(() => { + browser.test.sendMessage("violationEvent"); + }); + (${test.script})(${JSON.stringify(test.data)}).then(result => { + browser.test.sendMessage("result", result); + }); + `, + }, + }; + + let extension = ExtensionTestUtils.loadExtension(data); + await extension.startup(); + + let reportPromise = test.report && promiseCSPReport(); + let contentPage = await ExtensionTestUtils.loadContentPage(pageURL); + + info(`running: ${test.description}`); + await extension.awaitMessage("violationEvent"); + let result = await extension.awaitMessage("result"); + equal(result, test.expect, test.description); + if (test.report) { + let report = await reportPromise; + for (let key of Object.keys(test.report)) { + equal( + report["csp-report"][key], + test.report[key], + `csp-report ${key} matches` + ); + } + } + + await extension.unload(); + await contentPage.close(); + clearCache(); +} + +add_task(async function test_contentscript_csp() { + for (let test of TESTS) { + await runCSPTest(test); + } +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_css.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_css.js new file mode 100644 index 0000000000..d94023387f --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_css.js @@ -0,0 +1,48 @@ +"use strict"; + +const server = createHttpServer({ hosts: ["example.com"] }); + +server.registerPathHandler("/dummy", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/html", false); + response.write(""); +}); + +add_task(async function test_content_script_css() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + content_scripts: [ + { + matches: ["http://example.com/dummy"], + css: ["content.css"], + run_at: "document_start", + }, + ], + }, + + files: { + "content.css": "body { max-width: 42px; }", + }, + }); + + await extension.startup(); + + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/dummy" + ); + + function task() { + let style = this.content.getComputedStyle(this.content.document.body); + return style.maxWidth; + } + + let maxWidth = await contentPage.spawn(null, task); + equal(maxWidth, "42px", "Stylesheet correctly applied"); + + await extension.unload(); + + maxWidth = await contentPage.spawn(null, task); + equal(maxWidth, "none", "Stylesheet correctly removed"); + + await contentPage.close(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_exporthelpers.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_exporthelpers.js new file mode 100644 index 0000000000..f485a012c9 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_exporthelpers.js @@ -0,0 +1,98 @@ +"use strict"; + +const server = createHttpServer({ hosts: ["example.com"] }); +server.registerDirectory("/data/", do_get_file("data")); + +add_task(async function test_contentscript_exportHelpers() { + function contentScript() { + browser.test.assertTrue(typeof cloneInto === "function"); + browser.test.assertTrue(typeof createObjectIn === "function"); + browser.test.assertTrue(typeof exportFunction === "function"); + + /* globals exportFunction, precisePi, reportPi */ + let value = 3.14; + exportFunction(() => value, window, { defineAs: "precisePi" }); + + browser.test.assertEq( + "undefined", + typeof precisePi, + "exportFunction should export to the page's scope only" + ); + + browser.test.assertEq( + "undefined", + typeof window.precisePi, + "exportFunction should export to the page's scope only" + ); + + let results = []; + exportFunction(pi => results.push(pi), window, { defineAs: "reportPi" }); + + let s = document.createElement("script"); + s.textContent = `(${function() { + let result1 = "unknown 1"; + let result2 = "unknown 2"; + try { + result1 = precisePi(); + } catch (e) { + result1 = "err:" + e; + } + try { + result2 = window.precisePi(); + } catch (e) { + result2 = "err:" + e; + } + reportPi(result1); + reportPi(result2); + }})();`; + + document.documentElement.appendChild(s); + // Inline script ought to run synchronously. + + browser.test.assertEq( + 3.14, + results[0], + "exportFunction on window should define a global function" + ); + browser.test.assertEq( + 3.14, + results[1], + "exportFunction on window should export a property to window." + ); + + browser.test.assertEq( + 2, + results.length, + "Expecting the number of results to match the number of method calls" + ); + + browser.test.notifyPass("export helper test completed"); + } + + let extensionData = { + manifest: { + content_scripts: [ + { + js: ["contentscript.js"], + matches: ["http://example.com/data/file_sample.html"], + run_at: "document_start", + }, + ], + }, + + files: { + "contentscript.js": contentScript, + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/data/file_sample.html" + ); + + await extension.awaitFinish("export helper test completed"); + await contentPage.close(); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_in_background.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_in_background.js new file mode 100644 index 0000000000..1a8aa6d706 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_in_background.js @@ -0,0 +1,61 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const server = createHttpServer({ hosts: ["example.com"] }); +server.registerPathHandler("/dummyFrame", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/html; charset=utf-8", false); + response.write(""); +}); + +add_task(async function connect_from_background_frame() { + async function background() { + const FRAME_URL = "http://example.com:8888/dummyFrame"; + browser.runtime.onConnect.addListener(port => { + browser.test.assertEq(port.sender.tab, undefined, "Sender is not a tab"); + browser.test.assertEq(port.sender.url, FRAME_URL, "Expected sender URL"); + port.onMessage.addListener(msg => { + browser.test.assertEq("pong", msg, "Reply from content script"); + port.disconnect(); + }); + port.postMessage("ping"); + }); + + await browser.contentScripts.register({ + matches: ["http://example.com/dummyFrame"], + js: [{ file: "contentscript.js" }], + allFrames: true, + }); + + let f = document.createElement("iframe"); + f.src = FRAME_URL; + document.body.appendChild(f); + } + + function contentScript() { + browser.test.log(`Running content script at ${document.URL}`); + + let port = browser.runtime.connect(); + port.onMessage.addListener(msg => { + browser.test.assertEq("ping", msg, "Expected message to content script"); + port.postMessage("pong"); + }); + port.onDisconnect.addListener(() => { + browser.test.sendMessage("disconnected_in_content_script"); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["http://example.com/*"], + }, + files: { + "contentscript.js": contentScript, + }, + background, + }); + await extension.startup(); + await extension.awaitMessage("disconnected_in_content_script"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_perf_observers.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_perf_observers.js new file mode 100644 index 0000000000..484c41ad3f --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_perf_observers.js @@ -0,0 +1,71 @@ +"use strict"; + +const server = createHttpServer({ + hosts: ["a.example.com", "b.example.com", "c.example.com"], +}); +server.registerDirectory("/data/", do_get_file("data")); + +add_task(async function test_perf_observers_cors() { + const extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["http://b.example.com/"], + content_scripts: [ + { + matches: ["http://a.example.com/file_sample.html"], + js: ["cs.js"], + }, + ], + }, + files: { + "cs.js"() { + let obs = new window.PerformanceObserver(list => { + list.getEntries().forEach(e => { + browser.test.sendMessage("observed", { + url: e.name, + time: e.connectEnd, + size: e.encodedBodySize, + }); + }); + }); + obs.observe({ entryTypes: ["resource"] }); + + let b = document.createElement("link"); + b.rel = "stylesheet"; + + // Simulate page including a cross-origin resource from b.example.com. + b.wrappedJSObject.href = "http://b.example.com/file_download.txt"; + document.head.appendChild(b); + + let c = document.createElement("link"); + c.rel = "stylesheet"; + + // Simulate page including a cross-origin resource from c.example.com. + c.wrappedJSObject.href = "http://c.example.com/file_download.txt"; + document.head.appendChild(c); + }, + }, + }); + + let page = await ExtensionTestUtils.loadContentPage( + "http://a.example.com/file_sample.html" + ); + await extension.startup(); + + let b = await extension.awaitMessage("observed"); + let c = await extension.awaitMessage("observed"); + + if (b.url.startsWith("http://c.")) { + [c, b] = [b, c]; + } + + ok(b.url.startsWith("http://b."), "Observed resource from b.example.com"); + ok(b.time > 0, "connectionEnd available from b.example.com"); + equal(b.size, 428, "encodedBodySize available from b.example.com"); + + ok(c.url.startsWith("http://c."), "Observed resource from c.example.com"); + equal(c.time, 0, "connectionEnd == 0 from c.example.com"); + equal(c.size, 0, "encodedBodySize == 0 from c.example.com"); + + await extension.unload(); + await page.close(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_restrictSchemes.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_restrictSchemes.js new file mode 100644 index 0000000000..e9b1dbe57c --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_restrictSchemes.js @@ -0,0 +1,70 @@ +"use strict"; + +function makeExtension(id, isPrivileged) { + return ExtensionTestUtils.loadExtension({ + isPrivileged, + + manifest: { + applications: { gecko: { id } }, + + permissions: isPrivileged ? ["mozillaAddons"] : [], + + content_scripts: [ + { + matches: ["resource://foo/file_sample.html"], + js: ["content_script.js"], + run_at: "document_start", + }, + ], + }, + + files: { + "content_script.js"() { + browser.test.assertEq( + "resource://foo/file_sample.html", + document.documentURI, + `Loaded content script into the correct document (extension: ${browser.runtime.id})` + ); + browser.test.sendMessage(`content-script-${browser.runtime.id}`); + }, + }, + }); +} + +add_task(async function test_contentscript_restrictSchemes() { + let resProto = Services.io + .getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + resProto.setSubstitutionWithFlags( + "foo", + Services.io.newFileURI(do_get_file("data")), + resProto.ALLOW_CONTENT_ACCESS + ); + + let unprivileged = makeExtension("unprivileged@tests.mozilla.org", false); + let privileged = makeExtension("privileged@tests.mozilla.org", true); + + await unprivileged.startup(); + await privileged.startup(); + + unprivileged.onMessage( + "content-script-unprivileged@tests.mozilla.org", + () => { + ok( + false, + "Unprivileged extension executed content script on resource URL" + ); + } + ); + + let contentPage = await ExtensionTestUtils.loadContentPage( + `resource://foo/file_sample.html` + ); + + await privileged.awaitMessage("content-script-privileged@tests.mozilla.org"); + + await contentPage.close(); + + await privileged.unload(); + await unprivileged.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_scriptCreated.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_scriptCreated.js new file mode 100644 index 0000000000..e0ed263065 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_scriptCreated.js @@ -0,0 +1,61 @@ +"use strict"; + +const server = createHttpServer(); +server.registerDirectory("/data/", do_get_file("data")); + +const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`; + +// ExtensionContent.jsm needs to know when it's running from xpcshell, +// to use the right timeout for content scripts executed at document_idle. +ExtensionTestUtils.mockAppInfo(); + +// Test that document_start content scripts don't block script-created +// parsers. +add_task(async function test_contentscript_scriptCreated() { + let extensionData = { + manifest: { + content_scripts: [ + { + matches: ["http://*/*/file_document_write.html"], + js: ["content_script.js"], + run_at: "document_start", + match_about_blank: true, + all_frames: true, + }, + ], + }, + + files: { + "content_script.js": function() { + if (window === top) { + addEventListener( + "message", + msg => { + browser.test.assertEq( + "ok", + msg.data, + "document.write() succeeded" + ); + browser.test.sendMessage("content-script-done"); + }, + { once: true } + ); + } + }, + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + + await extension.startup(); + + let contentPage = await ExtensionTestUtils.loadContentPage( + `${BASE_URL}/file_document_write.html` + ); + + await extension.awaitMessage("content-script-done"); + + await contentPage.close(); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_teardown.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_teardown.js new file mode 100644 index 0000000000..2bf7981657 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_teardown.js @@ -0,0 +1,102 @@ +"use strict"; + +const server = createHttpServer({ hosts: ["example.com"] }); +server.registerDirectory("/data/", do_get_file("data")); + +add_task(async function test_contentscript_reload_and_unload() { + let extensionData = { + manifest: { + content_scripts: [ + { + matches: ["http://example.com/data/file_sample.html"], + js: ["contentscript.js"], + }, + ], + }, + + files: { + "contentscript.js"() { + browser.test.sendMessage("contentscript-run"); + }, + }, + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + + let events = []; + { + let { Management } = ChromeUtils.import( + "resource://gre/modules/Extension.jsm", + null + ); + let record = (type, extensionContext) => { + let eventType = type == "proxy-context-load" ? "load" : "unload"; + let url = extensionContext.uri.spec; + let extensionId = extensionContext.extension.id; + events.push({ eventType, url, extensionId }); + }; + + Management.on("proxy-context-load", record); + Management.on("proxy-context-unload", record); + registerCleanupFunction(() => { + Management.off("proxy-context-load", record); + Management.off("proxy-context-unload", record); + }); + } + + const tabUrl = "http://example.com/data/file_sample.html"; + let contentPage = await ExtensionTestUtils.loadContentPage(tabUrl); + + await extension.awaitMessage("contentscript-run"); + + let contextEvents = events.splice(0); + equal( + contextEvents.length, + 1, + "ExtensionContext state change after loading a content script" + ); + equal( + contextEvents[0].eventType, + "load", + "Create ExtensionContext for content script" + ); + equal(contextEvents[0].url, tabUrl, "ExtensionContext URL = page"); + + await contentPage.spawn(null, () => { + this.content.location.reload(); + }); + await extension.awaitMessage("contentscript-run"); + + contextEvents = events.splice(0); + equal( + contextEvents.length, + 2, + "ExtensionContext state changes after reloading a content script" + ); + equal(contextEvents[0].eventType, "unload", "Unload old ExtensionContext"); + equal(contextEvents[0].url, tabUrl, "ExtensionContext URL = page"); + equal( + contextEvents[1].eventType, + "load", + "Create new ExtensionContext for content script" + ); + equal(contextEvents[1].url, tabUrl, "ExtensionContext URL = page"); + + await contentPage.close(); + + contextEvents = events.splice(0); + equal( + contextEvents.length, + 1, + "ExtensionContext state change after unloading a content script" + ); + equal( + contextEvents[0].eventType, + "unload", + "Unload ExtensionContext after closing the tab with the content script" + ); + equal(contextEvents[0].url, tabUrl, "ExtensionContext URL = page"); + + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js new file mode 100644 index 0000000000..f5df8e61d2 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js @@ -0,0 +1,1373 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/** + * Tests that various types of inline content elements initiate requests + * with the triggering pringipal of the caller that requested the load, + * and that the correct security policies are applied to the resulting + * loads. + */ + +const env = Cc["@mozilla.org/process/environment;1"].getService( + Ci.nsIEnvironment +); + +// Make sure media pre-loading is enabled on Android so that our