+
+
+
diff --git a/layout/base/tests/border_radius_hit_testing_iframe.html b/layout/base/tests/border_radius_hit_testing_iframe.html
new file mode 100644
index 0000000000..a0f7ba1b92
--- /dev/null
+++ b/layout/base/tests/border_radius_hit_testing_iframe.html
@@ -0,0 +1,27 @@
+
+border-radius hit testing
+
+
+
+
diff --git a/layout/base/tests/browser.ini b/layout/base/tests/browser.ini
new file mode 100644
index 0000000000..a05e516f40
--- /dev/null
+++ b/layout/base/tests/browser.ini
@@ -0,0 +1,36 @@
+[browser_bug617076.js]
+[browser_bug839103.js]
+support-files =
+ file_bug839103.html
+ bug839103.css
+[browser_bug1701027-1.js]
+support-files =
+ helper_bug1701027-1.html
+[browser_bug1701027-2.js]
+support-files =
+ helper_bug1701027-2.html
+[browser_bug1757410.js]
+run-if = (((os == 'mac') || (os == 'win' && os_version != '6.1' && processor == 'x86_64')) && debug)
+[browser_bug1787079.js]
+run-if = ((os == 'win' && os_version != '6.1' && processor == 'x86_64') && debug)
+[browser_disableDialogs_onbeforeunload.js]
+[browser_onbeforeunload_only_after_interaction.js]
+[browser_onbeforeunload_only_after_interaction_in_frame.js]
+[browser_scroll_into_view_in_out_of_process_iframe.js]
+support-files =
+ test_scroll_into_view_in_oopif.html
+ scroll_into_view_in_child.html
+[browser_visual_viewport_iframe.js]
+support-files =
+ test_visual_viewport_in_oopif.html
+ visual_viewport_in_child.html
+[browser_select_popup_position_in_out_of_process_iframe.js]
+skip-if =
+ (verify && (os == 'mac')) # bug 1627874
+ apple_silicon # Disabled due to bleedover with other tests when run in regular suites; passes in "failures" jobs
+ (os == 'linux' && socketprocess_networking && fission && !debug) # high frequency intermittent
+support-files =
+ !/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
+ !/browser/base/content/test/forms/head.js
+[browser_bug1791083.js]
+skip-if = !sessionHistoryInParent
diff --git a/layout/base/tests/browser_bug1701027-1.js b/layout/base/tests/browser_bug1701027-1.js
new file mode 100644
index 0000000000..bf5a4ffaa3
--- /dev/null
+++ b/layout/base/tests/browser_bug1701027-1.js
@@ -0,0 +1,130 @@
+/* This test is based on
+ https://searchfox.org/mozilla-central/rev/e082df56bbfeaff0f388e7da9da401ff414df18f/gfx/layers/apz/test/mochitest/browser_test_select_zoom.js
+*/
+
+// In order for this test to test the original bug we need:
+// 1) At least e10s enabled so that apz is enabled so we can create an
+// nsDisplayAsyncZoom item
+// (the insertion of this item without marking the required frame modified
+// is what causes the bug in the retained display list merging)
+// 2) a root content document, again so that we can create a nsDisplayAsyncZoom
+// item
+// 3) the root content document cannot have a display port to start
+// (if it has a display port then it gets a nsDisplayAsyncZoom, but we need
+// that to be created after the anonymous content we insert into the
+// document)
+// Point 3) requires the root content document to be in the parent process,
+// since if it is in a content process it will get a displayport for being at
+// the root of a process.
+// Creating an in-process root content document I think is not possible in
+// mochitest-plain. mochitest-chrome does not have e10s enabled. So this has to
+// be a mochitest-browser-chrome test.
+
+// Outline of this test:
+// Open a new tab with a pretty simple content file, that is not scrollable
+// Use the anonymous content api to insert into that content doc
+// Send a mouse click over the content doc
+// The click hits fixed pos content.
+// This sets a displayport on the root scroll frame of the content doc.
+// (This is because we call GetAsyncScrollableAncestorFrame in
+// PrepareForSetTargetAPZCNotification
+// https://searchfox.org/mozilla-central/rev/e082df56bbfeaff0f388e7da9da401ff414df18f/gfx/layers/apz/util/APZCCallbackHelper.cpp#624
+// which passes the SCROLLABLE_FIXEDPOS_FINDS_ROOT flag
+// https://searchfox.org/mozilla-central/rev/e082df56bbfeaff0f388e7da9da401ff414df18f/layout/base/nsLayoutUtils.cpp#2884
+// so starting from fixed pos content means we always find the root scroll
+// frame, whereas if we started from non-fixed content we'd walk pass the root
+// scroll frame becase it isn't scrollable.)
+// Then we have to be careful not to do anything that causes a full display
+// list rebuild.
+// And finally we change the color of the fixed element which covers the whole
+// viewport which causes us to do a partial display list update including the
+// anonymous content, which hits the assert we are aiming to test.
+
+add_task(async function () {
+ function getChromeURL(filename) {
+ let chromeURL = getRootDirectory(gTestPath) + filename;
+ return chromeURL;
+ }
+
+ // We need this otherwise there is a burst animation on the new tab when it
+ // loads and that somehow scrolls a scroll frame, which makes it active,
+ // which makes the scrolled frame an AGR, which means we have multiple AGRs
+ // (the display port makes the root scroll frame active and an AGR) so we hit
+ // this
+ // https://searchfox.org/mozilla-central/rev/e082df56bbfeaff0f388e7da9da401ff414df18f/layout/painting/RetainedDisplayListBuilder.cpp#1179
+ // and are forced to do a full display list rebuild and that prevents us from
+ // testing the original bug.
+ await SpecialPowers.pushPrefEnv({
+ set: [["ui.prefersReducedMotion", 1]],
+ });
+
+ const pageUrl = getChromeURL("helper_bug1701027-1.html");
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ const [theX, theY] = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async () => {
+ content.document.body.offsetWidth;
+
+ await new Promise(r => content.window.requestAnimationFrame(r));
+
+ const rect = content.document
+ .getElementById("fd")
+ .getBoundingClientRect();
+ const x = content.window.mozInnerScreenX + rect.left + rect.width / 2;
+ const y = content.window.mozInnerScreenY + rect.top + rect.height / 2;
+
+ let doc = SpecialPowers.wrap(content.document);
+ var bq = doc.createElement("blockquote");
+ bq.textContent = "This blockquote text.";
+ var div = doc.createElement("div");
+ div.textContent = " This div text.";
+ bq.appendChild(div);
+ var ac = doc.insertAnonymousContent(bq);
+ content.document.body.offsetWidth;
+
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+
+ return [x, y];
+ }
+ );
+
+ EventUtils.synthesizeNativeMouseEvent({
+ type: "click",
+ target: window.document.documentElement,
+ screenX: theX,
+ screenY: theY,
+ });
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ content.document.getElementById("fd").style.backgroundColor = "blue";
+ });
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ content.document.getElementById("fd").style.backgroundColor = "red";
+ });
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ });
+
+ BrowserTestUtils.removeTab(tab);
+
+ ok(true, "didn't crash");
+});
diff --git a/layout/base/tests/browser_bug1701027-2.js b/layout/base/tests/browser_bug1701027-2.js
new file mode 100644
index 0000000000..00e55ca562
--- /dev/null
+++ b/layout/base/tests/browser_bug1701027-2.js
@@ -0,0 +1,126 @@
+/* This test is based on
+ https://searchfox.org/mozilla-central/rev/e082df56bbfeaff0f388e7da9da401ff414df18f/gfx/layers/apz/test/mochitest/browser_test_select_zoom.js
+*/
+
+// In order for this test to test the original bug we need:
+// 1) At least e10s enabled so that apz is enabled so we can create an
+// nsDisplayAsyncZoom item
+// (the insertion of this item without marking the required frame modified
+// is what causes the bug in the retained display list merging)
+// 2) a root content document, again so that we can create a nsDisplayAsyncZoom
+// item
+// 3) the root content document cannot have a display port to start
+// (if it has a display port then it gets a nsDisplayAsyncZoom, but we need
+// that to be created after the anonymous content we insert into the
+// document)
+// Point 3) requires the root content document to be in the parent process,
+// since if it is in a content process it will get a displayport for being at
+// the root of a process.
+// Creating an in-process root content document I think is not possible in
+// mochitest-plain. mochitest-chrome does not have e10s enabled. So this has to
+// be a mochitest-browser-chrome test.
+
+// Outline of this test:
+// Open a new tab with a pretty simple content file, that is not scrollable
+// Use the anonymous content api to insert into that content doc
+// Set a displayport on the root scroll frame of the content doc directly.
+// Then we have to be careful not to do anything that causes a full display
+// list rebuild.
+// And finally we change the color of the fixed element which covers the whole
+// viewport which causes us to do a partial display list update including the
+// anonymous content, which hits the assert we are aiming to test.
+
+add_task(async function () {
+ function getChromeURL(filename) {
+ let chromeURL = getRootDirectory(gTestPath) + filename;
+ return chromeURL;
+ }
+
+ // We need this otherwise there is a burst animation on the new tab when it
+ // loads and that somehow scrolls a scroll frame, which makes it active,
+ // which makes the scrolled frame an AGR, which means we have multiple AGRs
+ // (the display port makes the root scroll frame active and an AGR) so we hit
+ // this
+ // https://searchfox.org/mozilla-central/rev/e082df56bbfeaff0f388e7da9da401ff414df18f/layout/painting/RetainedDisplayListBuilder.cpp#1179
+ // and are forced to do a full display list rebuild and that prevents us from
+ // testing the original bug.
+ await SpecialPowers.pushPrefEnv({
+ set: [["ui.prefersReducedMotion", 1]],
+ });
+
+ const pageUrl = getChromeURL("helper_bug1701027-2.html");
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ const [theX, theY] = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async () => {
+ content.document.body.offsetWidth;
+
+ await new Promise(r => content.window.requestAnimationFrame(r));
+
+ const rect = content.document
+ .getElementById("fd")
+ .getBoundingClientRect();
+ const x = content.window.mozInnerScreenX + rect.left + rect.width / 2;
+ const y = content.window.mozInnerScreenY + rect.top + rect.height / 2;
+
+ let doc = SpecialPowers.wrap(content.document);
+ var bq = doc.createElement("blockquote");
+ bq.textContent = "This blockquote text.";
+ var div = doc.createElement("div");
+ div.textContent = " This div text.";
+ bq.appendChild(div);
+ var ac = doc.insertAnonymousContent(bq);
+ content.document.body.offsetWidth;
+
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+
+ content.window.windowUtils.setDisplayPortMarginsForElement(
+ 0,
+ 0,
+ 0,
+ 0,
+ doc.documentElement,
+ 1
+ );
+ content.window.windowUtils.setDisplayPortBaseForElement(
+ 0,
+ 0,
+ 100,
+ 100,
+ doc.documentElement
+ );
+
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+
+ return [x, y];
+ }
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ content.document.getElementById("fd").style.backgroundColor = "blue";
+ });
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ content.document.getElementById("fd").style.backgroundColor = "red";
+ });
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ });
+
+ BrowserTestUtils.removeTab(tab);
+
+ ok(true, "didn't crash");
+});
diff --git a/layout/base/tests/browser_bug1757410.js b/layout/base/tests/browser_bug1757410.js
new file mode 100644
index 0000000000..59c740e5a8
--- /dev/null
+++ b/layout/base/tests/browser_bug1757410.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const PAGECONTENT =
+ "" +
+ "" +
+ "" +
+ "";
+
+const pageUrl = "data:text/html," + encodeURIComponent(PAGECONTENT);
+
+add_task(async function test() {
+ if (window.devicePixelRatio == 1) {
+ ok(
+ true,
+ "Skip this test since this test is supposed to run on HiDPI mode, " +
+ "the devixePixelRato on this machine is " +
+ window.devicePixelRatio
+ );
+ return;
+ }
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ // Scroll the content a bit.
+ const originalScrollPosition = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async () => {
+ content.document.scrollingElement.scrollTop = 100;
+ return content.document.scrollingElement.scrollTop;
+ }
+ );
+
+ // Disabling HiDPI mode and check the scroll position.
+ SpecialPowers.DOMWindowUtils.setHiDPIMode(false);
+ // Make sure we restore even if this test failed.
+ registerCleanupFunction(() => {
+ SpecialPowers.DOMWindowUtils.restoreHiDPIMode();
+ });
+
+ const scrollPosition = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async () => {
+ return content.document.scrollingElement.scrollTop;
+ }
+ );
+ is(
+ originalScrollPosition,
+ scrollPosition,
+ "The scroll position should be kept"
+ );
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/layout/base/tests/browser_bug1787079.js b/layout/base/tests/browser_bug1787079.js
new file mode 100644
index 0000000000..4ce38f8b69
--- /dev/null
+++ b/layout/base/tests/browser_bug1787079.js
@@ -0,0 +1,86 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const PAGECONTENT =
+ "" +
+ "" +
+ "" +
+ "";
+
+const pageUrl = "data:text/html," + encodeURIComponent(PAGECONTENT);
+
+add_task(async function test() {
+ SpecialPowers.DOMWindowUtils.setHiDPIMode(true);
+ registerCleanupFunction(() => {
+ SpecialPowers.DOMWindowUtils.restoreHiDPIMode();
+ });
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ // Enter fullscreen.
+ let fullscreenChangePromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "fullscreenchange"
+ );
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ content.document.documentElement.requestFullscreen();
+ });
+ await fullscreenChangePromise;
+
+ let [originalInnerWidth, originalInnerHeight] = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ return [content.window.innerWidth, content.window.innerHeight];
+ }
+ );
+
+ // Then change the DPI.
+ let originalPixelRatio = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ return content.window.devicePixelRatio;
+ }
+ );
+ let dpiChangedPromise = TestUtils.waitForCondition(async () => {
+ let pixelRatio = await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ return content.window.devicePixelRatio;
+ });
+ return pixelRatio != originalPixelRatio;
+ }, "Make sure the DPI changed");
+ SpecialPowers.DOMWindowUtils.setHiDPIMode(false);
+ await dpiChangedPromise;
+
+ let [innerWidth, innerHeight] = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ return [content.window.innerWidth, content.window.innerHeight];
+ }
+ );
+
+ ok(
+ originalInnerWidth < innerWidth,
+ "window.innerWidth on a lower DPI should be greater than the original"
+ );
+ ok(
+ originalInnerHeight < innerHeight,
+ "window.innerHeight on a lower DPI should be greater than the original"
+ );
+
+ fullscreenChangePromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "fullscreenchange"
+ );
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ content.document.exitFullscreen();
+ });
+ await fullscreenChangePromise;
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/layout/base/tests/browser_bug1791083.js b/layout/base/tests/browser_bug1791083.js
new file mode 100644
index 0000000000..fcc59f5c18
--- /dev/null
+++ b/layout/base/tests/browser_bug1791083.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const URL =
+ "data:text/html," +
+ "" +
+ "" +
+ "Click Me" +
+ "";
+
+function isAnchorHovered(win) {
+ return SpecialPowers.spawn(
+ win.gBrowser.selectedBrowser,
+ [],
+ async function () {
+ const a = content.document.querySelector("a");
+ return a.matches(":hover");
+ }
+ );
+}
+
+add_task(async function test() {
+ let newWin = await BrowserTestUtils.openNewBrowserWindow();
+
+ // This bug is only reproducible if the cursor is out of the viewport, so
+ // we resize the window to ensure the cursor is out of the viewport.
+
+ // SynthesizeMouse isn't sufficient because it only synthesizes
+ // mouse events without actually moving the cursor permanently to a
+ // new location.
+ newWin.resizeTo(50, 50);
+
+ BrowserTestUtils.loadURIString(newWin.gBrowser.selectedBrowser, URL);
+ await BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser);
+
+ await SpecialPowers.spawn(
+ newWin.gBrowser.selectedBrowser,
+ [],
+ async function () {
+ const a = content.document.querySelector("a");
+ await EventUtils.synthesizeMouseAtCenter(
+ a,
+ { type: "mousemove" },
+ content
+ );
+ }
+ );
+
+ // We've hovered the anchor element.
+ let anchorHovered = await isAnchorHovered(newWin);
+ ok(anchorHovered, "Anchor should be hovered");
+
+ let locationChange = BrowserTestUtils.waitForLocationChange(newWin.gBrowser);
+
+ // Click the anchor to navigate away
+ await SpecialPowers.spawn(
+ newWin.gBrowser.selectedBrowser,
+ [],
+ async function () {
+ const a = content.document.querySelector("a");
+ await EventUtils.synthesizeMouseAtCenter(
+ a,
+ { type: "mousedown" },
+ content
+ );
+ await EventUtils.synthesizeMouseAtCenter(a, { type: "mouseup" }, content);
+ }
+ );
+ await locationChange;
+
+ // Navigate back to the previous page which has the anchor
+ locationChange = BrowserTestUtils.waitForLocationChange(newWin.gBrowser);
+ newWin.gBrowser.selectedBrowser.goBack();
+ await locationChange;
+
+ // Hover state should be cleared upon page caching.
+ anchorHovered = await isAnchorHovered(newWin);
+ ok(!anchorHovered, "Anchor should not be hovered");
+
+ BrowserTestUtils.closeWindow(newWin);
+});
diff --git a/layout/base/tests/browser_bug617076.js b/layout/base/tests/browser_bug617076.js
new file mode 100644
index 0000000000..c76cbd41d3
--- /dev/null
+++ b/layout/base/tests/browser_bug617076.js
@@ -0,0 +1,71 @@
+/**
+ * 1. load about:addons in a new tab and select that tab
+ * 2. insert a button with tooltiptext
+ * 3. create a new blank tab and select that tab
+ * 4. select the about:addons tab and hover the inserted button
+ * 5. remove the about:addons tab
+ * 6. remove the blank tab
+ *
+ * the test succeeds if it doesn't trigger any assertions
+ */
+
+add_task(async function test() {
+ // Open the test tab
+ let testTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:addons"
+ );
+
+ // insert button into test page content
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ let doc = content.document;
+ let e = doc.createXULElement("button");
+ e.setAttribute("label", "hello");
+ e.setAttribute("tooltiptext", "world");
+ e.setAttribute("id", "test-button");
+ doc.documentElement.insertBefore(e, doc.documentElement.firstChild);
+ });
+
+ // open a second tab and select it
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank",
+ true
+ );
+ gBrowser.selectedTab = tab2;
+
+ // Select the testTab then perform mouse events on inserted button
+ gBrowser.selectedTab = testTab;
+ let browser = gBrowser.selectedBrowser;
+ EventUtils.disableNonTestMouseEvents(true);
+ try {
+ await BrowserTestUtils.synthesizeMouse(
+ "#test-button",
+ 1,
+ 1,
+ { type: "mouseover" },
+ browser
+ );
+ await BrowserTestUtils.synthesizeMouse(
+ "#test-button",
+ 2,
+ 6,
+ { type: "mousemove" },
+ browser
+ );
+ await BrowserTestUtils.synthesizeMouse(
+ "#test-button",
+ 2,
+ 4,
+ { type: "mousemove" },
+ browser
+ );
+ } finally {
+ EventUtils.disableNonTestMouseEvents(false);
+ }
+
+ // cleanup
+ BrowserTestUtils.removeTab(testTab);
+ BrowserTestUtils.removeTab(tab2);
+ ok(true, "pass if no assertions");
+});
diff --git a/layout/base/tests/browser_bug839103.js b/layout/base/tests/browser_bug839103.js
new file mode 100644
index 0000000000..fd20b82029
--- /dev/null
+++ b/layout/base/tests/browser_bug839103.js
@@ -0,0 +1,82 @@
+const gTestRoot = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://127.0.0.1:8888/"
+);
+
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: gTestRoot + "file_bug839103.html" },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [gTestRoot], testBody);
+ }
+ );
+});
+
+// This function runs entirely in the content process. It doesn't have access
+// any free variables in this file.
+async function testBody(testRoot) {
+ const gStyleSheet = "bug839103.css";
+
+ function unexpectedContentEvent(event) {
+ ok(false, "Received a " + event.type + " event on content");
+ }
+
+ // We've seen the original stylesheet in the document.
+ // Now add a stylesheet on the fly and make sure we see it.
+ let doc = content.document;
+ doc.styleSheetChangeEventsEnabled = true;
+ doc.addEventListener(
+ "StyleSheetApplicableStateChanged",
+ unexpectedContentEvent
+ );
+ doc.defaultView.addEventListener(
+ "StyleSheetApplicableStateChanged",
+ unexpectedContentEvent
+ );
+
+ let link = doc.createElement("link");
+ link.setAttribute("rel", "stylesheet");
+ link.setAttribute("type", "text/css");
+ link.setAttribute("href", testRoot + gStyleSheet);
+
+ let stateChanged = ContentTaskUtils.waitForEvent(
+ docShell.chromeEventHandler,
+ "StyleSheetApplicableStateChanged",
+ true
+ );
+ doc.body.appendChild(link);
+
+ info("waiting for applicable state change event");
+ let evt = await stateChanged;
+ info("received dynamic style sheet applicable state change event");
+ is(
+ evt.type,
+ "StyleSheetApplicableStateChanged",
+ "evt.type has expected value"
+ );
+ is(evt.target, doc, "event targets correct document");
+ is(evt.stylesheet, link.sheet, "evt.stylesheet has the right value");
+ is(evt.applicable, true, "evt.applicable has the right value");
+
+ stateChanged = ContentTaskUtils.waitForEvent(
+ docShell.chromeEventHandler,
+ "StyleSheetApplicableStateChanged",
+ true
+ );
+ link.sheet.disabled = true;
+
+ evt = await stateChanged;
+ is(
+ evt.type,
+ "StyleSheetApplicableStateChanged",
+ "evt.type has expected value"
+ );
+ info(
+ 'received dynamic style sheet applicable state change event after media="" changed'
+ );
+ is(evt.target, doc, "event targets correct document");
+ is(evt.stylesheet, link.sheet, "evt.stylesheet has the right value");
+ is(evt.applicable, false, "evt.applicable has the right value");
+
+ doc.body.removeChild(link);
+}
diff --git a/layout/base/tests/browser_disableDialogs_onbeforeunload.js b/layout/base/tests/browser_disableDialogs_onbeforeunload.js
new file mode 100644
index 0000000000..684fbf1d10
--- /dev/null
+++ b/layout/base/tests/browser_disableDialogs_onbeforeunload.js
@@ -0,0 +1,64 @@
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+function pageScript() {
+ window.addEventListener(
+ "beforeunload",
+ function (event) {
+ var str = "Some text that causes the beforeunload dialog to be shown";
+ event.returnValue = str;
+ return str;
+ },
+ true
+ );
+}
+
+SpecialPowers.pushPrefEnv({
+ set: [["dom.require_user_interaction_for_beforeunload", false]],
+});
+
+const PAGE_URL =
+ "data:text/html," +
+ encodeURIComponent("");
+
+add_task(async function enableDialogs() {
+ // The onbeforeunload dialog should appear.
+ let dialogPromise = PromptTestUtils.waitForPrompt(null, {
+ modalType: Services.prompt.MODAL_TYPE_CONTENT,
+ promptType: "confirmEx",
+ });
+
+ let openPagePromise = openPage(true);
+ let dialog = await dialogPromise;
+ Assert.ok(true, "Showed the beforeunload dialog.");
+
+ await PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 0 });
+ await openPagePromise;
+});
+
+add_task(async function disableDialogs() {
+ // The onbeforeunload dialog should NOT appear.
+ await openPage(false);
+ info("If we time out here, then the dialog was shown...");
+});
+
+async function openPage(enableDialogs) {
+ // Open about:blank in a new tab.
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (browser) {
+ // Load the page.
+ BrowserTestUtils.loadURIString(browser, PAGE_URL);
+ await BrowserTestUtils.browserLoaded(browser);
+ // Load the content script in the frame.
+ let methodName = enableDialogs ? "enableDialogs" : "disableDialogs";
+ await SpecialPowers.spawn(browser, [methodName], async function (name) {
+ content.windowUtils[name]();
+ });
+ // And then navigate away.
+ BrowserTestUtils.loadURIString(browser, "http://example.com/");
+ await BrowserTestUtils.browserLoaded(browser);
+ }
+ );
+}
diff --git a/layout/base/tests/browser_onbeforeunload_only_after_interaction.js b/layout/base/tests/browser_onbeforeunload_only_after_interaction.js
new file mode 100644
index 0000000000..af60db1acf
--- /dev/null
+++ b/layout/base/tests/browser_onbeforeunload_only_after_interaction.js
@@ -0,0 +1,75 @@
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+function pageScript() {
+ window.addEventListener(
+ "beforeunload",
+ function (event) {
+ var str = "Some text that causes the beforeunload dialog to be shown";
+ event.returnValue = str;
+ return str;
+ },
+ true
+ );
+}
+
+SpecialPowers.pushPrefEnv({
+ set: [["dom.require_user_interaction_for_beforeunload", true]],
+});
+
+const PAGE_URL =
+ "data:text/html," +
+ encodeURIComponent("");
+
+add_task(async function doClick() {
+ // The onbeforeunload dialog should appear.
+ let dialogPromise = PromptTestUtils.waitForPrompt(null, {
+ modalType: Services.prompt.MODAL_TYPE_CONTENT,
+ promptType: "confirmEx",
+ });
+
+ let openPagePromise = openPage(true);
+ let dialog = await dialogPromise;
+ Assert.ok(true, "Showed the beforeunload dialog.");
+
+ await PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 0 });
+ await openPagePromise;
+});
+
+add_task(async function noClick() {
+ // The onbeforeunload dialog should NOT appear.
+ await openPage(false);
+ info("If we time out here, then the dialog was shown...");
+});
+
+async function openPage(shouldClick) {
+ // Open about:blank in a new tab.
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (browser) {
+ // Load the page.
+ BrowserTestUtils.loadURIString(browser, PAGE_URL);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ if (shouldClick) {
+ await BrowserTestUtils.synthesizeMouse("body", 2, 2, {}, browser);
+ }
+ let hasInteractedWith = await SpecialPowers.spawn(
+ browser,
+ [""],
+ function () {
+ return content.document.userHasInteracted;
+ }
+ );
+ is(
+ shouldClick,
+ hasInteractedWith,
+ "Click should update document interactivity state"
+ );
+ // And then navigate away.
+ BrowserTestUtils.loadURIString(browser, "http://example.com/");
+ await BrowserTestUtils.browserLoaded(browser);
+ }
+ );
+}
diff --git a/layout/base/tests/browser_onbeforeunload_only_after_interaction_in_frame.js b/layout/base/tests/browser_onbeforeunload_only_after_interaction_in_frame.js
new file mode 100644
index 0000000000..6f73a2ece2
--- /dev/null
+++ b/layout/base/tests/browser_onbeforeunload_only_after_interaction_in_frame.js
@@ -0,0 +1,96 @@
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+function pageScript() {
+ window.addEventListener(
+ "beforeunload",
+ function (event) {
+ var str = "Some text that causes the beforeunload dialog to be shown";
+ event.returnValue = str;
+ return str;
+ },
+ true
+ );
+}
+
+SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.require_user_interaction_for_beforeunload", true],
+ ["security.allow_eval_with_system_principal", true],
+ ],
+});
+
+const FRAME_URL =
+ "data:text/html," + encodeURIComponent("Just a frame");
+
+const PAGE_URL =
+ "data:text/html," +
+ encodeURIComponent(
+ ""
+ );
+
+add_task(async function doClick() {
+ // The onbeforeunload dialog should appear.
+ let dialogPromise = PromptTestUtils.waitForPrompt(null, {
+ modalType: Services.prompt.MODAL_TYPE_CONTENT,
+ promptType: "confirmEx",
+ });
+
+ let openPagePromise = openPage(true);
+ let dialog = await dialogPromise;
+ Assert.ok(true, "Showed the beforeunload dialog.");
+
+ await PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 0 });
+ await openPagePromise;
+});
+
+add_task(async function noClick() {
+ // The onbeforeunload dialog should NOT appear.
+ await openPage(false);
+ info("If we time out here, then the dialog was shown...");
+});
+
+async function openPage(shouldClick) {
+ // Open about:blank in a new tab.
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (browser) {
+ // Load the page.
+ BrowserTestUtils.loadURIString(browser, PAGE_URL);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let frameBC = browser.browsingContext.children[0];
+ if (shouldClick) {
+ await BrowserTestUtils.synthesizeMouse("body", 2, 2, {}, frameBC);
+ }
+ let hasInteractedWith = await SpecialPowers.spawn(
+ frameBC,
+ [],
+ function () {
+ return [
+ content.document.userHasInteracted,
+ content.document.userHasInteracted,
+ ];
+ }
+ );
+ is(
+ shouldClick,
+ hasInteractedWith[0],
+ "Click should update parent interactivity state"
+ );
+ is(
+ shouldClick,
+ hasInteractedWith[1],
+ "Click should update frame interactivity state"
+ );
+ // And then navigate away.
+ BrowserTestUtils.loadURIString(browser, "http://example.com/");
+ await BrowserTestUtils.browserLoaded(browser);
+ }
+ );
+}
diff --git a/layout/base/tests/browser_scroll_into_view_in_out_of_process_iframe.js b/layout/base/tests/browser_scroll_into_view_in_out_of_process_iframe.js
new file mode 100644
index 0000000000..edf926c4ca
--- /dev/null
+++ b/layout/base/tests/browser_scroll_into_view_in_out_of_process_iframe.js
@@ -0,0 +1,50 @@
+"use strict";
+
+add_task(async () => {
+ function httpURL(filename, host = "https://example.com/") {
+ let root = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ host
+ );
+ return root + filename;
+ }
+
+ const fissionWindow = await BrowserTestUtils.openNewBrowserWindow({
+ fission: true,
+ });
+ const url = httpURL(
+ "test_scroll_into_view_in_oopif.html",
+ "http://mochi.test:8888/"
+ );
+ const crossOriginIframeUrl = httpURL("scroll_into_view_in_child.html");
+
+ try {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: fissionWindow.gBrowser, url },
+ async browser => {
+ await SpecialPowers.spawn(
+ browser,
+ [crossOriginIframeUrl],
+ async iframeUrl => {
+ const iframe = content.document.getElementById("iframe");
+ iframe.setAttribute("src", iframeUrl);
+
+ // Wait for a scroll event since scrollIntoView for cross origin documents is
+ // asyncronously processed.
+ const scroller = content.document.getElementById("scroller");
+ await new Promise(resolve => {
+ scroller.addEventListener("scroll", resolve, { once: true });
+ });
+
+ ok(
+ scroller.scrollTop > 0,
+ "scrollIntoView works in a cross origin iframe"
+ );
+ }
+ );
+ }
+ );
+ } finally {
+ await BrowserTestUtils.closeWindow(fissionWindow);
+ }
+});
diff --git a/layout/base/tests/browser_select_popup_position_in_out_of_process_iframe.js b/layout/base/tests/browser_select_popup_position_in_out_of_process_iframe.js
new file mode 100644
index 0000000000..c118c1c726
--- /dev/null
+++ b/layout/base/tests/browser_select_popup_position_in_out_of_process_iframe.js
@@ -0,0 +1,122 @@
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js",
+ this
+);
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/base/content/test/forms/head.js",
+ this
+);
+
+const PAGECONTENT_TRANSLATED =
+ "" +
+ "
+
diff --git a/layout/base/tests/bug1518339-2.html b/layout/base/tests/bug1518339-2.html
new file mode 100644
index 0000000000..0b62b12680
--- /dev/null
+++ b/layout/base/tests/bug1518339-2.html
@@ -0,0 +1,23 @@
+
+
+user-select can be overriden on a contenteditable element
+
+
+
+
+ You should be able to select all of me.
+
+
diff --git a/layout/base/tests/bug1524266-1-ref.html b/layout/base/tests/bug1524266-1-ref.html
new file mode 100644
index 0000000000..be5cc8ff9b
--- /dev/null
+++ b/layout/base/tests/bug1524266-1-ref.html
@@ -0,0 +1,30 @@
+
+
+Caret doesn't get stuck in a select element inside an editor
+
+
+
+
+ xx
+
+ xxx
+
+
diff --git a/layout/base/tests/bug1524266-1.html b/layout/base/tests/bug1524266-1.html
new file mode 100644
index 0000000000..4a011e6892
--- /dev/null
+++ b/layout/base/tests/bug1524266-1.html
@@ -0,0 +1,27 @@
+
+
+Caret doesn't get stuck in a select element inside an editor
+
+
+
+
+ xx
+
+ xxx
+
+
diff --git a/layout/base/tests/bug1524266-2-ref.html b/layout/base/tests/bug1524266-2-ref.html
new file mode 100644
index 0000000000..99024c4b83
--- /dev/null
+++ b/layout/base/tests/bug1524266-2-ref.html
@@ -0,0 +1,24 @@
+
+
+Can delete non-selectable content in an editor
+
+
+
+
+ xxxxx
+
+
diff --git a/layout/base/tests/bug1524266-2.html b/layout/base/tests/bug1524266-2.html
new file mode 100644
index 0000000000..459bd12cb5
--- /dev/null
+++ b/layout/base/tests/bug1524266-2.html
@@ -0,0 +1,38 @@
+
+
+Can delete non-selectable content in an editor
+
+
+
+
+ xx
+
+ xxx
+
+
diff --git a/layout/base/tests/bug1524266-3.html b/layout/base/tests/bug1524266-3.html
new file mode 100644
index 0000000000..39708d00ba
--- /dev/null
+++ b/layout/base/tests/bug1524266-3.html
@@ -0,0 +1,37 @@
+
+
+Can delete non-selectable content in an editor
+
+
+
+
+ xx
+
+ NOT EDITABLE
+
+ xxx
+
+
diff --git a/layout/base/tests/bug1524266-4.html b/layout/base/tests/bug1524266-4.html
new file mode 100644
index 0000000000..1b0eb35a07
--- /dev/null
+++ b/layout/base/tests/bug1524266-4.html
@@ -0,0 +1,30 @@
+
+
+Can delete non-editable content in an editor
+
+
+
+
+
diff --git a/layout/base/tests/bug1634543-1.html b/layout/base/tests/bug1634543-1.html
new file mode 100644
index 0000000000..039d93df64
--- /dev/null
+++ b/layout/base/tests/bug1634543-1.html
@@ -0,0 +1,25 @@
+
+
+Caret is correctly position for block whose only child is an abspos pseudo
+
+
+
+
diff --git a/layout/base/tests/bug1634543-2.html b/layout/base/tests/bug1634543-2.html
new file mode 100644
index 0000000000..fb05a4e4bb
--- /dev/null
+++ b/layout/base/tests/bug1634543-2.html
@@ -0,0 +1,24 @@
+
+
+Caret is correctly position for block whose only child is an abspos span
+
+
+
+
diff --git a/layout/base/tests/bug1634543-3.html b/layout/base/tests/bug1634543-3.html
new file mode 100644
index 0000000000..547c1c3559
--- /dev/null
+++ b/layout/base/tests/bug1634543-3.html
@@ -0,0 +1,20 @@
+
+
+Caret is correctly position for block whose only child is an empty span
+
+
+
+
diff --git a/layout/base/tests/bug1634543-4.html b/layout/base/tests/bug1634543-4.html
new file mode 100644
index 0000000000..4f82ea65b8
--- /dev/null
+++ b/layout/base/tests/bug1634543-4.html
@@ -0,0 +1,23 @@
+
+
+Caret is correctly position for block whose only child is an empty pseudo
+
+
+
+
diff --git a/layout/base/tests/bug1634743-1-ref.html b/layout/base/tests/bug1634743-1-ref.html
new file mode 100644
index 0000000000..55d0366359
--- /dev/null
+++ b/layout/base/tests/bug1634743-1-ref.html
@@ -0,0 +1,23 @@
+
+
+Test reference
+
+
+
abc
+
diff --git a/layout/base/tests/bug1634743-1.html b/layout/base/tests/bug1634743-1.html
new file mode 100644
index 0000000000..1c41b2a012
--- /dev/null
+++ b/layout/base/tests/bug1634743-1.html
@@ -0,0 +1,29 @@
+
+
+Caret is correctly position for block whose only child is a non-empty pseudo, if line-height is used
+
+
+
+
diff --git a/layout/base/tests/bug1637476-1-ref.html b/layout/base/tests/bug1637476-1-ref.html
new file mode 100644
index 0000000000..a56137aca9
--- /dev/null
+++ b/layout/base/tests/bug1637476-1-ref.html
@@ -0,0 +1,24 @@
+
+
+Reference: Caret is correctly painted with placeholder opacity:1
+
+
+
+
diff --git a/layout/base/tests/bug1637476-1.html b/layout/base/tests/bug1637476-1.html
new file mode 100644
index 0000000000..7bc32fa031
--- /dev/null
+++ b/layout/base/tests/bug1637476-1.html
@@ -0,0 +1,24 @@
+
+
+Caret is correctly painted with placeholder opacity:1
+
+
+
+
diff --git a/layout/base/tests/bug1637476-2-ref.html b/layout/base/tests/bug1637476-2-ref.html
new file mode 100644
index 0000000000..da06bfafc2
--- /dev/null
+++ b/layout/base/tests/bug1637476-2-ref.html
@@ -0,0 +1,24 @@
+
+
+Reference: Caret is correctly painted with placeholder opacity:1
+
+
+
+
diff --git a/layout/base/tests/bug1637476-2.html b/layout/base/tests/bug1637476-2.html
new file mode 100644
index 0000000000..3fb69f5dca
--- /dev/null
+++ b/layout/base/tests/bug1637476-2.html
@@ -0,0 +1,24 @@
+
+
+Caret is correctly painted with placeholder opacity:1
+
+
+
+
diff --git a/layout/base/tests/bug1637476-3-ref.html b/layout/base/tests/bug1637476-3-ref.html
new file mode 100644
index 0000000000..ca4b9d97ef
--- /dev/null
+++ b/layout/base/tests/bug1637476-3-ref.html
@@ -0,0 +1,24 @@
+
+
+Reference: Caret is correctly painted with placeholder opacity:1
+
+
+
+
diff --git a/layout/base/tests/bug1637476-3.html b/layout/base/tests/bug1637476-3.html
new file mode 100644
index 0000000000..a133a07f2e
--- /dev/null
+++ b/layout/base/tests/bug1637476-3.html
@@ -0,0 +1,24 @@
+
+
+Caret is correctly painted with placeholder opacity:1
+
+
+
+
diff --git a/layout/base/tests/bug1663475-1-ref.html b/layout/base/tests/bug1663475-1-ref.html
new file mode 100644
index 0000000000..180a5aa7ce
--- /dev/null
+++ b/layout/base/tests/bug1663475-1-ref.html
@@ -0,0 +1,28 @@
+
+
+Caret is correctly painted over inline with clip
+
+
+
+
+ abcde
+
+
diff --git a/layout/base/tests/bug1663475-1.html b/layout/base/tests/bug1663475-1.html
new file mode 100644
index 0000000000..5e046d8c80
--- /dev/null
+++ b/layout/base/tests/bug1663475-1.html
@@ -0,0 +1,32 @@
+
+
+Caret is correctly painted over inline with clip
+
+
+
+
+ abcde
+
+
diff --git a/layout/base/tests/bug1663475-2-ref.html b/layout/base/tests/bug1663475-2-ref.html
new file mode 100644
index 0000000000..25584df128
--- /dev/null
+++ b/layout/base/tests/bug1663475-2-ref.html
@@ -0,0 +1,29 @@
+
+
+Caret is correctly painted over inline with clip
+
+
+
+
+ abcde
+
+
diff --git a/layout/base/tests/bug1663475-2.html b/layout/base/tests/bug1663475-2.html
new file mode 100644
index 0000000000..aa6ddf4bff
--- /dev/null
+++ b/layout/base/tests/bug1663475-2.html
@@ -0,0 +1,33 @@
+
+
+Caret is correctly painted over inline with clip
+
+
+
+
+ abcde
+
+
diff --git a/layout/base/tests/bug1670531-1.html b/layout/base/tests/bug1670531-1.html
new file mode 100644
index 0000000000..42fc3de818
--- /dev/null
+++ b/layout/base/tests/bug1670531-1.html
@@ -0,0 +1,27 @@
+
+
+Select non-editable content in an editor
+
+
+
+
+ xxNOT EDITABLExxx
+
+
diff --git a/layout/base/tests/bug1670531-2.html b/layout/base/tests/bug1670531-2.html
new file mode 100644
index 0000000000..7dfc05b06c
--- /dev/null
+++ b/layout/base/tests/bug1670531-2.html
@@ -0,0 +1,27 @@
+
+
+Select non-editable content in an editor
+
+
+
+
+ xxNOT EDITABLExxx
+
+
diff --git a/layout/base/tests/bug1670531-3-ref.html b/layout/base/tests/bug1670531-3-ref.html
new file mode 100644
index 0000000000..08aaf01f08
--- /dev/null
+++ b/layout/base/tests/bug1670531-3-ref.html
@@ -0,0 +1,27 @@
+
+
+Select non-editable content in an editor
+
+
+
+
+ xxxxx
+
+
diff --git a/layout/base/tests/bug1670531-3.html b/layout/base/tests/bug1670531-3.html
new file mode 100644
index 0000000000..aaec76ee4a
--- /dev/null
+++ b/layout/base/tests/bug1670531-3.html
@@ -0,0 +1,27 @@
+
+
+Select non-editable content in an editor
+
+
+
+
+ xxxxx
+
+
diff --git a/layout/base/tests/bug1670531-4.html b/layout/base/tests/bug1670531-4.html
new file mode 100644
index 0000000000..7b7786d232
--- /dev/null
+++ b/layout/base/tests/bug1670531-4.html
@@ -0,0 +1,27 @@
+
+
+Select non-editable content in an editor
+
+
+
+
+
+
diff --git a/layout/base/tests/file_zoom_restore_bfcache.html b/layout/base/tests/file_zoom_restore_bfcache.html
new file mode 100644
index 0000000000..77451f3ef6
--- /dev/null
+++ b/layout/base/tests/file_zoom_restore_bfcache.html
@@ -0,0 +1,92 @@
+
+
+This is a very interesting page
+
diff --git a/layout/base/tests/helper_bug1701027-1.html b/layout/base/tests/helper_bug1701027-1.html
new file mode 100644
index 0000000000..659c1f7826
--- /dev/null
+++ b/layout/base/tests/helper_bug1701027-1.html
@@ -0,0 +1,10 @@
+
+
+
+
+Here is some text to stare at as the test runs. It serves no functional
+purpose
+
+
+
+
diff --git a/layout/base/tests/helper_bug1701027-2.html b/layout/base/tests/helper_bug1701027-2.html
new file mode 100644
index 0000000000..659c1f7826
--- /dev/null
+++ b/layout/base/tests/helper_bug1701027-2.html
@@ -0,0 +1,10 @@
+
+
+
+
+Here is some text to stare at as the test runs. It serves no functional
+purpose
+
+
+
+
diff --git a/layout/base/tests/helper_synthmousemove.html b/layout/base/tests/helper_synthmousemove.html
new file mode 100644
index 0000000000..41d8c6525a
--- /dev/null
+++ b/layout/base/tests/helper_synthmousemove.html
@@ -0,0 +1,3 @@
+
+helper_synthmousemove.html
+
diff --git a/layout/base/tests/image_rgrg-256x256.png b/layout/base/tests/image_rgrg-256x256.png
new file mode 100644
index 0000000000..e6fba3daa5
Binary files /dev/null and b/layout/base/tests/image_rgrg-256x256.png differ
diff --git a/layout/base/tests/input-invalid-ref.html b/layout/base/tests/input-invalid-ref.html
new file mode 100644
index 0000000000..4b34c9a2f3
--- /dev/null
+++ b/layout/base/tests/input-invalid-ref.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-maxlength-invalid-change.html b/layout/base/tests/input-maxlength-invalid-change.html
new file mode 100644
index 0000000000..849445f85f
--- /dev/null
+++ b/layout/base/tests/input-maxlength-invalid-change.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-maxlength-ui-invalid-change.html b/layout/base/tests/input-maxlength-ui-invalid-change.html
new file mode 100644
index 0000000000..1f74f0730c
--- /dev/null
+++ b/layout/base/tests/input-maxlength-ui-invalid-change.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-maxlength-ui-valid-change.html b/layout/base/tests/input-maxlength-ui-valid-change.html
new file mode 100644
index 0000000000..47224772fa
--- /dev/null
+++ b/layout/base/tests/input-maxlength-ui-valid-change.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-maxlength-valid-before-change.html b/layout/base/tests/input-maxlength-valid-before-change.html
new file mode 100644
index 0000000000..8662e8f5f4
--- /dev/null
+++ b/layout/base/tests/input-maxlength-valid-before-change.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-maxlength-valid-change.html b/layout/base/tests/input-maxlength-valid-change.html
new file mode 100644
index 0000000000..2612642534
--- /dev/null
+++ b/layout/base/tests/input-maxlength-valid-change.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-minlength-invalid-change.html b/layout/base/tests/input-minlength-invalid-change.html
new file mode 100644
index 0000000000..543c3e335d
--- /dev/null
+++ b/layout/base/tests/input-minlength-invalid-change.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-minlength-ui-invalid-change.html b/layout/base/tests/input-minlength-ui-invalid-change.html
new file mode 100644
index 0000000000..6c5dfc0e22
--- /dev/null
+++ b/layout/base/tests/input-minlength-ui-invalid-change.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-minlength-ui-valid-change.html b/layout/base/tests/input-minlength-ui-valid-change.html
new file mode 100644
index 0000000000..96e6390b91
--- /dev/null
+++ b/layout/base/tests/input-minlength-ui-valid-change.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-minlength-valid-before-change.html b/layout/base/tests/input-minlength-valid-before-change.html
new file mode 100644
index 0000000000..21e6927926
--- /dev/null
+++ b/layout/base/tests/input-minlength-valid-before-change.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-minlength-valid-change.html b/layout/base/tests/input-minlength-valid-change.html
new file mode 100644
index 0000000000..92b7fd3390
--- /dev/null
+++ b/layout/base/tests/input-minlength-valid-change.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-password-RTL-input-ref.html b/layout/base/tests/input-password-RTL-input-ref.html
new file mode 100644
index 0000000000..f3b5efe3ae
--- /dev/null
+++ b/layout/base/tests/input-password-RTL-input-ref.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-password-RTL-input.html b/layout/base/tests/input-password-RTL-input.html
new file mode 100644
index 0000000000..3c73d3ef1c
--- /dev/null
+++ b/layout/base/tests/input-password-RTL-input.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-password-remask-ref.html b/layout/base/tests/input-password-remask-ref.html
new file mode 100644
index 0000000000..a7f105a01e
--- /dev/null
+++ b/layout/base/tests/input-password-remask-ref.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-password-remask.html b/layout/base/tests/input-password-remask.html
new file mode 100644
index 0000000000..31149eac8d
--- /dev/null
+++ b/layout/base/tests/input-password-remask.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-password-unmask-around-emoji-ref.html b/layout/base/tests/input-password-unmask-around-emoji-ref.html
new file mode 100644
index 0000000000..1fba0d9ae1
--- /dev/null
+++ b/layout/base/tests/input-password-unmask-around-emoji-ref.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-password-unmask-around-emoji.html b/layout/base/tests/input-password-unmask-around-emoji.html
new file mode 100644
index 0000000000..97df884850
--- /dev/null
+++ b/layout/base/tests/input-password-unmask-around-emoji.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-password-unmask-ref.html b/layout/base/tests/input-password-unmask-ref.html
new file mode 100644
index 0000000000..d1803551e4
--- /dev/null
+++ b/layout/base/tests/input-password-unmask-ref.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-password-unmask.html b/layout/base/tests/input-password-unmask.html
new file mode 100644
index 0000000000..34c63726bb
--- /dev/null
+++ b/layout/base/tests/input-password-unmask.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-stoppropagation-ref.html b/layout/base/tests/input-stoppropagation-ref.html
new file mode 100644
index 0000000000..99ff791588
--- /dev/null
+++ b/layout/base/tests/input-stoppropagation-ref.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-stoppropagation.html b/layout/base/tests/input-stoppropagation.html
new file mode 100644
index 0000000000..b246a6b6da
--- /dev/null
+++ b/layout/base/tests/input-stoppropagation.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/tests/input-ui-valid-ref.html b/layout/base/tests/input-ui-valid-ref.html
new file mode 100644
index 0000000000..76d9386678
--- /dev/null
+++ b/layout/base/tests/input-ui-valid-ref.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/layout/base/tests/input-valid-ref.html b/layout/base/tests/input-valid-ref.html
new file mode 100644
index 0000000000..ec01bb98f2
--- /dev/null
+++ b/layout/base/tests/input-valid-ref.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/layout/base/tests/interlinePosition-after-Selection-addRange-ref.html b/layout/base/tests/interlinePosition-after-Selection-addRange-ref.html
new file mode 100644
index 0000000000..55eccf814f
--- /dev/null
+++ b/layout/base/tests/interlinePosition-after-Selection-addRange-ref.html
@@ -0,0 +1,20 @@
+
+
+
+Selection.addRange() should always reset interline position
+
+
+
+
abc
diff --git a/layout/base/tests/interlinePosition-after-Selection-addRange.html b/layout/base/tests/interlinePosition-after-Selection-addRange.html
new file mode 100644
index 0000000000..8e45277249
--- /dev/null
+++ b/layout/base/tests/interlinePosition-after-Selection-addRange.html
@@ -0,0 +1,21 @@
+
+
+
+Selection.addRange() should always reset interline position
+
+
+
+
abc
diff --git a/layout/base/tests/marionette/manifest.ini b/layout/base/tests/marionette/manifest.ini
new file mode 100644
index 0000000000..8249f4010a
--- /dev/null
+++ b/layout/base/tests/marionette/manifest.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+prefs =
+ gfx.font_loader.delay=0
+run-if = buildapp == 'browser'
+[test_accessiblecaret_cursor_mode.py]
+[test_accessiblecaret_selection_mode.py]
diff --git a/layout/base/tests/marionette/selection.py b/layout/base/tests/marionette/selection.py
new file mode 100644
index 0000000000..6cfe155927
--- /dev/null
+++ b/layout/base/tests/marionette/selection.py
@@ -0,0 +1,335 @@
+# -*- coding: utf-8 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from marionette_driver.marionette import Actions, errors
+
+
+class CaretActions(Actions):
+ def __init__(self, marionette):
+ super(CaretActions, self).__init__(marionette)
+ self._reset_action_chain()
+
+ def _reset_action_chain(self):
+ self.mouse_chain = self.sequence(
+ "pointer", "pointer_id", {"pointerType": "mouse"}
+ )
+ self.key_chain = self.sequence("key", "keyboard_id")
+
+ def flick(self, element, x1, y1, x2, y2, duration=200):
+ """Perform a flick gesture on the target element.
+
+ :param element: The element to perform the flick gesture on.
+ :param x1: Starting x-coordinate of flick, relative to the top left
+ corner of the element.
+ :param y1: Starting y-coordinate of flick, relative to the top left
+ corner of the element.
+ :param x2: Ending x-coordinate of flick, relative to the top left
+ corner of the element.
+ :param y2: Ending y-coordinate of flick, relative to the top left
+ corner of the element.
+
+ """
+ rect = element.rect
+ el_x, el_y = rect["x"], rect["y"]
+
+ # Add element's (x, y) to make the coordinate relative to the viewport.
+ from_x, from_y = int(el_x + x1), int(el_y + y1)
+ to_x, to_y = int(el_x + x2), int(el_y + y2)
+
+ self.mouse_chain.pointer_move(from_x, from_y).pointer_down().pointer_move(
+ to_x, to_y, duration=duration
+ ).pointer_up()
+ return self
+
+ def send_keys(self, keys):
+ """Perform a keyDown and keyUp action for each character in `keys`.
+
+ :param keys: String of keys to perform key actions with.
+
+ """
+ self.key_chain.send_keys(keys)
+ return self
+
+ def perform(self):
+ """Perform the action chain built so far to the server side for execution
+ and clears the current chain of actions.
+
+ Warning: This method performs all the mouse actions before all the key
+ actions!
+
+ """
+ self.mouse_chain.perform()
+ self.key_chain.perform()
+ self._reset_action_chain()
+
+
+class SelectionManager(object):
+ """Interface for manipulating the selection and carets of the element.
+
+ We call the blinking cursor (nsCaret) as cursor, and call AccessibleCaret as
+ caret for short.
+
+ Simple usage example:
+
+ ::
+
+ element = marionette.find_element(By.ID, 'input')
+ sel = SelectionManager(element)
+ sel.move_caret_to_front()
+
+ """
+
+ def __init__(self, element):
+ self.element = element
+
+ def _input_or_textarea(self):
+ """Return True if element is either or