diff options
Diffstat (limited to 'toolkit/content/tests/browser')
72 files changed, 5774 insertions, 0 deletions
diff --git a/toolkit/content/tests/browser/audio.ogg b/toolkit/content/tests/browser/audio.ogg Binary files differnew file mode 100644 index 0000000000..7f1833508a --- /dev/null +++ b/toolkit/content/tests/browser/audio.ogg diff --git a/toolkit/content/tests/browser/browser.ini b/toolkit/content/tests/browser/browser.ini new file mode 100644 index 0000000000..927178c3a9 --- /dev/null +++ b/toolkit/content/tests/browser/browser.ini @@ -0,0 +1,118 @@ +[DEFAULT] +prefs = + plugin.load_flash_only=false + media.cubeb.sandbox=false # BMO 1610640 +support-files = + audio.ogg + empty.png + file_contentTitle.html + file_empty.html + file_findinframe.html + file_mediaPlayback2.html + file_multipleAudio.html + file_multiplePlayingAudio.html + file_nonAutoplayAudio.html + file_redirect.html + file_redirect_to.html + file_silentAudioTrack.html + file_webAudio.html + head.js + image.jpg + image_page.html + silentAudioTrack.webm + +[browser_autoscroll_disabled.js] +skip-if = true # Bug 1312652 +[browser_delay_autoplay_media.js] +tags = audiochannel +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1536573 +[browser_delay_autoplay_media_pausedAfterPlay.js] +tags = audiochannel +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1536573 +[browser_delay_autoplay_playAfterTabVisible.js] +tags = audiochannel +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1536573 +[browser_delay_autoplay_multipleMedia.js] +tags = audiochannel +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1536573 +[browser_delay_autoplay_notInTreeAudio.js] +tags = audiochannel +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1536573 +[browser_delay_autoplay_playMediaInMuteTab.js] +tags = audiochannel +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1536573 +[browser_delay_autoplay_silentAudioTrack_media.js] +tags = audiochannel +skip-if = (os == "win" && processor == "aarch64") || (os == "mac") || (os == "linux" && !debug) # aarch64 due to 1536573 #Bug 1524746 +[browser_delay_autoplay_webAudio.js] +tags = audiochannel +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1536573 +[browser_bug1572798.js] +tags = audiochannel +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1536573 +support-files = file_document_open_audio.html +[browser_bug1170531.js] +skip-if = + os == "linux" && !debug && !ccov # Bug 1647973 +[browser_bug1198465.js] +[browser_bug295977_autoscroll_overflow.js] +skip-if = ((debug || asan) && os == "win" && bits == 64) +[browser_bug451286.js] +skip-if = true # bug 1399845 tracks re-enabling this test. +[browser_bug594509.js] +[browser_bug982298.js] +[browser_charsetMenu_swapBrowsers.js] +[browser_content_url_annotation.js] +skip-if = !e10s || !crashreporter +[browser_contentTitle.js] +[browser_crash_previous_frameloader.js] +run-if = e10s && crashreporter +[browser_datetime_datepicker.js] +skip-if = tsan # Frequently times out on TSan +[browser_default_image_filename.js] +[browser_default_image_filename_redirect.js] +support-files = + doggy.png + firebird.png + firebird.png^headers^ +[browser_f7_caret_browsing.js] +[browser_findbar.js] +skip-if = os == "linux" && bits == 64 && os_version = "18.04" # Bug 1614739 +[browser_findbar_disabled_manual.js] +[browser_isSynthetic.js] +[browser_keyevents_during_autoscrolling.js] +[browser_label_textlink.js] +[browser_remoteness_change_listeners.js] +[browser_suspend_videos_outside_viewport.js] +support-files = + file_outside_viewport_videos.html + gizmo.mp4 +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1536573 +[browser_license_links.js] +[browser_media_wakelock.js] +support-files = + browser_mediaStreamPlayback.html + browser_mediaStreamPlaybackWithoutAudio.html + file_video.html + file_videoWithAudioOnly.html + file_videoWithoutAudioTrack.html + gizmo.mp4 + gizmo-noaudio.webm +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1536573 +[browser_media_wakelock_PIP.js] +support-files = + file_video.html + gizmo.mp4 +[browser_media_wakelock_webaudio.js] +[browser_quickfind_editable.js] +skip-if = (verify && debug && (os == 'linux')) +[browser_save_resend_postdata.js] +support-files = + common/mockTransfer.js + data/post_form_inner.sjs + data/post_form_outer.sjs +skip-if = e10s # Bug ?????? - test directly manipulates content (gBrowser.contentDocument.getElementById("postForm").submit();) +[browser_saveImageURL.js] +[browser_resume_bkg_video_on_tab_hover.js] +skip-if = (os == "win" && processor == "aarch64") || debug # aarch64 due to 1536573, Bug 1388959 + diff --git a/toolkit/content/tests/browser/browser_autoscroll_disabled.js b/toolkit/content/tests/browser/browser_autoscroll_disabled.js new file mode 100644 index 0000000000..d2f6efbd13 --- /dev/null +++ b/toolkit/content/tests/browser/browser_autoscroll_disabled.js @@ -0,0 +1,82 @@ +add_task(async function() { + const kPrefName_AutoScroll = "general.autoScroll"; + Services.prefs.setBoolPref(kPrefName_AutoScroll, false); + + let dataUri = + 'data:text/html,<html><body id="i" style="overflow-y: scroll"><div style="height: 2000px"></div>\ + <iframe id="iframe" style="display: none;"></iframe>\ +</body></html>'; + + let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + BrowserTestUtils.loadURI(gBrowser, dataUri); + await loadedPromise; + + await BrowserTestUtils.synthesizeMouse( + "#i", + 50, + 50, + { button: 1 }, + gBrowser.selectedBrowser + ); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function() { + var iframe = content.document.getElementById("iframe"); + + if (iframe) { + var e = new iframe.contentWindow.PageTransitionEvent("pagehide", { + bubbles: true, + cancelable: true, + persisted: false, + }); + iframe.contentDocument.dispatchEvent(e); + iframe.contentDocument.documentElement.dispatchEvent(e); + } + }); + + await BrowserTestUtils.synthesizeMouse( + "#i", + 100, + 100, + { type: "mousemove", clickCount: "0" }, + gBrowser.selectedBrowser + ); + + // If scrolling didn't work, we wouldn't do any redraws and thus time out, so + // request and force redraws to get the chance to check for scrolling at all. + await new Promise(resolve => window.requestAnimationFrame(resolve)); + + let msg = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + async function() { + // Skip the first animation frame callback as it's the same callback that + // the browser uses to kick off the scrolling. + return new Promise(resolve => { + function checkScroll() { + let msg = ""; + let elem = content.document.getElementById("i"); + if (elem.scrollTop != 0) { + msg += "element should not have scrolled vertically"; + } + if (elem.scrollLeft != 0) { + msg += "element should not have scrolled horizontally"; + } + + resolve(msg); + } + + content.requestAnimationFrame(checkScroll); + }); + } + ); + + ok(!msg, "element scroll " + msg); + + // restore the changed prefs + if (Services.prefs.prefHasUserValue(kPrefName_AutoScroll)) { + Services.prefs.clearUserPref(kPrefName_AutoScroll); + } + + // wait for focus to fix a failure in the next test if the latter runs too soon. + await SimpleTest.promiseFocus(); +}); diff --git a/toolkit/content/tests/browser/browser_bug1170531.js b/toolkit/content/tests/browser/browser_bug1170531.js new file mode 100644 index 0000000000..d5a6501dcd --- /dev/null +++ b/toolkit/content/tests/browser/browser_bug1170531.js @@ -0,0 +1,112 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +// Test for bug 1170531 +// https://bugzilla.mozilla.org/show_bug.cgi?id=1170531 + +add_task(async function() { + // Get a bunch of DOM nodes + let editMenu = document.getElementById("edit-menu"); + let menuPopup = editMenu.menupopup; + + let closeMenu = function(aCallback) { + if (OS.Constants.Sys.Name == "Darwin") { + executeSoon(aCallback); + return; + } + + menuPopup.addEventListener( + "popuphidden", + function() { + executeSoon(aCallback); + }, + { once: true } + ); + + executeSoon(function() { + editMenu.open = false; + }); + }; + + let openMenu = function(aCallback) { + if (OS.Constants.Sys.Name == "Darwin") { + goUpdateGlobalEditMenuItems(); + // On OSX, we have a native menu, so it has to be updated. In single process browsers, + // this happens synchronously, but in e10s, we have to wait for the main thread + // to deal with it for us. 1 second should be plenty of time. + setTimeout(aCallback, 1000); + return; + } + + menuPopup.addEventListener( + "popupshown", + function() { + executeSoon(aCallback); + }, + { once: true } + ); + + executeSoon(function() { + editMenu.open = true; + }); + }; + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function(browser) { + let menu_cut_disabled, menu_copy_disabled; + + BrowserTestUtils.loadURI(browser, "data:text/html,<div>hello!</div>"); + await BrowserTestUtils.browserLoaded(browser); + browser.focus(); + await new Promise(resolve => waitForFocus(resolve, window)); + await new Promise(resolve => + window.requestAnimationFrame(() => executeSoon(resolve)) + ); + await new Promise(openMenu); + menu_cut_disabled = + menuPopup.querySelector("#menu_cut").getAttribute("disabled") == "true"; + is(menu_cut_disabled, false, "menu_cut should be enabled"); + menu_copy_disabled = + menuPopup.querySelector("#menu_copy").getAttribute("disabled") == + "true"; + is(menu_copy_disabled, false, "menu_copy should be enabled"); + await new Promise(closeMenu); + + BrowserTestUtils.loadURI( + browser, + "data:text/html,<div contentEditable='true'>hello!</div>" + ); + await BrowserTestUtils.browserLoaded(browser); + browser.focus(); + await new Promise(resolve => waitForFocus(resolve, window)); + await new Promise(resolve => + window.requestAnimationFrame(() => executeSoon(resolve)) + ); + await new Promise(openMenu); + menu_cut_disabled = + menuPopup.querySelector("#menu_cut").getAttribute("disabled") == "true"; + is(menu_cut_disabled, false, "menu_cut should be enabled"); + menu_copy_disabled = + menuPopup.querySelector("#menu_copy").getAttribute("disabled") == + "true"; + is(menu_copy_disabled, false, "menu_copy should be enabled"); + await new Promise(closeMenu); + + BrowserTestUtils.loadURI(browser, "about:preferences"); + await BrowserTestUtils.browserLoaded(browser); + browser.focus(); + await new Promise(resolve => waitForFocus(resolve, window)); + await new Promise(resolve => + window.requestAnimationFrame(() => executeSoon(resolve)) + ); + await new Promise(openMenu); + menu_cut_disabled = + menuPopup.querySelector("#menu_cut").getAttribute("disabled") == "true"; + is(menu_cut_disabled, true, "menu_cut should be disabled"); + menu_copy_disabled = + menuPopup.querySelector("#menu_copy").getAttribute("disabled") == + "true"; + is(menu_copy_disabled, true, "menu_copy should be disabled"); + await new Promise(closeMenu); + } + ); +}); diff --git a/toolkit/content/tests/browser/browser_bug1198465.js b/toolkit/content/tests/browser/browser_bug1198465.js new file mode 100644 index 0000000000..7f578b3197 --- /dev/null +++ b/toolkit/content/tests/browser/browser_bug1198465.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +var kPrefName = "accessibility.typeaheadfind.prefillwithselection"; +var kEmptyURI = "data:text/html,"; + +// This pref is false by default in OSX; ensure the test still works there. +Services.prefs.setBoolPref(kPrefName, true); + +registerCleanupFunction(function() { + Services.prefs.clearUserPref(kPrefName); +}); + +add_task(async function() { + let aTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, kEmptyURI); + ok(!gFindBarInitialized, "findbar isn't initialized yet"); + + // Note: the use case here is when the user types directly in the findbar + // _before_ it's prefilled with a text selection in the page. + + // So `yield BrowserTestUtils.sendChar()` can't be used here: + // - synthesizing a key in the browser won't actually send it to the + // findbar; the findbar isn't part of the browser content. + // - we need to _not_ wait for _startFindDeferred to be resolved; yielding + // a synthesized keypress on the browser implicitely happens after the + // browser has dispatched its return message with the prefill value for + // the findbar, which essentially nulls these tests. + + // The parent-side of the sidebar initialization is also async, so we do + // need to wait for that. We verify a bit further down that _startFindDeferred + // hasn't been resolved yet. + await gFindBarPromise; + + let findBar = gFindBar; + is(findBar._findField.value, "", "findbar is empty"); + + // Test 1 + // Any input in the findbar should erase a previous search. + + findBar._findField.value = "xy"; + findBar.startFind(); + is(findBar._findField.value, "xy", "findbar should have xy initial query"); + is(findBar._findField, document.activeElement, "findbar is now focused"); + + EventUtils.sendChar("z", window); + is(findBar._findField.value, "z", "z erases xy"); + + findBar._findField.value = ""; + ok(!findBar._findField.value, "erase findbar after first test"); + + // Test 2 + // Prefilling the findbar should be ignored if a search has been run. + + findBar.startFind(); + ok(findBar._startFindDeferred, "prefilled value hasn't been fetched yet"); + is(findBar._findField, document.activeElement, "findbar is still focused"); + + EventUtils.sendChar("a", window); + EventUtils.sendChar("b", window); + is(findBar._findField.value, "ab", "initial ab typed in the findbar"); + + // This resolves _startFindDeferred if it's still pending; let's just skip + // over waiting for the browser's return message that should do this as it + // doesn't really matter. + findBar.onCurrentSelection("foo", true); + ok(!findBar._startFindDeferred, "prefilled value fetched"); + is(findBar._findField.value, "ab", "ab kept instead of prefill value"); + + EventUtils.sendChar("c", window); + is(findBar._findField.value, "abc", "c is appended after ab"); + + // Clear the findField value to make the test run successfully + // for multiple runs in the same browser session. + findBar._findField.value = ""; + BrowserTestUtils.removeTab(aTab); +}); diff --git a/toolkit/content/tests/browser/browser_bug1572798.js b/toolkit/content/tests/browser/browser_bug1572798.js new file mode 100644 index 0000000000..8b5dffd432 --- /dev/null +++ b/toolkit/content/tests/browser/browser_bug1572798.js @@ -0,0 +1,29 @@ +add_task(async function test_bug_1572798() { + let tab = BrowserTestUtils.addTab(window.gBrowser, "about:blank"); + BrowserTestUtils.loadURI( + tab.linkedBrowser, + "https://example.com/browser/toolkit/content/tests/browser/file_document_open_audio.html" + ); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + let windowLoaded = BrowserTestUtils.waitForNewWindow(); + info("- clicking button to spawn a new window -"); + await ContentTask.spawn(tab.linkedBrowser, null, function() { + content.document.querySelector("button").click(); + }); + info("- waiting for the new window -"); + let newWin = await windowLoaded; + info("- checking that the new window plays the audio -"); + let documentOpenedBrowser = newWin.gBrowser.selectedBrowser; + await ContentTask.spawn(documentOpenedBrowser, null, async function() { + try { + await content.document.querySelector("audio").play(); + ok(true, "Could play the audio"); + } catch (e) { + ok(false, "Rejected audio promise" + e); + } + }); + + info("- Cleaning up -"); + await BrowserTestUtils.closeWindow(newWin); + await BrowserTestUtils.removeTab(tab); +}); diff --git a/toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js b/toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js new file mode 100644 index 0000000000..5cc6de19da --- /dev/null +++ b/toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js @@ -0,0 +1,381 @@ +requestLongerTimeout(2); +add_task(async function() { + function pushPrefs(prefs) { + return SpecialPowers.pushPrefEnv({ set: prefs }); + } + + await pushPrefs([ + ["general.autoScroll", true], + ["test.events.async.enabled", true], + ]); + + const expectScrollNone = 0; + const expectScrollVert = 1; + const expectScrollHori = 2; + const expectScrollBoth = 3; + + var allTests = [ + { + dataUri: + 'data:text/html,<html><head><meta charset="utf-8"></head><body><style type="text/css">div { display: inline-block; }</style>\ + <div id="a" style="width: 100px; height: 100px; overflow: hidden;"><div style="width: 200px; height: 200px;"></div></div>\ + <div id="b" style="width: 100px; height: 100px; overflow: auto;"><div style="width: 200px; height: 200px;"></div></div>\ + <div id="c" style="width: 100px; height: 100px; overflow-x: auto; overflow-y: hidden;"><div style="width: 200px; height: 200px;"></div></div>\ + <div id="d" style="width: 100px; height: 100px; overflow-y: auto; overflow-x: hidden;"><div style="width: 200px; height: 200px;"></div></div>\ + <select id="e" style="width: 100px; height: 100px;" multiple="multiple"><option>aaaaaaaaaaaaaaaaaaaaaaaa</option><option>a</option><option>a</option>\ + <option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option>\ + <option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option></select>\ + <select id="f" style="width: 100px; height: 100px;"><option>a</option><option>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</option><option>a</option>\ + <option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option>\ + <option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option></select>\ + <div id="g" style="width: 99px; height: 99px; border: 10px solid black; margin: 10px; overflow: auto;"><div style="width: 100px; height: 100px;"></div></div>\ + <div id="h" style="width: 100px; height: 100px; overflow: clip;"><div style="width: 200px; height: 200px;"></div></div>\ + <iframe id="iframe" style="display: none;"></iframe>\ + </body></html>', + }, + { elem: "a", expected: expectScrollNone }, + { elem: "b", expected: expectScrollBoth }, + { elem: "c", expected: expectScrollHori }, + { elem: "d", expected: expectScrollVert }, + { elem: "e", expected: expectScrollVert }, + { elem: "f", expected: expectScrollNone }, + { elem: "g", expected: expectScrollBoth }, + { elem: "h", expected: expectScrollNone }, + { + dataUri: + 'data:text/html,<html><head><meta charset="utf-8"></head><body id="i" style="overflow-y: scroll"><div style="height: 2000px"></div>\ + <iframe id="iframe" style="display: none;"></iframe>\ + </body></html>', + }, + { elem: "i", expected: expectScrollVert }, // bug 695121 + { + dataUri: + 'data:text/html,<html><head><meta charset="utf-8"></head><style>html, body { width: 100%; height: 100%; overflow-x: hidden; overflow-y: scroll; }</style>\ + <body id="j"><div style="height: 2000px"></div>\ + <iframe id="iframe" style="display: none;"></iframe>\ + </body></html>', + }, + { elem: "j", expected: expectScrollVert }, // bug 914251 + { + dataUri: + 'data:text/html,<html><head><meta charset="utf-8">\ +<style>\ +body > div {scroll-behavior: smooth;width: 300px;height: 300px;overflow: scroll;}\ +body > div > div {width: 1000px;height: 1000px;}\ +</style>\ +</head><body><div id="t"><div></div></div></body></html>', + }, + { elem: "t", expected: expectScrollBoth }, // bug 1308775 + { + dataUri: + 'data:text/html,<html><head><meta charset="utf-8"></head><body>\ +<div id="k" style="height: 150px; width: 200px; overflow: scroll; border: 1px solid black;">\ +<iframe style="height: 200px; width: 300px;"></iframe>\ +</div>\ +<div id="l" style="height: 150px; width: 300px; overflow: scroll; border: 1px dashed black;">\ +<iframe style="height: 200px; width: 200px;" src="data:text/html,<div style=\'border: 5px solid blue; height: 200%; width: 200%;\'></div>"></iframe>\ +</div>\ +<iframe id="m"></iframe>\ +<div style="height: 200%; border: 5px dashed black;">filler to make document overflow: scroll;</div>\ +</body></html>', + }, + { elem: "k", expected: expectScrollBoth }, + { elem: "k", expected: expectScrollNone, testwindow: true }, + { elem: "l", expected: expectScrollNone }, + { elem: "m", expected: expectScrollVert, testwindow: true }, + { + dataUri: + 'data:text/html,<html><head><meta charset="utf-8"></head><body>\ +<img width="100" height="100" alt="image map" usemap="%23planetmap">\ +<map name="planetmap">\ + <area id="n" shape="rect" coords="0,0,100,100" href="javascript:void(null)">\ +</map>\ +<a href="javascript:void(null)" id="o" style="width: 100px; height: 100px; border: 1px solid black; display: inline-block; vertical-align: top;">link</a>\ +<input id="p" style="width: 100px; height: 100px; vertical-align: top;">\ +<textarea id="q" style="width: 100px; height: 100px; vertical-align: top;"></textarea>\ +<div style="height: 200%; border: 1px solid black;"></div>\ +</body></html>', + }, + { elem: "n", expected: expectScrollNone, testwindow: true }, + { elem: "o", expected: expectScrollNone, testwindow: true }, + { + elem: "p", + expected: expectScrollVert, + testwindow: true, + middlemousepastepref: false, + }, + { + elem: "q", + expected: expectScrollVert, + testwindow: true, + middlemousepastepref: false, + }, + { + dataUri: + 'data:text/html,<html><head><meta charset="utf-8"></head><body>\ +<input id="r" style="width: 100px; height: 100px; vertical-align: top;">\ +<textarea id="s" style="width: 100px; height: 100px; vertical-align: top;"></textarea>\ +<div style="height: 200%; border: 1px solid black;"></div>\ +</body></html>', + }, + { + elem: "r", + expected: expectScrollNone, + testwindow: true, + middlemousepastepref: true, + }, + { + elem: "s", + expected: expectScrollNone, + testwindow: true, + middlemousepastepref: true, + }, + { + dataUri: + "data:text/html," + + encodeURIComponent(` +<!doctype html> +<iframe id=i height=100 width=100 scrolling="no" srcdoc="<div style='height: 200px'>Auto-scrolling should never make me disappear"></iframe> +<div style="height: 100vh"></div> + `), + }, + { + elem: "i", + // We expect the outer window to scroll vertically, not the iframe's window. + expected: expectScrollVert, + testwindow: true, + }, + { + dataUri: + "data:text/html," + + encodeURIComponent(` +<!doctype html> +<iframe id=i height=100 width=100 srcdoc="<div style='height: 200px'>Auto-scrolling should make me disappear"></iframe> +<div style="height: 100vh"></div> + `), + }, + { + elem: "i", + // We expect the iframe's window to scroll vertically, so the outer window should not scroll. + expected: expectScrollNone, + testwindow: true, + }, + { + // Test: scroll is initiated in out of process iframe having no scrollable area + dataUri: + "data:text/html," + + encodeURIComponent(` +<!doctype html> +<head><meta content="text/html;charset=utf-8"></head><body> +<div id="scroller" style="width: 300px; height: 300px; overflow-y: scroll; overflow-x: hidden; border: solid 1px blue;"> + <iframe id="noscroll-outofprocess-iframe" + src="https://example.com/document-builder.sjs?html=<html><body>Hey!</body></html>" + style="border: solid 1px green; margin: 2px;"></iframe> + <div style="width: 100%; height: 200px;"></div> +</div></body> + `), + }, + { + elem: "noscroll-outofprocess-iframe", + // We expect the div to scroll vertically, not the iframe's window. + expected: expectScrollVert, + scrollable: "scroller", + }, + ]; + + for (let test of allTests) { + if (test.dataUri) { + let loadedPromise = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser + ); + BrowserTestUtils.loadURI(gBrowser, test.dataUri); + await loadedPromise; + continue; + } + + let prefsChanged = "middlemousepastepref" in test; + if (prefsChanged) { + await pushPrefs([["middlemouse.paste", test.middlemousepastepref]]); + } + + await BrowserTestUtils.synthesizeMouse( + "#" + test.elem, + 50, + 80, + { button: 1 }, + gBrowser.selectedBrowser + ); + + // This ensures bug 605127 is fixed: pagehide in an unrelated document + // should not cancel the autoscroll. + await ContentTask.spawn( + gBrowser.selectedBrowser, + { waitForAutoScrollStart: test.expected != expectScrollNone }, + async ({ waitForAutoScrollStart }) => { + var iframe = content.document.getElementById("iframe"); + + if (iframe) { + var e = new iframe.contentWindow.PageTransitionEvent("pagehide", { + bubbles: true, + cancelable: true, + persisted: false, + }); + iframe.contentDocument.dispatchEvent(e); + iframe.contentDocument.documentElement.dispatchEvent(e); + } + if (waitForAutoScrollStart) { + await new Promise(resolve => + Services.obs.addObserver(resolve, "autoscroll-start") + ); + } + } + ); + + is( + document.activeElement, + gBrowser.selectedBrowser, + "Browser still focused after autoscroll started" + ); + + await BrowserTestUtils.synthesizeMouse( + "#" + test.elem, + 100, + 100, + { type: "mousemove", clickCount: "0" }, + gBrowser.selectedBrowser + ); + + if (prefsChanged) { + await SpecialPowers.popPrefEnv(); + } + + // Start checking for the scroll. + let firstTimestamp = undefined; + let timeCompensation; + do { + let timestamp = await new Promise(resolve => + window.requestAnimationFrame(resolve) + ); + if (firstTimestamp === undefined) { + firstTimestamp = timestamp; + } + + // This value is calculated similarly to the value of the same name in + // ClickEventHandler.autoscrollLoop, except here it's cumulative across + // all frames after the first one instead of being based only on the + // current frame. + timeCompensation = (timestamp - firstTimestamp) / 20; + info( + "timestamp=" + + timestamp + + " firstTimestamp=" + + firstTimestamp + + " timeCompensation=" + + timeCompensation + ); + + // Try to wait until enough time has passed to allow the scroll to happen. + // autoscrollLoop incrementally scrolls during each animation frame, but + // due to how its calculations work, when a frame is very close to the + // previous frame, no scrolling may actually occur during that frame. + // After 100ms's worth of frames, timeCompensation will be 1, making it + // more likely that the accumulated scroll in autoscrollLoop will be >= 1, + // although it also depends on acceleration, which here in this test + // should be > 1 due to how it synthesizes mouse events below. + } while (timeCompensation < 5); + + // Close the autoscroll popup by synthesizing Esc. + EventUtils.synthesizeKey("KEY_Escape"); + let scrollVert = test.expected & expectScrollVert; + let scrollHori = test.expected & expectScrollHori; + + await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [ + { + scrollVert, + scrollHori, + elemid: test.scrollable || test.elem, + checkWindow: test.testwindow, + }, + ], + async function(args) { + let msg = ""; + if (args.checkWindow) { + if ( + !( + (args.scrollVert && content.scrollY > 0) || + (!args.scrollVert && content.scrollY == 0) + ) + ) { + msg += "Failed: "; + } + msg += + "Window for " + + args.elemid + + " should" + + (args.scrollVert ? "" : " not") + + " have scrolled vertically\n"; + + if ( + !( + (args.scrollHori && content.scrollX > 0) || + (!args.scrollHori && content.scrollX == 0) + ) + ) { + msg += "Failed: "; + } + msg += + " Window for " + + args.elemid + + " should" + + (args.scrollHori ? "" : " not") + + " have scrolled horizontally\n"; + } else { + let elem = content.document.getElementById(args.elemid); + if ( + !( + (args.scrollVert && elem.scrollTop > 0) || + (!args.scrollVert && elem.scrollTop == 0) + ) + ) { + msg += "Failed: "; + } + msg += + " " + + args.elemid + + " should" + + (args.scrollVert ? "" : " not") + + " have scrolled vertically\n"; + if ( + !( + (args.scrollHori && elem.scrollLeft > 0) || + (!args.scrollHori && elem.scrollLeft == 0) + ) + ) { + msg += "Failed: "; + } + msg += + args.elemid + + " should" + + (args.scrollHori ? "" : " not") + + " have scrolled horizontally"; + } + + Assert.ok(!msg.includes("Failed"), msg); + } + ); + + // Before continuing the test, we need to ensure that the IPC + // message that stops autoscrolling has had time to arrive. + await new Promise(resolve => executeSoon(resolve)); + } + + // remove 2 tabs that were opened by middle-click on links + while (gBrowser.visibleTabs.length > 1) { + gBrowser.removeTab(gBrowser.visibleTabs[gBrowser.visibleTabs.length - 1]); + } + + // wait for focus to fix a failure in the next test if the latter runs too soon. + await SimpleTest.promiseFocus(); +}); diff --git a/toolkit/content/tests/browser/browser_bug451286.js b/toolkit/content/tests/browser/browser_bug451286.js new file mode 100644 index 0000000000..7856356608 --- /dev/null +++ b/toolkit/content/tests/browser/browser_bug451286.js @@ -0,0 +1,166 @@ +Services.scriptloader.loadSubScript( + "chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js", + this +); + +add_task(async function() { + const SEARCH_TEXT = "text"; + const DATAURI = "data:text/html," + SEARCH_TEXT; + + // Bug 451286. An iframe that should be highlighted + let visible = "<iframe id='visible' src='" + DATAURI + "'></iframe>"; + + // Bug 493658. An invisible iframe that shouldn't interfere with + // highlighting matches lying after it in the document + let invisible = + "<iframe id='invisible' style='display: none;' " + + "src='" + + DATAURI + + "'></iframe>"; + + let uri = DATAURI + invisible + SEARCH_TEXT + visible + SEARCH_TEXT; + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, uri); + let contentRect = tab.linkedBrowser.getBoundingClientRect(); + let noHighlightSnapshot = snapshotRect(window, contentRect); + ok(noHighlightSnapshot, "Got noHighlightSnapshot"); + + await openFindBarAndWait(); + gFindBar._findField.value = SEARCH_TEXT; + await findAgainAndWait(); + var matchCase = gFindBar.getElement("find-case-sensitive"); + if (matchCase.checked) { + matchCase.doCommand(); + } + + // Turn on highlighting + await toggleHighlightAndWait(true); + await closeFindBarAndWait(); + + // Take snapshot of highlighting + let findSnapshot = snapshotRect(window, contentRect); + ok(findSnapshot, "Got findSnapshot"); + + // Now, remove the highlighting, and take a snapshot to compare + // to our original state + await openFindBarAndWait(); + await toggleHighlightAndWait(false); + await closeFindBarAndWait(); + + let unhighlightSnapshot = snapshotRect(window, contentRect); + ok(unhighlightSnapshot, "Got unhighlightSnapshot"); + + // Select the matches that should have been highlighted manually + await SpecialPowers.spawn(tab.linkedBrowser, [], async function() { + let doc = content.document; + let win = doc.defaultView; + + // Create a manual highlight in the visible iframe to test bug 451286 + let iframe = doc.getElementById("visible"); + let ifBody = iframe.contentDocument.body; + let range = iframe.contentDocument.createRange(); + range.selectNodeContents(ifBody.childNodes[0]); + let ifWindow = iframe.contentWindow; + let ifDocShell = ifWindow.docShell; + + let ifController = ifDocShell + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + + let frameFindSelection = ifController.getSelection( + ifController.SELECTION_FIND + ); + frameFindSelection.addRange(range); + + // Create manual highlights in the main document (the matches that lie + // before/after the iframes + let docShell = win.docShell; + + let controller = docShell + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + + let docFindSelection = controller.getSelection(ifController.SELECTION_FIND); + + range = doc.createRange(); + range.selectNodeContents(doc.body.childNodes[0]); + docFindSelection.addRange(range); + range = doc.createRange(); + range.selectNodeContents(doc.body.childNodes[2]); + docFindSelection.addRange(range); + range = doc.createRange(); + range.selectNodeContents(doc.body.childNodes[4]); + docFindSelection.addRange(range); + }); + + // Take snapshot of manual highlighting + let manualSnapshot = snapshotRect(window, contentRect); + ok(manualSnapshot, "Got manualSnapshot"); + + // Test 1: Were the matches in iframe correctly highlighted? + let res = compareSnapshots(findSnapshot, manualSnapshot, true); + ok(res[0], "Matches found in iframe correctly highlighted"); + + // Test 2: Were the matches in iframe correctly unhighlighted? + res = compareSnapshots(noHighlightSnapshot, unhighlightSnapshot, true); + ok(res[0], "Highlighting in iframe correctly removed"); + + BrowserTestUtils.removeTab(tab); +}); + +function toggleHighlightAndWait(shouldHighlight) { + return new Promise(resolve => { + let listener = { + onFindResult() {}, + onHighlightFinished() { + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + }, + onMatchesCountResult() {}, + }; + gFindBar.browser.finder.addResultListener(listener); + gFindBar.toggleHighlight(shouldHighlight); + }); +} + +function findAgainAndWait() { + return new Promise(resolve => { + let listener = { + onFindResult() { + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + }, + onHighlightFinished() {}, + onMatchesCountResult() {}, + }; + gFindBar.browser.finder.addResultListener(listener); + gFindBar.onFindAgainCommand(); + }); +} + +async function openFindBarAndWait() { + await gFindBarPromise; + let awaitTransitionEnd = BrowserTestUtils.waitForEvent( + gFindBar, + "transitionend" + ); + gFindBar.open(); + await awaitTransitionEnd; +} + +// This test is comparing snapshots. It is necessary to wait for the gFindBar +// to close before taking the snapshot so the gFindBar does not take up space +// on the new snapshot. +async function closeFindBarAndWait() { + let awaitTransitionEnd = BrowserTestUtils.waitForEvent( + gFindBar, + "transitionend", + false, + event => { + return event.propertyName == "visibility"; + } + ); + gFindBar.close(); + await awaitTransitionEnd; +} diff --git a/toolkit/content/tests/browser/browser_bug594509.js b/toolkit/content/tests/browser/browser_bug594509.js new file mode 100644 index 0000000000..cf93d2c71c --- /dev/null +++ b/toolkit/content/tests/browser/browser_bug594509.js @@ -0,0 +1,15 @@ +add_task(async function() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:rights" + ); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async function() { + Assert.ok( + content.document.getElementById("your-rights"), + "about:rights content loaded" + ); + }); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/toolkit/content/tests/browser/browser_bug982298.js b/toolkit/content/tests/browser/browser_bug982298.js new file mode 100644 index 0000000000..99a4dc5e96 --- /dev/null +++ b/toolkit/content/tests/browser/browser_bug982298.js @@ -0,0 +1,72 @@ +const scrollHtml = + '<textarea id="textarea1" row=2>Firefox\n\nFirefox\n\n\n\n\n\n\n\n\n\n' + + '</textarea><a href="about:blank">blank</a>'; + +add_task(async function() { + let url = "data:text/html;base64," + btoa(scrollHtml); + await BrowserTestUtils.withNewTab({ gBrowser, url }, async function(browser) { + let awaitFindResult = new Promise(resolve => { + let listener = { + onFindResult(aData) { + info("got find result"); + browser.finder.removeResultListener(listener); + + ok( + aData.result == Ci.nsITypeAheadFind.FIND_FOUND, + "should find string" + ); + resolve(); + }, + onCurrentSelection() {}, + onMatchesCountResult() {}, + }; + info("about to add results listener, open find bar, and send 'F' string"); + browser.finder.addResultListener(listener); + }); + await gFindBarPromise; + gFindBar.onFindCommand(); + EventUtils.sendString("F"); + info("added result listener and sent string 'F'"); + await awaitFindResult; + + // scroll textarea to bottom + await SpecialPowers.spawn(browser, [], () => { + let textarea = content.document.getElementById("textarea1"); + textarea.scrollTop = textarea.scrollHeight; + }); + BrowserTestUtils.loadURI(browser, "about:blank"); + await BrowserTestUtils.browserLoaded(browser); + + ok( + browser.currentURI.spec == "about:blank", + "got load event for about:blank" + ); + + let awaitFindResult2 = new Promise(resolve => { + let listener = { + onFindResult(aData) { + info("got find result #2"); + browser.finder.removeResultListener(listener); + resolve(); + }, + onCurrentSelection() {}, + onMatchesCountResult() {}, + }; + + browser.finder.addResultListener(listener); + info("added result listener"); + }); + // find again needs delay for crash test + setTimeout(function() { + // ignore exception if occured + try { + info("about to send find again command"); + gFindBar.onFindAgainCommand(false); + info("sent find again command"); + } catch (e) { + info("got exception from onFindAgainCommand: " + e); + } + }, 0); + await awaitFindResult2; + }); +}); diff --git a/toolkit/content/tests/browser/browser_charsetMenu_swapBrowsers.js b/toolkit/content/tests/browser/browser_charsetMenu_swapBrowsers.js new file mode 100644 index 0000000000..8eda01de8e --- /dev/null +++ b/toolkit/content/tests/browser/browser_charsetMenu_swapBrowsers.js @@ -0,0 +1,34 @@ +/* Test that the charset menu is properly enabled when swapping browsers. */ +add_task(async function test() { + // NB: This test cheats and calls updateCharacterEncodingMenuState directly + // instead of opening the "View" menu. + function charsetMenuEnabled() { + updateCharacterEncodingMenuState(); + return !document.getElementById("charsetMenu").hasAttribute("disabled"); + } + + const PAGE = "data:text/html,<!DOCTYPE html><body>hello"; + let tab1 = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + url: PAGE, + }); + ok(charsetMenuEnabled(), "should have a charset menu here"); + + let tab2 = await BrowserTestUtils.openNewForegroundTab({ gBrowser }); + ok(!charsetMenuEnabled(), "about:blank shouldn't have a charset menu"); + + await BrowserTestUtils.switchTab(gBrowser, tab1); + + let swapped = BrowserTestUtils.waitForEvent( + tab2.linkedBrowser, + "SwapDocShells" + ); + + // NB: Closes tab1. + gBrowser.swapBrowsersAndCloseOther(tab2, tab1); + await swapped; + + ok(charsetMenuEnabled(), "should have a charset after the swap"); + + BrowserTestUtils.removeTab(tab2); +}); diff --git a/toolkit/content/tests/browser/browser_contentTitle.js b/toolkit/content/tests/browser/browser_contentTitle.js new file mode 100644 index 0000000000..abd70809fc --- /dev/null +++ b/toolkit/content/tests/browser/browser_contentTitle.js @@ -0,0 +1,17 @@ +var url = + "https://example.com/browser/toolkit/content/tests/browser/file_contentTitle.html"; + +add_task(async function() { + let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url)); + await BrowserTestUtils.waitForContentEvent( + tab.linkedBrowser, + "TestLocationChange", + true, + null, + true + ); + + is(gBrowser.contentTitle, "Test Page", "Should have the right title."); + + gBrowser.removeTab(tab); +}); diff --git a/toolkit/content/tests/browser/browser_content_url_annotation.js b/toolkit/content/tests/browser/browser_content_url_annotation.js new file mode 100644 index 0000000000..72180ec2d0 --- /dev/null +++ b/toolkit/content/tests/browser/browser_content_url_annotation.js @@ -0,0 +1,78 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +/* global Services, requestLongerTimeout, TestUtils, BrowserTestUtils, + ok, info, dump, is, Ci, Cu, Components, ctypes, + gBrowser, add_task, addEventListener, removeEventListener, ContentTask */ + +"use strict"; + +// Running this test in ASAN is slow. +requestLongerTimeout(2); + +/** + * Removes a file from a directory. This is a no-op if the file does not + * exist. + * + * @param directory + * The nsIFile representing the directory to remove from. + * @param filename + * A string for the file to remove from the directory. + */ +function removeFile(directory, filename) { + let file = directory.clone(); + file.append(filename); + if (file.exists()) { + file.remove(false); + } +} + +/** + * Returns the directory where crash dumps are stored. + * + * @return nsIFile + */ +function getMinidumpDirectory() { + let dir = Services.dirsvc.get("ProfD", Ci.nsIFile); + dir.append("minidumps"); + return dir; +} + +/** + * Checks that the URL is correctly annotated on a content process crash. + */ +add_task(async function test_content_url_annotation() { + let url = + "https://example.com/browser/toolkit/content/tests/browser/file_redirect.html"; + let redirect_url = + "https://example.com/browser/toolkit/content/tests/browser/file_redirect_to.html"; + + await BrowserTestUtils.withNewTab( + { + gBrowser, + }, + async function(browser) { + ok(browser.isRemoteBrowser, "Should be a remote browser"); + + // file_redirect.html should send us to file_redirect_to.html + let promise = BrowserTestUtils.waitForContentEvent( + browser, + "RedirectDone", + true, + null, + true + ); + BrowserTestUtils.loadURI(browser, url); + await promise; + + // Crash the tab + let annotations = await BrowserTestUtils.crashFrame(browser); + + ok("URL" in annotations, "annotated a URL"); + is( + annotations.URL, + redirect_url, + "Should have annotated the URL after redirect" + ); + } + ); +}); diff --git a/toolkit/content/tests/browser/browser_crash_previous_frameloader.js b/toolkit/content/tests/browser/browser_crash_previous_frameloader.js new file mode 100644 index 0000000000..6870cd825d --- /dev/null +++ b/toolkit/content/tests/browser/browser_crash_previous_frameloader.js @@ -0,0 +1,131 @@ +"use strict"; + +/** + * Returns the id of the crash minidump. + * + * @param subject (nsISupports) + * The subject passed through the ipc:content-shutdown + * observer notification when a content process crash has + * occurred. + * @returns {String} The crash dump id. + */ +function getCrashDumpId(subject) { + Assert.ok( + subject instanceof Ci.nsIPropertyBag2, + "Subject needs to be a nsIPropertyBag2 to clean up properly" + ); + + return subject.getPropertyAsAString("dumpID"); +} + +/** + * Cleans up the .dmp and .extra file from a crash. + * + * @param id {String} The crash dump id. + */ +function cleanUpMinidump(id) { + let dir = Services.dirsvc.get("ProfD", Ci.nsIFile); + dir.append("minidumps"); + + let file = dir.clone(); + file.append(id + ".dmp"); + file.remove(true); + + file = dir.clone(); + file.append(id + ".extra"); + file.remove(true); +} + +/** + * This test ensures that if a remote frameloader crashes after + * the frameloader owner swaps it out for a new frameloader, + * that a oop-browser-crashed event is not sent to the new + * frameloader's browser element. + */ +add_task(async function test_crash_in_previous_frameloader() { + // On debug builds, crashing tabs results in much thinking, which + // slows down the test and results in intermittent test timeouts, + // so we'll pump up the expected timeout for this test. + requestLongerTimeout(2); + + if (!gMultiProcessBrowser) { + Assert.ok(false, "This test should only be run in multi-process mode."); + return; + } + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "http://example.com", + }, + async function(browser) { + // First, sanity check... + Assert.ok( + browser.isRemoteBrowser, + "This browser needs to be remote if this test is going to " + + "work properly." + ); + + // We will wait for the oop-browser-crashed event to have + // a chance to appear. That event is fired when RemoteTabs + // are destroyed, and that occurs _before_ ContentParents + // are destroyed, so we'll wait on the ipc:content-shutdown + // observer notification, which is fired when a ContentParent + // goes away. After we see this notification, oop-browser-crashed + // events should have fired. + let contentProcessGone = TestUtils.topicObserved("ipc:content-shutdown"); + let sawTabCrashed = false; + let onTabCrashed = () => { + sawTabCrashed = true; + }; + + browser.addEventListener("oop-browser-crashed", onTabCrashed); + + // The name of the game is to cause a crash in a remote browser, + // and then immediately swap out the browser for a non-remote one. + await SpecialPowers.spawn(browser, [], function() { + const { ctypes } = ChromeUtils.import( + "resource://gre/modules/ctypes.jsm" + ); + + let dies = function() { + ChromeUtils.privateNoteIntentionalCrash(); + let zero = new ctypes.intptr_t(8); + let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t)); + badptr.contents; + }; + + // When the parent flips the remoteness of the browser, the + // page should receive the pagehide event, which we'll then + // use to crash the frameloader. + docShell.chromeEventHandler.addEventListener("pagehide", function() { + dump("\nEt tu, Brute?\n"); + dies(); + }); + }); + + gBrowser.updateBrowserRemoteness(browser, { + remoteType: E10SUtils.NOT_REMOTE, + }); + info("Waiting for content process to go away."); + let [subject /* , data */] = await contentProcessGone; + + // If we don't clean up the minidump, the harness will + // complain. + let dumpID = getCrashDumpId(subject); + + Assert.ok(dumpID, "There should be a dumpID"); + if (dumpID) { + await Services.crashmanager.ensureCrashIsPresent(dumpID); + cleanUpMinidump(dumpID); + } + + info("Content process is gone!"); + Assert.ok( + !sawTabCrashed, + "Should not have seen the oop-browser-crashed event." + ); + browser.removeEventListener("oop-browser-crashed", onTabCrashed); + } + ); +}); diff --git a/toolkit/content/tests/browser/browser_datetime_datepicker.js b/toolkit/content/tests/browser/browser_datetime_datepicker.js new file mode 100644 index 0000000000..a354dda0ec --- /dev/null +++ b/toolkit/content/tests/browser/browser_datetime_datepicker.js @@ -0,0 +1,791 @@ +/* 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 MONTH_YEAR = ".month-year", + DAYS_VIEW = ".days-view", + BTN_PREV_MONTH = ".prev", + BTN_NEXT_MONTH = ".next"; +const DATE_FORMAT = new Intl.DateTimeFormat("en-US", { + year: "numeric", + month: "long", + timeZone: "UTC", +}).format; +const DATE_FORMAT_LOCAL = new Intl.DateTimeFormat("en-US", { + year: "numeric", + month: "long", +}).format; + +// Create a list of abbreviations for calendar class names +const W = "weekend", + O = "outside", + S = "selection", + R = "out-of-range", + T = "today", + P = "off-step"; + +// Calendar classlist for 2016-12. Used to verify the classNames are correct. +const calendarClasslist_201612 = [ + [W, O], + [O], + [O], + [O], + [], + [], + [W], + [W], + [], + [], + [], + [], + [], + [W], + [W], + [], + [], + [], + [S], + [], + [W], + [W], + [], + [], + [], + [], + [], + [W], + [W], + [], + [], + [], + [], + [], + [W], + [W, O], + [O], + [O], + [O], + [O], + [O], + [W, O], +]; + +function getCalendarText() { + return helper.getChildren(DAYS_VIEW).map(child => child.textContent); +} + +function getCalendarClassList() { + return helper + .getChildren(DAYS_VIEW) + .map(child => Array.from(child.classList)); +} + +function mergeArrays(a, b) { + return a.map((classlist, index) => classlist.concat(b[index])); +} + +async function verifyPickerPosition(browsingContext, inputId) { + let inputRect = await SpecialPowers.spawn( + browsingContext, + [inputId], + async function(inputIdChild) { + let rect = content.document + .getElementById(inputIdChild) + .getBoundingClientRect(); + return { + left: content.mozInnerScreenX + rect.left, + bottom: content.mozInnerScreenY + rect.bottom, + }; + } + ); + + function is_close(got, exp, msg) { + // on some platforms we see differences of a fraction of a pixel - so + // allow any difference of < 1 pixels as being OK. + ok( + Math.abs(got - exp) < 1, + msg + ": " + got + " should be equal(-ish) to " + exp + ); + } + is_close(helper.panel.screenX, inputRect.left, "datepicker x position"); + is_close(helper.panel.screenY, inputRect.bottom, "datepicker y position"); +} + +let helper = new DateTimeTestHelper(); + +registerCleanupFunction(() => { + helper.cleanup(); +}); + +/** + * Test that date picker opens to today's date when input field is blank + */ +add_task(async function test_datepicker_today() { + const date = new Date(); + + await helper.openPicker("data:text/html, <input type='date'>"); + + if (date.getMonth() === new Date().getMonth()) { + Assert.equal( + helper.getElement(MONTH_YEAR).textContent, + DATE_FORMAT_LOCAL(date) + ); + } else { + Assert.ok( + true, + "Skipping datepicker today test if month changes when opening picker." + ); + } + + await helper.tearDown(); +}); + +/** + * Test that date picker opens to the correct month, with calendar days + * displayed correctly, given a date value is set. + */ +add_task(async function test_datepicker_open() { + const inputValue = "2016-12-15"; + + await helper.openPicker( + `data:text/html, <input type="date" value="${inputValue}">` + ); + + Assert.equal( + helper.getElement(MONTH_YEAR).textContent, + DATE_FORMAT(new Date(inputValue)) + ); + Assert.deepEqual( + getCalendarText(), + [ + "27", + "28", + "29", + "30", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19", + "20", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "28", + "29", + "30", + "31", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + ], + "2016-12" + ); + Assert.deepEqual( + getCalendarClassList(), + calendarClasslist_201612, + "2016-12 classNames" + ); + + await helper.tearDown(); +}); + +/** + * Ensure picker closes when focus moves to a different input. + */ +add_task(async function test_datepicker_focus_change() { + await helper.openPicker( + `data:text/html,<input id=date type=date><input id=other>` + ); + let browser = helper.tab.linkedBrowser; + await verifyPickerPosition(browser, "date"); + + isnot(helper.panel.hidden, "Panel should be visible"); + + await SpecialPowers.spawn(browser, [], () => { + content.document.querySelector("#other").focus(); + }); + + // NOTE: Would be cool to be able to use promisePickerClosed(), but + // popuphidden isn't really triggered for this code path it seems, so oh + // well. + await BrowserTestUtils.waitForCondition( + () => helper.panel.hidden, + "waiting for close" + ); + await helper.tearDown(); +}); + +/** + * Ensure picker opens and closes with key bindings appropriately. + */ +/* +disabled for bug 1676078 +add_task(async function test_datepicker_keyboard_open() { + const inputValue = "2016-12-15"; + const prevMonth = "2016-11-01"; + await helper.openPicker( + `data:text/html,<input id=date type=date value=${inputValue}>` + ); + let browser = helper.tab.linkedBrowser; + await verifyPickerPosition(browser, "date"); + + BrowserTestUtils.synthesizeKey(" ", {}, browser); + + await BrowserTestUtils.waitForCondition( + () => helper.panel.hidden, + "waiting for close" + ); + + BrowserTestUtils.synthesizeKey(" ", {}, browser); + + await BrowserTestUtils.waitForCondition( + () => !helper.panel.hidden, + "waiting for open" + ); + + // NOTE: After the click, the first input field (the month one) is focused, + // so down arrow will change the selected month. + // + // This assumes en-US locale, which seems fine for testing purposes (as + // DATE_FORMAT and other bits around do the same). + BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser); + + // It'd be good to use something else than waitForCondition for this but + // there's no exposed event atm when the value changes from the child. + await BrowserTestUtils.waitForCondition(() => { + return ( + helper.getElement(MONTH_YEAR).textContent == + DATE_FORMAT(new Date(prevMonth)) + ); + }, "Should update date when updating months"); + + await helper.tearDown(); +}); +*/ + +/** + * When the prev month button is clicked, calendar should display the dates for + * the previous month. + */ +add_task(async function test_datepicker_prev_month_btn() { + const inputValue = "2016-12-15"; + const prevMonth = "2016-11-01"; + + await helper.openPicker( + `data:text/html, <input type="date" value="${inputValue}">` + ); + helper.click(helper.getElement(BTN_PREV_MONTH)); + + Assert.equal( + helper.getElement(MONTH_YEAR).textContent, + DATE_FORMAT(new Date(prevMonth)) + ); + Assert.deepEqual( + getCalendarText(), + [ + "30", + "31", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19", + "20", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "28", + "29", + "30", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + ], + "2016-11" + ); + + await helper.tearDown(); +}); + +/** + * When the next month button is clicked, calendar should display the dates for + * the next month. + */ +add_task(async function test_datepicker_next_month_btn() { + const inputValue = "2016-12-15"; + const nextMonth = "2017-01-01"; + + await helper.openPicker( + `data:text/html, <input type="date" value="${inputValue}">` + ); + helper.click(helper.getElement(BTN_NEXT_MONTH)); + + Assert.equal( + helper.getElement(MONTH_YEAR).textContent, + DATE_FORMAT(new Date(nextMonth)) + ); + Assert.deepEqual( + getCalendarText(), + [ + "25", + "26", + "27", + "28", + "29", + "30", + "31", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19", + "20", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "28", + "29", + "30", + "31", + "1", + "2", + "3", + "4", + ], + "2017-01" + ); + + await helper.tearDown(); +}); + +/** + * When a date on the calendar is clicked, date picker should close and set + * value to the input box. + */ +add_task(async function test_datepicker_clicked() { + const inputValue = "2016-12-15"; + const firstDayOnCalendar = "2016-11-27"; + + await helper.openPicker( + `data:text/html, <input id="date" type="date" value="${inputValue}">` + ); + + let browser = helper.tab.linkedBrowser; + await verifyPickerPosition(browser, "date"); + + // Click the first item (top-left corner) of the calendar + let promise = BrowserTestUtils.waitForContentEvent( + helper.tab.linkedBrowser, + "input" + ); + helper.click(helper.getElement(DAYS_VIEW).children[0]); + await promise; + + let value = await SpecialPowers.spawn( + browser, + [], + () => content.document.querySelector("input").value + ); + Assert.equal(value, firstDayOnCalendar); + + await helper.tearDown(); +}); + +/** + * Ensure that the datepicker popop appears correctly positioned when + * the input field has been transformed. + */ +add_task(async function test_datepicker_transformed_position() { + const inputValue = "2016-12-15"; + + const style = + "transform: translateX(7px) translateY(13px); border-top: 2px; border-left: 5px; margin: 30px;"; + const iframeContent = `<input id="date" type="date" value="${inputValue}" style="${style}">`; + await helper.openPicker( + "data:text/html,<iframe id='iframe' src='http://example.net/document-builder.sjs?html=" + + encodeURI(iframeContent) + + "'>", + true + ); + + let bc = helper.tab.linkedBrowser.browsingContext.children[0]; + await verifyPickerPosition(bc, "date"); + + await helper.tearDown(); +}); + +/** + * Make sure picker is in correct state when it is reopened. + */ +add_task(async function test_datepicker_reopen_state() { + const inputValue = "2016-12-15"; + const nextMonth = "2017-01-01"; + + await helper.openPicker( + `data:text/html, <input type="date" value="${inputValue}">` + ); + // Navigate to the next month but does not commit the change + Assert.equal( + helper.getElement(MONTH_YEAR).textContent, + DATE_FORMAT(new Date(inputValue)) + ); + helper.click(helper.getElement(BTN_NEXT_MONTH)); + Assert.equal( + helper.getElement(MONTH_YEAR).textContent, + DATE_FORMAT(new Date(nextMonth)) + ); + EventUtils.synthesizeKey("VK_ESCAPE", {}, window); + + // Ensures the picker opens to the month of the input value + await BrowserTestUtils.synthesizeMouseAtCenter( + "input", + {}, + gBrowser.selectedBrowser + ); + await helper.waitForPickerReady(); + Assert.equal( + helper.getElement(MONTH_YEAR).textContent, + DATE_FORMAT(new Date(inputValue)) + ); + + await helper.tearDown(); +}); + +/** + * When min and max attributes are set, calendar should show some dates as + * out-of-range. + */ +add_task(async function test_datepicker_min_max() { + const inputValue = "2016-12-15"; + const inputMin = "2016-12-05"; + const inputMax = "2016-12-25"; + + await helper.openPicker( + `data:text/html, <input type="date" value="${inputValue}" min="${inputMin}" max="${inputMax}">` + ); + + Assert.deepEqual( + getCalendarClassList(), + mergeArrays(calendarClasslist_201612, [ + // R denotes out-of-range + [R], + [R], + [R], + [R], + [R], + [R], + [R], + [R], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [R], + [R], + [R], + [R], + [R], + [R], + [R], + [R], + [R], + [R], + [R], + [R], + [R], + ]), + "2016-12 with min & max" + ); + + await helper.tearDown(); +}); + +/** + * When step attribute is set, calendar should show some dates as off-step. + */ +add_task(async function test_datepicker_step() { + const inputValue = "2016-12-15"; + const inputStep = "5"; + + await helper.openPicker( + `data:text/html, <input type="date" value="${inputValue}" step="${inputStep}">` + ); + + Assert.deepEqual( + getCalendarClassList(), + mergeArrays(calendarClasslist_201612, [ + // P denotes off-step + [P], + [P], + [P], + [], + [P], + [P], + [P], + [P], + [], + [P], + [P], + [P], + [P], + [], + [P], + [P], + [P], + [P], + [], + [P], + [P], + [P], + [P], + [], + [P], + [P], + [P], + [P], + [], + [P], + [P], + [P], + [P], + [], + [P], + [P], + [P], + [P], + [], + [P], + [P], + [P], + ]), + "2016-12 with step" + ); + + await helper.tearDown(); +}); + +add_task(async function test_datepicker_abs_min() { + const inputValue = "0001-01-01"; + await helper.openPicker( + `data:text/html, <input type="date" value="${inputValue}">` + ); + + Assert.deepEqual( + getCalendarText(), + [ + "", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19", + "20", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "28", + "29", + "30", + "31", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + ], + "0001-01" + ); + + await helper.tearDown(); +}); + +// This test checks if the change event is considered as user input event. +add_task(async function test_datepicker_handling_user_input() { + await helper.openPicker(`data:text/html, <input type="date">`); + + let changeEventPromise = SpecialPowers.spawn( + helper.tab.linkedBrowser, + [], + async () => { + let input = content.document.querySelector("input"); + await ContentTaskUtils.waitForEvent(input, "change", false, e => { + ok( + content.window.windowUtils.isHandlingUserInput, + "isHandlingUserInput should be true" + ); + return true; + }); + } + ); + // Click the first item (top-left corner) of the calendar + helper.click(helper.getElement(DAYS_VIEW).children[0]); + await changeEventPromise; + + await helper.tearDown(); +}); + +add_task(async function test_datepicker_abs_max() { + const inputValue = "275760-09-13"; + await helper.openPicker( + `data:text/html, <input type="date" value="${inputValue}">` + ); + + Assert.deepEqual( + getCalendarText(), + [ + "31", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ], + "275760-09" + ); + + await helper.tearDown(); +}); diff --git a/toolkit/content/tests/browser/browser_default_image_filename.js b/toolkit/content/tests/browser/browser_default_image_filename.js new file mode 100644 index 0000000000..dc6eb8559e --- /dev/null +++ b/toolkit/content/tests/browser/browser_default_image_filename.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.init(window); + +/** + * TestCase for bug 564387 + * <https://bugzilla.mozilla.org/show_bug.cgi?id=564387> + */ +add_task(async function() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: + "data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7", + }, + async function(browser) { + let popupShownPromise = BrowserTestUtils.waitForEvent( + document, + "popupshown" + ); + + await BrowserTestUtils.synthesizeMouseAtCenter( + "img", + { + type: "contextmenu", + button: 2, + }, + browser + ); + + await popupShownPromise; + + let showFilePickerPromise = new Promise(resolve => { + MockFilePicker.showCallback = function(fp) { + is(fp.defaultString, "index.gif"); + resolve(); + }; + }); + + registerCleanupFunction(function() { + MockFilePicker.cleanup(); + }); + + // Select "Save Image As" option from context menu + var saveImageAsCommand = document.getElementById("context-saveimage"); + saveImageAsCommand.doCommand(); + + await showFilePickerPromise; + + let contextMenu = document.getElementById("contentAreaContextMenu"); + let popupHiddenPromise = BrowserTestUtils.waitForEvent( + contextMenu, + "popuphidden" + ); + contextMenu.hidePopup(); + await popupHiddenPromise; + } + ); +}); diff --git a/toolkit/content/tests/browser/browser_default_image_filename_redirect.js b/toolkit/content/tests/browser/browser_default_image_filename_redirect.js new file mode 100644 index 0000000000..a17d3dc3ee --- /dev/null +++ b/toolkit/content/tests/browser/browser_default_image_filename_redirect.js @@ -0,0 +1,53 @@ +/** + * TestCase for bug 1406253 + * <https://bugzilla.mozilla.org/show_bug.cgi?id=1406253> + * + * Load firebird.png, redirect it to doggy.png, and verify the filename is + * doggy.png in file picker dialog. + */ + +let MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.init(window); +add_task(async function() { + // This URL will redirect to doggy.png. + const URL_FIREBIRD = + "http://mochi.test:8888/browser/toolkit/content/tests/browser/firebird.png"; + + await BrowserTestUtils.withNewTab(URL_FIREBIRD, async function(browser) { + // Click image to show context menu. + let popupShownPromise = BrowserTestUtils.waitForEvent( + document, + "popupshown" + ); + await BrowserTestUtils.synthesizeMouseAtCenter( + "img", + { type: "contextmenu", button: 2 }, + browser + ); + await popupShownPromise; + + // Prepare mock file picker. + let showFilePickerPromise = new Promise(resolve => { + MockFilePicker.showCallback = fp => resolve(fp.defaultString); + }); + registerCleanupFunction(function() { + MockFilePicker.cleanup(); + }); + + // Select "Save Image As" option from context menu + var saveImageAsCommand = document.getElementById("context-saveimage"); + saveImageAsCommand.doCommand(); + + let filename = await showFilePickerPromise; + is(filename, "doggy.png", "Verify image filename."); + + // Close context menu. + let contextMenu = document.getElementById("contentAreaContextMenu"); + let popupHiddenPromise = BrowserTestUtils.waitForEvent( + contextMenu, + "popuphidden" + ); + contextMenu.hidePopup(); + await popupHiddenPromise; + }); +}); diff --git a/toolkit/content/tests/browser/browser_delay_autoplay_media.js b/toolkit/content/tests/browser/browser_delay_autoplay_media.js new file mode 100644 index 0000000000..acaf4ed16a --- /dev/null +++ b/toolkit/content/tests/browser/browser_delay_autoplay_media.js @@ -0,0 +1,145 @@ +const PAGE = + "https://example.com/browser/toolkit/content/tests/browser/file_multipleAudio.html"; +const PAGE_NO_AUTOPLAY = + "https://example.com/browser/toolkit/content/tests/browser/file_nonAutoplayAudio.html"; + +function check_audio_paused(browser, shouldBePaused) { + return SpecialPowers.spawn(browser, [shouldBePaused], shouldBePaused => { + var autoPlay = content.document.getElementById("autoplay"); + if (!autoPlay) { + ok(false, "Can't get the audio element!"); + } + is( + autoPlay.paused, + shouldBePaused, + "autoplay audio should " + (!shouldBePaused ? "not " : "") + "be paused." + ); + }); +} + +add_task(async function setup_test_preference() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.useAudioChannelService.testing", true], + ["media.block-autoplay-until-in-foreground", true], + ], + }); +}); + +function set_media_autoplay() { + let audio = content.document.getElementById("testAudio"); + if (!audio) { + ok(false, "Can't get the audio element!"); + return; + } + audio.autoplay = true; +} + +add_task(async function delay_media_with_autoplay_keyword() { + info("- open new background tab -"); + const tab = BrowserTestUtils.addTab(window.gBrowser, PAGE_NO_AUTOPLAY); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + info("- set media's autoplay property -"); + await SpecialPowers.spawn(tab.linkedBrowser, [], set_media_autoplay); + + info("- should delay autoplay media -"); + await waitForTabBlockEvent(tab, true); + + info("- switch tab to foreground -"); + await BrowserTestUtils.switchTab(window.gBrowser, tab); + + info("- media should be resumed because tab has been visited -"); + await waitForTabPlayingEvent(tab, true); + await waitForTabBlockEvent(tab, false); + + info("- remove tab -"); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function delay_media_with_play_invocation() { + info("- open new background tab1 -"); + let tab1 = BrowserTestUtils.addTab(window.gBrowser, PAGE); + await BrowserTestUtils.browserLoaded(tab1.linkedBrowser); + + info("- should delay autoplay media for non-visited tab1 -"); + await waitForTabBlockEvent(tab1, true); + + info("- open new background tab2 -"); + let tab2 = BrowserTestUtils.addTab(window.gBrowser, PAGE); + await BrowserTestUtils.browserLoaded(tab2.linkedBrowser); + + info("- should delay autoplay for non-visited tab2 -"); + await waitForTabBlockEvent(tab2, true); + + info("- switch to tab1 -"); + await BrowserTestUtils.switchTab(window.gBrowser, tab1); + + info("- media in tab1 should be unblocked because the tab was visited -"); + await waitForTabPlayingEvent(tab1, true); + await waitForTabBlockEvent(tab1, false); + + info("- open another new foreground tab3 -"); + let tab3 = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + "about:blank" + ); + info("- should still play media from tab1 -"); + await waitForTabPlayingEvent(tab1, true); + + info("- should still block media from tab2 -"); + await waitForTabPlayingEvent(tab2, false); + await waitForTabBlockEvent(tab2, true); + + info("- remove tabs -"); + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); + BrowserTestUtils.removeTab(tab3); +}); + +add_task(async function resume_delayed_media_when_enable_blocking_autoplay() { + // Disable autoplay and verify that when a tab is opened in the + // background and has had its playback start delayed, resuming via the audio + // tab indicator overrides the autoplay blocking logic. + // + // Clicking "play" on the audio tab indicator should always start playback + // in that tab, even if it's in an autoplay-blocked origin. + // + // Also test that that this block-autoplay logic override doesn't survive + // a new document being loaded into the tab; the new document should have + // to satisfy the autoplay requirements on its own. + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED], + ["media.autoplay.blocking_policy", 0], + ], + }); + + info("- open new background tab -"); + let tab = BrowserTestUtils.addTab(window.gBrowser, PAGE); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + info("- should block autoplay for non-visited tab -"); + await waitForTabBlockEvent(tab, true); + await check_audio_paused(tab.linkedBrowser, true); + tab.linkedBrowser.resumeMedia(); + + info("- should not block media from tab -"); + await waitForTabPlayingEvent(tab, true); + await check_audio_paused(tab.linkedBrowser, false); + + info( + "- check that loading a new URI in page clears gesture activation status -" + ); + BrowserTestUtils.loadURI(tab.linkedBrowser, PAGE); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + info("- should block autoplay again as gesture activation status cleared -"); + await check_audio_paused(tab.linkedBrowser, true); + + info("- remove tab -"); + BrowserTestUtils.removeTab(tab); + + // Clear the block-autoplay pref. + await SpecialPowers.popPrefEnv(); +}); diff --git a/toolkit/content/tests/browser/browser_delay_autoplay_media_pausedAfterPlay.js b/toolkit/content/tests/browser/browser_delay_autoplay_media_pausedAfterPlay.js new file mode 100644 index 0000000000..09dc5f9691 --- /dev/null +++ b/toolkit/content/tests/browser/browser_delay_autoplay_media_pausedAfterPlay.js @@ -0,0 +1,121 @@ +const PAGE = + "https://example.com/browser/toolkit/content/tests/browser/file_nonAutoplayAudio.html"; + +add_task(async function setup_test_preference() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.useAudioChannelService.testing", true], + ["media.block-autoplay-until-in-foreground", true], + ], + }); +}); + +/** + * When media starts in an unvisited tab, we would delay its playback and resume + * media playback when the tab goes to foreground first time. There are two test + * cases are used to check different situations. + * + * The first one is used to check if the delayed media has been paused before + * the tab goes to foreground. Then, when the tab goes to foreground, media + * should still be paused. + * + * The second one is used to check if the delayed media has been paused, but + * it eventually was started again before the tab goes to foreground. Then, when + * the tab goes to foreground, media should be resumed. + */ +add_task(async function testShouldNotResumePausedMedia() { + info("- open new background tab and wait until it finishes loading -"); + const tab = BrowserTestUtils.addTab(window.gBrowser, PAGE); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + info("- play media and then immediately pause it -"); + await doPlayThenPauseOnMedia(tab); + + info("- show delay media playback icon on tab -"); + await waitForTabBlockEvent(tab, true); + + info("- selecting tab as foreground tab would resume the tab -"); + await BrowserTestUtils.switchTab(window.gBrowser, tab); + + info("- resuming tab should dismiss delay autoplay icon -"); + await waitForTabBlockEvent(tab, false); + + info("- paused media should still be paused -"); + await checkAudioPauseState(tab, true /* should be paused */); + + info("- paused media won't generate tab playing icon -"); + await waitForTabPlayingEvent(tab, false); + + info("- remove tab -"); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function testShouldResumePlayedMedia() { + info("- open new background tab and wait until it finishes loading -"); + const tab = BrowserTestUtils.addTab(window.gBrowser, PAGE); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + info("- play media, pause it, then play it again -"); + await doPlayPauseThenPlayOnMedia(tab); + + info("- show delay media playback icon on tab -"); + await waitForTabBlockEvent(tab, true); + + info("- select tab as foreground tab -"); + await BrowserTestUtils.switchTab(window.gBrowser, tab); + + info("- resuming tab should dismiss delay autoplay icon -"); + await waitForTabBlockEvent(tab, false); + + info("- played media should still be played -"); + await checkAudioPauseState(tab, false /* should be played */); + + info("- played media would generate tab playing icon -"); + await waitForTabPlayingEvent(tab, true); + + info("- remove tab -"); + BrowserTestUtils.removeTab(tab); +}); + +/** + * Helper functions. + */ +async function checkAudioPauseState(tab, expectedPaused) { + await SpecialPowers.spawn( + tab.linkedBrowser, + [expectedPaused], + expectedPaused => { + const audio = content.document.getElementById("testAudio"); + if (!audio) { + ok(false, "Can't get the audio element!"); + } + + is(audio.paused, expectedPaused, "The pause state of audio is corret."); + } + ); +} + +async function doPlayThenPauseOnMedia(tab) { + await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + const audio = content.document.getElementById("testAudio"); + if (!audio) { + ok(false, "Can't get the audio element!"); + } + + audio.play(); + audio.pause(); + }); +} + +async function doPlayPauseThenPlayOnMedia(tab) { + await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + const audio = content.document.getElementById("testAudio"); + if (!audio) { + ok(false, "Can't get the audio element!"); + } + + audio.play(); + audio.pause(); + audio.play(); + }); +} diff --git a/toolkit/content/tests/browser/browser_delay_autoplay_multipleMedia.js b/toolkit/content/tests/browser/browser_delay_autoplay_multipleMedia.js new file mode 100644 index 0000000000..3d60156cf1 --- /dev/null +++ b/toolkit/content/tests/browser/browser_delay_autoplay_multipleMedia.js @@ -0,0 +1,77 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +const PAGE = + "https://example.com/browser/toolkit/content/tests/browser/file_multipleAudio.html"; + +function check_autoplay_audio_onplay() { + let autoPlay = content.document.getElementById("autoplay"); + if (!autoPlay) { + ok(false, "Can't get the audio element!"); + } + + return new Promise((resolve, reject) => { + autoPlay.onplay = () => { + ok(false, "Should not receive play event!"); + this.onplay = null; + reject(); + }; + + autoPlay.pause(); + autoPlay.play(); + + content.setTimeout(() => { + ok(true, "Doesn't receive play event when media was blocked."); + autoPlay.onplay = null; + resolve(); + }, 1000); + }); +} + +function play_nonautoplay_audio_should_be_blocked() { + let nonAutoPlay = content.document.getElementById("nonautoplay"); + if (!nonAutoPlay) { + ok(false, "Can't get the audio element!"); + } + + nonAutoPlay.play(); + ok(nonAutoPlay.paused, "The blocked audio can't be playback."); +} + +add_task(async function setup_test_preference() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.useAudioChannelService.testing", true], + ["media.block-autoplay-until-in-foreground", true], + ], + }); +}); + +add_task(async function block_multiple_media() { + info("- open new background tab -"); + let tab = BrowserTestUtils.addTab(window.gBrowser, "about:blank"); + let browser = tab.linkedBrowser; + BrowserTestUtils.loadURI(browser, PAGE); + await BrowserTestUtils.browserLoaded(browser); + + info("- tab should be blocked -"); + await waitForTabBlockEvent(tab, true); + + info("- autoplay media should be blocked -"); + await SpecialPowers.spawn(browser, [], check_autoplay_audio_onplay); + + info("- non-autoplay can't start playback when the tab is blocked -"); + await SpecialPowers.spawn( + browser, + [], + play_nonautoplay_audio_should_be_blocked + ); + + info("- select tab as foreground tab -"); + await BrowserTestUtils.switchTab(window.gBrowser, tab); + + info("- tab should be resumed -"); + await waitForTabPlayingEvent(tab, true); + await waitForTabBlockEvent(tab, false); + + info("- remove tab -"); + BrowserTestUtils.removeTab(tab); +}); diff --git a/toolkit/content/tests/browser/browser_delay_autoplay_notInTreeAudio.js b/toolkit/content/tests/browser/browser_delay_autoplay_notInTreeAudio.js new file mode 100644 index 0000000000..c1cca96b2d --- /dev/null +++ b/toolkit/content/tests/browser/browser_delay_autoplay_notInTreeAudio.js @@ -0,0 +1,66 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +const PAGE = + "https://example.com/browser/toolkit/content/tests/browser/file_nonAutoplayAudio.html"; + +function check_audio_pause_state(expectPause) { + var audio = content.document.getElementById("testAudio"); + if (!audio) { + ok(false, "Can't get the audio element!"); + } + + is(audio.paused, expectPause, "The pause state of audio is corret."); +} + +function play_not_in_tree_audio() { + var audio = content.document.getElementById("testAudio"); + if (!audio) { + ok(false, "Can't get the audio element!"); + } + + content.document.body.removeChild(audio); + + // Add timeout to ensure the audio is removed from DOM tree. + content.setTimeout(function() { + info("Prepare to start playing audio."); + audio.play(); + }, 1000); +} + +add_task(async function setup_test_preference() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.useAudioChannelService.testing", true], + ["media.block-autoplay-until-in-foreground", true], + ], + }); +}); + +add_task(async function block_not_in_tree_media() { + info("- open new background tab -"); + let tab = BrowserTestUtils.addTab(window.gBrowser, PAGE); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + info("- tab should not be blocked -"); + await waitForTabBlockEvent(tab, false); + + info("- check audio's playing state -"); + await SpecialPowers.spawn(tab.linkedBrowser, [true], check_audio_pause_state); + + info("- playing audio -"); + await SpecialPowers.spawn(tab.linkedBrowser, [], play_not_in_tree_audio); + + info("- tab should be blocked -"); + await waitForTabBlockEvent(tab, true); + + info("- switch tab -"); + await BrowserTestUtils.switchTab(window.gBrowser, tab); + + info("- tab should be resumed -"); + await waitForTabBlockEvent(tab, false); + + info("- tab should be audible -"); + await waitForTabPlayingEvent(tab, true); + + info("- remove tab -"); + BrowserTestUtils.removeTab(tab); +}); diff --git a/toolkit/content/tests/browser/browser_delay_autoplay_playAfterTabVisible.js b/toolkit/content/tests/browser/browser_delay_autoplay_playAfterTabVisible.js new file mode 100644 index 0000000000..e1f3f8f814 --- /dev/null +++ b/toolkit/content/tests/browser/browser_delay_autoplay_playAfterTabVisible.js @@ -0,0 +1,68 @@ +const PAGE = + "https://example.com/browser/toolkit/content/tests/browser/file_nonAutoplayAudio.html"; + +function check_audio_pause_state(expectPause) { + var audio = content.document.getElementById("testAudio"); + if (!audio) { + ok(false, "Can't get the audio element!"); + } + + is(audio.paused, expectPause, "The pause state of audio is corret."); +} + +function play_audio() { + var audio = content.document.getElementById("testAudio"); + if (!audio) { + ok(false, "Can't get the audio element!"); + } + + audio.play(); + return new Promise(resolve => { + audio.onplay = function() { + audio.onplay = null; + ok(true, "Audio starts playing."); + resolve(); + }; + }); +} + +add_task(async function setup_test_preference() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.useAudioChannelService.testing", true], + ["media.block-autoplay-until-in-foreground", true], + ], + }); +}); + +/** + * This test is used for testing the visible tab which was not resumed yet. + * If the tab doesn't have any media component, it won't be resumed even it + * has already gone to foreground until we start audio. + */ +add_task(async function media_should_be_able_to_play_in_visible_tab() { + info("- open new background tab, and check tab's media pause state -"); + let tab = BrowserTestUtils.addTab(window.gBrowser, PAGE); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + await SpecialPowers.spawn(tab.linkedBrowser, [true], check_audio_pause_state); + + info( + "- select tab as foreground tab, and tab's media should still be paused -" + ); + await BrowserTestUtils.switchTab(window.gBrowser, tab); + await SpecialPowers.spawn(tab.linkedBrowser, [true], check_audio_pause_state); + + info("- start audio in tab -"); + await SpecialPowers.spawn(tab.linkedBrowser, [], play_audio); + + info("- audio should be playing -"); + await waitForTabBlockEvent(tab, false); + await SpecialPowers.spawn( + tab.linkedBrowser, + [false], + check_audio_pause_state + ); + + info("- remove tab -"); + BrowserTestUtils.removeTab(tab); +}); diff --git a/toolkit/content/tests/browser/browser_delay_autoplay_playMediaInMuteTab.js b/toolkit/content/tests/browser/browser_delay_autoplay_playMediaInMuteTab.js new file mode 100644 index 0000000000..c333021697 --- /dev/null +++ b/toolkit/content/tests/browser/browser_delay_autoplay_playMediaInMuteTab.js @@ -0,0 +1,97 @@ +const PAGE = + "https://example.com/browser/toolkit/content/tests/browser/file_nonAutoplayAudio.html"; + +function wait_for_event(browser, event) { + return BrowserTestUtils.waitForEvent(browser, event, false, event => { + is( + event.originalTarget, + browser, + "Event must be dispatched to correct browser." + ); + return true; + }); +} + +function check_audio_volume_and_mute(expectedMute) { + var audio = content.document.getElementById("testAudio"); + if (!audio) { + ok(false, "Can't get the audio element!"); + } + + let expectedVolume = expectedMute ? 0.0 : 1.0; + is(expectedVolume, audio.computedVolume, "Audio's volume is correct!"); + is(expectedMute, audio.computedMuted, "Audio's mute state is correct!"); +} + +function check_audio_pause_state(expectedPauseState) { + var audio = content.document.getElementById("testAudio"); + if (!audio) { + ok(false, "Can't get the audio element!"); + } + + is(audio.paused, expectedPauseState, "Audio is paused."); +} + +function play_audio() { + var audio = content.document.getElementById("testAudio"); + if (!audio) { + ok(false, "Can't get the audio element!"); + } + + audio.play(); + ok(audio.paused, "Can't play audio, because the tab was still blocked."); +} + +add_task(async function setup_test_preference() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.useAudioChannelService.testing", true], + ["media.block-autoplay-until-in-foreground", true], + ], + }); +}); + +add_task(async function unblock_icon_should_disapear_after_resume_tab() { + info("- open new background tab -"); + let tab = BrowserTestUtils.addTab(window.gBrowser, PAGE); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + info("- audio doesn't be started in beginning -"); + await SpecialPowers.spawn(tab.linkedBrowser, [true], check_audio_pause_state); + + info("- audio shouldn't be muted -"); + await SpecialPowers.spawn( + tab.linkedBrowser, + [false], + check_audio_volume_and_mute + ); + + info("- tab shouldn't display unblocking icon -"); + await waitForTabBlockEvent(tab, false); + + info("- mute tab -"); + tab.linkedBrowser.mute(); + ok(tab.linkedBrowser.audioMuted, "Audio should be muted now"); + + info("- try to start audio in background tab -"); + await SpecialPowers.spawn(tab.linkedBrowser, [], play_audio); + + info("- tab should display unblocking icon -"); + await waitForTabBlockEvent(tab, true); + + info("- select tab as foreground tab -"); + await BrowserTestUtils.switchTab(window.gBrowser, tab); + + info("- audio shoule be muted, but not be blocked -"); + await SpecialPowers.spawn( + tab.linkedBrowser, + [true], + check_audio_volume_and_mute + ); + + info("- tab should not display unblocking icon -"); + await waitForTabBlockEvent(tab, false); + + info("- remove tab -"); + BrowserTestUtils.removeTab(tab); +}); diff --git a/toolkit/content/tests/browser/browser_delay_autoplay_silentAudioTrack_media.js b/toolkit/content/tests/browser/browser_delay_autoplay_silentAudioTrack_media.js new file mode 100644 index 0000000000..24b48d4999 --- /dev/null +++ b/toolkit/content/tests/browser/browser_delay_autoplay_silentAudioTrack_media.js @@ -0,0 +1,63 @@ +const PAGE = + "https://example.com/browser/toolkit/content/tests/browser/file_silentAudioTrack.html"; + +async function click_unblock_icon(tab) { + let icon = tab.soundPlayingIcon; + + await hover_icon(icon, document.getElementById("tabbrowser-tab-tooltip")); + EventUtils.synthesizeMouseAtCenter(icon, { button: 0 }); + leave_icon(icon); +} + +add_task(async function setup_test_preference() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.useAudioChannelService.testing", true], + ["media.block-autoplay-until-in-foreground", true], + ], + }); +}); + +add_task(async function unblock_icon_should_disapear_after_resume_tab() { + info("- open new background tab -"); + let tab = BrowserTestUtils.addTab(window.gBrowser, "about:blank"); + BrowserTestUtils.loadURI(tab.linkedBrowser, PAGE); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + info("- tab should display unblocking icon -"); + await waitForTabBlockEvent(tab, true); + + info("- select tab as foreground tab -"); + await BrowserTestUtils.switchTab(window.gBrowser, tab); + + info("- should not display unblocking icon -"); + await waitForTabBlockEvent(tab, false); + + info("- should not display sound indicator icon -"); + await waitForTabPlayingEvent(tab, false); + + info("- remove tab -"); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function should_not_show_sound_indicator_after_resume_tab() { + info("- open new background tab -"); + let tab = BrowserTestUtils.addTab(window.gBrowser, "about:blank"); + BrowserTestUtils.loadURI(tab.linkedBrowser, PAGE); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + info("- tab should display unblocking icon -"); + await waitForTabBlockEvent(tab, true); + + info("- click play tab icon -"); + await click_unblock_icon(tab); + + info("- should not display unblocking icon -"); + await waitForTabBlockEvent(tab, false); + + info("- should not display sound indicator icon -"); + await waitForTabPlayingEvent(tab, false); + + info("- remove tab -"); + BrowserTestUtils.removeTab(tab); +}); diff --git a/toolkit/content/tests/browser/browser_delay_autoplay_webAudio.js b/toolkit/content/tests/browser/browser_delay_autoplay_webAudio.js new file mode 100644 index 0000000000..8758cf2629 --- /dev/null +++ b/toolkit/content/tests/browser/browser_delay_autoplay_webAudio.js @@ -0,0 +1,40 @@ +const PAGE = + "https://example.com/browser/toolkit/content/tests/browser/file_webAudio.html"; + +// The tab closing code leaves an uncaught rejection. This test has been +// whitelisted until the issue is fixed. +if (!gMultiProcessBrowser) { + ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm", this); + PromiseTestUtils.expectUncaughtRejection(/is no longer, usable/); +} + +add_task(async function setup_test_preference() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.useAudioChannelService.testing", true], + ["media.block-autoplay-until-in-foreground", true], + ], + }); +}); + +add_task(async function block_web_audio() { + info("- open new background tab -"); + let tab = BrowserTestUtils.addTab(window.gBrowser, "about:blank"); + BrowserTestUtils.loadURI(tab.linkedBrowser, PAGE); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + info("- tab should be blocked -"); + await waitForTabBlockEvent(tab, true); + + info("- switch tab -"); + await BrowserTestUtils.switchTab(window.gBrowser, tab); + + info("- tab should be resumed -"); + await waitForTabBlockEvent(tab, false); + + info("- tab should be audible -"); + await waitForTabPlayingEvent(tab, true); + + info("- remove tab -"); + BrowserTestUtils.removeTab(tab); +}); diff --git a/toolkit/content/tests/browser/browser_f7_caret_browsing.js b/toolkit/content/tests/browser/browser_f7_caret_browsing.js new file mode 100644 index 0000000000..334d40a1f8 --- /dev/null +++ b/toolkit/content/tests/browser/browser_f7_caret_browsing.js @@ -0,0 +1,278 @@ +var gListener = null; +const kURL = + "data:text/html;charset=utf-8,Caret browsing is fun.<input id='in'>"; + +const kPrefShortcutEnabled = "accessibility.browsewithcaret_shortcut.enabled"; +const kPrefWarnOnEnable = "accessibility.warn_on_browsewithcaret"; +const kPrefCaretBrowsingOn = "accessibility.browsewithcaret"; + +var oldPrefs = {}; +for (let pref of [ + kPrefShortcutEnabled, + kPrefWarnOnEnable, + kPrefCaretBrowsingOn, +]) { + oldPrefs[pref] = Services.prefs.getBoolPref(pref); +} + +Services.prefs.setBoolPref(kPrefShortcutEnabled, true); +Services.prefs.setBoolPref(kPrefWarnOnEnable, true); +Services.prefs.setBoolPref(kPrefCaretBrowsingOn, false); + +registerCleanupFunction(function() { + for (let pref of [ + kPrefShortcutEnabled, + kPrefWarnOnEnable, + kPrefCaretBrowsingOn, + ]) { + Services.prefs.setBoolPref(pref, oldPrefs[pref]); + } +}); + +// NB: not using BrowserTestUtils.domWindowOpened here because there's no way to +// undo waiting for a window open. If we don't want the window to be opened, and +// wait for it to verify that it indeed does not open, we need to be able to +// then "stop" waiting so that when we next *do* want it to open, our "old" +// listener doesn't fire and do things we don't want (like close the window...). +let gCaretPromptOpeningObserver; +function promiseCaretPromptOpened() { + return new Promise(resolve => { + function observer(subject, topic, data) { + if (topic == "domwindowopened") { + Services.ww.unregisterNotification(observer); + let win = subject; + BrowserTestUtils.waitForEvent( + win, + "load", + false, + e => e.target.location.href != "about:blank" + ).then(() => resolve(win)); + gCaretPromptOpeningObserver = null; + } + } + Services.ww.registerNotification(observer); + gCaretPromptOpeningObserver = observer; + }); +} + +function hitF7(async = true) { + let f7 = () => EventUtils.sendKey("F7"); + // Need to not stop execution inside this task: + if (async) { + executeSoon(f7); + } else { + f7(); + } +} + +function syncToggleCaretNoDialog(expected) { + let openedDialog = false; + promiseCaretPromptOpened().then(function(win) { + openedDialog = true; + win.close(); // This will eventually return focus here and allow the test to continue... + }); + // Cause the dialog to appear sync, if it still does. + hitF7(false); + + let expectedStr = expected ? "on." : "off."; + ok( + !openedDialog, + "Shouldn't open a dialog to turn caret browsing " + expectedStr + ); + // Need to clean up if the dialog wasn't opened, so the observer doesn't get + // re-triggered later on causing "issues". + if (!openedDialog) { + Services.ww.unregisterNotification(gCaretPromptOpeningObserver); + gCaretPromptOpeningObserver = null; + } + let prefVal = Services.prefs.getBoolPref(kPrefCaretBrowsingOn); + is(prefVal, expected, "Caret browsing should now be " + expectedStr); +} + +function waitForFocusOnInput(browser) { + return SpecialPowers.spawn(browser, [], async function() { + let textEl = content.document.getElementById("in"); + return ContentTaskUtils.waitForCondition(() => { + return content.document.activeElement == textEl; + }, "Input should get focused."); + }); +} + +function focusInput(browser) { + return SpecialPowers.spawn(browser, [], async function() { + let textEl = content.document.getElementById("in"); + textEl.focus(); + }); +} + +add_task(async function checkTogglingCaretBrowsing() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, kURL); + await focusInput(tab.linkedBrowser); + + let promiseGotKey = promiseCaretPromptOpened(); + hitF7(); + let prompt = await promiseGotKey; + let doc = prompt.document; + let dialog = doc.getElementById("commonDialog"); + is(dialog.defaultButton, "cancel", "No button should be the default"); + ok( + !doc.getElementById("checkbox").checked, + "Checkbox shouldn't be checked by default." + ); + let promiseDialogUnloaded = BrowserTestUtils.waitForEvent(prompt, "unload"); + + dialog.cancelDialog(); + await promiseDialogUnloaded; + info("Dialog unloaded"); + await waitForFocusOnInput(tab.linkedBrowser); + ok( + !Services.prefs.getBoolPref(kPrefCaretBrowsingOn), + "Caret browsing should still be off after cancelling the dialog." + ); + + promiseGotKey = promiseCaretPromptOpened(); + hitF7(); + prompt = await promiseGotKey; + + doc = prompt.document; + dialog = doc.getElementById("commonDialog"); + is(dialog.defaultButton, "cancel", "No button should be the default"); + ok( + !doc.getElementById("checkbox").checked, + "Checkbox shouldn't be checked by default." + ); + promiseDialogUnloaded = BrowserTestUtils.waitForEvent(prompt, "unload"); + + dialog.acceptDialog(); + await promiseDialogUnloaded; + info("Dialog unloaded"); + await waitForFocusOnInput(tab.linkedBrowser); + ok( + Services.prefs.getBoolPref(kPrefCaretBrowsingOn), + "Caret browsing should be on after accepting the dialog." + ); + + syncToggleCaretNoDialog(false); + + promiseGotKey = promiseCaretPromptOpened(); + hitF7(); + prompt = await promiseGotKey; + doc = prompt.document; + dialog = doc.getElementById("commonDialog"); + + is(dialog.defaultButton, "cancel", "No button should be the default"); + ok( + !doc.getElementById("checkbox").checked, + "Checkbox shouldn't be checked by default." + ); + + promiseDialogUnloaded = BrowserTestUtils.waitForEvent(prompt, "unload"); + dialog.cancelDialog(); + await promiseDialogUnloaded; + info("Dialog unloaded"); + await waitForFocusOnInput(tab.linkedBrowser); + + ok( + !Services.prefs.getBoolPref(kPrefCaretBrowsingOn), + "Caret browsing should still be off after cancelling the dialog." + ); + + Services.prefs.setBoolPref(kPrefShortcutEnabled, true); + Services.prefs.setBoolPref(kPrefWarnOnEnable, true); + Services.prefs.setBoolPref(kPrefCaretBrowsingOn, false); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function toggleCheckboxNoCaretBrowsing() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, kURL); + await focusInput(tab.linkedBrowser); + + let promiseGotKey = promiseCaretPromptOpened(); + hitF7(); + let prompt = await promiseGotKey; + let doc = prompt.document; + let dialog = doc.getElementById("commonDialog"); + is(dialog.defaultButton, "cancel", "No button should be the default"); + let checkbox = doc.getElementById("checkbox"); + ok(!checkbox.checked, "Checkbox shouldn't be checked by default."); + + // Check the box: + checkbox.click(); + + let promiseDialogUnloaded = BrowserTestUtils.waitForEvent(prompt, "unload"); + + // Say no: + dialog.getButton("cancel").click(); + + await promiseDialogUnloaded; + info("Dialog unloaded"); + await waitForFocusOnInput(tab.linkedBrowser); + ok( + !Services.prefs.getBoolPref(kPrefCaretBrowsingOn), + "Caret browsing should still be off." + ); + ok( + !Services.prefs.getBoolPref(kPrefShortcutEnabled), + "Shortcut should now be disabled." + ); + + syncToggleCaretNoDialog(false); + ok( + !Services.prefs.getBoolPref(kPrefShortcutEnabled), + "Shortcut should still be disabled." + ); + + Services.prefs.setBoolPref(kPrefShortcutEnabled, true); + Services.prefs.setBoolPref(kPrefWarnOnEnable, true); + Services.prefs.setBoolPref(kPrefCaretBrowsingOn, false); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function toggleCheckboxWantCaretBrowsing() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, kURL); + await focusInput(tab.linkedBrowser); + + let promiseGotKey = promiseCaretPromptOpened(); + hitF7(); + let prompt = await promiseGotKey; + let doc = prompt.document; + let dialog = doc.getElementById("commonDialog"); + is(dialog.defaultButton, "cancel", "No button should be the default"); + let checkbox = doc.getElementById("checkbox"); + ok(!checkbox.checked, "Checkbox shouldn't be checked by default."); + + // Check the box: + checkbox.click(); + + let promiseDialogUnloaded = BrowserTestUtils.waitForEvent(prompt, "unload"); + + // Say yes: + dialog.acceptDialog(); + await promiseDialogUnloaded; + info("Dialog unloaded"); + await waitForFocusOnInput(tab.linkedBrowser); + ok( + Services.prefs.getBoolPref(kPrefCaretBrowsingOn), + "Caret browsing should now be on." + ); + ok( + Services.prefs.getBoolPref(kPrefShortcutEnabled), + "Shortcut should still be enabled." + ); + ok( + !Services.prefs.getBoolPref(kPrefWarnOnEnable), + "Should no longer warn when enabling." + ); + + syncToggleCaretNoDialog(false); + syncToggleCaretNoDialog(true); + syncToggleCaretNoDialog(false); + + Services.prefs.setBoolPref(kPrefShortcutEnabled, true); + Services.prefs.setBoolPref(kPrefWarnOnEnable, true); + Services.prefs.setBoolPref(kPrefCaretBrowsingOn, false); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/toolkit/content/tests/browser/browser_findbar.js b/toolkit/content/tests/browser/browser_findbar.js new file mode 100644 index 0000000000..b6f116245e --- /dev/null +++ b/toolkit/content/tests/browser/browser_findbar.js @@ -0,0 +1,523 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +ChromeUtils.import("resource://gre/modules/Timer.jsm", this); + +const TEST_PAGE_URI = "data:text/html;charset=utf-8,The letter s."; +// Using 'javascript' schema to bypass E10SUtils.canLoadURIInRemoteType, because +// it does not allow 'data:' URI to be loaded in the parent process. +const E10S_PARENT_TEST_PAGE_URI = + getRootDirectory(gTestPath) + "file_empty.html"; +const TEST_PAGE_URI_WITHIFRAME = + "https://example.com/browser/toolkit/content/tests/browser/file_findinframe.html"; + +/** + * Makes sure that the findbar hotkeys (' and /) event listeners + * are added to the system event group and do not get blocked + * by calling stopPropagation on a keypress event on a page. + */ +add_task(async function test_hotkey_event_propagation() { + info("Ensure hotkeys are not affected by stopPropagation."); + + // Opening new tab + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI + ); + let browser = gBrowser.getBrowserForTab(tab); + let findbar = await gBrowser.getFindBar(); + + // Pressing these keys open the findbar. + const HOTKEYS = ["/", "'"]; + + // Checking if findbar appears when any hotkey is pressed. + for (let key of HOTKEYS) { + is(findbar.hidden, true, "Findbar is hidden now."); + gBrowser.selectedTab = tab; + await SimpleTest.promiseFocus(gBrowser.selectedBrowser); + await BrowserTestUtils.sendChar(key, browser); + is(findbar.hidden, false, "Findbar should not be hidden."); + await closeFindbarAndWait(findbar); + } + + // Stop propagation for all keyboard events. + await SpecialPowers.spawn(browser, [], () => { + const stopPropagation = e => { + e.stopImmediatePropagation(); + }; + let window = content.document.defaultView; + window.addEventListener("keydown", stopPropagation); + window.addEventListener("keypress", stopPropagation); + window.addEventListener("keyup", stopPropagation); + }); + + // Checking if findbar still appears when any hotkey is pressed. + for (let key of HOTKEYS) { + is(findbar.hidden, true, "Findbar is hidden now."); + gBrowser.selectedTab = tab; + await SimpleTest.promiseFocus(gBrowser.selectedBrowser); + await BrowserTestUtils.sendChar(key, browser); + is(findbar.hidden, false, "Findbar should not be hidden."); + await closeFindbarAndWait(findbar); + } + + gBrowser.removeTab(tab); +}); + +add_task(async function test_not_found() { + info("Check correct 'Phrase not found' on new tab"); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI + ); + + // Search for the first word. + await promiseFindFinished("--- THIS SHOULD NEVER MATCH ---", false); + let findbar = gBrowser.getCachedFindBar(); + is( + findbar._findStatusDesc.textContent, + findbar._notFoundStr, + "Findbar status text should be 'Phrase not found'" + ); + + gBrowser.removeTab(tab); +}); + +add_task(async function test_found() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI + ); + + // Search for a string that WILL be found, with 'Highlight All' on + await promiseFindFinished("S", true); + ok( + !gBrowser.getCachedFindBar()._findStatusDesc.textContent, + "Findbar status should be empty" + ); + + gBrowser.removeTab(tab); +}); + +// Setting first findbar to case-sensitive mode should not affect +// new tab find bar. +add_task(async function test_tabwise_case_sensitive() { + let tab1 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI + ); + let findbar1 = await gBrowser.getFindBar(); + + let tab2 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI + ); + let findbar2 = await gBrowser.getFindBar(); + + // Toggle case sensitivity for first findbar + findbar1.getElement("find-case-sensitive").click(); + + gBrowser.selectedTab = tab1; + + // Not found for first tab. + await promiseFindFinished("S", true); + is( + findbar1._findStatusDesc.textContent, + findbar1._notFoundStr, + "Findbar status text should be 'Phrase not found'" + ); + + gBrowser.selectedTab = tab2; + + // But it didn't affect the second findbar. + await promiseFindFinished("S", true); + ok(!findbar2._findStatusDesc.textContent, "Findbar status should be empty"); + + gBrowser.removeTab(tab1); + gBrowser.removeTab(tab2); +}); + +/** + * Navigating from a web page (for example mozilla.org) to an internal page + * (like about:addons) might trigger a change of browser's remoteness. + * 'Remoteness change' means that rendering page content moves from child + * process into the parent process or the other way around. + * This test ensures that findbar properly handles such a change. + */ +add_task(async function test_reinitialization_at_remoteness_change() { + // This test only makes sence in e10s evironment. + if (!gMultiProcessBrowser) { + info("Skipping this test because of non-e10s environment."); + return; + } + + info("Ensure findbar re-initialization at remoteness change."); + + // Load a remote page and trigger findbar construction. + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI + ); + let browser = gBrowser.getBrowserForTab(tab); + let findbar = await gBrowser.getFindBar(); + + // Findbar should operate normally. + await promiseFindFinished("z", false); + is( + findbar._findStatusDesc.textContent, + findbar._notFoundStr, + "Findbar status text should be 'Phrase not found'" + ); + + await promiseFindFinished("s", false); + ok(!findbar._findStatusDesc.textContent, "Findbar status should be empty"); + + // Moving browser into the parent process and reloading sample data. + ok(browser.isRemoteBrowser, "Browser should be remote now."); + await promiseRemotenessChange(tab, false); + let docLoaded = BrowserTestUtils.browserLoaded( + browser, + false, + E10S_PARENT_TEST_PAGE_URI + ); + BrowserTestUtils.loadURI(browser, E10S_PARENT_TEST_PAGE_URI); + await docLoaded; + ok(!browser.isRemoteBrowser, "Browser should not be remote any more."); + browser.contentDocument.body.append("The letter s."); + browser.contentDocument.body.clientHeight; // Force flush. + + // Findbar should keep operating normally after remoteness change. + await promiseFindFinished("z", false); + is( + findbar._findStatusDesc.textContent, + findbar._notFoundStr, + "Findbar status text should be 'Phrase not found'" + ); + + await promiseFindFinished("s", false); + ok(!findbar._findStatusDesc.textContent, "Findbar status should be empty"); + + BrowserTestUtils.removeTab(tab); +}); + +/** + * Ensure that the initial typed characters aren't lost immediately after + * opening the find bar. + */ +add_task(async function e10sLostKeys() { + // This test only makes sence in e10s evironment. + if (!gMultiProcessBrowser) { + info("Skipping this test because of non-e10s environment."); + return; + } + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI + ); + + ok(!gFindBarInitialized, "findbar isn't initialized yet"); + + await gFindBarPromise; + let findBar = gFindBar; + let initialValue = findBar._findField.value; + + await EventUtils.synthesizeAndWaitKey( + "F", + { accelKey: true }, + window, + null, + () => { + // We can't afford to wait for the promise to resolve, by then the + // find bar is visible and focused, so sending characters to the + // content browser wouldn't work. + isnot( + document.activeElement, + findBar._findField, + "findbar is not yet focused" + ); + EventUtils.synthesizeKey("a"); + EventUtils.synthesizeKey("b"); + EventUtils.synthesizeKey("c"); + is( + findBar._findField.value, + initialValue, + "still has initial find query" + ); + } + ); + + await BrowserTestUtils.waitForCondition( + () => findBar._findField.value.length == 3 + ); + is(document.activeElement, findBar._findField, "findbar is now focused"); + is(findBar._findField.value, "abc", "abc fully entered as find query"); + + BrowserTestUtils.removeTab(tab); +}); + +/** + * This test makes sure that keyboard operations still occur + * after the findbar is opened and closed. + */ +add_task(async function test_open_and_close_keys() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "data:text/html,<body style='height: 5000px;'>Hello There</body>" + ); + + await gFindBarPromise; + let findBar = gFindBar; + + is(findBar.hidden, true, "Findbar is hidden now."); + let openedPromise = BrowserTestUtils.waitForEvent(findBar, "findbaropen"); + await EventUtils.synthesizeKey("f", { accelKey: true }); + await openedPromise; + + is(findBar.hidden, false, "Findbar should not be hidden."); + + let closedPromise = BrowserTestUtils.waitForEvent(findBar, "findbarclose"); + await EventUtils.synthesizeKey("KEY_Escape"); + await closedPromise; + + let scrollPromise = BrowserTestUtils.waitForContentEvent( + tab.linkedBrowser, + "scroll" + ); + await EventUtils.synthesizeKey("KEY_ArrowDown"); + await scrollPromise; + + let scrollPosition = await SpecialPowers.spawn( + tab.linkedBrowser, + [], + async function() { + return content.document.body.scrollTop; + } + ); + + ok(scrollPosition > 0, "Scrolled ok to " + scrollPosition); + + BrowserTestUtils.removeTab(tab); +}); + +// This test loads an editable area within an iframe and then +// performs a search. Focusing the editable area should still +// allow keyboard events to be received. +add_task(async function test_hotkey_insubframe() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI_WITHIFRAME + ); + + await gFindBarPromise; + let findBar = gFindBar; + + // Focus the editable area within the frame. + let browser = gBrowser.selectedBrowser; + let frameBC = browser.browsingContext.children[0]; + await SpecialPowers.spawn(frameBC, [], async () => { + content.document.body.focus(); + content.document.defaultView.focus(); + }); + + // Start a find and wait for the findbar to open. + let findBarOpenPromise = BrowserTestUtils.waitForEvent( + gBrowser, + "findbaropen" + ); + EventUtils.synthesizeKey("f", { accelKey: true }); + await findBarOpenPromise; + + // Opening the findbar would have focused the find textbox. + // Focus the editable area again. + let cursorPos = await SpecialPowers.spawn(frameBC, [], async () => { + content.document.body.focus(); + content.document.defaultView.focus(); + return content.getSelection().anchorOffset; + }); + is(cursorPos, 0, "initial cursor position"); + + // Try moving the caret. + await BrowserTestUtils.synthesizeKey("KEY_ArrowRight", {}, frameBC); + + cursorPos = await SpecialPowers.spawn(frameBC, [], async () => { + return content.getSelection().anchorOffset; + }); + is(cursorPos, 1, "cursor moved"); + + await closeFindbarAndWait(findBar); + gBrowser.removeTab(tab); +}); + +/** + * Reloading a page should use the same match case / whole word + * state for the search. + */ +add_task(async function test_preservestate_on_reload() { + for (let stateChange of ["case-sensitive", "entire-word"]) { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "data:text/html,<p>There is a cat named Theo in the kitchen with another cat named Catherine. The two of them are thirsty." + ); + + // Start a find and wait for the findbar to open. + let findBarOpenPromise = BrowserTestUtils.waitForEvent( + gBrowser, + "findbaropen" + ); + EventUtils.synthesizeKey("f", { accelKey: true }); + await findBarOpenPromise; + + let isEntireWord = stateChange == "entire-word"; + + let findbar = await gBrowser.getFindBar(); + + // Find some text. + let promiseMatches = promiseGetMatchCount(findbar); + await promiseFindFinished("The", true); + + let matches = await promiseMatches; + is(matches.current, 1, "Correct match position " + stateChange); + is(matches.total, 7, "Correct number of matches " + stateChange); + + // Turn on the case sensitive or entire word option. + findbar.getElement("find-" + stateChange).click(); + + promiseMatches = promiseGetMatchCount(findbar); + gFindBar.onFindAgainCommand(); + matches = await promiseMatches; + is( + matches.current, + 2, + "Correct match position after state change matches " + stateChange + ); + is( + matches.total, + isEntireWord ? 2 : 3, + "Correct number after state change matches " + stateChange + ); + + // Reload the page. + let loadedPromise = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + true + ); + gBrowser.reload(); + await loadedPromise; + + // Perform a find again. The state should be preserved. + promiseMatches = promiseGetMatchCount(findbar); + gFindBar.onFindAgainCommand(); + matches = await promiseMatches; + is( + matches.current, + 1, + "Correct match position after reload and find again " + stateChange + ); + is( + matches.total, + isEntireWord ? 2 : 3, + "Correct number of matches after reload and find again " + stateChange + ); + + // Turn off the case sensitive or entire word option and find again. + findbar.getElement("find-" + stateChange).click(); + + promiseMatches = promiseGetMatchCount(findbar); + gFindBar.onFindAgainCommand(); + matches = await promiseMatches; + + is( + matches.current, + isEntireWord ? 4 : 2, + "Correct match position after reload and find again reset " + stateChange + ); + is( + matches.total, + 7, + "Correct number of matches after reload and find again reset " + + stateChange + ); + + findbar.clear(); + await closeFindbarAndWait(findbar); + + gBrowser.removeTab(tab); + } +}); + +async function promiseFindFinished(searchText, highlightOn) { + let findbar = await gBrowser.getFindBar(); + findbar.startFind(findbar.FIND_NORMAL); + let highlightElement = findbar.getElement("highlight"); + if (highlightElement.checked != highlightOn) { + highlightElement.click(); + } + return new Promise(resolve => { + executeSoon(() => { + findbar._findField.value = searchText; + + let resultListener; + // When highlighting is on the finder sends a second "FOUND" message after + // the search wraps. This causes timing problems with e10s. waitMore + // forces foundOrTimeout wait for the second "FOUND" message before + // resolving the promise. + let waitMore = highlightOn; + let findTimeout = setTimeout(() => foundOrTimedout(null), 2000); + let foundOrTimedout = function(aData) { + if (aData !== null && waitMore) { + waitMore = false; + return; + } + if (aData === null) { + info("Result listener not called, timeout reached."); + } + clearTimeout(findTimeout); + findbar.browser.finder.removeResultListener(resultListener); + resolve(); + }; + + resultListener = { + onFindResult: foundOrTimedout, + onCurrentSelection() {}, + onMatchesCountResult() {}, + onHighlightFinished() {}, + }; + findbar.browser.finder.addResultListener(resultListener); + findbar._find(); + }); + }); +} + +function promiseGetMatchCount(findbar) { + return new Promise(resolve => { + let resultListener = { + onFindResult() {}, + onCurrentSelection() {}, + onHighlightFinished() {}, + onMatchesCountResult(response) { + if (response.total > 0) { + findbar.browser.finder.removeResultListener(resultListener); + resolve(response); + } + }, + }; + findbar.browser.finder.addResultListener(resultListener); + }); +} + +function promiseRemotenessChange(tab, shouldBeRemote) { + return new Promise(resolve => { + let browser = gBrowser.getBrowserForTab(tab); + tab.addEventListener( + "TabRemotenessChange", + function() { + resolve(); + }, + { once: true } + ); + let remoteType = shouldBeRemote + ? E10SUtils.DEFAULT_REMOTE_TYPE + : E10SUtils.NOT_REMOTE; + gBrowser.updateBrowserRemoteness(browser, { remoteType }); + }); +} diff --git a/toolkit/content/tests/browser/browser_findbar_disabled_manual.js b/toolkit/content/tests/browser/browser_findbar_disabled_manual.js new file mode 100644 index 0000000000..ef37f7dc9a --- /dev/null +++ b/toolkit/content/tests/browser/browser_findbar_disabled_manual.js @@ -0,0 +1,33 @@ +const TEST_PAGE_URI = "data:text/html;charset=utf-8,The letter s."; + +// Disable manual (FAYT) findbar hotkeys. +add_task(async function setup_test_preference() { + await SpecialPowers.pushPrefEnv({ + set: [["accessibility.typeaheadfind.manual", false]], + }); +}); + +// Makes sure that the findbar hotkeys (' and /) have no effect. +add_task(async function test_hotkey_disabled() { + // Opening new tab. + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE_URI + ); + let browser = gBrowser.getBrowserForTab(tab); + let findbar = await gBrowser.getFindBar(); + + // Pressing these keys open the findbar normally. + const HOTKEYS = ["/", "'"]; + + // Make sure no findbar appears when pressed. + for (let key of HOTKEYS) { + is(findbar.hidden, true, "Findbar is hidden now."); + gBrowser.selectedTab = tab; + await SimpleTest.promiseFocus(gBrowser.selectedBrowser); + await BrowserTestUtils.sendChar(key, browser); + is(findbar.hidden, true, "Findbar should still be hidden."); + } + + gBrowser.removeTab(tab); +}); diff --git a/toolkit/content/tests/browser/browser_isSynthetic.js b/toolkit/content/tests/browser/browser_isSynthetic.js new file mode 100644 index 0000000000..f2207cb2b6 --- /dev/null +++ b/toolkit/content/tests/browser/browser_isSynthetic.js @@ -0,0 +1,66 @@ +function LocationChangeListener(browser) { + this.browser = browser; + browser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION); +} + +LocationChangeListener.prototype = { + wasSynthetic: false, + browser: null, + + destroy() { + this.browser.removeProgressListener(this); + }, + + onLocationChange(webProgress, request, location, flags) { + this.wasSynthetic = this.browser.isSyntheticDocument; + }, + + QueryInterface: ChromeUtils.generateQI([ + "nsIWebProgressListener", + "nsISupportsWeakReference", + ]), +}; + +const FILES = gTestPath + .replace("browser_isSynthetic.js", "") + .replace("chrome://mochitests/content/", "http://example.com/"); + +function waitForPageShow(browser) { + return BrowserTestUtils.waitForContentEvent(browser, "pageshow", true); +} + +add_task(async function() { + let tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + let browser = tab.linkedBrowser; + await BrowserTestUtils.browserLoaded(browser); + let listener = new LocationChangeListener(browser); + + is(browser.isSyntheticDocument, false, "Should not be synthetic"); + + let loadPromise = waitForPageShow(browser); + BrowserTestUtils.loadURI(browser, "data:text/html;charset=utf-8,<html/>"); + await loadPromise; + is(listener.wasSynthetic, false, "Should not be synthetic"); + is(browser.isSyntheticDocument, false, "Should not be synthetic"); + + loadPromise = waitForPageShow(browser); + BrowserTestUtils.loadURI(browser, FILES + "empty.png"); + await loadPromise; + is(listener.wasSynthetic, true, "Should be synthetic"); + is(browser.isSyntheticDocument, true, "Should be synthetic"); + + loadPromise = waitForPageShow(browser); + browser.goBack(); + await loadPromise; + is(listener.wasSynthetic, false, "Should not be synthetic"); + is(browser.isSyntheticDocument, false, "Should not be synthetic"); + + loadPromise = waitForPageShow(browser); + browser.goForward(); + await loadPromise; + is(listener.wasSynthetic, true, "Should be synthetic"); + is(browser.isSyntheticDocument, true, "Should be synthetic"); + + listener.destroy(); + gBrowser.removeTab(tab); +}); diff --git a/toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js b/toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js new file mode 100644 index 0000000000..4eda24f531 --- /dev/null +++ b/toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js @@ -0,0 +1,129 @@ +add_task(async function() { + const kPrefName_AutoScroll = "general.autoScroll"; + Services.prefs.setBoolPref(kPrefName_AutoScroll, true); + registerCleanupFunction(() => + Services.prefs.clearUserPref(kPrefName_AutoScroll) + ); + + const kNoKeyEvents = 0; + const kKeyDownEvent = 1; + const kKeyPressEvent = 2; + const kKeyUpEvent = 4; + const kAllKeyEvents = 7; + + var expectedKeyEvents; + var dispatchedKeyEvents; + var key; + + /** + * Encapsulates EventUtils.sendChar(). + */ + function sendChar(aChar) { + key = aChar; + dispatchedKeyEvents = kNoKeyEvents; + EventUtils.sendChar(key); + is( + dispatchedKeyEvents, + expectedKeyEvents, + "unexpected key events were dispatched or not dispatched: " + key + ); + } + + /** + * Encapsulates EventUtils.sendKey(). + */ + function sendKey(aKey) { + key = aKey; + dispatchedKeyEvents = kNoKeyEvents; + EventUtils.sendKey(key); + is( + dispatchedKeyEvents, + expectedKeyEvents, + "unexpected key events were dispatched or not dispatched: " + key + ); + } + + function onKey(aEvent) { + // if (aEvent.target != root && aEvent.target != root.ownerDocument.body) { + // ok(false, "unknown target: " + aEvent.target.tagName); + // return; + // } + + var keyFlag; + switch (aEvent.type) { + case "keydown": + keyFlag = kKeyDownEvent; + break; + case "keypress": + keyFlag = kKeyPressEvent; + break; + case "keyup": + keyFlag = kKeyUpEvent; + break; + default: + ok(false, "Unknown events: " + aEvent.type); + return; + } + dispatchedKeyEvents |= keyFlag; + is(keyFlag, expectedKeyEvents & keyFlag, aEvent.type + " fired: " + key); + } + + var dataUri = 'data:text/html,<body style="height:10000px;"></body>'; + + await BrowserTestUtils.withNewTab(dataUri, async function(browser) { + info("Loaded data URI in new tab"); + await SimpleTest.promiseFocus(browser); + info("Focused selected browser"); + + window.addEventListener("keydown", onKey); + window.addEventListener("keypress", onKey); + window.addEventListener("keyup", onKey); + registerCleanupFunction(() => { + window.removeEventListener("keydown", onKey); + window.removeEventListener("keypress", onKey); + window.removeEventListener("keyup", onKey); + }); + + // Test whether the key events are handled correctly under normal condition + expectedKeyEvents = kAllKeyEvents; + sendChar("A"); + + // Start autoscrolling by middle button click on the page + info("Creating popup shown promise"); + let shownPromise = BrowserTestUtils.waitForEvent( + window, + "popupshown", + false, + event => event.originalTarget.className == "autoscroller" + ); + await BrowserTestUtils.synthesizeMouseAtPoint( + 10, + 10, + { button: 1 }, + gBrowser.selectedBrowser + ); + info("Waiting for autoscroll popup to show"); + await shownPromise; + + // Most key events should be eaten by the browser. + expectedKeyEvents = kNoKeyEvents; + sendChar("A"); + sendKey("DOWN"); + sendKey("RETURN"); + sendKey("RETURN"); + sendKey("HOME"); + sendKey("END"); + sendKey("TAB"); + sendKey("RETURN"); + + // Finish autoscrolling by ESC key. Note that only keydown and keypress + // events are eaten because keyup event is fired *after* the autoscrolling + // is finished. + expectedKeyEvents = kKeyUpEvent; + sendKey("ESCAPE"); + + // Test whether the key events are handled correctly under normal condition + expectedKeyEvents = kAllKeyEvents; + sendChar("A"); + }); +}); diff --git a/toolkit/content/tests/browser/browser_label_textlink.js b/toolkit/content/tests/browser/browser_label_textlink.js new file mode 100644 index 0000000000..e6b967cbe9 --- /dev/null +++ b/toolkit/content/tests/browser/browser_label_textlink.js @@ -0,0 +1,63 @@ +add_task(async function() { + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:preferences" }, + async function(browser) { + let newTabURL = "http://www.example.com/"; + await SpecialPowers.spawn(browser, [newTabURL], async function( + newTabURL + ) { + let doc = content.document; + let label = doc.createXULElement("label", { is: "text-link" }); + label.href = newTabURL; + label.id = "textlink-test"; + label.textContent = "click me"; + doc.body.append(label); + }); + + // Test that click will open tab in foreground. + let awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser, newTabURL); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#textlink-test", + {}, + browser + ); + let newTab = await awaitNewTab; + is( + newTab.linkedBrowser, + gBrowser.selectedBrowser, + "selected tab should be example page" + ); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + + // Test that ctrl+shift+click/meta+shift+click will open tab in background. + awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser, newTabURL); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#textlink-test", + { ctrlKey: true, metaKey: true, shiftKey: true }, + browser + ); + await awaitNewTab; + is( + gBrowser.selectedBrowser, + browser, + "selected tab should be original tab" + ); + BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]); + + // Middle-clicking should open tab in foreground. + awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser, newTabURL); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#textlink-test", + { button: 1 }, + browser + ); + newTab = await awaitNewTab; + is( + newTab.linkedBrowser, + gBrowser.selectedBrowser, + "selected tab should be example page" + ); + BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]); + } + ); +}); diff --git a/toolkit/content/tests/browser/browser_license_links.js b/toolkit/content/tests/browser/browser_license_links.js new file mode 100644 index 0000000000..3eff69ba75 --- /dev/null +++ b/toolkit/content/tests/browser/browser_license_links.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Verify that we can reach about:rights and about:buildconfig using links + * from about:license. + */ +add_task(async function check_links() { + await BrowserTestUtils.withNewTab("about:license", async browser => { + for (let otherPage of ["about:rights", "about:buildconfig"]) { + let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, otherPage); + await BrowserTestUtils.synthesizeMouse( + `a[href='${otherPage}']`, + 2, + 2, + { accelKey: true }, + browser + ); + info("Clicked " + otherPage + " link"); + let tab = await tabPromise; + ok(true, otherPage + " tab opened correctly"); + BrowserTestUtils.removeTab(tab); + } + }); +}); diff --git a/toolkit/content/tests/browser/browser_mediaStreamPlayback.html b/toolkit/content/tests/browser/browser_mediaStreamPlayback.html new file mode 100644 index 0000000000..09685d488e --- /dev/null +++ b/toolkit/content/tests/browser/browser_mediaStreamPlayback.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html> +<body> +<video id="v" controls></video> +<script> +const v = document.getElementById("v"); + +function audioTrack() { + const ctx = new AudioContext(), oscillator = ctx.createOscillator(); + const dst = oscillator.connect(ctx.createMediaStreamDestination()); + oscillator.start(); + return dst.stream.getAudioTracks()[0]; +} + +function videoTrack(width = 640, height = 480) { + const canvas = Object.assign(document.createElement("canvas"), {width, height}); + canvas.getContext('2d').fillRect(0, 0, width, height); + return canvas.captureStream(10).getVideoTracks()[0]; +} + +onload = () => v.srcObject = new MediaStream([videoTrack(), audioTrack()]); +</script> +</body> +</html> diff --git a/toolkit/content/tests/browser/browser_mediaStreamPlaybackWithoutAudio.html b/toolkit/content/tests/browser/browser_mediaStreamPlaybackWithoutAudio.html new file mode 100644 index 0000000000..fbc9fcb033 --- /dev/null +++ b/toolkit/content/tests/browser/browser_mediaStreamPlaybackWithoutAudio.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<body> +<video id="v" controls></video> +<script> +const v = document.getElementById("v"); + +function videoTrack(width = 640, height = 480) { + const canvas = Object.assign(document.createElement("canvas"), {width, height}); + canvas.getContext('2d').fillRect(0, 0, width, height); + return canvas.captureStream(10).getVideoTracks()[0]; +} + +onload = () => v.srcObject = new MediaStream([videoTrack()]); +</script> +</body> +</html> diff --git a/toolkit/content/tests/browser/browser_media_wakelock.js b/toolkit/content/tests/browser/browser_media_wakelock.js new file mode 100644 index 0000000000..dee4b1a26f --- /dev/null +++ b/toolkit/content/tests/browser/browser_media_wakelock.js @@ -0,0 +1,160 @@ +/** + * Test whether the wakelock state is correct under different situations. However, + * the lock state of power manager doesn't equal to the actual platform lock. + * Now we don't have any way to detect whether platform lock is set correctly or + * not, but we can at least make sure the specific topic's state in power manager + * is correct. + */ +"use strict"; + +// Import this in order to use `triggerPictureInPicture()`. +/* import-globals-from ../../../../toolkit/components/pictureinpicture/tests/head.js */ +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/toolkit/components/pictureinpicture/tests/head.js", + this +); + +const LOCATION = "https://example.com/browser/toolkit/content/tests/browser/"; +const AUDIO_WAKELOCK_NAME = "audio-playing"; +const VIDEO_WAKELOCK_NAME = "video-playing"; + +add_task(async function testCheckWakelockWhenChangeTabVisibility() { + await checkWakelockWhenChangeTabVisibility({ + description: "playing video", + url: "file_video.html", + lockAudio: true, + lockVideo: true, + }); + await checkWakelockWhenChangeTabVisibility({ + description: "playing muted video", + url: "file_video.html", + additionalParams: { + muted: true, + }, + lockAudio: false, + lockVideo: true, + }); + await checkWakelockWhenChangeTabVisibility({ + description: "playing volume=0 video", + url: "file_video.html", + additionalParams: { + volume: 0.0, + }, + lockAudio: false, + lockVideo: true, + }); + await checkWakelockWhenChangeTabVisibility({ + description: "playing video without audio in it", + url: "file_videoWithoutAudioTrack.html", + lockAudio: false, + lockVideo: false, + }); + await checkWakelockWhenChangeTabVisibility({ + description: "playing audio in video element", + url: "file_videoWithAudioOnly.html", + lockAudio: true, + lockVideo: false, + }); + await checkWakelockWhenChangeTabVisibility({ + description: "playing audio in audio element", + url: "file_mediaPlayback2.html", + lockAudio: true, + lockVideo: false, + }); + await checkWakelockWhenChangeTabVisibility({ + description: "playing video from media stream with audio and video tracks", + url: "browser_mediaStreamPlayback.html", + lockAudio: true, + lockVideo: true, + }); + await checkWakelockWhenChangeTabVisibility({ + description: "playing video from media stream without audio track", + url: "browser_mediaStreamPlaybackWithoutAudio.html", + lockAudio: true, + lockVideo: true, + }); +}); + +/** + * Following are helper functions. + */ +async function checkWakelockWhenChangeTabVisibility({ + description, + url, + additionalParams, + lockAudio, + lockVideo, +}) { + const originalTab = gBrowser.selectedTab; + info(`start a new tab for '${description}'`); + const mediaTab = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + LOCATION + url + ); + + info(`wait for media starting playing`); + await waitUntilVideoStarted(mediaTab, additionalParams); + await waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, { + needLock: lockAudio, + isForegroundLock: true, + }); + await waitForExpectedWakeLockState(VIDEO_WAKELOCK_NAME, { + needLock: lockVideo, + isForegroundLock: true, + }); + + info(`switch media tab to background`); + await BrowserTestUtils.switchTab(window.gBrowser, originalTab); + await waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, { + needLock: lockAudio, + isForegroundLock: false, + }); + await waitForExpectedWakeLockState(VIDEO_WAKELOCK_NAME, { + needLock: lockVideo, + isForegroundLock: false, + }); + + info(`switch media tab to foreground again`); + await BrowserTestUtils.switchTab(window.gBrowser, mediaTab); + await waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, { + needLock: lockAudio, + isForegroundLock: true, + }); + await waitForExpectedWakeLockState(VIDEO_WAKELOCK_NAME, { + needLock: lockVideo, + isForegroundLock: true, + }); + + info(`remove tab`); + if (mediaTab.PIPWindow) { + await BrowserTestUtils.closeWindow(mediaTab.PIPWindow); + } + BrowserTestUtils.removeTab(mediaTab); +} + +async function waitUntilVideoStarted(tab, { muted, volume } = {}) { + await SpecialPowers.spawn( + tab.linkedBrowser, + [muted, volume], + async (muted, volume) => { + const video = content.document.getElementById("v"); + if (!video) { + ok(false, "can't get media element!"); + return; + } + if (muted) { + video.muted = muted; + } + if (volume !== undefined) { + video.volume = volume; + } + ok( + await video.play().then( + () => true, + () => false + ), + `video started playing.` + ); + } + ); +} diff --git a/toolkit/content/tests/browser/browser_media_wakelock_PIP.js b/toolkit/content/tests/browser/browser_media_wakelock_PIP.js new file mode 100644 index 0000000000..d75c1ef1d7 --- /dev/null +++ b/toolkit/content/tests/browser/browser_media_wakelock_PIP.js @@ -0,0 +1,156 @@ +/** + * Test the wakelock usage for video being used in the picture-in-picuture (PIP) + * mode. When video is playing in PIP window, we would always request a video + * wakelock, and request audio wakelock only when video is audible. + */ +add_task(async function testCheckWakelockForPIPVideo() { + await checkWakelockWhenChangeTabVisibility({ + description: "playing a PIP video", + lockAudio: true, + lockVideo: true, + }); + await checkWakelockWhenChangeTabVisibility({ + description: "playing a muted PIP video", + additionalParams: { + muted: true, + }, + lockAudio: false, + lockVideo: true, + }); + await checkWakelockWhenChangeTabVisibility({ + description: "playing a volume=0 PIP video", + additionalParams: { + volume: 0.0, + }, + lockAudio: false, + lockVideo: true, + }); +}); + +/** + * Following are helper functions and variables. + */ +const PAGE_URL = + "https://example.com/browser/toolkit/content/tests/browser/file_video.html"; +const AUDIO_WAKELOCK_NAME = "audio-playing"; +const VIDEO_WAKELOCK_NAME = "video-playing"; +const TEST_VIDEO_ID = "v"; + +// Import this in order to use `triggerPictureInPicture()`. +/* import-globals-from ../../../../toolkit/components/pictureinpicture/tests/head.js */ +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/toolkit/components/pictureinpicture/tests/head.js", + this +); + +async function checkWakelockWhenChangeTabVisibility({ + description, + additionalParams, + lockAudio, + lockVideo, +}) { + const originalTab = gBrowser.selectedTab; + info(`start a new tab for '${description}'`); + const tab = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + PAGE_URL + ); + + info(`wait for PIP video starting playing`); + await startPIPVideo(tab, additionalParams); + await waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, { + needLock: lockAudio, + isForegroundLock: true, + }); + await waitForExpectedWakeLockState(VIDEO_WAKELOCK_NAME, { + needLock: lockVideo, + isForegroundLock: true, + }); + + info( + `switch tab to background and still own foreground locks due to visible PIP video` + ); + await BrowserTestUtils.switchTab(window.gBrowser, originalTab); + await waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, { + needLock: lockAudio, + isForegroundLock: true, + }); + await waitForExpectedWakeLockState(VIDEO_WAKELOCK_NAME, { + needLock: lockVideo, + isForegroundLock: true, + }); + + info(`pausing PIP video should release all locks`); + await pausePIPVideo(tab); + await waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, { + needLock: false, + }); + await waitForExpectedWakeLockState(VIDEO_WAKELOCK_NAME, { + needLock: false, + }); + + info(`resuming PIP video should request locks again`); + await resumePIPVideo(tab); + await waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, { + needLock: lockAudio, + isForegroundLock: true, + }); + await waitForExpectedWakeLockState(VIDEO_WAKELOCK_NAME, { + needLock: lockVideo, + isForegroundLock: true, + }); + + info(`switch tab to foreground again`); + await BrowserTestUtils.switchTab(window.gBrowser, tab); + await waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, { + needLock: lockAudio, + isForegroundLock: true, + }); + await waitForExpectedWakeLockState(VIDEO_WAKELOCK_NAME, { + needLock: lockVideo, + isForegroundLock: true, + }); + + info(`remove tab`); + await BrowserTestUtils.closeWindow(tab.PIPWindow); + BrowserTestUtils.removeTab(tab); +} + +async function startPIPVideo(tab, { muted, volume } = {}) { + tab.PIPWindow = await triggerPictureInPicture( + tab.linkedBrowser, + TEST_VIDEO_ID + ); + await SpecialPowers.spawn( + tab.linkedBrowser, + [muted, volume, TEST_VIDEO_ID], + async (muted, volume, Id) => { + const video = content.document.getElementById(Id); + if (muted) { + video.muted = muted; + } + if (volume !== undefined) { + video.volume = volume; + } + ok( + await video.play().then( + () => true, + () => false + ), + `video started playing.` + ); + } + ); +} + +function pausePIPVideo(tab) { + return SpecialPowers.spawn(tab.linkedBrowser, [TEST_VIDEO_ID], Id => { + content.document.getElementById(Id).pause(); + }); +} + +function resumePIPVideo(tab) { + return SpecialPowers.spawn(tab.linkedBrowser, [TEST_VIDEO_ID], async Id => { + await content.document.getElementById(Id).play(); + }); +} diff --git a/toolkit/content/tests/browser/browser_media_wakelock_webaudio.js b/toolkit/content/tests/browser/browser_media_wakelock_webaudio.js new file mode 100644 index 0000000000..7c40b5fe1a --- /dev/null +++ b/toolkit/content/tests/browser/browser_media_wakelock_webaudio.js @@ -0,0 +1,127 @@ +/** + * Test if wakelock can be required correctly when we play web audio. The + * wakelock should only be required when web audio is audible. + */ + +const AUDIO_WAKELOCK_NAME = "audio-playing"; +const VIDEO_WAKELOCK_NAME = "video-playing"; + +add_task(async function testCheckAudioWakelockWhenChangeTabVisibility() { + await checkWakelockWhenChangeTabVisibility({ + description: "playing audible web audio", + needLock: true, + }); + await checkWakelockWhenChangeTabVisibility({ + description: "suspended web audio", + additionalParams: { + suspend: true, + }, + needLock: false, + }); +}); + +add_task( + async function testBrieflyAudibleAudioContextReleasesAudioWakeLockWhenInaudible() { + const tab = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + "about:blank" + ); + + info(`make a short noise on web audio`); + await Promise.all([ + // As the sound would only happen for a really short period, calling + // checking wakelock first helps to ensure that we won't miss that moment. + waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, { + needLock: true, + isForegroundLock: true, + }), + createWebAudioDocument(tab, { stopTimeOffset: 0.1 }), + ]); + await ensureNeverAcquireVideoWakelock(); + + info(`audio wakelock should be released after web audio becomes silent`); + await waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, false, { + needLock: false, + }); + await ensureNeverAcquireVideoWakelock(); + + await BrowserTestUtils.removeTab(tab); + } +); + +/** + * Following are helper functions. + */ +async function checkWakelockWhenChangeTabVisibility({ + description, + additionalParams, + needLock, + elementIdForEnteringPIPMode, +}) { + const originalTab = gBrowser.selectedTab; + info(`start a new tab for '${description}'`); + const mediaTab = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + "about:blank" + ); + await createWebAudioDocument(mediaTab, additionalParams); + await waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, { + needLock, + isForegroundLock: true, + }); + await ensureNeverAcquireVideoWakelock(); + + info(`switch media tab to background`); + await BrowserTestUtils.switchTab(window.gBrowser, originalTab); + await waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, { + needLock, + isForegroundLock: false, + }); + await ensureNeverAcquireVideoWakelock(); + + info(`switch media tab to foreground again`); + await BrowserTestUtils.switchTab(window.gBrowser, mediaTab); + await waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, { + needLock, + isForegroundLock: true, + }); + await ensureNeverAcquireVideoWakelock(); + + info(`remove media tab`); + BrowserTestUtils.removeTab(mediaTab); +} + +function createWebAudioDocument(tab, { stopTimeOffset, suspend } = {}) { + return SpecialPowers.spawn( + tab.linkedBrowser, + [suspend, stopTimeOffset], + async (suspend, stopTimeOffset) => { + // Create an oscillatorNode to produce sound. + content.ac = new content.AudioContext(); + const ac = content.ac; + const dest = ac.destination; + const source = new content.OscillatorNode(ac); + source.start(ac.currentTime); + source.connect(dest); + + if (stopTimeOffset) { + source.stop(ac.currentTime + 0.1); + } + + if (suspend) { + await content.ac.suspend(); + } else { + while (ac.state != "running") { + info(`wait until AudioContext starts running`); + await new Promise(r => (ac.onstatechange = r)); + } + info("AudioContext is running"); + } + } + ); +} + +function ensureNeverAcquireVideoWakelock() { + // Web audio won't play any video, we never need video wakelock. + return waitForExpectedWakeLockState(VIDEO_WAKELOCK_NAME, { needLock: false }); +} diff --git a/toolkit/content/tests/browser/browser_quickfind_editable.js b/toolkit/content/tests/browser/browser_quickfind_editable.js new file mode 100644 index 0000000000..7ece285602 --- /dev/null +++ b/toolkit/content/tests/browser/browser_quickfind_editable.js @@ -0,0 +1,59 @@ +const PAGE = + "data:text/html,<div contenteditable>foo</div><input><textarea></textarea>"; +const DESIGNMODE_PAGE = + "data:text/html,<body onload='document.designMode=\"on\";'>"; +const HOTKEYS = ["/", "'"]; + +async function test_hotkeys(browser, expected) { + let findbar = await gBrowser.getFindBar(); + for (let key of HOTKEYS) { + is(findbar.hidden, true, "findbar is hidden"); + await BrowserTestUtils.sendChar(key, gBrowser.selectedBrowser); + is( + findbar.hidden, + expected, + "findbar should" + (expected ? "" : " not") + " be hidden" + ); + if (!expected) { + await closeFindbarAndWait(findbar); + } + } +} + +async function focus_element(browser, query) { + await SpecialPowers.spawn(browser, [query], async function focus(query) { + let element = content.document.querySelector(query); + element.focus(); + }); +} + +add_task(async function test_hotkey_on_editable_element() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: PAGE, + }, + async function do_tests(browser) { + await test_hotkeys(browser, false); + const ELEMENTS = ["div", "input", "textarea"]; + for (let elem of ELEMENTS) { + await focus_element(browser, elem); + await test_hotkeys(browser, true); + await focus_element(browser, ":root"); + await test_hotkeys(browser, false); + } + } + ); +}); + +add_task(async function test_hotkey_on_designMode_document() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: DESIGNMODE_PAGE, + }, + async function do_tests(browser) { + await test_hotkeys(browser, true); + } + ); +}); diff --git a/toolkit/content/tests/browser/browser_remoteness_change_listeners.js b/toolkit/content/tests/browser/browser_remoteness_change_listeners.js new file mode 100644 index 0000000000..34c3c4112e --- /dev/null +++ b/toolkit/content/tests/browser/browser_remoteness_change_listeners.js @@ -0,0 +1,39 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Check that adding progress listeners to a browser doesn't break things + * when switching the remoteness of that browser. + */ +add_task(async function test_remoteness_switch_listeners() { + await BrowserTestUtils.withNewTab("about:support", async function(browser) { + let wpl; + let navigated = new Promise(resolve => { + wpl = { + onLocationChange() { + is(browser.currentURI.spec, "https://example.com/"); + if (browser.currentURI?.spec == "https://example.com/") { + resolve(); + } + }, + QueryInterface: ChromeUtils.generateQI([ + Ci.nsISupportsWeakReference, + Ci.nsIWebProgressListener2, + Ci.nsIWebProgressListener, + ]), + }; + browser.addProgressListener(wpl); + }); + + let loaded = BrowserTestUtils.browserLoaded( + browser, + null, + "https://example.com/" + ); + BrowserTestUtils.loadURI(browser, "https://example.com/"); + await Promise.all([loaded, navigated]); + browser.removeProgressListener(wpl); + }); +}); diff --git a/toolkit/content/tests/browser/browser_resume_bkg_video_on_tab_hover.js b/toolkit/content/tests/browser/browser_resume_bkg_video_on_tab_hover.js new file mode 100644 index 0000000000..f8087f6a60 --- /dev/null +++ b/toolkit/content/tests/browser/browser_resume_bkg_video_on_tab_hover.js @@ -0,0 +1,154 @@ +const PAGE = + "https://example.com/browser/toolkit/content/tests/browser/file_silentAudioTrack.html"; + +async function check_video_decoding_state(args) { + let video = content.document.getElementById("autoplay"); + if (!video) { + ok(false, "Can't get the video element!"); + } + + let isSuspended = args.suspend; + let reload = args.reload; + + if (reload) { + // It is too late to register event handlers when playback is half + // way done. Let's start playback from the beginning so we won't + // miss any events. + video.load(); + video.play(); + } + + let state = isSuspended ? "suspended" : "resumed"; + let event = isSuspended ? "mozentervideosuspend" : "mozexitvideosuspend"; + return new Promise(resolve => { + video.addEventListener( + event, + function() { + ok(true, `Video decoding is ${state}.`); + resolve(); + }, + { once: true } + ); + }); +} + +async function check_should_send_unselected_tab_hover_msg(browser) { + info("did not update the value now, wait until it changes."); + if (browser.shouldHandleUnselectedTabHover) { + ok( + true, + "Should send unselected tab hover msg, someone is listening for it." + ); + return true; + } + return BrowserTestUtils.waitForCondition( + () => browser.shouldHandleUnselectedTabHover, + "Should send unselected tab hover msg, someone is listening for it." + ); +} + +async function check_should_not_send_unselected_tab_hover_msg(browser) { + info("did not update the value now, wait until it changes."); + return BrowserTestUtils.waitForCondition( + () => !browser.shouldHandleUnselectedTabHover, + "Should not send unselected tab hover msg, no one is listening for it." + ); +} + +function get_video_decoding_suspend_promise(browser, reload) { + let suspend = true; + return SpecialPowers.spawn( + browser, + [{ suspend, reload }], + check_video_decoding_state + ); +} + +function get_video_decoding_resume_promise(browser) { + let suspend = false; + let reload = false; + return ContentTask.spawn( + browser, + { suspend, reload }, + check_video_decoding_state + ); +} + +/** + * Because of bug1029451, we can't receive "mouseover" event correctly when + * we disable non-test mouse event. Therefore, we can't synthesize mouse event + * to simulate cursor hovering, so we temporarily use a hacky way to resume and + * suspend video decoding. + */ +function cursor_hover_over_tab_and_resume_video_decoding(browser) { + // TODO : simulate cursor hovering over the tab after fixing bug1029451. + browser.unselectedTabHover(true /* hover */); +} + +function cursor_leave_tab_and_suspend_video_decoding(browser) { + // TODO : simulate cursor leaveing the tab after fixing bug1029451. + browser.unselectedTabHover(false /* leave */); +} + +add_task(async function setup_test_preference() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.block-autoplay-until-in-foreground", false], + ["media.suspend-bkgnd-video.enabled", true], + ["media.suspend-bkgnd-video.delay-ms", 0], + ["media.resume-bkgnd-video-on-tabhover", true], + ], + }); +}); + +/** + * TODO : add the following user-level tests after fixing bug1029451. + * test1 - only affect the unselected tab + * test2 - only affect the tab with suspended video + */ +add_task(async function resume_and_suspend_background_video_decoding() { + info("- open new background tab -"); + let tab = BrowserTestUtils.addTab(window.gBrowser, "about:blank"); + let browser = tab.linkedBrowser; + await BrowserTestUtils.browserLoaded(browser); + + info("- before loading media, we shoudn't send the tab hover msg for tab -"); + await check_should_not_send_unselected_tab_hover_msg(browser); + BrowserTestUtils.loadURI(browser, PAGE); + await BrowserTestUtils.browserLoaded(browser); + + info("- should suspend background video decoding -"); + await get_video_decoding_suspend_promise(browser, true); + await check_should_send_unselected_tab_hover_msg(browser); + + info("- when cursor is hovering over the tab, resuming the video decoding -"); + let promise = get_video_decoding_resume_promise(browser); + await cursor_hover_over_tab_and_resume_video_decoding(browser); + await promise; + await check_should_send_unselected_tab_hover_msg(browser); + + info("- when cursor leaves the tab, suspending the video decoding -"); + promise = get_video_decoding_suspend_promise(browser); + await cursor_leave_tab_and_suspend_video_decoding(browser); + await promise; + await check_should_send_unselected_tab_hover_msg(browser); + + info("- select video's owner tab as foreground tab, should resume video -"); + promise = get_video_decoding_resume_promise(browser); + await BrowserTestUtils.switchTab(window.gBrowser, tab); + await promise; + await check_should_send_unselected_tab_hover_msg(browser); + + info("- video's owner tab goes to background again, should suspend video -"); + promise = get_video_decoding_suspend_promise(browser); + let blankTab = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + "about:blank" + ); + await promise; + await check_should_send_unselected_tab_hover_msg(browser); + + info("- remove tabs -"); + BrowserTestUtils.removeTab(tab); + BrowserTestUtils.removeTab(blankTab); +}); diff --git a/toolkit/content/tests/browser/browser_saveImageURL.js b/toolkit/content/tests/browser/browser_saveImageURL.js new file mode 100644 index 0000000000..764e7e9aa5 --- /dev/null +++ b/toolkit/content/tests/browser/browser_saveImageURL.js @@ -0,0 +1,75 @@ +"use strict"; + +const IMAGE_PAGE = + "https://example.com/browser/toolkit/content/tests/browser/image_page.html"; + +var MockFilePicker = SpecialPowers.MockFilePicker; + +MockFilePicker.init(window); +MockFilePicker.returnValue = MockFilePicker.returnCancel; + +registerCleanupFunction(function() { + MockFilePicker.cleanup(); +}); + +function waitForFilePicker() { + return new Promise(resolve => { + MockFilePicker.showCallback = () => { + MockFilePicker.showCallback = null; + ok(true, "Saw the file picker"); + resolve(); + }; + }); +} + +/** + * Test that internalSave works when saving an image like the context menu does. + */ +add_task(async function preferred_API() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: IMAGE_PAGE, + }, + async function(browser) { + let url = await SpecialPowers.spawn(browser, [], async function() { + let image = content.document.getElementById("image"); + return image.href; + }); + + let filePickerPromise = waitForFilePicker(); + internalSave( + url, + null, // document + "image.jpg", + null, // content disposition + "image/jpeg", + true, // bypass cache + null, // dialog title key + null, // chosen data + null, // no referrer info + null, // no document + false, // don't skip the filename prompt + null, // cache key + false, // not private. + gBrowser.contentPrincipal + ); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { + let channel = docShell.currentDocumentChannel; + if (channel) { + todo( + channel.QueryInterface(Ci.nsIHttpChannelInternal) + .channelIsForDownload + ); + + // Throttleable is the only class flag assigned to downloads. + todo( + channel.QueryInterface(Ci.nsIClassOfService).classFlags == + Ci.nsIClassOfService.Throttleable + ); + } + }); + await filePickerPromise; + } + ); +}); diff --git a/toolkit/content/tests/browser/browser_save_resend_postdata.js b/toolkit/content/tests/browser/browser_save_resend_postdata.js new file mode 100644 index 0000000000..c519c19880 --- /dev/null +++ b/toolkit/content/tests/browser/browser_save_resend_postdata.js @@ -0,0 +1,169 @@ +/* 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/. */ + +var MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.init(window); + +/** + * Test for bug 471962 <https://bugzilla.mozilla.org/show_bug.cgi?id=471962>: + * When saving an inner frame as file only, the POST data of the outer page is + * sent to the address of the inner page. + * + * Test for bug 485196 <https://bugzilla.mozilla.org/show_bug.cgi?id=485196>: + * Web page generated by POST is retried as GET when Save Frame As used, and the + * page is no longer in the cache. + */ +function test() { + waitForExplicitFinish(); + + BrowserTestUtils.loadURI( + gBrowser, + "http://mochi.test:8888/browser/toolkit/content/tests/browser/data/post_form_outer.sjs" + ); + + gBrowser.addEventListener("pageshow", function pageShown(event) { + if (event.target.location == "about:blank") { + return; + } + gBrowser.removeEventListener("pageshow", pageShown); + + // Submit the form in the outer page, then wait for both the outer + // document and the inner frame to be loaded again. + gBrowser.addEventListener("DOMContentLoaded", handleOuterSubmit); + gBrowser.contentDocument.getElementById("postForm").submit(); + }); + + var framesLoaded = 0; + var innerFrame; + + function handleOuterSubmit() { + if (++framesLoaded < 2) { + return; + } + + gBrowser.removeEventListener("DOMContentLoaded", handleOuterSubmit); + + innerFrame = gBrowser.contentDocument.getElementById("innerFrame"); + + // Submit the form in the inner page. + gBrowser.addEventListener("DOMContentLoaded", handleInnerSubmit); + innerFrame.contentDocument.getElementById("postForm").submit(); + } + + function handleInnerSubmit() { + gBrowser.removeEventListener("DOMContentLoaded", handleInnerSubmit); + + // Create the folder the page will be saved into. + var destDir = createTemporarySaveDirectory(); + var file = destDir.clone(); + file.append("no_default_file_name"); + MockFilePicker.setFiles([file]); + MockFilePicker.showCallback = function(fp) { + MockFilePicker.filterIndex = 1; // kSaveAsType_URL + }; + + mockTransferCallback = onTransferComplete; + mockTransferRegisterer.register(); + + registerCleanupFunction(function() { + mockTransferRegisterer.unregister(); + MockFilePicker.cleanup(); + destDir.remove(true); + }); + + var docToSave = innerFrame.contentDocument; + // We call internalSave instead of saveDocument to bypass the history + // cache. + internalSave( + docToSave.location.href, + docToSave, + null, + null, + docToSave.contentType, + false, + null, + null, + docToSave.referrer ? makeURI(docToSave.referrer) : null, + docToSave, + false, + null + ); + } + + function onTransferComplete(downloadSuccess) { + ok( + downloadSuccess, + "The inner frame should have been downloaded successfully" + ); + + // Read the entire saved file. + var file = MockFilePicker.getNsIFile(); + var fileContents = readShortFile(file); + + // Check if outer POST data is found (bug 471962). + is( + fileContents.indexOf("inputfield=outer"), + -1, + "The saved inner frame does not contain outer POST data" + ); + + // Check if inner POST data is found (bug 485196). + isnot( + fileContents.indexOf("inputfield=inner"), + -1, + "The saved inner frame was generated using the correct POST data" + ); + + finish(); + } +} + +/* import-globals-from common/mockTransfer.js */ +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", + this +); + +function createTemporarySaveDirectory() { + var saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile); + saveDir.append("testsavedir"); + if (!saveDir.exists()) { + saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + } + return saveDir; +} + +/** + * Reads the contents of the provided short file (up to 1 MiB). + * + * @param aFile + * nsIFile object pointing to the file to be read. + * + * @return + * String containing the raw octets read from the file. + */ +function readShortFile(aFile) { + var inputStream = Cc[ + "@mozilla.org/network/file-input-stream;1" + ].createInstance(Ci.nsIFileInputStream); + inputStream.init(aFile, -1, 0, 0); + try { + var scrInputStream = Cc[ + "@mozilla.org/scriptableinputstream;1" + ].createInstance(Ci.nsIScriptableInputStream); + scrInputStream.init(inputStream); + try { + // Assume that the file is much shorter than 1 MiB. + return scrInputStream.read(1048576); + } finally { + // Close the scriptable stream after reading, even if the operation + // failed. + scrInputStream.close(); + } + } finally { + // Close the stream after reading, if it is still open, even if the read + // operation failed. + inputStream.close(); + } +} diff --git a/toolkit/content/tests/browser/browser_suspend_videos_outside_viewport.js b/toolkit/content/tests/browser/browser_suspend_videos_outside_viewport.js new file mode 100644 index 0000000000..333bd0d41c --- /dev/null +++ b/toolkit/content/tests/browser/browser_suspend_videos_outside_viewport.js @@ -0,0 +1,39 @@ +/** + * This test is used to ensure we suspend video decoding if video is not in the + * viewport. + */ +"use strict"; + +const PAGE = + "https://example.com/browser/toolkit/content/tests/browser/file_outside_viewport_videos.html"; + +async function test_suspend_video_decoding() { + let videos = content.document.getElementsByTagName("video"); + for (let video of videos) { + info(`- start video on the ${video.id} side and outside the viewport -`); + await video.play(); + ok(true, `video started playing`); + ok(video.isVideoDecodingSuspended, `video decoding is suspended`); + } +} + +add_task(async function setup_test_preference() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.suspend-bkgnd-video.enabled", true], + ["media.suspend-bkgnd-video.delay-ms", 0], + ], + }); +}); + +add_task(async function start_test() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: PAGE, + }, + async browser => { + await SpecialPowers.spawn(browser, [], test_suspend_video_decoding); + } + ); +}); diff --git a/toolkit/content/tests/browser/common/mockTransfer.js b/toolkit/content/tests/browser/common/mockTransfer.js new file mode 100644 index 0000000000..f4afa44903 --- /dev/null +++ b/toolkit/content/tests/browser/common/mockTransfer.js @@ -0,0 +1,85 @@ +/* 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/. */ + +Services.scriptloader.loadSubScript( + "chrome://mochikit/content/tests/SimpleTest/MockObjects.js", + this +); + +var mockTransferCallback; + +/** + * This "transfer" object implementation continues the currently running test + * when the download is completed, reporting true for success or false for + * failure as the first argument of the testRunner.continueTest function. + */ +function MockTransfer() { + this._downloadIsSuccessful = true; +} + +MockTransfer.prototype = { + QueryInterface: ChromeUtils.generateQI([ + "nsIWebProgressListener", + "nsIWebProgressListener2", + "nsITransfer", + ]), + + /* nsIWebProgressListener */ + onStateChange: function MTFC_onStateChange( + aWebProgress, + aRequest, + aStateFlags, + aStatus + ) { + // If at least one notification reported an error, the download failed. + if (!Components.isSuccessCode(aStatus)) { + this._downloadIsSuccessful = false; + } + + // If the download is finished + if ( + aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK + ) { + // Continue the test, reporting the success or failure condition. + mockTransferCallback(this._downloadIsSuccessful); + } + }, + onProgressChange() {}, + onLocationChange() {}, + onStatusChange: function MTFC_onStatusChange( + aWebProgress, + aRequest, + aStatus, + aMessage + ) { + // If at least one notification reported an error, the download failed. + if (!Components.isSuccessCode(aStatus)) { + this._downloadIsSuccessful = false; + } + }, + onSecurityChange() {}, + onContentBlockingEvent() {}, + + /* nsIWebProgressListener2 */ + onProgressChange64() {}, + onRefreshAttempted() {}, + + /* nsITransfer */ + init() {}, + initWithBrowsingContext() {}, + setSha256Hash() {}, + setSignatureInfo() {}, +}; + +// Create an instance of a MockObjectRegisterer whose methods can be used to +// temporarily replace the default "@mozilla.org/transfer;1" object factory with +// one that provides the mock implementation above. To activate the mock object +// factory, call the "register" method. Starting from that moment, all the +// transfer objects that are requested will be mock objects, until the +// "unregister" method is called. +var mockTransferRegisterer = new MockObjectRegisterer( + "@mozilla.org/transfer;1", + MockTransfer +); diff --git a/toolkit/content/tests/browser/data/post_form_inner.sjs b/toolkit/content/tests/browser/data/post_form_inner.sjs new file mode 100644 index 0000000000..ce72159d81 --- /dev/null +++ b/toolkit/content/tests/browser/data/post_form_inner.sjs @@ -0,0 +1,31 @@ +/* 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 CC = Components.Constructor; +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream"); + +function handleRequest(request, response) +{ + var body = + '<html>\ + <body>\ + Inner POST data: '; + + var bodyStream = new BinaryInputStream(request.bodyInputStream); + var bytes = [], avail = 0; + while ((avail = bodyStream.available()) > 0) + body += String.fromCharCode.apply(String, bodyStream.readByteArray(avail)); + + body += + '<form id="postForm" action="post_form_inner.sjs" method="post">\ + <input type="text" name="inputfield" value="inner">\ + <input type="submit">\ + </form>\ + </body>\ + </html>'; + + response.bodyOutputStream.write(body, body.length); +} diff --git a/toolkit/content/tests/browser/data/post_form_outer.sjs b/toolkit/content/tests/browser/data/post_form_outer.sjs new file mode 100644 index 0000000000..89256fcfb2 --- /dev/null +++ b/toolkit/content/tests/browser/data/post_form_outer.sjs @@ -0,0 +1,34 @@ +/* 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 CC = Components.Constructor; +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream"); + +function handleRequest(request, response) +{ + var body = + '<html>\ + <body>\ + Outer POST data: '; + + var bodyStream = new BinaryInputStream(request.bodyInputStream); + var bytes = [], avail = 0; + while ((avail = bodyStream.available()) > 0) + body += String.fromCharCode.apply(String, bodyStream.readByteArray(avail)); + + body += + '<form id="postForm" action="post_form_outer.sjs" method="post">\ + <input type="text" name="inputfield" value="outer">\ + <input type="submit">\ + </form>\ + \ + <iframe id="innerFrame" src="post_form_inner.sjs" width="400" height="200">\ + \ + </body>\ + </html>'; + + response.bodyOutputStream.write(body, body.length); +} diff --git a/toolkit/content/tests/browser/doggy.png b/toolkit/content/tests/browser/doggy.png Binary files differnew file mode 100644 index 0000000000..73632d3229 --- /dev/null +++ b/toolkit/content/tests/browser/doggy.png diff --git a/toolkit/content/tests/browser/empty.png b/toolkit/content/tests/browser/empty.png Binary files differnew file mode 100644 index 0000000000..17ddf0c3ee --- /dev/null +++ b/toolkit/content/tests/browser/empty.png diff --git a/toolkit/content/tests/browser/file_contentTitle.html b/toolkit/content/tests/browser/file_contentTitle.html new file mode 100644 index 0000000000..8d330aa0f2 --- /dev/null +++ b/toolkit/content/tests/browser/file_contentTitle.html @@ -0,0 +1,14 @@ +<html> +<head><title>Test Page</title></head> +<body> +<script type="text/javascript"> +dump("Script!\n"); +addEventListener("load", () => { + // Trigger an onLocationChange event. We want to make sure the title is still correct afterwards. + location.hash = "#x2"; + var event = new Event("TestLocationChange"); + document.dispatchEvent(event); +}, false); +</script> +</body> +</html> diff --git a/toolkit/content/tests/browser/file_document_open_audio.html b/toolkit/content/tests/browser/file_document_open_audio.html new file mode 100644 index 0000000000..1234299c67 --- /dev/null +++ b/toolkit/content/tests/browser/file_document_open_audio.html @@ -0,0 +1,11 @@ +<!doctype html> +<title>Test for bug 1572798</title> +<script> + function openVideo() { + var w = window.open('', '', 'width = 640, height = 480, scrollbars=yes, menubar=no, toolbar=no, resizable=yes'); + w.document.open(); + w.document.write('<!DOCTYPE html><title>test popup</title><audio controls src="audio.ogg"></audio>'); + w.document.close(); + } +</script> +<button onclick="openVideo()">Open video</button> diff --git a/toolkit/content/tests/browser/file_empty.html b/toolkit/content/tests/browser/file_empty.html new file mode 100644 index 0000000000..d2b0361f09 --- /dev/null +++ b/toolkit/content/tests/browser/file_empty.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> + <head> + <title>Page left intentionally blank...</title> + </head> + <body> + </body> +</html> diff --git a/toolkit/content/tests/browser/file_findinframe.html b/toolkit/content/tests/browser/file_findinframe.html new file mode 100644 index 0000000000..27a9d00a97 --- /dev/null +++ b/toolkit/content/tests/browser/file_findinframe.html @@ -0,0 +1,5 @@ +<html> + <body> + <iframe src="data:text/html,<body contenteditable>Test</body>"></iframe> + </body> +</html> diff --git a/toolkit/content/tests/browser/file_mediaPlayback2.html b/toolkit/content/tests/browser/file_mediaPlayback2.html new file mode 100644 index 0000000000..890b494a05 --- /dev/null +++ b/toolkit/content/tests/browser/file_mediaPlayback2.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<body> +<script type="text/javascript"> +var audio = new Audio(); +audio.oncanplay = function() { + audio.oncanplay = null; + audio.play(); +}; +audio.src = "audio.ogg"; +audio.loop = true; +audio.id = "v"; +document.body.appendChild(audio); +</script> +</body> diff --git a/toolkit/content/tests/browser/file_multipleAudio.html b/toolkit/content/tests/browser/file_multipleAudio.html new file mode 100644 index 0000000000..5dc37febb4 --- /dev/null +++ b/toolkit/content/tests/browser/file_multipleAudio.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<head> + <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> + <meta content="utf-8" http-equiv="encoding"> +</head> +<body> +<audio id="autoplay" src="audio.ogg"></audio> +<audio id="nonautoplay" src="audio.ogg"></audio> +<script type="text/javascript"> + +// In linux debug on try server, sometimes the download process would fail, so +// we can't activate the "auto-play" or playing after receving "oncanplay". +// Therefore, we just call play here. +var audio = document.getElementById("autoplay"); +audio.loop = true; +audio.play(); + +</script> +</body> diff --git a/toolkit/content/tests/browser/file_multiplePlayingAudio.html b/toolkit/content/tests/browser/file_multiplePlayingAudio.html new file mode 100644 index 0000000000..ae122506fb --- /dev/null +++ b/toolkit/content/tests/browser/file_multiplePlayingAudio.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<head> + <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> + <meta content="utf-8" http-equiv="encoding"> +</head> +<body> +<audio id="audio1" src="audio.ogg" controls></audio> +<audio id="audio2" src="audio.ogg" controls></audio> +<script type="text/javascript"> + +// In linux debug on try server, sometimes the download process would fail, so +// we can't activate the "auto-play" or playing after receving "oncanplay". +// Therefore, we just call play here. +var audio1 = document.getElementById("audio1"); +audio1.loop = true; +audio1.play(); + +var audio2 = document.getElementById("audio2"); +audio2.loop = true; +audio2.play(); + +</script> +</body> diff --git a/toolkit/content/tests/browser/file_nonAutoplayAudio.html b/toolkit/content/tests/browser/file_nonAutoplayAudio.html new file mode 100644 index 0000000000..4d2641021a --- /dev/null +++ b/toolkit/content/tests/browser/file_nonAutoplayAudio.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<head> + <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> + <meta content="utf-8" http-equiv="encoding"> +</head> +<body> +<audio id="testAudio" src="audio.ogg" loop></audio> diff --git a/toolkit/content/tests/browser/file_outside_viewport_videos.html b/toolkit/content/tests/browser/file_outside_viewport_videos.html new file mode 100644 index 0000000000..84aa34358d --- /dev/null +++ b/toolkit/content/tests/browser/file_outside_viewport_videos.html @@ -0,0 +1,41 @@ +<html> +<head> + <title>outside viewport videos</title> +<style> +/** + * These CSS would move elements to the far left/right/top/bottom where user + * can not see elements in the viewport if user doesn't scroll the page. + */ +.outside-left { + position: absolute; + left: -1000%; +} +.outside-right { + position: absolute; + right: -1000%; +} +.outside-top { + position: absolute; + top: -1000%; +} +.outside-bottom { + position: absolute; + bottom: -1000%; +} +</style> +</head> +<body> + <div class="outside-left"> + <video id="left" src="gizmo.mp4"> + </div> + <div class="outside-right"> + <video id="right" src="gizmo.mp4"> + </div> + <div class="outside-top"> + <video id="top" src="gizmo.mp4"> + </div> + <div class="outside-bottom"> + <video id="bottom" src="gizmo.mp4"> + </div> +</body> +</html> diff --git a/toolkit/content/tests/browser/file_redirect.html b/toolkit/content/tests/browser/file_redirect.html new file mode 100644 index 0000000000..4d5fa9dfd1 --- /dev/null +++ b/toolkit/content/tests/browser/file_redirect.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>redirecting...</title> +<script> +window.addEventListener("load", + () => window.location = "file_redirect_to.html"); +</script> +<body> +redirectin u bro +</body> +</html> diff --git a/toolkit/content/tests/browser/file_redirect_to.html b/toolkit/content/tests/browser/file_redirect_to.html new file mode 100644 index 0000000000..28c0b53713 --- /dev/null +++ b/toolkit/content/tests/browser/file_redirect_to.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>redirected!</title> +<script> +window.addEventListener("load", () => { + var event = new Event("RedirectDone"); + document.dispatchEvent(event); +}); +</script> +<body> +u got redirected, bro +</body> +</html> diff --git a/toolkit/content/tests/browser/file_silentAudioTrack.html b/toolkit/content/tests/browser/file_silentAudioTrack.html new file mode 100644 index 0000000000..afdf2c5297 --- /dev/null +++ b/toolkit/content/tests/browser/file_silentAudioTrack.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<head> + <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> + <meta content="utf-8" http-equiv="encoding"> +</head> +<body> +<video id="autoplay" src="silentAudioTrack.webm"></video> +<script type="text/javascript"> + +// In linux debug on try server, sometimes the download process would fail, so +// we can't activate the "auto-play" or playing after receving "oncanplay". +// Therefore, we just call play here. +var video = document.getElementById("autoplay"); +video.loop = true; +video.play(); + +</script> +</body> diff --git a/toolkit/content/tests/browser/file_video.html b/toolkit/content/tests/browser/file_video.html new file mode 100644 index 0000000000..3c70268fbb --- /dev/null +++ b/toolkit/content/tests/browser/file_video.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<title>video</title> +</head> +<body> +<video id="v" src="gizmo.mp4" controls loop></video> +</body> +</html> diff --git a/toolkit/content/tests/browser/file_videoWithAudioOnly.html b/toolkit/content/tests/browser/file_videoWithAudioOnly.html new file mode 100644 index 0000000000..be84d60c34 --- /dev/null +++ b/toolkit/content/tests/browser/file_videoWithAudioOnly.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<title>video</title> +</head> +<body> +<video id="v" src="audio.ogg" controls loop></video> +</body> +</html> diff --git a/toolkit/content/tests/browser/file_videoWithoutAudioTrack.html b/toolkit/content/tests/browser/file_videoWithoutAudioTrack.html new file mode 100644 index 0000000000..a732b7c9d0 --- /dev/null +++ b/toolkit/content/tests/browser/file_videoWithoutAudioTrack.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<title>video without audio track</title> +</head> +<body> +<video id="v" src="gizmo-noaudio.webm" controls loop></video> +</body> +</html> diff --git a/toolkit/content/tests/browser/file_webAudio.html b/toolkit/content/tests/browser/file_webAudio.html new file mode 100644 index 0000000000..f6fb5e7c07 --- /dev/null +++ b/toolkit/content/tests/browser/file_webAudio.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<head> + <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> + <meta content="utf-8" http-equiv="encoding"> +</head> +<body> +<pre id=state></pre> +<button id="start" onclick="start_webaudio()">Start</button> +<button id="stop" onclick="stop_webaudio()">Stop</button> +<script type="text/javascript"> + var ac = new AudioContext(); + var dest = ac.destination; + var osc = ac.createOscillator(); + osc.connect(dest); + osc.start(); + document.querySelector("pre").innerText = ac.state; + ac.onstatechange = function() { + document.querySelector("pre").innerText = ac.state; + } + + function start_webaudio() { + ac.resume(); + } + + function stop_webaudio() { + ac.suspend(); + } +</script> +</body> diff --git a/toolkit/content/tests/browser/firebird.png b/toolkit/content/tests/browser/firebird.png Binary files differnew file mode 100644 index 0000000000..de5c22f8ce --- /dev/null +++ b/toolkit/content/tests/browser/firebird.png diff --git a/toolkit/content/tests/browser/firebird.png^headers^ b/toolkit/content/tests/browser/firebird.png^headers^ new file mode 100644 index 0000000000..2918fdbe5f --- /dev/null +++ b/toolkit/content/tests/browser/firebird.png^headers^ @@ -0,0 +1,2 @@ +HTTP 302 Found +Location: doggy.png diff --git a/toolkit/content/tests/browser/gizmo-noaudio.webm b/toolkit/content/tests/browser/gizmo-noaudio.webm Binary files differnew file mode 100644 index 0000000000..9f412cb6e3 --- /dev/null +++ b/toolkit/content/tests/browser/gizmo-noaudio.webm diff --git a/toolkit/content/tests/browser/gizmo.mp4 b/toolkit/content/tests/browser/gizmo.mp4 Binary files differnew file mode 100644 index 0000000000..87efad5ade --- /dev/null +++ b/toolkit/content/tests/browser/gizmo.mp4 diff --git a/toolkit/content/tests/browser/head.js b/toolkit/content/tests/browser/head.js new file mode 100644 index 0000000000..8973f1a6b4 --- /dev/null +++ b/toolkit/content/tests/browser/head.js @@ -0,0 +1,338 @@ +"use strict"; + +ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this); + +/** + * A wrapper for the findbar's method "close", which is not synchronous + * because of animation. + */ +function closeFindbarAndWait(findbar) { + return new Promise(resolve => { + if (findbar.hidden) { + resolve(); + return; + } + findbar.addEventListener("transitionend", function cont(aEvent) { + if (aEvent.propertyName != "visibility") { + return; + } + findbar.removeEventListener("transitionend", cont); + resolve(); + }); + let close = findbar.getElement("find-closebutton"); + close.doCommand(); + }); +} + +function pushPrefs(...aPrefs) { + return new Promise(resolve => { + SpecialPowers.pushPrefEnv({ set: aPrefs }, resolve); + }); +} + +/** + * Used to check whether the audio unblocking icon is in the tab. + */ +async function waitForTabBlockEvent(tab, expectBlocked) { + if (tab.activeMediaBlocked == expectBlocked) { + ok(true, "The tab should " + (expectBlocked ? "" : "not ") + "be blocked"); + } else { + info("Block state doens't match, wait for attributes changes."); + await BrowserTestUtils.waitForEvent( + tab, + "TabAttrModified", + false, + event => { + if (event.detail.changed.includes("activemedia-blocked")) { + is( + tab.activeMediaBlocked, + expectBlocked, + "The tab should " + (expectBlocked ? "" : "not ") + "be blocked" + ); + return true; + } + return false; + } + ); + } +} + +/** + * Used to check whether the tab has soundplaying attribute. + */ +async function waitForTabPlayingEvent(tab, expectPlaying) { + if (tab.soundPlaying == expectPlaying) { + ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); + } else { + info("Playing state doesn't match, wait for attributes changes."); + await BrowserTestUtils.waitForEvent( + tab, + "TabAttrModified", + false, + event => { + if (event.detail.changed.includes("soundplaying")) { + is( + tab.soundPlaying, + expectPlaying, + "The tab should " + (expectPlaying ? "" : "not ") + "be playing" + ); + return true; + } + return false; + } + ); + } +} + +function getTestPlugin(pluginName) { + var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"].getService( + SpecialPowers.Ci.nsIPluginHost + ); + var tags = ph.getPluginTags(); + var name = pluginName || "Test Plug-in"; + for (var tag of tags) { + if (tag.name == name) { + return tag; + } + } + + ok(false, "Could not find plugin tag with plugin name '" + name + "'"); + return null; +} + +async function setTestPluginEnabledState(newEnabledState, pluginName) { + var oldEnabledState = await SpecialPowers.setTestPluginEnabledState( + newEnabledState, + pluginName + ); + if (!oldEnabledState) { + return; + } + var plugin = getTestPlugin(pluginName); + // Run a nested event loop to wait for the preference change to + // propagate to the child. Yuck! + SpecialPowers.Services.tm.spinEventLoopUntil(() => { + return plugin.enabledState == newEnabledState; + }); + SimpleTest.registerCleanupFunction(function() { + return SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName); + }); +} + +function disable_non_test_mouse(disable) { + let utils = window.windowUtils; + utils.disableNonTestMouseEvents(disable); +} + +function hover_icon(icon, tooltip) { + disable_non_test_mouse(true); + + let popupShownPromise = BrowserTestUtils.waitForEvent(tooltip, "popupshown"); + EventUtils.synthesizeMouse(icon, 1, 1, { type: "mouseover" }); + EventUtils.synthesizeMouse(icon, 2, 2, { type: "mousemove" }); + EventUtils.synthesizeMouse(icon, 3, 3, { type: "mousemove" }); + EventUtils.synthesizeMouse(icon, 4, 4, { type: "mousemove" }); + return popupShownPromise; +} + +function leave_icon(icon) { + EventUtils.synthesizeMouse(icon, 0, 0, { type: "mouseout" }); + EventUtils.synthesizeMouseAtCenter(document.documentElement, { + type: "mousemove", + }); + EventUtils.synthesizeMouseAtCenter(document.documentElement, { + type: "mousemove", + }); + EventUtils.synthesizeMouseAtCenter(document.documentElement, { + type: "mousemove", + }); + + disable_non_test_mouse(false); +} + +/** + * Helper class for testing datetime input picker widget + */ +class DateTimeTestHelper { + constructor() { + this.panel = gBrowser._getAndMaybeCreateDateTimePickerPanel(); + this.panel.setAttribute("animate", false); + this.tab = null; + this.frame = null; + } + + /** + * Opens a new tab with the URL of the test page, and make sure the picker is + * ready for testing. + * + * @param {String} pageUrl + * @param {bool} inFrame true if input is in the first child frame + */ + async openPicker(pageUrl, inFrame) { + this.tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl); + let bc = gBrowser.selectedBrowser; + if (inFrame) { + await SpecialPowers.spawn(bc, [], async function() { + const iframe = content.document.querySelector("iframe"); + // Ensure the iframe's position is correct before doing any + // other operations + iframe.getBoundingClientRect(); + }); + bc = bc.browsingContext.children[0]; + } + await BrowserTestUtils.synthesizeMouseAtCenter("input", {}, bc); + this.frame = this.panel.querySelector("#dateTimePopupFrame"); + await this.waitForPickerReady(); + } + + promisePickerClosed() { + return new Promise(resolve => { + this.panel.addEventListener("popuphidden", resolve, { once: true }); + }); + } + + async waitForPickerReady() { + let readyPromise; + let loadPromise = new Promise(resolve => { + this.frame.addEventListener( + "load", + () => { + // Add the PickerReady event listener directly inside the load event + // listener to avoid missing the event. + readyPromise = BrowserTestUtils.waitForEvent( + this.frame.contentDocument, + "PickerReady" + ); + resolve(); + }, + { capture: true, once: true } + ); + }); + + await loadPromise; + // Wait for picker elements to be ready + await readyPromise; + } + + /** + * Find an element on the picker. + * + * @param {String} selector + * @return {DOMElement} + */ + getElement(selector) { + return this.frame.contentDocument.querySelector(selector); + } + + /** + * Find the children of an element on the picker. + * + * @param {String} selector + * @return {Array<DOMElement>} + */ + getChildren(selector) { + return Array.from(this.getElement(selector).children); + } + + /** + * Click on an element + * + * @param {DOMElement} element + */ + click(element) { + EventUtils.synthesizeMouseAtCenter(element, {}, this.frame.contentWindow); + } + + /** + * Close the panel and the tab + */ + async tearDown() { + if (!this.panel.hidden) { + let pickerClosePromise = this.promisePickerClosed(); + this.panel.hidePopup(); + await pickerClosePromise; + } + BrowserTestUtils.removeTab(this.tab); + this.tab = null; + } + + /** + * Clean up after tests. Remove the frame to prevent leak. + */ + cleanup() { + this.frame.remove(); + this.frame = null; + this.panel.removeAttribute("animate"); + this.panel = null; + } +} + +/** + * Used to listen events if you just need it once + */ +function once(target, name) { + var p = new Promise(function(resolve, reject) { + target.addEventListener( + name, + function() { + resolve(); + }, + { once: true } + ); + }); + return p; +} + +/** + * check if current wakelock is equal to expected state, if not, then wait until + * the wakelock changes its state to expected state. + * @param needLock + * the wakolock should be locked or not + * @param isForegroundLock + * when the lock is on, the wakelock should be in the foreground or not + */ +async function waitForExpectedWakeLockState( + topic, + { needLock, isForegroundLock } +) { + const powerManagerService = Cc["@mozilla.org/power/powermanagerservice;1"]; + const powerManager = powerManagerService.getService( + Ci.nsIPowerManagerService + ); + const wakelockState = powerManager.getWakeLockState(topic); + let expectedLockState = "unlocked"; + if (needLock) { + expectedLockState = isForegroundLock + ? "locked-foreground" + : "locked-background"; + } + if (wakelockState != expectedLockState) { + info(`wait until wakelock becomes ${expectedLockState}`); + await wakeLockObserved( + powerManager, + topic, + state => state == expectedLockState + ); + } + is( + powerManager.getWakeLockState(topic), + expectedLockState, + `the wakelock state for '${topic}' is equal to '${expectedLockState}'` + ); +} + +function wakeLockObserved(powerManager, observeTopic, checkFn) { + return new Promise(resolve => { + function wakeLockListener() {} + wakeLockListener.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIDOMMozWakeLockListener"]), + callback(topic, state) { + if (topic == observeTopic && checkFn(state)) { + powerManager.removeWakeLockListener(wakeLockListener.prototype); + resolve(); + } + }, + }; + powerManager.addWakeLockListener(wakeLockListener.prototype); + }); +} diff --git a/toolkit/content/tests/browser/image.jpg b/toolkit/content/tests/browser/image.jpg Binary files differnew file mode 100644 index 0000000000..5031808ad2 --- /dev/null +++ b/toolkit/content/tests/browser/image.jpg diff --git a/toolkit/content/tests/browser/image_page.html b/toolkit/content/tests/browser/image_page.html new file mode 100644 index 0000000000..522a1d8cf9 --- /dev/null +++ b/toolkit/content/tests/browser/image_page.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>OHAI</title> +<body> +<img id="image" src="image.jpg" /> +</body> +</html> diff --git a/toolkit/content/tests/browser/silentAudioTrack.webm b/toolkit/content/tests/browser/silentAudioTrack.webm Binary files differnew file mode 100644 index 0000000000..8e08a86c45 --- /dev/null +++ b/toolkit/content/tests/browser/silentAudioTrack.webm |