From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- dom/tests/browser/.eslintrc.js | 12 + dom/tests/browser/beforeunload_test_page.html | 92 +++ dom/tests/browser/browser.ini | 140 ++++ .../browser/browser_ConsoleAPI_originAttributes.js | 108 +++ .../browser/browser_ConsoleStorageAPITests.js | 108 +++ .../browser_ConsoleStoragePBTest_perwindowpb.js | 93 +++ dom/tests/browser/browser_autofocus_background.js | 59 ++ dom/tests/browser/browser_autofocus_preference.js | 16 + .../browser_beforeunload_between_chrome_content.js | 152 ++++ dom/tests/browser/browser_bug1004814.js | 46 ++ .../browser_bug1008941_dismissGeolocationHanger.js | 39 + dom/tests/browser/browser_bug1236512.js | 116 +++ dom/tests/browser/browser_bug1238427.js | 40 + dom/tests/browser/browser_bug1316330.js | 52 ++ dom/tests/browser/browser_bug1563629.js | 79 ++ dom/tests/browser/browser_bug1685807.js | 78 ++ dom/tests/browser/browser_bug1709346.js | 48 ++ dom/tests/browser/browser_bug396843.js | 337 ++++++++ dom/tests/browser/browser_bytecode_cache_asm_js.js | 31 + .../browser_cancel_keydown_keypress_event.js | 41 + .../browser_data_document_crossOriginIsolated.js | 68 ++ .../browser/browser_focus_steal_from_chrome.js | 214 +++++ ...ser_focus_steal_from_chrome_during_mousedown.js | 76 ++ ...ser_form_associated_custom_elements_validity.js | 111 +++ dom/tests/browser/browser_frame_elements.html | 15 + dom/tests/browser/browser_frame_elements.js | 74 ++ .../browser/browser_hasActivePeerConnections.js | 134 ++++ dom/tests/browser/browser_hasbeforeunload.js | 875 +++++++++++++++++++++ dom/tests/browser/browser_keypressTelemetry.js | 69 ++ dom/tests/browser/browser_localStorage_e10s.js | 284 +++++++ dom/tests/browser/browser_localStorage_fis.js | 529 +++++++++++++ .../browser_localStorage_privatestorageevent.js | 87 ++ .../browser/browser_localStorage_snapshotting.js | 774 ++++++++++++++++++ .../browser_navigate_replace_browsingcontext.js | 23 + dom/tests/browser/browser_noopener.js | 182 +++++ dom/tests/browser/browser_noopener_null_uri.js | 15 + dom/tests/browser/browser_persist_cookies.js | 128 +++ .../browser/browser_persist_cross_origin_iframe.js | 198 +++++ dom/tests/browser/browser_persist_image_accept.js | 140 ++++ .../browser/browser_persist_mixed_content_image.js | 109 +++ dom/tests/browser/browser_pointerlock_warning.js | 129 +++ .../browser/browser_sessionStorage_navigation.js | 271 +++++++ .../browser_test_focus_after_modal_state.js | 71 ++ .../browser_test_new_window_from_content.js | 221 ++++++ .../browser/browser_test_toolbars_visibility.js | 323 ++++++++ ...nlinkable_about_page_can_load_module_scripts.js | 83 ++ dom/tests/browser/browser_wakelock.js | 40 + .../browser/browser_windowProxy_transplant.js | 215 +++++ dom/tests/browser/browser_xhr_sandbox.js | 62 ++ .../browser/create_webrtc_peer_connection.html | 28 + dom/tests/browser/dummy.html | 13 + dom/tests/browser/dummy.png | Bin 0 -> 703 bytes dom/tests/browser/file_bug1685807.html | 12 + dom/tests/browser/file_coop_coep.html | 6 + dom/tests/browser/file_coop_coep.html^headers^ | 2 + dom/tests/browser/file_empty.html | 0 dom/tests/browser/file_empty_cross_site_frame.html | 2 + dom/tests/browser/file_load_module_script.html | 8 + dom/tests/browser/file_module_loaded.js | 6 + dom/tests/browser/file_module_loaded2.js | 3 + dom/tests/browser/file_postMessage_parent.html | 48 ++ dom/tests/browser/focus_after_prompt.html | 18 + dom/tests/browser/geo_leak_test.html | 17 + dom/tests/browser/helper_localStorage.js | 302 +++++++ dom/tests/browser/image.html | 2 + dom/tests/browser/load_forever.sjs | 15 + dom/tests/browser/mimeme.sjs | 32 + dom/tests/browser/page_bytecode_cache_asm_js.html | 10 + dom/tests/browser/page_bytecode_cache_asm_js.js | 30 + dom/tests/browser/page_localStorage.js | 127 +++ dom/tests/browser/page_localstorage.html | 8 + dom/tests/browser/page_localstorage_coop+coep.html | 8 + .../page_localstorage_coop+coep.html^headers^ | 2 + .../browser/page_localstorage_snapshotting.html | 68 ++ dom/tests/browser/page_privatestorageevent.html | 5 + dom/tests/browser/perfmetrics/browser.ini | 21 + .../browser_test_performance_metrics.js | 201 +++++ .../perfmetrics/browser_test_unresponsive.js | 31 + dom/tests/browser/perfmetrics/dummy.html | 13 + dom/tests/browser/perfmetrics/hello.ogg | Bin 0 -> 11328 bytes dom/tests/browser/perfmetrics/ping_worker.html | 26 + dom/tests/browser/perfmetrics/ping_worker.js | 11 + dom/tests/browser/perfmetrics/ping_worker2.html | 20 + dom/tests/browser/perfmetrics/setinterval.html | 19 + dom/tests/browser/perfmetrics/settimeout.html | 17 + dom/tests/browser/perfmetrics/shared_worker.js | 7 + dom/tests/browser/perfmetrics/sound.html | 14 + dom/tests/browser/perfmetrics/unresponsive.html | 21 + dom/tests/browser/position.html | 31 + dom/tests/browser/prevent_return_key.html | 34 + .../browser/set-samesite-cookies-and-redirect.sjs | 43 + dom/tests/browser/test-console-api.html | 78 ++ dom/tests/browser/test_bug1004814.html | 8 + dom/tests/browser/test_mixed_content_image.html | 1 + .../test_new_window_from_content_child.html | 25 + dom/tests/browser/test_noopener_source.html | 15 + dom/tests/browser/test_noopener_target.html | 9 + dom/tests/browser/worker_bug1004814.js | 6 + 98 files changed, 8630 insertions(+) create mode 100644 dom/tests/browser/.eslintrc.js create mode 100644 dom/tests/browser/beforeunload_test_page.html create mode 100644 dom/tests/browser/browser.ini create mode 100644 dom/tests/browser/browser_ConsoleAPI_originAttributes.js create mode 100644 dom/tests/browser/browser_ConsoleStorageAPITests.js create mode 100644 dom/tests/browser/browser_ConsoleStoragePBTest_perwindowpb.js create mode 100644 dom/tests/browser/browser_autofocus_background.js create mode 100644 dom/tests/browser/browser_autofocus_preference.js create mode 100644 dom/tests/browser/browser_beforeunload_between_chrome_content.js create mode 100644 dom/tests/browser/browser_bug1004814.js create mode 100644 dom/tests/browser/browser_bug1008941_dismissGeolocationHanger.js create mode 100644 dom/tests/browser/browser_bug1236512.js create mode 100644 dom/tests/browser/browser_bug1238427.js create mode 100644 dom/tests/browser/browser_bug1316330.js create mode 100644 dom/tests/browser/browser_bug1563629.js create mode 100644 dom/tests/browser/browser_bug1685807.js create mode 100644 dom/tests/browser/browser_bug1709346.js create mode 100644 dom/tests/browser/browser_bug396843.js create mode 100644 dom/tests/browser/browser_bytecode_cache_asm_js.js create mode 100644 dom/tests/browser/browser_cancel_keydown_keypress_event.js create mode 100644 dom/tests/browser/browser_data_document_crossOriginIsolated.js create mode 100644 dom/tests/browser/browser_focus_steal_from_chrome.js create mode 100644 dom/tests/browser/browser_focus_steal_from_chrome_during_mousedown.js create mode 100644 dom/tests/browser/browser_form_associated_custom_elements_validity.js create mode 100644 dom/tests/browser/browser_frame_elements.html create mode 100644 dom/tests/browser/browser_frame_elements.js create mode 100644 dom/tests/browser/browser_hasActivePeerConnections.js create mode 100644 dom/tests/browser/browser_hasbeforeunload.js create mode 100644 dom/tests/browser/browser_keypressTelemetry.js create mode 100644 dom/tests/browser/browser_localStorage_e10s.js create mode 100644 dom/tests/browser/browser_localStorage_fis.js create mode 100644 dom/tests/browser/browser_localStorage_privatestorageevent.js create mode 100644 dom/tests/browser/browser_localStorage_snapshotting.js create mode 100644 dom/tests/browser/browser_navigate_replace_browsingcontext.js create mode 100644 dom/tests/browser/browser_noopener.js create mode 100644 dom/tests/browser/browser_noopener_null_uri.js create mode 100644 dom/tests/browser/browser_persist_cookies.js create mode 100644 dom/tests/browser/browser_persist_cross_origin_iframe.js create mode 100644 dom/tests/browser/browser_persist_image_accept.js create mode 100644 dom/tests/browser/browser_persist_mixed_content_image.js create mode 100644 dom/tests/browser/browser_pointerlock_warning.js create mode 100644 dom/tests/browser/browser_sessionStorage_navigation.js create mode 100644 dom/tests/browser/browser_test_focus_after_modal_state.js create mode 100644 dom/tests/browser/browser_test_new_window_from_content.js create mode 100644 dom/tests/browser/browser_test_toolbars_visibility.js create mode 100644 dom/tests/browser/browser_unlinkable_about_page_can_load_module_scripts.js create mode 100644 dom/tests/browser/browser_wakelock.js create mode 100644 dom/tests/browser/browser_windowProxy_transplant.js create mode 100644 dom/tests/browser/browser_xhr_sandbox.js create mode 100644 dom/tests/browser/create_webrtc_peer_connection.html create mode 100644 dom/tests/browser/dummy.html create mode 100644 dom/tests/browser/dummy.png create mode 100644 dom/tests/browser/file_bug1685807.html create mode 100644 dom/tests/browser/file_coop_coep.html create mode 100644 dom/tests/browser/file_coop_coep.html^headers^ create mode 100644 dom/tests/browser/file_empty.html create mode 100644 dom/tests/browser/file_empty_cross_site_frame.html create mode 100644 dom/tests/browser/file_load_module_script.html create mode 100644 dom/tests/browser/file_module_loaded.js create mode 100644 dom/tests/browser/file_module_loaded2.js create mode 100644 dom/tests/browser/file_postMessage_parent.html create mode 100644 dom/tests/browser/focus_after_prompt.html create mode 100644 dom/tests/browser/geo_leak_test.html create mode 100644 dom/tests/browser/helper_localStorage.js create mode 100644 dom/tests/browser/image.html create mode 100644 dom/tests/browser/load_forever.sjs create mode 100644 dom/tests/browser/mimeme.sjs create mode 100644 dom/tests/browser/page_bytecode_cache_asm_js.html create mode 100644 dom/tests/browser/page_bytecode_cache_asm_js.js create mode 100644 dom/tests/browser/page_localStorage.js create mode 100644 dom/tests/browser/page_localstorage.html create mode 100644 dom/tests/browser/page_localstorage_coop+coep.html create mode 100644 dom/tests/browser/page_localstorage_coop+coep.html^headers^ create mode 100644 dom/tests/browser/page_localstorage_snapshotting.html create mode 100644 dom/tests/browser/page_privatestorageevent.html create mode 100644 dom/tests/browser/perfmetrics/browser.ini create mode 100644 dom/tests/browser/perfmetrics/browser_test_performance_metrics.js create mode 100644 dom/tests/browser/perfmetrics/browser_test_unresponsive.js create mode 100644 dom/tests/browser/perfmetrics/dummy.html create mode 100644 dom/tests/browser/perfmetrics/hello.ogg create mode 100644 dom/tests/browser/perfmetrics/ping_worker.html create mode 100644 dom/tests/browser/perfmetrics/ping_worker.js create mode 100644 dom/tests/browser/perfmetrics/ping_worker2.html create mode 100644 dom/tests/browser/perfmetrics/setinterval.html create mode 100644 dom/tests/browser/perfmetrics/settimeout.html create mode 100644 dom/tests/browser/perfmetrics/shared_worker.js create mode 100644 dom/tests/browser/perfmetrics/sound.html create mode 100644 dom/tests/browser/perfmetrics/unresponsive.html create mode 100644 dom/tests/browser/position.html create mode 100644 dom/tests/browser/prevent_return_key.html create mode 100644 dom/tests/browser/set-samesite-cookies-and-redirect.sjs create mode 100644 dom/tests/browser/test-console-api.html create mode 100644 dom/tests/browser/test_bug1004814.html create mode 100644 dom/tests/browser/test_mixed_content_image.html create mode 100644 dom/tests/browser/test_new_window_from_content_child.html create mode 100644 dom/tests/browser/test_noopener_source.html create mode 100644 dom/tests/browser/test_noopener_target.html create mode 100644 dom/tests/browser/worker_bug1004814.js (limited to 'dom/tests/browser') diff --git a/dom/tests/browser/.eslintrc.js b/dom/tests/browser/.eslintrc.js new file mode 100644 index 0000000000..e247fd78e9 --- /dev/null +++ b/dom/tests/browser/.eslintrc.js @@ -0,0 +1,12 @@ +"use strict"; + +module.exports = { + overrides: [ + { + files: ["file_module_loaded.js", "file_module_loaded2.js"], + parserOptions: { + sourceType: "module", + }, + }, + ], +}; diff --git a/dom/tests/browser/beforeunload_test_page.html b/dom/tests/browser/beforeunload_test_page.html new file mode 100644 index 0000000000..12dc636e23 --- /dev/null +++ b/dom/tests/browser/beforeunload_test_page.html @@ -0,0 +1,92 @@ + + + + +

+ There are currently 0 beforeunload handlers registered in this frame. +

+

+ There are currently 0 beforeunload handlers registered on my subframe. +

