summaryrefslogtreecommitdiffstats
path: root/widget/tests/browser
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /widget/tests/browser
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/tests/browser')
-rw-r--r--widget/tests/browser/browser.ini66
-rw-r--r--widget/tests/browser/browser_test_ContentCache.js296
-rw-r--r--widget/tests/browser/browser_test_InputContextURI.js156
-rw-r--r--widget/tests/browser/browser_test_clipboardcache.js141
-rw-r--r--widget/tests/browser/browser_test_fullscreen_size.js66
-rw-r--r--widget/tests/browser/browser_test_ime_state_in_contenteditable_on_focus_move_in_remote_content.js122
-rw-r--r--widget/tests/browser/browser_test_ime_state_in_contenteditable_on_readonly_change_in_remote_content.js261
-rw-r--r--widget/tests/browser/browser_test_ime_state_in_designMode_on_focus_move_in_remote_content.js116
-rw-r--r--widget/tests/browser/browser_test_ime_state_in_plugin_in_remote_content.js120
-rw-r--r--widget/tests/browser/browser_test_ime_state_in_text_control_on_reframe_in_remote_content.js78
-rw-r--r--widget/tests/browser/browser_test_ime_state_on_editable_state_change_in_remote_content.js297
-rw-r--r--widget/tests/browser/browser_test_ime_state_on_focus_move_in_remote_content.js128
-rw-r--r--widget/tests/browser/browser_test_ime_state_on_input_type_change_in_remote_content.js70
-rw-r--r--widget/tests/browser/browser_test_ime_state_on_readonly_change_in_remote_content.js68
-rw-r--r--widget/tests/browser/browser_test_scrollbar_colors.js146
-rw-r--r--widget/tests/browser/browser_test_swipe_gesture.js1275
-rw-r--r--widget/tests/browser/file_ime_state_tests.html48
-rw-r--r--widget/tests/browser/helper_scrollbar_colors.html22
-rw-r--r--widget/tests/browser/helper_swipe_gesture.html20
19 files changed, 3496 insertions, 0 deletions
diff --git a/widget/tests/browser/browser.ini b/widget/tests/browser/browser.ini
new file mode 100644
index 0000000000..5bd1ce0dc1
--- /dev/null
+++ b/widget/tests/browser/browser.ini
@@ -0,0 +1,66 @@
+[DEFAULT]
+skip-if = os == 'andriod'
+
+[browser_test_ContentCache.js]
+[browser_test_InputContextURI.js]
+[browser_test_clipboardcache.js]
+skip-if =
+ (os == 'linux' && ccov) || tsan # Bug 1613516, the test consistently timeouts on Linux coverage builds.
+ os == "win" && bits == 32 && !debug # Bug 1759422
+ os == "linux" # Bug 1792749
+[browser_test_fullscreen_size.js]
+[browser_test_ime_state_in_contenteditable_on_focus_move_in_remote_content.js]
+support-files =
+ file_ime_state_tests.html
+ ../file_ime_state_test_helper.js
+ ../file_test_ime_state_on_focus_move.js
+[browser_test_ime_state_in_contenteditable_on_readonly_change_in_remote_content.js]
+support-files =
+ file_ime_state_tests.html
+ ../file_ime_state_test_helper.js
+ ../file_test_ime_state_in_contenteditable_on_readonly_change.js
+[browser_test_ime_state_in_designMode_on_focus_move_in_remote_content.js]
+support-files =
+ file_ime_state_tests.html
+ ../file_ime_state_test_helper.js
+ ../file_test_ime_state_on_focus_move.js
+[browser_test_ime_state_in_plugin_in_remote_content.js]
+support-files =
+ ../file_ime_state_test_helper.js
+[browser_test_ime_state_in_text_control_on_reframe_in_remote_content.js]
+support-files =
+ ../file_ime_state_test_helper.js
+ ../file_test_ime_state_in_text_control_on_reframe.js
+[browser_test_ime_state_on_editable_state_change_in_remote_content.js]
+support-files =
+ ../file_ime_state_test_helper.js
+[browser_test_ime_state_on_focus_move_in_remote_content.js]
+support-files =
+ file_ime_state_tests.html
+ ../file_ime_state_test_helper.js
+ ../file_test_ime_state_on_focus_move.js
+[browser_test_ime_state_on_input_type_change_in_remote_content.js]
+skip-if = true # Bug 1817704
+support-files =
+ file_ime_state_tests.html
+ ../file_ime_state_test_helper.js
+ ../file_test_ime_state_on_input_type_change.js
+[browser_test_ime_state_on_readonly_change_in_remote_content.js]
+support-files =
+ file_ime_state_tests.html
+ ../file_ime_state_test_helper.js
+ ../file_test_ime_state_on_readonly_change.js
+[browser_test_scrollbar_colors.js]
+skip-if = (os == 'linux') # bug 1460109
+support-files =
+ helper_scrollbar_colors.html
+[browser_test_swipe_gesture.js]
+run-if = (os == 'mac' || os == 'win' || os == 'linux')
+skip-if =
+ os == "win" && bits == 32 && !debug # Bug 1759422
+ verify # Bug 1800022
+ os == "linux" # Bug 1784772
+support-files =
+ helper_swipe_gesture.html
+ !/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
+ !/gfx/layers/apz/test/mochitest/apz_test_utils.js
diff --git a/widget/tests/browser/browser_test_ContentCache.js b/widget/tests/browser/browser_test_ContentCache.js
new file mode 100644
index 0000000000..7b64e00f13
--- /dev/null
+++ b/widget/tests/browser/browser_test_ContentCache.js
@@ -0,0 +1,296 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function () {
+ const TIP = Cc["@mozilla.org/text-input-processor;1"].createInstance(
+ Ci.nsITextInputProcessor
+ );
+ let notifications = [];
+ const observer = (aTIP, aNotification) => {
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ case "notify-end-input-transaction":
+ case "notify-focus":
+ case "notify-blur":
+ case "notify-text-change":
+ case "notify-selection-change":
+ notifications.push(aNotification);
+ break;
+ }
+ return true;
+ };
+
+ function checkNotifications(aExpectedNotifications, aDescription) {
+ for (const expectedNotification of aExpectedNotifications) {
+ const notification = notifications.find(
+ element => element.type == expectedNotification.type
+ );
+ if (expectedNotification.expected) {
+ isnot(
+ notification,
+ undefined,
+ `"${expectedNotification.type}" should be notified ${aDescription}`
+ );
+ } else {
+ is(
+ notification,
+ undefined,
+ `"${expectedNotification.type}" should not be notified ${aDescription}`
+ );
+ }
+ }
+ }
+
+ ok(
+ TIP.beginInputTransaction(window, observer),
+ "nsITextInputProcessor.beingInputTransaction should return true"
+ );
+ ok(
+ TIP.beginInputTransactionForTests(window, observer),
+ "nsITextInputProcessor.beginInputTransactionForTests should return true"
+ );
+
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/toolkit/content/tests/browser/file_empty.html",
+ async function (browser) {
+ ok(browser.isRemoteBrowser, "This test passes only in e10s mode");
+
+ // IMEContentObserver flushes pending IME notifications at next vsync
+ // after something happens. Therefore, after doing something in content
+ // process, we need to guarantee that IMEContentObserver has a change to
+ // send IME notifications to the main process with calling this function.
+ function waitForSendingIMENotificationsInContent() {
+ return SpecialPowers.spawn(browser, [], async () => {
+ await new Promise(resolve =>
+ content.requestAnimationFrame(() =>
+ content.requestAnimationFrame(resolve)
+ )
+ );
+ });
+ }
+
+ /**
+ * Test when empty editor gets focus
+ */
+ notifications = [];
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.innerHTML = "<div contenteditable><br></div>";
+ const editor = content.document.querySelector("div[contenteditable]");
+ editor.focus();
+ });
+
+ await waitForSendingIMENotificationsInContent();
+
+ (function () {
+ checkNotifications(
+ [
+ { type: "notify-focus", expected: true },
+ { type: "notify-blur", expected: false },
+ { type: "notify-end-input-transaction", expected: false },
+ { type: "notify-text-change", expected: false },
+ { type: "notify-selection-change", expected: false },
+ ],
+ "after empty editor gets focus"
+ );
+ const text = EventUtils.synthesizeQueryTextContent(0, 1000);
+ ok(
+ text?.succeeded,
+ "query text content should succeed after empty editor gets focus"
+ );
+ if (text?.succeeded) {
+ is(
+ text.text.replace(/[\r\n]/g, ""),
+ "",
+ "text should be only line breaks after empty editor gets focus"
+ );
+ }
+ const selection = EventUtils.synthesizeQuerySelectedText();
+ ok(
+ selection?.succeeded,
+ "query selected text should succeed after empty editor gets focus"
+ );
+ if (selection?.succeeded) {
+ ok(
+ !selection.notFound,
+ "query selected text should find a selection range after empty editor gets focus"
+ );
+ if (!selection.notFound) {
+ is(
+ selection.text,
+ "",
+ "selection should be collapsed after empty editor gets focus"
+ );
+ }
+ }
+ })();
+
+ /**
+ * Test when there is non-collapsed selection
+ */
+ notifications = [];
+ await SpecialPowers.spawn(browser, [], () => {
+ const editor = content.document.querySelector("div[contenteditable]");
+ editor.innerHTML = "<p>abc</p><p>def</p>";
+ content
+ .getSelection()
+ .setBaseAndExtent(
+ editor.querySelector("p").firstChild,
+ 2,
+ editor.querySelector("p + p").firstChild,
+ 1
+ );
+ });
+
+ await waitForSendingIMENotificationsInContent();
+
+ (function () {
+ checkNotifications(
+ [
+ { type: "notify-focus", expected: false },
+ { type: "notify-blur", expected: false },
+ { type: "notify-end-input-transaction", expected: false },
+ { type: "notify-text-change", expected: true },
+ { type: "notify-selection-change", expected: true },
+ ],
+ "after modifying focused editor"
+ );
+ const text = EventUtils.synthesizeQueryTextContent(0, 1000);
+ ok(
+ text?.succeeded,
+ "query text content should succeed after modifying focused editor"
+ );
+ if (text?.succeeded) {
+ is(
+ text.text.trim().replace(/\r\n/g, "\n").replace(/\n\n+/g, "\n"),
+ "abc\ndef",
+ "text should include the both paragraph's text after modifying focused editor"
+ );
+ }
+ const selection = EventUtils.synthesizeQuerySelectedText();
+ ok(
+ selection?.succeeded,
+ "query selected text should succeed after modifying focused editor"
+ );
+ if (selection?.succeeded) {
+ ok(
+ !selection.notFound,
+ "query selected text should find a selection range after modifying focused editor"
+ );
+ if (!selection.notFound) {
+ is(
+ selection.text
+ .trim()
+ .replace(/\r\n/g, "\n")
+ .replace(/\n\n+/g, "\n"),
+ "c\nd",
+ "selection should have the selected characters in the both paragraphs after modifying focused editor"
+ );
+ }
+ }
+ })();
+
+ /**
+ * Test when there is no selection ranges
+ */
+ notifications = [];
+ await SpecialPowers.spawn(browser, [], () => {
+ content.getSelection().removeAllRanges();
+ });
+
+ await waitForSendingIMENotificationsInContent();
+
+ (function () {
+ checkNotifications(
+ [
+ { type: "notify-focus", expected: false },
+ { type: "notify-blur", expected: false },
+ { type: "notify-end-input-transaction", expected: false },
+ { type: "notify-text-change", expected: false },
+ { type: "notify-selection-change", expected: true },
+ ],
+ "after removing all selection ranges from the focused editor"
+ );
+ const text = EventUtils.synthesizeQueryTextContent(0, 1000);
+ ok(
+ text?.succeeded,
+ "query text content should succeed after removing all selection ranges from the focused editor"
+ );
+ if (text?.succeeded) {
+ is(
+ text.text.trim().replace(/\r\n/g, "\n").replace(/\n\n+/g, "\n"),
+ "abc\ndef",
+ "text should include the both paragraph's text after removing all selection ranges from the focused editor"
+ );
+ }
+ const selection = EventUtils.synthesizeQuerySelectedText();
+ ok(
+ selection?.succeeded,
+ "query selected text should succeed after removing all selection ranges from the focused editor"
+ );
+ if (selection?.succeeded) {
+ ok(
+ selection.notFound,
+ "query selected text should find no selection range after removing all selection ranges from the focused editor"
+ );
+ }
+ })();
+
+ /**
+ * Test when no editable element has focus.
+ */
+ notifications = [];
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.innerHTML = "abcdef";
+ });
+
+ await waitForSendingIMENotificationsInContent();
+
+ (function () {
+ checkNotifications(
+ [
+ { type: "notify-focus", expected: false },
+ { type: "notify-blur", expected: true },
+ ],
+ "removing editor should make ContentCacheInParent not have any data"
+ );
+ const text = EventUtils.synthesizeQueryTextContent(0, 1000);
+ ok(
+ !text?.succeeded,
+ "query text content should fail because no editable element has focus"
+ );
+ const selection = EventUtils.synthesizeQuerySelectedText();
+ ok(
+ !selection?.succeeded,
+ "query selected text should fail because no editable element has focus"
+ );
+ const caret = EventUtils.synthesizeQueryCaretRect(0);
+ ok(
+ !caret?.succeeded,
+ "query caret rect should fail because no editable element has focus"
+ );
+ const textRect = EventUtils.synthesizeQueryTextRect(0, 5, false);
+ ok(
+ !textRect?.succeeded,
+ "query text rect should fail because no editable element has focus"
+ );
+ const textRectArray = EventUtils.synthesizeQueryTextRectArray(0, 5);
+ ok(
+ !textRectArray?.succeeded,
+ "query text rect array should fail because no editable element has focus"
+ );
+ const editorRect = EventUtils.synthesizeQueryEditorRect();
+ todo(
+ !editorRect?.succeeded,
+ "query editor rect should fail because no editable element has focus"
+ );
+ })();
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_InputContextURI.js b/widget/tests/browser/browser_test_InputContextURI.js
new file mode 100644
index 0000000000..52f05d90f9
--- /dev/null
+++ b/widget/tests/browser/browser_test_InputContextURI.js
@@ -0,0 +1,156 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const gDOMWindowUtils = EventUtils._getDOMWindowUtils(window);
+
+function promiseURLBarFocus() {
+ const waitForFocusInURLBar = BrowserTestUtils.waitForEvent(gURLBar, "focus");
+ gURLBar.blur();
+ gURLBar.focus();
+ return Promise.all([
+ waitForFocusInURLBar,
+ TestUtils.waitForCondition(
+ () =>
+ gDOMWindowUtils.IMEStatus === Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED &&
+ gDOMWindowUtils.inputContextOrigin ===
+ Ci.nsIDOMWindowUtils.INPUT_CONTEXT_ORIGIN_MAIN
+ ),
+ ]);
+}
+
+function promiseIMEStateEnabledByRemote() {
+ return TestUtils.waitForCondition(
+ () =>
+ gDOMWindowUtils.IMEStatus === Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED &&
+ gDOMWindowUtils.inputContextOrigin ===
+ Ci.nsIDOMWindowUtils.INPUT_CONTEXT_ORIGIN_CONTENT
+ );
+}
+
+async function test_url_bar_url(aDesc) {
+ await promiseURLBarFocus();
+
+ is(
+ gDOMWindowUtils.inputContextURI,
+ null,
+ `When the search bar has focus, input context URI should be null because of in chrome document (${aDesc})`
+ );
+}
+
+async function test_input_in_http_or_https(aIsHTTPS) {
+ await promiseURLBarFocus();
+
+ const scheme = aIsHTTPS ? "https" : "http";
+ const url = `${scheme}://example.com/browser/toolkit/content/tests/browser/file_empty.html`;
+ await BrowserTestUtils.withNewTab(url, async browser => {
+ ok(browser.isRemoteBrowser, "This test passes only in e10s mode");
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.document.body.innerHTML = "<input>";
+ const input = content.document.querySelector("input");
+ input.focus();
+
+ // Wait for a tick for flushing IMEContentObserver's pending notifications.
+ await new Promise(resolve =>
+ content.requestAnimationFrame(() =>
+ content.requestAnimationFrame(resolve)
+ )
+ );
+ });
+
+ await promiseIMEStateEnabledByRemote();
+ if (!gDOMWindowUtils.inputContextURI) {
+ ok(
+ false,
+ `Input context should have valid URI when the scheme of focused tab's URL is ${scheme}`
+ );
+ return;
+ }
+ is(
+ gDOMWindowUtils.inputContextURI.spec,
+ url,
+ `Input context should have the document URI when the scheme of focused tab's URL is ${scheme}`
+ );
+ });
+}
+
+add_task(async () => {
+ await test_url_bar_url("first check");
+});
+add_task(async () => {
+ await test_input_in_http_or_https(true);
+});
+add_task(async () => {
+ await test_url_bar_url("check after remote content sets the URI");
+});
+add_task(async () => {
+ await test_input_in_http_or_https(false);
+});
+
+add_task(async function test_input_in_data() {
+ await BrowserTestUtils.withNewTab("data:text/html,<input>", async browser => {
+ ok(browser.isRemoteBrowser, "This test passes only in e10s mode");
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ const input = content.document.querySelector("input");
+ input.focus();
+
+ // Wait for a tick for flushing IMEContentObserver's pending notifications.
+ await new Promise(resolve =>
+ content.requestAnimationFrame(() =>
+ content.requestAnimationFrame(resolve)
+ )
+ );
+ });
+
+ await promiseIMEStateEnabledByRemote();
+ is(
+ gDOMWindowUtils.inputContextURI,
+ null,
+ "Input context should not have data URI"
+ );
+ });
+});
+
+add_task(async function test_omit_private_things_in_URL() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.auth.confirmAuth.enabled", false]],
+ });
+ await promiseURLBarFocus();
+
+ await BrowserTestUtils.withNewTab(
+ "https://username:password@example.com/browser/toolkit/content/tests/browser/file_empty.html?query=some#ref",
+ async browser => {
+ ok(browser.isRemoteBrowser, "This test passes only in e10s mode");
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.document.body.innerHTML = "<input>";
+ const input = content.document.querySelector("input");
+ input.focus();
+
+ // Wait for a tick for flushing IMEContentObserver's pending notifications.
+ await new Promise(resolve =>
+ content.requestAnimationFrame(() =>
+ content.requestAnimationFrame(resolve)
+ )
+ );
+ });
+
+ await promiseIMEStateEnabledByRemote();
+ if (!gDOMWindowUtils.inputContextURI) {
+ ok(
+ false,
+ `Input context should have valid URI even when the URL contains some private things`
+ );
+ return;
+ }
+ is(
+ gDOMWindowUtils.inputContextURI.spec,
+ "https://example.com/browser/toolkit/content/tests/browser/file_empty.html",
+ `Input context should have the document URI which omit some private things in the URL`
+ );
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_clipboardcache.js b/widget/tests/browser/browser_test_clipboardcache.js
new file mode 100644
index 0000000000..bce0b9a918
--- /dev/null
+++ b/widget/tests/browser/browser_test_clipboardcache.js
@@ -0,0 +1,141 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// Note: widget/tests/test_bug1123480.xhtml checks whether nsTransferable behaves
+// as expected with regards to private browsing mode and the clipboard cache,
+// i.e. that the clipboard is not cached to the disk when private browsing mode
+// is enabled.
+//
+// This test tests that the clipboard is not cached to the disk by IPC,
+// as a regression test for bug 1396224.
+// It indirectly uses nsTransferable, via the async navigator.clipboard API.
+
+// Create over 1 MB of sample garbage text. JavaScript strings are represented
+// by UTF16 strings, so the size is twice as much as the actual string length.
+// This value is chosen such that the size of the memory for the string exceeds
+// the kLargeDatasetSize threshold in nsTransferable.h.
+// It is also not a round number to reduce the odds of having an accidental
+// collisions with another file (since the test below looks at the file size
+// to identify the file).
+var Ipsum = "0123456789".repeat(1234321);
+var IpsumByteLength = Ipsum.length * 2;
+var SHORT_STRING_NO_CACHE = "short string that will not be cached to the disk";
+
+// Get a list of open file descriptors that refer to a file with the same size
+// as the expected data (and assume that any mutations in file descriptor counts
+// are caused by our test).
+// TODO: This logic only counts file descriptors that are still open (e.g. when
+// data persists after a copy). It does not detect cache files that exist only
+// temporarily (e.g. after a paste).
+function getClipboardCacheFDCount() {
+ let dir;
+ if (AppConstants.platform === "win") {
+ // On Windows, nsAnonymousTemporaryFile does not immediately delete a file.
+ // Instead, the Windows-specific FILE_FLAG_DELETE_ON_CLOSE flag is used,
+ // which means that the file is deleted when the last handle is closed.
+ // Apparently, this flag is unreliable (e.g. when the application crashes),
+ // so nsAnonymousTemporaryFile stores the temporary files in a subdirectory,
+ // which is cleaned up some time after start-up.
+
+ // This is just a test, and during the test we deterministically close the
+ // handles, so if FILE_FLAG_DELETE_ON_CLOSE does the thing it promises, the
+ // file is actually removed when the handle is closed.
+
+ let { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+ );
+ // Path from nsAnonymousTemporaryFile.cpp, GetTempDir.
+ dir = FileUtils.getFile("TmpD", ["mozilla-temp-files"]);
+ } else {
+ dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dir.initWithPath("/dev/fd");
+ }
+ let count = 0;
+ for (let fdFile of dir.directoryEntries) {
+ let fileSize;
+ try {
+ fileSize = fdFile.fileSize;
+ } catch (e) {
+ // This can happen on macOS.
+ continue;
+ }
+ if (fileSize === IpsumByteLength) {
+ // Assume that the file was created by us if the size matches.
+ ++count;
+ }
+ }
+ return count;
+}
+
+async function testCopyPaste(isPrivate) {
+ let win = await BrowserTestUtils.openNewBrowserWindow({ private: isPrivate });
+ let tab = await BrowserTestUtils.openNewForegroundTab(win);
+ let browser = tab.linkedBrowser;
+
+ // Sanitize environment
+ await ContentTask.spawn(browser, SHORT_STRING_NO_CACHE, async shortStr => {
+ await content.navigator.clipboard.writeText(shortStr);
+ });
+
+ let initialFdCount = getClipboardCacheFDCount();
+
+ await SpecialPowers.spawn(browser, [Ipsum], async largeString => {
+ await content.navigator.clipboard.writeText(largeString);
+ });
+
+ let fdCountAfterCopy = getClipboardCacheFDCount();
+ if (isPrivate) {
+ is(fdCountAfterCopy, initialFdCount, "Private write");
+ } else {
+ is(fdCountAfterCopy, initialFdCount + 1, "Cached write");
+ }
+
+ let readStr = await SpecialPowers.spawn(browser, [], async () => {
+ let { document } = content;
+ document.body.contentEditable = true;
+ document.body.focus();
+ let pastePromise = new Promise(resolve => {
+ document.addEventListener(
+ "paste",
+ e => {
+ resolve(e.clipboardData.getData("text/plain"));
+ },
+ { once: true }
+ );
+ });
+ document.execCommand("paste");
+ return pastePromise;
+ });
+ ok(readStr === Ipsum, "Read what we pasted");
+
+ if (isPrivate) {
+ is(getClipboardCacheFDCount(), fdCountAfterCopy, "Private read");
+ } else {
+ // Upon reading from the clipboard, a temporary nsTransferable is used, for
+ // which the cache is disabled. The content process does not cache clipboard
+ // data either. So the file descriptor count should be identical.
+ is(getClipboardCacheFDCount(), fdCountAfterCopy, "Read not cached");
+ }
+
+ // Cleanup.
+ await SpecialPowers.spawn(
+ browser,
+ [SHORT_STRING_NO_CACHE],
+ async shortStr => {
+ await content.navigator.clipboard.writeText(shortStr);
+ }
+ );
+ is(getClipboardCacheFDCount(), initialFdCount, "Drop clipboard cache if any");
+
+ BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+}
+
+add_task(async function test_private() {
+ await testCopyPaste(true);
+});
+
+add_task(async function test_non_private() {
+ await testCopyPaste(false);
+});
diff --git a/widget/tests/browser/browser_test_fullscreen_size.js b/widget/tests/browser/browser_test_fullscreen_size.js
new file mode 100644
index 0000000000..c358d335d8
--- /dev/null
+++ b/widget/tests/browser/browser_test_fullscreen_size.js
@@ -0,0 +1,66 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function waitForReflow(aWindow) {
+ return new Promise(resolve => {
+ aWindow.requestAnimationFrame(() => {
+ aWindow.requestAnimationFrame(resolve);
+ });
+ });
+}
+
+add_task(async function fullscreen_size() {
+ let win = await BrowserTestUtils.openNewBrowserWindow({});
+ win.gBrowser.selectedBrowser.focus();
+
+ info("Enter browser fullscreen mode");
+ let promise = Promise.all([
+ BrowserTestUtils.waitForEvent(win, "fullscreen"),
+ BrowserTestUtils.waitForEvent(win, "resize"),
+ ]);
+ win.fullScreen = true;
+ await promise;
+
+ info("Await reflow of the chrome window");
+ await waitForReflow(win);
+
+ is(win.innerHeight, win.outerHeight, "Check height");
+ is(win.innerWidth, win.outerWidth, "Check width");
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1830721
+add_task(async function fullscreen_size_moz_appearance() {
+ const win = await BrowserTestUtils.openNewBrowserWindow({});
+ win.gBrowser.selectedBrowser.focus();
+
+ info("Add -moz-appearance style to chrome document");
+ const style = win.document.createElement("style");
+ style.innerHTML = `
+ #main-window {
+ -moz-appearance: -moz-win-borderless-glass;
+ }
+ `;
+ win.document.head.appendChild(style);
+
+ info("Await reflow of the chrome window");
+ await waitForReflow(win);
+
+ info("Enter browser fullscreen mode");
+ let promise = Promise.all([
+ BrowserTestUtils.waitForEvent(win, "fullscreen"),
+ BrowserTestUtils.waitForEvent(win, "resize"),
+ ]);
+ win.fullScreen = true;
+ await promise;
+
+ info("Await reflow of the chrome window");
+ await waitForReflow(win);
+
+ is(win.innerHeight, win.outerHeight, "Check height");
+ is(win.innerWidth, win.outerWidth, `Check width`);
+
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/widget/tests/browser/browser_test_ime_state_in_contenteditable_on_focus_move_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_in_contenteditable_on_focus_move_in_remote_content.js
new file mode 100644
index 0000000000..50b19f0cc3
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_in_contenteditable_on_focus_move_in_remote_content.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_on_focus_move.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_test_ime_state_on_focus_move.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/widget/tests/browser/file_ime_state_tests.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ // isnot is used in file_test_ime_state_on_focus_move.js, but it's not
+ // defined as the alias of Assert.notEqual in browser-chrome tests.
+ // Therefore, we need to define it here.
+ // eslint-disable-next-line no-unused-vars
+ const isnot = Assert.notEqual;
+
+ async function runIMEStateOnFocusMoveTests(aDescription) {
+ await (async function test_IMEState_without_focused_element() {
+ const checker = new IMEStateWhenNoActiveElementTester(aDescription);
+ const expectedData = await SpecialPowers.spawn(
+ browser,
+ [aDescription],
+ description => {
+ const runner =
+ content.wrappedJSObject.createIMEStateWhenNoActiveElementTester(
+ description
+ );
+ return runner.run(content.document, content.window);
+ }
+ );
+ checker.check(expectedData);
+ })();
+ for (
+ let index = 0;
+ index < IMEStateOnFocusMoveTester.numberOfTests;
+ ++index
+ ) {
+ const checker = new IMEStateOnFocusMoveTester(aDescription, index);
+ const expectedData = await SpecialPowers.spawn(
+ browser,
+ [aDescription, index],
+ (description, aIndex) => {
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateOnFocusMoveTester(
+ description,
+ aIndex,
+ content.window
+ );
+ return content.wrappedJSObject.runner.prepareToRun(
+ content.document.querySelector("div")
+ );
+ }
+ );
+ checker.prepareToCheck(expectedData, tipWrapper);
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.run();
+ });
+ checker.check(expectedData);
+
+ if (checker.canTestOpenCloseState(expectedData)) {
+ for (const defaultOpenState of [false, true]) {
+ const expectedOpenStateData = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.prepareToRunOpenCloseTest(
+ content.document.querySelector("div")
+ );
+ }
+ );
+ checker.prepareToCheckOpenCloseTest(
+ defaultOpenState,
+ expectedOpenStateData
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runOpenCloseTest();
+ });
+ checker.checkOpenCloseTest(expectedOpenStateData);
+ }
+ }
+ await SpecialPowers.spawn(browser, [], () => {
+ content.wrappedJSObject.runner.destroy();
+ content.wrappedJSObject.runner = undefined;
+ });
+ checker.destroy();
+ } // for loop iterating test of IMEStateOnFocusMoveTester
+ } // definition of runIMEStateOnFocusMoveTests
+
+ // test for contentEditable="true"
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.document
+ .querySelector("div")
+ .setAttribute("contenteditable", "true");
+ });
+ await runIMEStateOnFocusMoveTests("in div[contenteditable]");
+
+ // test for contentEditable="false"
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.document
+ .querySelector("div")
+ .setAttribute("contenteditable", "false");
+ });
+ await runIMEStateOnFocusMoveTests('in div[contenteditable="false"]');
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_in_contenteditable_on_readonly_change_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_in_contenteditable_on_readonly_change_in_remote_content.js
new file mode 100644
index 0000000000..33217d1d2c
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_in_contenteditable_on_readonly_change_in_remote_content.js
@@ -0,0 +1,261 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_in_contenteditable_on_readonly_change.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_test_ime_state_in_contenteditable_on_readonly_change.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/widget/tests/browser/file_ime_state_tests.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ await (async function test_ime_state_in_contenteditable_on_readonly_change() {
+ const expectedDataOfInitialization = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ content.document.body.innerHTML = "<div contenteditable><br></div>";
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateInContentEditableOnReadonlyChangeTester();
+ const editingHost = content.document.querySelector(
+ "div[contenteditable]"
+ );
+ return content.wrappedJSObject.runner.prepareToRun(
+ editingHost,
+ editingHost,
+ content.window
+ );
+ }
+ );
+ const tester = new IMEStateInContentEditableOnReadonlyChangeTester();
+ tester.checkResultOfPreparation(
+ expectedDataOfInitialization,
+ window,
+ tipWrapper
+ );
+ const expectedDataOfReadonly = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorReadonly();
+ }
+ );
+ tester.checkResultOfMakingHTMLEditorReadonly(expectedDataOfReadonly);
+ const expectedDataOfEditable = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorEditable();
+ }
+ );
+ tester.checkResultOfMakingHTMLEditorEditable(expectedDataOfEditable);
+ const expectedDataOfFinalization = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.runToRemoveContentEditableAttribute();
+ }
+ );
+ tester.checkResultOfRemovingContentEditableAttribute(
+ expectedDataOfFinalization
+ );
+ tester.clear();
+ })();
+
+ await (async function test_ime_state_in_button_in_contenteditable_on_readonly_change() {
+ const expectedDataOfInitialization = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ content.document.body.innerHTML =
+ "<div contenteditable><br><button>button</button></div>";
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateInContentEditableOnReadonlyChangeTester();
+ const editingHost = content.document.querySelector(
+ "div[contenteditable]"
+ );
+ return content.wrappedJSObject.runner.prepareToRun(
+ editingHost,
+ editingHost.querySelector("button"),
+ content.window
+ );
+ }
+ );
+ const tester = new IMEStateInContentEditableOnReadonlyChangeTester();
+ tester.checkResultOfPreparation(
+ expectedDataOfInitialization,
+ window,
+ tipWrapper
+ );
+ const expectedDataOfReadonly = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorReadonly();
+ }
+ );
+ tester.checkResultOfMakingHTMLEditorReadonly(expectedDataOfReadonly);
+ const expectedDataOfEditable = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorEditable();
+ }
+ );
+ tester.checkResultOfMakingHTMLEditorEditable(expectedDataOfEditable);
+ const expectedDataOfFinalization = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.runToRemoveContentEditableAttribute();
+ }
+ );
+ tester.checkResultOfRemovingContentEditableAttribute(
+ expectedDataOfFinalization
+ );
+ tester.clear();
+ })();
+
+ await (async function test_ime_state_of_text_controls_in_contenteditable_on_readonly_change() {
+ const tester =
+ new IMEStateOfTextControlInContentEditableOnReadonlyChangeTester();
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.innerHTML = "<div contenteditable></div>";
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateOfTextControlInContentEditableOnReadonlyChangeTester();
+ });
+ for (
+ let index = 0;
+ index <
+ IMEStateOfTextControlInContentEditableOnReadonlyChangeTester.numberOfTextControlTypes;
+ index++
+ ) {
+ const expectedDataOfInitialization = await SpecialPowers.spawn(
+ browser,
+ [index],
+ aIndex => {
+ const editingHost = content.document.querySelector("div");
+ return content.wrappedJSObject.runner.prepareToRun(
+ aIndex,
+ editingHost,
+ content.window
+ );
+ }
+ );
+ tester.checkResultOfPreparation(
+ expectedDataOfInitialization,
+ window,
+ tipWrapper
+ );
+ const expectedDataOfMakingParentEditingHost =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeParentEditingHost();
+ });
+ tester.checkResultOfMakingParentEditingHost(
+ expectedDataOfMakingParentEditingHost
+ );
+ const expectedDataOfMakingHTMLEditorReadonly =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorReadonly();
+ });
+ tester.checkResultOfMakingHTMLEditorReadonly(
+ expectedDataOfMakingHTMLEditorReadonly
+ );
+ const expectedDataOfMakingHTMLEditorEditable =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorEditable();
+ });
+ tester.checkResultOfMakingHTMLEditorEditable(
+ expectedDataOfMakingHTMLEditorEditable
+ );
+ const expectedDataOfMakingParentNonEditable =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeParentNonEditingHost();
+ });
+ tester.checkResultOfMakingParentNonEditable(
+ expectedDataOfMakingParentNonEditable
+ );
+ tester.clear();
+ }
+ })();
+
+ await (async function test_ime_state_outside_contenteditable_on_readonly_change() {
+ const tester =
+ new IMEStateOutsideContentEditableOnReadonlyChangeTester();
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.innerHTML = "<div contenteditable></div>";
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateOutsideContentEditableOnReadonlyChangeTester();
+ });
+ for (
+ let index = 0;
+ index <
+ IMEStateOutsideContentEditableOnReadonlyChangeTester.numberOfFocusTargets;
+ index++
+ ) {
+ const expectedDataOfInitialization = await SpecialPowers.spawn(
+ browser,
+ [index],
+ aIndex => {
+ const editingHost = content.document.querySelector("div");
+ return content.wrappedJSObject.runner.prepareToRun(
+ aIndex,
+ editingHost,
+ content.window
+ );
+ }
+ );
+ tester.checkResultOfPreparation(
+ expectedDataOfInitialization,
+ window,
+ tipWrapper
+ );
+ const expectedDataOfMakingParentEditingHost =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeParentEditingHost();
+ });
+ tester.checkResultOfMakingParentEditingHost(
+ expectedDataOfMakingParentEditingHost
+ );
+ const expectedDataOfMakingHTMLEditorReadonly =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorReadonly();
+ });
+ tester.checkResultOfMakingHTMLEditorReadonly(
+ expectedDataOfMakingHTMLEditorReadonly
+ );
+ const expectedDataOfMakingHTMLEditorEditable =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorEditable();
+ });
+ tester.checkResultOfMakingHTMLEditorEditable(
+ expectedDataOfMakingHTMLEditorEditable
+ );
+ const expectedDataOfMakingParentNonEditable =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeParentNonEditingHost();
+ });
+ tester.checkResultOfMakingParentNonEditable(
+ expectedDataOfMakingParentNonEditable
+ );
+ tester.clear();
+ }
+ })();
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_in_designMode_on_focus_move_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_in_designMode_on_focus_move_in_remote_content.js
new file mode 100644
index 0000000000..5ea5990e96
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_in_designMode_on_focus_move_in_remote_content.js
@@ -0,0 +1,116 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_on_focus_move.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_test_ime_state_on_focus_move.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/widget/tests/browser/file_ime_state_tests.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ // isnot is used in file_test_ime_state_on_focus_move.js, but it's not
+ // defined as the alias of Assert.notEqual in browser-chrome tests.
+ // Therefore, we need to define it here.
+ // eslint-disable-next-line no-unused-vars
+ const isnot = Assert.notEqual;
+
+ async function runIMEStateOnFocusMoveTests(aDescription) {
+ await (async function test_IMEState_without_focused_element() {
+ const checker = new IMEStateWhenNoActiveElementTester(aDescription);
+ const expectedData = await SpecialPowers.spawn(
+ browser,
+ [aDescription],
+ description => {
+ const runner =
+ content.wrappedJSObject.createIMEStateWhenNoActiveElementTester(
+ description
+ );
+ return runner.run(content.document, content.window);
+ }
+ );
+ checker.check(expectedData);
+ })();
+ for (
+ let index = 0;
+ index < IMEStateOnFocusMoveTester.numberOfTests;
+ ++index
+ ) {
+ const checker = new IMEStateOnFocusMoveTester(aDescription, index);
+ const expectedData = await SpecialPowers.spawn(
+ browser,
+ [aDescription, index],
+ (description, aIndex) => {
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateOnFocusMoveTester(
+ description,
+ aIndex,
+ content.window
+ );
+ return content.wrappedJSObject.runner.prepareToRun(
+ content.document.querySelector("div")
+ );
+ }
+ );
+ checker.prepareToCheck(expectedData, tipWrapper);
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.run();
+ });
+ checker.check(expectedData);
+
+ if (checker.canTestOpenCloseState(expectedData)) {
+ for (const defaultOpenState of [false, true]) {
+ const expectedOpenStateData = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.prepareToRunOpenCloseTest(
+ content.document.querySelector("div")
+ );
+ }
+ );
+ checker.prepareToCheckOpenCloseTest(
+ defaultOpenState,
+ expectedOpenStateData
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runOpenCloseTest();
+ });
+ checker.checkOpenCloseTest(expectedOpenStateData);
+ }
+ }
+ await SpecialPowers.spawn(browser, [], () => {
+ content.wrappedJSObject.runner.destroy();
+ content.wrappedJSObject.runner = undefined;
+ });
+ checker.destroy();
+ } // for loop iterating test of IMEStateOnFocusMoveTester
+ } // definition of runIMEStateOnFocusMoveTests
+
+ // test designMode
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.document.designMode = "on";
+ });
+ await runIMEStateOnFocusMoveTests('in designMode="on"');
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.document.designMode = "off";
+ });
+ await runIMEStateOnFocusMoveTests('in designMode="off"');
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_in_plugin_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_in_plugin_in_remote_content.js
new file mode 100644
index 0000000000..0862b51080
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_in_plugin_in_remote_content.js
@@ -0,0 +1,120 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/toolkit/content/tests/browser/file_empty.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.wrappedJSObject.waitForIMEContentObserverSendingNotifications =
+ () => {
+ return new content.window.Promise(resolve =>
+ content.window.requestAnimationFrame(() =>
+ content.window.requestAnimationFrame(resolve)
+ )
+ );
+ };
+ content.document.body.innerHTML =
+ '<input><object type="application/x-test"></object>';
+ });
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.activeElement?.blur();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "IME enabled state should be disabled when no element has focus"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "IME should not have focus when no element has focus"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("object").focus();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "IME enabled state should be disabled when an <object> for plugin has focus"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "IME enabled state should not have focus when an <object> for plugin has focus"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("object").blur();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "IME enabled state should be disabled when an <object> for plugin gets blurred"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "IME should not have focus when an <object> for plugin gets blurred"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("object").focus();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "IME enabled state should be disabled when an <object> for plugin gets focused again"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "IME should not have focus when an <object> for plugin gets focused again"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("object").remove();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "IME enabled state should be disabled when focused <object> for plugin is removed from the document"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "IME should not have focus when focused <object> for plugin is removed from the document"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("input").focus();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ "IME enabled state should be enabled after <input> gets focus"
+ );
+ ok(
+ tipWrapper.IMEHasFocus,
+ "IME should have focus after <input> gets focus"
+ );
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_in_text_control_on_reframe_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_in_text_control_on_reframe_in_remote_content.js
new file mode 100644
index 0000000000..4d8acab1ff
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_in_text_control_on_reframe_in_remote_content.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_in_text_control_on_reframe.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_test_ime_state_in_text_control_on_reframe.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/widget/tests/browser/file_ime_state_tests.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ await (async function test_ime_state_outside_contenteditable_on_readonly_change() {
+ const tester = new IMEStateInTextControlOnReframeTester();
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.innerHTML = "<div contenteditable></div>";
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateInTextControlOnReframeTester();
+ });
+ for (
+ let index = 0;
+ index < IMEStateInTextControlOnReframeTester.numberOfTextControlTypes;
+ index++
+ ) {
+ tipWrapper.clearFocusBlurNotifications();
+ const expectedData1 = await SpecialPowers.spawn(
+ browser,
+ [index],
+ aIndex => {
+ return content.wrappedJSObject.runner.prepareToRun(
+ aIndex,
+ content.document,
+ content.window
+ );
+ }
+ );
+ tipWrapper.typeA();
+ await SpecialPowers.spawn(browser, [], () => {
+ return new Promise(resolve =>
+ content.window.requestAnimationFrame(() =>
+ content.window.requestAnimationFrame(resolve)
+ )
+ );
+ });
+ tester.checkResultAfterTypingA(expectedData1, window, tipWrapper);
+
+ const expectedData2 = await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.prepareToRun2();
+ });
+ tipWrapper.typeA();
+ await SpecialPowers.spawn(browser, [], () => {
+ return new Promise(resolve =>
+ content.window.requestAnimationFrame(() =>
+ content.window.requestAnimationFrame(resolve)
+ )
+ );
+ });
+ tester.checkResultAfterTypingA2(expectedData2);
+ tester.clear();
+ }
+ })();
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_on_editable_state_change_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_on_editable_state_change_in_remote_content.js
new file mode 100644
index 0000000000..8c38f97b72
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_on_editable_state_change_in_remote_content.js
@@ -0,0 +1,297 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/toolkit/content/tests/browser/file_empty.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.wrappedJSObject.waitForIMEContentObserverSendingNotifications =
+ () => {
+ return new content.window.Promise(resolve =>
+ content.window.requestAnimationFrame(() =>
+ content.window.requestAnimationFrame(resolve)
+ )
+ );
+ };
+ content.wrappedJSObject.resetIMEStateWithFocusMove = () => {
+ const input = content.document.createElement("input");
+ content.document.body.appendChild(input);
+ input.focus();
+ input.remove();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ };
+ content.document.body.innerHTML = "<div></div>";
+ });
+
+ function resetIMEStateWithFocusMove() {
+ return SpecialPowers.spawn(browser, [], () => {
+ const input = content.document.createElement("input");
+ content.document.body.appendChild(input);
+ input.focus();
+ input.remove();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ }
+
+ await (async function test_setting_contenteditable_of_focused_div() {
+ await SpecialPowers.spawn(browser, [], () => {
+ const div = content.document.querySelector("div");
+ div.setAttribute("tabindex", "0");
+ div.focus();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "test_setting_contenteditable_of_focused_div: IME should be disabled when non-editable <div> has focus"
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .querySelector("div")
+ .setAttribute("contenteditable", "");
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ "test_setting_contenteditable_of_focused_div: IME should be enabled when contenteditable of focused <div> is set"
+ );
+ ok(
+ tipWrapper.IMEHasFocus,
+ "test_setting_contenteditable_of_focused_div: IME should have focus when contenteditable of focused <div> is set"
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .querySelector("div")
+ .removeAttribute("contenteditable");
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "test_setting_contenteditable_of_focused_div: IME should be disabled when contenteditable of focused <div> is removed"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "test_setting_contenteditable_of_focused_div: IME should not have focus when contenteditable of focused <div> is removed"
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("div").removeAttribute("tabindex");
+ });
+ })();
+
+ await resetIMEStateWithFocusMove();
+
+ await (async function test_removing_contenteditable_of_non_last_editable_div() {
+ await SpecialPowers.spawn(browser, [], async () => {
+ const div = content.document.querySelector("div");
+ div.setAttribute("tabindex", "0");
+ div.setAttribute("contenteditable", "");
+ const anotherEditableDiv = content.document.createElement("div");
+ anotherEditableDiv.setAttribute("contenteditable", "");
+ div.parentElement.appendChild(anotherEditableDiv);
+ div.focus();
+ await content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ div.removeAttribute("contenteditable");
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "test_removing_contenteditable_of_non_last_editable_div: IME should be disabled when contenteditable of focused <div> is removed"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "test_removing_contenteditable_of_non_last_editable_div: IME should not have focus when contenteditable of focused <div> is removed"
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ const divs = content.document.querySelectorAll("div");
+ divs[1].remove();
+ divs[0].removeAttribute("tabindex");
+ });
+ })();
+
+ await resetIMEStateWithFocusMove();
+
+ await (async function test_setting_designMode() {
+ await SpecialPowers.spawn(browser, [], () => {
+ content.window.focus();
+ content.document.designMode = "on";
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ 'test_setting_designMode: IME should be enabled when designMode is set to "on"'
+ );
+ ok(
+ tipWrapper.IMEHasFocus,
+ 'test_setting_designMode: IME should have focus when designMode is set to "on"'
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.designMode = "off";
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ 'test_setting_designMode: IME should be disabled when designMode is set to "off"'
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ 'test_setting_designMode: IME should not have focus when designMode is set to "off"'
+ );
+ })();
+
+ await resetIMEStateWithFocusMove();
+
+ async function test_setting_content_editable_of_body_when_shadow_DOM_has_focus(
+ aMode
+ ) {
+ await SpecialPowers.spawn(browser, [aMode], mode => {
+ const div = content.document.querySelector("div");
+ const shadow = div.attachShadow({ mode });
+ content.wrappedJSObject.divInShadow =
+ content.document.createElement("div");
+ content.wrappedJSObject.divInShadow.setAttribute("tabindex", "0");
+ shadow.appendChild(content.wrappedJSObject.divInShadow);
+ content.wrappedJSObject.divInShadow.focus();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `test_setting_content_editable_of_body_when_shadow_DOM_has_focus(${aMode}): IME should be disabled when non-editable <div> in a shadow DOM has focus`
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.setAttribute("contenteditable", "");
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ // todo_is because of bug 1807597. Gecko does not update focus when focused
+ // element becomes an editable child. Therefore, cannot initialize
+ // HTMLEditor with the new editing host.
+ todo_is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ `test_setting_content_editable_of_body_when_shadow_DOM_has_focus(${aMode}): IME should be enabled when the <body> becomes editable`
+ );
+ todo(
+ tipWrapper.IMEHasFocus,
+ `test_setting_content_editable_of_body_when_shadow_DOM_has_focus(${aMode}): IME should have focus when the <body> becomes editable`
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.removeAttribute("contenteditable");
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `test_setting_content_editable_of_body_when_shadow_DOM_has_focus)${aMode}): IME should be disabled when the <body> becomes not editable`
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ `test_setting_content_editable_of_body_when_shadow_DOM_has_focus)${aMode}): IME should not have focus when the <body> becomes not editable`
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("div").remove();
+ content.document.body.appendChild(
+ content.document.createElement("div")
+ );
+ });
+ }
+
+ async function test_setting_designMode_when_shadow_DOM_has_focus(aMode) {
+ await SpecialPowers.spawn(browser, [aMode], mode => {
+ const div = content.document.querySelector("div");
+ const shadow = div.attachShadow({ mode });
+ content.wrappedJSObject.divInShadow =
+ content.document.createElement("div");
+ content.wrappedJSObject.divInShadow.setAttribute("tabindex", "0");
+ shadow.appendChild(content.wrappedJSObject.divInShadow);
+ content.wrappedJSObject.divInShadow.focus();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should be disabled when non-editable <div> in a shadow DOM has focus`
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.designMode = "on";
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should stay disabled when designMode is set`
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should not have focus when designMode is set`
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.wrappedJSObject.divInShadow.setAttribute(
+ "contenteditable",
+ ""
+ );
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ // todo_is because of bug 1807597. Gecko does not update focus when focused
+ // document is into the design mode. Therefore, cannot initialize
+ // HTMLEditor with the document node properly.
+ todo_is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should be enabled when focused <div> in a shadow DOM becomes editable`
+ );
+ todo(
+ tipWrapper.IMEHasFocus,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should have focus when focused <div> in a shadow DOM becomes editable`
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.designMode = "off";
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should be disabled when designMode is unset`
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should not have focus when designMode is unset`
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("div").remove();
+ content.document.body.appendChild(
+ content.document.createElement("div")
+ );
+ });
+ }
+
+ for (const mode of ["open", "closed"]) {
+ await test_setting_content_editable_of_body_when_shadow_DOM_has_focus(
+ mode
+ );
+ await resetIMEStateWithFocusMove();
+ await test_setting_designMode_when_shadow_DOM_has_focus(mode);
+ await resetIMEStateWithFocusMove();
+ }
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_on_focus_move_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_on_focus_move_in_remote_content.js
new file mode 100644
index 0000000000..3916d3d47c
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_on_focus_move_in_remote_content.js
@@ -0,0 +1,128 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_on_focus_move.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_test_ime_state_on_focus_move.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/widget/tests/browser/file_ime_state_tests.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ // isnot is used in file_test_ime_state_on_focus_move.js, but it's not
+ // defined as the alias of Assert.notEqual in browser-chrome tests.
+ // Therefore, we need to define it here.
+ // eslint-disable-next-line no-unused-vars
+ const isnot = Assert.notEqual;
+
+ async function runIMEStateOnFocusMoveTests(aDescription) {
+ await (async function test_IMEState_without_focused_element() {
+ const checker = new IMEStateWhenNoActiveElementTester(aDescription);
+ const expectedData = await SpecialPowers.spawn(
+ browser,
+ [aDescription],
+ description => {
+ const runner =
+ content.wrappedJSObject.createIMEStateWhenNoActiveElementTester(
+ description
+ );
+ return runner.run(content.document, content.window);
+ }
+ );
+ checker.check(expectedData);
+ })();
+ for (
+ let index = 0;
+ index < IMEStateOnFocusMoveTester.numberOfTests;
+ ++index
+ ) {
+ const checker = new IMEStateOnFocusMoveTester(aDescription, index);
+ const expectedData = await SpecialPowers.spawn(
+ browser,
+ [aDescription, index],
+ (description, aIndex) => {
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateOnFocusMoveTester(
+ description,
+ aIndex,
+ content.window
+ );
+ return content.wrappedJSObject.runner.prepareToRun(
+ content.document.querySelector("div")
+ );
+ }
+ );
+ checker.prepareToCheck(expectedData, tipWrapper);
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.run();
+ });
+ checker.check(expectedData);
+
+ if (checker.canTestOpenCloseState(expectedData)) {
+ for (const defaultOpenState of [false, true]) {
+ const expectedOpenStateData = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.prepareToRunOpenCloseTest(
+ content.document.querySelector("div")
+ );
+ }
+ );
+ checker.prepareToCheckOpenCloseTest(
+ defaultOpenState,
+ expectedOpenStateData
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runOpenCloseTest();
+ });
+ checker.checkOpenCloseTest(expectedOpenStateData);
+ }
+ }
+ await SpecialPowers.spawn(browser, [], () => {
+ content.wrappedJSObject.runner.destroy();
+ content.wrappedJSObject.runner = undefined;
+ });
+ checker.destroy();
+ } // for loop iterating test of IMEStateOnFocusMoveTester
+ } // definition of runIMEStateOnFocusMoveTests
+
+ // test for normal contents.
+ await runIMEStateOnFocusMoveTests("in non-editable container");
+
+ // test for removing contentEditable
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.document
+ .querySelector("div")
+ .setAttribute("contenteditable", "true");
+ content.document.querySelector("div").focus();
+ await new Promise(resolve =>
+ content.window.requestAnimationFrame(() =>
+ content.window.requestAnimationFrame(resolve)
+ )
+ );
+ content.document
+ .querySelector("div")
+ .removeAttribute("contenteditable");
+ });
+ await runIMEStateOnFocusMoveTests(
+ "after removing contenteditable from the container"
+ );
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_on_input_type_change_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_on_input_type_change_in_remote_content.js
new file mode 100644
index 0000000000..2a4bc4c332
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_on_input_type_change_in_remote_content.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_on_input_type_change.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_test_ime_state_on_input_type_change.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/widget/tests/browser/file_ime_state_tests.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ for (
+ let srcIndex = 0;
+ srcIndex < IMEStateOnInputTypeChangeTester.numberOfTests;
+ srcIndex++
+ ) {
+ const tester = new IMEStateOnInputTypeChangeTester(srcIndex);
+ for (
+ let destIndex = 0;
+ destIndex < IMEStateOnInputTypeChangeTester.numberOfTests;
+ destIndex++
+ ) {
+ const expectedResultBefore = await SpecialPowers.spawn(
+ browser,
+ [srcIndex, destIndex],
+ (aSrcIndex, aDestIndex) => {
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateOnInputTypeChangeTester(
+ aSrcIndex
+ );
+ return content.wrappedJSObject.runner.prepareToRun(
+ aDestIndex,
+ content.window,
+ content.document.body
+ );
+ }
+ );
+ if (expectedResultBefore === false) {
+ continue;
+ }
+ tester.checkBeforeRun(expectedResultBefore, tipWrapper);
+ const expectedResult = await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.run();
+ });
+ tester.checkResult(expectedResultBefore, expectedResult);
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.clear();
+ });
+ tipWrapper.clearFocusBlurNotifications();
+ }
+ tester.clear();
+ }
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_on_readonly_change_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_on_readonly_change_in_remote_content.js
new file mode 100644
index 0000000000..a0c0019328
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_on_readonly_change_in_remote_content.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_on_readonly_change.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_test_ime_state_on_readonly_change.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/widget/tests/browser/file_ime_state_tests.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ const tester = new IMEStateOnReadonlyChangeTester();
+ for (
+ let i = 0;
+ i < IMEStateOnReadonlyChangeTester.numberOfTextControlTypes;
+ i++
+ ) {
+ const expectedResultBefore = await SpecialPowers.spawn(
+ browser,
+ [i],
+ aIndex => {
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateOnReadonlyChangeTester(
+ aIndex
+ );
+ return content.wrappedJSObject.runner.prepareToRun(
+ aIndex,
+ content.window,
+ content.document.body
+ );
+ }
+ );
+ tester.checkBeforeRun(expectedResultBefore, tipWrapper);
+ const expectedResultOfMakingTextControlReadonly =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeTextControlReadonly();
+ });
+ tester.checkResultOfMakingTextControlReadonly(
+ expectedResultOfMakingTextControlReadonly
+ );
+ const expectedResultOfMakingTextControlEditable =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeTextControlEditable();
+ });
+ tester.checkResultOfMakingTextControlEditable(
+ expectedResultOfMakingTextControlEditable
+ );
+ tipWrapper.clearFocusBlurNotifications();
+ tester.clear();
+ }
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_scrollbar_colors.js b/widget/tests/browser/browser_test_scrollbar_colors.js
new file mode 100644
index 0000000000..2152412071
--- /dev/null
+++ b/widget/tests/browser/browser_test_scrollbar_colors.js
@@ -0,0 +1,146 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+
+"use strict";
+
+add_task(async () => {
+ const URL_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://mochi.test:8888/"
+ );
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ URL_ROOT + "helper_scrollbar_colors.html"
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ ChromeUtils.defineESModuleGetters(this, {
+ WindowsVersionInfo:
+ "resource://gre/modules/components-utils/WindowsVersionInfo.sys.mjs",
+ });
+
+ Services.scriptloader.loadSubScript(
+ "chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js",
+ this
+ );
+
+ // == Native theme ==
+
+ const WIN_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 6889],
+ // Blue scrollbar face
+ ["0,0,255", 540],
+ // Cyan scrollbar track
+ ["0,255,255", 2487],
+ ];
+
+ const MAC_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 7225],
+ // Blue scrollbar face
+ ["0,0,255", 416],
+ // Cyan scrollbar track
+ ["0,255,255", 1760],
+ ];
+
+ // Values have been updated from 8100, 720, 1180 for linux1804
+ const LINUX_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 7744],
+ // Blue scrollbar face
+ ["0,0,255", 1104],
+ // Cyan scrollbar track
+ ["0,255,255", 1152],
+ ];
+
+ // == Non-native theme ==
+
+ const WIN10_NNT_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 6889],
+ // Blue scrollbar face
+ ["0,0,255", 612],
+ // Cyan scrollbar track
+ ["0,255,255", 2355],
+ ];
+
+ const WIN11_NNT_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 6889],
+ // Blue scrollbar face
+ ["0,0,255", 324],
+ // Cyan scrollbar track
+ ["0,255,255", 2787],
+ ];
+
+ const MAC_NNT_REFERENCES = MAC_REFERENCES;
+
+ const LINUX_NNT_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 7744],
+ // Blue scrollbar face
+ ["0,0,255", 368],
+ // Cyan scrollbar track
+ ["0,255,255", 1852],
+ ];
+
+ function countPixels(canvas) {
+ let result = new Map();
+ let ctx = canvas.getContext("2d");
+ let image = ctx.getImageData(0, 0, canvas.width, canvas.height);
+ let data = image.data;
+ let size = image.width * image.height;
+ for (let i = 0; i < size; i++) {
+ let key = data.subarray(i * 4, i * 4 + 3).toString();
+ let value = result.get(key);
+ value = value ? value : 0;
+ result.set(key, value + 1);
+ }
+ return result;
+ }
+
+ let outer = content.document.querySelector(".outer");
+ let outerRect = outer.getBoundingClientRect();
+ if (
+ outerRect.width == outer.clientWidth &&
+ outerRect.height == outer.clientHeight
+ ) {
+ ok(true, "Using overlay scrollbar, skip this test");
+ return;
+ }
+ content.document.querySelector("#style").textContent = `
+ .outer { scrollbar-color: blue cyan; }
+ `;
+
+ let canvas = snapshotRect(content.window, outerRect);
+ let stats = countPixels(canvas);
+ let isNNT = SpecialPowers.getBoolPref("widget.non-native-theme.enabled");
+
+ let references;
+ if (content.navigator.platform.startsWith("Win")) {
+ if (!isNNT) {
+ references = WIN_REFERENCES;
+ } else if (WindowsVersionInfo.get().buildNumber >= 22000) {
+ // Windows 11 NNT
+ references = WIN11_NNT_REFERENCES;
+ } else {
+ // Windows 10 NNT
+ references = WIN10_NNT_REFERENCES;
+ }
+ } else if (content.navigator.platform.startsWith("Mac")) {
+ references = isNNT ? MAC_NNT_REFERENCES : MAC_REFERENCES;
+ } else if (content.navigator.platform.startsWith("Linux")) {
+ references = isNNT ? LINUX_NNT_REFERENCES : LINUX_REFERENCES;
+ } else {
+ ok(false, "Unsupported platform");
+ }
+ for (let [color, count] of references) {
+ let value = stats.get(color);
+ is(value, count, `Pixel count of color ${color}`);
+ }
+ });
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/widget/tests/browser/browser_test_swipe_gesture.js b/widget/tests/browser/browser_test_swipe_gesture.js
new file mode 100644
index 0000000000..e66e2f10b9
--- /dev/null
+++ b/widget/tests/browser/browser_test_swipe_gesture.js
@@ -0,0 +1,1275 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+
+"use strict";
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js",
+ this
+);
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js",
+ this
+);
+
+async function waitForWhile() {
+ await new Promise(resolve => {
+ requestIdleCallback(resolve, { timeout: 300 });
+ });
+ await new Promise(r => requestAnimationFrame(r));
+}
+
+requestLongerTimeout(2);
+
+add_task(async () => {
+ // Set the default values for an OS that supports swipe to nav, except for
+ // whole-page-pixel-size which varies by OS, we vary it in differente tests
+ // in this file.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ // Set the velocity-contribution to 0 so we can exactly control the
+ // values in the swipe tracker via the delta in the events that we send.
+ ["widget.swipe.success-velocity-contribution", 0.0],
+ ["widget.swipe.whole-page-pixel-size", 550.0],
+ ],
+ });
+
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!gBrowser.webNavigation.canGoForward);
+
+ let wheelEventCount = 0;
+ tab.linkedBrowser.addEventListener("wheel", () => {
+ wheelEventCount++;
+ });
+
+ // Send a pan that starts a navigate back but doesn't have enough delta to do
+ // anything. Don't send the pan end because we want to check the opacity
+ // before the MSD animation in SwipeTracker starts which can temporarily put
+ // us at 1 opacity.
+ await panLeftToRightBegin(tab.linkedBrowser, 100, 100, 0.9);
+ await panLeftToRightUpdate(tab.linkedBrowser, 100, 100, 0.9);
+
+ // Check both getComputedStyle instead of element.style.opacity because we use a transition on the opacity.
+ let computedOpacity = window
+ .getComputedStyle(gHistorySwipeAnimation._prevBox)
+ .getPropertyValue("opacity");
+ is(computedOpacity, "1", "opacity of prevbox is 1");
+ let opacity = gHistorySwipeAnimation._prevBox.style.opacity;
+ is(opacity, "", "opacity style isn't explicitly set");
+
+ const isTranslatingIcon =
+ Services.prefs.getIntPref(
+ "browser.swipe.navigation-icon-start-position",
+ 0
+ ) != 0 ||
+ Services.prefs.getIntPref(
+ "browser.swipe.navigation-icon-end-position",
+ 0
+ ) != 0;
+ if (isTranslatingIcon != 0) {
+ isnot(
+ window
+ .getComputedStyle(gHistorySwipeAnimation._prevBox)
+ .getPropertyValue("translate"),
+ "none",
+ "translate of prevbox is not `none` during gestures"
+ );
+ }
+
+ await panLeftToRightEnd(tab.linkedBrowser, 100, 100, 0.9);
+
+ // NOTE: We only get a wheel event for the beginPhase, rest of events have
+ // been captured by the swipe gesture module.
+ is(wheelEventCount, 1, "Received a wheel event");
+
+ await waitForWhile();
+ // Make sure any navigation didn't happen.
+ is(tab.linkedBrowser.currentURI.spec, secondPage);
+
+ // Try to navigate backward.
+ wheelEventCount = 0;
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 1);
+ // NOTE: We only get a wheel event for the beginPhase, rest of events have
+ // been captured by the swipe gesture module.
+ is(wheelEventCount, 1, "Received a wheel event");
+
+ // The element.style opacity will be 0 because we set it to 0 on successful navigation, however
+ // we have a tranisition on it so the computed style opacity will still be 1 because the transition hasn't started yet.
+ computedOpacity = window
+ .getComputedStyle(gHistorySwipeAnimation._prevBox)
+ .getPropertyValue("opacity");
+ ok(computedOpacity == 1, "computed opacity of prevbox is 1");
+ opacity = gHistorySwipeAnimation._prevBox.style.opacity;
+ ok(opacity == 0, "element.style opacity of prevbox 0");
+
+ if (isTranslatingIcon) {
+ // We don't have a transition for translate property so that we still have
+ // some amount of translate.
+ isnot(
+ window
+ .getComputedStyle(gHistorySwipeAnimation._prevBox)
+ .getPropertyValue("translate"),
+ "none",
+ "translate of prevbox is not `none` during the opacity transition"
+ );
+ }
+
+ // Make sure the gesture triggered going back to the previous page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+// Same test as above but whole-page-pixel-size is increased and the multipliers passed to panLeftToRight correspondingly increased.
+add_task(async () => {
+ // Set the default values for an OS that supports swipe to nav, except for
+ // whole-page-pixel-size which varies by OS, we vary it in differente tests
+ // in this file.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ // Set the velocity-contribution to 0 so we can exactly control the
+ // values in the swipe tracker via the delta in the events that we send.
+ ["widget.swipe.success-velocity-contribution", 0.0],
+ ["widget.swipe.whole-page-pixel-size", 1100.0],
+ ],
+ });
+
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!gBrowser.webNavigation.canGoForward);
+
+ let wheelEventCount = 0;
+ tab.linkedBrowser.addEventListener("wheel", () => {
+ wheelEventCount++;
+ });
+
+ // Send a pan that starts a navigate back but doesn't have enough delta to do
+ // anything. Don't send the pan end because we want to check the opacity
+ // before the MSD animation in SwipeTracker starts which can temporarily put
+ // us at 1 opacity.
+ await panLeftToRightBegin(tab.linkedBrowser, 100, 100, 1.8);
+ await panLeftToRightUpdate(tab.linkedBrowser, 100, 100, 1.8);
+
+ // Check both getComputedStyle instead of element.style.opacity because we use a transition on the opacity.
+ let computedOpacity = window
+ .getComputedStyle(gHistorySwipeAnimation._prevBox)
+ .getPropertyValue("opacity");
+ is(computedOpacity, "1", "opacity of prevbox is 1");
+ let opacity = gHistorySwipeAnimation._prevBox.style.opacity;
+ is(opacity, "", "opacity style isn't explicitly set");
+
+ await panLeftToRightEnd(tab.linkedBrowser, 100, 100, 1.8);
+
+ // NOTE: We only get a wheel event for the beginPhase, rest of events have
+ // been captured by the swipe gesture module.
+ is(wheelEventCount, 1, "Received a wheel event");
+
+ await waitForWhile();
+ // Make sure any navigation didn't happen.
+ is(tab.linkedBrowser.currentURI.spec, secondPage);
+
+ // Try to navigate backward.
+ wheelEventCount = 0;
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 2);
+ // NOTE: We only get a wheel event for the beginPhase, rest of events have
+ // been captured by the swipe gesture module.
+ is(wheelEventCount, 1, "Received a wheel event");
+
+ // The element.style opacity will be 0 because we set it to 0 on successful navigation, however
+ // we have a tranisition on it so the computed style opacity will still be 1 because the transition hasn't started yet.
+ computedOpacity = window
+ .getComputedStyle(gHistorySwipeAnimation._prevBox)
+ .getPropertyValue("opacity");
+ ok(computedOpacity == 1, "computed opacity of prevbox is 1");
+ opacity = gHistorySwipeAnimation._prevBox.style.opacity;
+ ok(opacity == 0, "element.style opacity of prevbox 0");
+
+ // Make sure the gesture triggered going back to the previous page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ // Set the default values for an OS that supports swipe to nav, except for
+ // whole-page-pixel-size which varies by OS, we vary it in different tests
+ // in this file.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ // Set the velocity-contribution to 1 (default 0.05f) so velocity is a
+ // large contribution to the success value in SwipeTracker.cpp so it
+ // pushes us into success territory without going into success territory
+ // purely from th deltas.
+ ["widget.swipe.success-velocity-contribution", 2.0],
+ ["widget.swipe.whole-page-pixel-size", 550.0],
+ ],
+ });
+
+ async function runTest() {
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!gBrowser.webNavigation.canGoForward);
+
+ let wheelEventCount = 0;
+ tab.linkedBrowser.addEventListener("wheel", () => {
+ wheelEventCount++;
+ });
+
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let startTime = performance.now();
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 0.2);
+ let endTime = performance.now();
+
+ // If sending the events took too long then we might not have been able
+ // to generate enough velocity.
+ // The value 230 was picked based on try runs, in particular test verify
+ // runs on mac were the long pole, and when we get times near this we can
+ // still achieve the required velocity.
+ if (endTime - startTime > 230) {
+ BrowserTestUtils.removeTab(tab);
+ return false;
+ }
+
+ // NOTE: We only get a wheel event for the beginPhase, rest of events have
+ // been captured by the swipe gesture module.
+ is(wheelEventCount, 1, "Received a wheel event");
+
+ // The element.style opacity will be 0 because we set it to 0 on successful navigation, however
+ // we have a tranisition on it so the computed style opacity will still be 1 because the transition hasn't started yet.
+ let computedOpacity = window
+ .getComputedStyle(gHistorySwipeAnimation._prevBox)
+ .getPropertyValue("opacity");
+ ok(computedOpacity == 1, "computed opacity of prevbox is 1");
+ let opacity = gHistorySwipeAnimation._prevBox.style.opacity;
+ ok(opacity == 0, "element.style opacity of prevbox 0");
+
+ // Make sure the gesture triggered going back to the previous page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ BrowserTestUtils.removeTab(tab);
+
+ return true;
+ }
+
+ let numTries = 15;
+ while (numTries > 0) {
+ await new Promise(r => requestAnimationFrame(r));
+ await new Promise(resolve => requestIdleCallback(resolve));
+ await new Promise(r => requestAnimationFrame(r));
+
+ // runTest return value indicates if test was able to run to the end.
+ if (await runTest()) {
+ break;
+ }
+ numTries--;
+ }
+ ok(numTries > 0, "never ran the test");
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ // Set the default values for an OS that supports swipe to nav, except for
+ // whole-page-pixel-size which varies by OS, we vary it in differente tests
+ // in this file.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ // Set the velocity-contribution to 0 so we can exactly control the
+ // values in the swipe tracker via the delta in the events that we send.
+ ["widget.swipe.success-velocity-contribution", 0.0],
+ ["widget.swipe.whole-page-pixel-size", 550.0],
+ ],
+ });
+
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!gBrowser.webNavigation.canGoForward);
+
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 2);
+
+ // Make sure the gesture triggered going back to the previous page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ while (
+ gHistorySwipeAnimation._prevBox != null ||
+ gHistorySwipeAnimation._nextBox != null
+ ) {
+ await new Promise(r => requestAnimationFrame(r));
+ }
+
+ ok(
+ gHistorySwipeAnimation._prevBox == null &&
+ gHistorySwipeAnimation._nextBox == null
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ // Set the velocity-contribution to 0 so we can exactly control the
+ // values in the swipe tracker via the delta in the events that we send.
+ ["widget.swipe.success-velocity-contribution", 0.0],
+ ["widget.swipe.whole-page-pixel-size", 550.0],
+ ],
+ });
+
+ function swipeGestureEndPromise() {
+ return new Promise(resolve => {
+ let promiseObserver = {
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "MozSwipeGestureEnd":
+ gBrowser.tabbox.removeEventListener(
+ "MozSwipeGestureEnd",
+ promiseObserver,
+ true
+ );
+ resolve();
+ break;
+ }
+ },
+ };
+ gBrowser.tabbox.addEventListener(
+ "MozSwipeGestureEnd",
+ promiseObserver,
+ true
+ );
+ });
+ }
+
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!gBrowser.webNavigation.canGoForward);
+
+ let numSwipeGestureEndEvents = 0;
+ var anObserver = {
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "MozSwipeGestureEnd":
+ numSwipeGestureEndEvents++;
+ break;
+ }
+ },
+ };
+
+ gBrowser.tabbox.addEventListener("MozSwipeGestureEnd", anObserver, true);
+
+ let gestureEndPromise = swipeGestureEndPromise();
+
+ is(
+ numSwipeGestureEndEvents,
+ 0,
+ "expected no MozSwipeGestureEnd got " + numSwipeGestureEndEvents
+ );
+
+ // Send a pan that starts a navigate back but doesn't have enough delta to do
+ // anything.
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 0.9);
+
+ await waitForWhile();
+ // Make sure any navigation didn't happen.
+ is(tab.linkedBrowser.currentURI.spec, secondPage);
+ // end event comes after a swipe that does not navigate
+ await gestureEndPromise;
+ is(
+ numSwipeGestureEndEvents,
+ 1,
+ "expected one MozSwipeGestureEnd got " + numSwipeGestureEndEvents
+ );
+
+ // Try to navigate backward.
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+
+ gestureEndPromise = swipeGestureEndPromise();
+
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 1);
+
+ // Make sure the gesture triggered going back to the previous page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ await gestureEndPromise;
+
+ is(
+ numSwipeGestureEndEvents,
+ 2,
+ "expected one MozSwipeGestureEnd got " + (numSwipeGestureEndEvents - 1)
+ );
+
+ gBrowser.tabbox.removeEventListener("MozSwipeGestureEnd", anObserver, true);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ // success-velocity-contribution is very high and whole-page-pixel-size is
+ // very low so that one swipe goes over the threshold asap.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ ["widget.swipe.success-velocity-contribution", 999999.0],
+ ["widget.swipe.whole-page-pixel-size", 1.0],
+ ],
+ });
+
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!gBrowser.webNavigation.canGoForward);
+
+ // Navigate backward.
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+
+ await panLeftToRightBegin(tab.linkedBrowser, 100, 100, 100);
+
+ ok(gHistorySwipeAnimation._prevBox != null, "should have prevbox");
+ let transitionCancelPromise = new Promise(resolve => {
+ gHistorySwipeAnimation._prevBox.addEventListener(
+ "transitioncancel",
+ event => {
+ if (
+ event.propertyName == "opacity" &&
+ event.target == gHistorySwipeAnimation._prevBox
+ ) {
+ resolve();
+ }
+ },
+ { once: true }
+ );
+ });
+ let transitionStartPromise = new Promise(resolve => {
+ gHistorySwipeAnimation._prevBox.addEventListener(
+ "transitionstart",
+ event => {
+ if (
+ event.propertyName == "opacity" &&
+ event.target == gHistorySwipeAnimation._prevBox
+ ) {
+ resolve();
+ }
+ },
+ { once: true }
+ );
+ });
+
+ await panLeftToRightUpdate(tab.linkedBrowser, 100, 100, 100);
+ await panLeftToRightEnd(tab.linkedBrowser, 100, 100, 100);
+
+ // Make sure the gesture triggered going back to the previous page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ await Promise.any([transitionStartPromise, transitionCancelPromise]);
+
+ await TestUtils.waitForCondition(() => {
+ return (
+ gHistorySwipeAnimation._prevBox == null &&
+ gHistorySwipeAnimation._nextBox == null
+ );
+ });
+
+ // Navigate forward and check the forward navigation icon box state.
+ startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ secondPage
+ );
+ stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ secondPage
+ );
+
+ await panRightToLeftBegin(tab.linkedBrowser, 100, 100, 100);
+
+ ok(gHistorySwipeAnimation._nextBox != null, "should have nextbox");
+ transitionCancelPromise = new Promise(resolve => {
+ gHistorySwipeAnimation._nextBox.addEventListener(
+ "transitioncancel",
+ event => {
+ if (
+ event.propertyName == "opacity" &&
+ event.target == gHistorySwipeAnimation._nextBox
+ ) {
+ resolve();
+ }
+ }
+ );
+ });
+ transitionStartPromise = new Promise(resolve => {
+ gHistorySwipeAnimation._nextBox.addEventListener(
+ "transitionstart",
+ event => {
+ if (
+ event.propertyName == "opacity" &&
+ event.target == gHistorySwipeAnimation._nextBox
+ ) {
+ resolve();
+ }
+ }
+ );
+ });
+
+ await panRightToLeftUpdate(tab.linkedBrowser, 100, 100, 100);
+ await panRightToLeftEnd(tab.linkedBrowser, 100, 100, 100);
+
+ // Make sure the gesture triggered going forward to the next page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoBack);
+
+ await Promise.any([transitionStartPromise, transitionCancelPromise]);
+
+ await TestUtils.waitForCondition(() => {
+ return (
+ gHistorySwipeAnimation._nextBox == null &&
+ gHistorySwipeAnimation._prevBox == null
+ );
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+// A simple test case on RTL.
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ ["widget.swipe.success-velocity-contribution", 0.5],
+ ["intl.l10n.pseudo", "bidi"],
+ ],
+ });
+
+ const newWin = await BrowserTestUtils.openNewBrowserWindow();
+
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ newWin.gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(newWin.gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!newWin.gBrowser.webNavigation.canGoForward);
+
+ // Make sure that our gesture support stuff has been initialized in the new
+ // browser window.
+ await TestUtils.waitForCondition(() => {
+ return newWin.gHistorySwipeAnimation.active;
+ });
+
+ // Try to navigate backward.
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+ await panRightToLeft(tab.linkedBrowser, 100, 100, 1);
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(newWin.gBrowser.webNavigation.canGoForward);
+
+ // Now try to navigate forward again.
+ startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ secondPage
+ );
+ stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ secondPage
+ );
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 1);
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(newWin.gBrowser.webNavigation.canGoBack);
+
+ await BrowserTestUtils.closeWindow(newWin);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ ["widget.swipe.success-velocity-contribution", 0.5],
+ ["apz.overscroll.enabled", true],
+ ["apz.test.logging_enabled", true],
+ ],
+ });
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:about",
+ true /* waitForLoad */
+ );
+
+ const URL_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://mochi.test:8888/"
+ );
+ BrowserTestUtils.loadURIString(
+ tab.linkedBrowser,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false /* includeSubFrames */,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ // Set `overscroll-behavior-x: contain` and flush it.
+ content.document.documentElement.style.overscrollBehaviorX = "contain";
+ content.document.documentElement.getBoundingClientRect();
+ await content.wrappedJSObject.promiseApzFlushedRepaints();
+ });
+
+ // Start a pan gesture but keep touching.
+ await panLeftToRightBegin(tab.linkedBrowser, 100, 100, 2);
+
+ // Flush APZ pending requests to make sure the pan gesture has been processed.
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await content.wrappedJSObject.promiseApzFlushedRepaints();
+ });
+
+ const isOverscrolled = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ const scrollId = SpecialPowers.DOMWindowUtils.getViewId(
+ content.document.scrollingElement
+ );
+ const data = SpecialPowers.DOMWindowUtils.getCompositorAPZTestData();
+ return data.additionalData.some(entry => {
+ return (
+ entry.key == scrollId &&
+ entry.value.split(",").includes("overscrolled")
+ );
+ });
+ }
+ );
+
+ ok(isOverscrolled, "The root scroller should have overscrolled");
+
+ // Finish the pan gesture.
+ await panLeftToRightUpdate(tab.linkedBrowser, 100, 100, 2);
+ await panLeftToRightEnd(tab.linkedBrowser, 100, 100, 2);
+
+ // And wait a while to give a chance to navigate.
+ await waitForWhile();
+
+ // Make sure any navigation didn't happen.
+ is(tab.linkedBrowser.currentURI.spec, URL_ROOT + "helper_swipe_gesture.html");
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+// A test case to make sure the short circuit path for swipe-to-navigations in
+// APZ works, i.e. cases where we know for sure that the target APZC for a given
+// pan-start event isn't scrollable in the pan-start event direction.
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["apz.overscroll.enabled", true],
+ ],
+ });
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:about",
+ true /* waitForLoad */
+ );
+
+ const URL_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://mochi.test:8888/"
+ );
+ BrowserTestUtils.loadURIString(
+ tab.linkedBrowser,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false /* includeSubFrames */,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+
+ // Make sure the content can allow both of overscrolling and
+ // swipe-to-navigations.
+ const overscrollBehaviorX = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ return content.window.getComputedStyle(content.document.documentElement)
+ .overscrollBehaviorX;
+ }
+ );
+ is(overscrollBehaviorX, "auto");
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+
+ // Start a pan gesture but keep touching.
+ await panLeftToRightBegin(tab.linkedBrowser, 100, 100, 2);
+
+ // The above pan event should invoke a SwipeGestureStart event immediately so
+ // that the swipe-to-navigation icon box should be uncollapsed to show it.
+ ok(!gHistorySwipeAnimation._prevBox.collapsed);
+
+ // Finish the pan gesture, i.e. sending a pan-end event, otherwise a new
+ // pan-start event in the next will also generate a pan-interrupt event which
+ // will break the test.
+ await panLeftToRightUpdate(tab.linkedBrowser, 100, 100, 2);
+ await panLeftToRightEnd(tab.linkedBrowser, 100, 100, 2);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ ["widget.swipe.success-velocity-contribution", 0.5],
+ ["apz.overscroll.enabled", true],
+ ["apz.test.logging_enabled", true],
+ ],
+ });
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:about",
+ true /* waitForLoad */
+ );
+
+ const URL_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://mochi.test:8888/"
+ );
+ BrowserTestUtils.loadURIString(
+ tab.linkedBrowser,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false /* includeSubFrames */,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+
+ // Start a pan gesture but keep touching.
+ await panLeftToRightBegin(tab.linkedBrowser, 100, 100, 2);
+
+ // Flush APZ pending requests to make sure the pan gesture has been processed.
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await content.wrappedJSObject.promiseApzFlushedRepaints();
+ });
+
+ const isOverscrolled = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ const scrollId = SpecialPowers.DOMWindowUtils.getViewId(
+ content.document.scrollingElement
+ );
+ const data = SpecialPowers.DOMWindowUtils.getCompositorAPZTestData();
+ return data.additionalData.some(entry => {
+ return entry.key == scrollId && entry.value.includes("overscrolled");
+ });
+ }
+ );
+
+ ok(!isOverscrolled, "The root scroller should not have overscrolled");
+
+ await panLeftToRightEnd(tab.linkedBrowser, 100, 100, 0);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ ["widget.swipe.success-velocity-contribution", 0.5],
+ ],
+ });
+
+ // Load three pages and go to the second page so that it can be navigated
+ // to both back and forward.
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:about",
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, "about:mozilla");
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false /* includeSubFrames */,
+ "about:mozilla"
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, "about:home");
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false /* includeSubFrames */,
+ "about:home"
+ );
+
+ gBrowser.goBack();
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false /* includeSubFrames */,
+ "about:mozilla"
+ );
+
+ // Make sure we can go back and go forward.
+ ok(gBrowser.webNavigation.canGoBack);
+ ok(gBrowser.webNavigation.canGoForward);
+
+ // Start a history back pan gesture but keep touching.
+ await panLeftToRightBegin(tab.linkedBrowser, 100, 100, 1);
+
+ ok(
+ !gHistorySwipeAnimation._prevBox.collapsed,
+ "The icon box for the previous navigation should NOT be collapsed"
+ );
+ ok(
+ gHistorySwipeAnimation._nextBox.collapsed,
+ "The icon box for the next navigation should be collapsed"
+ );
+
+ // Pan back to the opposite direction so that the gesture should be cancelled.
+ // eslint-disable-next-line no-undef
+ await NativePanHandler.promiseNativePanEvent(
+ tab.linkedBrowser,
+ 100,
+ 100,
+ // eslint-disable-next-line no-undef
+ NativePanHandler.delta,
+ 0,
+ // eslint-disable-next-line no-undef
+ NativePanHandler.updatePhase
+ );
+
+ ok(
+ gHistorySwipeAnimation._prevBox.collapsed,
+ "The icon box for the previous navigation should be collapsed"
+ );
+ ok(
+ gHistorySwipeAnimation._nextBox.collapsed,
+ "The icon box for the next navigation should be collapsed"
+ );
+
+ await panLeftToRightEnd(tab.linkedBrowser, 100, 100, 0);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ ["widget.swipe.success-velocity-contribution", 0.5],
+ ["apz.overscroll.enabled", true],
+ ["apz.overscroll.damping", 5.0],
+ ["apz.content_response_timeout", 0],
+ ],
+ });
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:about",
+ true /* waitForLoad */
+ );
+
+ const URL_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://mochi.test:8888/"
+ );
+
+ // Load a horizontal scrollable content.
+ BrowserTestUtils.loadURIString(
+ tab.linkedBrowser,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false /* includeSubFrames */,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+
+ // Shift the horizontal scroll position slightly to make the content
+ // overscrollable.
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ content.document.documentElement.scrollLeft = 1;
+ content.document.documentElement.getBoundingClientRect();
+ await content.wrappedJSObject.promiseApzFlushedRepaints();
+ });
+
+ // Swipe horizontally to overscroll.
+ await panLeftToRight(tab.linkedBrowser, 1, 100, 1);
+
+ // Swipe again over the overscroll gutter.
+ await panLeftToRight(tab.linkedBrowser, 1, 100, 1);
+
+ // Wait the overscroll gutter is restored.
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ // For some reasons using functions in apz_test_native_event_utils.js
+ // sometimes causes "TypeError content.wrappedJSObject.XXXX is not a
+ // function" error, so we observe "APZ:TransformEnd" instead of using
+ // promiseTransformEnd().
+ await new Promise((resolve, reject) => {
+ SpecialPowers.Services.obs.addObserver(function observer(
+ subject,
+ topic,
+ data
+ ) {
+ try {
+ SpecialPowers.Services.obs.removeObserver(observer, topic);
+ resolve([subject, data]);
+ } catch (ex) {
+ SpecialPowers.Services.obs.removeObserver(observer, topic);
+ reject(ex);
+ }
+ },
+ "APZ:TransformEnd");
+ });
+ });
+
+ // Set up an APZ aware event listener and...
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ content.document.documentElement.addEventListener("wheel", e => {}, {
+ passive: false,
+ });
+ await content.wrappedJSObject.promiseApzFlushedRepaints();
+ });
+
+ // Try to swipe back again without overscrolling to make sure swipe-navigation
+ // works with the APZ aware event listener.
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 1);
+
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ "about:about"
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ "about:about"
+ );
+
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+// NOTE: This test listens wheel events so that it causes an overscroll issue
+// (bug 1800022). To avoid the bug, we need to run this test case at the end
+// of this file.
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ ["widget.swipe.success-velocity-contribution", 0.5],
+ ],
+ });
+
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!gBrowser.webNavigation.canGoForward);
+
+ let wheelEventCount = 0;
+ tab.linkedBrowser.addEventListener("wheel", () => {
+ wheelEventCount++;
+ });
+
+ // Try to navigate forward.
+ await panRightToLeft(tab.linkedBrowser, 100, 100, 1);
+ // NOTE: The last endPhase shouldn't fire a wheel event since
+ // its delta is zero.
+ is(wheelEventCount, 2, "Received 2 wheel events");
+
+ await waitForWhile();
+ // Make sure any navigation didn't happen.
+ is(tab.linkedBrowser.currentURI.spec, secondPage);
+
+ // Try to navigate backward.
+ wheelEventCount = 0;
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 1);
+ // NOTE: We only get a wheel event for the beginPhase, rest of events have
+ // been captured by the swipe gesture module.
+ is(wheelEventCount, 1, "Received a wheel event");
+
+ // Make sure the gesture triggered going back to the previous page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ // Now try to navigate forward again.
+ wheelEventCount = 0;
+ startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ secondPage
+ );
+ stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ secondPage
+ );
+ await panRightToLeft(tab.linkedBrowser, 100, 100, 1);
+ is(wheelEventCount, 1, "Received a wheel event");
+
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoBack);
+
+ // Now try to navigate backward again but with preventDefault-ed event
+ // handler.
+ wheelEventCount = 0;
+ let wheelEventListener = event => {
+ event.preventDefault();
+ };
+ tab.linkedBrowser.addEventListener("wheel", wheelEventListener);
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 1);
+ is(wheelEventCount, 3, "Received all wheel events");
+
+ await waitForWhile();
+ // Make sure any navigation didn't happen.
+ is(tab.linkedBrowser.currentURI.spec, secondPage);
+
+ // Now drop the event handler and disable the swipe tracker and try to swipe
+ // again.
+ wheelEventCount = 0;
+ tab.linkedBrowser.removeEventListener("wheel", wheelEventListener);
+ await SpecialPowers.pushPrefEnv({
+ set: [["widget.disable-swipe-tracker", true]],
+ });
+
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 1);
+ is(wheelEventCount, 3, "Received all wheel events");
+
+ await waitForWhile();
+ // Make sure any navigation didn't happen.
+ is(tab.linkedBrowser.currentURI.spec, secondPage);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/widget/tests/browser/file_ime_state_tests.html b/widget/tests/browser/file_ime_state_tests.html
new file mode 100644
index 0000000000..d6b63f1e52
--- /dev/null
+++ b/widget/tests/browser/file_ime_state_tests.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<html style="ime-mode: disabled;">
+<head>
+<meta charset="utf-8">
+<script src="file_ime_state_test_helper.js"></script>
+<script src="file_test_ime_state_in_contenteditable_on_readonly_change.js"></script>
+<script src="file_test_ime_state_in_text_control_on_reframe.js"></script>
+<script src="file_test_ime_state_on_focus_move.js"></script>
+<script src="file_test_ime_state_on_input_type_change.js"></script>
+<script src="file_test_ime_state_on_readonly_change.js"></script>
+<script>
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_in_contenteditable_on_readonly_change.js */
+/* import-globals-from ../file_test_ime_state_in_text_control_on_reframe.js */
+/* import-globals-from ../file_test_ime_state_on_focus_move.js */
+/* import-globals-from ../file_test_ime_state_on_input_type_change.js */
+/* import-globals-from ../file_test_ime_state_on_readonly_change.js */
+
+function createIMEStateInContentEditableOnReadonlyChangeTester() {
+ return new IMEStateInContentEditableOnReadonlyChangeTester();
+}
+function createIMEStateOfTextControlInContentEditableOnReadonlyChangeTester() {
+ return new IMEStateOfTextControlInContentEditableOnReadonlyChangeTester();
+}
+function createIMEStateOutsideContentEditableOnReadonlyChangeTester() {
+ return new IMEStateOutsideContentEditableOnReadonlyChangeTester();
+}
+function createIMEStateInTextControlOnReframeTester() {
+ return new IMEStateInTextControlOnReframeTester();
+}
+function createIMEStateWhenNoActiveElementTester(aDescription) {
+ return new IMEStateWhenNoActiveElementTester(aDescription);
+}
+function createIMEStateOnFocusMoveTester(aDescription, aIndex, aWindow = window) {
+ return new IMEStateOnFocusMoveTester(aDescription, aIndex, aWindow);
+}
+function createIMEStateOnInputTypeChangeTester(aSrcIndex) {
+ return new IMEStateOnInputTypeChangeTester(aSrcIndex);
+}
+function createIMEStateOnReadonlyChangeTester() {
+ return new IMEStateOnReadonlyChangeTester();
+}
+</script>
+</head>
+<body style="ime-mode: disabled;"><div style="ime-mode: disabled;"></div></body>
+</html>
diff --git a/widget/tests/browser/helper_scrollbar_colors.html b/widget/tests/browser/helper_scrollbar_colors.html
new file mode 100644
index 0000000000..e6001906e2
--- /dev/null
+++ b/widget/tests/browser/helper_scrollbar_colors.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<meta charset="UTF-8">
+<title>Test for scrollbar-*-color properties</title>
+<style>
+ .outer {
+ width: 100px;
+ height: 100px;
+ background: yellow;
+ overflow: scroll;
+ }
+ .inner {
+ width: 200px;
+ height: 200px;
+ }
+</style>
+<style id="style"></style>
+<div class="outer">
+ <div class="inner">
+ </div>
+</div>
+</html>
diff --git a/widget/tests/browser/helper_swipe_gesture.html b/widget/tests/browser/helper_swipe_gesture.html
new file mode 100644
index 0000000000..1fa79dbbf3
--- /dev/null
+++ b/widget/tests/browser/helper_swipe_gesture.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<script src="/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+<script src="/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js"></script>
+<style>
+html {
+ overflow-x: scroll;
+}
+body {
+ margin: 0;
+}
+div {
+ height: 100vh;
+ width: 110vw;
+ background-color: blue;
+}
+</style>
+<div></div>
+</html>