summaryrefslogtreecommitdiffstats
path: root/toolkit/content/tests/browser
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/content/tests/browser')
-rw-r--r--toolkit/content/tests/browser/audio.oggbin0 -> 14290 bytes
-rw-r--r--toolkit/content/tests/browser/browser.ini118
-rw-r--r--toolkit/content/tests/browser/browser_autoscroll_disabled.js82
-rw-r--r--toolkit/content/tests/browser/browser_bug1170531.js112
-rw-r--r--toolkit/content/tests/browser/browser_bug1198465.js76
-rw-r--r--toolkit/content/tests/browser/browser_bug1572798.js29
-rw-r--r--toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js381
-rw-r--r--toolkit/content/tests/browser/browser_bug451286.js166
-rw-r--r--toolkit/content/tests/browser/browser_bug594509.js15
-rw-r--r--toolkit/content/tests/browser/browser_bug982298.js72
-rw-r--r--toolkit/content/tests/browser/browser_charsetMenu_swapBrowsers.js34
-rw-r--r--toolkit/content/tests/browser/browser_contentTitle.js17
-rw-r--r--toolkit/content/tests/browser/browser_content_url_annotation.js78
-rw-r--r--toolkit/content/tests/browser/browser_crash_previous_frameloader.js131
-rw-r--r--toolkit/content/tests/browser/browser_datetime_datepicker.js791
-rw-r--r--toolkit/content/tests/browser/browser_default_image_filename.js61
-rw-r--r--toolkit/content/tests/browser/browser_default_image_filename_redirect.js53
-rw-r--r--toolkit/content/tests/browser/browser_delay_autoplay_media.js145
-rw-r--r--toolkit/content/tests/browser/browser_delay_autoplay_media_pausedAfterPlay.js121
-rw-r--r--toolkit/content/tests/browser/browser_delay_autoplay_multipleMedia.js77
-rw-r--r--toolkit/content/tests/browser/browser_delay_autoplay_notInTreeAudio.js66
-rw-r--r--toolkit/content/tests/browser/browser_delay_autoplay_playAfterTabVisible.js68
-rw-r--r--toolkit/content/tests/browser/browser_delay_autoplay_playMediaInMuteTab.js97
-rw-r--r--toolkit/content/tests/browser/browser_delay_autoplay_silentAudioTrack_media.js63
-rw-r--r--toolkit/content/tests/browser/browser_delay_autoplay_webAudio.js40
-rw-r--r--toolkit/content/tests/browser/browser_f7_caret_browsing.js278
-rw-r--r--toolkit/content/tests/browser/browser_findbar.js523
-rw-r--r--toolkit/content/tests/browser/browser_findbar_disabled_manual.js33
-rw-r--r--toolkit/content/tests/browser/browser_isSynthetic.js66
-rw-r--r--toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js129
-rw-r--r--toolkit/content/tests/browser/browser_label_textlink.js63
-rw-r--r--toolkit/content/tests/browser/browser_license_links.js27
-rw-r--r--toolkit/content/tests/browser/browser_mediaStreamPlayback.html24
-rw-r--r--toolkit/content/tests/browser/browser_mediaStreamPlaybackWithoutAudio.html17
-rw-r--r--toolkit/content/tests/browser/browser_media_wakelock.js160
-rw-r--r--toolkit/content/tests/browser/browser_media_wakelock_PIP.js156
-rw-r--r--toolkit/content/tests/browser/browser_media_wakelock_webaudio.js127
-rw-r--r--toolkit/content/tests/browser/browser_quickfind_editable.js59
-rw-r--r--toolkit/content/tests/browser/browser_remoteness_change_listeners.js39
-rw-r--r--toolkit/content/tests/browser/browser_resume_bkg_video_on_tab_hover.js154
-rw-r--r--toolkit/content/tests/browser/browser_saveImageURL.js75
-rw-r--r--toolkit/content/tests/browser/browser_save_resend_postdata.js169
-rw-r--r--toolkit/content/tests/browser/browser_suspend_videos_outside_viewport.js39
-rw-r--r--toolkit/content/tests/browser/common/mockTransfer.js85
-rw-r--r--toolkit/content/tests/browser/data/post_form_inner.sjs31
-rw-r--r--toolkit/content/tests/browser/data/post_form_outer.sjs34
-rw-r--r--toolkit/content/tests/browser/doggy.pngbin0 -> 46876 bytes
-rw-r--r--toolkit/content/tests/browser/empty.pngbin0 -> 14528 bytes
-rw-r--r--toolkit/content/tests/browser/file_contentTitle.html14
-rw-r--r--toolkit/content/tests/browser/file_document_open_audio.html11
-rw-r--r--toolkit/content/tests/browser/file_empty.html8
-rw-r--r--toolkit/content/tests/browser/file_findinframe.html5
-rw-r--r--toolkit/content/tests/browser/file_mediaPlayback2.html14
-rw-r--r--toolkit/content/tests/browser/file_multipleAudio.html19
-rw-r--r--toolkit/content/tests/browser/file_multiplePlayingAudio.html23
-rw-r--r--toolkit/content/tests/browser/file_nonAutoplayAudio.html7
-rw-r--r--toolkit/content/tests/browser/file_outside_viewport_videos.html41
-rw-r--r--toolkit/content/tests/browser/file_redirect.html13
-rw-r--r--toolkit/content/tests/browser/file_redirect_to.html15
-rw-r--r--toolkit/content/tests/browser/file_silentAudioTrack.html18
-rw-r--r--toolkit/content/tests/browser/file_video.html9
-rw-r--r--toolkit/content/tests/browser/file_videoWithAudioOnly.html9
-rw-r--r--toolkit/content/tests/browser/file_videoWithoutAudioTrack.html9
-rw-r--r--toolkit/content/tests/browser/file_webAudio.html29
-rw-r--r--toolkit/content/tests/browser/firebird.pngbin0 -> 16179 bytes
-rw-r--r--toolkit/content/tests/browser/firebird.png^headers^2
-rw-r--r--toolkit/content/tests/browser/gizmo-noaudio.webmbin0 -> 112663 bytes
-rw-r--r--toolkit/content/tests/browser/gizmo.mp4bin0 -> 455255 bytes
-rw-r--r--toolkit/content/tests/browser/head.js338
-rw-r--r--toolkit/content/tests/browser/image.jpgbin0 -> 24204 bytes
-rw-r--r--toolkit/content/tests/browser/image_page.html9
-rw-r--r--toolkit/content/tests/browser/silentAudioTrack.webmbin0 -> 224800 bytes
72 files changed, 5774 insertions, 0 deletions
diff --git a/toolkit/content/tests/browser/audio.ogg b/toolkit/content/tests/browser/audio.ogg
new file mode 100644
index 0000000000..7f1833508a
--- /dev/null
+++ b/toolkit/content/tests/browser/audio.ogg
Binary files differ
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
new file mode 100644
index 0000000000..73632d3229
--- /dev/null
+++ b/toolkit/content/tests/browser/doggy.png
Binary files differ
diff --git a/toolkit/content/tests/browser/empty.png b/toolkit/content/tests/browser/empty.png
new file mode 100644
index 0000000000..17ddf0c3ee
--- /dev/null
+++ b/toolkit/content/tests/browser/empty.png
Binary files differ
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,&lt;body contenteditable&gt;Test&lt;/body&gt;"></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
new file mode 100644
index 0000000000..de5c22f8ce
--- /dev/null
+++ b/toolkit/content/tests/browser/firebird.png
Binary files differ
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
new file mode 100644
index 0000000000..9f412cb6e3
--- /dev/null
+++ b/toolkit/content/tests/browser/gizmo-noaudio.webm
Binary files differ
diff --git a/toolkit/content/tests/browser/gizmo.mp4 b/toolkit/content/tests/browser/gizmo.mp4
new file mode 100644
index 0000000000..87efad5ade
--- /dev/null
+++ b/toolkit/content/tests/browser/gizmo.mp4
Binary files differ
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
new file mode 100644
index 0000000000..5031808ad2
--- /dev/null
+++ b/toolkit/content/tests/browser/image.jpg
Binary files differ
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
new file mode 100644
index 0000000000..8e08a86c45
--- /dev/null
+++ b/toolkit/content/tests/browser/silentAudioTrack.webm
Binary files differ