+ + + + + + + diff --git a/dom/tests/browser/browser.ini b/dom/tests/browser/browser.ini new file mode 100644 index 0000000000..2fae2c0f4a --- /dev/null +++ b/dom/tests/browser/browser.ini @@ -0,0 +1,140 @@ +[DEFAULT] +support-files = + browser_frame_elements.html + page_privatestorageevent.html + page_localStorage.js + page_localstorage.html + page_localstorage_coop+coep.html + page_localstorage_coop+coep.html^headers^ + page_localstorage_snapshotting.html + position.html + test-console-api.html + test_bug1004814.html + worker_bug1004814.js + geo_leak_test.html + dummy.html + dummy.png + helper_localStorage.js + !/dom/tests/mochitest/geolocation/network_geolocation.sjs + +[browser_autofocus_background.js] +[browser_autofocus_preference.js] +[browser_beforeunload_between_chrome_content.js] +https_first_disabled = true +[browser_bug396843.js] +[browser_bug1004814.js] +[browser_bug1008941_dismissGeolocationHanger.js] +tags = geolocation +[browser_bug1236512.js] +skip-if = os != "mac" +[browser_bug1238427.js] +[browser_bug1316330.js] +[browser_bug1563629.js] +support-files = + file_postMessage_parent.html +[browser_bug1685807.js] +support-files = + file_bug1685807.html +skip-if = + os == "win" && os_version == "6.1" # Skip on Azure - frequent failure +[browser_cancel_keydown_keypress_event.js] +support-files = + prevent_return_key.html +[browser_ConsoleAPI_originAttributes.js] +[browser_ConsoleStorageAPITests.js] +[browser_ConsoleStoragePBTest_perwindowpb.js] +[browser_data_document_crossOriginIsolated.js] +[browser_focus_steal_from_chrome.js] +[browser_focus_steal_from_chrome_during_mousedown.js] +[browser_form_associated_custom_elements_validity.js] +support-files = + file_empty.html +[browser_frame_elements.js] +[browser_hasbeforeunload.js] +https_first_disabled = true +support-files = + beforeunload_test_page.html +[browser_hasActivePeerConnections.js] +support-files = + create_webrtc_peer_connection.html +skip-if = + os == "linux" && bits == 64 # Bug 1742012 + os == "win" && debug # Bug 1742012 + os == "win" && os_version == "6.1" # Skip on Azure - frequent failure +[browser_localStorage_e10s.js] +https_first_disabled = true +fail-if = fission +skip-if = + verify + tsan # Times out on TSan intermittently. +[browser_localStorage_fis.js] +skip-if = + verify + tsan + !fission +[browser_localStorage_privatestorageevent.js] +[browser_localStorage_snapshotting.js] +[browser_persist_cookies.js] +support-files = + set-samesite-cookies-and-redirect.sjs + mimeme.sjs +[browser_persist_image_accept.js] +[browser_persist_mixed_content_image.js] +support-files = + test_mixed_content_image.html +[browser_pointerlock_warning.js] +[browser_sessionStorage_navigation.js] +skip-if = + os == "linux" && bits == 64 && !debug # Bug 1712961 +support-files = + file_empty.html + file_coop_coep.html + file_coop_coep.html^headers^ +[browser_test_focus_after_modal_state.js] +skip-if = verify +support-files = + focus_after_prompt.html +[browser_test_new_window_from_content.js] +tags = openwindow +skip-if = toolkit == 'android' + (os == "linux" && debug) # see bug 1261495 for Linux debug time outs +support-files = + test_new_window_from_content_child.html +[browser_test_toolbars_visibility.js] +https_first_disabled = true +support-files = + test_new_window_from_content_child.html +skip-if = + os == "win" && os_version == "6.1" # Skip on Azure - frequent failure +[browser_unlinkable_about_page_can_load_module_scripts.js] +support-files = + file_load_module_script.html + file_module_loaded.js + file_module_loaded2.js +[browser_xhr_sandbox.js] +[browser_noopener.js] +skip-if = verify && debug && (os == 'linux') +support-files = + test_noopener_source.html + test_noopener_target.html +[browser_noopener_null_uri.js] +[browser_wakelock.js] +[browser_keypressTelemetry.js] +skip-if = true +[browser_windowProxy_transplant.js] +support-files = + file_postMessage_parent.html +[browser_navigate_replace_browsingcontext.js] +[browser_persist_cross_origin_iframe.js] +skip-if = + os == "win" && os_version == "6.1" # Skip on Azure - frequent failure +support-files = + image.html +[browser_bytecode_cache_asm_js.js] +support-files = + page_bytecode_cache_asm_js.html + page_bytecode_cache_asm_js.js +[browser_bug1709346.js] +support-files = + load_forever.sjs + file_empty_cross_site_frame.html diff --git a/dom/tests/browser/browser_ConsoleAPI_originAttributes.js b/dom/tests/browser/browser_ConsoleAPI_originAttributes.js new file mode 100644 index 0000000000..9e96dba728 --- /dev/null +++ b/dom/tests/browser/browser_ConsoleAPI_originAttributes.js @@ -0,0 +1,108 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService( + Ci.nsIConsoleAPIStorage +); + +const { WebExtensionPolicy } = Cu.getGlobalForObject( + ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs") +); + +const FAKE_ADDON_ID = "test-webext-addon@mozilla.org"; +const EXPECTED_CONSOLE_ID = `addon/${FAKE_ADDON_ID}`; +const EXPECTED_CONSOLE_MESSAGE_CONTENT = "fake-webext-addon-test-log-message"; + +const ConsoleObserver = { + init() { + ConsoleAPIStorage.addLogEventListener( + this.observe, + Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal) + ); + }, + + uninit() { + ConsoleAPIStorage.removeLogEventListener(this.observe); + }, + + observe(aSubject) { + let consoleAPIMessage = aSubject.wrappedJSObject; + + is( + consoleAPIMessage.arguments[0], + EXPECTED_CONSOLE_MESSAGE_CONTENT, + "the consoleAPIMessage contains the expected message" + ); + + is( + consoleAPIMessage.addonId, + FAKE_ADDON_ID, + "the consoleAPImessage originAttributes contains the expected addonId" + ); + + let cachedMessages = ConsoleAPIStorage.getEvents().filter(msg => { + return msg.addonId == FAKE_ADDON_ID; + }); + + is( + cachedMessages.length, + 1, + "found the expected cached console messages from the addon" + ); + is( + cachedMessages[0] && cachedMessages[0].addonId, + FAKE_ADDON_ID, + "the cached message originAttributes contains the expected addonId" + ); + + finish(); + }, +}; + +function test() { + ConsoleObserver.init(); + + waitForExplicitFinish(); + + let uuidGenerator = Services.uuid; + let uuid = uuidGenerator.generateUUID().number; + uuid = uuid.slice(1, -1); // Strip { and } off the UUID. + + const url = `moz-extension://${uuid}/`; + let policy = new WebExtensionPolicy({ + id: FAKE_ADDON_ID, + mozExtensionHostname: uuid, + baseURL: "file:///", + allowedOrigins: new MatchPatternSet([]), + localizeCallback() {}, + }); + policy.active = true; + + let baseURI = Services.io.newURI(url); + let principal = Services.scriptSecurityManager.createContentPrincipal( + baseURI, + {} + ); + + let chromeWebNav = Services.appShell.createWindowlessBrowser(true); + let docShell = chromeWebNav.docShell; + docShell.createAboutBlankContentViewer(principal, principal); + + info("fake webextension docShell created"); + + registerCleanupFunction(function () { + policy.active = false; + if (chromeWebNav) { + chromeWebNav.close(); + chromeWebNav = null; + } + ConsoleObserver.uninit(); + }); + + let window = docShell.contentViewer.DOMDocument.defaultView; + window.eval(`console.log("${EXPECTED_CONSOLE_MESSAGE_CONTENT}");`); + chromeWebNav.close(); + chromeWebNav = null; + + info("fake webextension page logged a console api message"); +} diff --git a/dom/tests/browser/browser_ConsoleStorageAPITests.js b/dom/tests/browser/browser_ConsoleStorageAPITests.js new file mode 100644 index 0000000000..f28db48a91 --- /dev/null +++ b/dom/tests/browser/browser_ConsoleStorageAPITests.js @@ -0,0 +1,108 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TEST_URI = + "http://example.com/browser/dom/tests/browser/test-console-api.html"; + +function tearDown() { + while (gBrowser.tabs.length > 1) { + gBrowser.removeCurrentTab(); + } +} + +add_task(async function () { + // Don't cache removed tabs, so "clear console cache on tab close" triggers. + await SpecialPowers.pushPrefEnv({ set: [["browser.tabs.max_tabs_undo", 0]] }); + + registerCleanupFunction(tearDown); + + info( + "Open a keepalive tab in the background to make sure we don't accidentally kill the content process" + ); + var keepaliveTab = await BrowserTestUtils.addTab( + gBrowser, + "data:text/html,Keep Alive Tab" + ); + + info("Open the main tab to run the test in"); + var tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI); + var browser = gBrowser.selectedBrowser; + + const windowId = await ContentTask.spawn(browser, null, async function (opt) { + let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService( + Ci.nsIConsoleAPIStorage + ); + + let observerPromise = new Promise(resolve => { + let apiCallCount = 0; + function observe(aSubject) { + apiCallCount++; + info(`Received ${apiCallCount} console log events`); + if (apiCallCount == 4) { + ConsoleAPIStorage.removeLogEventListener(observe); + resolve(); + } + } + + info("Setting up observer"); + ConsoleAPIStorage.addLogEventListener( + observe, + Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal) + ); + }); + + info("Emit a few console API logs"); + content.console.log("this", "is", "a", "log", "message"); + content.console.info("this", "is", "a", "info", "message"); + content.console.warn("this", "is", "a", "warn", "message"); + content.console.error("this", "is", "a", "error", "message"); + + info("Wait for the corresponding log event"); + await observerPromise; + + const innerWindowId = content.windowGlobalChild.innerWindowId; + const events = ConsoleAPIStorage.getEvents(innerWindowId).filter( + message => + message.arguments[0] === "this" && + message.arguments[1] === "is" && + message.arguments[2] === "a" && + message.arguments[4] === "message" + ); + is(events.length, 4, "The storage service got the messages we emitted"); + + info("Ensure clearEvents does remove the events from storage"); + ConsoleAPIStorage.clearEvents(); + is(ConsoleAPIStorage.getEvents(innerWindowId).length, 0, "Cleared Storage"); + + return content.windowGlobalChild.innerWindowId; + }); + + await SpecialPowers.spawn(browser, [], function () { + // make sure a closed window's events are in fact removed from + // the storage cache + content.console.log("adding a new event"); + }); + + info("Close the window"); + gBrowser.removeTab(tab, { animate: false }); + // Ensure actual window destruction is not delayed (too long). + SpecialPowers.DOMWindowUtils.garbageCollect(); + + // Spawn the check in the keepaliveTab, so that we can read the ConsoleAPIStorage correctly + gBrowser.selectedTab = keepaliveTab; + browser = gBrowser.selectedBrowser; + + // Spin the event loop to make sure everything is cleared. + await SpecialPowers.spawn(browser, [], () => {}); + + await SpecialPowers.spawn(browser, [windowId], function (windowId) { + var ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService( + Ci.nsIConsoleAPIStorage + ); + is( + ConsoleAPIStorage.getEvents(windowId).length, + 0, + "tab close is clearing the cache" + ); + }); +}); diff --git a/dom/tests/browser/browser_ConsoleStoragePBTest_perwindowpb.js b/dom/tests/browser/browser_ConsoleStoragePBTest_perwindowpb.js new file mode 100644 index 0000000000..79b5a820ff --- /dev/null +++ b/dom/tests/browser/browser_ConsoleStoragePBTest_perwindowpb.js @@ -0,0 +1,93 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +function test() { + // initialization + waitForExplicitFinish(); + let windowsToClose = []; + let innerID; + let beforeEvents; + let afterEvents; + let storageShouldOccur; + let testURI = + "http://example.com/browser/dom/tests/browser/test-console-api.html"; + let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService( + Ci.nsIConsoleAPIStorage + ); + + function getInnerWindowId(aWindow) { + return aWindow.windowGlobalChild.innerWindowId; + } + + function whenNewWindowLoaded(aOptions, aCallback) { + let win = OpenBrowserWindow(aOptions); + win.addEventListener( + "load", + function () { + aCallback(win); + }, + { once: true } + ); + } + + function doTest(aIsPrivateMode, aWindow, aCallback) { + BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser).then( + () => { + function observe(aSubject) { + afterEvents = ConsoleAPIStorage.getEvents(innerID); + is( + beforeEvents.length == afterEvents.length - 1, + storageShouldOccur, + "storage should" + (storageShouldOccur ? "" : " not") + " occur" + ); + + executeSoon(function () { + ConsoleAPIStorage.removeLogEventListener(observe); + aCallback(); + }); + } + + ConsoleAPIStorage.addLogEventListener( + observe, + aWindow.document.nodePrincipal + ); + aWindow.nativeConsole.log( + "foo bar baz (private: " + aIsPrivateMode + ")" + ); + } + ); + + // We expect that console API messages are always stored. + storageShouldOccur = true; + innerID = getInnerWindowId(aWindow); + beforeEvents = ConsoleAPIStorage.getEvents(innerID); + BrowserTestUtils.loadURIString(aWindow.gBrowser.selectedBrowser, testURI); + } + + function testOnWindow(aOptions, aCallback) { + whenNewWindowLoaded(aOptions, function (aWin) { + windowsToClose.push(aWin); + // execute should only be called when need, like when you are opening + // web pages on the test. If calling executeSoon() is not necesary, then + // call whenNewWindowLoaded() instead of testOnWindow() on your test. + executeSoon(() => aCallback(aWin)); + }); + } + + // this function is called after calling finish() on the test. + registerCleanupFunction(function () { + windowsToClose.forEach(function (aWin) { + aWin.close(); + }); + }); + + // test first when not on private mode + testOnWindow({}, function (aWin) { + doTest(false, aWin, function () { + // then test when on private mode + testOnWindow({ private: true }, function (aWin) { + doTest(true, aWin, finish); + }); + }); + }); +} diff --git a/dom/tests/browser/browser_autofocus_background.js b/dom/tests/browser/browser_autofocus_background.js new file mode 100644 index 0000000000..ecac9a9a98 --- /dev/null +++ b/dom/tests/browser/browser_autofocus_background.js @@ -0,0 +1,59 @@ +add_task(async function () { + const URL = + "data:text/html,"; + const foregroundTab = gBrowser.selectedTab; + const backgroundTab = BrowserTestUtils.addTab(gBrowser); + + // Ensure tab is still in the foreground. + is( + gBrowser.selectedTab, + foregroundTab, + "foregroundTab should still be selected" + ); + + // Load the second tab in the background. + const loadedPromise = BrowserTestUtils.browserLoaded( + backgroundTab.linkedBrowser, + /* includesubframes */ false, + URL + ); + BrowserTestUtils.loadURIString(backgroundTab.linkedBrowser, URL); + await loadedPromise; + + // Get active element in the tab. + let tagName = await SpecialPowers.spawn( + backgroundTab.linkedBrowser, + [], + async function () { + // Spec asks us to flush autofocus candidates in the + // `update-the-rendering` step, so we need to wait + // for a rAF to ensure autofocus candidates are + // flushed. + await new Promise(r => { + content.requestAnimationFrame(r); + }); + return content.document.activeElement.tagName; + } + ); + + is( + tagName, + "INPUT", + "The background tab's focused element should be the " + ); + + is( + gBrowser.selectedTab, + foregroundTab, + "foregroundTab tab should still be selected, shouldn't cause a tab switch" + ); + + is( + document.activeElement, + foregroundTab.linkedBrowser, + "The background tab's focused element should not cause the tab to be selected" + ); + + // Cleaning up. + BrowserTestUtils.removeTab(backgroundTab); +}); diff --git a/dom/tests/browser/browser_autofocus_preference.js b/dom/tests/browser/browser_autofocus_preference.js new file mode 100644 index 0000000000..fc8b318a17 --- /dev/null +++ b/dom/tests/browser/browser_autofocus_preference.js @@ -0,0 +1,16 @@ +add_task(async function () { + await SpecialPowers.pushPrefEnv({ set: [["browser.autofocus", false]] }); + + const url = + "data:text/html,"; + + let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, url); + await loadedPromise; + + await new Promise(resolve => executeSoon(resolve)); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { + is(content.document.activeElement, content.document.body, "body focused"); + }); +}); diff --git a/dom/tests/browser/browser_beforeunload_between_chrome_content.js b/dom/tests/browser/browser_beforeunload_between_chrome_content.js new file mode 100644 index 0000000000..67579592ed --- /dev/null +++ b/dom/tests/browser/browser_beforeunload_between_chrome_content.js @@ -0,0 +1,152 @@ +const TEST_URL = "http://www.example.com/browser/dom/tests/browser/dummy.html"; + +const { PromptTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromptTestUtils.sys.mjs" +); + +function pageScript() { + window.addEventListener( + "beforeunload", + function (event) { + var str = "Leaving?"; + event.returnValue = str; + return str; + }, + true + ); +} + +function injectBeforeUnload(browser) { + return ContentTask.spawn(browser, null, async function () { + content.window.addEventListener( + "beforeunload", + function (event) { + sendAsyncMessage("Test:OnBeforeUnloadReceived"); + var str = "Leaving?"; + event.returnValue = str; + return str; + }, + true + ); + }); +} + +// Wait for onbeforeunload dialog, and dismiss it immediately. +function awaitAndCloseBeforeUnloadDialog(browser, doStayOnPage) { + return PromptTestUtils.handleNextPrompt( + browser, + { modalType: Services.prompt.MODAL_TYPE_CONTENT, promptType: "confirmEx" }, + { buttonNumClick: doStayOnPage ? 1 : 0 } + ); +} + +SpecialPowers.pushPrefEnv({ + set: [["dom.require_user_interaction_for_beforeunload", false]], +}); + +/** + * Test navigation from a content page to a chrome page. Also check that only + * one beforeunload event is fired. + */ +/* global messageManager */ +add_task(async function () { + let beforeUnloadCount = 0; + messageManager.addMessageListener("Test:OnBeforeUnloadReceived", function () { + beforeUnloadCount++; + }); + + // Open a content page. + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + let browser = tab.linkedBrowser; + + ok(browser.isRemoteBrowser, "Browser should be remote."); + + await injectBeforeUnload(browser); + // Navigate to a chrome page. + let dialogShown1 = awaitAndCloseBeforeUnloadDialog(browser, false); + BrowserTestUtils.loadURIString(browser, "about:support"); + await Promise.all([dialogShown1, BrowserTestUtils.browserLoaded(browser)]); + + is(beforeUnloadCount, 1, "Should have received one beforeunload event."); + ok(!browser.isRemoteBrowser, "Browser should not be remote."); + + // Go back to content page. + ok(gBrowser.webNavigation.canGoBack, "Should be able to go back."); + gBrowser.goBack(); + await BrowserTestUtils.browserLoaded(browser); + await injectBeforeUnload(browser); + + // Test that going forward triggers beforeunload prompt as well. + ok(gBrowser.webNavigation.canGoForward, "Should be able to go forward."); + let dialogShown2 = awaitAndCloseBeforeUnloadDialog(false); + gBrowser.goForward(); + await Promise.all([dialogShown2, BrowserTestUtils.browserLoaded(browser)]); + is(beforeUnloadCount, 2, "Should have received two beforeunload events."); + + BrowserTestUtils.removeTab(tab); +}); + +/** + * Test navigation from a chrome page to a content page. Also check that only + * one beforeunload event is fired. + */ +add_task(async function () { + let beforeUnloadCount = 0; + messageManager.addMessageListener("Test:OnBeforeUnloadReceived", function () { + beforeUnloadCount++; + }); + + // Open a chrome page. + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:support" + ); + let browser = tab.linkedBrowser; + + ok(!browser.isRemoteBrowser, "Browser should not be remote."); + await ContentTask.spawn(browser, null, async function () { + content.window.addEventListener( + "beforeunload", + function (event) { + sendAsyncMessage("Test:OnBeforeUnloadReceived"); + var str = "Leaving?"; + event.returnValue = str; + return str; + }, + true + ); + }); + + // Navigate to a content page. + let dialogShown1 = awaitAndCloseBeforeUnloadDialog(false); + BrowserTestUtils.loadURIString(browser, TEST_URL); + await Promise.all([dialogShown1, BrowserTestUtils.browserLoaded(browser)]); + is(beforeUnloadCount, 1, "Should have received one beforeunload event."); + ok(browser.isRemoteBrowser, "Browser should be remote."); + + // Go back to chrome page. + ok(gBrowser.webNavigation.canGoBack, "Should be able to go back."); + gBrowser.goBack(); + await BrowserTestUtils.browserLoaded(browser); + await ContentTask.spawn(browser, null, async function () { + content.window.addEventListener( + "beforeunload", + function (event) { + sendAsyncMessage("Test:OnBeforeUnloadReceived"); + var str = "Leaving?"; + event.returnValue = str; + return str; + }, + true + ); + }); + + // Test that going forward triggers beforeunload prompt as well. + ok(gBrowser.webNavigation.canGoForward, "Should be able to go forward."); + let dialogShown2 = awaitAndCloseBeforeUnloadDialog(false); + gBrowser.goForward(); + await Promise.all([dialogShown2, BrowserTestUtils.browserLoaded(browser)]); + is(beforeUnloadCount, 2, "Should have received two beforeunload events."); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/dom/tests/browser/browser_bug1004814.js b/dom/tests/browser/browser_bug1004814.js new file mode 100644 index 0000000000..efe3bd44f9 --- /dev/null +++ b/dom/tests/browser/browser_bug1004814.js @@ -0,0 +1,46 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function () { + const TEST_URI = + "http://example.com/browser/dom/tests/browser/test_bug1004814.html"; + + await BrowserTestUtils.withNewTab(TEST_URI, async aBrowser => { + let duration = await SpecialPowers.spawn(aBrowser, [], function (opts) { + const ConsoleAPIStorage = Cc[ + "@mozilla.org/consoleAPI-storage;1" + ].getService(Ci.nsIConsoleAPIStorage); + + return new Promise(resolve => { + function observe(aSubject) { + var obj = aSubject.wrappedJSObject; + if ( + obj.arguments.length != 1 || + obj.arguments[0] != "bug1004814" || + obj.level != "timeEnd" + ) { + return; + } + + ConsoleAPIStorage.removeLogEventListener(observe); + resolve(obj.timer.duration); + } + + ConsoleAPIStorage.addLogEventListener( + observe, + Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal) + ); + + var w = new content.Worker("worker_bug1004814.js"); + w.postMessage(true); + }); + }); + + ok( + duration > 0, + "ConsoleEvent.timer.duration > 0: " + duration + " ~ 200ms" + ); + }); +}); diff --git a/dom/tests/browser/browser_bug1008941_dismissGeolocationHanger.js b/dom/tests/browser/browser_bug1008941_dismissGeolocationHanger.js new file mode 100644 index 0000000000..21f03721c5 --- /dev/null +++ b/dom/tests/browser/browser_bug1008941_dismissGeolocationHanger.js @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +"use strict"; + +const TEST_URI = + // eslint-disable-next-line no-useless-concat + "https://example.com/" + "browser/dom/tests/browser/position.html"; + +add_task(async function testDismissHanger() { + info( + "Check that location is not shared when dismissing the geolocation hanger" + ); + + let promisePanelShown = BrowserTestUtils.waitForEvent( + PopupNotifications.panel, + "popupshown", + true + ); + await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI); + await promisePanelShown; + + // click outside the Geolocation hanger to dismiss it + window.document.getElementById("nav-bar").click(); + info("Clicked outside the Geolocation panel to dismiss it"); + + let hasLocation = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + async function () { + return content.document.body.innerHTML.includes("location..."); + } + ); + + ok(hasLocation, "Location is not shared"); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/dom/tests/browser/browser_bug1236512.js b/dom/tests/browser/browser_bug1236512.js new file mode 100644 index 0000000000..18c92e9609 --- /dev/null +++ b/dom/tests/browser/browser_bug1236512.js @@ -0,0 +1,116 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const testPageURL = + "http://mochi.test:8888/browser/dom/tests/browser/dummy.html"; + +async function testContentVisibilityState(aIsHidden, aBrowser) { + await SpecialPowers.spawn( + aBrowser.selectedBrowser, + [aIsHidden], + aExpectedResult => { + is(content.document.hidden, aExpectedResult, "document.hidden"); + is( + content.document.visibilityState, + aExpectedResult ? "hidden" : "visible", + "document.visibilityState" + ); + } + ); +} + +async function waitContentVisibilityChange(aIsHidden, aBrowser) { + await SpecialPowers.spawn( + aBrowser.selectedBrowser, + [aIsHidden], + async function (aExpectedResult) { + let visibilityState = aExpectedResult ? "hidden" : "visible"; + if ( + content.document.hidden === aExpectedResult && + content.document.visibilityState === visibilityState + ) { + ok(true, "already changed to expected visibility state"); + return; + } + + info("wait visibilitychange event"); + await ContentTaskUtils.waitForEvent( + content.document, + "visibilitychange", + true /* capture */, + aEvent => { + info( + `visibilitychange: ${content.document.hidden} ${content.document.visibilityState}` + ); + return ( + content.document.hidden === aExpectedResult && + content.document.visibilityState === visibilityState + ); + } + ); + } + ); +} + +/** + * This test is to test the visibility state will change to "hidden" when browser + * window is fully covered by another non-translucent application. Note that we + * only support this on Mac for now, other platforms don't support reporting + * occlusion state. + */ +add_task(async function () { + info("creating test window"); + let winTest = await BrowserTestUtils.openNewBrowserWindow(); + // Specify the width, height, left and top, so that the new window can be + // fully covered by "window". + let resizePromise = BrowserTestUtils.waitForEvent( + winTest, + "resize", + false, + e => { + return winTest.innerHeight <= 500 && winTest.innerWidth <= 500; + } + ); + winTest.moveTo(200, 200); + winTest.resizeTo(500, 500); + await resizePromise; + + let browserTest = winTest.gBrowser; + + info(`loading test page: ${testPageURL}`); + BrowserTestUtils.loadURIString(browserTest.selectedBrowser, testPageURL); + await BrowserTestUtils.browserLoaded(browserTest.selectedBrowser); + + info("test init visibility state"); + await testContentVisibilityState(false /* isHidden */, browserTest); + + info( + "test window should report 'hidden' if it is fully covered by another " + + "window" + ); + await new Promise(resolve => waitForFocus(resolve, window)); + await waitContentVisibilityChange(true /* isHidden */, browserTest); + + info( + "test window should still report 'hidden' since it is still fully covered " + + "by another window" + ); + let tab = BrowserTestUtils.addTab(browserTest); + await BrowserTestUtils.switchTab(browserTest, tab); + BrowserTestUtils.removeTab(browserTest.selectedTab); + await testContentVisibilityState(true /* isHidden */, browserTest); + + info( + "test window should report 'visible' if it is not fully covered by " + + "another window" + ); + await new Promise(resolve => waitForFocus(resolve, winTest)); + await waitContentVisibilityChange(false /* isHidden */, browserTest); + + info("closing test window"); + await BrowserTestUtils.closeWindow(winTest); +}); diff --git a/dom/tests/browser/browser_bug1238427.js b/dom/tests/browser/browser_bug1238427.js new file mode 100644 index 0000000000..60cb383e03 --- /dev/null +++ b/dom/tests/browser/browser_bug1238427.js @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +"use strict"; + +const TEST_URI = + // eslint-disable-next-line no-useless-concat + "http://example.com/" + "browser/dom/tests/browser/geo_leak_test.html"; + +const BASE_GEO_URL = + "http://mochi.test:8888/tests/dom/tests/mochitest/geolocation/network_geolocation.sjs"; + +add_task(async function () { + Services.prefs.setBoolPref("geo.prompt.testing", true); + Services.prefs.setBoolPref("geo.prompt.testing.allow", true); + + // Make the geolocation provider responder very slowly to ensure that + // it does not reply before we close the tab. + Services.prefs.setCharPref( + "geo.provider.network.url", + BASE_GEO_URL + "?delay=100000" + ); + + // Open the test URI and close it. The test harness will make sure that the + // page is cleaned up after some GCs. If geolocation is not shut down properly, + // it will show up as a non-shutdown leak. + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_URI, + }, + function (browser) { + /* ... */ + } + ); + + ok(true, "Need to do something in this test"); +}); diff --git a/dom/tests/browser/browser_bug1316330.js b/dom/tests/browser/browser_bug1316330.js new file mode 100644 index 0000000000..c81d489cf7 --- /dev/null +++ b/dom/tests/browser/browser_bug1316330.js @@ -0,0 +1,52 @@ +const URL = + "data:text/html,"; + +add_task(async function () { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL); + let browser = tab.linkedBrowser; + + await EventUtils.synthesizeAndWaitKey("d", { repeat: 3 }); + + await SpecialPowers.spawn(browser, [], async function () { + is( + content.document.body.getAttribute("data-down"), + "2", + "Correct number of events" + ); + is( + content.document.body.getAttribute("data-press"), + "2", + "Correct number of events" + ); + }); + + await EventUtils.synthesizeAndWaitKey("p", { repeat: 3 }); + + await SpecialPowers.spawn(browser, [], async function () { + is( + content.document.body.getAttribute("data-down"), + "4", + "Correct number of events" + ); + is( + content.document.body.getAttribute("data-press"), + "4", + "Correct number of events" + ); + }); + + gBrowser.removeCurrentTab(); +}); diff --git a/dom/tests/browser/browser_bug1563629.js b/dom/tests/browser/browser_bug1563629.js new file mode 100644 index 0000000000..2ccbaced1c --- /dev/null +++ b/dom/tests/browser/browser_bug1563629.js @@ -0,0 +1,79 @@ +"use strict"; + +const DIRPATH = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "" +); +const PATH = DIRPATH + "file_postMessage_parent.html"; + +const URL1 = `https://example.com/${PATH}`; +const URL2 = `https://example.org/${PATH}`; + +function listenForCrash(win) { + function listener(event) { + ok(false, "a crash occurred"); + } + + win.addEventListener("oop-browser-crashed", listener); + registerCleanupFunction(() => { + win.removeEventListener("oop-browser-crashed", listener); + }); +} + +add_task(async function () { + let win = await BrowserTestUtils.openNewBrowserWindow({ + fission: true, + private: true, + remote: true, + }); + + listenForCrash(win); + + try { + let tab = win.gBrowser.selectedTab; + let browser = tab.linkedBrowser; + + BrowserTestUtils.loadURIString(browser, URL1); + await BrowserTestUtils.browserLoaded(browser, false, URL1); + + async function loadURL(url) { + let iframe = content.document.createElement("iframe"); + content.document.body.appendChild(iframe); + + iframe.contentWindow.location = url; + await new Promise(resolve => + iframe.addEventListener("load", resolve, { once: true }) + ); + + return iframe.browsingContext; + } + + function length() { + return content.length; + } + + let outer = await SpecialPowers.spawn(browser, [URL2], loadURL); + let inner = await SpecialPowers.spawn(outer, [URL2], loadURL); + + is(await SpecialPowers.spawn(outer, [], length), 1, "have 1 inner frame"); + is(await SpecialPowers.spawn(browser, [], length), 1, "have 1 outer frame"); + + // Send a message from the outer iframe to the inner one. + // + // This would've previously crashed the content process that URL2 is running + // in. + await SpecialPowers.spawn(outer, [], () => { + content.frames[0].postMessage("foo", "*"); + }); + + // Now send a message from the inner frame to the outer one. + await SpecialPowers.spawn(inner, [], () => { + content.parent.postMessage("bar", "*"); + }); + + // Assert we've made it this far. + ok(true, "didn't crash"); + } finally { + await BrowserTestUtils.closeWindow(win); + } +}); diff --git a/dom/tests/browser/browser_bug1685807.js b/dom/tests/browser/browser_bug1685807.js new file mode 100644 index 0000000000..0e0dae2b16 --- /dev/null +++ b/dom/tests/browser/browser_bug1685807.js @@ -0,0 +1,78 @@ +/** + * Bug 1685807 - Testing that the window.name won't be reset when loading an + * about:blank page to a window which had loaded non-about:blank + * page. And other case that window.name should be reset if + * the document.domain has changed. + */ + +"use strict"; + +const EMPTY_URI = + "https://test1.example.com/browser/dom/tests/browser/file_empty.html"; +const TEST_URI = + "https://test1.example.com/browser/dom/tests/browser/file_bug1685807.html"; + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["privacy.window.name.update.enabled", true]], + }); +}); + +add_task(async function doTests() { + for (let testDocDomain of [false, true]) { + // Open an empty tab. + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, EMPTY_URI); + let browser = tab.linkedBrowser; + + // Create a promise in order to wait loading of the about:blank page. + let loadedPromise = BrowserTestUtils.browserLoaded( + browser, + false, + "about:blank" + ); + + // Set the window.name and document.domain. + SpecialPowers.spawn( + browser, + [TEST_URI, testDocDomain], + (aTestURI, aTestDocDomain) => { + content.name = "Test"; + + if (aTestDocDomain) { + content.document.domain = "example.com"; + } + + // Open the page which will trigger the loading of the about:blank page. + content.open(aTestURI); + } + ); + + // Wait until the about:blank page is loaded. + await loadedPromise; + + // Check the window.name. + await SpecialPowers.spawn(browser, [testDocDomain], aTestDocDomain => { + if (aTestDocDomain) { + // The window.name should be reset if the document.domain was set to a + // cross-origin. + is(content.name, "", "The window.name should be reset."); + } else { + is(content.name, "Test", "The window.name shouldn't be reset."); + } + }); + + let awaitPageShow = BrowserTestUtils.waitForContentEvent( + browser, + "pageshow" + ); + browser.goBack(); + await awaitPageShow; + + // Check the window.name. + await SpecialPowers.spawn(browser, [], () => { + is(content.name, "Test", "The window.name is correct."); + }); + + BrowserTestUtils.removeTab(tab); + } +}); diff --git a/dom/tests/browser/browser_bug1709346.js b/dom/tests/browser/browser_bug1709346.js new file mode 100644 index 0000000000..bcfe456196 --- /dev/null +++ b/dom/tests/browser/browser_bug1709346.js @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function remove_subframe_in_cross_site_frame() { + await BrowserTestUtils.withNewTab( + "http://mochi.test:8888/browser/dom/tests/browser/file_empty_cross_site_frame.html", + async browser => { + await TestUtils.waitForCondition( + () => !XULBrowserWindow.isBusy, + "browser is not busy after the tab finishes loading" + ); + + // Spawn into the cross-site subframe, and begin loading a slow network + // connection. We'll cancel the load before this navigation completes. + await SpecialPowers.spawn( + browser.browsingContext.children[0], + [], + async () => { + let frame = content.document.createElement("iframe"); + frame.src = "load_forever.sjs"; + content.document.body.appendChild(frame); + + frame.addEventListener("load", function () { + ok(false, "load should not finish before the frame is removed"); + }); + } + ); + + is( + XULBrowserWindow.isBusy, + true, + "browser should be busy after the load starts" + ); + + // Remove the outer iframe, ending the load within this frame's subframe + // early. + await SpecialPowers.spawn(browser, [], async () => { + content.document.querySelector("iframe").remove(); + }); + + await TestUtils.waitForCondition( + () => !XULBrowserWindow.isBusy, + "Browser should no longer be busy after the frame is removed" + ); + } + ); +}); diff --git a/dom/tests/browser/browser_bug396843.js b/dom/tests/browser/browser_bug396843.js new file mode 100644 index 0000000000..ea25bd10b6 --- /dev/null +++ b/dom/tests/browser/browser_bug396843.js @@ -0,0 +1,337 @@ +/** Test for Bug 396843 **/ + +function testInDocument(doc, documentID) { + var allNodes = []; + var XMLNodes = []; + + // HTML + function HTML_TAG(name) { + allNodes.push(doc.createElementNS("http://www.w3.org/1999/xhtml", name)); + } + + /* List copy/pasted from nsHTMLTagList.h */ + HTML_TAG("a", "Anchor"); + HTML_TAG("abbr", "Span"); + HTML_TAG("acronym", "Span"); + HTML_TAG("address", "Span"); + HTML_TAG("applet", "Unknown"); + HTML_TAG("area", "Area"); + HTML_TAG("b", "Span"); + HTML_TAG("base", "Shared"); + HTML_TAG("basefont", "Span"); + HTML_TAG("bdi", ""); + HTML_TAG("bdo", "Span"); + HTML_TAG("bgsound", "Span"); + HTML_TAG("big", "Span"); + HTML_TAG("blockquote", "Shared"); + HTML_TAG("body", "Body"); + HTML_TAG("br", "BR"); + HTML_TAG("button", "Button"); + HTML_TAG("canvas", "Canvas"); + HTML_TAG("caption", "TableCaption"); + HTML_TAG("center", "Span"); + HTML_TAG("cite", "Span"); + HTML_TAG("code", "Span"); + HTML_TAG("col", "TableCol"); + HTML_TAG("colgroup", "TableCol"); + HTML_TAG("dd", "Span"); + HTML_TAG("del", "Mod"); + HTML_TAG("dfn", "Span"); + HTML_TAG("dir", "Shared"); + HTML_TAG("div", "Div"); + HTML_TAG("dl", "SharedList"); + HTML_TAG("dt", "Span"); + HTML_TAG("em", "Span"); + HTML_TAG("embed", "Embed"); + HTML_TAG("fieldset", "FieldSet"); + HTML_TAG("font", "Font"); + HTML_TAG("form", "Form"); + HTML_TAG("frame", "Frame"); + HTML_TAG("frameset", "FrameSet"); + HTML_TAG("h1", "Heading"); + HTML_TAG("h2", "Heading"); + HTML_TAG("h3", "Heading"); + HTML_TAG("h4", "Heading"); + HTML_TAG("h5", "Heading"); + HTML_TAG("h6", "Heading"); + HTML_TAG("head", "Head"); + HTML_TAG("hr", "HR"); + HTML_TAG("html", "Html"); + HTML_TAG("i", "Span"); + HTML_TAG("iframe", "IFrame"); + HTML_TAG("image", ""); + HTML_TAG("img", "Image"); + HTML_TAG("input", "Input"); + HTML_TAG("ins", "Mod"); + HTML_TAG("isindex", "Unknown"); + HTML_TAG("kbd", "Span"); + HTML_TAG("keygen", "Span"); + HTML_TAG("label", "Label"); + HTML_TAG("legend", "Legend"); + HTML_TAG("li", "LI"); + HTML_TAG("link", "Link"); + HTML_TAG("listing", "Span"); + HTML_TAG("map", "Map"); + HTML_TAG("marquee", "Div"); + HTML_TAG("menu", "Shared"); + HTML_TAG("meta", "Meta"); + HTML_TAG("multicol", "Unknown"); + HTML_TAG("nobr", "Span"); + HTML_TAG("noembed", "Div"); + HTML_TAG("noframes", "Div"); + HTML_TAG("noscript", "Div"); + HTML_TAG("object", "Object"); + HTML_TAG("ol", "SharedList"); + HTML_TAG("optgroup", "OptGroup"); + HTML_TAG("option", "Option"); + HTML_TAG("p", "Paragraph"); + HTML_TAG("param", "Shared"); + HTML_TAG("plaintext", "Span"); + HTML_TAG("pre", "Pre"); + HTML_TAG("q", "Shared"); + HTML_TAG("s", "Span"); + HTML_TAG("samp", "Span"); + HTML_TAG("script", "Script"); + HTML_TAG("select", "Select"); + HTML_TAG("small", "Span"); + HTML_TAG("spacer", "Unknown"); + HTML_TAG("span", "Span"); + HTML_TAG("strike", "Span"); + HTML_TAG("strong", "Span"); + HTML_TAG("style", "Style"); + HTML_TAG("sub", "Span"); + HTML_TAG("sup", "Span"); + HTML_TAG("table", "Table"); + HTML_TAG("tbody", "TableSection"); + HTML_TAG("td", "TableCell"); + HTML_TAG("textarea", "TextArea"); + HTML_TAG("tfoot", "TableSection"); + HTML_TAG("th", "TableCell"); + HTML_TAG("thead", "TableSection"); + HTML_TAG("template", "Template"); + HTML_TAG("title", "Title"); + HTML_TAG("tr", "TableRow"); + HTML_TAG("tt", "Span"); + HTML_TAG("u", "Span"); + HTML_TAG("ul", "SharedList"); + HTML_TAG("var", "Span"); + HTML_TAG("wbr", "Shared"); + HTML_TAG("xmp", "Span"); + + function SVG_TAG(name) { + allNodes.push(doc.createElementNS("http://www.w3.org/2000/svg", name)); + } + + // List sorta stolen from SVG element factory. + SVG_TAG("a"); + SVG_TAG("polyline"); + SVG_TAG("polygon"); + SVG_TAG("circle"); + SVG_TAG("ellipse"); + SVG_TAG("line"); + SVG_TAG("rect"); + SVG_TAG("svg"); + SVG_TAG("g"); + SVG_TAG("foreignObject"); + SVG_TAG("path"); + SVG_TAG("text"); + SVG_TAG("tspan"); + SVG_TAG("image"); + SVG_TAG("style"); + SVG_TAG("linearGradient"); + SVG_TAG("metadata"); + SVG_TAG("radialGradient"); + SVG_TAG("stop"); + SVG_TAG("defs"); + SVG_TAG("desc"); + SVG_TAG("script"); + SVG_TAG("use"); + SVG_TAG("symbol"); + SVG_TAG("marker"); + SVG_TAG("title"); + SVG_TAG("clipPath"); + SVG_TAG("textPath"); + SVG_TAG("filter"); + SVG_TAG("feBlend"); + SVG_TAG("feColorMatrix"); + SVG_TAG("feComponentTransfer"); + SVG_TAG("feComposite"); + SVG_TAG("feFuncR"); + SVG_TAG("feFuncG"); + SVG_TAG("feFuncB"); + SVG_TAG("feFuncA"); + SVG_TAG("feGaussianBlur"); + SVG_TAG("feMerge"); + SVG_TAG("feMergeNode"); + SVG_TAG("feMorphology"); + SVG_TAG("feOffset"); + SVG_TAG("feFlood"); + SVG_TAG("feTile"); + SVG_TAG("feTurbulence"); + SVG_TAG("feConvolveMatrix"); + SVG_TAG("feDistantLight"); + SVG_TAG("fePointLight"); + SVG_TAG("feSpotLight"); + SVG_TAG("feDiffuseLighting"); + SVG_TAG("feSpecularLighting"); + SVG_TAG("feDisplacementMap"); + SVG_TAG("feImage"); + SVG_TAG("pattern"); + SVG_TAG("mask"); + SVG_TAG("svgSwitch"); + + // Toss in some other namespaced stuff too, for good measure + // XUL stuff might not be creatable in content documents + try { + allNodes.push( + doc.createElementNS( + "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", + "window" + ) + ); + } catch (e) {} + allNodes.push( + doc.createElementNS("http://www.w3.org/1998/Math/MathML", "math") + ); + allNodes.push( + doc.createElementNS("http://www.w3.org/2001/xml-events", "testname") + ); + allNodes.push(doc.createElementNS("bogus.namespace", "testname")); + + var XMLDoc = doc.implementation.createDocument("", "", null); + + // And non-elements + allNodes.push(doc.createTextNode("some text")); + allNodes.push(doc.createComment("some text")); + allNodes.push(doc.createDocumentFragment()); + XMLNodes.push(XMLDoc.createCDATASection("some text")); + XMLNodes.push(XMLDoc.createProcessingInstruction("PI", "data")); + + function runTestUnwrapped() { + if (!("wrappedJSObject" in doc)) { + return; + } + ok( + doc.wrappedJSObject.nodePrincipal === undefined, + "Must not have document principal for " + documentID + ); + ok( + doc.wrappedJSObject.baseURIObject === undefined, + "Must not have document base URI for " + documentID + ); + ok( + doc.wrappedJSObject.documentURIObject === undefined, + "Must not have document URI for " + documentID + ); + + for (var i = 0; i < allNodes.length; ++i) { + ok( + allNodes[i].wrappedJSObject.nodePrincipal === undefined, + "Unexpected principal appears for " + + allNodes[i].nodeName + + " in " + + documentID + ); + ok( + allNodes[i].wrappedJSObject.baseURIObject === undefined, + "Unexpected base URI appears for " + + allNodes[i].nodeName + + " in " + + documentID + ); + } + } + + function runTestProps() { + isnot( + doc.nodePrincipal, + null, + "Must have document principal in " + documentID + ); + is( + doc.nodePrincipal instanceof Ci.nsIPrincipal, + true, + "document principal must be a principal in " + documentID + ); + isnot( + doc.baseURIObject, + null, + "Must have document base URI in" + documentID + ); + is( + doc.baseURIObject instanceof Ci.nsIURI, + true, + "document base URI must be a URI in " + documentID + ); + isnot(doc.documentURIObject, null, "Must have document URI " + documentID); + is( + doc.documentURIObject instanceof Ci.nsIURI, + true, + "document URI must be a URI in " + documentID + ); + is( + doc.documentURIObject.spec, + doc.documentURI, + "document URI must be the right URI in " + documentID + ); + + for (var i = 0; i < allNodes.length; ++i) { + is( + allNodes[i].nodePrincipal, + doc.nodePrincipal, + "Unexpected principal for " + allNodes[i].nodeName + " in " + documentID + ); + is( + allNodes[i].baseURIObject, + doc.baseURIObject, + "Unexpected base URI for " + allNodes[i].nodeName + " in " + documentID + ); + } + + for (i = 0; i < XMLNodes.length; ++i) { + is( + XMLNodes[i].nodePrincipal, + doc.nodePrincipal, + "Unexpected principal for " + XMLNodes[i].nodeName + " in " + documentID + ); + is( + XMLNodes[i].baseURIObject.spec, + "about:blank", + "Unexpected base URI for " + XMLNodes[i].nodeName + " in " + documentID + ); + } + } + + runTestUnwrapped(); + runTestProps(); + runTestUnwrapped(); +} + +add_task(async function test1() { + testInDocument(document, "browser window"); +}); + +async function newTabTest(location) { + await BrowserTestUtils.withNewTab( + { gBrowser, url: location }, + async function (browser) { + await SpecialPowers.spawn( + browser, + [{ location, testInDocument_: testInDocument.toSource() }], + async function ({ location, testInDocument_ }) { + // eslint-disable-next-line no-eval + let testInDocument = eval(`(() => (${testInDocument_}))()`); + testInDocument(content.document, location); + } + ); + } + ); +} + +add_task(async function test2() { + await newTabTest("about:blank"); +}); + +add_task(async function test3() { + await newTabTest("about:config"); +}); diff --git a/dom/tests/browser/browser_bytecode_cache_asm_js.js b/dom/tests/browser/browser_bytecode_cache_asm_js.js new file mode 100644 index 0000000000..74dcdf3b72 --- /dev/null +++ b/dom/tests/browser/browser_bytecode_cache_asm_js.js @@ -0,0 +1,31 @@ +"use strict"; + +const PAGE_URL = + "http://example.com/browser/dom/tests/browser/page_bytecode_cache_asm_js.html"; + +add_task(async function () { + // Eagerly generate bytecode cache. + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.script_loader.bytecode_cache.enabled", true], + ["dom.script_loader.bytecode_cache.strategy", -1], + ], + }); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: PAGE_URL, + waitForLoad: true, + }, + async browser => { + let result = await SpecialPowers.spawn(browser, [], () => { + return content.document.getElementById("result").textContent; + }); + // No error shoud be caught by content. + is(result, "ok"); + } + ); + + await SpecialPowers.popPrefEnv(); +}); diff --git a/dom/tests/browser/browser_cancel_keydown_keypress_event.js b/dom/tests/browser/browser_cancel_keydown_keypress_event.js new file mode 100644 index 0000000000..de08aa17e9 --- /dev/null +++ b/dom/tests/browser/browser_cancel_keydown_keypress_event.js @@ -0,0 +1,41 @@ +const URL = + "https://example.com/browser/dom/tests/browser/prevent_return_key.html"; + +const { PromptTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromptTestUtils.sys.mjs" +); + +// Wait for alert dialog and dismiss it immediately. +function awaitAndCloseAlertDialog(browser) { + return PromptTestUtils.handleNextPrompt( + browser, + { modalType: Services.prompt.MODAL_TYPE_CONTENT, promptType: "alert" }, + { buttonNumClick: 0 } + ); +} + +add_task(async function () { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL); + let browser = tab.linkedBrowser; + + // Focus and enter random text on input. + await SpecialPowers.spawn(browser, [], async function () { + let input = content.document.getElementById("input"); + input.focus(); + input.value = "abcd"; + }); + + // Send return key (cross process) to submit the form implicitly. + let dialogShown = awaitAndCloseAlertDialog(browser); + EventUtils.synthesizeKey("KEY_Enter"); + await dialogShown; + + // Check that the form should not have been submitted. + await SpecialPowers.spawn(browser, [], async function () { + let result = content.document.getElementById("result").innerHTML; + info("submit result: " + result); + is(result, "not submitted", "form should not have submitted"); + }); + + gBrowser.removeCurrentTab(); +}); diff --git a/dom/tests/browser/browser_data_document_crossOriginIsolated.js b/dom/tests/browser/browser_data_document_crossOriginIsolated.js new file mode 100644 index 0000000000..d38eda3f0a --- /dev/null +++ b/dom/tests/browser/browser_data_document_crossOriginIsolated.js @@ -0,0 +1,68 @@ +"use strict"; + +const DIRPATH = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "" +); +const PATH = DIRPATH + "file_coop_coep.html"; + +const ORIGIN = "https://test1.example.com"; +const URL = `${ORIGIN}/${PATH}`; + +add_task(async function () { + await BrowserTestUtils.withNewTab(URL, async function (browser) { + BrowserTestUtils.loadURIString(browser, URL); + await BrowserTestUtils.browserLoaded(browser); + + await SpecialPowers.spawn(browser, [ORIGIN], async origin => { + is( + content.window.origin, + origin, + `Opened a tab and navigated to ${origin}` + ); + + ok( + content.window.crossOriginIsolated, + `Should have been cross-origin-isolated env` + ); + + let hostIds = []; + function createShadowDOMAndTriggerSlotChange(host) { + var shadow = host.attachShadow({ mode: "closed" }); + + let promise = new Promise(resolve => { + shadow.addEventListener("slotchange", function () { + hostIds.push(host.id); + resolve(); + }); + }); + + shadow.innerHTML = ""; + + host.appendChild(host.ownerDocument.createElement("span")); + + return promise; + } + + let host1 = content.document.getElementById("host1"); + + let dataDoc = content.document.implementation.createHTMLDocument(); + dataDoc.body.innerHTML = "
"; + let host2 = dataDoc.body.firstChild; + + let host3 = content.document.getElementById("host3"); + + let promises = []; + promises.push(createShadowDOMAndTriggerSlotChange(host1)); + promises.push(createShadowDOMAndTriggerSlotChange(host2)); + promises.push(createShadowDOMAndTriggerSlotChange(host3)); + + await Promise.all(promises); + + is(hostIds.length, 3, `Got 3 slot change events`); + is(hostIds[0], "host1", `The first one was host1`); + is(hostIds[1], "host2", `The second one was host2`); + is(hostIds[2], "host3", `The third one was host3`); + }); + }); +}); diff --git a/dom/tests/browser/browser_focus_steal_from_chrome.js b/dom/tests/browser/browser_focus_steal_from_chrome.js new file mode 100644 index 0000000000..ead2fa0ba1 --- /dev/null +++ b/dom/tests/browser/browser_focus_steal_from_chrome.js @@ -0,0 +1,214 @@ +add_task(async function () { + requestLongerTimeout(2); + + let testingList = [ + { + uri: "data:text/html,", + tagName: "INPUT", + methodName: "focus", + }, + { + uri: "data:text/html,", + tagName: "INPUT", + methodName: "select", + }, + { + uri: "data:text/html,anchor", + tagName: "A", + methodName: "focus", + }, + { + uri: "data:text/html,", + tagName: "BUTTON", + methodName: "focus", + }, + { + uri: "data:text/html,", + tagName: "SELECT", + methodName: "focus", + }, + { + uri: "data:text/html,", + tagName: "TEXTAREA", + methodName: "focus", + }, + { + uri: "data:text/html,", + tagName: "TEXTAREA", + methodName: "select", + }, + { + uri: "data:text/html,", + tagName: "INPUT", + methodName: "focus of label element", + }, + { + uri: "data:text/html,
legend
", + tagName: "INPUT", + methodName: "focus of legend element", + }, + { + uri: + 'data:text/html,' + + "", + tagName: "INPUT", + methodName: "click event on the label element", + }, + ]; + + await BrowserTestUtils.withNewTab("about:blank", async function (bg) { + await BrowserTestUtils.withNewTab("about:blank", async function (fg) { + for (let test of testingList) { + // Focus the foreground tab's content + fg.focus(); + + // Load the URIs. + BrowserTestUtils.loadURIString(bg, test.uri); + await BrowserTestUtils.browserLoaded(bg); + BrowserTestUtils.loadURIString(fg, test.uri); + await BrowserTestUtils.browserLoaded(fg); + + ok(true, "Test1: Both of the tabs are loaded"); + + // Confirm that the contents should be able to steal focus from content. + await SpecialPowers.spawn(fg, [test], test => { + return new Promise(res => { + function f() { + let e = content.document.activeElement; + if (e.tagName != test.tagName) { + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + content.setTimeout(f, 10); + } else { + is( + Services.focus.focusedElement, + e, + "the foreground tab's " + + test.tagName + + " element isn't focused by the " + + test.methodName + + " (Test1: content can steal focus)" + ); + res(); + } + } + f(); + }); + }); + + await SpecialPowers.spawn(bg, [test], test => { + return new Promise(res => { + function f() { + let e = content.document.activeElement; + if (e.tagName != test.tagName) { + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + content.setTimeout(f, 10); + } else { + isnot( + Services.focus.focusedElement, + e, + "the background tab's " + + test.tagName + + " element is focused by the " + + test.methodName + + " (Test1: content can steal focus)" + ); + res(); + } + } + f(); + }); + }); + + if (fg.isRemoteBrowser) { + is( + Services.focus.focusedElement, + fg, + "Focus should be on the content in the parent process" + ); + } + + // Focus chrome + gURLBar.focus(); + let originalFocus = Services.focus.focusedElement; + + // Load about:blank just to make sure that everything works nicely + BrowserTestUtils.loadURIString(bg, "about:blank"); + await BrowserTestUtils.browserLoaded(bg); + BrowserTestUtils.loadURIString(fg, "about:blank"); + await BrowserTestUtils.browserLoaded(fg); + + // Load the URIs. + BrowserTestUtils.loadURIString(bg, test.uri); + await BrowserTestUtils.browserLoaded(bg); + BrowserTestUtils.loadURIString(fg, test.uri); + await BrowserTestUtils.browserLoaded(fg); + + ok(true, "Test2: Both of the tabs are loaded"); + + // Confirm that the contents should be able to steal focus from content. + await SpecialPowers.spawn(fg, [test], test => { + return new Promise(res => { + function f() { + let e = content.document.activeElement; + if (e.tagName != test.tagName) { + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + content.setTimeout(f, 10); + } else { + isnot( + Services.focus.focusedElement, + e, + "the foreground tab's " + + test.tagName + + " element is focused by the " + + test.methodName + + " (Test2: content can NOT steal focus)" + ); + res(); + } + } + f(); + }); + }); + + await SpecialPowers.spawn(bg, [test], test => { + return new Promise(res => { + function f() { + let e = content.document.activeElement; + if (e.tagName != test.tagName) { + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + content.setTimeout(f, 10); + } else { + isnot( + Services.focus.focusedElement, + e, + "the background tab's " + + test.tagName + + " element is focused by the " + + test.methodName + + " (Test2: content can NOT steal focus)" + ); + res(); + } + } + f(); + }); + }); + + is( + Services.focus.focusedElement, + originalFocus, + "The parent process's focus has shifted " + + "(methodName = " + + test.methodName + + ")" + + " (Test2: content can NOT steal focus)" + ); + } + }); + }); +}); diff --git a/dom/tests/browser/browser_focus_steal_from_chrome_during_mousedown.js b/dom/tests/browser/browser_focus_steal_from_chrome_during_mousedown.js new file mode 100644 index 0000000000..61c36f2436 --- /dev/null +++ b/dom/tests/browser/browser_focus_steal_from_chrome_during_mousedown.js @@ -0,0 +1,76 @@ +add_task(async function test() { + const kTestURI = + "data:text/html," + + '" + + '' + + '' + + ''; + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, kTestURI); + + let fm = Services.focus; + + for (var button = 0; button < 3; button++) { + // Set focus to a chrome element before synthesizing a mouse down event. + gURLBar.focus(); + + is( + fm.focusedElement, + gURLBar.inputField, + "Failed to move focus to search bar: button=" + button + ); + + // Synthesize mouse down event on browser object over the button, such that + // the event propagates through both processes. + EventUtils.synthesizeMouse(tab.linkedBrowser, 20, 20, { button }); + + isnot( + fm.focusedElement, + gURLBar.inputField, + "Failed to move focus away from search bar: button=" + button + ); + + await SpecialPowers.spawn( + tab.linkedBrowser, + [button], + async function (button) { + let fm = Services.focus; + + let attempts = 10; + await new Promise(resolve => { + function check() { + if ( + attempts > 0 && + content.document.activeElement.id != "willBeFocused" + ) { + attempts--; + content.window.setTimeout(check, 100); + return; + } + + Assert.equal( + content.document.activeElement.id, + "willBeFocused", + "The input element isn't active element: button=" + button + ); + Assert.equal( + fm.focusedElement, + content.document.activeElement, + "The active element isn't focused element in App level: button=" + + button + ); + resolve(); + } + check(); + }); + } + ); + } + + gBrowser.removeTab(tab); +}); diff --git a/dom/tests/browser/browser_form_associated_custom_elements_validity.js b/dom/tests/browser/browser_form_associated_custom_elements_validity.js new file mode 100644 index 0000000000..3765405735 --- /dev/null +++ b/dom/tests/browser/browser_form_associated_custom_elements_validity.js @@ -0,0 +1,111 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +add_task(async function report_validity() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: `data:text/html,`, + }, + async function (aBrowser) { + let promisePopupShown = BrowserTestUtils.waitForEvent( + window, + "popupshown" + ); + + let message = "valueMissing message"; + await SpecialPowers.spawn(aBrowser, [message], function (aMessage) { + class MyControl extends content.HTMLElement { + static get formAssociated() { + return true; + } + constructor() { + super(); + let shadow = this.attachShadow({ mode: "open" }); + let input = content.document.createElement("input"); + shadow.appendChild(input); + + let internals = this.attachInternals(); + internals.setValidity({ valueMissing: true }, aMessage, input); + internals.reportValidity(); + } + } + content.customElements.define("my-control", MyControl); + + let myControl = content.document.querySelector("my-control"); + content.customElements.upgrade(myControl); + }); + await promisePopupShown; + + let invalidFormPopup = + window.document.getElementById("invalid-form-popup"); + is(invalidFormPopup.state, "open", "invalid-form-popup should be opened"); + is(invalidFormPopup.firstChild.textContent, message, "check message"); + + let promisePopupHidden = BrowserTestUtils.waitForEvent( + invalidFormPopup, + "popuphidden" + ); + invalidFormPopup.hidePopup(); + await promisePopupHidden; + } + ); +}); + +add_task(async function form_report_validity() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: `data:text/html,
`, + }, + async function (aBrowser) { + let promisePopupShown = BrowserTestUtils.waitForEvent( + window, + "popupshown" + ); + + let message = "valueMissing message"; + await SpecialPowers.spawn(aBrowser, [message], function (aMessage) { + class MyControl extends content.HTMLElement { + static get formAssociated() { + return true; + } + constructor() { + super(); + let shadow = this.attachShadow({ mode: "open" }); + let input = content.document.createElement("input"); + shadow.appendChild(input); + + let internals = this.attachInternals(); + internals.setValidity({ valueMissing: true }, aMessage, input); + } + } + content.customElements.define("my-control", MyControl); + + let myControl = content.document.querySelector("my-control"); + content.customElements.upgrade(myControl); + + let form = content.document.querySelector("form"); + is(form.length, "1", "check form.length"); + form.reportValidity(); + }); + await promisePopupShown; + + let invalidFormPopup = + window.document.getElementById("invalid-form-popup"); + is(invalidFormPopup.state, "open", "invalid-form-popup should be opened"); + is(invalidFormPopup.firstChild.textContent, message, "check message"); + + let promisePopupHidden = BrowserTestUtils.waitForEvent( + invalidFormPopup, + "popuphidden" + ); + invalidFormPopup.hidePopup(); + await promisePopupHidden; + } + ); +}); diff --git a/dom/tests/browser/browser_frame_elements.html b/dom/tests/browser/browser_frame_elements.html new file mode 100644 index 0000000000..4843173f57 --- /dev/null +++ b/dom/tests/browser/browser_frame_elements.html @@ -0,0 +1,15 @@ + + + + Frame Element Tests + + +

Frame Element Tests

+ + + + + + + + diff --git a/dom/tests/browser/browser_frame_elements.js b/dom/tests/browser/browser_frame_elements.js new file mode 100644 index 0000000000..13f79edbfd --- /dev/null +++ b/dom/tests/browser/browser_frame_elements.js @@ -0,0 +1,74 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const TEST_URI = + "http://example.com/browser/dom/tests/browser/browser_frame_elements.html"; + +add_task(async function test() { + await BrowserTestUtils.withNewTab( + { gBrowser, url: TEST_URI }, + async function (browser) { + // Confirm its embedder is the browser: + is( + browser.browsingContext.embedderElement, + browser, + "Embedder element for main window is xul:browser" + ); + + await SpecialPowers.spawn(browser, [], startTests); + } + ); +}); + +function startTests() { + info("Frame tests started"); + + info("Checking top window"); + let gWindow = content; + Assert.equal(gWindow.top, gWindow, "gWindow is top"); + Assert.equal(gWindow.parent, gWindow, "gWindow is parent"); + + info("Checking about:blank iframe"); + let iframeBlank = gWindow.document.querySelector("#iframe-blank"); + Assert.ok(iframeBlank, "Iframe exists on page"); + Assert.equal( + iframeBlank.browsingContext.embedderElement, + iframeBlank, + "Embedder element for iframe window is iframe" + ); + Assert.equal(iframeBlank.contentWindow.top, gWindow, "gWindow is top"); + Assert.equal(iframeBlank.contentWindow.parent, gWindow, "gWindow is parent"); + + info("Checking iframe with data url src"); + let iframeDataUrl = gWindow.document.querySelector("#iframe-data-url"); + Assert.ok(iframeDataUrl, "Iframe exists on page"); + Assert.equal( + iframeDataUrl.browsingContext.embedderElement, + iframeDataUrl, + "Embedder element for iframe window is iframe" + ); + Assert.equal(iframeDataUrl.contentWindow.top, gWindow, "gWindow is top"); + Assert.equal( + iframeDataUrl.contentWindow.parent, + gWindow, + "gWindow is parent" + ); + + info("Checking object with data url data attribute"); + let objectDataUrl = gWindow.document.querySelector("#object-data-url"); + Assert.ok(objectDataUrl, "Object exists on page"); + Assert.equal( + objectDataUrl.browsingContext.embedderElement, + objectDataUrl, + "Embedder element for object window is the object" + ); + Assert.equal(objectDataUrl.contentWindow.top, gWindow, "gWindow is top"); + Assert.equal( + objectDataUrl.contentWindow.parent, + gWindow, + "gWindow is parent" + ); +} diff --git a/dom/tests/browser/browser_hasActivePeerConnections.js b/dom/tests/browser/browser_hasActivePeerConnections.js new file mode 100644 index 0000000000..91d51efbfc --- /dev/null +++ b/dom/tests/browser/browser_hasActivePeerConnections.js @@ -0,0 +1,134 @@ +const TEST_URI1 = + "http://mochi.test:8888/browser/dom/tests/browser/" + + "create_webrtc_peer_connection.html"; + +const TEST_URI2 = + "https://example.com/browser/dom/tests/browser/" + + "create_webrtc_peer_connection.html"; + +add_task(async () => { + await BrowserTestUtils.withNewTab(TEST_URI1, async browser => { + const windowGlobal = browser.browsingContext.currentWindowGlobal; + Assert.ok(windowGlobal); + + Assert.strictEqual( + windowGlobal.hasActivePeerConnections(), + false, + "No active connections at the beginning" + ); + + await SpecialPowers.spawn(browser, [], async () => { + content.postMessage("push-peer-connection", "*"); + return new Promise(resolve => + content.addEventListener("message", function onMessage(event) { + if (event.data == "ack") { + content.removeEventListener(event.type, onMessage); + resolve(); + } + }) + ); + }); + + Assert.strictEqual( + windowGlobal.hasActivePeerConnections(), + true, + "One connection in the top window" + ); + + await SpecialPowers.spawn(browser, [], async () => { + content.postMessage("pop-peer-connection", "*"); + return new Promise(resolve => + content.addEventListener("message", function onMessage(event) { + if (event.data == "ack") { + content.removeEventListener(event.type, onMessage); + resolve(); + } + }) + ); + }); + + Assert.strictEqual( + windowGlobal.hasActivePeerConnections(), + false, + "All connections have been closed" + ); + + await SpecialPowers.spawn( + browser, + [TEST_URI1, TEST_URI2], + async (TEST_URI1, TEST_URI2) => { + // Create a promise that is fulfilled when the "ack" message is received + // |targetCount| times. + const createWaitForAckPromise = (eventTarget, targetCount) => { + let counter = 0; + return new Promise(resolve => { + eventTarget.addEventListener("message", function onMsg(event) { + if (event.data == "ack") { + ++counter; + if (counter == targetCount) { + eventTarget.removeEventListener(event.type, onMsg); + resolve(); + } + } + }); + }); + }; + + const addFrame = (id, url) => { + const iframe = content.document.createElement("iframe"); + iframe.id = id; + iframe.src = url; + content.document.body.appendChild(iframe); + return iframe; + }; + + // Create two iframes hosting a same-origin page and a cross-origin page + const iframe1 = addFrame("iframe-same-origin", TEST_URI1); + const iframe2 = addFrame("iframe-cross-origin", TEST_URI2); + await ContentTaskUtils.waitForEvent(iframe1, "load"); + await ContentTaskUtils.waitForEvent(iframe2, "load"); + + // Make sure the counter is not messed up after successive push/pop + // messages + const kLoopCount = 100; + for (let i = 0; i < kLoopCount; ++i) { + content.postMessage("push-peer-connection", "*"); + iframe1.contentWindow.postMessage("push-peer-connection", "*"); + iframe2.contentWindow.postMessage("push-peer-connection", "*"); + iframe1.contentWindow.postMessage("pop-peer-connection", "*"); + iframe2.contentWindow.postMessage("pop-peer-connection", "*"); + content.postMessage("pop-peer-connection", "*"); + } + iframe2.contentWindow.postMessage("push-peer-connection", "*"); + + return createWaitForAckPromise(content, kLoopCount * 6 + 1); + } + ); + + Assert.strictEqual( + windowGlobal.hasActivePeerConnections(), + true, + "#iframe-cross-origin still has an active connection" + ); + + await SpecialPowers.spawn(browser, [], async () => { + content.document + .getElementById("iframe-cross-origin") + .contentWindow.postMessage("pop-peer-connection", "*"); + return new Promise(resolve => + content.addEventListener("message", function onMessage(event) { + if (event.data == "ack") { + content.removeEventListener(event.type, onMessage); + resolve(); + } + }) + ); + }); + + Assert.strictEqual( + windowGlobal.hasActivePeerConnections(), + false, + "All connections have been closed" + ); + }); +}); diff --git a/dom/tests/browser/browser_hasbeforeunload.js b/dom/tests/browser/browser_hasbeforeunload.js new file mode 100644 index 0000000000..1ffc06656e --- /dev/null +++ b/dom/tests/browser/browser_hasbeforeunload.js @@ -0,0 +1,875 @@ +"use strict"; + +const PAGE_URL = + "http://example.com/browser/dom/tests/browser/beforeunload_test_page.html"; + +/** + * Adds 1 or more inert beforeunload event listeners in this browser. + * By default, will target the top-level content window, but callers + * can specify the index of a subframe to target. See prepareSubframes + * for an idea of how the subframes are structured. + * + * @param {} browser + * The browser to add the beforeunload event listener in. + * @param {int} howMany + * How many beforeunload event listeners to add. Note that these + * beforeunload event listeners are inert and will not actually + * prevent the host window from navigating. + * @param {optional int} frameDepth + * The depth of the frame to add the event listener to. Defaults + * to 0, which is the top-level content window. + * @return {Promise} + */ +function addBeforeUnloadListeners(browser, howMany = 1, frameDepth = 0) { + return controlFrameAt(browser, frameDepth, { + name: "AddBeforeUnload", + howMany, + }); +} + +/** + * Adds 1 or more inert beforeunload event listeners in this browser on + * a particular subframe. By default, this will target the first subframe + * under the top-level content window, but callers can specify the index + * of a subframe to target. See prepareSubframes for an idea of how the + * subframes are structured. + * + * Note that this adds the beforeunload event listener on the "outer" window, + * by doing: + * + * iframe.addEventListener("beforeunload", ...); + * + * @param {} browser + * The browser to add the beforeunload event listener in. + * @param {int} howMany + * How many beforeunload event listeners to add. Note that these + * beforeunload event listeners are inert and will not actually + * prevent the host window from navigating. + * @param {optional int} frameDepth + * The depth of the frame to add the event listener to. Defaults + * to 1, which is the first subframe inside the top-level content + * window. Setting this to 0 will throw. + * @return {Promise} + */ +function addOuterBeforeUnloadListeners(browser, howMany = 1, frameDepth = 1) { + if (frameDepth == 0) { + throw new Error( + "When adding a beforeunload listener on an outer " + + "window, the frame you're targeting needs to be at " + + "depth > 0." + ); + } + + return controlFrameAt(browser, frameDepth, { + name: "AddOuterBeforeUnload", + howMany, + }); +} + +/** + * Removes 1 or more inert beforeunload event listeners in this browser. + * This assumes that addBeforeUnloadListeners has been called previously + * for the target frame. + * + * By default, will target the top-level content window, but callers + * can specify the index of a subframe to target. See prepareSubframes + * for an idea of how the subframes are structured. + * + * @param {} browser + * The browser to remove the beforeunload event listener from. + * @param {int} howMany + * How many beforeunload event listeners to remove. + * @param {optional int} frameDepth + * The depth of the frame to remove the event listener from. Defaults + * to 0, which is the top-level content window. + * @return {Promise} + */ +function removeBeforeUnloadListeners(browser, howMany = 1, frameDepth = 0) { + return controlFrameAt(browser, frameDepth, { + name: "RemoveBeforeUnload", + howMany, + }); +} + +/** + * Removes 1 or more inert beforeunload event listeners in this browser on + * a particular subframe. By default, this will target the first subframe + * under the top-level content window, but callers can specify the index + * of a subframe to target. See prepareSubframes for an idea of how the + * subframes are structured. + * + * Note that this removes the beforeunload event listener on the "outer" window, + * by doing: + * + * iframe.removeEventListener("beforeunload", ...); + * + * @param {} browser + * The browser to remove the beforeunload event listener from. + * @param {int} howMany + * How many beforeunload event listeners to remove. + * @param {optional int} frameDepth + * The depth of the frame to remove the event listener from. Defaults + * to 1, which is the first subframe inside the top-level content + * window. Setting this to 0 will throw. + * @return {Promise} + */ +function removeOuterBeforeUnloadListeners( + browser, + howMany = 1, + frameDepth = 1 +) { + if (frameDepth == 0) { + throw new Error( + "When removing a beforeunload listener from an outer " + + "window, the frame you're targeting needs to be at " + + "depth > 0." + ); + } + + return controlFrameAt(browser, frameDepth, { + name: "RemoveOuterBeforeUnload", + howMany, + }); +} + +/** + * Navigates a content window to a particular URL and waits for it to + * finish loading that URL. + * + * By default, will target the top-level content window, but callers + * can specify the index of a subframe to target. See prepareSubframes + * for an idea of how the subframes are structured. + * + * @param {} browser + * The browser that will have the navigation occur within it. + * @param {string} url + * The URL to send the content window to. + * @param {optional int} frameDepth + * The depth of the frame to navigate. Defaults to 0, which is + * the top-level content window. + * @return {Promise} + */ +function navigateSubframe(browser, url, frameDepth = 0) { + let navigatePromise = controlFrameAt(browser, frameDepth, { + name: "Navigate", + url, + }); + let subframeLoad = BrowserTestUtils.browserLoaded(browser, true); + return Promise.all([navigatePromise, subframeLoad]); +} + +/** + * Removes the '; + +function checkWarningState(aWarningElement, aExpectedState, aMsg) { + ["hidden", "ontop", "onscreen"].forEach(state => { + is( + aWarningElement.hasAttribute(state), + state == aExpectedState, + `${aMsg} - check ${state} attribute.` + ); + }); +} + +async function waitForWarningState(aWarningElement, aExpectedState) { + await BrowserTestUtils.waitForAttribute(aExpectedState, aWarningElement, ""); + checkWarningState( + aWarningElement, + aExpectedState, + `Wait for ${aExpectedState} state` + ); +} + +// Make sure the pointerlock warning is shown and exited with the escape key +add_task(async function show_pointerlock_warning_escape() { + let urls = [TEST_URL, FRAME_TEST_URL]; + for (let url of urls) { + info("Pointerlock warning test for " + url); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + + let warning = document.getElementById("pointerlock-warning"); + let warningShownPromise = waitForWarningState(warning, "onscreen"); + + let expectedWarningText; + + let bc = tab.linkedBrowser.browsingContext; + if (bc.children.length) { + // use the subframe if it exists + bc = bc.children[0]; + expectedWarningText = "example.org"; + } else { + expectedWarningText = "This document"; + } + expectedWarningText += + " has control of your pointer. Press Esc to take back control."; + + await BrowserTestUtils.synthesizeMouse("body", 4, 4, {}, bc); + + await warningShownPromise; + + ok(true, "Pointerlock warning shown"); + + let warningHiddenPromise = waitForWarningState(warning, "hidden"); + + await BrowserTestUtils.waitForCondition( + () => warning.innerText == expectedWarningText, + "Warning text" + ); + + EventUtils.synthesizeKey("KEY_Escape"); + await warningHiddenPromise; + + ok(true, "Pointerlock warning hidden"); + + // Pointerlock should be released after escape is pressed. + await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { + Assert.equal(content.document.pointerLockElement, null); + }); + + await BrowserTestUtils.removeTab(tab); + } +}); + +/* +// XXX Bug 1580961 - this part of the test is disabled. +// +// Make sure the pointerlock warning is shown, but this time escape is not pressed until after the +// notification is closed via the timeout. +add_task(async function show_pointerlock_warning_timeout() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + let warning = document.getElementById("pointerlock-warning"); + let warningShownPromise = BrowserTestUtils.waitForAttribute( + "onscreen", + warning, + "true" + ); + let warningHiddenPromise = BrowserTestUtils.waitForAttribute( + "hidden", + warning, + "true" + ); + await BrowserTestUtils.synthesizeMouse("body", 4, 4, {}, tab.linkedBrowser); + + await warningShownPromise; + ok(true, "Pointerlock warning shown"); + await warningHiddenPromise; + + // The warning closes after a few seconds, but this does not exit pointerlock mode. + await SpecialPowers.spawn(tab.linkedBrowser, [], async function() { + Assert.equal(content.document.pointerLockElement, content.document.body); + }); + + EventUtils.synthesizeKey("KEY_Escape"); + + ok(true, "Pointerlock warning hidden"); + + // Pointerlock should now be released. + await SpecialPowers.spawn(tab.linkedBrowser, [], async function() { + Assert.equal(content.document.pointerLockElement, null); + }); + + await BrowserTestUtils.removeTab(tab); +}); +*/ diff --git a/dom/tests/browser/browser_sessionStorage_navigation.js b/dom/tests/browser/browser_sessionStorage_navigation.js new file mode 100644 index 0000000000..094527dd44 --- /dev/null +++ b/dom/tests/browser/browser_sessionStorage_navigation.js @@ -0,0 +1,271 @@ +"use strict"; + +const DIRPATH = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "" +); +const PATH = DIRPATH + "file_empty.html"; + +const ORIGIN1 = "https://example.com"; +const ORIGIN2 = "https://example.org"; +const URL1 = `${ORIGIN1}/${PATH}`; +const URL2 = `${ORIGIN2}/${PATH}`; +const URL1_WITH_COOP_COEP = `${ORIGIN1}/${DIRPATH}file_coop_coep.html`; + +add_task(async function () { + await BrowserTestUtils.withNewTab(URL1, async function (browser) { + const key = "key"; + const value = "value"; + + info( + `Verifying sessionStorage is preserved after navigating to a ` + + `cross-origin site and then navigating back` + ); + + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "privacy.partition.always_partition_third_party_non_cookie_storage", + false, + ], + ], + }); + + BrowserTestUtils.loadURIString(browser, URL1); + await BrowserTestUtils.browserLoaded(browser); + + await SpecialPowers.spawn( + browser, + [ORIGIN1, key, value], + async (ORIGIN, key, value) => { + is(content.window.origin, ORIGIN, `Navigate to ${ORIGIN} as expected`); + + let value1 = content.window.sessionStorage.getItem(key); + is( + value1, + null, + `SessionStorage for ${key} in ${content.window.origin} is null ` + + `since it's the first visit` + ); + + content.window.sessionStorage.setItem(key, value); + + let value2 = content.window.sessionStorage.getItem(key); + is( + value2, + value, + `SessionStorage for ${key} in ${content.window.origin} is set ` + + `correctly` + ); + } + ); + + BrowserTestUtils.loadURIString(browser, URL2); + await BrowserTestUtils.browserLoaded(browser); + + await SpecialPowers.spawn( + browser, + [ORIGIN2, key, value], + async (ORIGIN, key, value) => { + is(content.window.origin, ORIGIN, `Navigate to ${ORIGIN} as expected`); + + let value1 = content.window.sessionStorage.getItem(key); + is( + value1, + null, + `SessionStorage for ${key} in ${content.window.origin} is null ` + + `since it's the first visit` + ); + } + ); + + BrowserTestUtils.loadURIString(browser, URL1); + await BrowserTestUtils.browserLoaded(browser); + + await SpecialPowers.spawn( + browser, + [ORIGIN1, key, value], + async (ORIGIN, key, value) => { + is(content.window.origin, ORIGIN, `Navigate to ${ORIGIN} as expected`); + + let value1 = content.window.sessionStorage.getItem(key); + is( + value1, + value, + `SessionStorage for ${key} in ${content.window.origin} is preserved` + ); + } + ); + + info(`Verifying sessionStorage is preserved for ${URL1} after navigating`); + + BrowserTestUtils.loadURIString(browser, URL2); + await BrowserTestUtils.browserLoaded(browser); + + await SpecialPowers.spawn( + browser, + [ORIGIN2, ORIGIN1, URL1, key, value], + async (ORIGIN, iframeORIGIN, iframeURL, key, value) => { + is(content.window.origin, ORIGIN, `Navigate to ${ORIGIN} as expected`); + + let iframe = content.document.createElement("iframe"); + iframe.src = iframeURL; + content.document.body.appendChild(iframe); + await ContentTaskUtils.waitForEvent(iframe, "load"); + + await content.SpecialPowers.spawn( + iframe, + [iframeORIGIN, key, value], + async function (ORIGIN, key, value) { + is( + content.window.origin, + ORIGIN, + `Navigate to ${ORIGIN} as expected` + ); + + // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5) + // Acquire storage access permission here so that the iframe has + // first-party access to the sessionStorage. Without this, it is + // isolated and this test will always fail + SpecialPowers.wrap(content.document).notifyUserGestureActivation(); + await SpecialPowers.addPermission( + "storageAccessAPI", + true, + content.window.location.href + ); + await SpecialPowers.wrap(content.document).requestStorageAccess(); + + let value1 = content.window.sessionStorage.getItem(key); + is( + value1, + value, + `SessionStorage for ${key} in ${content.window.origin} is ` + + `preserved` + ); + } + ); + } + ); + + info(`Verifying SSCache is loaded to the content process only once`); + + BrowserTestUtils.loadURIString(browser, URL1); + await BrowserTestUtils.browserLoaded(browser); + + await SpecialPowers.spawn( + browser, + [ORIGIN1, URL1, key, value], + async (ORIGIN, iframeURL, key, value) => { + is(content.window.origin, ORIGIN, `Navigate to ${ORIGIN} as expected`); + + let iframe = content.document.createElement("iframe"); + iframe.src = iframeURL; + content.document.body.appendChild(iframe); + await ContentTaskUtils.waitForEvent(iframe, "load"); + + await content.SpecialPowers.spawn( + iframe, + [ORIGIN, key, value], + async function (ORIGIN, key, value) { + is( + content.window.origin, + ORIGIN, + `Load an iframe to ${ORIGIN} as expected` + ); + + let value1 = content.window.sessionStorage.getItem(key); + is( + value1, + value, + `SessionStorage for ${key} in ${content.window.origin} is ` + + `preserved.` + ); + + // When we are here, it means we didn't hit the assertion for + // ensuring a SSCache can only be loaded on the content process + // once. + } + ); + } + ); + + info( + `Verifying the sessionStorage for a tab shares between ` + + `cross-origin-isolated and non cross-origin-isolated environments` + ); + const anotherKey = `anotherKey`; + const anotherValue = `anotherValue;`; + + BrowserTestUtils.loadURIString(browser, URL1_WITH_COOP_COEP); + await BrowserTestUtils.browserLoaded(browser); + + await SpecialPowers.spawn( + browser, + [ORIGIN1, key, value, anotherKey, anotherValue], + async (ORIGIN, key, value, anotherKey, anotherValue) => { + is(content.window.origin, ORIGIN, `Navigate to ${ORIGIN} as expected`); + ok( + content.window.crossOriginIsolated, + `The window is cross-origin-isolated.` + ); + + let value1 = content.window.sessionStorage.getItem(key); + is( + value1, + value, + `SessionStorage for ${key} in ${content.window.origin} was ` + + `propagated to COOP+COEP process correctly.` + ); + + let value2 = content.window.sessionStorage.getItem(anotherKey); + is( + value2, + null, + `SessionStorage for ${anotherKey} in ${content.window.origin} ` + + `hasn't been set yet.` + ); + + content.window.sessionStorage.setItem(anotherKey, anotherValue); + + let value3 = content.window.sessionStorage.getItem(anotherKey); + is( + value3, + anotherValue, + `SessionStorage for ${anotherKey} in ${content.window.origin} ` + + `was set as expected.` + ); + } + ); + + BrowserTestUtils.loadURIString(browser, URL1); + await BrowserTestUtils.browserLoaded(browser); + + await SpecialPowers.spawn( + browser, + [ORIGIN1, key, value, anotherKey, anotherValue], + async (ORIGIN, key, value, anotherKey, anotherValue) => { + is(content.window.origin, ORIGIN, `Navigate to ${ORIGIN} as expected`); + ok( + !content.window.crossOriginIsolated, + `The window is not cross-origin-isolated.` + ); + + let value1 = content.window.sessionStorage.getItem(key); + is( + value1, + value, + `SessionStorage for ${key} in ${content.window.origin} is ` + + `preserved.` + ); + + let value2 = content.window.sessionStorage.getItem(anotherKey); + is( + value2, + anotherValue, + `SessionStorage for ${anotherKey} in ${content.window.origin} was ` + + `propagated to non-COOP+COEP process correctly.` + ); + } + ); + }); +}); diff --git a/dom/tests/browser/browser_test_focus_after_modal_state.js b/dom/tests/browser/browser_test_focus_after_modal_state.js new file mode 100644 index 0000000000..2193d8fdc4 --- /dev/null +++ b/dom/tests/browser/browser_test_focus_after_modal_state.js @@ -0,0 +1,71 @@ +const TEST_URL = + "https://example.com/browser/dom/tests/browser/focus_after_prompt.html"; + +const { PromptTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromptTestUtils.sys.mjs" +); + +function awaitAndClosePrompt(browser) { + return PromptTestUtils.handleNextPrompt( + browser, + { modalType: Services.prompt.MODAL_TYPE_CONTENT, promptType: "prompt" }, + { buttonNumClick: 0 } + ); +} + +add_task(async function () { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + let browser = tab.linkedBrowser; + + // Focus on editable iframe. + await BrowserTestUtils.synthesizeMouseAtCenter("#edit", {}, browser); + await SpecialPowers.spawn(browser, [], async function () { + is( + content.document.activeElement, + content.document.getElementById("edit"), + "Focus should be on iframe element" + ); + }); + + let focusBlurPromise = SpecialPowers.spawn(browser, [], async function () { + let focusOccurred = false; + let blurOccurred = false; + + return new Promise(resolve => { + let doc = content.document.getElementById("edit").contentDocument; + doc.addEventListener("focus", function (event) { + focusOccurred = true; + if (blurOccurred) { + resolve(true); + } + }); + + doc.addEventListener("blur", function (event) { + blurOccurred = true; + if (focusOccurred) { + resolve(false); + } + }); + }); + }); + + // Click on div that triggers a prompt, and then check that focus is back on + // the editable iframe. + let dialogShown = awaitAndClosePrompt(browser); + await SpecialPowers.spawn(browser, [], async function () { + let div = content.document.getElementById("clickMeDiv"); + div.click(); + }); + await dialogShown; + let blurCameFirst = await focusBlurPromise; + await SpecialPowers.spawn(browser, [], async function () { + is( + content.document.activeElement, + content.document.getElementById("edit"), + "Focus should be back on iframe element" + ); + }); + ok(blurCameFirst, "Should receive blur and then focus event"); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/dom/tests/browser/browser_test_new_window_from_content.js b/dom/tests/browser/browser_test_new_window_from_content.js new file mode 100644 index 0000000000..e150ea3232 --- /dev/null +++ b/dom/tests/browser/browser_test_new_window_from_content.js @@ -0,0 +1,221 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* + We have three ways for content to open new windows: + 1) window.open (with the default features) + 2) window.open (with non-default features) + 3) target="_blank" in tags + + We also have two prefs that modify our window opening behaviours: + + 1) browser.link.open_newwindow + + This has a numeric value that allows us to set our window-opening behaviours from + content in three ways: + 1) Open links that would normally open a new window in the current tab + 2) Open links that would normally open a new window in a new window + 3) Open links that would normally open a new window in a new tab (default) + + 2) browser.link.open_newwindow.restriction + + This has a numeric value that allows us to fine tune the browser.link.open_newwindow + pref so that it can discriminate between different techniques for opening windows. + + 0) All things that open windows should behave according to browser.link.open_newwindow. + 1) No things that open windows should behave according to browser.link.open_newwindow + (essentially rendering browser.link.open_newwindow inert). + 2) Most things that open windows should behave according to browser.link.open_newwindow, + _except_ for window.open calls with the "feature" parameter. This will open in a new + window regardless of what browser.link.open_newwindow is set at. (default) + + This file attempts to test each window opening technique against all possible settings for + each preference. +*/ + +const kContentDoc = + "https://www.example.com/browser/dom/tests/browser/test_new_window_from_content_child.html"; +const kNewWindowPrefKey = "browser.link.open_newwindow"; +const kNewWindowRestrictionPrefKey = "browser.link.open_newwindow.restriction"; +const kSameTab = "same tab"; +const kNewWin = "new window"; +const kNewTab = "new tab"; + +SpecialPowers.pushPrefEnv({ + set: [["dom.require_user_interaction_for_beforeunload", false]], +}); + +requestLongerTimeout(3); + +// The following "matrices" represent the result of content attempting to +// open a window with window.open with the default feature set. The key of +// the kWinOpenDefault object represents the value of browser.link.open_newwindow. +// The value for each key is an array that represents the result (either opening +// the link in the same tab, a new window, or a new tab), where the index of each +// result maps to the browser.link.open_newwindow.restriction pref. I've tried +// to illustrate this more clearly in the kWinOpenDefault object. +const kWinOpenDefault = { + // open_newwindow.restriction + // 0 1 2 + // open_newwindow + 1: [kSameTab, kNewWin, kSameTab], + 2: [kNewWin, kNewWin, kNewWin], + 3: [kNewTab, kNewWin, kNewTab], +}; + +const kWinOpenNonDefault = { + 1: [kSameTab, kNewWin, kNewWin], + 2: [kNewWin, kNewWin, kNewWin], + 3: [kNewTab, kNewWin, kNewWin], +}; + +const kTargetBlank = { + 1: [kSameTab, kSameTab, kSameTab], + 2: [kNewWin, kNewWin, kNewWin], + 3: [kNewTab, kNewTab, kNewTab], +}; + +// We'll be changing these preferences a lot, so we'll stash their original +// values and make sure we restore them at the end of the test. +var originalNewWindowPref = Services.prefs.getIntPref(kNewWindowPrefKey); +var originalNewWindowRestrictionPref = Services.prefs.getIntPref( + kNewWindowRestrictionPrefKey +); + +registerCleanupFunction(function () { + Services.prefs.setIntPref(kNewWindowPrefKey, originalNewWindowPref); + Services.prefs.setIntPref( + kNewWindowRestrictionPrefKey, + originalNewWindowRestrictionPref + ); +}); + +/** + * For some expectation when a link is clicked, creates and + * returns a Promise that resolves when that expectation is + * fulfilled. For example, aExpectation might be kSameTab, which + * will cause this function to return a Promise that resolves when + * the current tab attempts to browse to about:blank. + * + * This function also takes care of cleaning up once the result has + * occurred - for example, if a new window was opened, this function + * closes it before resolving. + * + * @param aBrowser the with the test document + * @param aExpectation one of kSameTab, kNewWin, or kNewTab. + * @return a Promise that resolves when the expectation is fulfilled, + * and cleaned up after. + */ +function prepareForResult(aBrowser, aExpectation) { + let expectedSpec = kContentDoc.replace(/[^\/]*$/, "dummy.html"); + switch (aExpectation) { + case kSameTab: + return (async function () { + await BrowserTestUtils.browserLoaded(aBrowser); + is(aBrowser.currentURI.spec, expectedSpec, "Should be at dummy.html"); + // Now put the browser back where it came from + BrowserTestUtils.loadURIString(aBrowser, kContentDoc); + await BrowserTestUtils.browserLoaded(aBrowser); + })(); + case kNewWin: + return (async function () { + let newWin = await BrowserTestUtils.waitForNewWindow({ + url: expectedSpec, + }); + let newBrowser = newWin.gBrowser.selectedBrowser; + is(newBrowser.currentURI.spec, expectedSpec, "Should be at dummy.html"); + await BrowserTestUtils.closeWindow(newWin); + })(); + case kNewTab: + return (async function () { + let newTab = await BrowserTestUtils.waitForNewTab(gBrowser); + is( + newTab.linkedBrowser.currentURI.spec, + expectedSpec, + "Should be at dummy.html" + ); + BrowserTestUtils.removeTab(newTab); + })(); + default: + ok( + false, + "prepareForResult can't handle an expectation of " + aExpectation + ); + return Promise.resolve(); + } +} + +/** + * Ensure that clicks on a link with ID aLinkID cause us to + * perform as specified in the supplied aMatrix (kWinOpenDefault, + * for example). + * + * @param aLinkSelector a selector for the link within the testing page to click. + * @param aMatrix a testing matrix for the + * browser.link.open_newwindow and browser.link.open_newwindow.restriction + * prefs to test against. See kWinOpenDefault for an example. + */ +function testLinkWithMatrix(aLinkSelector, aMatrix) { + return BrowserTestUtils.withNewTab( + { + gBrowser, + url: kContentDoc, + }, + async function (browser) { + // This nested for-loop is unravelling the matrix const + // we set up, and gives us three things through each tick + // of the inner loop: + // 1) newWindowPref: a browser.link.open_newwindow pref to try + // 2) newWindowRestPref: a browser.link.open_newwindow.restriction pref to try + // 3) expectation: what we expect the click outcome on this link to be, + // which will either be kSameTab, kNewWin or kNewTab. + for (let newWindowPref in aMatrix) { + let expectations = aMatrix[newWindowPref]; + for (let i = 0; i < expectations.length; ++i) { + let newWindowRestPref = i; + let expectation = expectations[i]; + + Services.prefs.setIntPref( + "browser.link.open_newwindow", + newWindowPref + ); + Services.prefs.setIntPref( + "browser.link.open_newwindow.restriction", + newWindowRestPref + ); + info("Clicking on " + aLinkSelector); + info( + "Testing with browser.link.open_newwindow = " + + newWindowPref + + " and " + + "browser.link.open_newwindow.restriction = " + + newWindowRestPref + ); + info("Expecting: " + expectation); + let resultPromise = prepareForResult(browser, expectation); + BrowserTestUtils.synthesizeMouseAtCenter(aLinkSelector, {}, browser); + await resultPromise; + info("Got expectation: " + expectation); + } + } + } + ); +} + +add_task(async function test_window_open_with_defaults() { + await testLinkWithMatrix("#winOpenDefault", kWinOpenDefault); +}); + +add_task(async function test_window_open_with_non_defaults() { + await testLinkWithMatrix("#winOpenNonDefault", kWinOpenNonDefault); +}); + +add_task(async function test_window_open_dialog() { + await testLinkWithMatrix("#winOpenDialog", kWinOpenNonDefault); +}); + +add_task(async function test_target__blank() { + await testLinkWithMatrix("#targetBlank", kTargetBlank); +}); diff --git a/dom/tests/browser/browser_test_toolbars_visibility.js b/dom/tests/browser/browser_test_toolbars_visibility.js new file mode 100644 index 0000000000..f313f1549f --- /dev/null +++ b/dom/tests/browser/browser_test_toolbars_visibility.js @@ -0,0 +1,323 @@ +// Tests that toolbars have proper visibility when opening a new window +// in either content or chrome context. + +const ROOT = "http://www.example.com/browser/dom/tests/browser/"; +const CONTENT_PAGE = ROOT + "test_new_window_from_content_child.html"; +const TARGET_PAGE = ROOT + "dummy.html"; + +/** + * This function retrieves the visibility state of the toolbars of a + * window within the content context. + * + * @param aBrowser () + * The browser to query for toolbar visibility states + * @returns Promise + * A promise that resolves when the toolbar state is retrieved + * within the content context, which value is an object that holds + * the visibility state of the toolbars + */ +function getToolbarsFromBrowserContent(aBrowser) { + return SpecialPowers.spawn(aBrowser, [], async function () { + // This is still chrome context. + // Inject a script that runs on content context, and gather the result. + + let script = content.document.createElement("script"); + script.textContent = ` +let bars = [ + "toolbar", + "menubar", + "personalbar", + "statusbar", + "scrollbars", + "locationbar", +]; + +for (let bar of bars) { + let node = document.createElement("span"); + node.id = bar; + node.textContent = window[bar].visible; + document.body.appendChild(node); +} +`; + content.document.body.appendChild(script); + + let result = {}; + + let bars = [ + "toolbar", + "menubar", + "personalbar", + "statusbar", + "scrollbars", + "locationbar", + ]; + + for (let bar of bars) { + let node = content.document.getElementById(bar); + let value = node.textContent; + if (value !== "true" && value !== "false") { + throw new Error("bar visibility isn't set"); + } + result[bar] = value === "true"; + node.remove(); + } + + return result; + }); +} + +/** + * This function retrieves the visibility state of the toolbars of a + * window within the chrome context. + * + * @param win + * the chrome privileged window + * @returns object + * an object that holds the visibility state of the toolbars + */ +function getToolbarsFromWindowChrome(win) { + return { + toolbar: win.toolbar.visible, + menubar: win.menubar.visible, + personalbar: win.personalbar.visible, + statusbar: win.statusbar.visible, + scrollbars: win.scrollbars.visible, + locationbar: win.locationbar.visible, + }; +} + +/** + * Tests toolbar visibility when opening a window with default parameters. + * + * @param toolbars + * the visibility state of the toolbar elements + */ +function testDefaultToolbars(toolbars) { + ok( + toolbars.locationbar, + "locationbar should be visible on default window.open()" + ); + ok(toolbars.menubar, "menubar be visible on default window.open()"); + ok( + toolbars.personalbar, + "personalbar should be visible on default window.open()" + ); + ok( + toolbars.statusbar, + "statusbar should be visible on default window.open()" + ); + ok( + toolbars.scrollbars, + "scrollbars should be visible on default window.open()" + ); + ok(toolbars.toolbar, "toolbar should be visible on default window.open()"); +} + +/** + * Tests toolbar visibility when opening a popup window on the content context, + * and reading the value from context context. + * + * @param toolbars + * the visibility state of the toolbar elements + */ +function testNonDefaultContentToolbarsFromContent(toolbars) { + // Accessing BarProp.visible from content context should return false for + // popup, regardless of the each feature value in window.open parameter. + ok(!toolbars.locationbar, "locationbar.visible should be false for popup"); + ok(!toolbars.menubar, "menubar.visible should be false for popup"); + ok(!toolbars.personalbar, "personalbar.visible should be false for popup"); + ok(!toolbars.statusbar, "statusbar.visible should be false for popup"); + ok(!toolbars.scrollbars, "scrollbars.visible should be false for popup"); + ok(!toolbars.toolbar, "toolbar.visible should be false for popup"); +} + +/** + * Tests toolbar visibility when opening a popup window on the content context, + * and reading the value from chrome context. + * + * @param toolbars + * the visibility state of the toolbar elements + */ +function testNonDefaultContentToolbarsFromChrome(toolbars) { + // Accessing BarProp.visible from chrome context should return the + // actual visibility. + + // Locationbar should always be visible on content context + ok( + toolbars.locationbar, + "locationbar should be visible even with location=no" + ); + ok(!toolbars.menubar, "menubar shouldn't be visible when menubar=no"); + ok( + !toolbars.personalbar, + "personalbar shouldn't be visible when personalbar=no" + ); + // statusbar will report visible=true even when it's hidden because of bug#55820 + todo(!toolbars.statusbar, "statusbar shouldn't be visible when status=no"); + ok( + toolbars.scrollbars, + "scrollbars should be visible even with scrollbars=no" + ); + ok(!toolbars.toolbar, "toolbar shouldn't be visible when toolbar=no"); +} + +/** + * Tests toolbar visibility when opening a window with non default parameters + * on the chrome context. + * + * @param toolbars + * the visibility state of the toolbar elements + */ +function testNonDefaultChromeToolbars(toolbars) { + // None of the toolbars should be visible if hidden with chrome privileges + ok( + !toolbars.locationbar, + "locationbar should not be visible with location=no" + ); + ok(!toolbars.menubar, "menubar should not be visible with menubar=no"); + ok( + !toolbars.personalbar, + "personalbar should not be visible with personalbar=no" + ); + ok(!toolbars.statusbar, "statusbar should not be visible with status=no"); + ok( + toolbars.scrollbars, + "scrollbars should be visible even with scrollbars=no" + ); + ok(!toolbars.toolbar, "toolbar should not be visible with toolbar=no"); +} + +/** + * Ensure that toolbars of a window opened in the content context have the + * correct visibility. + * + * A window opened with default parameters should have all toolbars visible. + * + * A window opened with "location=no, personalbar=no, toolbar=no, scrollbars=no, + * menubar=no, status=no", should only have location visible. + */ +add_task(async function () { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: CONTENT_PAGE, + }, + async function (browser) { + // First, call the default window.open() which will open a new tab + let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#winOpenDefault", + {}, + browser + ); + let tab = await newTabPromise; + + // Check that all toolbars are visible + let toolbars = await getToolbarsFromBrowserContent( + gBrowser.selectedBrowser + ); + testDefaultToolbars(toolbars); + + // Cleanup + BrowserTestUtils.removeTab(tab); + + // Now let's open a window with toolbars hidden + let winPromise = BrowserTestUtils.waitForNewWindow({ url: TARGET_PAGE }); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#winOpenNonDefault", + {}, + browser + ); + let popupWindow = await winPromise; + + let popupBrowser = popupWindow.gBrowser.selectedBrowser; + + // Test toolbars visibility value from content. + let popupToolbars = await getToolbarsFromBrowserContent(popupBrowser); + testNonDefaultContentToolbarsFromContent(popupToolbars); + + // Test toolbars visibility value from chrome. + let chromeToolbars = getToolbarsFromWindowChrome(popupWindow); + testNonDefaultContentToolbarsFromChrome(chromeToolbars); + + // Close the new window + await BrowserTestUtils.closeWindow(popupWindow); + } + ); +}); + +/** + * Ensure that toolbars of a window opened to about:blank in the content context + * have the correct visibility. + * + * A window opened with "location=no, personalbar=no, toolbar=no, scrollbars=no, + * menubar=no, status=no", should only have location visible. + */ +add_task(async function () { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: CONTENT_PAGE, + }, + async function (browser) { + // Open a blank window with toolbars hidden + let winPromise = BrowserTestUtils.waitForNewWindow(); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#winOpenNoURLNonDefault", + {}, + browser + ); + let popupWindow = await winPromise; + + // No need to wait for this window to load, since it's loading about:blank + let popupBrowser = popupWindow.gBrowser.selectedBrowser; + let popupToolbars = await getToolbarsFromBrowserContent(popupBrowser); + testNonDefaultContentToolbarsFromContent(popupToolbars); + + let chromeToolbars = getToolbarsFromWindowChrome(popupWindow); + testNonDefaultContentToolbarsFromChrome(chromeToolbars); + + // Close the new window + await BrowserTestUtils.closeWindow(popupWindow); + } + ); +}); + +/** + * Ensure that toolbars of a window opened in the chrome context have the + * correct visibility. + * + * A window opened with default parameters should have all toolbars visible. + * + * A window opened with "location=no, personalbar=no, toolbar=no, scrollbars=no, + * menubar=no, status=no", should not have any toolbar visible. + */ +add_task(async function () { + // First open a default window from this chrome context + let defaultWindowPromise = BrowserTestUtils.waitForNewWindow({ + url: TARGET_PAGE, + }); + window.open(TARGET_PAGE, "_blank", "noopener"); + let defaultWindow = await defaultWindowPromise; + + // Check that all toolbars are visible + let toolbars = getToolbarsFromWindowChrome(defaultWindow); + testDefaultToolbars(toolbars); + + // Now lets open a window with toolbars hidden from this chrome context + let features = + "location=no, personalbar=no, toolbar=no, scrollbars=no, menubar=no, status=no, noopener"; + let popupWindowPromise = BrowserTestUtils.waitForNewWindow({ + url: TARGET_PAGE, + }); + window.open(TARGET_PAGE, "_blank", features); + let popupWindow = await popupWindowPromise; + + // Test none of the tooolbars are visible + let hiddenToolbars = getToolbarsFromWindowChrome(popupWindow); + testNonDefaultChromeToolbars(hiddenToolbars); + + // Cleanup + await BrowserTestUtils.closeWindow(defaultWindow); + await BrowserTestUtils.closeWindow(popupWindow); +}); diff --git a/dom/tests/browser/browser_unlinkable_about_page_can_load_module_scripts.js b/dom/tests/browser/browser_unlinkable_about_page_can_load_module_scripts.js new file mode 100644 index 0000000000..ca4f9a7bc3 --- /dev/null +++ b/dom/tests/browser/browser_unlinkable_about_page_can_load_module_scripts.js @@ -0,0 +1,83 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +"use strict"; + +const kTestPage = getRootDirectory(gTestPath) + "file_load_module_script.html"; + +const kDefaultFlags = + Ci.nsIAboutModule.ALLOW_SCRIPT | + Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD | + Ci.nsIAboutModule.URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS | + Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT; + +const kAboutPagesRegistered = Promise.all([ + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, + "test-linkable-page", + kTestPage, + kDefaultFlags | Ci.nsIAboutModule.MAKE_LINKABLE + ), + BrowserTestUtils.registerAboutPage( + registerCleanupFunction, + "test-unlinkable-page", + kTestPage, + kDefaultFlags + ), +]); + +add_task(async function () { + await kAboutPagesRegistered; + + let consoleListener = { + observe() { + errorCount++; + if (shouldHaveJSError) { + ok(true, "JS error is expected and received"); + } else { + ok(false, "Unexpected JS error was received"); + } + }, + }; + Services.console.registerListener(consoleListener); + registerCleanupFunction(() => + Services.console.unregisterListener(consoleListener) + ); + + let shouldHaveJSError = true; + let errorCount = 0; + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:test-linkable-page" }, + async browser => { + await SpecialPowers.spawn(browser, [], () => { + isnot( + content.document.body.textContent, + "scriptLoaded", + "The page content shouldn't be changed on the linkable page" + ); + }); + } + ); + ok( + errorCount > 0, + "Should have an error when loading test-linkable-page, got " + errorCount + ); + + shouldHaveJSError = false; + errorCount = 0; + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:test-unlinkable-page" }, + async browser => { + await SpecialPowers.spawn(browser, [], () => { + is( + content.document.body.textContent, + "scriptLoaded", + "The page content should be changed on the unlinkable page" + ); + }); + } + ); + is(errorCount, 0, "Should have no errors when loading test-unlinkable-page"); +}); diff --git a/dom/tests/browser/browser_wakelock.js b/dom/tests/browser/browser_wakelock.js new file mode 100644 index 0000000000..5cef231d07 --- /dev/null +++ b/dom/tests/browser/browser_wakelock.js @@ -0,0 +1,40 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +add_task(async () => { + info("creating test window"); + let win = await BrowserTestUtils.openNewBrowserWindow(); + + const pm = Cc["@mozilla.org/power/powermanagerservice;1"].getService( + Ci.nsIPowerManagerService + ); + + is( + pm.getWakeLockState("screen"), + "unlocked", + "Wakelock should be unlocked state" + ); + + info("aquiring wakelock"); + const wakelock = pm.newWakeLock("screen", win); + isnot( + pm.getWakeLockState("screen"), + "unlocked", + "Wakelock shouldn't be unlocked state" + ); + + info("releasing wakelock"); + wakelock.unlock(); + is( + pm.getWakeLockState("screen"), + "unlocked", + "Wakelock should be unlocked state" + ); + + info("closing test window"); + await BrowserTestUtils.closeWindow(win); +}); diff --git a/dom/tests/browser/browser_windowProxy_transplant.js b/dom/tests/browser/browser_windowProxy_transplant.js new file mode 100644 index 0000000000..9001437a2d --- /dev/null +++ b/dom/tests/browser/browser_windowProxy_transplant.js @@ -0,0 +1,215 @@ +"use strict"; + +const DIRPATH = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "" +); +const PATH = DIRPATH + "file_postMessage_parent.html"; + +const URL1 = `http://mochi.test:8888/${PATH}`; +const URL2 = `http://example.com/${PATH}`; +const URL3 = `http://example.org/${PATH}`; + +// A bunch of boilerplate which needs to be dealt with. +add_task(async function () { + // Turn on BC preservation and frameloader rebuilding to ensure that the + // BrowsingContext is preserved. + await SpecialPowers.pushPrefEnv({ + set: [["fission.preserve_browsing_contexts", true]], + }); + + // Open a window with fission force-enabled in it. + let win = await BrowserTestUtils.openNewBrowserWindow({ + fission: true, + remote: true, + }); + try { + // Get the tab & browser to perform the test in. + let tab = win.gBrowser.selectedTab; + let browser = tab.linkedBrowser; + + // Start loading the original URI, then wait until it is loaded. + BrowserTestUtils.loadURIString(browser, URL1); + await BrowserTestUtils.browserLoaded(browser, false, URL1); + + info("Chrome script has loaded initial URI."); + await SpecialPowers.spawn( + browser, + [{ URL1, URL2, URL3 }], + async ({ URL1, URL2, URL3 }) => { + let iframe = content.document.createElement("iframe"); + content.document.body.appendChild(iframe); + + info("Chrome script created iframe"); + + // Here and below, we have to store references to things in the + // iframes on the content window, because all chrome references + // to content will be turned into dead wrappers when the iframes + // are closed. + content.win0 = iframe.contentWindow; + content.bc0 = iframe.browsingContext; + + ok( + !Cu.isDeadWrapper(content.win0), + "win0 shouldn't be a dead wrapper before navigation" + ); + + // Helper for waiting for a load. + function waitLoad() { + return new Promise(resolve => { + iframe.addEventListener( + "load", + event => { + info("Got an iframe load event!"); + resolve(); + }, + { once: true } + ); + }); + } + + function askLoad(url) { + info("Chrome script asking for load of " + url); + iframe.contentWindow.postMessage( + { + action: "navigate", + location: url, + }, + "*" + ); + info("Chrome script done calling PostMessage"); + } + + // Check that BC and WindowProxy are preserved across navigations. + iframe.contentWindow.location = URL1; + await waitLoad(); + + content.win1 = iframe.contentWindow; + let chromeWin1 = iframe.contentWindow; + let chromeWin1x = Cu.waiveXrays(iframe.contentWindow); + content.win1x = Cu.waiveXrays(iframe.contentWindow); + + ok(chromeWin1 != chromeWin1x, "waiving xrays creates a new thing?"); + + content.bc1 = iframe.browsingContext; + + is( + content.bc0, + content.bc1, + "same to same-origin BrowsingContext match" + ); + is(content.win0, content.win1, "same to same-origin WindowProxy match"); + + ok( + !Cu.isDeadWrapper(content.win1), + "win1 shouldn't be a dead wrapper before navigation" + ); + ok( + !Cu.isDeadWrapper(chromeWin1), + "chromeWin1 shouldn't be a dead wrapper before navigation" + ); + + askLoad(URL2); + await waitLoad(); + + content.win2 = iframe.contentWindow; + content.bc2 = iframe.browsingContext; + + // When chrome accesses a remote window proxy in content, the result + // should be a remote outer window proxy in the chrome compartment, not an + // Xray wrapper around the content remote window proxy. The former will + // throw a security error, because @@toPrimitive can't be called cross + // process, while the latter will result in an opaque wrapper, because + // XPConnect doesn't know what to do when trying to create an Xray wrapper + // around a remote outer window proxy. See bug 1556845. + Assert.throws( + () => { + dump("content.win1 " + content.win1 + "\n"); + }, + /SecurityError: Permission denied to access property Symbol.toPrimitive on cross-origin object/, + "Should get a remote outer window proxy when accessing old window proxy" + ); + Assert.throws( + () => { + dump("content.win2 " + content.win2 + "\n"); + }, + /SecurityError: Permission denied to access property Symbol.toPrimitive on cross-origin object/, + "Should get a remote outer window proxy when accessing new window proxy" + ); + + // If we fail to transplant existing non-remote outer window proxies, then + // after we navigate the iframe existing chrome references to the window will + // become dead wrappers. Also check content.win1 for thoroughness, though + // we don't nuke content-content references. + ok( + !Cu.isDeadWrapper(content.win1), + "win1 shouldn't be a dead wrapper after navigation" + ); + ok( + !Cu.isDeadWrapper(chromeWin1), + "chromeWin1 shouldn't be a dead wrapper after navigation" + ); + ok( + Cu.isDeadWrapper(chromeWin1x), + "chromeWin1x should be a dead wrapper after navigation" + ); + ok( + Cu.isDeadWrapper(content.win1x), + "content.win1x should be a dead wrapper after navigation" + ); + + is( + content.bc1, + content.bc2, + "same to cross-origin navigation BrowsingContext match" + ); + is( + content.win1, + content.win2, + "same to cross-origin navigation WindowProxy match" + ); + + ok( + !Cu.isDeadWrapper(content.win1), + "win1 shouldn't be a dead wrapper after navigation" + ); + + askLoad(URL3); + await waitLoad(); + + content.win3 = iframe.contentWindow; + content.bc3 = iframe.browsingContext; + + is( + content.bc2, + content.bc3, + "cross to cross-origin navigation BrowsingContext match" + ); + is( + content.win2, + content.win3, + "cross to cross-origin navigation WindowProxy match" + ); + + askLoad(URL1); + await waitLoad(); + + content.win4 = iframe.contentWindow; + content.bc4 = iframe.browsingContext; + + is( + content.bc3, + content.bc4, + "cross to same-origin navigation BrowsingContext match" + ); + is( + content.win3, + content.win4, + "cross to same-origin navigation WindowProxy match" + ); + } + ); + } finally { + await BrowserTestUtils.closeWindow(win); + } +}); diff --git a/dom/tests/browser/browser_xhr_sandbox.js b/dom/tests/browser/browser_xhr_sandbox.js new file mode 100644 index 0000000000..cca69b916b --- /dev/null +++ b/dom/tests/browser/browser_xhr_sandbox.js @@ -0,0 +1,62 @@ +// This code is evaluated in a sandbox courtesy of toSource(); +var sandboxCode = + function () { + let req = new XMLHttpRequest(); + req.open("GET", "http://mochi.test:8888/browser/dom/tests/browser/", true); + req.onreadystatechange = function () { + if (req.readyState === 4) { + // If we get past the problem above, we end up with a req.status of zero + // (ie, blocked due to CORS) even though we are fetching from the same + // origin as the window itself. + let result; + if (req.status != 200) { + result = "ERROR: got request status of " + req.status; + } else if (!req.responseText.length) { + result = "ERROR: got zero byte response text"; + } else { + result = "ok"; + } + postMessage({ result }, "*"); + } + }; + req.send(null); + }.toSource() + "();"; + +add_task(async function test() { + await SpecialPowers.pushPrefEnv({ + set: [["security.allow_unsafe_parent_loads", true]], + }); + + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + + let frame = newWin.document.createXULElement("iframe"); + frame.setAttribute("type", "content"); + frame.setAttribute( + "src", + "http://mochi.test:8888/browser/dom/tests/browser/browser_xhr_sandbox.js" + ); + + newWin.document.documentElement.appendChild(frame); + await BrowserTestUtils.waitForEvent(frame, "load", true); + + let contentWindow = frame.contentWindow; + let sandbox = new Cu.Sandbox(contentWindow); + + // inject some functions from the window into the sandbox. + // postMessage so the async code in the sandbox can report a result. + sandbox.importFunction( + contentWindow.postMessage.bind(contentWindow), + "postMessage" + ); + sandbox.importFunction(contentWindow.XMLHttpRequest, "XMLHttpRequest"); + Cu.evalInSandbox(sandboxCode, sandbox, "1.8"); + + let sandboxReply = await BrowserTestUtils.waitForEvent( + contentWindow, + "message", + true + ); + is(sandboxReply.data.result, "ok", "check the sandbox code was felipe"); + + await BrowserTestUtils.closeWindow(newWin); +}); diff --git a/dom/tests/browser/create_webrtc_peer_connection.html b/dom/tests/browser/create_webrtc_peer_connection.html new file mode 100644 index 0000000000..ee993d4892 --- /dev/null +++ b/dom/tests/browser/create_webrtc_peer_connection.html @@ -0,0 +1,28 @@ + + + + + + +
+ diff --git a/dom/tests/browser/dummy.html b/dom/tests/browser/dummy.html new file mode 100644 index 0000000000..6ec72c2160 --- /dev/null +++ b/dom/tests/browser/dummy.html @@ -0,0 +1,13 @@ + + + +Dummy test page + + + +

Dummy test page

+ + + diff --git a/dom/tests/browser/dummy.png b/dom/tests/browser/dummy.png new file mode 100644 index 0000000000..a1089af09b Binary files /dev/null and b/dom/tests/browser/dummy.png differ diff --git a/dom/tests/browser/file_bug1685807.html b/dom/tests/browser/file_bug1685807.html new file mode 100644 index 0000000000..9e35fa1730 --- /dev/null +++ b/dom/tests/browser/file_bug1685807.html @@ -0,0 +1,12 @@ + + + + + + + diff --git a/dom/tests/browser/file_coop_coep.html b/dom/tests/browser/file_coop_coep.html new file mode 100644 index 0000000000..f77ef9d5f2 --- /dev/null +++ b/dom/tests/browser/file_coop_coep.html @@ -0,0 +1,6 @@ + + +
+
+ + diff --git a/dom/tests/browser/file_coop_coep.html^headers^ b/dom/tests/browser/file_coop_coep.html^headers^ new file mode 100644 index 0000000000..63b60e490f --- /dev/null +++ b/dom/tests/browser/file_coop_coep.html^headers^ @@ -0,0 +1,2 @@ +Cross-Origin-Opener-Policy: same-origin +Cross-Origin-Embedder-Policy: require-corp diff --git a/dom/tests/browser/file_empty.html b/dom/tests/browser/file_empty.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/dom/tests/browser/file_empty_cross_site_frame.html b/dom/tests/browser/file_empty_cross_site_frame.html new file mode 100644 index 0000000000..fb134786f6 --- /dev/null +++ b/dom/tests/browser/file_empty_cross_site_frame.html @@ -0,0 +1,2 @@ + + diff --git a/dom/tests/browser/file_load_module_script.html b/dom/tests/browser/file_load_module_script.html new file mode 100644 index 0000000000..004c727bb4 --- /dev/null +++ b/dom/tests/browser/file_load_module_script.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/dom/tests/browser/file_module_loaded.js b/dom/tests/browser/file_module_loaded.js new file mode 100644 index 0000000000..8763889e1f --- /dev/null +++ b/dom/tests/browser/file_module_loaded.js @@ -0,0 +1,6 @@ +"use strict"; + +import setBodyText from "chrome://mochitests/content/browser/dom/tests/browser/file_module_loaded2.js"; +document.addEventListener("DOMContentLoaded", () => { + setBodyText(); +}); diff --git a/dom/tests/browser/file_module_loaded2.js b/dom/tests/browser/file_module_loaded2.js new file mode 100644 index 0000000000..28b26cd16e --- /dev/null +++ b/dom/tests/browser/file_module_loaded2.js @@ -0,0 +1,3 @@ +export default function setBodyText() { + document.body.textContent = "scriptLoaded"; +} diff --git a/dom/tests/browser/file_postMessage_parent.html b/dom/tests/browser/file_postMessage_parent.html new file mode 100644 index 0000000000..f9aa63a8c7 --- /dev/null +++ b/dom/tests/browser/file_postMessage_parent.html @@ -0,0 +1,48 @@ + + diff --git a/dom/tests/browser/focus_after_prompt.html b/dom/tests/browser/focus_after_prompt.html new file mode 100644 index 0000000000..db60d19189 --- /dev/null +++ b/dom/tests/browser/focus_after_prompt.html @@ -0,0 +1,18 @@ + + + + + Cursor should not be lost after prompt + + + + +
Click me!
+ + + diff --git a/dom/tests/browser/geo_leak_test.html b/dom/tests/browser/geo_leak_test.html new file mode 100644 index 0000000000..fb3fabac40 --- /dev/null +++ b/dom/tests/browser/geo_leak_test.html @@ -0,0 +1,17 @@ + + + +Geolocation incomplete position leak test + + + + + diff --git a/dom/tests/browser/helper_localStorage.js b/dom/tests/browser/helper_localStorage.js new file mode 100644 index 0000000000..386d2d8b4e --- /dev/null +++ b/dom/tests/browser/helper_localStorage.js @@ -0,0 +1,302 @@ +/* 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/. */ + +// Simple tab wrapper abstracting our messaging mechanism; +class KnownTab { + constructor(name, tab) { + this.name = name; + this.tab = tab; + } + + cleanup() { + this.tab = null; + } +} + +// Simple data structure class to help us track opened tabs and their pids. +class KnownTabs { + constructor() { + this.byPid = new Map(); + this.byName = new Map(); + } + + cleanup() { + for (let key of this.byPid.keys()) { + this.byPid[key] = null; + } + this.byPid = null; + this.byName = null; + } +} + +/** + * Open our helper page in a tab in its own content process, asserting that it + * really is in its own process. We initially load and wait for about:blank to + * load, and only then loadURI to our actual page. This is to ensure that + * LocalStorageManager has had an opportunity to be created and populate + * mOriginsHavingData. + * + * (nsGlobalWindow will reliably create LocalStorageManager as a side-effect of + * the unconditional call to nsGlobalWindow::PreloadLocalStorage. This will + * reliably create the StorageDBChild instance, and its corresponding + * StorageDBParent will send the set of origins when it is constructed.) + */ +async function openTestTab( + helperPageUrl, + name, + knownTabs, + shouldLoadInNewProcess +) { + let realUrl = helperPageUrl + "?" + encodeURIComponent(name); + // Load and wait for about:blank. + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:blank", + forceNewProcess: true, + }); + ok(!knownTabs.byName.has(name), "tab needs its own name: " + name); + + let knownTab = new KnownTab(name, tab); + knownTabs.byName.set(name, knownTab); + + // Now trigger the actual load of our page. + BrowserTestUtils.loadURIString(tab.linkedBrowser, realUrl); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + let pid = tab.linkedBrowser.frameLoader.remoteTab.osPid; + if (shouldLoadInNewProcess) { + ok( + !knownTabs.byPid.has(pid), + "tab should be loaded in new process, pid: " + pid + ); + } else { + ok( + knownTabs.byPid.has(pid), + "tab should be loaded in the same process, new pid: " + pid + ); + } + + if (knownTabs.byPid.has(pid)) { + knownTabs.byPid.get(pid).set(name, knownTab); + } else { + let pidMap = new Map(); + pidMap.set(name, knownTab); + knownTabs.byPid.set(pid, pidMap); + } + + return knownTab; +} + +/** + * Close all the tabs we opened. + */ +async function cleanupTabs(knownTabs) { + for (let knownTab of knownTabs.byName.values()) { + BrowserTestUtils.removeTab(knownTab.tab); + knownTab.cleanup(); + } + knownTabs.cleanup(); +} + +/** + * Wait for a LocalStorage flush to occur. This notification can occur as a + * result of any of: + * - The normal, hardcoded 5-second flush timer. + * - InsertDBOp seeing a preload op for an origin with outstanding changes. + * - Us generating a "domstorage-test-flush-force" observer notification. + */ +function waitForLocalStorageFlush() { + if (Services.domStorageManager.nextGenLocalStorageEnabled) { + return new Promise(resolve => executeSoon(resolve)); + } + + return new Promise(function (resolve) { + let observer = { + observe() { + SpecialPowers.removeObserver(observer, "domstorage-test-flushed"); + resolve(); + }, + }; + SpecialPowers.addObserver(observer, "domstorage-test-flushed"); + }); +} + +/** + * Trigger and wait for a flush. This is only necessary for forcing + * mOriginsHavingData to be updated. Normal operations exposed to content know + * to automatically flush when necessary for correctness. + * + * The notification we're waiting for to verify flushing is fundamentally + * ambiguous (see waitForLocalStorageFlush), so we actually trigger the flush + * twice and wait twice. In the event there was a race, there will be 3 flush + * notifications, but correctness is guaranteed after the second notification. + */ +function triggerAndWaitForLocalStorageFlush() { + if (Services.domStorageManager.nextGenLocalStorageEnabled) { + return new Promise(resolve => executeSoon(resolve)); + } + + SpecialPowers.notifyObservers(null, "domstorage-test-flush-force"); + // This first wait is ambiguous... + return waitForLocalStorageFlush().then(function () { + // So issue a second flush and wait for that. + SpecialPowers.notifyObservers(null, "domstorage-test-flush-force"); + return waitForLocalStorageFlush(); + }); +} + +/** + * Clear the origin's storage so that "OriginsHavingData" will return false for + * our origin. Note that this is only the case for AsyncClear() which is + * explicitly issued against a cache, or AsyncClearAll() which we can trigger + * by wiping all storage. However, the more targeted domain clearings that + * we can trigger via observer, AsyncClearMatchingOrigin and + * AsyncClearMatchingOriginAttributes will not clear the hashtable entry for + * the origin. + * + * So we explicitly access the cache here in the parent for the origin and issue + * an explicit clear. Clearing all storage might be a little easier but seems + * like asking for intermittent failures. + */ +function clearOriginStorageEnsuringNoPreload(origin) { + let principal = + Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin); + + if (Services.domStorageManager.nextGenLocalStorageEnabled) { + let request = Services.qms.clearStoragesForPrincipal( + principal, + "default", + "ls" + ); + let promise = new Promise(resolve => { + request.callback = () => { + resolve(); + }; + }); + return promise; + } + + // We want to use createStorage to force the cache to be created so we can + // issue the clear. It's possible for getStorage to return false but for the + // origin preload hash to still have our origin in it. + let storage = Services.domStorageManager.createStorage( + null, + principal, + principal, + "" + ); + storage.clear(); + + // We also need to trigger a flush os that mOriginsHavingData gets updated. + // The inherent flush race is fine here because + return triggerAndWaitForLocalStorageFlush(); +} + +async function verifyTabPreload(knownTab, expectStorageExists, origin) { + let storageExists = await SpecialPowers.spawn( + knownTab.tab.linkedBrowser, + [origin], + function (origin) { + let principal = + Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin); + if (Services.domStorageManager.nextGenLocalStorageEnabled) { + return Services.domStorageManager.isPreloaded(principal); + } + return !!Services.domStorageManager.getStorage( + null, + principal, + principal + ); + } + ); + is(storageExists, expectStorageExists, "Storage existence === preload"); +} + +/** + * Instruct the given tab to execute the given series of mutations. For + * simplicity, the mutations representation matches the expected events rep. + */ +async function mutateTabStorage(knownTab, mutations, sentinelValue) { + await SpecialPowers.spawn( + knownTab.tab.linkedBrowser, + [{ mutations, sentinelValue }], + function (args) { + return content.wrappedJSObject.mutateStorage(Cu.cloneInto(args, content)); + } + ); +} + +/** + * Instruct the given tab to add a "storage" event listener and record all + * received events. verifyTabStorageEvents is the corresponding method to + * check and assert the recorded events. + */ +async function recordTabStorageEvents(knownTab, sentinelValue) { + await SpecialPowers.spawn( + knownTab.tab.linkedBrowser, + [sentinelValue], + function (sentinelValue) { + return content.wrappedJSObject.listenForStorageEvents(sentinelValue); + } + ); +} + +/** + * Retrieve the current localStorage contents perceived by the tab and assert + * that they match the provided expected state. + * + * If maybeSentinel is non-null, it's assumed to be a string that identifies the + * value we should be waiting for the sentinel key to take on. This is + * necessary because we cannot make any assumptions about when state will be + * propagated to the given process. See the comments in + * page_localstorage_e10s.js for more context. In general, a sentinel value is + * required for correctness unless the process in question is the one where the + * writes were performed or verifyTabStorageEvents was used. + */ +async function verifyTabStorageState(knownTab, expectedState, maybeSentinel) { + let actualState = await SpecialPowers.spawn( + knownTab.tab.linkedBrowser, + [maybeSentinel], + function (maybeSentinel) { + return content.wrappedJSObject.getStorageState(maybeSentinel); + } + ); + + for (let [expectedKey, expectedValue] of Object.entries(expectedState)) { + ok(actualState.hasOwnProperty(expectedKey), "key present: " + expectedKey); + is(actualState[expectedKey], expectedValue, "value correct"); + } + for (let actualKey of Object.keys(actualState)) { + if (!expectedState.hasOwnProperty(actualKey)) { + ok(false, "actual state has key it shouldn't have: " + actualKey); + } + } +} + +/** + * Retrieve and clear the storage events recorded by the tab and assert that + * they match the provided expected events. For simplicity, the expected events + * representation is the same as that used by mutateTabStorage. + * + * Note that by convention for test readability we are passed a 3rd argument of + * the sentinel value, but we don't actually care what it is. + */ +async function verifyTabStorageEvents(knownTab, expectedEvents) { + let actualEvents = await SpecialPowers.spawn( + knownTab.tab.linkedBrowser, + [], + function () { + return content.wrappedJSObject.returnAndClearStorageEvents(); + } + ); + + is(actualEvents.length, expectedEvents.length, "right number of events"); + for (let i = 0; i < actualEvents.length; i++) { + let [actualKey, actualNewValue, actualOldValue] = actualEvents[i]; + let [expectedKey, expectedNewValue, expectedOldValue] = expectedEvents[i]; + is(actualKey, expectedKey, "keys match"); + is(actualNewValue, expectedNewValue, "new values match"); + is(actualOldValue, expectedOldValue, "old values match"); + } +} diff --git a/dom/tests/browser/image.html b/dom/tests/browser/image.html new file mode 100644 index 0000000000..843ebfd1b5 --- /dev/null +++ b/dom/tests/browser/image.html @@ -0,0 +1,2 @@ + + diff --git a/dom/tests/browser/load_forever.sjs b/dom/tests/browser/load_forever.sjs new file mode 100644 index 0000000000..8f9fdd18a8 --- /dev/null +++ b/dom/tests/browser/load_forever.sjs @@ -0,0 +1,15 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let gResponses = []; + +function handleRequest(request, response) { + response.seizePower(); + response.write("HTTP/1.1 200 OK\r\n"); + response.write("Content-Type: text/plain; charset=utf-8\r\n"); + response.write("Cache-Control: no-cache, must-revalidate\r\n"); + response.write("\r\n"); + + // Keep the response alive indefinitely. + gResponses.push(response); +} diff --git a/dom/tests/browser/mimeme.sjs b/dom/tests/browser/mimeme.sjs new file mode 100644 index 0000000000..c3c7122979 --- /dev/null +++ b/dom/tests/browser/mimeme.sjs @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Small red image. +const IMG_BYTES = atob( + "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" + + "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" +); + +function handleRequest(request, response) { + let mimeType = request.queryString.match(/type=([a-z]*)/)[1]; + switch (mimeType) { + case "css": + response.setHeader("Content-Type", "text/css"); + response.write("#hi {color: red}"); + break; + case "js": + response.setHeader("Content-Type", "application/javascript"); + response.write("var foo;"); + break; + case "png": + response.setHeader("Content-Type", "image/png"); + response.write(IMG_BYTES); + break; + case "html": + response.setHeader("Content-Type", "text/html"); + response.write("I am a subframe"); + break; + } +} diff --git a/dom/tests/browser/page_bytecode_cache_asm_js.html b/dom/tests/browser/page_bytecode_cache_asm_js.html new file mode 100644 index 0000000000..018099cc6d --- /dev/null +++ b/dom/tests/browser/page_bytecode_cache_asm_js.html @@ -0,0 +1,10 @@ + + + + asm.js test + + +

asm.js test

+ ok + + diff --git a/dom/tests/browser/page_bytecode_cache_asm_js.js b/dom/tests/browser/page_bytecode_cache_asm_js.js new file mode 100644 index 0000000000..03f8a6c0b8 --- /dev/null +++ b/dom/tests/browser/page_bytecode_cache_asm_js.js @@ -0,0 +1,30 @@ +window.onerror = function (e) { + document.getElementById("result").textContent = e; +}; + +function f() { + "use asm"; + function test() { + return 10; + } + return test; +} + +var dummy = [ + "dummy text to exceed the minimal source length for bytecode cache", + "dummy text to exceed the minimal source length for bytecode cache", + "dummy text to exceed the minimal source length for bytecode cache", + "dummy text to exceed the minimal source length for bytecode cache", + "dummy text to exceed the minimal source length for bytecode cache", + "dummy text to exceed the minimal source length for bytecode cache", + "dummy text to exceed the minimal source length for bytecode cache", + "dummy text to exceed the minimal source length for bytecode cache", + "dummy text to exceed the minimal source length for bytecode cache", + "dummy text to exceed the minimal source length for bytecode cache", + "dummy text to exceed the minimal source length for bytecode cache", + "dummy text to exceed the minimal source length for bytecode cache", + "dummy text to exceed the minimal source length for bytecode cache", + "dummy text to exceed the minimal source length for bytecode cache", + "dummy text to exceed the minimal source length for bytecode cache", + "dummy text to exceed the minimal source length for bytecode cache", +]; diff --git a/dom/tests/browser/page_localStorage.js b/dom/tests/browser/page_localStorage.js new file mode 100644 index 0000000000..4fecf10bcf --- /dev/null +++ b/dom/tests/browser/page_localStorage.js @@ -0,0 +1,127 @@ +/** + * Helper page used by browser_localStorage_xxx.js. + * + * We expose methods to be invoked by SpecialPowers.spawn() calls. + * SpecialPowers.spawn() uses the message manager and is PContent-based. When + * LocalStorage was PContent-managed, ordering was inherently ensured so we + * could assume each page had already received all relevant events. Now some + * explicit type of coordination is required. + * + * This gets complicated because: + * - LocalStorage is an ugly API that gives us almost unlimited implementation + * flexibility in the face of multiple processes. It's also an API that sites + * may misuse which may encourage us to leverage that flexibility in the + * future to improve performance at the expense of propagation latency, and + * possibly involving content-observable coalescing of events. + * - The Quantum DOM effort and its event labeling and separate task queues and + * green threading and current LocalStorage implementation mean that using + * other PBackground-based APIs such as BroadcastChannel may not provide + * reliable ordering guarantees. Specifically, it's hard to guarantee that + * a BroadcastChannel postMessage() issued after a series of LocalStorage + * writes won't be received by the target window before the writes are + * perceived. At least not without constraining the implementations of both + * APIs. + * - Some of our tests explicitly want to verify LocalStorage behavior without + * having a "storage" listener, so we can't add a storage listener if the test + * didn't already want one. + * + * We use 2 approaches for coordination: + * 1. If we're already listening for events, we listen for the sentinel value to + * be written. This is efficient and appropriate in this case. + * 2. If we're not listening for events, we use setTimeout(0) to poll the + * localStorage key and value until it changes to our expected value. + * setTimeout(0) eventually clamps to setTimeout(4), so in the event we are + * experiencing delays, we have reasonable, non-CPU-consuming back-off in + * place that leaves the CPU free to time out and fail our test if something + * broke. This is ugly but makes us less brittle. + * + * Both of these involve mutateStorage writing the sentinel value at the end of + * the batch. All of our result-returning methods accordingly filter out the + * sentinel key/value pair. + **/ + +var pageName = document.location.search.substring(1); +window.addEventListener("load", () => { + document.getElementById("pageNameH").textContent = pageName; +}); + +// Key that conveys the end of a write batch. Filtered out from state and +// events. +const SENTINEL_KEY = "WRITE_BATCH_SENTINEL"; + +var storageEventsPromise = null; +function listenForStorageEvents(sentinelValue) { + const recordedEvents = []; + storageEventsPromise = new Promise(function (resolve, reject) { + window.addEventListener("storage", function thisHandler(event) { + if (event.key === SENTINEL_KEY) { + // There should be no way for this to have the wrong value, but reject + // if it is wrong. + if (event.newValue === sentinelValue) { + window.removeEventListener("storage", thisHandler); + resolve(recordedEvents); + } else { + reject(event.newValue); + } + } else { + recordedEvents.push([event.key, event.newValue, event.oldValue]); + } + }); + }); +} + +function mutateStorage({ mutations, sentinelValue }) { + mutations.forEach(function ([key, value]) { + if (key !== null) { + if (value === null) { + localStorage.removeItem(key); + } else { + localStorage.setItem(key, value); + } + } else { + localStorage.clear(); + } + }); + localStorage.setItem(SENTINEL_KEY, sentinelValue); +} + +// Returns a promise that is resolve when the sentinel key has taken on the +// sentinel value. Oddly structured to make sure promises don't let us +// accidentally side-step the timeout clamping logic. +function waitForSentinelValue(sentinelValue) { + return new Promise(function (resolve) { + function checkFunc() { + if (localStorage.getItem(SENTINEL_KEY) === sentinelValue) { + resolve(); + } else { + // I believe linters will only yell at us if we use a non-zero constant. + // Other forms of back-off were considered, including attempting to + // issue a round-trip through PBackground, but that still potentially + // runs afoul of labeling while also making us dependent on unrelated + // APIs. + setTimeout(checkFunc, 0); + } + } + checkFunc(); + }); +} + +async function getStorageState(maybeSentinel) { + if (maybeSentinel) { + await waitForSentinelValue(maybeSentinel); + } + + let numKeys = localStorage.length; + let state = {}; + for (var iKey = 0; iKey < numKeys; iKey++) { + let key = localStorage.key(iKey); + if (key !== SENTINEL_KEY) { + state[key] = localStorage.getItem(key); + } + } + return state; +} + +function returnAndClearStorageEvents() { + return storageEventsPromise; +} diff --git a/dom/tests/browser/page_localstorage.html b/dom/tests/browser/page_localstorage.html new file mode 100644 index 0000000000..aacb0ef6ac --- /dev/null +++ b/dom/tests/browser/page_localstorage.html @@ -0,0 +1,8 @@ + + + + + + +

+ diff --git a/dom/tests/browser/page_localstorage_coop+coep.html b/dom/tests/browser/page_localstorage_coop+coep.html new file mode 100644 index 0000000000..aacb0ef6ac --- /dev/null +++ b/dom/tests/browser/page_localstorage_coop+coep.html @@ -0,0 +1,8 @@ + + + + + + +

+ diff --git a/dom/tests/browser/page_localstorage_coop+coep.html^headers^ b/dom/tests/browser/page_localstorage_coop+coep.html^headers^ new file mode 100644 index 0000000000..63b60e490f --- /dev/null +++ b/dom/tests/browser/page_localstorage_coop+coep.html^headers^ @@ -0,0 +1,2 @@ +Cross-Origin-Opener-Policy: same-origin +Cross-Origin-Embedder-Policy: require-corp diff --git a/dom/tests/browser/page_localstorage_snapshotting.html b/dom/tests/browser/page_localstorage_snapshotting.html new file mode 100644 index 0000000000..b64f4b8cab --- /dev/null +++ b/dom/tests/browser/page_localstorage_snapshotting.html @@ -0,0 +1,68 @@ + + + + + + +

+ diff --git a/dom/tests/browser/page_privatestorageevent.html b/dom/tests/browser/page_privatestorageevent.html new file mode 100644 index 0000000000..90c2184aee --- /dev/null +++ b/dom/tests/browser/page_privatestorageevent.html @@ -0,0 +1,5 @@ + + +la la la la + + diff --git a/dom/tests/browser/perfmetrics/browser.ini b/dom/tests/browser/perfmetrics/browser.ini new file mode 100644 index 0000000000..b4b1d4b63a --- /dev/null +++ b/dom/tests/browser/perfmetrics/browser.ini @@ -0,0 +1,21 @@ +[DEFAULT] +prefs = + dom.performance.children_results_ipc_timeout=2000 + +support-files = + dummy.html + ping_worker.html + ping_worker2.html + ping_worker.js + setinterval.html + settimeout.html + shared_worker.js + unresponsive.html + hello.ogg + sound.html + +[browser_test_performance_metrics.js] +skip-if = verify + +[browser_test_unresponsive.js] +skip-if = true # Bug 1498426 diff --git a/dom/tests/browser/perfmetrics/browser_test_performance_metrics.js b/dom/tests/browser/perfmetrics/browser_test_performance_metrics.js new file mode 100644 index 0000000000..65a7aaaee2 --- /dev/null +++ b/dom/tests/browser/perfmetrics/browser_test_performance_metrics.js @@ -0,0 +1,201 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const ROOT_URL = "http://example.com/browser/dom/tests/browser/perfmetrics"; +const DUMMY_URL = ROOT_URL + "/dummy.html"; +const WORKER_URL = ROOT_URL + "/ping_worker.html"; +const WORKER_URL2 = ROOT_URL + "/ping_worker2.html"; +const INTERVAL_URL = ROOT_URL + "/setinterval.html"; +const TIMEOUT_URL = ROOT_URL + "/settimeout.html"; +const SOUND_URL = ROOT_URL + "/sound.html"; +const CATEGORY_TIMER = 2; + +add_task(async function test() { + waitForExplicitFinish(); + + // Load 3 pages and wait. The 3rd one has a worker + let page1 = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:about", + forceNewProcess: false, + }); + + let page2 = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:memory", + forceNewProcess: false, + }); + + let page3 = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: WORKER_URL, + }); + // load a 4th tab with a worker + await BrowserTestUtils.withNewTab( + { gBrowser, url: WORKER_URL2 }, + async function (browser) { + // grab events.. + let workerDuration = 0; + let workerTotal = 0; + let duration = 0; + let total = 0; + let isTopLevel = false; + let aboutMemoryFound = false; + let parentProcessEvent = false; + let subFrameIds = []; + let topLevelIds = []; + let sharedWorker = false; + let counterIds = []; + let timerCalls = 0; + let heapUsage = 0; + let mediaMemory = 0; + + function exploreResults(data, filterByWindowId) { + for (let entry of data) { + if (filterByWindowId && entry.windowId != filterByWindowId) { + continue; + } + if (!counterIds.includes(entry.pid + ":" + entry.counterId)) { + counterIds.push(entry.pid + ":" + entry.counterId); + } + sharedWorker = + entry.host.endsWith("shared_worker.js") || sharedWorker; + heapUsage += entry.memoryInfo.jsMemUsage; + mediaMemory += + entry.memoryInfo.media.audioSize + + entry.memoryInfo.media.resourcesSize; + Assert.ok( + entry.host != "" || entry.windowId != 0, + "An entry should have a host or a windowId" + ); + if ( + entry.windowId != 0 && + !entry.isToplevel && + !entry.isWorker && + !subFrameIds.includes(entry.windowId) + ) { + subFrameIds.push(entry.windowId); + } + if (entry.isTopLevel && !topLevelIds.includes(entry.windowId)) { + topLevelIds.push(entry.windowId); + } + if (entry.host == "example.com" && entry.isTopLevel) { + isTopLevel = true; + } + if (entry.host == "about:memory") { + aboutMemoryFound = true; + } + if (entry.pid == Services.appinfo.processID) { + parentProcessEvent = true; + } + if (entry.isWorker) { + workerDuration += entry.duration; + } else { + duration += entry.duration; + } + // let's look at the data we got back + for (let item of entry.items) { + Assert.ok( + item.count > 0, + "Categories with an empty count are dropped" + ); + if (entry.isWorker) { + workerTotal += item.count; + } else { + total += item.count; + } + if (item.category == CATEGORY_TIMER) { + timerCalls += item.count; + } + } + } + } + + // get all metrics via the promise + let results = await ChromeUtils.requestPerformanceMetrics(); + exploreResults(results); + + Assert.greater(workerDuration, 0, "Worker duration should be positive"); + Assert.greater(workerTotal, 0, "Worker count should be positive"); + Assert.greater(duration, 0, "Duration should be positive"); + Assert.greater(total, 0, "Should get a positive count"); + Assert.ok(parentProcessEvent, "parent process sent back some events"); + Assert.ok(isTopLevel, "example.com as a top level window"); + Assert.ok(aboutMemoryFound, "about:memory"); + Assert.greater(heapUsage, 0, "got some memory value reported"); + Assert.ok(sharedWorker, "We got some info from a shared worker"); + let numCounters = counterIds.length; + Assert.ok( + numCounters > 5, + "This test generated at least " + numCounters + " unique counters" + ); + + // checking that subframes are not orphans + for (let frameId of subFrameIds) { + Assert.ok(topLevelIds.includes(frameId), "subframe is not orphan "); + } + + // Doing a second call, we shoud get bigger values + let previousWorkerDuration = workerDuration; + let previousWorkerTotal = workerTotal; + let previousDuration = duration; + let previousTotal = total; + + results = await ChromeUtils.requestPerformanceMetrics(); + exploreResults(results); + + Assert.ok( + workerDuration > previousWorkerDuration, + "Worker duration should be positive" + ); + Assert.ok( + workerTotal > previousWorkerTotal, + "Worker count should be positive" + ); + Assert.greater(duration, previousDuration, "Duration should be positive"); + Assert.greater(total, previousTotal, "Should get a positive count"); + + // load a tab with a setInterval, we should get counters on TaskCategory::Timer + await BrowserTestUtils.withNewTab( + { gBrowser, url: INTERVAL_URL }, + async function (browser) { + let tabId = gBrowser.selectedBrowser.outerWindowID; + let previousTimerCalls = timerCalls; + results = await ChromeUtils.requestPerformanceMetrics(); + exploreResults(results, tabId); + Assert.greater(timerCalls, previousTimerCalls, "Got timer calls"); + } + ); + + // load a tab with a setTimeout, we should get counters on TaskCategory::Timer + await BrowserTestUtils.withNewTab( + { gBrowser, url: TIMEOUT_URL }, + async function (browser) { + let tabId = gBrowser.selectedBrowser.outerWindowID; + let previousTimerCalls = timerCalls; + results = await ChromeUtils.requestPerformanceMetrics(); + exploreResults(results, tabId); + Assert.greater(timerCalls, previousTimerCalls, "Got timer calls"); + } + ); + + // load a tab with a sound + await BrowserTestUtils.withNewTab( + { gBrowser, url: SOUND_URL }, + async function (browser) { + let tabId = gBrowser.selectedBrowser.outerWindowID; + results = await ChromeUtils.requestPerformanceMetrics(); + exploreResults(results, tabId); + Assert.greater(mediaMemory, 0, "Got some memory used for media"); + } + ); + } + ); + + BrowserTestUtils.removeTab(page1); + BrowserTestUtils.removeTab(page2); + BrowserTestUtils.removeTab(page3); +}); diff --git a/dom/tests/browser/perfmetrics/browser_test_unresponsive.js b/dom/tests/browser/perfmetrics/browser_test_unresponsive.js new file mode 100644 index 0000000000..770c387e7a --- /dev/null +++ b/dom/tests/browser/perfmetrics/browser_test_unresponsive.js @@ -0,0 +1,31 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const ROOT_URL = "http://example.com/browser/dom/tests/browser/perfmetrics"; +const PAGE_URL = ROOT_URL + "/unresponsive.html"; + +add_task(async function test() { + waitForExplicitFinish(); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: PAGE_URL }, + async function (browser) { + let dataBack = 0; + let tabId = gBrowser.selectedBrowser.outerWindowID; + + function exploreResults(data, filterByWindowId) { + for (let entry of data) { + if (entry.windowId == tabId && entry.host != "about:blank") { + dataBack += 1; + } + } + } + let results = await ChromeUtils.requestPerformanceMetrics(); + exploreResults(results); + Assert.ok(dataBack == 0); + } + ); +}); diff --git a/dom/tests/browser/perfmetrics/dummy.html b/dom/tests/browser/perfmetrics/dummy.html new file mode 100644 index 0000000000..6ec72c2160 --- /dev/null +++ b/dom/tests/browser/perfmetrics/dummy.html @@ -0,0 +1,13 @@ + + + +Dummy test page + + + +

Dummy test page

+ + + diff --git a/dom/tests/browser/perfmetrics/hello.ogg b/dom/tests/browser/perfmetrics/hello.ogg new file mode 100644 index 0000000000..7a80926065 Binary files /dev/null and b/dom/tests/browser/perfmetrics/hello.ogg differ diff --git a/dom/tests/browser/perfmetrics/ping_worker.html b/dom/tests/browser/perfmetrics/ping_worker.html new file mode 100644 index 0000000000..c576dbcb22 --- /dev/null +++ b/dom/tests/browser/perfmetrics/ping_worker.html @@ -0,0 +1,26 @@ + + + + + + + +

A page with a worker and a shared worker

+ + diff --git a/dom/tests/browser/perfmetrics/ping_worker.js b/dom/tests/browser/perfmetrics/ping_worker.js new file mode 100644 index 0000000000..0ed6bb8ba4 --- /dev/null +++ b/dom/tests/browser/perfmetrics/ping_worker.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +function messageListener(event) { + postMessage("pong"); +} + +addEventListener("message", { handleEvent: messageListener }); diff --git a/dom/tests/browser/perfmetrics/ping_worker2.html b/dom/tests/browser/perfmetrics/ping_worker2.html new file mode 100644 index 0000000000..48f6658218 --- /dev/null +++ b/dom/tests/browser/perfmetrics/ping_worker2.html @@ -0,0 +1,20 @@ + + + + + + + +

A page with a shared worker

+ + diff --git a/dom/tests/browser/perfmetrics/setinterval.html b/dom/tests/browser/perfmetrics/setinterval.html new file mode 100644 index 0000000000..4c3e7264ca --- /dev/null +++ b/dom/tests/browser/perfmetrics/setinterval.html @@ -0,0 +1,19 @@ + + + + + + + +

A page with a setInterval() call

+ + diff --git a/dom/tests/browser/perfmetrics/settimeout.html b/dom/tests/browser/perfmetrics/settimeout.html new file mode 100644 index 0000000000..01f632caf5 --- /dev/null +++ b/dom/tests/browser/perfmetrics/settimeout.html @@ -0,0 +1,17 @@ + + + + + + + +

A page with a setTimeout() call

+ + diff --git a/dom/tests/browser/perfmetrics/shared_worker.js b/dom/tests/browser/perfmetrics/shared_worker.js new file mode 100644 index 0000000000..cb00bfb3eb --- /dev/null +++ b/dom/tests/browser/perfmetrics/shared_worker.js @@ -0,0 +1,7 @@ +let onconnect = function (e) { + var port = e.ports[0]; + + port.onmessage = function (e) { + port.postMessage(e.data[0]); + }; +}; diff --git a/dom/tests/browser/perfmetrics/sound.html b/dom/tests/browser/perfmetrics/sound.html new file mode 100644 index 0000000000..e365396f31 --- /dev/null +++ b/dom/tests/browser/perfmetrics/sound.html @@ -0,0 +1,14 @@ + + + +Dummy test page + + + +

Page with a sound

+ + + + diff --git a/dom/tests/browser/perfmetrics/unresponsive.html b/dom/tests/browser/perfmetrics/unresponsive.html new file mode 100644 index 0000000000..e139eb7f9d --- /dev/null +++ b/dom/tests/browser/perfmetrics/unresponsive.html @@ -0,0 +1,21 @@ + + + + + + + +

An unresponsive page

+ + diff --git a/dom/tests/browser/position.html b/dom/tests/browser/position.html new file mode 100644 index 0000000000..8fca4d6af9 --- /dev/null +++ b/dom/tests/browser/position.html @@ -0,0 +1,31 @@ + + + + + + +

location...

+ + diff --git a/dom/tests/browser/prevent_return_key.html b/dom/tests/browser/prevent_return_key.html new file mode 100644 index 0000000000..4ec846f2e0 --- /dev/null +++ b/dom/tests/browser/prevent_return_key.html @@ -0,0 +1,34 @@ + + + + + Prevent return key should not submit form + + + + +
+ + +
+

not submitted

+ + diff --git a/dom/tests/browser/set-samesite-cookies-and-redirect.sjs b/dom/tests/browser/set-samesite-cookies-and-redirect.sjs new file mode 100644 index 0000000000..940b1ee845 --- /dev/null +++ b/dom/tests/browser/set-samesite-cookies-and-redirect.sjs @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +function handleRequest(request, response) { + // Set cookies and redirect for .org: + if (request.host.endsWith(".org")) { + response.setHeader("Set-Cookie", "normalCookie=true; path=/;", true); + response.setHeader( + "Set-Cookie", + "laxHeader=true; path=/; SameSite=Lax", + true + ); + response.setHeader( + "Set-Cookie", + "strictHeader=true; path=/; SameSite=Strict", + true + ); + response.write(` + + + + + + + `); + } else { + let baseURI = + "https://example.org/" + + request.path.replace(/[a-z-]*\.sjs/, "mimeme.sjs?type="); + response.write(` + + + + + `); + } +} diff --git a/dom/tests/browser/test-console-api.html b/dom/tests/browser/test-console-api.html new file mode 100644 index 0000000000..e8da7c311e --- /dev/null +++ b/dom/tests/browser/test-console-api.html @@ -0,0 +1,78 @@ + + + + + Console API test page + + + +

Console API Test Page

+ + + + + + + + + + + + diff --git a/dom/tests/browser/test_bug1004814.html b/dom/tests/browser/test_bug1004814.html new file mode 100644 index 0000000000..9f97e0487a --- /dev/null +++ b/dom/tests/browser/test_bug1004814.html @@ -0,0 +1,8 @@ + + + + Console API test bug 1004814 + + + + diff --git a/dom/tests/browser/test_mixed_content_image.html b/dom/tests/browser/test_mixed_content_image.html new file mode 100644 index 0000000000..c8b7661f42 --- /dev/null +++ b/dom/tests/browser/test_mixed_content_image.html @@ -0,0 +1 @@ + diff --git a/dom/tests/browser/test_new_window_from_content_child.html b/dom/tests/browser/test_new_window_from_content_child.html new file mode 100644 index 0000000000..9e3f7016d9 --- /dev/null +++ b/dom/tests/browser/test_new_window_from_content_child.html @@ -0,0 +1,25 @@ + + + + Test popup window opening behaviour + + +

Open a new window via window.open with default features.

+

Open a new window via window.open with non-default features.

+

Open a new window via window.open with dialog=1.

+

Open a blank new window via window.open with non-default features.

+

Open a new window via target="_blank".

+ + + + diff --git a/dom/tests/browser/test_noopener_source.html b/dom/tests/browser/test_noopener_source.html new file mode 100644 index 0000000000..fbf28be260 --- /dev/null +++ b/dom/tests/browser/test_noopener_source.html @@ -0,0 +1,15 @@ +1 +2 +3 + +4 +5 +6 + +7 +8 +9 + +10 +11 +12 diff --git a/dom/tests/browser/test_noopener_target.html b/dom/tests/browser/test_noopener_target.html new file mode 100644 index 0000000000..f437fc7ba5 --- /dev/null +++ b/dom/tests/browser/test_noopener_target.html @@ -0,0 +1,9 @@ + +

name

+
+ + diff --git a/dom/tests/browser/worker_bug1004814.js b/dom/tests/browser/worker_bug1004814.js new file mode 100644 index 0000000000..4fb54da692 --- /dev/null +++ b/dom/tests/browser/worker_bug1004814.js @@ -0,0 +1,6 @@ +onmessage = function (evt) { + console.time("bug1004814"); + setTimeout(function () { + console.timeEnd("bug1004814"); + }, 200); +}; -- cgit v1.2.3