diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/content/tests/chrome | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/content/tests/chrome')
177 files changed, 22434 insertions, 0 deletions
diff --git a/toolkit/content/tests/chrome/RegisterUnregisterChrome.js b/toolkit/content/tests/chrome/RegisterUnregisterChrome.js new file mode 100644 index 0000000000..26e4e80922 --- /dev/null +++ b/toolkit/content/tests/chrome/RegisterUnregisterChrome.js @@ -0,0 +1,141 @@ +/* This code is mostly copied from chrome/test/unit/head_crtestutils.js */ + +// This file assumes chrome-harness.js is loaded in the global scope. +/* import-globals-from ../../../../testing/mochitest/chrome-harness.js */ + +const NS_CHROME_MANIFESTS_FILE_LIST = "ChromeML"; +const XUL_CACHE_PREF = "nglayout.debug.disable_xul_cache"; + +var gChromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService( + Ci.nsIXULChromeRegistry +); + +// Create the temporary file in the profile, instead of in TmpD, because +// we know the mochitest harness kills off the profile when it's done. +function copyToTemporaryFile(f) { + let tmpd = Services.dirsvc.get("ProfD", Ci.nsIFile); + let tmpf = tmpd.clone(); + tmpf.append("temp.manifest"); + tmpf.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + tmpf.remove(false); + f.copyTo(tmpd, tmpf.leafName); + return tmpf; +} + +function* dirIter(directory) { + var testsDir = Services.io + .newURI(directory) + .QueryInterface(Ci.nsIFileURL).file; + + let en = testsDir.directoryEntries; + while (en.hasMoreElements()) { + yield en.nextFile; + } +} + +function getParent(path) { + let lastSlash = path.lastIndexOf("/"); + if (lastSlash == -1) { + lastSlash = path.lastIndexOf("\\"); + if (lastSlash == -1) { + return ""; + } + return "/" + path.substring(0, lastSlash).replace(/\\/g, "/"); + } + return path.substring(0, lastSlash); +} + +function copyDirToTempProfile(path, subdirname) { + if (subdirname === undefined) { + subdirname = "mochikit-tmp"; + } + + let tmpdir = Services.dirsvc.get("ProfD", Ci.nsIFile); + tmpdir.append(subdirname); + tmpdir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o777); + + let rootDir = getParent(path); + if (rootDir == "") { + return tmpdir; + } + + // The SimpleTest directory is hidden + var files = Array.from(dirIter("file://" + rootDir)); + for (let f in files) { + files[f].copyTo(tmpdir, ""); + } + return tmpdir; +} + +function convertChromeURI(chromeURI) { + let uri = Services.io.newURI(chromeURI); + return gChromeReg.convertChromeURL(uri); +} + +function chromeURIToFile(chromeURI) { + var jar = getJar(chromeURI); + if (jar) { + var tmpDir = extractJarToTmp(jar); + let parts = chromeURI.split("/"); + if (parts[parts.length - 1] != "") { + tmpDir.append(parts[parts.length - 1]); + } + return tmpDir; + } + + return convertChromeURI(chromeURI).QueryInterface(Ci.nsIFileURL).file; +} + +// Register a chrome manifest temporarily and return a function which un-does +// the registrarion when no longer needed. +function createManifestTemporarily(tempDir, manifestText) { + Services.prefs.setBoolPref(XUL_CACHE_PREF, true); + + tempDir.append("temp.manifest"); + + let foStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance( + Ci.nsIFileOutputStream + ); + foStream.init(tempDir, 0x02 | 0x08 | 0x20, 0o664, 0); // write, create, truncate + foStream.write(manifestText, manifestText.length); + foStream.close(); + let tempfile = copyToTemporaryFile(tempDir); + + Components.manager + .QueryInterface(Ci.nsIComponentRegistrar) + .autoRegister(tempfile); + + return function () { + tempfile.fileSize = 0; // truncate the manifest + gChromeReg.checkForNewChrome(); + Services.prefs.clearUserPref(XUL_CACHE_PREF); + }; +} + +// Register a chrome manifest temporarily and return a function which un-does +// the registrarion when no longer needed. +function registerManifestTemporarily(manifestURI) { + Services.prefs.setBoolPref(XUL_CACHE_PREF, true); + + let file = chromeURIToFile(manifestURI); + + let tempfile = copyToTemporaryFile(file); + Components.manager + .QueryInterface(Ci.nsIComponentRegistrar) + .autoRegister(tempfile); + + return function () { + tempfile.fileSize = 0; // truncate the manifest + gChromeReg.checkForNewChrome(); + Services.prefs.clearUserPref(XUL_CACHE_PREF); + }; +} + +function registerManifestPermanently(manifestURI) { + var chromepath = chromeURIToFile(manifestURI); + + Components.manager + .QueryInterface(Ci.nsIComponentRegistrar) + .autoRegister(chromepath); + return chromepath; +} diff --git a/toolkit/content/tests/chrome/bug263683_window.xhtml b/toolkit/content/tests/chrome/bug263683_window.xhtml new file mode 100644 index 0000000000..b124361fd7 --- /dev/null +++ b/toolkit/content/tests/chrome/bug263683_window.xhtml @@ -0,0 +1,206 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window id="263683test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="SimpleTest.executeSoon(startTest);" + title="263683 test"> + + <script type="application/javascript"><![CDATA[ + const {AppConstants} = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" + ); + const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" + ); + + var gFindBar = null; + var gBrowser; + + var SimpleTest = window.arguments[0].SimpleTest; + var ok = window.arguments[0].ok; + var info = window.arguments[0].info; + var is = window.arguments[0].is; + + function startTest() { + (async function() { + gFindBar = document.getElementById("FindToolbar"); + // Testing on a remote browser has been disabled due to frequent + // intermittent failures. + for (let browserId of ["content"/*, "content-remote"*/]) { + await startTestWithBrowser(browserId); + } + })().then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + async function startTestWithBrowser(browserId) { + // We're bailing out when testing a remote browser on OSX 10.6, because it + // fails permanently. + if (browserId.endsWith("remote") && AppConstants.isPlatformAndVersionAtMost("macosx", 11)) { + return; + } + + info("Starting test with browser '" + browserId + "'"); + gBrowser = document.getElementById(browserId); + gFindBar.browser = gBrowser; + let promise = BrowserTestUtils.browserLoaded(gBrowser); + BrowserTestUtils.startLoadingURIString(gBrowser, 'data:text/html,<h2>Text mozilla</h2><input id="inp" type="text" />'); + await promise; + await onDocumentLoaded(); + } + + function toggleHighlightAndWait(highlight) { + return new Promise(resolve => { + let listener = { + onHighlightFinished() { + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + } + }; + gFindBar.browser.finder.addResultListener(listener); + gFindBar.toggleHighlight(highlight); + }); + } + + async function onDocumentLoaded() { + gFindBar.open(); + var search = "mozilla"; + gFindBar._findField.focus(); + gFindBar._findField.value = search; + var matchCase = gFindBar.getElement("find-case-sensitive"); + if (matchCase.checked) { + matchCase.doCommand(); + } + + let promise = toggleHighlightAndWait(true); + gFindBar._find(); + await promise; + + await SpecialPowers.spawn(gBrowser, [{ search }], async function(args) { + let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + Assert.ok("SELECTION_FIND" in controller, "Correctly detects new selection type"); + let selection = controller.getSelection(controller.SELECTION_FIND); + + Assert.equal(selection.rangeCount, 1, + "Correctly added a match to the selection type"); + Assert.equal(selection.getRangeAt(0).toString().toLowerCase(), + args.search, "Added the correct match"); + }); + + await toggleHighlightAndWait(false); + + await SpecialPowers.spawn(gBrowser, [{ search }], async function(args) { + let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + let selection = controller.getSelection(controller.SELECTION_FIND); + Assert.equal(selection.rangeCount, 0, "Correctly removed the range"); + + let input = content.document.getElementById("inp"); + input.value = args.search; + }); + + await toggleHighlightAndWait(true); + + await SpecialPowers.spawn(gBrowser, [{ search }], async function(args) { + let input = content.document.getElementById("inp"); + let inputController = input.editor.selectionController; + let inputSelection = inputController.getSelection(inputController.SELECTION_FIND); + + Assert.equal(inputSelection.rangeCount, 1, + "Correctly added a match from input to the selection type"); + Assert.equal(inputSelection.getRangeAt(0).toString().toLowerCase(), + args.search, "Added the correct match"); + }); + + await toggleHighlightAndWait(false); + + await SpecialPowers.spawn(gBrowser, [], async function() { + let input = content.document.getElementById("inp"); + let inputController = input.editor.selectionController; + let inputSelection = inputController.getSelection(inputController.SELECTION_FIND); + + Assert.equal(inputSelection.rangeCount, 0, "Correctly removed the range"); + }); + + // For posterity, test iframes too. + + promise = BrowserTestUtils.browserLoaded(gBrowser); + BrowserTestUtils.startLoadingURIString(gBrowser, 'data:text/html,<h2>Text mozilla</h2><iframe id="leframe" ' + + 'src="data:text/html,Text mozilla"></iframe>'); + await promise; + + await toggleHighlightAndWait(true); + + await SpecialPowers.spawn(gBrowser, [{ search }], async function(args) { + function getSelection(docShell) { + let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + return controller.getSelection(controller.SELECTION_FIND); + } + + let selection = getSelection(docShell); + Assert.equal(selection.rangeCount, 1, + "Correctly added a match to the selection type"); + Assert.equal(selection.getRangeAt(0).toString().toLowerCase(), + args.search, "Added the correct match"); + + // Check the iframe too: + let frame = content.document.getElementById("leframe"); + // Hoops! Get the docShell first, then the selection. + selection = getSelection(frame.contentWindow.docShell); + Assert.equal(selection.rangeCount, 1, + "Correctly added a match to the selection type"); + Assert.equal(selection.getRangeAt(0).toString().toLowerCase(), + args.search, "Added the correct match"); + }); + + await toggleHighlightAndWait(false); + + const matches = JSON.parse(gFindBar._foundMatches.dataset.l10nArgs); + is(matches.total, 2, "Found correct amount of matches") + + await SpecialPowers.spawn(gBrowser, [], async function(args) { + function getSelection(docShell) { + let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + return controller.getSelection(controller.SELECTION_FIND); + } + + let selection = getSelection(docShell); + Assert.equal(selection.rangeCount, 0, "Correctly removed the range"); + + // Check the iframe too: + let frame = content.document.getElementById("leframe"); + // Hoops! Get the docShell first, then the selection. + selection = getSelection(frame.contentWindow.docShell); + Assert.equal(selection.rangeCount, 0, "Correctly removed the range"); + + content.document.documentElement.focus(); + }); + + gFindBar.close(true); + } + ]]></script> + + <browser type="content" primary="true" flex="1" id="content" messagemanagergroup="test" src="about:blank"/> + <browser type="content" primary="true" flex="1" id="content-remote" remote="true" messagemanagergroup="test" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/bug304188_window.xhtml b/toolkit/content/tests/chrome/bug304188_window.xhtml new file mode 100644 index 0000000000..11343f2ace --- /dev/null +++ b/toolkit/content/tests/chrome/bug304188_window.xhtml @@ -0,0 +1,94 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="onLoad();" + title="FindbarTest for bug 304188 - +find-menu appears in editor element which has had makeEditable() called but designMode not set"> + + <script type="application/javascript"><![CDATA[ + const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" + ); + const {ContentTask} = ChromeUtils.importESModule( + "resource://testing-common/ContentTask.sys.mjs" + ); + ContentTask.setTestScope(window.arguments[0]); + + var gFindBar = null; + var gBrowser; + + var SimpleTest = window.arguments[0].SimpleTest; + var info = window.arguments[0].info; + var ok = window.arguments[0].ok; + + function onLoad() { + (async function() { + gFindBar = document.getElementById("FindToolbar"); + for (let browserId of ["content", "content-remote"]) { + await startTestWithBrowser(browserId); + } + })().then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + async function startTestWithBrowser(browserId) { + info("Starting test with browser '" + browserId + "'"); + gBrowser = document.getElementById(browserId); + gFindBar.browser = gBrowser; + let promise = ContentTask.spawn(gBrowser, [], async function() { + return new Promise(resolve => { + addEventListener("DOMContentLoaded", () => resolve(), { once: true }); + }); + }); + BrowserTestUtils.startLoadingURIString(gBrowser, "data:text/html;charset=utf-8,some%20random%20text"); + await promise; + await onDocumentLoaded(); + } + + async function onDocumentLoaded() { + await ContentTask.spawn(gBrowser, [], async function() { + var edsession = content.docShell.editingSession; + edsession.makeWindowEditable(content, "html", false, true, false); + content.focus(); + }); + + await enterStringIntoEditor("'"); + await enterStringIntoEditor("/"); + + ok(gFindBar.hidden, + "Findfield should have stayed hidden after entering editor test"); + } + + async function enterStringIntoEditor(aString) { + for (let i = 0; i < aString.length; i++) { + await ContentTask.spawn(gBrowser, [{ charCode: aString.charCodeAt(i) }], async function(args) { + let event = new content.window.KeyboardEvent("keypress", { + bubbles: true, + cancelable: true, + view: null, + keyCode: 0, + charCode: args.charCode, + }); + content.document.body.dispatchEvent(event); + }); + } + } + ]]></script> + + <browser id="content" flex="1" src="about:blank" type="content" primary="true" messagemanagergroup="test"/> + <browser id="content-remote" remote="true" flex="1" src="about:blank" type="content" primary="true" messagemanagergroup="test"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/bug331215_window.xhtml b/toolkit/content/tests/chrome/bug331215_window.xhtml new file mode 100644 index 0000000000..20c08dbd66 --- /dev/null +++ b/toolkit/content/tests/chrome/bug331215_window.xhtml @@ -0,0 +1,99 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window id="331215test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="SimpleTest.executeSoon(startTest);" + title="331215 test"> + + <script type="application/javascript"><![CDATA[ + const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" + ); + + var gFindBar = null; + var gBrowser; + + var SimpleTest = window.arguments[0].SimpleTest; + var info = window.arguments[0].info; + var ok = window.arguments[0].ok; + SimpleTest.requestLongerTimeout(2); + + function startTest() { + (async function() { + gFindBar = document.getElementById("FindToolbar"); + for (let browserId of ["content", "content-remote"]) { + await startTestWithBrowser(browserId); + } + })().then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + async function startTestWithBrowser(browserId) { + info("Starting test with browser '" + browserId + "'"); + gBrowser = document.getElementById(browserId); + gFindBar.browser = gBrowser; + let promise = BrowserTestUtils.browserLoaded(gBrowser); + BrowserTestUtils.startLoadingURIString(gBrowser, "data:text/plain,latest"); + await promise; + await onDocumentLoaded(); + } + + async function onDocumentLoaded() { + document.getElementById("cmd_find").doCommand(); + await promiseEnterStringIntoFindField("test"); + document.commandDispatcher + .getControllerForCommand("cmd_moveTop") + .doCommand("cmd_moveTop"); + await promiseEnterStringIntoFindField("l"); + ok(gFindBar._findField.getAttribute("status") == "notfound", + "Findfield status attribute should have been 'notfound' after entering test"); + await promiseEnterStringIntoFindField("a"); + ok(gFindBar._findField.getAttribute("status") != "notfound", + "Findfield status attribute should not have been 'notfound' after entering latest"); + } + + function promiseEnterStringIntoFindField(aString) { + return new Promise(resolve => { + let listener = { + onFindResult(result) { + if (result.result == Ci.nsITypeAheadFind.FIND_FOUND && result.searchString != aString) + return; + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + } + }; + gFindBar.browser.finder.addResultListener(listener); + + for (let c of aString) { + let code = c.charCodeAt(0); + let ev = new KeyboardEvent("keypress", { + keyCode: code, + charCode: code, + bubbles: true + }); + gFindBar._findField.dispatchEvent(ev); + } + }); + } + ]]></script> + + <commandset> + <command id="cmd_find" oncommand="document.getElementById('FindToolbar').onFindCommand();"/> + </commandset> + <browser type="content" primary="true" flex="1" id="content" messagemanagergroup="test" src="about:blank"/> + <browser type="content" primary="true" flex="1" id="content-remote" remote="true" messagemanagergroup="test" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/bug360437_window.xhtml b/toolkit/content/tests/chrome/bug360437_window.xhtml new file mode 100644 index 0000000000..dd5e336555 --- /dev/null +++ b/toolkit/content/tests/chrome/bug360437_window.xhtml @@ -0,0 +1,129 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window id="360437Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + width="600" + height="600" + onload="startTest();" + title="360437 test"> + + <script type="application/javascript"><![CDATA[ + const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" + ); + const {ContentTask} = ChromeUtils.importESModule( + "resource://testing-common/ContentTask.sys.mjs" + ); + ContentTask.setTestScope(window.arguments[0]); + + var gFindBar = null; + var gBrowser; + + var SimpleTest = window.arguments[0].SimpleTest; + var ok = window.arguments[0].ok; + var is = window.arguments[0].is; + var info = window.arguments[0].info; + + function startTest() { + (async function() { + gFindBar = document.getElementById("FindToolbar"); + for (let browserId of ["content", "content-remote"]) { + await startTestWithBrowser(browserId); + } + })().then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + async function startTestWithBrowser(browserId) { + info("Starting test with browser '" + browserId + "'"); + gBrowser = document.getElementById(browserId); + gFindBar.browser = gBrowser; + + let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser); + let contentLoadedPromise = ContentTask.spawn(gBrowser, null, async function() { + return new Promise(resolve => { + addEventListener("DOMContentLoaded", () => resolve(), { once: true }); + }); + }); + BrowserTestUtils.startLoadingURIString(gBrowser, "data:text/html,<form><input id='input' type='text' value='text inside an input element'></form>"); + await loadedPromise; + await contentLoadedPromise; + + gFindBar.onFindCommand(); + + // Make sure the findfield is correctly focused on open + var searchStr = "text inside an input element"; + await promiseEnterStringIntoFindField(searchStr); + is(document.commandDispatcher.focusedElement, + gFindBar._findField, "Find field isn't focused"); + + // Make sure "find again" correctly transfers focus to the content element + // when the find bar is closed. + await new Promise(resolve => { + window.addEventListener("findbarclose", resolve, { once: true }); + gFindBar.close(); + }); + gFindBar.onFindAgainCommand(false); + await SpecialPowers.spawn(gBrowser, [], async function() { + Assert.equal(content.document.activeElement, + content.document.getElementById("input"), "Input Element isn't focused"); + }); + + // Make sure "find again" doesn't focus the content element if focus + // isn't in the content document. + var textbox = document.getElementById("textbox"); + textbox.focus(); + + ok(gFindBar.hidden, "Findbar is hidden"); + gFindBar.onFindAgainCommand(false); + is(document.activeElement, textbox, + "Focus was stolen from a chrome element"); + } + + function promiseFindResult(str = null) { + return new Promise(resolve => { + let listener = { + onFindResult({ searchString }) { + if (str !== null && str != searchString) { + return; + } + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + } + }; + gFindBar.browser.finder.addResultListener(listener); + }); + } + + function promiseEnterStringIntoFindField(str) { + let promise = promiseFindResult(str); + for (let i = 0; i < str.length; i++) { + let event = new KeyboardEvent("keypress", { + bubbles: true, + cancelable: true, + view: null, + keyCode: 0, + charCode: str.charCodeAt(i), + }); + gFindBar._findField.dispatchEvent(event); + } + return promise; + } + ]]></script> + <html:input id="textbox"/> + <browser type="content" primary="true" flex="1" id="content" messagemanagergroup="test" src="about:blank"/> + <browser type="content" primary="true" flex="1" id="content-remote" remote="true" messagemanagergroup="test" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/bug366992_window.xhtml b/toolkit/content/tests/chrome/bug366992_window.xhtml new file mode 100644 index 0000000000..698d26b43a --- /dev/null +++ b/toolkit/content/tests/chrome/bug366992_window.xhtml @@ -0,0 +1,74 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="366992 test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + onload="onLoad();" + width="600" + height="600" + title="366992 test"> + + <commandset id="editMenuCommands"> + <commandset id="editMenuCommandSetAll" commandupdater="true" events="focus,select" + oncommandupdate="goUpdateGlobalEditMenuItems()"/> + <commandset id="editMenuCommandSetUndo" commandupdater="true" events="undo" + oncommandupdate="goUpdateUndoEditMenuItems()"/> + <commandset id="editMenuCommandSetPaste" commandupdater="true" events="clipboard" + oncommandupdate="goUpdatePasteMenuItems()"/> + <command id="cmd_undo" oncommand="goDoCommand('cmd_undo')"/> + <command id="cmd_redo" oncommand="goDoCommand('cmd_redo')"/> + <command id="cmd_cut" oncommand="goDoCommand('cmd_cut')"/> + <command id="cmd_copy" oncommand="goDoCommand('cmd_copy')"/> + <command id="cmd_paste" oncommand="goDoCommand('cmd_paste')"/> + <command id="cmd_delete" oncommand="goDoCommand('cmd_delete')"/> + <command id="cmd_selectAll" oncommand="goDoCommand('cmd_selectAll')"/> + <command id="cmd_switchTextDirection" oncommand="goDoCommand('cmd_switchTextDirection');"/> + </commandset> + + <script type="application/javascript" + src="chrome://global/content/globalOverlay.js"/> + <script type="application/javascript" + src="chrome://global/content/editMenuOverlay.js"/> + <script type="application/javascript"><![CDATA[ + // Without the fix for bug 366992, the delete command would be enabled + // for the input even though the input's controller for this command + // disables it. + var gShouldNotBeReachedController = { + supportsCommand(aCommand) { + return aCommand == "cmd_delete"; + }, + isCommandEnabled(aCommand) { + return aCommand == "cmd_delete"; + }, + doCommand(aCommand) { } + } + + function ok(condition, message) { + window.arguments[0].SimpleTest.ok(condition, message); + } + function finish() { + window.controllers.removeController(gShouldNotBeReachedController); + window.close(); + window.arguments[0].SimpleTest.finish(); + } + + function onLoad() { + document.getElementById("input").focus(); + var deleteDisabled = document.getElementById("cmd_delete") + .getAttribute("disabled") == "true"; + ok(deleteDisabled, + "cmd_delete should be disabled when the empty input is focused"); + finish(); + } + + window.controllers.appendController(gShouldNotBeReachedController); + ]]></script> + + <html:input id="input"/> +</window> diff --git a/toolkit/content/tests/chrome/bug409624_window.xhtml b/toolkit/content/tests/chrome/bug409624_window.xhtml new file mode 100644 index 0000000000..88033ba794 --- /dev/null +++ b/toolkit/content/tests/chrome/bug409624_window.xhtml @@ -0,0 +1,98 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="409624test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + title="409624 test"> + + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript"><![CDATA[ + const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" + ); + var gFindBar = null; + var gBrowser; + + var SimpleTest = window.arguments[0].SimpleTest; + var ok = window.arguments[0].ok; + var is = window.arguments[0].is; + + function finish() { + window.close(); + SimpleTest.finish(); + } + + function startTest() { + gFindBar = document.getElementById("FindToolbar"); + gBrowser = document.getElementById("content"); + gBrowser.addEventListener("pageshow", onPageShow, { once: true }); + BrowserTestUtils.startLoadingURIString(gBrowser, 'data:text/html,<h2>Text mozilla</h2><input id="inp" type="text" />'); + } + + function onPageShow() { + gFindBar.clear(); + let textbox = gFindBar.getElement("findbar-textbox"); + + // Clear should work regardless of whether the editor has been lazily + // initialised yet + ok(!gFindBar.hasTransactions, "No transactions when findbar empty"); + textbox.value = "mozilla"; + ok(gFindBar.hasTransactions, "Has transactions when findbar value set without editor init"); + gFindBar.clear(); + is(textbox.value, '', "findbar input value cleared after clear() call without editor init"); + ok(!gFindBar.hasTransactions, "No transactions after clear() call"); + + gFindBar.open(); + let matchCaseCheckbox = gFindBar.getElement("find-case-sensitive"); + if (!matchCaseCheckbox.hidden && matchCaseCheckbox.checked) + matchCaseCheckbox.click(); + ok(!matchCaseCheckbox.checked, "case-insensitivity correctly set"); + + // Simulate typical input + textbox.focus(); + gFindBar.clear(); + sendChar("m"); + ok(gFindBar.hasTransactions, "Has transactions after input"); + let preSelection = gBrowser.contentWindow.getSelection(); + ok(!preSelection.isCollapsed, "Found item and selected range"); + gFindBar.clear(); + is(textbox.value, '', "findbar input value cleared after clear() call"); + let postSelection = gBrowser.contentWindow.getSelection(); + ok(postSelection.isCollapsed, "item found deselected after clear() call"); + let fp = gFindBar.getElement("find-previous"); + ok(fp.disabled, "find-previous button disabled after clear() call"); + let fn = gFindBar.getElement("find-next"); + ok(fn.disabled, "find-next button disabled after clear() call"); + + // Test status updated after a search for text not in page + textbox.focus(); + sendChar("x"); + gFindBar.clear(); + let ftext = gFindBar.getElement("find-status"); + is(ftext.textContent, "", "status text disabled after clear() call"); + + // Test input empty with undo stack non-empty + textbox.focus(); + sendChar("m"); + sendKey("BACK_SPACE"); + ok(gFindBar.hasTransactions, "Has transactions when undo available"); + gFindBar.clear(); + gFindBar.close(); + + finish(); + } + + SimpleTest.waitForFocus(startTest, window); + ]]></script> + + <browser type="content" primary="true" flex="1" id="content" messagemanagergroup="test" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/bug429723_window.xhtml b/toolkit/content/tests/chrome/bug429723_window.xhtml new file mode 100644 index 0000000000..52e743239b --- /dev/null +++ b/toolkit/content/tests/chrome/bug429723_window.xhtml @@ -0,0 +1,94 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="429723Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="onLoad();" + title="429723 test"> + + <script type="application/javascript"><![CDATA[ + const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" + ); + var gFindBar = null; + var gBrowser; + + function ok(condition, message) { + window.arguments[0].SimpleTest.ok(condition, message); + } + + function finish() { + window.close(); + window.arguments[0].SimpleTest.finish(); + } + + function onLoad() { + var _delayedOnLoad = function() { + gFindBar = document.getElementById("FindToolbar"); + gBrowser = document.getElementById("content"); + gBrowser.addEventListener("pageshow", onPageShow, { once: true }); + BrowserTestUtils.startLoadingURIString(gBrowser, "data:text/html,<h2 id='h2'>mozilla</h2>"); + } + setTimeout(_delayedOnLoad, 1000); + } + + function enterStringIntoFindField(aString) { + for (var i=0; i < aString.length; i++) { + var event = new KeyboardEvent("keypress", { + bubbles: true, + cancelable: true, + view: null, + keyCode: 0, + charCode: aString.charCodeAt(i), + }); + gFindBar._findField.dispatchEvent(event); + } + } + + function onPageShow() { + var findField = gFindBar._findField; + document.getElementById("cmd_find").doCommand(); + + var matchCaseCheckbox = gFindBar.getElement("find-case-sensitive"); + if (!matchCaseCheckbox.hidden & matchCaseCheckbox.checked) + matchCaseCheckbox.click(); + + // Perform search + var searchStr = "z"; + enterStringIntoFindField(searchStr); + + // Highlight search term + var highlight = gFindBar.getElement("highlight"); + if (!highlight.checked) + highlight.click(); + + // Delete search term + var event = new KeyboardEvent("keypress", { + bubbles: true, + cancelable: true, + view: null, + keyCode: KeyEvent.DOM_VK_BACK_SPACE, + charCode: 0, + }); + gFindBar._findField.dispatchEvent(event); + + var notRed = !findField.hasAttribute("status") || + (findField.getAttribute("status") != "notfound"); + ok(notRed, "Find Bar textbox is correct colour"); + finish(); + } + ]]></script> + + <commandset> + <command id="cmd_find" oncommand="document.getElementById('FindToolbar').onFindCommand();"/> + </commandset> + <browser type="content" primary="true" flex="1" id="content" messagemanagergroup="test" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/bug451540_window.xhtml b/toolkit/content/tests/chrome/bug451540_window.xhtml new file mode 100644 index 0000000000..f8b8900ac6 --- /dev/null +++ b/toolkit/content/tests/chrome/bug451540_window.xhtml @@ -0,0 +1,255 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"?> + +<window id="451540test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + title="451540 test"> + + <script type="application/javascript"><![CDATA[ + const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" + ); + const SEARCH_TEXT = "minefield"; + + let gFindBar = null; + let gBrowser; + + var SimpleTest = window.arguments[0].SimpleTest; + var ok = window.arguments[0].ok; + var is = window.arguments[0].is; + var info = window.arguments[0].info; + + SimpleTest.requestLongerTimeout(2); + + function startTest() { + gFindBar = document.getElementById("FindToolbar"); + gBrowser = document.getElementById("content"); + gBrowser.addEventListener("pageshow", onPageShow, { once: true }); + let data = `data:text/html,<input id="inp" type="text" /> + <textarea id="tarea"/>`; + BrowserTestUtils.startLoadingURIString(gBrowser, data); + } + + function promiseHighlightFinished() { + return new Promise(resolve => { + let listener = { + onHighlightFinished() { + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + } + }; + gFindBar.browser.finder.addResultListener(listener); + }); + } + + async function resetForNextTest(elementId, aText) { + if (!aText) + aText = SEARCH_TEXT; + + // Turn off highlighting + let highlightButton = gFindBar.getElement("highlight"); + if (highlightButton.checked) { + highlightButton.click(); + } + + // Initialise input + info(`setting element value to ${aText}`); + await SpecialPowers.spawn(gBrowser, [{elementId, aText}], async function(args) { + let {elementId, aText} = args; + let doc = content.document; + let element = doc.getElementById(elementId); + element.value = aText; + element.focus(); + }); + info(`just set element value to ${aText}`); + gFindBar._findField.value = SEARCH_TEXT; + + // Perform search and turn on highlighting + gFindBar._find(); + highlightButton.click(); + await promiseHighlightFinished(); + + // Move caret to start of element + info(`focusing element`); + await SpecialPowers.spawn(gBrowser, [elementId], async function(elementId) { + let doc = content.document; + let element = doc.getElementById(elementId); + element.focus(); + }); + info(`focused element`); + if (navigator.platform.includes("Mac")) { + await BrowserTestUtils.synthesizeKey("KEY_ArrowLeft", { metaKey: true }, gBrowser); + } else { + await BrowserTestUtils.synthesizeKey("KEY_Home", {}, gBrowser); + } + } + + async function testSelection(elementId, expectedRangeCount, message) { + await SpecialPowers.spawn(gBrowser, [{elementId, expectedRangeCount, message}], async function(args) { + let {elementId, expectedRangeCount, message} = args; + let doc = content.document; + let element = doc.getElementById(elementId); + let controller = element.editor.selectionController; + let selection = controller.getSelection(controller.SELECTION_FIND); + Assert.equal(selection.rangeCount, expectedRangeCount, message); + }); + } + + async function testInput(elementId, testTypeText) { + let isEditableElement = await SpecialPowers.spawn(gBrowser, [elementId], async function(elementId) { + let doc = content.document; + let element = doc.getElementById(elementId); + let elementClass = ChromeUtils.getClassName(element); + return elementClass === "HTMLInputElement" || + elementClass === "HTMLTextAreaElement"; + }); + if (!isEditableElement) { + return; + } + + let moveCaretToNextWordBoundary = async (aBrowser) => { + if (!navigator.platform.includes("Mac")) { + return BrowserTestUtils.synthesizeKey("KEY_ArrowRight", { accelKey: true }, aBrowser); + } + // macOS does not have default shortcut key to move caret per word. + return SpecialPowers.spawn(aBrowser, [], async () => { + content.docShell.doCommand("cmd_wordNext"); + }); + }; + + // Initialize the findbar + let matchCase = gFindBar.getElement("find-case-sensitive"); + if (matchCase.checked) { + matchCase.doCommand(); + } + + // First check match has been correctly highlighted + await resetForNextTest(elementId); + + await testSelection(elementId, 1, testTypeText + " correctly highlighted match"); + + // Test 2: check highlight removed when text added within the highlight + await BrowserTestUtils.synthesizeKey("KEY_ArrowRight", {}, gBrowser); + await BrowserTestUtils.synthesizeKey("a", {}, gBrowser); + + await testSelection(elementId, 0, testTypeText + " correctly removed highlight on text insertion"); + + // Test 3: check highlighting remains when text added before highlight + await resetForNextTest(elementId); + await BrowserTestUtils.synthesizeKey("a", {}, gBrowser); + await testSelection(elementId, 1, testTypeText + " highlight correctly remained on text insertion at start"); + + // Test 4: check highlighting remains when text added after highlight + await resetForNextTest(elementId); + for (let x = 0; x < SEARCH_TEXT.length; x++) { + await BrowserTestUtils.synthesizeKey("KEY_ArrowRight", {}, gBrowser); + } + await BrowserTestUtils.synthesizeKey("a", {}, gBrowser); + await testSelection(elementId, 1, testTypeText + " highlight correctly remained on text insertion at end"); + + // Test 5: deleting text within the highlight + await resetForNextTest(elementId); + await BrowserTestUtils.synthesizeKey("KEY_ArrowRight", {}, gBrowser); + await BrowserTestUtils.synthesizeKey("KEY_Backspace", {}, gBrowser); + await testSelection(elementId, 0, testTypeText + " correctly removed highlight on text deletion"); + + // Test 6: deleting text at end of highlight + await resetForNextTest(elementId, SEARCH_TEXT + "A"); + for (let x = 0; x < (SEARCH_TEXT + "A").length; x++) { + await BrowserTestUtils.synthesizeKey("KEY_ArrowRight", {}, gBrowser); + } + await BrowserTestUtils.synthesizeKey("KEY_Backspace", {}, gBrowser); + await testSelection(elementId, 1, testTypeText + " highlight correctly remained on text deletion at end"); + + // Test 7: deleting text at start of highlight + await resetForNextTest(elementId, "A" + SEARCH_TEXT); + await BrowserTestUtils.synthesizeKey("KEY_ArrowRight", {}, gBrowser); + await BrowserTestUtils.synthesizeKey("KEY_Backspace", {}, gBrowser); + await testSelection(elementId, 1, testTypeText + " highlight correctly remained on text deletion at start"); + + // Test 8: deleting selection + await resetForNextTest(elementId); + await BrowserTestUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true }, gBrowser); + await BrowserTestUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true }, gBrowser); + await BrowserTestUtils.synthesizeKey("x", { accelKey: true }, gBrowser); + await testSelection(elementId, 0, testTypeText + " correctly removed highlight on selection deletion"); + + // Test 9: Multiple matches within one editor (part 1) + // Check second match remains highlighted after inserting text into + // first match, and that its highlighting gets removed when the + // second match is edited + await resetForNextTest(elementId, SEARCH_TEXT + " " + SEARCH_TEXT); + await testSelection(elementId, 2, testTypeText + " correctly highlighted both matches"); + await BrowserTestUtils.synthesizeKey("KEY_ArrowRight", {}, gBrowser); + await BrowserTestUtils.synthesizeKey("a", {}, gBrowser); + await testSelection(elementId, 1, testTypeText + " correctly removed only the first highlight on text insertion"); + await moveCaretToNextWordBoundary(gBrowser); + await moveCaretToNextWordBoundary(gBrowser); + await BrowserTestUtils.synthesizeKey("KEY_ArrowLeft", {}, gBrowser); + await BrowserTestUtils.synthesizeKey("a", {}, gBrowser); + await testSelection(elementId, 0, testTypeText + " correctly removed second highlight on text insertion"); + + // Test 10: Multiple matches within one editor (part 2) + // Check second match remains highlighted after deleting text in + // first match, and that its highlighting gets removed when the + // second match is edited + await resetForNextTest(elementId, SEARCH_TEXT + " " + SEARCH_TEXT); + await testSelection(elementId, 2, testTypeText + " correctly highlighted both matches"); + await BrowserTestUtils.synthesizeKey("KEY_ArrowRight", {}, gBrowser); + await BrowserTestUtils.synthesizeKey("KEY_Backspace", {}, gBrowser); + await testSelection(elementId, 1, testTypeText + " correctly removed only the first highlight on text deletion"); + await moveCaretToNextWordBoundary(gBrowser); + await moveCaretToNextWordBoundary(gBrowser); + await BrowserTestUtils.synthesizeKey("KEY_ArrowLeft", {}, gBrowser); + await BrowserTestUtils.synthesizeKey("KEY_Backspace", {}, gBrowser); + await testSelection(elementId, 0, testTypeText + " correctly removed second highlight on text deletion"); + + // Test 11: Multiple matches within one editor (part 3) + // Check second match remains highlighted after deleting selection + // in first match, and that second match highlighting gets correctly + // removed when it has a selection deleted from it + await resetForNextTest(elementId, SEARCH_TEXT + " " + SEARCH_TEXT); + await BrowserTestUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true }, gBrowser); + await BrowserTestUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true }, gBrowser); + await BrowserTestUtils.synthesizeKey("x", { accelKey: true }, gBrowser); + await testSelection(elementId, 1, testTypeText + " correctly removed only first highlight on selection deletion"); + await moveCaretToNextWordBoundary(gBrowser); + await moveCaretToNextWordBoundary(gBrowser); + await BrowserTestUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true }, gBrowser); + await BrowserTestUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true }, gBrowser); + await BrowserTestUtils.synthesizeKey("x", { accelKey: true }, gBrowser); + await testSelection(elementId, 0, testTypeText + " correctly removed second highlight on selection deletion"); + + // Turn off highlighting + let highlightButton = gFindBar.getElement("highlight"); + if (highlightButton.checked) { + highlightButton.click(); + } + } + + function onPageShow() { + (async function() { + gFindBar.open(); + await testInput("inp", "Input:"); + await testInput("tarea", "Textarea:"); + })().then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + SimpleTest.waitForFocus(startTest, window); + ]]></script> + + <browser type="content" primary="true" flex="1" id="content" messagemanagergroup="test" src="about:blank"/> + <browser type="content" primary="true" flex="1" id="content-remote" remote="true" messagemanagergroup="test" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/bug624329_window.xhtml b/toolkit/content/tests/chrome/bug624329_window.xhtml new file mode 100644 index 0000000000..8cef32e4e5 --- /dev/null +++ b/toolkit/content/tests/chrome/bug624329_window.xhtml @@ -0,0 +1,22 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Test for bug 624329 context menu position" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + context="menu"> + + <script> + window.arguments[0].SimpleTest.waitForFocus(window.arguments[0].childFocused, window); + </script> + + <menupopup id="menu"> + <!-- The bug demonstrated only when the accesskey was presented separately + from the label. + e.g. because the accesskey is not a letter in the label. + + The bug demonstrates only on the first show of the context menu + unless menu items are removed/added each time the menu is + constructed. --> + <menuitem label="Long label to ensure the popup would hit the right of the screen" accesskey="1"/> + </menupopup> +</window> diff --git a/toolkit/content/tests/chrome/chrome.toml b/toolkit/content/tests/chrome/chrome.toml new file mode 100644 index 0000000000..70fa12c4b6 --- /dev/null +++ b/toolkit/content/tests/chrome/chrome.toml @@ -0,0 +1,360 @@ +[DEFAULT] +skip-if = ["os == 'android'"] +support-files = [ + "../widgets/popup_shared.js", + "../widgets/tree_shared.js", + "RegisterUnregisterChrome.js", + "bug263683_window.xhtml", + "bug304188_window.xhtml", + "bug331215_window.xhtml", + "bug360437_window.xhtml", + "bug366992_window.xhtml", + "bug409624_window.xhtml", + "bug429723_window.xhtml", + "bug624329_window.xhtml", + "dialog_button.xhtml", + "dialog_dialogfocus.xhtml", + "dialog_dialogfocus2.xhtml", + "file_empty.xhtml", + "file_edit_contextmenu.xhtml", + "file_about_networking_wsh.py", + "file_autocomplete_with_composition.js", + "file_editor_with_autocomplete.js", + "findbar_entireword_window.xhtml", + "findbar_events_window.xhtml", + "findbar_window.xhtml", + "frame_popup_anchor.xhtml", + "frame_subframe_origin_subframe1.xhtml", + "frame_subframe_origin_subframe2.xhtml", + "popup_trigger.js", + "sample_entireword_latin1.html", + "window_browser_drop.xhtml", + "window_keys.xhtml", + "window_largemenu.xhtml", + "window_panel.xhtml", + "window_panel_anchoradjust.xhtml", + "window_popup_anchor.xhtml", + "window_popup_anchoratrect.xhtml", + "window_popup_attribute.xhtml", + "window_popup_button.xhtml", + "window_popup_preventdefault_chrome.xhtml", + "window_preferences.xhtml", + "window_preferences2.xhtml", + "window_preferences3.xhtml", + "window_preferences_commandretarget.xhtml", + "window_preferences_disabled.xhtml", + "window_screenPosSize.xhtml", + "window_showcaret.xhtml", + "window_subframe_origin.xhtml", + "window_tooltip.xhtml", + "xul_selectcontrol.js", +] +prefs = [ + "gfx.font_rendering.fallback.async=false", + "widget.non-native-theme.enabled=false", +] + +["test_about_networking.html"] + +["test_arrowpanel.xhtml"] +skip-if = [ + "os == 'win' && verify", + "win10_2009", # Bug 1727507 + "win11_2009", # Bug 1797751 +] + +["test_autocomplete2.xhtml"] + +["test_autocomplete3.xhtml"] + +["test_autocomplete4.xhtml"] + +["test_autocomplete5.xhtml"] + +["test_autocomplete_emphasis.xhtml"] + +["test_autocomplete_mac_caret.xhtml"] +run-if = ["os == 'mac'"] + +["test_autocomplete_placehold_last_complete.xhtml"] + +["test_autocomplete_with_composition_on_input.html"] +skip-if = [ + "apple_catalina" # Bug 1784825 +] +["test_browser_drop.xhtml"] + +["test_bug1048178.xhtml"] +skip-if = ["apple_catalina"] + +["test_bug263683.xhtml"] +skip-if = [ + "debug && os == 'linux'", + "debug && os == 'win'", +] + +["test_bug304188.xhtml"] +skip-if = ["true"] + +["test_bug331215.xhtml"] +skip-if = ["true"] # Bug 1339326 #Bug 1582327 + +["test_bug360220.xhtml"] + +["test_bug360437.xhtml"] +skip-if = ["true"] # Bug 1264604 # Bug 1784826 + +["test_bug365773.xhtml"] + +["test_bug366992.xhtml"] + +["test_bug382990.xhtml"] + +["test_bug409624.xhtml"] + +["test_bug418874.xhtml"] + +["test_bug429723.xhtml"] + +["test_bug451540.xhtml"] +support-files = ["bug451540_window.xhtml"] + +["test_bug457632.xhtml"] + +["test_bug460942.xhtml"] + +["test_bug471776.xhtml"] + +["test_bug509732.xhtml"] + +["test_bug557987.xhtml"] + +["test_bug562554.xhtml"] + +["test_bug624329.xhtml"] +fail-if = ["os == 'linux' && os_version == '18.04'"] # Bug 1600194 + +["test_bug792324.xhtml"] + +["test_button.xhtml"] + +["test_chromemargin.xhtml"] +support-files = "window_chromemargin.xhtml" +skip-if = ["apple_catalina"] + +["test_closemenu_attribute.xhtml"] + +["test_contextmenu_list.xhtml"] + +["test_contextmenu_rtl.xhtml"] + +["test_cursorsnap.xhtml"] +disabled = true +#skip-if = os != 'win' +support-files = [ + "window_cursorsnap_dialog.xhtml", + "window_cursorsnap_wizard.xhtml", +] + +["test_custom_element_base.xhtml"] + +["test_custom_element_delay_connection.xhtml"] + +["test_custom_element_parts.html"] + +["test_deck.xhtml"] + +["test_dialog_button.xhtml"] + +["test_dialogfocus.xhtml"] + +["test_edit_contextmenu.html"] + +["test_editor_for_input_with_autocomplete.html"] + +["test_findbar.xhtml"] +skip-if = ["apple_catalina"] # macosx1014/15 due to 1550078 + +["test_findbar_entireword.xhtml"] + +["test_findbar_events.xhtml"] + +["test_frames.xhtml"] + +["test_hiddenitems.xhtml"] + +["test_hiddenpaging.xhtml"] + +["test_keys.xhtml"] + +["test_labelcontrol.xhtml"] + +["test_largemenu.html"] +skip-if = ["os == 'linux' && !debug"] # Bug 1207174 + +["test_maximized_persist.xhtml"] +support-files = [ + "window_maximized_persist.xhtml", + "file_maximized_persist.js", +] + +["test_maximized_persist_with_no_titlebar.xhtml"] +support-files = [ + "window_maximized_persist_with_no_titlebar.xhtml", + "file_maximized_persist.js", +] + +["test_menu.xhtml"] + +["test_menu_activateitem.xhtml"] + +["test_menu_hide.xhtml"] + +["test_menu_mouse_menuactive.xhtml"] + +["test_menu_withcapture.xhtml"] + +["test_menuchecks.xhtml"] + +["test_menuitem_blink.xhtml"] + +["test_menuitem_commands.xhtml"] + +["test_menulist.xhtml"] + +["test_menulist_in_popup.xhtml"] + +["test_menulist_keynav.xhtml"] + +["test_menulist_null_value.xhtml"] + +["test_menulist_paging.xhtml"] + +["test_menulist_position.xhtml"] + +["test_mousescroll.xhtml"] + +["test_mozinputbox_dictionary.xhtml"] + +["test_named_deck.html"] + +["test_navigate_persist.html"] +support-files = ["window_navigate_persist.html"] + +["test_notificationbox.xhtml"] +skip-if = [ + "os == 'linux' && debug", # Bug 1429649 + "os == 'win'", # Bug 1429649 +] + +["test_panel.xhtml"] +skip-if = ["apple_catalina"] # macosx1014 due to 1550078 + +["test_panel_anchoradjust.xhtml"] + +# test_panel_focus.xhtml won't work if the Full Keyboard Access preference is set to +# textboxes and lists only, so skip this test on Mac +["test_panel_focus.xhtml"] +support-files = "window_panel_focus.xhtml" +skip-if = ["apple_catalina"] + +["test_panel_hover_menu.xhtml"] + +["test_panel_open.xhtml"] + +["test_panelfrommenu.xhtml"] + +["test_popup_anchor.xhtml"] + +["test_popup_anchoratrect.xhtml"] +skip-if = ["os == 'linux'"] # 1167694 + +["test_popup_attribute.xhtml"] +skip-if = [ + "os == 'linux' && os_version == '18.04' && asan", # Bug 1582610 + "apple_catalina && debug", # Bug 1281360 +] + +["test_popup_button.xhtml"] +skip-if = [ + "os == 'linux' && os_version == '18.04' && asan", # Bug 1582610 + "apple_catalina && debug", # Bug 1281360 +] + +["test_popup_coords.xhtml"] + +["test_popup_keys.xhtml"] + +["test_popup_moveToAnchor.xhtml"] + +["test_popup_preventdefault.xhtml"] + +["test_popup_preventdefault_chrome.xhtml"] + +["test_popup_recreate.xhtml"] + +["test_popup_scaled.xhtml"] + +["test_popup_tree.xhtml"] + +["test_popuphidden.xhtml"] + +["test_popupincontent.xhtml"] +skip-if = ["verify && os == 'win'"] + +["test_popupremoving.xhtml"] + +["test_position.xhtml"] + +["test_preferences.xhtml"] + +["test_preferences_beforeaccept.xhtml"] +support-files = ["window_preferences_beforeaccept.xhtml"] + +["test_preferences_onsyncfrompreference.xhtml"] +support-files = ["window_preferences_onsyncfrompreference.xhtml"] + +["test_props.xhtml"] + +["test_radio.xhtml"] + +["test_richlistbox.xhtml"] + +["test_screenPersistence.xhtml"] + +["test_scrollbar.xhtml"] + +["test_showcaret.xhtml"] + +["test_subframe_origin.xhtml"] + +["test_tabbox.xhtml"] + +["test_tabindex.xhtml"] + +["test_textbox_search.xhtml"] + +["test_tooltip.xhtml"] +skip-if = [ + "apple_catalina", # Bug 1141245, frequent timeouts on OSX 10.14, Windows + "os == 'win'", # Bug 1141245, frequent timeouts on OSX 10.14, Windows +] + +["test_tooltip_noautohide.xhtml"] + +["test_tree.xhtml"] + +["test_tree_hier.xhtml"] + +["test_tree_scroll.xhtml"] +support-files = [ + "!/gfx/layers/apz/test/mochitest/apz_test_utils.js", + "!/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js", +] + +["test_tree_single.xhtml"] + +["test_tree_view.xhtml"] + +["test_window_intrinsic_size.xhtml"] +support-files = ["window_intrinsic_size.xhtml"] diff --git a/toolkit/content/tests/chrome/dialog_button.xhtml b/toolkit/content/tests/chrome/dialog_button.xhtml new file mode 100644 index 0000000000..64bf916fff --- /dev/null +++ b/toolkit/content/tests/chrome/dialog_button.xhtml @@ -0,0 +1,9 @@ +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<window id='root' xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <dialog id="dialog-focus" + buttons="accept" + buttonlabelaccept="accept" + buttonaccesskeyaccept="a"> + <button id="button"></button> + </dialog> +</window> diff --git a/toolkit/content/tests/chrome/dialog_dialogfocus.xhtml b/toolkit/content/tests/chrome/dialog_dialogfocus.xhtml new file mode 100644 index 0000000000..bcd303b52f --- /dev/null +++ b/toolkit/content/tests/chrome/dialog_dialogfocus.xhtml @@ -0,0 +1,62 @@ +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> +<dialog id="dialog-focus" + buttons="extra2,accept,cancel"> + +<tabbox id="tabbox" hidden="true"> + <tabs> + <tab id="tab" label="Tab"/> + </tabs> + <tabpanels> + <tabpanel> + <button id="tabbutton" label="Tab Button"/> + <button id="tabbutton2" label="Tab Button 2"/> + </tabpanel> + </tabpanels> +</tabbox> + +<html:input id="textbox-yes" value="textbox-yes" hidden="true"/> +<html:input id="textbox-no" value="textbox-no" noinitialfocus="true" hidden="true"/> +<button id="one" label="One"/> +<button id="two" label="Two" hidden="true"/> + +<script> +if (window.arguments) { + var step = window.arguments[0]; + switch (step) { + case 2: + document.getElementById("one").setAttribute("noinitialfocus", "true"); + break; + case 3: + document.getElementById("one").hidden = true; + // no-fallthrough + case 4: + document.getElementById("tabbutton2").setAttribute("noinitialfocus", "true"); + // no-fallthrough + case 5: + document.getElementById("tabbutton").setAttribute("noinitialfocus", "true"); + // no-fallthrough + case 6: + document.getElementById("tabbox").hidden = false; + break; + case 7: + window.addEventListener("load", function() { + var two = document.getElementById("two"); + two.hidden = false; + two.focus(); + }); + break; + case 8: + document.getElementById("textbox-yes").hidden = false; + break; + case 9: + document.getElementById("textbox-no").hidden = false; + break; + } +} +</script> + +</dialog> +</window> diff --git a/toolkit/content/tests/chrome/dialog_dialogfocus2.xhtml b/toolkit/content/tests/chrome/dialog_dialogfocus2.xhtml new file mode 100644 index 0000000000..da4b3dbdfa --- /dev/null +++ b/toolkit/content/tests/chrome/dialog_dialogfocus2.xhtml @@ -0,0 +1,8 @@ +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="root" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<dialog id="dialog-focus" + buttons="none"> + <button id="nonbutton" noinitialfocus="true"/> +</dialog> +</window> diff --git a/toolkit/content/tests/chrome/file_about_networking_wsh.py b/toolkit/content/tests/chrome/file_about_networking_wsh.py new file mode 100644 index 0000000000..57bd353c21 --- /dev/null +++ b/toolkit/content/tests/chrome/file_about_networking_wsh.py @@ -0,0 +1,10 @@ +from mod_pywebsocket import msgutil + + +def web_socket_do_extra_handshake(request): + pass + + +def web_socket_transfer_data(request): + while not request.client_terminated: + msgutil.receive_message(request) diff --git a/toolkit/content/tests/chrome/file_autocomplete_with_composition.js b/toolkit/content/tests/chrome/file_autocomplete_with_composition.js new file mode 100644 index 0000000000..86619a95d1 --- /dev/null +++ b/toolkit/content/tests/chrome/file_autocomplete_with_composition.js @@ -0,0 +1,714 @@ +// nsDoTestsForAutoCompleteWithComposition tests autocomplete with composition. +// Users must include SimpleTest.js and EventUtils.js. + +function waitForCondition(condition, nextTest) { + var tries = 0; + var interval = setInterval(function () { + if (condition() || tries >= 30) { + moveOn(); + } + tries++; + }, 100); + var moveOn = function () { + clearInterval(interval); + nextTest(); + }; +} + +function nsDoTestsForAutoCompleteWithComposition( + aDescription, + aWindow, + aTarget, + aAutoCompleteController, + aIsFunc, + aGetTargetValueFunc, + aOnFinishFunc +) { + this._description = aDescription; + this._window = aWindow; + this._target = aTarget; + this._controller = aAutoCompleteController; + + this._is = aIsFunc; + this._getTargetValue = aGetTargetValueFunc; + this._onFinish = aOnFinishFunc; + + this._target.focus(); + + this._DefaultCompleteDefaultIndex = + this._controller.input.completeDefaultIndex; + + this._doTests(); +} + +nsDoTestsForAutoCompleteWithComposition.prototype = { + _window: null, + _target: null, + _controller: null, + _DefaultCompleteDefaultIndex: false, + _description: "", + + _is: null, + _getTargetValue() { + return "not initialized"; + }, + _onFinish: null, + + _doTests() { + if (++this._testingIndex == this._tests.length) { + this._controller.input.completeDefaultIndex = + this._DefaultCompleteDefaultIndex; + this._onFinish(); + return; + } + + var test = this._tests[this._testingIndex]; + if ( + this._controller.input.completeDefaultIndex != test.completeDefaultIndex + ) { + this._controller.input.completeDefaultIndex = test.completeDefaultIndex; + } + test.execute(this._window); + + if (test.popup) { + waitForCondition( + () => this._controller.input.popupOpen, + this._checkResult.bind(this) + ); + } else { + waitForCondition(() => { + this._controller.searchStatus >= + Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH; + }, this._checkResult.bind(this)); + } + }, + + _checkResult() { + var test = this._tests[this._testingIndex]; + this._is( + this._getTargetValue(), + test.value, + this._description + ", " + test.description + ": value" + ); + this._is( + this._controller.searchString, + test.searchString, + this._description + ", " + test.description + ": searchString" + ); + this._is( + this._controller.input.popupOpen, + test.popup, + this._description + ", " + test.description + ": popupOpen" + ); + this._doTests(); + }, + + _testingIndex: -1, + _tests: [ + // Simple composition when popup hasn't been shown. + // The autocomplete popup should not be shown during composition, but + // after compositionend, the popup should be shown. + { + description: "compositionstart shouldn't open the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { + string: "M", + clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 1, length: 0 }, + key: { key: "M" }, + }, + aWindow + ); + }, + popup: false, + value: "M", + searchString: "", + }, + { + description: "modifying composition string shouldn't open the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { + string: "Mo", + clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 2, length: 0 }, + key: { key: "o" }, + }, + aWindow + ); + }, + popup: false, + value: "Mo", + searchString: "", + }, + { + description: "compositionend should open the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeComposition( + { type: "compositioncommitasis", key: { key: "KEY_Enter" } }, + aWindow + ); + }, + popup: true, + value: "Mo", + searchString: "Mo", + }, + // If composition starts when popup is shown, the compositionstart event + // should cause closing the popup. + { + description: "compositionstart should close the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { + string: "z", + clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 1, length: 0 }, + key: { key: "z" }, + }, + aWindow + ); + }, + popup: false, + value: "Moz", + searchString: "Mo", + }, + { + description: "modifying composition string shouldn't reopen the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { + string: "zi", + clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 2, length: 0 }, + key: { key: "i" }, + }, + aWindow + ); + }, + popup: false, + value: "Mozi", + searchString: "Mo", + }, + { + description: + "compositionend should research the result and open the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeComposition( + { type: "compositioncommitasis", key: { key: "KEY_Enter" } }, + aWindow + ); + }, + popup: true, + value: "Mozi", + searchString: "Mozi", + }, + // If composition is cancelled, the value shouldn't be changed. + { + description: "compositionstart should reclose the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { + string: "l", + clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 1, length: 0 }, + key: { key: "l" }, + }, + aWindow + ); + }, + popup: false, + value: "Mozil", + searchString: "Mozi", + }, + { + description: "modifying composition string shouldn't reopen the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { + string: "ll", + clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 2, length: 0 }, + key: { key: "l" }, + }, + aWindow + ); + }, + popup: false, + value: "Mozill", + searchString: "Mozi", + }, + { + description: + "modifying composition string to empty string shouldn't reopen the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { string: "", clauses: [{ length: 0, attr: 0 }] }, + caret: { start: 0, length: 0 }, + key: { key: "KEY_Backspace" }, + }, + aWindow + ); + }, + popup: false, + value: "Mozi", + searchString: "Mozi", + }, + { + description: "cancled compositionend should reopen the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeComposition( + { type: "compositioncommit", data: "", key: { key: "KEY_Escape" } }, + aWindow + ); + }, + popup: true, + value: "Mozi", + searchString: "Mozi", + }, + // But if composition replaces some characters and canceled, the search + // string should be the latest value. + { + description: + "compositionstart with selected string should close the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeKey("VK_LEFT", { shiftKey: true }, aWindow); + synthesizeKey("VK_LEFT", { shiftKey: true }, aWindow); + synthesizeCompositionChange( + { + composition: { + string: "z", + clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 1, length: 0 }, + key: { key: "z" }, + }, + aWindow + ); + }, + popup: false, + value: "Moz", + searchString: "Mozi", + }, + { + description: "modifying composition string shouldn't reopen the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { + string: "zi", + clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 2, length: 0 }, + key: { key: "i" }, + }, + aWindow + ); + }, + popup: false, + value: "Mozi", + searchString: "Mozi", + }, + { + description: + "modifying composition string to empty string shouldn't reopen the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { string: "", clauses: [{ length: 0, attr: 0 }] }, + caret: { start: 0, length: 0 }, + key: { key: "KEY_Backspace" }, + }, + aWindow + ); + }, + popup: false, + value: "Mo", + searchString: "Mozi", + }, + { + description: + "canceled compositionend should search the result with the latest value", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeComposition( + { type: "compositioncommitasis", key: { key: "KEY_Escape" } }, + aWindow + ); + }, + popup: true, + value: "Mo", + searchString: "Mo", + }, + // If all characters are removed, the popup should be closed. + { + description: + "the value becomes empty by backspace, the popup should be closed", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeKey("KEY_Backspace", {}, aWindow); + synthesizeKey("KEY_Backspace", {}, aWindow); + }, + popup: false, + value: "", + searchString: "", + }, + // composition which is canceled shouldn't cause opening the popup. + { + description: "compositionstart shouldn't open the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { + string: "M", + clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 1, length: 0 }, + key: { key: "M" }, + }, + aWindow + ); + }, + popup: false, + value: "M", + searchString: "", + }, + { + description: "modifying composition string shouldn't open the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { + string: "Mo", + clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 2, length: 0 }, + key: { key: "o" }, + }, + aWindow + ); + }, + popup: false, + value: "Mo", + searchString: "", + }, + { + description: + "modifying composition string to empty string shouldn't open the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { string: "", clauses: [{ length: 0, attr: 0 }] }, + caret: { start: 0, length: 0 }, + key: { key: "KEY_Backspace" }, + }, + aWindow + ); + }, + popup: false, + value: "", + searchString: "", + }, + { + description: + "canceled compositionend shouldn't open the popup if it was closed", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeComposition( + { type: "compositioncommitasis", key: { key: "KEY_Escape" } }, + aWindow + ); + }, + popup: false, + value: "", + searchString: "", + }, + // Down key should open the popup even if the editor is empty. + { + description: "DOWN key should open the popup even if the value is empty", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeKey("KEY_ArrowDown", {}, aWindow); + }, + popup: true, + value: "", + searchString: "", + }, + // If popup is open at starting composition, the popup should be reopened + // after composition anyway. + { + description: "compositionstart shouldn't open the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { + string: "M", + clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 1, length: 0 }, + key: { key: "M" }, + }, + aWindow + ); + }, + popup: false, + value: "M", + searchString: "", + }, + { + description: "modifying composition string shouldn't open the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { + string: "Mo", + clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 2, length: 0 }, + key: { key: "o" }, + }, + aWindow + ); + }, + popup: false, + value: "Mo", + searchString: "", + }, + { + description: + "modifying composition string to empty string shouldn't open the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { string: "", clauses: [{ length: 0, attr: 0 }] }, + caret: { start: 0, length: 0 }, + key: { key: "KEY_Backspace" }, + }, + aWindow + ); + }, + popup: false, + value: "", + searchString: "", + }, + { + description: + "canceled compositionend should open the popup if it was opened", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeComposition( + { type: "compositioncommitasis", key: { key: "KEY_Escape" } }, + aWindow + ); + }, + popup: true, + value: "", + searchString: "", + }, + // Type normally, and hit escape, the popup should be closed. + { + description: "ESCAPE should close the popup after typing something", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeKey("M", {}, aWindow); + synthesizeKey("o", {}, aWindow); + synthesizeKey("KEY_Escape", {}, aWindow); + }, + popup: false, + value: "Mo", + searchString: "Mo", + }, + // Even if the popup is closed, composition which is canceled should open + // the popup if the value isn't empty. + // XXX This might not be good behavior, but anyway, this is minor issue... + { + description: "compositionstart shouldn't open the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { + string: "z", + clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 1, length: 0 }, + key: { key: "z" }, + }, + aWindow + ); + }, + popup: false, + value: "Moz", + searchString: "Mo", + }, + { + description: "modifying composition string shouldn't open the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { + string: "zi", + clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 2, length: 0 }, + key: { key: "i" }, + }, + aWindow + ); + }, + popup: false, + value: "Mozi", + searchString: "Mo", + }, + { + description: + "modifying composition string to empty string shouldn't open the popup", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { string: "", clauses: [{ length: 0, attr: 0 }] }, + caret: { start: 0, length: 0 }, + key: { key: "KEY_Backspace" }, + }, + aWindow + ); + }, + popup: false, + value: "Mo", + searchString: "Mo", + }, + { + description: + "canceled compositionend shouldn't open the popup if the popup was closed", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeComposition( + { type: "compositioncommitasis", key: { key: "KEY_Escape" } }, + aWindow + ); + }, + popup: true, + value: "Mo", + searchString: "Mo", + }, + // House keeping... + { + description: "house keeping for next tests", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeKey("KEY_Backspace", {}, aWindow); + synthesizeKey("KEY_Backspace", {}, aWindow); + }, + popup: false, + value: "", + searchString: "", + }, + // Testing for nsIAutoCompleteInput.completeDefaultIndex being true. + { + description: + "compositionstart shouldn't open the popup (completeDefaultIndex is true)", + completeDefaultIndex: true, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { + string: "M", + clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 1, length: 0 }, + key: { key: "M" }, + }, + aWindow + ); + }, + popup: false, + value: "M", + searchString: "", + }, + { + description: + "modifying composition string shouldn't open the popup (completeDefaultIndex is true)", + completeDefaultIndex: true, + execute(aWindow) { + synthesizeCompositionChange( + { + composition: { + string: "Mo", + clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }], + }, + caret: { start: 2, length: 0 }, + key: { key: "o" }, + }, + aWindow + ); + }, + popup: false, + value: "Mo", + searchString: "", + }, + { + description: + "compositionend should open the popup (completeDefaultIndex is true)", + completeDefaultIndex: true, + execute(aWindow) { + synthesizeComposition( + { type: "compositioncommitasis", key: { key: "KEY_Enter" } }, + aWindow + ); + }, + popup: true, + value: "Mozilla", + searchString: "Mo", + }, + // House keeping... + { + description: "house keeping for next tests", + completeDefaultIndex: false, + execute(aWindow) { + synthesizeKey("KEY_Backspace", {}, aWindow); + synthesizeKey("KEY_Backspace", {}, aWindow); + synthesizeKey("KEY_Backspace", {}, aWindow); + synthesizeKey("KEY_Backspace", {}, aWindow); + synthesizeKey("KEY_Backspace", {}, aWindow); + synthesizeKey("KEY_Backspace", {}, aWindow); + }, + popup: false, + value: "", + searchString: "", + }, + ], +}; diff --git a/toolkit/content/tests/chrome/file_edit_contextmenu.xhtml b/toolkit/content/tests/chrome/file_edit_contextmenu.xhtml new file mode 100644 index 0000000000..e4d1a79040 --- /dev/null +++ b/toolkit/content/tests/chrome/file_edit_contextmenu.xhtml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet href="chrome://global/skin/global.css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> +<script> +customElements.define("shadow-input", class extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }); + this.shadowRoot.appendChild(document.createElement("input")); + } +}); +</script> +<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/> +<!-- Copied from toolkit/content/editMenuCommands.inc.xul --> +<script type="application/javascript" src="chrome://global/content/editMenuOverlay.js"/> +<commandset id="editMenuCommands"> + <commandset id="editMenuCommandSetAll" commandupdater="true" events="focus,select" + oncommandupdate="goUpdateGlobalEditMenuItems()"/> + <commandset id="editMenuCommandSetUndo" commandupdater="true" events="undo" + oncommandupdate="goUpdateUndoEditMenuItems()"/> + <commandset id="editMenuCommandSetPaste" commandupdater="true" events="clipboard" + oncommandupdate="goUpdatePasteMenuItems()"/> + <command id="cmd_undo" oncommand="goDoCommand('cmd_undo')"/> + <command id="cmd_redo" oncommand="goDoCommand('cmd_redo')"/> + <command id="cmd_cut" oncommand="goDoCommand('cmd_cut')"/> + <command id="cmd_copy" oncommand="goDoCommand('cmd_copy')"/> + <command id="cmd_paste" oncommand="goDoCommand('cmd_paste')"/> + <command id="cmd_delete" oncommand="goDoCommand('cmd_delete')"/> + <command id="cmd_selectAll" oncommand="goDoCommand('cmd_selectAll')"/> + <command id="cmd_switchTextDirection" oncommand="goDoCommand('cmd_switchTextDirection');"/> +</commandset> + +<menupopup id="outer-context-menu"> + <menuseparator id="customizeMailToolbarMenuSeparator"/> + <menuitem id="hello" label="Hello" accesskey="H"/> +</menupopup> + +<hbox context="outer-context-menu"> +<html:textarea /> +<html:input /> +<search-textbox /> +<html:shadow-input /> +</hbox> + +</window> diff --git a/toolkit/content/tests/chrome/file_editor_with_autocomplete.js b/toolkit/content/tests/chrome/file_editor_with_autocomplete.js new file mode 100644 index 0000000000..78611efc70 --- /dev/null +++ b/toolkit/content/tests/chrome/file_editor_with_autocomplete.js @@ -0,0 +1,627 @@ +// nsDoTestsForEditorWithAutoComplete tests basic functions of editor with autocomplete. +// Users must include SimpleTest.js and EventUtils.js, and register "Mozilla" to the autocomplete for the target. + +async function waitForCondition(condition) { + return new Promise(resolve => { + var tries = 0; + var interval = setInterval(function () { + if (condition() || tries >= 60) { + moveOn(); + } + tries++; + }, 100); + var moveOn = function () { + clearInterval(interval); + resolve(); + }; + }); +} + +function nsDoTestsForEditorWithAutoComplete( + aDescription, + aWindow, + aTarget, + aAutoCompleteController, + aIsFunc, + aTodoIsFunc, + aGetTargetValueFunc +) { + this._description = aDescription; + this._window = aWindow; + this._target = aTarget; + this._controller = aAutoCompleteController; + + this._is = aIsFunc; + this._todo_is = aTodoIsFunc; + this._getTargetValue = aGetTargetValueFunc; + + this._target.focus(); + + this._DefaultCompleteDefaultIndex = + this._controller.input.completeDefaultIndex; +} + +nsDoTestsForEditorWithAutoComplete.prototype = { + _window: null, + _target: null, + _controller: null, + _DefaultCompleteDefaultIndex: false, + _description: "", + + _is: null, + _getTargetValue() { + return "not initialized"; + }, + + run: async function runTestsImpl() { + for (let test of this._tests) { + if ( + this._controller.input.completeDefaultIndex != test.completeDefaultIndex + ) { + this._controller.input.completeDefaultIndex = test.completeDefaultIndex; + } + + let beforeInputEvents = []; + let inputEvents = []; + function onBeforeInput(aEvent) { + beforeInputEvents.push(aEvent); + } + function onInput(aEvent) { + inputEvents.push(aEvent); + } + this._target.addEventListener("beforeinput", onBeforeInput); + this._target.addEventListener("input", onInput); + + if (test.execute(this._window, this._target) === false) { + this._target.removeEventListener("beforeinput", onBeforeInput); + this._target.removeEventListener("input", onInput); + continue; + } + + await waitForCondition(() => { + return ( + this._controller.searchStatus >= + Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH + ); + }); + this._target.removeEventListener("beforeinput", onBeforeInput); + this._target.removeEventListener("input", onInput); + this._checkResult(test, beforeInputEvents, inputEvents); + } + this._controller.input.completeDefaultIndex = + this._DefaultCompleteDefaultIndex; + }, + + _checkResult(aTest, aBeforeInputEvents, aInputEvents) { + this._is( + this._getTargetValue(), + aTest.value, + this._description + ", " + aTest.description + ": value" + ); + this._is( + this._controller.searchString, + aTest.searchString, + this._description + ", " + aTest.description + ": searchString" + ); + this._is( + this._controller.input.popupOpen, + aTest.popup, + this._description + ", " + aTest.description + ": popupOpen" + ); + this._is( + this._controller.searchStatus, + Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH, + this._description + ", " + aTest.description + ": status" + ); + this._is( + aBeforeInputEvents.length, + aTest.inputEvents.length, + this._description + + ", " + + aTest.description + + ": number of beforeinput events wrong" + ); + this._is( + aInputEvents.length, + aTest.inputEvents.length, + this._description + + ", " + + aTest.description + + ": number of input events wrong" + ); + for (let events of [aBeforeInputEvents, aInputEvents]) { + for (let i = 0; i < events.length; i++) { + if (aTest.inputEvents[i] === undefined) { + this._is( + true, + false, + this._description + + ", " + + aTest.description + + ': "beforeinput" and "input" event shouldn\'t be dispatched anymore' + ); + return; + } + this._is( + events[i] instanceof this._window.InputEvent, + true, + `${this._description}, ${aTest.description}: "${events[i].type}" event should be dispatched with InputEvent interface` + ); + let expectCancelable = + events[i].type === "beforeinput" && + (aTest.inputEvents[i].inputType !== "insertReplacementText" || + SpecialPowers.getBoolPref( + "dom.input_event.allow_to_cancel_set_user_input" + )); + + this._is( + events[i].cancelable, + expectCancelable, + `${this._description}, ${aTest.description}: "${ + events[i].type + }" event should ${expectCancelable ? "be" : "be never"} cancelable` + ); + this._is( + events[i].bubbles, + true, + `${this._description}, ${aTest.description}: "${events[i].type}" event should always bubble` + ); + this._is( + events[i].inputType, + aTest.inputEvents[i].inputType, + `${this._description}, ${aTest.description}: inputType of "${events[i].type}" event should be "${aTest.inputEvents[i].inputType}"` + ); + this._is( + events[i].data, + aTest.inputEvents[i].data, + `${this._description}, ${aTest.description}: data of "${events[i].type}" event should be ${aTest.inputEvents[i].data}` + ); + this._is( + events[i].dataTransfer, + null, + `${this._description}, ${aTest.description}: dataTransfer of "${events[i].type}" event should be null` + ); + this._is( + events[i].getTargetRanges().length, + 0, + `${this._description}, ${aTest.description}: getTargetRanges() of "${events[i].type}" event should return empty array` + ); + } + } + }, + + _tests: [ + { + description: + "Undo/Redo behavior check when typed text exactly matches the case: type 'Mo'", + completeDefaultIndex: false, + execute(aWindow, aTarget) { + synthesizeKey("M", { shiftKey: true }, aWindow); + synthesizeKey("o", {}, aWindow); + return true; + }, + popup: true, + value: "Mo", + searchString: "Mo", + inputEvents: [ + { inputType: "insertText", data: "M" }, + { inputType: "insertText", data: "o" }, + ], + }, + { + description: + "Undo/Redo behavior check when typed text exactly matches the case: select 'Mozilla' to complete the word", + completeDefaultIndex: false, + execute(aWindow, aTarget) { + synthesizeKey("KEY_ArrowDown", {}, aWindow); + synthesizeKey("KEY_Enter", {}, aWindow); + return true; + }, + popup: false, + value: "Mozilla", + searchString: "Mozilla", + inputEvents: [{ inputType: "insertReplacementText", data: "Mozilla" }], + }, + { + description: + "Undo/Redo behavior check when typed text exactly matches the case: undo the word, but typed text shouldn't be canceled", + completeDefaultIndex: false, + execute(aWindow, aTarget) { + synthesizeKey("z", { accelKey: true }, aWindow); + return true; + }, + popup: true, + value: "Mo", + searchString: "Mo", + inputEvents: [{ inputType: "historyUndo", data: null }], + }, + { + description: + "Undo/Redo behavior check when typed text exactly matches the case: undo the typed text", + completeDefaultIndex: false, + execute(aWindow, aTarget) { + synthesizeKey("z", { accelKey: true }, aWindow); + return true; + }, + popup: false, + value: "", + searchString: "", + inputEvents: [{ inputType: "historyUndo", data: null }], + }, + { + description: + "Undo/Redo behavior check when typed text exactly matches the case: redo the typed text", + completeDefaultIndex: false, + execute(aWindow, aTarget) { + synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow); + return true; + }, + popup: true, + value: "Mo", + searchString: "Mo", + inputEvents: [{ inputType: "historyRedo", data: null }], + }, + { + description: + "Undo/Redo behavior check when typed text exactly matches the case: redo the word", + completeDefaultIndex: false, + execute(aWindow, aTarget) { + synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow); + return true; + }, + popup: true, + value: "Mozilla", + searchString: "Mozilla", + inputEvents: [{ inputType: "historyRedo", data: null }], + }, + { + description: + "Undo/Redo behavior check when typed text exactly matches the case: removing all text for next test...", + completeDefaultIndex: false, + execute(aWindow, aTarget) { + synthesizeKey("a", { accelKey: true }, aWindow); + synthesizeKey("KEY_Backspace", {}, aWindow); + return true; + }, + popup: false, + value: "", + searchString: "", + inputEvents: [{ inputType: "deleteContentBackward", data: null }], + }, + + { + description: + "Undo/Redo behavior check when typed text does not match the case: type 'mo'", + completeDefaultIndex: false, + execute(aWindow, aTarget) { + synthesizeKey("m", {}, aWindow); + synthesizeKey("o", {}, aWindow); + return true; + }, + popup: true, + value: "mo", + searchString: "mo", + inputEvents: [ + { inputType: "insertText", data: "m" }, + { inputType: "insertText", data: "o" }, + ], + }, + { + description: + "Undo/Redo behavior check when typed text does not match the case: select 'Mozilla' to complete the word", + completeDefaultIndex: false, + execute(aWindow, aTarget) { + synthesizeKey("KEY_ArrowDown", {}, aWindow); + synthesizeKey("KEY_Enter", {}, aWindow); + return true; + }, + popup: false, + value: "Mozilla", + searchString: "Mozilla", + inputEvents: [{ inputType: "insertReplacementText", data: "Mozilla" }], + }, + { + description: + "Undo/Redo behavior check when typed text does not match the case: undo the word, but typed text shouldn't be canceled", + completeDefaultIndex: false, + execute(aWindow, aTarget) { + synthesizeKey("z", { accelKey: true }, aWindow); + return true; + }, + popup: true, + value: "mo", + searchString: "mo", + inputEvents: [{ inputType: "historyUndo", data: null }], + }, + { + description: + "Undo/Redo behavior check when typed text does not match the case: undo the typed text", + completeDefaultIndex: false, + execute(aWindow, aTarget) { + synthesizeKey("z", { accelKey: true }, aWindow); + return true; + }, + popup: false, + value: "", + searchString: "", + inputEvents: [{ inputType: "historyUndo", data: null }], + }, + { + description: + "Undo/Redo behavior check when typed text does not match the case: redo the typed text", + completeDefaultIndex: false, + execute(aWindow, aTarget) { + synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow); + return true; + }, + popup: true, + value: "mo", + searchString: "mo", + inputEvents: [{ inputType: "historyRedo", data: null }], + }, + { + description: + "Undo/Redo behavior check when typed text does not match the case: redo the word", + completeDefaultIndex: false, + execute(aWindow, aTarget) { + synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow); + return true; + }, + popup: true, + value: "Mozilla", + searchString: "Mozilla", + inputEvents: [{ inputType: "historyRedo", data: null }], + }, + { + description: + "Undo/Redo behavior check when typed text does not match the case: removing all text for next test...", + completeDefaultIndex: false, + execute(aWindow, aTarget) { + synthesizeKey("a", { accelKey: true }, aWindow); + synthesizeKey("KEY_Backspace", {}, aWindow); + return true; + }, + popup: false, + value: "", + searchString: "", + inputEvents: [{ inputType: "deleteContentBackward", data: null }], + }, + + // Testing for nsIAutoCompleteInput.completeDefaultIndex being true. + { + description: + "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): type 'Mo'", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("M", { shiftKey: true }, aWindow); + synthesizeKey("o", {}, aWindow); + return true; + }, + popup: true, + value: "Mozilla", + searchString: "Mo", + inputEvents: [ + { inputType: "insertText", data: "M" }, + { inputType: "insertText", data: "o" }, + { inputType: "insertReplacementText", data: "Mozilla" }, + ], + }, + { + description: + "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): select 'Mozilla' to complete the word", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("KEY_ArrowDown", {}, aWindow); + synthesizeKey("KEY_Enter", {}, aWindow); + return true; + }, + popup: false, + value: "Mozilla", + searchString: "Mozilla", + inputEvents: [], + }, + { + description: + "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): undo the word, but typed text shouldn't be canceled", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("z", { accelKey: true }, aWindow); + return true; + }, + popup: true, + value: "Mo", + searchString: "Mo", + inputEvents: [{ inputType: "historyUndo", data: null }], + }, + { + description: + "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): undo the typed text", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("z", { accelKey: true }, aWindow); + return true; + }, + popup: false, + value: "", + searchString: "", + inputEvents: [{ inputType: "historyUndo", data: null }], + }, + { + description: + "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): redo the typed text", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow); + return true; + }, + popup: true, + value: "Mozilla", + searchString: "Mo", + inputEvents: [ + { inputType: "historyRedo", data: null }, + { inputType: "insertReplacementText", data: "Mozilla" }, + ], + }, + { + description: + "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): redo the word", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow); + return true; + }, + popup: true, + value: "Mozilla", + searchString: "Mo", + inputEvents: [], + }, + { + description: + "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): removing all text for next test...", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("a", { accelKey: true }, aWindow); + synthesizeKey("KEY_Backspace", {}, aWindow); + return true; + }, + popup: false, + value: "", + searchString: "", + inputEvents: [{ inputType: "deleteContentBackward", data: null }], + }, + + { + description: + "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): type 'mo'", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("m", {}, aWindow); + synthesizeKey("o", {}, aWindow); + return true; + }, + popup: true, + value: "mozilla", + searchString: "mo", + inputEvents: [ + { inputType: "insertText", data: "m" }, + { inputType: "insertText", data: "o" }, + { inputType: "insertReplacementText", data: "mozilla" }, + ], + }, + { + description: + "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): select 'Mozilla' to complete the word", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("KEY_ArrowDown", {}, aWindow); + synthesizeKey("KEY_Enter", {}, aWindow); + return true; + }, + popup: false, + value: "Mozilla", + searchString: "Mozilla", + inputEvents: [{ inputType: "insertReplacementText", data: "Mozilla" }], + }, + // Different from "exactly matches the case" case, modifying the case causes one additional transaction. + // Although we could make this transaction ignored. + { + description: + "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): undo the selected word, but typed text shouldn't be canceled", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("z", { accelKey: true }, aWindow); + return true; + }, + popup: true, + value: "mozilla", + searchString: "mozilla", + inputEvents: [{ inputType: "historyUndo", data: null }], + }, + { + description: + "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): undo the word, but typed text shouldn't be canceled", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("z", { accelKey: true }, aWindow); + return true; + }, + popup: true, + value: "mo", + searchString: "mo", + inputEvents: [{ inputType: "historyUndo", data: null }], + }, + { + description: + "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): undo the typed text", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("z", { accelKey: true }, aWindow); + return true; + }, + popup: false, + value: "", + searchString: "", + inputEvents: [{ inputType: "historyUndo", data: null }], + }, + // XXX This is odd case. Consistency with undo behavior, this should restore "mo". + // However, looks like that autocomplete automatically restores "mozilla". + // Additionally, looks like that it causes clearing the redo stack. + // Therefore, the following redo operations do nothing. + { + description: + "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): redo the typed text", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow); + return true; + }, + popup: true, + value: "mozilla", + searchString: "mo", + inputEvents: [ + { inputType: "historyRedo", data: null }, + { inputType: "insertReplacementText", data: "mozilla" }, + ], + }, + { + description: + "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): redo the default index word", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow); + return true; + }, + popup: true, + value: "mozilla", + searchString: "mo", + inputEvents: [], + }, + { + description: + "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): redo the word", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow); + return true; + }, + popup: true, + value: "mozilla", + searchString: "mo", + inputEvents: [], + }, + { + description: + "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): removing all text for next test...", + completeDefaultIndex: true, + execute(aWindow, aTarget) { + synthesizeKey("a", { accelKey: true }, aWindow); + synthesizeKey("KEY_Backspace", {}, aWindow); + return true; + }, + popup: false, + value: "", + searchString: "", + inputEvents: [{ inputType: "deleteContentBackward", data: null }], + }, + ], +}; diff --git a/toolkit/content/tests/chrome/file_empty.xhtml b/toolkit/content/tests/chrome/file_empty.xhtml new file mode 100644 index 0000000000..cda0d080bd --- /dev/null +++ b/toolkit/content/tests/chrome/file_empty.xhtml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8"?> +<html xmlns='http://www.w3.org/1999/xhtml'> +</html>
\ No newline at end of file diff --git a/toolkit/content/tests/chrome/file_maximized_persist.js b/toolkit/content/tests/chrome/file_maximized_persist.js new file mode 100644 index 0000000000..c0d543728b --- /dev/null +++ b/toolkit/content/tests/chrome/file_maximized_persist.js @@ -0,0 +1,141 @@ +SimpleTest.waitForExplicitFinish(); +const WIDTH = 300; +const HEIGHT = 300; +let gWindow; +let gTitlebar; + +function promiseMessage(msg) { + info(`wait for message "${msg}"`); + return new Promise(resolve => { + function listener(evt) { + info(`got message "${evt.data}"`); + if (evt.data == msg) { + window.removeEventListener("message", listener); + resolve(); + } + } + window.addEventListener("message", listener); + }); +} + +function openWindow(features = "") { + return window.browsingContext.topChromeWindow.openDialog( + gWindow, + "_blank", + "chrome,dialog=no,all," + features, + window + ); +} + +function checkWindow(msg, win, sizemode, width, height) { + is(win.windowState, sizemode, "sizemode should match " + msg); + if (sizemode == win.STATE_NORMAL) { + is(win.innerWidth, width, "width should match " + msg); + is(win.innerHeight, height, "height should match " + msg); + } +} + +function todoCheckWindow(msg, win, sizemode) { + todo_is(win.windowState, sizemode, "sizemode should match " + msg); +} + +// Persistence of "sizemode" is delayed to 500ms after it's changed. +// See SIZE_PERSISTENCE_TIMEOUT in nsWebShellWindow.cpp. +// We wait for 1000ms to ensure that it is actually persisted. +// We can also wait for condition that XULStore does have the value +// set, but that way we cannot test the cases where we don't expect +// persistence to happen. +function waitForSizeModePersisted() { + return new Promise(resolve => { + setTimeout(resolve, 1000); + }); +} + +async function changeSizeMode(func) { + let promiseSizeModeChange = promiseMessage("sizemodechange"); + func(); + await promiseSizeModeChange; + await waitForSizeModePersisted(); +} + +async function runTest(aWindow) { + gWindow = aWindow; + gTitlebar = aWindow != "window_maximized_persist_with_no_titlebar.xhtml"; + let win = openWindow(); + await SimpleTest.promiseFocus(win); + + // Check the default state. + const chrome_url = win.location.href; + checkWindow("when open initially", win, win.STATE_NORMAL, WIDTH, HEIGHT); + const widthDiff = win.outerWidth - win.innerWidth; + const heightDiff = win.outerHeight - win.innerHeight; + // Maximize the window. + await changeSizeMode(() => win.maximize()); + checkWindow("after maximize window", win, win.STATE_MAXIMIZED); + win.close(); + + // Open a new window to check persisted sizemode. + win = openWindow(); + await SimpleTest.promiseFocus(win); + checkWindow("when reopen to maximized", win, win.STATE_MAXIMIZED); + // Restore the window. + if (win.windowState == win.STATE_MAXIMIZED) { + await changeSizeMode(() => win.restore()); + } + checkWindow("after restore window", win, win.STATE_NORMAL, WIDTH, HEIGHT); + win.close(); + + // Open a new window again to check persisted sizemode. + win = openWindow(); + await SimpleTest.promiseFocus(win); + checkWindow("when reopen to normal", win, win.STATE_NORMAL, WIDTH, HEIGHT); + // And maximize the window again for next test. + await changeSizeMode(() => win.maximize()); + win.close(); + + // Open a new window again with centerscreen which shouldn't revert + // the persisted sizemode. + win = openWindow("centerscreen"); + await SimpleTest.promiseFocus(win); + checkWindow("when open with centerscreen", win, win.STATE_MAXIMIZED); + win.close(); + + // Linux doesn't seem to persist sizemode across opening window + // with specified size, so mark it expected fail for now. + const isLinux = navigator.platform.includes("Linux"); + let checkWindowMayFail = isLinux ? todoCheckWindow : checkWindow; + + // Open a new window with size specified. + win = openWindow("width=400,height=400"); + await SimpleTest.promiseFocus(win); + checkWindow("when reopen with size", win, win.STATE_NORMAL, 400, 400); + await waitForSizeModePersisted(); + win.close(); + + // Open a new window without size specified. + // The window opened before should not change persisted sizemode. + win = openWindow(); + await SimpleTest.promiseFocus(win); + checkWindowMayFail("when reopen without size", win, win.STATE_MAXIMIZED); + win.close(); + + // Open a new window with sizing synchronously. + win = openWindow(); + win.resizeTo(500 + widthDiff, 500 + heightDiff); + await SimpleTest.promiseFocus(win); + checkWindow("when sized synchronously", win, win.STATE_NORMAL, 500, 500); + await waitForSizeModePersisted(); + win.close(); + + // Open a new window without any sizing. + // The window opened before should not change persisted sizemode. + win = openWindow(); + await SimpleTest.promiseFocus(win); + checkWindowMayFail("when reopen without sizing", win, win.STATE_MAXIMIZED); + win.close(); + + // Clean up the XUL store for the given window. + Services.xulStore.removeDocument(chrome_url); + + SimpleTest.finish(); +} diff --git a/toolkit/content/tests/chrome/findbar_entireword_window.xhtml b/toolkit/content/tests/chrome/findbar_entireword_window.xhtml new file mode 100644 index 0000000000..1cb85cffca --- /dev/null +++ b/toolkit/content/tests/chrome/findbar_entireword_window.xhtml @@ -0,0 +1,274 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"?> + +<window id="FindbarEntireWordTest" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="onLoad();" + title="findbar test - entire words only"> + + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript"><![CDATA[ + const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" + ); + + var gFindBar = null; + var gBrowser; + + var SimpleTest = window.arguments[0].SimpleTest; + var SpecialPowers = window.arguments[0].SpecialPowers; + var ok = window.arguments[0].ok; + var is = window.arguments[0].is; + var isnot = window.arguments[0].isnot; + var info = window.arguments[0].info; + SimpleTest.requestLongerTimeout(2); + + const kBaseURL = "chrome://mochitests/content/chrome/toolkit/content/tests/chrome"; + const kTests = { + latin1: { + testSimpleEntireWord: { + "and": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'and' should've been found"); + is(results.matches.total, 6, "should've found 6 matches"); + }, + "an": results => { + is(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'an' shouldn't have been found"); + is(results.matches.total, 0, "should've found 0 matches"); + }, + "darkness": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'darkness' should've been found"); + is(results.matches.total, 3, "should've found 3 matches"); + }, + "mammon": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'mammon' should've been found"); + is(results.matches.total, 1, "should've found 1 match"); + } + }, + testCaseSensitive: { + "And": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'And' should've been found"); + is(results.matches.total, 1, "should've found 1 match"); + }, + "and": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'and' should've been found"); + is(results.matches.total, 5, "should've found 5 matches"); + }, + "Mammon": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'mammon' should've been found"); + is(results.matches.total, 1, "should've found 1 match"); + } + }, + testWordBreakChars: { + "a": results => { + // 'a' is a common charactar, but there should only be one occurrence + // separated by word boundaries. + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'a' should've been found"); + is(results.matches.total, 1, "should've found 1 match"); + }, + "quarrelled": results => { + // 'quarrelled' is denoted as a word by a period char. + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'quarrelled' should've been found"); + is(results.matches.total, 1, "should've found 1 match"); + } + }, + testQuickfind: { + "and": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'and' should've been found"); + is(results.matches.total, 6, "should've found 6 matches"); + }, + "an": results => { + is(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'an' shouldn't have been found"); + is(results.matches.total, 0, "should've found 0 matches"); + }, + "darkness": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'darkness' should've been found"); + is(results.matches.total, 3, "should've found 3 matches"); + }, + "mammon": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'mammon' should've been found"); + is(results.matches.total, 1, "should've found 1 match"); + } + } + } + }; + + function onLoad() { + (async function() { + await SpecialPowers.pushPrefEnv( + { set: [["findbar.entireword", true]] }); + + gFindBar = document.getElementById("FindToolbar"); + for (let browserId of ["content", "content-remote"]) { + // XXXmikedeboer: when multiple test samples are available, make this + // a nested loop that iterates over them. For now, only + // latin is available. + await startTestWithBrowser("latin1", browserId); + } + })().then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + async function startTestWithBrowser(testName, browserId) { + info("Starting test with browser '" + browserId + "'"); + gBrowser = document.getElementById(browserId); + + let promise = BrowserTestUtils.browserLoaded(gBrowser); + BrowserTestUtils.startLoadingURIString(gBrowser, kBaseURL + "/sample_entireword_" + testName + ".html"); + await promise; + gFindBar.browser = gBrowser; + await onDocumentLoaded(testName); + } + + async function onDocumentLoaded(testName) { + let suite = kTests[testName]; + await testSimpleEntireWord(suite.testSimpleEntireWord); + await testCaseSensitive(suite.testCaseSensitive); + await testWordBreakChars(suite.testWordBreakChars); + await testQuickfind(suite.testQuickfind); + } + + var enterStringIntoFindField = async function(str, waitForResult = true) { + for (let promise, i = 0; i < str.length; i++) { + if (waitForResult) { + promise = promiseFindResult(); + } + let event = new KeyboardEvent("keypress", { + bubbles: true, + cancelable: true, + view: null, + keyCode: 0, + charCode: str.charCodeAt(i), + }); + gFindBar._findField.dispatchEvent(event); + if (waitForResult) { + await promise; + } + } + }; + + function openFindbar() { + document.getElementById("cmd_find").doCommand(); + return gFindBar._startFindDeferred && gFindBar._startFindDeferred.promise; + } + + function promiseFindResult(searchString) { + return new Promise(resolve => { + let data = {}; + let listener = { + onFindResult: res => { + if (searchString && res.searchString != searchString) + return; + + gFindBar.browser.finder.removeResultListener(listener); + data.find = res; + if (res.result == Ci.nsITypeAheadFind.FIND_NOTFOUND) { + data.matches = { total: 0, current: 0 }; + resolve(data); + return; + } + listener = { + onMatchesCountResult: res => { + if (searchString && res.searchString != searchString) + return; + + gFindBar.browser.finder.removeResultListener(listener); + data.matches = res; + resolve(data); + } + }; + gFindBar.browser.finder.addResultListener(listener); + } + }; + + gFindBar.browser.finder.addResultListener(listener); + }); + } + + async function testIterator(tests) { + for (let searchString of Object.getOwnPropertyNames(tests)) { + gFindBar.clear(); + + let promise = promiseFindResult(searchString); + + await enterStringIntoFindField(searchString, false); + + let result = await promise; + tests[searchString](result); + } + } + + async function testSimpleEntireWord(tests) { + await openFindbar(); + ok(!gFindBar.hidden, "testSimpleEntireWord: findbar should be open"); + + await testIterator(tests); + + gFindBar.close(); + } + + async function testCaseSensitive(tests) { + await openFindbar(); + ok(!gFindBar.hidden, "testCaseSensitive: findbar should be open"); + + let matchCaseCheckbox = gFindBar.getElement("find-case-sensitive"); + if (!matchCaseCheckbox.hidden && !matchCaseCheckbox.checked) + matchCaseCheckbox.click(); + + await testIterator(tests); + + if (!matchCaseCheckbox.hidden) + matchCaseCheckbox.click(); + gFindBar.close(); + } + + async function testWordBreakChars(tests) { + await openFindbar(); + ok(!gFindBar.hidden, "testWordBreakChars: findbar should be open"); + + await testIterator(tests); + + gFindBar.close(); + } + + async function testQuickfind(tests) { + await SpecialPowers.spawn(gBrowser, [], async function() { + let event = new content.window.KeyboardEvent("keypress", { + bubbles: true, + cancelable: true, + view: null, + keyCode: 0, + charCode: "/".charCodeAt(0), + }); + content.document.documentElement.dispatchEvent(event); + }); + + ok(!gFindBar.hidden, "testQuickfind: failed to open findbar"); + ok(document.commandDispatcher.focusedElement == gFindBar._findField, + "testQuickfind: find field is not focused"); + ok(!gFindBar.getElement("entire-word-status").hidden, + "testQuickfind: entire word mode status text should be visible"); + + await testIterator(tests); + + gFindBar.close(); + } + ]]></script> + + <commandset> + <command id="cmd_find" oncommand="document.getElementById('FindToolbar').onFindCommand();"/> + </commandset> + <browser type="content" primary="true" flex="1" id="content" messagemanagergroup="test" src="about:blank"/> + <browser type="content" primary="true" flex="1" id="content-remote" remote="true" messagemanagergroup="test" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/findbar_events_window.xhtml b/toolkit/content/tests/chrome/findbar_events_window.xhtml new file mode 100644 index 0000000000..bc02ef6bcc --- /dev/null +++ b/toolkit/content/tests/chrome/findbar_events_window.xhtml @@ -0,0 +1,195 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window id="FindbarTest" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="SimpleTest.executeSoon(startTest);" + title="findbar events test"> + + <script type="application/javascript"><![CDATA[ + const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" + ); + + var gFindBar = null; + var gBrowser; + const kTimeout = 5000; // 5 seconds. + + var SimpleTest = window.arguments[0].SimpleTest; + var ok = window.arguments[0].ok; + var is = window.arguments[0].is; + var info = window.arguments[0].info; + SimpleTest.requestLongerTimeout(2); + + function startTest() { + (async function() { + gFindBar = document.getElementById("FindToolbar"); + for (let browserId of ["content", "content-remote"]) { + await startTestWithBrowser(browserId); + } + })().then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + async function startTestWithBrowser(browserId) { + info("Starting test with browser '" + browserId + "'"); + gBrowser = document.getElementById(browserId); + const url = "data:text/html,hello there"; + let promise = BrowserTestUtils.browserLoaded(gBrowser, false, url); + BrowserTestUtils.startLoadingURIString(gBrowser, url); + await promise; + gFindBar.browser = gBrowser; + await onDocumentLoaded(); + } + + async function onDocumentLoaded() { + gFindBar.open(); + gFindBar.onFindCommand(); + + await testFind(); + await testFindAgain(); + await testCaseSensitivity(); + await testDiacriticMatching(); + await testHighlight(); + } + + function checkSelection() { + return new Promise(resolve => { + SimpleTest.executeSoon(() => { + SpecialPowers.spawn(gBrowser, [], async function() { + let selected = content.getSelection(); + Assert.equal(String(selected), "", "No text is selected"); + + let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + let selection = controller.getSelection(controller.SELECTION_FIND); + Assert.equal(selection.rangeCount, 0, "No text is highlighted"); + }).then(resolve); + }); + }); + } + + function once(node, eventName, preventDefault = true) { + return new Promise((resolve, reject) => { + let timeout = window.setTimeout(() => { + reject("Event wasn't fired within " + kTimeout + "ms for event '" + + eventName + "'."); + }, kTimeout); + + node.addEventListener(eventName, function clb(e) { + window.clearTimeout(timeout); + node.removeEventListener(eventName, clb); + if (preventDefault) + e.preventDefault(); + resolve(e); + }); + }); + } + + async function testFind() { + info("Testing normal find."); + let query = "t"; + let promise = once(gFindBar, "find"); + + // Put some text in the find box. + let event = new KeyboardEvent("keypress", { + bubbles: true, + cancelable: true, + view: null, + keyCode: 0, + charCode: query.charCodeAt(0), + }); + gFindBar._findField.dispatchEvent(event); + + let e = await promise; + ok(e.detail.query === query, "find event query should match '" + query + "'"); + // Since we're preventing the default make sure nothing was selected. + await checkSelection(); + } + + async function testFindAgain() { + info("Testing repeating normal find."); + let promise = once(gFindBar, "findagain"); + + gFindBar.onFindAgainCommand(); + + await promise; + // Since we're preventing the default make sure nothing was selected. + await checkSelection(); + } + + async function testCaseSensitivity() { + info("Testing normal case sensitivity."); + let promise = once(gFindBar, "findcasesensitivitychange", false); + + let matchCaseCheckbox = gFindBar.getElement("find-case-sensitive"); + matchCaseCheckbox.click(); + + let e = await promise; + ok(e.detail.caseSensitive, "find should be case sensitive"); + + // Toggle it back to the original setting. + matchCaseCheckbox.click(); + + // Changing case sensitivity does the search so clear the selected text + // before the next test. + await SpecialPowers.spawn(gBrowser, [], () => content.getSelection().removeAllRanges()); + } + + async function testDiacriticMatching() { + info("Testing normal diacritic matching."); + let promise = once(gFindBar, "finddiacriticmatchingchange", false); + + let matchDiacriticsCheckbox = gFindBar.getElement("find-match-diacritics"); + matchDiacriticsCheckbox.click(); + + let e = await promise; + ok(e.detail.matchDiacritics, "find should match diacritics"); + + // Toggle it back to the original setting. + matchDiacriticsCheckbox.click(); + + // Changing diacritic matching does the search so clear the selected text + // before the next test. + await SpecialPowers.spawn(gBrowser, [], () => content.getSelection().removeAllRanges()); + } + + async function testHighlight() { + info("Testing find with highlight all."); + // Update the find state so the highlight button is clickable. + gFindBar.updateControlState(Ci.nsITypeAheadFind.FIND_FOUND, false); + + let promise = once(gFindBar, "findhighlightallchange"); + + let highlightButton = gFindBar.getElement("highlight"); + if (!highlightButton.checked) + highlightButton.click(); + + let e = await promise; + ok(e.detail.highlightAll, "find event should have highlight all set"); + // Since we're preventing the default make sure nothing was highlighted. + await checkSelection(); + + // Toggle it back to the original setting. + if (highlightButton.checked) + highlightButton.click(); + } + ]]></script> + + <browser type="content" primary="true" flex="1" id="content" messagemanagergroup="test" src="about:blank"/> + <browser type="content" primary="true" flex="1" id="content-remote" remote="true" messagemanagergroup="test" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/findbar_window.xhtml b/toolkit/content/tests/chrome/findbar_window.xhtml new file mode 100644 index 0000000000..76bf1cae13 --- /dev/null +++ b/toolkit/content/tests/chrome/findbar_window.xhtml @@ -0,0 +1,792 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window id="FindbarTest" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="onLoad();" + title="findbar test"> + + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript"><![CDATA[ + const {AppConstants} = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" + ); + const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" + ); + + const SAMPLE_URL = "http://www.mozilla.org/"; + const SAMPLE_TEXT = "Some text in a text field."; + const SEARCH_TEXT = "Text Test (δοκιμή)"; + const NOT_FOUND_TEXT = "This text is not on the page." + const ITERATOR_TIMEOUT = Services.prefs.getIntPref("findbar.iteratorTimeout"); + + var gFindBar = null; + var gBrowser; + + var gHasFindClipboard = Services.clipboard.isClipboardTypeSupported(Services.clipboard.kFindClipboard); + + var gStatusText; + var gXULBrowserWindow = { + QueryInterface: ChromeUtils.generateQI(["nsIXULBrowserWindow"]), + + setOverLink(aStatusText) { + gStatusText = aStatusText; + }, + + onBeforeLinkTraversal() { } + }; + + var SimpleTest = window.arguments[0].SimpleTest; + var ok = window.arguments[0].ok; + var is = window.arguments[0].is; + var info = window.arguments[0].info; + SimpleTest.requestLongerTimeout(2); + + function onLoad() { + (async function() { + window.docShell + .treeOwner + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIAppWindow) + .XULBrowserWindow = gXULBrowserWindow; + + gFindBar = document.getElementById("FindToolbar"); + for (let browserId of ["content", "content-remote"]) { + await startTestWithBrowser(browserId); + } + })().then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + async function startTestWithBrowser(browserId) { + info("Starting test with browser '" + browserId + "'"); + gBrowser = document.getElementById(browserId); + + // Tests delays the loading of a document for one second. + await new Promise(resolve => setTimeout(resolve, 1000)); + + let promise = BrowserTestUtils.browserLoaded(gBrowser); + BrowserTestUtils.startLoadingURIString(gBrowser, "data:text/html;charset=utf-8,<h2 id='h2'>" + SEARCH_TEXT + + "</h2><h2><a href='" + SAMPLE_URL + "'>Link Test</a></h2><input id='text' type='text' value='" + + SAMPLE_TEXT + "'></input><input id='button' type='button'></input><img id='img' width='50' height='50'/>", + { triggeringPrincipal: window.document.nodePrincipal }); + await promise; + gFindBar.browser = gBrowser; + await onDocumentLoaded(); + } + + async function onDocumentLoaded() { + await testNormalFind(); + gFindBar.close(); + ok(gFindBar.hidden, "Failed to close findbar after testNormalFind"); + await openFindbar(); + await testNormalFindWithComposition(); + gFindBar.close(); + ok(gFindBar.hidden, "findbar should be hidden after testNormalFindWithComposition"); + await openFindbar(); + await testAutoCaseSensitivityUI(); + await testQuickFindText(); + gFindBar.close(); + ok(gFindBar.hidden, "Failed to close findbar after testQuickFindText"); + // TODO: `testFindWithHighlight` tests fastFind integrity, which can not + // be accessed with RemoteFinder. We're skipping it for now. + if (gFindBar._browser.finder._fastFind) { + await testFindWithHighlight(); + gFindBar.close(); + ok(gFindBar.hidden, "Failed to close findbar after testFindWithHighlight"); + } + await testFindbarSelection(); + ok(gFindBar.hidden, "Failed to close findbar after testFindbarSelection"); + // TODO: I don't know how to drop a content element on a chrome input. + if (!gBrowser.hasAttribute("remote")) + testDrop(); + await testQuickFindLink(); + if (gHasFindClipboard) { + await testStatusText(); + } + + if (!AppConstants.DEBUG) { + await testFindCountUI(); + gFindBar.close(); + ok(gFindBar.hidden, "Failed to close findbar after testFindCountUI"); + await testFindCountUI(true); + gFindBar.close(); + ok(gFindBar.hidden, "Failed to close findbar after testFindCountUI - linksOnly"); + } + + await openFindbar(); + await testFindAfterCaseChanged(); + gFindBar.close(); + await openFindbar(); + await testFailedStringReset(); + gFindBar.close(); + await testQuickFindClose(); + // TODO: This doesn't seem to work when the findbar is connected to a + // remote browser element. + if (!gBrowser.hasAttribute("remote")) + await testFindAgainNotFound(); + await testToggleEntireWord(); + } + + async function testFindbarSelection() { + function checkFindbarState(aTestName, aExpSelection) { + ok(!gFindBar.hidden, "testFindbarSelection: failed to open findbar: " + aTestName); + ok(document.commandDispatcher.focusedElement == gFindBar._findField, + "testFindbarSelection: find field is not focused: " + aTestName); + if (!gHasFindClipboard) { + ok(gFindBar._findField.value == aExpSelection, + "Incorrect selection in testFindbarSelection: " + aTestName + + ". Selection: " + gFindBar._findField.value); + } + + // Clear the value, close the findbar. + gFindBar._findField.value = ""; + gFindBar.close(); + } + + // Test normal selected text. + await SpecialPowers.spawn(gBrowser, [], async function() { + let document = content.document; + let cH2 = document.getElementById("h2"); + let cSelection = content.getSelection(); + let cRange = document.createRange(); + cRange.setStart(cH2, 0); + cRange.setEnd(cH2, 1); + cSelection.removeAllRanges(); + cSelection.addRange(cRange); + }); + await openFindbar(); + checkFindbarState("plain text", SEARCH_TEXT); + + // Test editable element with selection. + await SpecialPowers.spawn(gBrowser, [], async function() { + let textInput = content.document.getElementById("text"); + textInput.focus(); + textInput.select(); + }); + await openFindbar(); + checkFindbarState("text input", SAMPLE_TEXT); + + // Test non-editable input element (type="button"). + await SpecialPowers.spawn(gBrowser, [], async function() { + content.document.getElementById("button").focus(); + }); + await openFindbar(); + checkFindbarState("button", ""); + } + + function testDrop() { + gFindBar.open(); + // use an dummy image to start the drag so it doesn't get interrupted by a selection + var img = gBrowser.contentDocument.getElementById("img"); + synthesizeDrop(img, gFindBar._findField, [[ {type: "text/plain", data: "Rabbits" } ]], "copy", window); + is(gFindBar._findField.value, "Rabbits", "drop on findbar"); + gFindBar.close(); + } + + function testQuickFindClose() { + return new Promise(resolve => { + var _isClosedCallback = function() { + ok(gFindBar.hidden, + "_isClosedCallback: Failed to auto-close quick find bar after " + + gFindBar._quickFindTimeoutLength + "ms"); + resolve(); + }; + setTimeout(_isClosedCallback, gFindBar._quickFindTimeoutLength + 100); + }); + } + + function testStatusText() { + return new Promise(resolve => { + var _delayedCheckStatusText = function() { + ok(gStatusText == SAMPLE_URL, "testStatusText: Failed to set status text of found link"); + resolve(); + }; + setTimeout(_delayedCheckStatusText, 100); + }); + } + + function promiseFindResult(expectedString) { + return new Promise(resolve => { + let listener = { + onFindResult(result) { + if (expectedString && result.searchString != expectedString) { + return; + } + + gFindBar.browser.finder.removeResultListener(listener); + resolve(result); + }, + }; + gFindBar.browser.finder.addResultListener(listener); + }); + } + + function promiseMatchesCountResult(expectedString) { + return new Promise(resolve => { + let listener = { + onMatchesCountResult(result) { + if (expectedString && result.searchString != expectedString) + return; + + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + }, + }; + gFindBar.browser.finder.addResultListener(listener); + // Make sure we resolve _at least_ after five times the find iterator timeout. + setTimeout(resolve, (ITERATOR_TIMEOUT * 5) + 20); + }); + } + + function promiseHighlightFinished(expectedString) { + return new Promise(resolve => { + let listener = { + onHighlightFinished(result) { + if (expectedString && result.searchString != expectedString) + return; + + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + } + }; + gFindBar.browser.finder.addResultListener(listener); + }); + } + + var enterStringIntoFindField = async function(str, waitForResult = true) { + for (let promise, i = 0; i < str.length; i++) { + if (waitForResult) { + promise = promiseFindResult(); + } + let event = new KeyboardEvent("keypress", { + bubbles: true, + cancelable: true, + view: null, + keyCode: 0, + charCode: str.charCodeAt(i), + }); + gFindBar._findField.dispatchEvent(event); + if (waitForResult) { + await promise; + } + } + }; + + function promiseExpectRangeCount(rangeCount) { + return SpecialPowers.spawn(gBrowser, [{ rangeCount }], async function(args) { + let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + let sel = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND); + Assert.equal(sel.rangeCount, args.rangeCount, + "Expected the correct amount of ranges inside the Find selection"); + }); + } + + // also test match-case + async function testNormalFind() { + document.getElementById("cmd_find").doCommand(); + + ok(!gFindBar.hidden, "testNormalFind: failed to open findbar"); + ok(document.commandDispatcher.focusedElement == gFindBar._findField, + "testNormalFind: find field is not focused"); + + let promise; + let matchCaseCheckbox = gFindBar.getElement("find-case-sensitive"); + if (!matchCaseCheckbox.hidden && matchCaseCheckbox.checked) { + promise = promiseFindResult(); + matchCaseCheckbox.click(); + await promise; + } + + var searchStr = "text tes"; + await enterStringIntoFindField(searchStr); + + let sel = await SpecialPowers.spawn(gBrowser, [{ searchStr }], async function(args) { + let sel = content.getSelection().toString(); + Assert.equal(sel.toLowerCase(), args.searchStr, + "testNormalFind: failed to find '" + args.searchStr + "'"); + return sel; + }); + testClipboardSearchString(sel); + + if (!matchCaseCheckbox.hidden) { + promise = promiseFindResult(); + matchCaseCheckbox.click(); + await promise; + enterStringIntoFindField("t"); + await SpecialPowers.spawn(gBrowser, [{ searchStr }], async function(args) { + Assert.notEqual(content.getSelection().toString(), args.searchStr, + "testNormalFind: Case-sensitivy is broken '" + args.searchStr + "'"); + }); + promise = promiseFindResult(); + matchCaseCheckbox.click(); + await promise; + } + } + + function openFindbar() { + document.getElementById("cmd_find").doCommand(); + return gFindBar._startFindDeferred && gFindBar._startFindDeferred.promise; + } + + async function testNormalFindWithComposition() { + ok(!gFindBar.hidden, "testNormalFindWithComposition: findbar should be open"); + ok(document.commandDispatcher.focusedElement == gFindBar._findField, + "testNormalFindWithComposition: find field should be focused"); + + var matchCaseCheckbox = gFindBar.getElement("find-case-sensitive"); + var clicked = false; + if (!matchCaseCheckbox.hidden & matchCaseCheckbox.checked) { + matchCaseCheckbox.click(); + clicked = true; + } + + gFindBar._findField.focus(); + + var searchStr = "text"; + + synthesizeCompositionChange( + { "composition": + { "string": searchStr, + "clauses": + [ + { "length": searchStr.length, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": searchStr.length, "length": 0 } + }); + + await SpecialPowers.spawn(gBrowser, [{ searchStr }], async function(args) { + Assert.notEqual(content.getSelection().toString().toLowerCase(), args.searchStr, + "testNormalFindWithComposition: text shouldn't be found during composition"); + }); + + synthesizeComposition({ type: "compositioncommitasis" }); + + let sel = await SpecialPowers.spawn(gBrowser, [{ searchStr }], async function(args) { + let sel = content.getSelection().toString(); + Assert.equal(sel.toLowerCase(), args.searchStr, + "testNormalFindWithComposition: text should be found after committing composition"); + return sel; + }); + testClipboardSearchString(sel); + + if (clicked) { + matchCaseCheckbox.click(); + } + } + + async function testAutoCaseSensitivityUI() { + var matchCaseCheckbox = gFindBar.getElement("find-case-sensitive"); + var matchCaseLabel = gFindBar.getElement("match-case-status"); + ok(!matchCaseCheckbox.hidden, "match case box is hidden in manual mode"); + ok(matchCaseLabel.hidden, "match case label is visible in manual mode"); + + await changeCase(2); + + ok(matchCaseCheckbox.hidden, + "match case box is visible in automatic mode"); + ok(!matchCaseLabel.hidden, + "match case label is hidden in automatic mode"); + + await enterStringIntoFindField("a"); + ok(matchCaseLabel.hidden, + "match case label is hidden in automatic mode with lower-case input"); + await enterStringIntoFindField("A"); + ok(!matchCaseLabel.hidden, + "match case label is visible in automatic mode with upper-case input"); + + // bug 365551 + gFindBar.onFindAgainCommand(); + ok(matchCaseCheckbox.hidden && !matchCaseLabel.hidden, + "bug 365551: case sensitivity UI is broken after find-again"); + await changeCase(0); + gFindBar.close(); + } + + async function clearFocus() { + document.commandDispatcher.focusedElement = null; + document.commandDispatcher.focusedWindow = null; + await SpecialPowers.spawn(gBrowser, [], async function() { + content.focus(); + }); + } + + async function testQuickFindLink() { + await clearFocus(); + + await SpecialPowers.spawn(gBrowser, [], async function() { + let event = new content.window.KeyboardEvent("keypress", { + bubbles: true, + cancelable: true, + view: null, + keyCode: 0, + charCode: "'".charCodeAt(0), + }); + content.document.documentElement.dispatchEvent(event); + }); + + ok(!gFindBar.hidden, "testQuickFindLink: failed to open findbar"); + ok(document.commandDispatcher.focusedElement == gFindBar._findField, + "testQuickFindLink: find field is not focused"); + + var searchStr = "Link Test"; + await enterStringIntoFindField(searchStr); + await SpecialPowers.spawn(gBrowser, [{ searchStr }], async function(args) { + Assert.equal(content.getSelection().toString(), args.searchStr, + "testQuickFindLink: failed to find sample link"); + }); + testClipboardSearchString(searchStr); + } + + // See bug 963925 for more details on this test. + async function testFindWithHighlight() { + gFindBar._findField.value = ""; + + // For this test, we want to closely control the selection. The easiest + // way to do so is to replace the implementation of + // Finder.getInitialSelection with a no-op and call the findbar's callback + // (onCurrentSelection(..., true)) ourselves with our hand-picked + // selection. + let oldGetInitialSelection = gFindBar.browser.finder.getInitialSelection; + let searchStr; + gFindBar.browser.finder.getInitialSelection = function(){}; + + let findCommand = document.getElementById("cmd_find"); + findCommand.doCommand(); + + gFindBar.onCurrentSelection("", true); + + searchStr = "e"; + await enterStringIntoFindField(searchStr); + + let a = gFindBar._findField.value; + let b = gFindBar._browser.finder._fastFind.searchString; + let c = gFindBar._browser.finder.searchString; + ok(a == b && b == c, "testFindWithHighlight 1: " + a + ", " + b + ", " + c + "."); + + searchStr = "t"; + findCommand.doCommand(); + + gFindBar.onCurrentSelection(searchStr, true); + gFindBar.browser.finder.getInitialSelection = oldGetInitialSelection; + + a = gFindBar._findField.value; + b = gFindBar._browser.finder._fastFind.searchString; + c = gFindBar._browser.finder.searchString; + ok(a == searchStr && b == c, "testFindWithHighlight 2: " + searchStr + + ", " + a + ", " + b + ", " + c + "."); + + let highlightButton = gFindBar.getElement("highlight"); + highlightButton.click(); + ok(highlightButton.checked, "testFindWithHighlight 3: Highlight All should be checked."); + + a = gFindBar._findField.value; + b = gFindBar._browser.finder._fastFind.searchString; + c = gFindBar._browser.finder.searchString; + ok(a == searchStr && b == c, "testFindWithHighlight 4: " + a + ", " + b + ", " + c + "."); + + gFindBar.onFindAgainCommand(); + a = gFindBar._findField.value; + b = gFindBar._browser.finder._fastFind.searchString; + c = gFindBar._browser.finder.searchString; + ok(a == b && b == c, "testFindWithHighlight 5: " + a + ", " + b + ", " + c + "."); + + highlightButton.click(); + ok(!highlightButton.checked, "testFindWithHighlight: Highlight All should be unchecked."); + + // Regression test for bug 1316515. + searchStr = "e"; + gFindBar.clear(); + await enterStringIntoFindField(searchStr); + await promiseExpectRangeCount(0); + + highlightButton.click(); + ok(highlightButton.checked, "testFindWithHighlight: Highlight All should be checked."); + await promiseHighlightFinished(searchStr); + await promiseExpectRangeCount(3); + + synthesizeKey("KEY_Backspace"); + await promiseExpectRangeCount(0); + + // Regression test for bug 1316513. + highlightButton.click(); + ok(!highlightButton.checked, "testFindWithHighlight - 1316513: Highlight All should be unchecked."); + await enterStringIntoFindField(searchStr); + + highlightButton.click(); + ok(highlightButton.checked, "testFindWithHighlight - 1316513: Highlight All should be checked."); + await promiseHighlightFinished(searchStr); + await promiseExpectRangeCount(3); + + let promise = BrowserTestUtils.browserLoaded(gBrowser); + gBrowser.reload(); + await promise; + + ok(highlightButton.checked, "testFindWithHighlight - 1316513: Highlight All " + + "should still be checked after a reload."); + synthesizeKey("KEY_Enter"); + await promiseHighlightFinished(searchStr); + await promiseExpectRangeCount(3); + + // Uncheck at test end to not interfere with other test functions that are + // run after this one. + highlightButton.click(); + } + + async function testQuickFindText() { + await clearFocus(); + + await SpecialPowers.spawn(gBrowser, [], async function() { + let event = new content.window.KeyboardEvent("keypress", { + bubbles: true, + cancelable: true, + view: null, + keyCode: 0, + charCode: "/".charCodeAt(0), + }); + content.document.documentElement.dispatchEvent(event); + }); + + ok(!gFindBar.hidden, "testQuickFindText: failed to open findbar"); + ok(document.commandDispatcher.focusedElement == gFindBar._findField, + "testQuickFindText: find field is not focused"); + + await enterStringIntoFindField(SEARCH_TEXT); + await SpecialPowers.spawn(gBrowser, [{ SEARCH_TEXT }], async function(args) { + Assert.equal(content.getSelection().toString(), args.SEARCH_TEXT, + "testQuickFindText: failed to find '" + args.SEARCH_TEXT + "'"); + }); + testClipboardSearchString(SEARCH_TEXT); + } + + async function testFindCountUI(linksOnly = false) { + await clearFocus(); + + if (linksOnly) { + await SpecialPowers.spawn(gBrowser, [], async function() { + let event = new content.window.KeyboardEvent("keypress", { + bubbles: true, + cancelable: true, + view: null, + keyCode: 0, + charCode: "'".charCodeAt(0), + }); + content.document.documentElement.dispatchEvent(event); + }); + } else { + document.getElementById("cmd_find").doCommand(); + } + + ok(!gFindBar.hidden, "testFindCountUI: failed to open findbar"); + ok(document.commandDispatcher.focusedElement == gFindBar._findField, + "testFindCountUI: find field is not focused"); + + let promise; + let matchCase = gFindBar.getElement("find-case-sensitive"); + if (matchCase.checked) { + promise = promiseFindResult(); + matchCase.click(); + await new Promise(resolve => setTimeout(resolve, ITERATOR_TIMEOUT + 20)); + await promise; + } + + let foundMatches = gFindBar._foundMatches; + let tests = [{ + text: "t", + current: linksOnly ? 1 : 5, + total: linksOnly ? 2 : 10, + }, { + text: "te", + current: linksOnly ? 1 : 3, + total: linksOnly ? 1 : 5, + }, { + text: "tes", + current: 1, + total: linksOnly ? 1 : 2, + }, { + text: "texxx", + current: 0, + total: 0 + }]; + + function assertMatches(aTest) { + const matches = JSON.parse(foundMatches.dataset.l10nArgs); + is(matches.current, aTest.current, + `${linksOnly ? "[Links-only] " : ""}Currently highlighted match should be at ${aTest.current} for '${aTest.text}'`); + is(matches.total, aTest.total, + `${linksOnly ? "[Links-only] " : ""}Total amount of matches should be ${aTest.total} for '${aTest.text}'`); + } + + for (let test of tests) { + gFindBar._findField.select(); + gFindBar._findField.focus(); + + let timeout = ITERATOR_TIMEOUT; + if (test.text.length == 1) + timeout *= 4; + else if (test.text.length == 2) + timeout *= 2; + timeout += 20; + await new Promise(resolve => setTimeout(resolve, timeout)); + await enterStringIntoFindField(test.text, false); + await promiseMatchesCountResult(test.text); + if (!test.total) { + ok(!foundMatches.dataset.l10nId, "No message should be shown when 0 matches are expected"); + } else { + assertMatches(test); + for (let i = 1; i < test.total; i++) { + await new Promise(resolve => setTimeout(resolve, timeout)); + gFindBar.onFindAgainCommand(); + await promiseMatchesCountResult(test.text); + // test.current + 1, test.current + 2, ..., test.total, 1, ..., test.current + let current = (test.current + i - 1) % test.total + 1; + assertMatches({ + text: test.text, + current, + total: test.total + }); + } + } + } + } + + // See bug 1051187. + async function testFindAfterCaseChanged() { + // Search to set focus on "Text Test" so that searching for "t" selects first + // (upper case!) "T". + await enterStringIntoFindField(SEARCH_TEXT); + gFindBar.clear(); + + // Case-insensitive should already be the current value. + Services.prefs.setIntPref("accessibility.typeaheadfind.casesensitive", 0); + + await enterStringIntoFindField("t"); + await SpecialPowers.spawn(gBrowser, [], async function() { + Assert.equal(content.getSelection().toString(), "T", "First T should be selected."); + }); + + await changeCase(1); + await SpecialPowers.spawn(gBrowser, [], async function() { + Assert.equal(content.getSelection().toString(), "t", "First t should be selected."); + }); + } + + // Make sure that _findFailedString is cleared: + // 1. Do a search that fails with case sensitivity but matches with no case sensitivity. + // 2. Uncheck case sensitivity button to match the string. + async function testFailedStringReset() { + Services.prefs.setIntPref("accessibility.typeaheadfind.casesensitive", 1); + + let promise = promiseFindResult(gBrowser.hasAttribute("remote") ? SEARCH_TEXT.toUpperCase() : ""); + await enterStringIntoFindField(SEARCH_TEXT.toUpperCase(), false); + await promise; + await SpecialPowers.spawn(gBrowser, [], async function() { + Assert.equal(content.getSelection().toString(), "", "Not found."); + }); + + await changeCase(0); + await SpecialPowers.spawn(gBrowser, [{ SEARCH_TEXT }], async function(args) { + Assert.equal(content.getSelection().toString(), args.SEARCH_TEXT, + "Search text should be selected."); + }); + } + + function testClipboardSearchString(aExpected) { + if (!gHasFindClipboard) + return; + + if (!aExpected) + aExpected = ""; + var searchStr = gFindBar.browser.finder.clipboardSearchString; + ok(searchStr.toLowerCase() == aExpected.toLowerCase(), + "testClipboardSearchString: search string not set to '" + aExpected + + "', instead found '" + searchStr + "'"); + } + + // See bug 967982. + async function testFindAgainNotFound() { + await openFindbar(); + await enterStringIntoFindField(NOT_FOUND_TEXT, false); + gFindBar.close(); + ok(gFindBar.hidden, "The findbar is closed."); + let promise = promiseFindResult(); + gFindBar.onFindAgainCommand(); + await promise; + ok(!gFindBar.hidden, "Unsuccessful Find Again opens the find bar."); + + await enterStringIntoFindField(SEARCH_TEXT); + gFindBar.close(); + ok(gFindBar.hidden, "The findbar is closed."); + promise = promiseFindResult(); + gFindBar.onFindAgainCommand(); + await promise; + ok(gFindBar.hidden, "Successful Find Again leaves the find bar closed."); + } + + async function testToggleDiacriticMatching() { + await openFindbar(); + let promise = promiseFindResult(); + await enterStringIntoFindField("δοκιμη", false); + let result = await promise; + is(result.result, Ci.nsITypeAheadFind.FIND_FOUND, "Text should be found"); + + await new Promise(resolve => setTimeout(resolve, ITERATOR_TIMEOUT + 20)); + promise = promiseFindResult(); + let check = gFindBar.getElement("find-match-diacritics"); + check.click(); + result = await promise; + is(result.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "Text should NOT be found"); + + check.click(); + gFindBar.close(true); + } + + async function testToggleEntireWord() { + await openFindbar(); + let promise = promiseFindResult(); + await enterStringIntoFindField("Tex", false); + let result = await promise; + is(result.result, Ci.nsITypeAheadFind.FIND_FOUND, "Text should be found"); + + await new Promise(resolve => setTimeout(resolve, ITERATOR_TIMEOUT + 20)); + promise = promiseFindResult(); + let check = gFindBar.getElement("find-entire-word"); + check.click(); + result = await promise; + is(result.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "Text should NOT be found"); + + check.click(); + gFindBar.close(true); + } + + function changeCase(value) { + let promise = gBrowser.hasAttribute("remote") ? promiseFindResult() : Promise.resolve(); + Services.prefs.setIntPref("accessibility.typeaheadfind.casesensitive", value); + return promise; + } + ]]></script> + + <commandset> + <command id="cmd_find" oncommand="document.getElementById('FindToolbar').onFindCommand();"/> + </commandset> + <browser type="content" primary="true" flex="1" id="content" messagemanagergroup="test" src="about:blank"/> + <browser type="content" primary="true" flex="1" id="content-remote" remote="true" messagemanagergroup="test" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/frame_popup_anchor.xhtml b/toolkit/content/tests/chrome/frame_popup_anchor.xhtml new file mode 100644 index 0000000000..934cea6faf --- /dev/null +++ b/toolkit/content/tests/chrome/frame_popup_anchor.xhtml @@ -0,0 +1,84 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<menupopup id="popup" onpopupshowing="if (isSecondTest) popupShowing(event)" onpopupshown="popupShown()" + onpopuphidden="nextTest()"> + <menuitem label="One"/> + <menuitem label="Two"/> +</menupopup> + +<button id="button" label="OK" popup="popup"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +var isSecondTest = false; + +function openPopup() +{ + document.getElementById("popup").openPopup(parent.document.getElementById("outerbutton"), "after_start", 3, 1); +} + +function popupShowing(event) +{ + var buttonrect = document.getElementById("button").getBoundingClientRect(); + parent.arguments[0].SimpleTest.is(event.clientX, buttonrect.left + 6, "popup clientX with mouse"); + parent.arguments[0].SimpleTest.is(event.clientY, buttonrect.top + 6, "popup clientY with mouse"); +} + +function popupShown() +{ + var left, top; + var popup = document.getElementById("popup"); + var popuprect = popup.getBoundingClientRect(); + if (isSecondTest) { + let buttonrect = document.getElementById("button").getBoundingClientRect(); + left = buttonrect.left + 6; + top = buttonrect.top + 6; + } else { + let iframerect = parent.document.getElementById("frame").getBoundingClientRect(); + let buttonrect = parent.document.getElementById("outerbutton").getBoundingClientRect(); + + // The popup should appear anchored on the bottom left edge of the button, however + // the client rectangle is relative to the iframe's document. Thus the coordinates + // are: + // left = iframe's left - anchor button's left - 3 pixel offset passed to openPopup + + // iframe border (17px) + iframe padding (0) + // top = iframe's top - anchor button's bottom - 1 pixel offset passed to openPopup + + // iframe border (0) + iframe padding (3px); + left = -(Math.round(iframerect.left) - Math.round(buttonrect.left) + 14); + top = -(Math.round(iframerect.top) - Math.round(buttonrect.bottom) + 2); + } + + left += parseFloat(getComputedStyle(popup).marginLeft); + top += parseFloat(getComputedStyle(popup).marginTop); + + var testid = isSecondTest ? "with mouse" : "anchored to parent frame"; + parent.arguments[0].SimpleTest.is(Math.round(popuprect.left), left, "popup left " + testid); + parent.arguments[0].SimpleTest.is(Math.round(popuprect.top), top, "popup top " + testid); + + document.getElementById("popup").hidePopup(); +} + +function nextTest() +{ + if (isSecondTest) { + parent.arguments[0].SimpleTest.finish(); + parent.close(); + } + else { + // this second test ensures that the popupshowing coordinates when a popup in + // a frame is opened are correct + isSecondTest = true; + synthesizeMouse(document.getElementById("button"), 6, 6, { }); + } +} + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/frame_subframe_origin_subframe1.xhtml b/toolkit/content/tests/chrome/frame_subframe_origin_subframe1.xhtml new file mode 100644 index 0000000000..f68a06d85d --- /dev/null +++ b/toolkit/content/tests/chrome/frame_subframe_origin_subframe1.xhtml @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<!-- 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/. --> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="frame1" + style="background-color:green;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<spacer height="10px"/> +<iframe + style="margin:10px; min-height:170px; max-width:200px; max-height:200px; border:solid 1px white;" + src="frame_subframe_origin_subframe2.xhtml"></iframe> +<spacer height="3px"/> +<caption id="cap1" style="min-width:200px; max-width:200px; background-color:white;" label=""/> +<script class="testbody" type="application/javascript"> + +// Fire a mouse move event aimed at this window, and check to be +// sure the client coords translate from widget to the dom correctly. + +function runTests() +{ + synthesizeMouse(document.getElementById("frame1"), 3, 4, { type: "mousemove" }); +} + +function mouseMove(e) { + e.stopPropagation(); + var el = document.getElementById("cap1"); + el.label = "client: (" + e.clientX + "," + e.clientY + ")"; + parent.arguments[0].SimpleTest.is(e.clientX, 3, "mouse event clientX on sub frame 1"); + parent.arguments[0].SimpleTest.is(e.clientY, 4, "mouse event clientY on sub frame 1"); + // fire the next test on the sub frame + frames[0].runTests(); +} + +window.addEventListener("mousemove", mouseMove); + +</script> +</window> diff --git a/toolkit/content/tests/chrome/frame_subframe_origin_subframe2.xhtml b/toolkit/content/tests/chrome/frame_subframe_origin_subframe2.xhtml new file mode 100644 index 0000000000..338877ff96 --- /dev/null +++ b/toolkit/content/tests/chrome/frame_subframe_origin_subframe2.xhtml @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<!-- 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/. --> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="frame2" + style="background-color:red;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<spacer height="10px"/> +<caption id="cap2" style="background-color:white;" label=""/> +<script class="testbody" type="application/javascript"> + +// Fire a mouse move event aimed at this window, and check to be +// sure the client coords translate from widget to the dom correctly. + +function runTests() +{ + synthesizeMouse(document.getElementById("frame2"), 6, 5, { type: "mousemove" }); +} + +function mouseMove(e) { + e.stopPropagation(); + var el = document.getElementById("cap2"); + el.label = "client: (" + e.clientX + "," + e.clientY + ")"; + parent.parent.arguments[0].SimpleTest.is(e.clientX, 6, "mouse event clientX on sub frame 2"); + parent.parent.arguments[0].SimpleTest.is(e.clientY, 5, "mouse event clientY on sub frame 2"); + parent.parent.arguments[0].SimpleTest.finish(); + parent.parent.close(); +} + +window.addEventListener("mousemove", mouseMove); + +</script> +</window> diff --git a/toolkit/content/tests/chrome/popup_trigger.js b/toolkit/content/tests/chrome/popup_trigger.js new file mode 100644 index 0000000000..003af044e5 --- /dev/null +++ b/toolkit/content/tests/chrome/popup_trigger.js @@ -0,0 +1,1210 @@ +/* import-globals-from ../widgets/popup_shared.js */ + +var gMenuPopup = null; +var gTrigger = null; +var gIsMenu = false; +var gScreenX = -1, + gScreenY = -1; +var gCachedEvent = null; +var gCachedEvent2 = null; + +function cacheEvent(modifiers) { + var cachedEvent = null; + + var mouseFn = function (event) { + cachedEvent = event; + }; + + window.addEventListener("mousedown", mouseFn); + synthesizeMouse(document.documentElement, 0, 0, modifiers); + window.removeEventListener("mousedown", mouseFn); + + return cachedEvent; +} + +function runTests() { + if (screen.height < 768) { + ok( + false, + "popup tests are likely to fail for screen heights less than 768 pixels" + ); + } + + gMenuPopup = document.getElementById("thepopup"); + gTrigger = document.getElementById("trigger"); + + gIsMenu = gTrigger.hasMenu(); + + // a hacky way to get the screen position of the document. Cache the event + // so that we can use it in calls to openPopup. + gCachedEvent = cacheEvent({ shiftKey: true }); + gScreenX = gCachedEvent.screenX; + gScreenY = gCachedEvent.screenY; + gCachedEvent2 = cacheEvent({ + altKey: true, + ctrlKey: true, + shiftKey: true, + metaKey: true, + }); + + startPopupTests(popupTests); +} + +var popupTests = [ + { + testname: "mouse click on trigger", + events: ["popupshowing thepopup", "popupshown thepopup"], + test() { + // for menus, no trigger will be set. For non-menus using the popup + // attribute, the trigger will be set to the node with the popup attribute + gExpectedTriggerNode = gIsMenu ? "notset" : gTrigger; + synthesizeMouse(gTrigger, 4, 4, {}); + }, + async result(testname) { + gExpectedTriggerNode = null; + // menus are the anchor but non-menus are opened at screen coordinates + is( + gMenuPopup.anchorNode, + gIsMenu ? gTrigger : null, + testname + " anchorNode" + ); + // menus are opened internally, but non-menus have a mouse event which + // triggered them + is( + gMenuPopup.triggerNode, + gIsMenu ? null : gTrigger, + testname + " triggerNode" + ); + + // Popup may have wrong initial size in non e10s mode tests, because + // layout is not yet ready for popup content lazy population on + // popupshowing event. + await new Promise(r => + requestAnimationFrame(() => requestAnimationFrame(r)) + ); + + // this will be used in some tests to ensure the size doesn't change + var popuprect = gMenuPopup.getBoundingClientRect(); + gPopupWidth = Math.round(popuprect.width); + gPopupHeight = Math.round(popuprect.height); + + checkActive(gMenuPopup, "", testname); + checkOpen("trigger", testname); + // if a menu, the popup should be opened underneath the menu in the + // 'after_start' position, otherwise it is opened at the mouse position + if (gIsMenu) { + compareEdge(gTrigger, gMenuPopup, "after_start", 0, 0, testname); + } + }, + }, + { + // check that pressing cursor down while there is no selection + // highlights the first item + testname: "cursor down no selection", + events: ["DOMMenuItemActive item1"], + test() { + synthesizeKey("KEY_ArrowDown"); + }, + result(testname) { + checkActive(gMenuPopup, "item1", testname); + }, + }, + { + // check that pressing cursor up wraps and highlights the last item + testname: "cursor up wrap", + events() { + // No wrapping on menus on Mac + return platformIsMac() + ? [] + : ["DOMMenuItemInactive item1", "DOMMenuItemActive last"]; + }, + test() { + synthesizeKey("KEY_ArrowUp"); + }, + result(testname) { + checkActive(gMenuPopup, platformIsMac() ? "item1" : "last", testname); + }, + }, + { + // check that pressing cursor down wraps and highlights the first item + testname: "cursor down wrap", + condition() { + return !platformIsMac(); + }, + events: ["DOMMenuItemInactive last", "DOMMenuItemActive item1"], + test() { + synthesizeKey("KEY_ArrowDown"); + }, + result(testname) { + checkActive(gMenuPopup, "item1", testname); + }, + }, + { + // check that pressing cursor down highlights the second item + testname: "cursor down", + events: ["DOMMenuItemInactive item1", "DOMMenuItemActive item2"], + test() { + synthesizeKey("KEY_ArrowDown"); + }, + result(testname) { + checkActive(gMenuPopup, "item2", testname); + }, + }, + { + // check that pressing cursor up highlights the second item + testname: "cursor up", + events: ["DOMMenuItemInactive item2", "DOMMenuItemActive item1"], + test() { + synthesizeKey("KEY_ArrowUp"); + }, + result(testname) { + checkActive(gMenuPopup, "item1", testname); + }, + }, + { + // cursor left should not do anything + testname: "cursor left", + test() { + synthesizeKey("KEY_ArrowLeft"); + }, + result(testname) { + checkActive(gMenuPopup, "item1", testname); + }, + }, + { + // cursor right should not do anything + testname: "cursor right", + test() { + synthesizeKey("KEY_ArrowRight"); + }, + result(testname) { + checkActive(gMenuPopup, "item1", testname); + }, + }, + { + // check cursor down when a disabled item exists in the menu + testname: "cursor down disabled", + events() { + // On Windows, disabled items are included when navigating, but on + // other platforms, disabled items are skipped over + if (navigator.platform.indexOf("Win") == 0) { + return ["DOMMenuItemInactive item1", "DOMMenuItemActive item2"]; + } + return ["DOMMenuItemInactive item1", "DOMMenuItemActive amenu"]; + }, + test() { + document.getElementById("item2").disabled = true; + synthesizeKey("KEY_ArrowDown"); + }, + }, + { + // check cursor up when a disabled item exists in the menu + testname: "cursor up disabled", + events() { + if (navigator.platform.indexOf("Win") == 0) { + return [ + "DOMMenuItemInactive item2", + "DOMMenuItemActive amenu", + "DOMMenuItemInactive amenu", + "DOMMenuItemActive item2", + "DOMMenuItemInactive item2", + "DOMMenuItemActive item1", + ]; + } + return ["DOMMenuItemInactive amenu", "DOMMenuItemActive item1"]; + }, + test() { + if (navigator.platform.indexOf("Win") == 0) { + synthesizeKey("KEY_ArrowDown"); + } + synthesizeKey("KEY_ArrowUp"); + if (navigator.platform.indexOf("Win") == 0) { + synthesizeKey("KEY_ArrowUp"); + } + }, + }, + { + testname: "mouse click outside", + events: [ + "popuphiding thepopup", + "popuphidden thepopup", + "DOMMenuItemInactive item1", + "DOMMenuInactive thepopup", + ], + test() { + gMenuPopup.hidePopup(); + // XXXndeakin event simulation fires events outside of the platform specific + // widget code so the popup capturing isn't handled. Thus, the menu won't + // rollup this way. + // synthesizeMouse(gTrigger, 0, -12, { }); + }, + result(testname, step) { + is(gMenuPopup.anchorNode, null, testname + " anchorNode"); + is(gMenuPopup.triggerNode, null, testname + " triggerNode"); + checkClosed("trigger", testname); + }, + }, + { + // these tests check to ensure that passing an anchor and position + // puts the popup in the right place + testname: "open popup anchored", + events: ["popupshowing thepopup", "popupshown thepopup"], + autohide: "thepopup", + steps: [ + "before_start", + "before_end", + "after_start", + "after_end", + "start_before", + "start_after", + "end_before", + "end_after", + "after_pointer", + "overlap", + "topleft topleft", + "topcenter topleft", + "topright topleft", + "leftcenter topright", + "rightcenter topright", + "bottomleft bottomleft", + "bottomcenter bottomleft", + "bottomright bottomleft", + "topleft bottomright", + "bottomcenter bottomright", + "rightcenter topright", + "bottomcenter topcenter", + "rightcenter leftcenter", + ], + test(testname, step) { + gExpectedTriggerNode = "notset"; + gMenuPopup.openPopup(gTrigger, step, 0, 0, false, false); + }, + result(testname, step) { + // no triggerNode because it was opened without passing an event + gExpectedTriggerNode = null; + is(gMenuPopup.anchorNode, gTrigger, testname + " anchorNode"); + is(gMenuPopup.triggerNode, null, testname + " triggerNode"); + compareEdge(gTrigger, gMenuPopup, step, 0, 0, testname); + }, + }, + { + // these tests check the same but with a 10 pixel margin on the popup + testname: "open popup anchored with margin", + events: ["popupshowing thepopup", "popupshown thepopup"], + autohide: "thepopup", + steps: [ + "before_start", + "before_end", + "after_start", + "after_end", + "start_before", + "start_after", + "end_before", + "end_after", + "after_pointer", + "overlap", + "topleft topleft", + "topcenter topleft", + "topright topleft", + "leftcenter topright", + "rightcenter topright", + "bottomleft bottomleft", + "bottomcenter bottomleft", + "bottomright bottomleft", + "topleft bottomright", + "bottomcenter bottomright", + "rightcenter topright", + ], + test(testname, step) { + gMenuPopup.setAttribute("style", "margin: 10px;"); + gMenuPopup.openPopup(gTrigger, step, 0, 0, false, false); + }, + result(testname, step) { + var rightmod = + step == "before_end" || + step == "after_end" || + step == "start_before" || + step == "start_after" || + step.match(/topright$/) || + step.match(/bottomright$/); + var bottommod = + step == "before_start" || + step == "before_end" || + step == "start_after" || + step == "end_after" || + step.match(/bottomleft$/) || + step.match(/bottomright$/); + compareEdge( + gTrigger, + gMenuPopup, + step, + rightmod ? -10 : 10, + bottommod ? -10 : 10, + testname + ); + gMenuPopup.removeAttribute("style"); + }, + }, + { + // these tests check the same but with a -8 pixel margin on the popup + testname: "open popup anchored with negative margin", + events: ["popupshowing thepopup", "popupshown thepopup"], + autohide: "thepopup", + steps: [ + "before_start", + "before_end", + "after_start", + "after_end", + "start_before", + "start_after", + "end_before", + "end_after", + "after_pointer", + "overlap", + ], + test(testname, step) { + gMenuPopup.setAttribute("style", "margin: -8px;"); + gMenuPopup.openPopup(gTrigger, step, 0, 0, false, false); + }, + result(testname, step) { + var rightmod = + step == "before_end" || + step == "after_end" || + step == "start_before" || + step == "start_after"; + var bottommod = + step == "before_start" || + step == "before_end" || + step == "start_after" || + step == "end_after"; + compareEdge( + gTrigger, + gMenuPopup, + step, + rightmod ? 8 : -8, + bottommod ? 8 : -8, + testname + ); + gMenuPopup.removeAttribute("style"); + }, + }, + { + testname: "open popup with large positive margin", + events: ["popupshowing thepopup", "popupshown thepopup"], + autohide: "thepopup", + steps: [ + "before_start", + "before_end", + "after_start", + "after_end", + "start_before", + "start_after", + "end_before", + "end_after", + "after_pointer", + "overlap", + ], + test(testname, step) { + gMenuPopup.setAttribute("style", "margin: 1000px;"); + gMenuPopup.openPopup(gTrigger, step, 0, 0, false, false); + }, + result(testname, step) { + var popuprect = gMenuPopup.getBoundingClientRect(); + // as there is more room on the 'end' or 'after' side, popups will always + // appear on the right or bottom corners, depending on which side they are + // allowed to be flipped by. + var expectedleft = + step == "before_end" || step == "after_end" + ? 0 + : Math.round(window.innerWidth - gPopupWidth); + var expectedtop = + step == "start_after" || step == "end_after" + ? 0 + : Math.round(window.innerHeight - gPopupHeight); + is( + Math.round(popuprect.left), + expectedleft, + testname + " x position " + step + ); + is( + Math.round(popuprect.top), + expectedtop, + testname + " y position " + step + ); + gMenuPopup.removeAttribute("style"); + }, + }, + { + testname: "open popup with large negative margin", + events: ["popupshowing thepopup", "popupshown thepopup"], + autohide: "thepopup", + steps: [ + "before_start", + "before_end", + "after_start", + "after_end", + "start_before", + "start_after", + "end_before", + "end_after", + "after_pointer", + "overlap", + ], + test(testname, step) { + gMenuPopup.setAttribute("style", "margin: -1000px;"); + gMenuPopup.openPopup(gTrigger, step, 0, 0, false, false); + }, + result(testname, step) { + var popuprect = gMenuPopup.getBoundingClientRect(); + // using negative margins causes the reverse of positive margins, and + // popups will appear on the left or top corners. + var expectedleft = + step == "before_end" || step == "after_end" + ? Math.round(window.innerWidth - gPopupWidth) + : 0; + var expectedtop = + step == "start_after" || step == "end_after" + ? Math.round(window.innerHeight - gPopupHeight) + : 0; + is( + Math.round(popuprect.left), + expectedleft, + testname + " x position " + step + ); + is( + Math.round(popuprect.top), + expectedtop, + testname + " y position " + step + ); + gMenuPopup.removeAttribute("style"); + }, + }, + { + testname: "popup with unknown step", + events: ["popupshowing thepopup", "popupshown thepopup"], + autohide: "thepopup", + test() { + gMenuPopup.openPopup(gTrigger, "other", 0, 0, false, false); + }, + result(testname) { + var triggerrect = gMenuPopup.getBoundingClientRect(); + var popuprect = gMenuPopup.getBoundingClientRect(); + is( + Math.round(popuprect.left), + triggerrect.left, + testname + " x position " + ); + is(Math.round(popuprect.top), triggerrect.top, testname + " y position "); + }, + }, + { + // these tests check to ensure that the position attribute can be used + // to set the position of a popup instead of passing it as an argument + testname: "open popup anchored with attribute", + events: ["popupshowing thepopup", "popupshown thepopup"], + autohide: "thepopup", + steps: [ + "before_start", + "before_end", + "after_start", + "after_end", + "start_before", + "start_after", + "end_before", + "end_after", + "after_pointer", + "overlap", + "topcenter topleft", + "topright bottomright", + "leftcenter topright", + ], + test(testname, step) { + gMenuPopup.setAttribute("position", step); + gMenuPopup.openPopup(gTrigger, "", 0, 0, false, false); + }, + result(testname, step) { + compareEdge(gTrigger, gMenuPopup, step, 0, 0, testname); + }, + }, + { + // this test checks to ensure that the attributes override flag to openPopup + // can be used to override the popup's position. This test also passes an + // event to openPopup to check the trigger node. + testname: "open popup anchored with override", + events: ["popupshowing thepopup 0010", "popupshown thepopup"], + test(testname, step) { + // attribute overrides the position passed in + gMenuPopup.setAttribute("position", "end_after"); + gExpectedTriggerNode = gCachedEvent.target; + gMenuPopup.openPopup( + gTrigger, + "before_start", + 0, + 0, + false, + true, + gCachedEvent + ); + }, + result(testname, step) { + gExpectedTriggerNode = null; + is(gMenuPopup.anchorNode, gTrigger, testname + " anchorNode"); + is( + gMenuPopup.triggerNode, + gCachedEvent.target, + testname + " triggerNode" + ); + compareEdge(gTrigger, gMenuPopup, "end_after", 0, 0, testname); + }, + }, + { + testname: "close popup with escape", + events: [ + "popuphiding thepopup", + "popuphidden thepopup", + "DOMMenuInactive thepopup", + ], + test(testname, step) { + synthesizeKey("KEY_Escape"); + checkClosed("trigger", testname); + }, + }, + { + // check that offsets may be supplied to the openPopup method + testname: "open popup anchored with offsets", + events: ["popupshowing thepopup", "popupshown thepopup"], + autohide: "thepopup", + test(testname, step) { + // attribute is empty so does not override + gMenuPopup.setAttribute("position", ""); + gMenuPopup.openPopup(gTrigger, "before_start", 5, 10, true, true); + }, + result(testname, step) { + compareEdge(gTrigger, gMenuPopup, "before_start", 5, 10, testname); + }, + }, + { + // if no anchor is supplied to openPopup, it should be opened relative + // to the viewport. + testname: "open popup unanchored", + events: ["popupshowing thepopup", "popupshown thepopup"], + test(testname, step) { + gMenuPopup.openPopup(null, "after_start", 6, 8, false); + }, + result(testname, step) { + var rect = gMenuPopup.getBoundingClientRect(); + ok( + rect.left == 6 && rect.top == 8 && rect.right && rect.bottom, + testname + ); + }, + }, + { + testname: "activate menuitem with mouse", + events: [ + "DOMMenuInactive thepopup", + "command item3", + "popuphiding thepopup", + "popuphidden thepopup", + ], + test(testname, step) { + var item3 = document.getElementById("item3"); + synthesizeMouse(item3, 4, 4, {}); + }, + result(testname, step) { + checkClosed("trigger", testname); + }, + }, + { + testname: "close popup", + condition() { + return false; + }, + events: [ + "popuphiding thepopup", + "popuphidden thepopup", + "DOMMenuInactive thepopup", + ], + test(testname, step) { + gMenuPopup.hidePopup(); + }, + }, + { + testname: "open popup at screen", + events: ["popupshowing thepopup", "popupshown thepopup"], + test(testname, step) { + gExpectedTriggerNode = "notset"; + gMenuPopup.openPopupAtScreen(gScreenX + 24, gScreenY + 20, false); + }, + result(testname, step) { + gExpectedTriggerNode = null; + is(gMenuPopup.anchorNode, null, testname + " anchorNode"); + is(gMenuPopup.triggerNode, null, testname + " triggerNode"); + var rect = gMenuPopup.getBoundingClientRect(); + is(rect.left, 24, testname + " left"); + is(rect.top, 20, testname + " top"); + ok(rect.right, testname + " right is " + rect.right); + ok(rect.bottom, testname + " bottom is " + rect.bottom); + }, + }, + { + // check that pressing a menuitem's accelerator selects it. Note that + // the menuitem with the M accesskey overrides the earlier menuitem that + // begins with M. + testname: "menuitem accelerator", + events: [ + "DOMMenuItemActive amenu", + "DOMMenuItemInactive amenu", + "DOMMenuInactive thepopup", + "command amenu", + "popuphiding thepopup", + "popuphidden thepopup", + ], + test() { + sendString("M"); + }, + result(testname) { + checkClosed("trigger", testname); + }, + }, + { + testname: "open context popup at screen", + events: ["popupshowing thepopup 0010", "popupshown thepopup"], + test(testname, step) { + gExpectedTriggerNode = gCachedEvent.target; + gMenuPopup.openPopupAtScreen( + gScreenX + 8, + gScreenY + 16, + true, + gCachedEvent + ); + }, + result(testname, step) { + gExpectedTriggerNode = null; + is(gMenuPopup.anchorNode, null, testname + " anchorNode"); + is( + gMenuPopup.triggerNode, + gCachedEvent.target, + testname + " triggerNode" + ); + + var openX = 8; + var openY = 16; + var rect = gMenuPopup.getBoundingClientRect(); + is(rect.left, openX + (platformIsMac() ? 1 : 2), testname + " left"); + is(rect.top, openY + (platformIsMac() ? -6 : 2), testname + " top"); + ok(rect.right, testname + " right is " + rect.right); + ok(rect.bottom, testname + " bottom is " + rect.bottom); + }, + }, + { + // pressing a letter that doesn't correspond to an accelerator, but does + // correspond to the first letter in a menu's label. The menu should not + // close because there is more than one item corresponding to that letter + testname: "menuitem with non accelerator", + events: ["DOMMenuItemActive one"], + test() { + sendString("O"); + }, + result(testname) { + checkOpen("trigger", testname); + checkActive(gMenuPopup, "one", testname); + }, + }, + { + // pressing the letter again should select the next one that starts with + // that letter + testname: "menuitem with non accelerator again", + events: ["DOMMenuItemInactive one", "DOMMenuItemActive submenu"], + test() { + sendString("O"); + }, + result(testname) { + // 'submenu' is a menu but it should not be open + checkOpen("trigger", testname); + checkClosed("submenu", testname); + checkActive(gMenuPopup, "submenu", testname); + }, + }, + { + // open the submenu with the cursor right key + testname: "open submenu with cursor right", + events: [ + "popupshowing submenupopup", + "DOMMenuItemActive submenuitem", + "popupshown submenupopup", + ], + test() { + synthesizeKey("KEY_ArrowRight"); + }, + result(testname) { + checkOpen("trigger", testname); + checkOpen("submenu", testname); + checkActive(gMenuPopup, "submenu", testname); + checkActive( + document.getElementById("submenupopup"), + "submenuitem", + testname + ); + }, + }, + { + // close the submenu with the cursor left key + testname: "close submenu with cursor left", + events: [ + "popuphiding submenupopup", + "popuphidden submenupopup", + "DOMMenuItemInactive submenuitem", + "DOMMenuInactive submenupopup", + ], + test() { + synthesizeKey("KEY_ArrowLeft"); + }, + result(testname) { + checkOpen("trigger", testname); + checkClosed("submenu", testname); + checkActive(gMenuPopup, "submenu", testname); + checkActive(document.getElementById("submenupopup"), "", testname); + }, + }, + { + // open the submenu with the enter key + testname: "open submenu with enter", + events: [ + "popupshowing submenupopup", + "DOMMenuItemActive submenuitem", + "popupshown submenupopup", + ], + test() { + synthesizeKey("KEY_Enter"); + }, + result(testname) { + checkOpen("trigger", testname); + checkOpen("submenu", testname); + checkActive(gMenuPopup, "submenu", testname); + checkActive( + document.getElementById("submenupopup"), + "submenuitem", + testname + ); + }, + }, + { + // close the submenu with the escape key + testname: "close submenu with escape", + events: [ + "popuphiding submenupopup", + "popuphidden submenupopup", + "DOMMenuItemInactive submenuitem", + "DOMMenuInactive submenupopup", + ], + test() { + synthesizeKey("KEY_Escape"); + }, + result(testname) { + checkOpen("trigger", testname); + checkClosed("submenu", testname); + checkActive(gMenuPopup, "submenu", testname); + checkActive(document.getElementById("submenupopup"), "", testname); + }, + }, + { + // pressing the letter again when the next item is disabled should still + // select the disabled item on Windows, but select the next item on other + // platforms + testname: "menuitem with non accelerator disabled", + events() { + if (navigator.platform.indexOf("Win") == 0) { + return [ + "DOMMenuItemInactive submenu", + "DOMMenuItemActive other", + "DOMMenuItemInactive other", + "DOMMenuItemActive item1", + ]; + } + return [ + "DOMMenuItemInactive submenu", + "DOMMenuItemActive last", + "DOMMenuItemInactive last", + "DOMMenuItemActive item1", + ]; + }, + test() { + sendString("OF"); + }, + result(testname) { + checkActive(gMenuPopup, "item1", testname); + }, + }, + { + // pressing a letter that doesn't correspond to an accelerator nor the + // first letter of a menu. This should have no effect. + testname: "menuitem with keypress no accelerator found", + test() { + sendString("G"); + }, + result(testname) { + checkOpen("trigger", testname); + checkActive(gMenuPopup, "item1", testname); + }, + }, + { + // when only one menuitem starting with that letter exists, it should be + // selected and the menu closed + testname: "menuitem with non accelerator single", + events: [ + "DOMMenuItemInactive item1", + "DOMMenuItemActive amenu", + "DOMMenuItemInactive amenu", + "DOMMenuInactive thepopup", + "command amenu", + "popuphiding thepopup", + "popuphidden thepopup", + ], + test() { + sendString("M"); + }, + result(testname) { + checkClosed("trigger", testname); + checkActive(gMenuPopup, "", testname); + }, + }, + { + testname: "open context popup at screen with all modifiers set", + events: ["popupshowing thepopup 1111", "popupshown thepopup"], + autohide: "thepopup", + test(testname, step) { + gMenuPopup.openPopupAtScreen( + gScreenX + 8, + gScreenY + 16, + true, + gCachedEvent2 + ); + }, + }, + { + testname: "open popup with open property", + events: ["popupshowing thepopup", "popupshown thepopup"], + test(testname, step) { + openMenu(gTrigger); + }, + result(testname, step) { + checkOpen("trigger", testname); + if (gIsMenu) { + compareEdge(gTrigger, gMenuPopup, "after_start", 0, 0, testname); + } + }, + }, + { + testname: "open submenu with open property", + events: [ + "popupshowing submenupopup", + "DOMMenuItemActive submenu", + "popupshown submenupopup", + ], + test(testname, step) { + openMenu(document.getElementById("submenu")); + }, + result(testname, step) { + checkOpen("trigger", testname); + checkOpen("submenu", testname); + // XXXndeakin + // getBoundingClientRect doesn't seem to working right for submenus + // so disable this test for now + // compareEdge(document.getElementById("submenu"), + // document.getElementById("submenupopup"), "end_before", 0, 0, testname); + }, + }, + { + testname: "hidePopup hides entire chain", + events: [ + "popuphiding submenupopup", + "popuphidden submenupopup", + "popuphiding thepopup", + "popuphidden thepopup", + "DOMMenuInactive submenupopup", + "DOMMenuItemInactive submenu", + "DOMMenuInactive thepopup", + ], + test() { + gMenuPopup.hidePopup(); + }, + result(testname, step) { + checkClosed("trigger", testname); + checkClosed("submenu", testname); + }, + }, + { + testname: "open submenu with open property without parent open", + test(testname, step) { + openMenu(document.getElementById("submenu")); + }, + result(testname, step) { + checkClosed("trigger", testname); + checkClosed("submenu", testname); + }, + }, + { + testname: "open popup with open property and position", + condition() { + return gIsMenu; + }, + events: ["popupshowing thepopup", "popupshown thepopup"], + test(testname, step) { + gMenuPopup.setAttribute("position", "before_start"); + openMenu(gTrigger); + }, + result(testname, step) { + compareEdge(gTrigger, gMenuPopup, "before_start", 0, 0, testname); + }, + }, + { + testname: "close popup with open property", + condition() { + return gIsMenu; + }, + events: [ + "popuphiding thepopup", + "popuphidden thepopup", + "DOMMenuInactive thepopup", + ], + test(testname, step) { + closeMenu(gTrigger, gMenuPopup); + }, + result(testname, step) { + checkClosed("trigger", testname); + }, + }, + { + testname: "open popup with open property, position, anchor and alignment", + condition() { + return gIsMenu; + }, + events: ["popupshowing thepopup", "popupshown thepopup"], + autohide: "thepopup", + test(testname, step) { + gMenuPopup.setAttribute("position", "start_after"); + gMenuPopup.setAttribute("popupanchor", "topright"); + gMenuPopup.setAttribute("popupalign", "bottomright"); + openMenu(gTrigger); + }, + result(testname, step) { + compareEdge(gTrigger, gMenuPopup, "start_after", 0, 0, testname); + }, + }, + { + testname: "open popup with open property, anchor and alignment", + condition() { + return gIsMenu; + }, + events: ["popupshowing thepopup", "popupshown thepopup"], + autohide: "thepopup", + test(testname, step) { + gMenuPopup.removeAttribute("position"); + gMenuPopup.setAttribute("popupanchor", "bottomright"); + gMenuPopup.setAttribute("popupalign", "topright"); + openMenu(gTrigger); + }, + result(testname, step) { + compareEdge(gTrigger, gMenuPopup, "after_end", 0, 0, testname); + gMenuPopup.removeAttribute("popupanchor"); + gMenuPopup.removeAttribute("popupalign"); + }, + }, + { + testname: "focus and cursor down on trigger", + condition() { + return gIsMenu; + }, + events: ["popupshowing thepopup", "popupshown thepopup"], + autohide: "thepopup", + test(testname, step) { + gTrigger.focus(); + synthesizeKey("KEY_ArrowDown", { altKey: !platformIsMac() }); + }, + result(testname, step) { + checkOpen("trigger", testname); + checkActive(gMenuPopup, "", testname); + }, + }, + { + testname: "focus and cursor up on trigger", + condition() { + return gIsMenu; + }, + events: ["popupshowing thepopup", "popupshown thepopup"], + test(testname, step) { + gTrigger.focus(); + synthesizeKey("KEY_ArrowUp", { altKey: !platformIsMac() }); + }, + result(testname, step) { + checkOpen("trigger", testname); + checkActive(gMenuPopup, "", testname); + }, + }, + { + testname: "select and enter on menuitem", + condition() { + return gIsMenu; + }, + events: [ + "DOMMenuItemActive item1", + "DOMMenuItemInactive item1", + "DOMMenuInactive thepopup", + "command item1", + "popuphiding thepopup", + "popuphidden thepopup", + ], + test(testname, step) { + synthesizeKey("KEY_ArrowDown"); + synthesizeKey("KEY_Enter"); + }, + result(testname, step) { + checkClosed("trigger", testname); + }, + }, + { + testname: "focus trigger and key to open", + condition() { + return gIsMenu; + }, + events: ["popupshowing thepopup", "popupshown thepopup"], + autohide: "thepopup", + test(testname, step) { + gTrigger.focus(); + synthesizeKey(platformIsMac() ? " " : "KEY_F4"); + }, + result(testname, step) { + checkOpen("trigger", testname); + checkActive(gMenuPopup, "", testname); + }, + }, + { + // the menu should only open when the meta or alt key is not pressed + testname: "focus trigger and key wrong modifier", + condition() { + return gIsMenu; + }, + test(testname, step) { + gTrigger.focus(); + if (platformIsMac()) { + synthesizeKey("KEY_F4", { altKey: true }); + } else { + synthesizeKey("", { metaKey: true }); + } + }, + result(testname, step) { + checkClosed("trigger", testname); + }, + }, + { + testname: "mouse click on disabled menu", + condition() { + return gIsMenu; + }, + test(testname, step) { + gTrigger.setAttribute("disabled", "true"); + synthesizeMouse(gTrigger, 4, 4, {}); + }, + result(testname, step) { + checkClosed("trigger", testname); + gTrigger.removeAttribute("disabled"); + }, + }, + { + // openPopup using object as position argument + testname: "openPopup with object argument", + events: ["popupshowing thepopup 0000", "popupshown thepopup"], + autohide: "thepopup", + test(testname, step) { + gMenuPopup.openPopup(gTrigger, { position: "before_start", x: 5, y: 7 }); + checkOpen("trigger", testname); + }, + result(testname, step) { + var triggerrect = gTrigger.getBoundingClientRect(); + var popuprect = gMenuPopup.getBoundingClientRect(); + is( + Math.round(popuprect.left), + Math.round(triggerrect.left + 5), + testname + " x position " + ); + is( + Math.round(popuprect.bottom), + Math.round(triggerrect.top + 7), + testname + " y position " + ); + }, + }, + { + testname: "openPopup with object argument with event", + events: ["popupshowing thepopup 1000", "popupshown thepopup"], + autohide: "thepopup", + test(testname, step) { + gMenuPopup.openPopup(gTrigger, { + position: "after_start", + x: 0, + y: 0, + triggerEvent: new MouseEvent("mousedown", { altKey: true }), + }); + checkOpen("trigger", testname); + }, + }, + { + testname: "openPopup with no arguments", + events: ["popupshowing thepopup", "popupshown thepopup"], + autohide: "thepopup", + test(testname, step) { + gMenuPopup.openPopup(); + }, + result(testname, step) { + let isMenu = gTrigger.type == "menu"; + // With no arguments, open in default menu position + var triggerrect = gTrigger.getBoundingClientRect(); + var popuprect = gMenuPopup.getBoundingClientRect(); + is( + Math.round(popuprect.left), + isMenu ? Math.round(triggerrect.left) : 0, + testname + " x position " + ); + is( + Math.round(popuprect.top), + isMenu ? Math.round(triggerrect.bottom) : 0, + testname + " y position " + ); + }, + }, + { + // openPopup should open the menu synchronously, however popupshown + // is fired asynchronously + testname: "openPopup synchronous", + events: [ + "popupshowing thepopup", + "popupshowing submenupopup", + "popupshown thepopup", + "DOMMenuItemActive submenu", + "popupshown submenupopup", + ], + test(testname, step) { + gMenuPopup.openPopup(gTrigger, "after_start", 0, 0, false, true); + document + .getElementById("submenupopup") + .openPopup(gTrigger, "end_before", 0, 0, false, true); + checkOpen("trigger", testname); + checkOpen("submenu", testname); + }, + }, + { + // remove the content nodes for the popup + testname: "remove content", + test(testname, step) { + var submenupopup = document.getElementById("submenupopup"); + submenupopup.remove(); + var popup = document.getElementById("thepopup"); + popup.remove(); + }, + }, +]; + +function platformIsMac() { + return navigator.platform.indexOf("Mac") > -1; +} diff --git a/toolkit/content/tests/chrome/sample_entireword_latin1.html b/toolkit/content/tests/chrome/sample_entireword_latin1.html new file mode 100644 index 0000000000..b2d66fa3c4 --- /dev/null +++ b/toolkit/content/tests/chrome/sample_entireword_latin1.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <head><title>Latin entire-word find test page</title></head> + <body> + <!-- Feel free to extend the contents of this page with more comprehensive + - Latin punctuation and/ or word markers. + --> + <p>The twins of Mammon quarrelled. Their warring plunged the world into a new darkness, and the beast abhorred the darkness. So it began to move swiftly, and grew more powerful, and went forth and multiplied. And the beasts brought fire and light to the darkness.</p> + <p>from The Book of Mozilla, 15:1</p> + </body> +</html> diff --git a/toolkit/content/tests/chrome/test_about_networking.html b/toolkit/content/tests/chrome/test_about_networking.html new file mode 100644 index 0000000000..5465c07751 --- /dev/null +++ b/toolkit/content/tests/chrome/test_about_networking.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=912103 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + SimpleTest.waitForExplicitFinish(); + + function runTest() { + var dashboard = Cc["@mozilla.org/network/dashboard;1"] + .getService(Ci.nsIDashboard); + dashboard.enableLogging = true; + + var wsURI = "ws://mochi.test:8888/chrome/toolkit/content/tests/chrome/file_about_networking"; + var websocket = new WebSocket(wsURI); + + websocket.addEventListener("open", function() { + dashboard.requestWebsocketConnections(function(data) { + var found = false; + for (var i = 0; i < data.websockets.length; i++) { + if (data.websockets[i].hostport == "mochi.test:8888") { + found = true; + break; + } + } + isnot(found, false, "tested websocket entry not found"); + websocket.close(); + SimpleTest.finish(); + }); + }); + } + + window.addEventListener("DOMContentLoaded", function() { + runTest(); + }, {once: true}); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=912103">Mozilla Bug </a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/chrome/test_arrowpanel.xhtml b/toolkit/content/tests/chrome/test_arrowpanel.xhtml new file mode 100644 index 0000000000..cd8d312e1d --- /dev/null +++ b/toolkit/content/tests/chrome/test_arrowpanel.xhtml @@ -0,0 +1,332 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Arrow Panels" + style="padding: 10px;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<stack flex="1"> + <label id="topleft" value="Top Left Corner" style="justify-self: left; margin-left: 15px; align-self: start; margin-top: 15px;"/> + <label id="topright" value="Top Right" style="justify-self: right; margin-right: 15px; align-self: start; margin-top: 15px;"/> + <label id="bottomleft" value="Bottom Left Corner" style="justify-self: left; margin-left: 15px; align-self: end; margin-bottom: 15px;"/> + <label id="bottomright" value="Bottom Right" style="justify-self: right; margin-right: 15px; align-self: end; margin-bottom: 15px;"/> + <!-- Our SimpleTest/TestRunner.js runs tests inside an iframe which sizes are W=500 H=300. + 'left' and 'top' values need to be set so that the panel (popup) has enough room to display on its 4 sides. --> + <label id="middle" value="+/- Centered" style="justify-self: left; margin-left: 225px; align-self: start; margin-top: 135px;"/> + <iframe id="frame" type="content" + src="data:text/html,<input id='input'>" style="width: 100px; height: 100px; justify-self: left; margin-left: 225px; align-self: start; margin-top: 120px;"/> +</stack> + +<panel id="panel" type="arrow" animate="false" + onpopupshown="checkPanelPosition(this)" onpopuphidden="runNextTest.next()"> + <box style="width: 115px; height: 65px"/> +</panel> + +<panel id="bigpanel" type="arrow" animate="false" + onpopupshown="checkBigPanel(this)" onpopuphidden="runNextTest.next()"> + <box style="width: 125px; height: 3000px"/> +</panel> + +<panel id="animatepanel" type="arrow" + onpopupshown="animatedPopupShown = true;" + onpopuphidden="animatedPopupHidden = true; runNextTest.next();"> + <label value="Animate Closed" style="height: 40px"/> +</panel> + +<html:style type="text/css"> + panel { + /** + * We hardcode a panel padding here to avoid rounding issues caused by + * using em unit padding, which is the default as of bug 1701920. + */ + --arrowpanel-padding: 16px; + /** + * Linux and windows have some negative margin-inline that can change the + * overflow calculations + */ + margin-inline: 0 !important; + } +</html:style> + +<script type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var expectedAnchor = null; +var expectedSide = "", expectedAnchorEdge = ""; +var zoomFactor = 1; +var animatedPopupShown = false; +var animatedPopupHidden = false; +var runNextTest; + +function startTest() +{ + runNextTest = nextTest(); + runNextTest.next(); +} + +function* nextTest() +{ + var panel = $("panel"); + + function openPopup(position, anchor, expected, anchorEdge) + { + expectedAnchor = Node.isInstance(anchor) ? anchor : $(anchor); + expectedSide = expected; + expectedAnchorEdge = anchorEdge; + + panel.removeAttribute("side"); + panel.openPopup(expectedAnchor, position, 0, 0, false, false, null); + } + + for (var iter = 0; iter < 2; iter++) { + openPopup("after_start", "topleft", "top", "left"); + yield; + openPopup("after_start", "bottomleft", "bottom", "left"); + yield; + openPopup("before_start", "topleft", "top", "left"); + yield; + openPopup("before_start", "bottomleft", "bottom", "left"); + yield; + openPopup("after_start", "middle", "top", "left"); + yield; + openPopup("before_start", "middle", "bottom", "left"); + yield; + + openPopup("after_start", "topright", "top", "right"); + yield; + openPopup("after_start", "bottomright", "bottom", "right"); + yield; + openPopup("before_start", "topright", "top", "right"); + yield; + openPopup("before_start", "bottomright", "bottom", "right"); + yield; + + openPopup("after_end", "middle", "top", "right"); + yield; + openPopup("before_end", "middle", "bottom", "right"); + yield; + + openPopup("start_before", "topleft", "left", "top"); + yield; + openPopup("start_before", "topright", "right", "top"); + yield; + openPopup("end_before", "topleft", "left", "top"); + yield; + openPopup("end_before", "topright", "right", "top"); + yield; + openPopup("start_before", "middle", "right", "top"); + yield; + openPopup("end_before", "middle", "left", "top"); + yield; + + openPopup("start_before", "bottomleft", "left", "bottom"); + yield; + openPopup("start_before", "bottomright", "right", "bottom"); + yield; + openPopup("end_before", "bottomleft", "left", "bottom"); + yield; + openPopup("end_before", "bottomright", "right", "bottom"); + yield; + + openPopup("start_after", "middle", "right", "bottom"); + yield; + openPopup("end_after", "middle", "left", "bottom"); + yield; + + openPopup("topcenter bottomleft", "bottomleft", "bottom", "center left"); + yield; + openPopup("bottomcenter topleft", "topleft", "top", "center left"); + yield; + openPopup("topcenter bottomright", "bottomright", "bottom", "center right"); + yield; + openPopup("bottomcenter topright", "topright", "top", "center right"); + yield; + openPopup("topcenter bottomleft", "middle", "bottom", "center left"); + yield; + openPopup("bottomcenter topleft", "middle", "top", "center left"); + yield; + + openPopup("leftcenter topright", "middle", "right", "center top"); + yield; + openPopup("rightcenter bottomleft", "middle", "left", "center bottom"); + yield; + +/* + XXXndeakin disable these parts of the test which often cause problems, see bug 626563 + + openPopup("after_start", frames[0].document.getElementById("input"), "top", "left"); + yield; + + setScale(frames[0], 1.5); + openPopup("after_start", frames[0].document.getElementById("input"), "top", "left"); + yield; + + setScale(frames[0], 2.5); + openPopup("before_start", frames[0].document.getElementById("input"), "bottom", "left"); + yield; + + setScale(frames[0], 1); +*/ + + $("bigpanel").openPopup($("topleft"), "after_start", 0, 0, false, false, null, "start"); + yield; + + // switch to rtl mode + document.documentElement.style.direction = "rtl"; + + $("topleft").style.marginRight = "15px"; + $("topleft").style.justifySelf = "right"; + + $("topright").style.marginLeft = "15px"; + $("topright").style.justifySelf = "left"; + + $("bottomleft").style.marginRight = "15px"; + $("bottomleft").style.justifySelf = "right"; + + $("bottomright").style.marginLeft = "15px"; + $("bottomright").style.justifySelf = "left"; + + $("topleft").style.removeProperty("margin-left"); + $("topright").style.removeProperty("margin-right"); + $("bottomleft").style.removeProperty("margin-left"); + $("bottomright").style.removeProperty("margin-right"); + } + + // Test that a transition occurs when opening or closing the popup. + if (matchMedia("(-moz-panel-animations").matches) { + function transitionEnded(event) { + if ($("animatepanel").state != "open") { + is($("animatepanel").state, "showing", "state is showing during transitionend"); + ok(!animatedPopupShown, "popupshown not fired yet") + } else { + is($("animatepanel").state, "open", "state is open after transitionend"); + ok(animatedPopupShown, "popupshown now fired") + SimpleTest.executeSoon(() => runNextTest.next()); + } + } + + // Check that the transition occurs for an arrow panel with animate="true" + $("animatepanel").addEventListener("transitionend", transitionEnded); + $("animatepanel").openPopup($("topleft"), "after_start", 0, 0, false, false, null, "start"); + is($("animatepanel").state, "showing", "state is showing"); + yield; + $("animatepanel").removeEventListener("transitionend", transitionEnded); + + synthesizeKey("KEY_Escape"); + ok(!animatedPopupHidden, "animated popup not hidden yet"); + yield; + } + + SimpleTest.finish() +} + +function setScale(win, scale) +{ + SpecialPowers.setFullZoom(win, scale); + zoomFactor = scale; +} + +function checkPanelPosition(panel) +{ + let anchor = panel.anchorNode; + let adj = 0, hwinpos = 0, vwinpos = 0; + if (anchor.ownerDocument != document) { + var framerect = anchor.ownerGlobal.frameElement.getBoundingClientRect(); + hwinpos = framerect.left; + vwinpos = framerect.top; + } + + // Positions are reversed in rtl yet the coordinates used in the computations + // are not, so flip the expected label side and anchor edge. + var isRTL = (window.getComputedStyle(panel).direction == "rtl"); + if (isRTL) { + var flipLeftRight = val => val == "left" ? "right" : "left"; + expectedAnchorEdge = expectedAnchorEdge.replace(/(left|right)/, flipLeftRight); + expectedSide = expectedSide.replace(/(left|right)/, flipLeftRight); + } + + var panelRect = panel.getBoundingClientRect(); + var anchorRect = anchor.getBoundingClientRect(); + var contentRect = panel.firstChild.getBoundingClientRect(); + switch (expectedSide) { + case "top": + ok(contentRect.top > vwinpos + anchorRect.bottom * zoomFactor + 5, "panel content is below"); + break; + case "bottom": + ok(contentRect.bottom < vwinpos + anchorRect.top * zoomFactor - 5, "panel content is above"); + break; + case "left": + ok(contentRect.left > hwinpos + anchorRect.right * zoomFactor + 5, "panel content is right"); + break; + case "right": + ok(contentRect.right < hwinpos + anchorRect.left * zoomFactor - 5, "panel content is left"); + break; + } + + let desc = panel.id + ": anchored on " + expectedAnchorEdge + " to " + anchor.id + " | " + (isRTL ? "rtl" : "ltr") + " | " + anchor.getAttribute("style"); + let iscentered = false; + if (expectedAnchorEdge.indexOf("center ") == 0) { + expectedAnchorEdge = expectedAnchorEdge.substring(7); + iscentered = true; + } + + switch (expectedAnchorEdge) { + case "top": + adj = vwinpos + parseInt(getComputedStyle(panel).marginTop); + if (iscentered) + adj += anchorRect.height / 2; + isWithinHalfPixel(panelRect.top, anchorRect.top * zoomFactor + adj, desc); + break; + case "bottom": + adj = vwinpos + parseInt(getComputedStyle(panel).marginBottom); + if (iscentered) + adj += anchorRect.height / 2; + isWithinHalfPixel(panelRect.bottom, anchorRect.bottom * zoomFactor - adj, desc); + break; + case "left": + adj = hwinpos + parseInt(getComputedStyle(panel).marginLeft); + if (iscentered) + adj += anchorRect.width / 2; + isWithinHalfPixel(panelRect.left, anchorRect.left * zoomFactor + adj, desc); + break; + case "right": + adj = hwinpos + parseInt(getComputedStyle(panel).marginRight); + if (iscentered) + adj += anchorRect.width / 2; + isWithinHalfPixel(panelRect.right, anchorRect.right * zoomFactor - adj, desc); + break; + } + + is(anchor, expectedAnchor, "anchor"); + + is(panel.getAttribute("side"), expectedSide, "panel arrow side"); + + panel.hidePopup(); +} + +function isWithinHalfPixel(a, b, desc) +{ + ok(Math.abs(a - b) <= 0.5, `${desc}: ${a} vs. ${b}`); +} + +function checkBigPanel(panel) +{ + ok(panel.getBoundingClientRect().height < screen.height, "big panel height"); + panel.hidePopup(); +} + +SimpleTest.waitForFocus(startTest); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"/> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete2.xhtml b/toolkit/content/tests/chrome/test_autocomplete2.xhtml new file mode 100644 index 0000000000..844305c321 --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete2.xhtml @@ -0,0 +1,189 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Autocomplete Widget Test 2" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<html:input id="autocomplete" + is="autocomplete-input" + autocompletesearch="simple"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +// Set to indicate whether or not we want autoCompleteSimple to return a result +var returnResult = false; + +const ACR = Ci.nsIAutoCompleteResult; + +// This result can't be constructed in-line, because otherwise we leak memory. +function nsAutoCompleteSimpleResult(aString) +{ + this.searchString = aString; + if (returnResult) { + this.searchResult = ACR.RESULT_SUCCESS; + this.matchCount = 1; + this._param = "SUCCESS"; + } +} + +nsAutoCompleteSimpleResult.prototype = { + _param: "", + searchString: null, + searchResult: ACR.RESULT_FAILURE, + defaultIndex: -1, + errorDescription: null, + matchCount: 0, + getValueAt() { return this._param; }, + getCommentAt() { return null; }, + getStyleAt() { return null; }, + getImageAtn() { return null; }, + getFinalCompleteValueAt() { return this.getValueAt(); }, + getLabelAt() { return null; }, + removeValueAt() {} +}; + +// A basic autocomplete implementation that either returns one result or none +var autoCompleteSimpleID = Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"); +var autoCompleteSimpleName = "@mozilla.org/autocomplete/search;1?name=simple" +var autoCompleteSimple = { + QueryInterface: ChromeUtils.generateQI(["nsIFactory", "nsIAutoCompleteSearch"]), + + createInstance(iid) { + return this.QueryInterface(iid); + }, + + startSearch(aString, aParam, aResult, aListener) { + var result = new nsAutoCompleteSimpleResult(aString); + aListener.onSearchResult(this, result); + }, + + stopSearch() {} +}; + +var componentManager = Components.manager + .QueryInterface(Ci.nsIComponentRegistrar); +componentManager.registerFactory(autoCompleteSimpleID, "Test Simple Autocomplete", + autoCompleteSimpleName, autoCompleteSimple); + +var element = document.getElementById("autocomplete"); + +// Create stub to intercept `onSearchComplete` event. +element.onSearchComplete = function(original) { + return function() { + original.apply(this, arguments); + checkResult(); + }; +}(element.onSearchComplete); + +// Test Bug 441530 - correctly setting "nomatch" +// Test Bug 441526 - correctly setting style with "highlightnonmatches" + +SimpleTest.waitForExplicitFinish(); +setTimeout(startTest, 0); + +function startTest() { + var autocomplete = $("autocomplete"); + + // Ensure highlightNonMatches can be set correctly. + + // This should not be set by default. + is(autocomplete.hasAttribute("highlightnonmatches"), false, + "highlight nonmatches not set by default"); + + autocomplete.highlightNonMatches = "true"; + + is(autocomplete.getAttribute("highlightnonmatches"), "true", + "highlight non matches attribute set correctly"); + is(autocomplete.highlightNonMatches, true, + "highlight non matches getter returned correctly"); + + autocomplete.highlightNonMatches = "false"; + + is(autocomplete.getAttribute("highlightnonmatches"), "false", + "highlight non matches attribute set to false correctly"); + is(autocomplete.highlightNonMatches, false, + "highlight non matches getter returned false correctly"); + + check(); +} + +function check() { + var autocomplete = $("autocomplete"); + + // Toggle this value, so we can re-use the one function. + returnResult = !returnResult; + + // blur the field to ensure that the popup is closed and that the previous + // search has stopped, then start a new search. + autocomplete.blur(); + autocomplete.focus(); + sendString("r"); +} + +function checkResult() { + var autocomplete = $("autocomplete"); + var style = window.getComputedStyle(autocomplete); + + if (returnResult) { + // Result was returned, so there should not be a nomatch attribute + is(autocomplete.hasAttribute("nomatch"), false, + "nomatch attribute shouldn't be present here"); + + // Ensure that the style is set correctly whichever way highlightNonMatches + // is set. + autocomplete.highlightNonMatches = "true"; + + isnot(style.color, "rgb(255, 0, 0)", + "not nomatch and highlightNonMatches - should not be red"); + + autocomplete.highlightNonMatches = "false"; + + isnot(style.color, "rgb(255, 0, 0)", + "not nomatch and not highlightNonMatches - should not be red"); + + setTimeout(check, 0); + } + else { + // No result was returned, so there should be nomatch attribute + is(autocomplete.getAttribute("nomatch"), "true", + "nomatch attribute not correctly set when expected"); + + // Ensure that the style is set correctly whichever way highlightNonMatches + // is set. + autocomplete.highlightNonMatches = "true"; + + is(style.color, "rgb(255, 0, 0)", + "nomatch and highlightNonMatches - should be red"); + + autocomplete.highlightNonMatches = "false"; + + isnot(style.color, "rgb(255, 0, 0)", + "nomatch and not highlightNonMatches - should not be red"); + + setTimeout(function() { + // Unregister the factory so that we don't get in the way of other tests + componentManager.unregisterFactory(autoCompleteSimpleID, autoCompleteSimple); + SimpleTest.finish(); + }, 0); + } +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete3.xhtml b/toolkit/content/tests/chrome/test_autocomplete3.xhtml new file mode 100644 index 0000000000..a1b9ef84ea --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete3.xhtml @@ -0,0 +1,200 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Autocomplete Widget Test 3" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<html:input id="autocomplete" + is="autocomplete-input" + autocompletesearch="simple"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +// Set to indicate whether or not we want autoCompleteSimple to return a result +var returnResult = true; + +const ACR = Ci.nsIAutoCompleteResult; + +// This result can't be constructed in-line, because otherwise we leak memory. +function nsAutoCompleteSimpleResult(aString) +{ + this.searchString = aString; + if (returnResult) { + this.searchResult = ACR.RESULT_SUCCESS; + this.matchCount = 1; + this._param = "Result"; + } +} + +nsAutoCompleteSimpleResult.prototype = { + _param: "", + searchString: null, + searchResult: ACR.RESULT_FAILURE, + defaultIndex: 0, + errorDescription: null, + matchCount: 0, + getValueAt() { return this._param; }, + getCommentAt() { return null; }, + getStyleAt() { return null; }, + getImageAt() { return null; }, + getFinalCompleteValueAt() { return this.getValueAt(); }, + getLabelAt() { return null; }, + removeValueAt() {} +}; + +// A basic autocomplete implementation that either returns one result or none +var autoCompleteSimpleID = Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"); +var autoCompleteSimpleName = "@mozilla.org/autocomplete/search;1?name=simple" +var autoCompleteSimple = { + QueryInterface: ChromeUtils.generateQI(["nsIFactory", "nsIAutoCompleteSearch"]), + + createInstance(iid) { + return this.QueryInterface(iid); + }, + + startSearch(aString, aParam, aResult, aListener) { + var result = new nsAutoCompleteSimpleResult(aString); + aListener.onSearchResult(this, result); + }, + + stopSearch() {} +}; + +var componentManager = Components.manager + .QueryInterface(Ci.nsIComponentRegistrar); +componentManager.registerFactory(autoCompleteSimpleID, "Test Simple Autocomplete", + autoCompleteSimpleName, autoCompleteSimple); + +let element = document.getElementById("autocomplete"); + +// Create stub to intercept `onSearchComplete` event. +element.onSearchComplete = function(original) { + return function() { + original.apply(this, arguments); + checkResult(); + }; +}(element.onSearchComplete); + +// Test Bug 325842 - completeDefaultIndex + +SimpleTest.waitForExplicitFinish(); +setTimeout(startTest, 0); + +var currentTest = 0; + +// Note the entries for these tests (key) are incremental. +const tests = [ + { completeDefaultIndex: "false", key: "r", result: "r", + start: 1, end: 1 }, + { completeDefaultIndex: "true", key: "e", result: "result", + start: 2, end: 6 }, + { completeDefaultIndex: "true", key: "t", result: "ret >> Result", + start: 3, end: 13 } +]; + +function startTest() { + var autocomplete = $("autocomplete"); + + // These should not be set by default. + is(autocomplete.hasAttribute("completedefaultindex"), false, + "completedefaultindex not set by default"); + + autocomplete.completeDefaultIndex = "true"; + + is(autocomplete.getAttribute("completedefaultindex"), "true", + "completedefaultindex attribute set correctly"); + is(autocomplete.completeDefaultIndex, true, + "autoFill getter returned correctly"); + + autocomplete.completeDefaultIndex = "false"; + + is(autocomplete.getAttribute("completedefaultindex"), "false", + "completedefaultindex attribute set to false correctly"); + is(autocomplete.completeDefaultIndex, false, + "completeDefaultIndex getter returned false correctly"); + + checkNext(); +} + +function checkNext() { + var autocomplete = $("autocomplete"); + + autocomplete.completeDefaultIndex = tests[currentTest].completeDefaultIndex; + autocomplete.focus(); + + synthesizeKey(tests[currentTest].key); +} + +function checkResult() { + var autocomplete = $("autocomplete"); + + is(autocomplete.value, tests[currentTest].result, + "Test " + currentTest + ": autocomplete.value should equal '" + + tests[currentTest].result + "'"); + + is(autocomplete.selectionStart, tests[currentTest].start, + "Test " + currentTest + ": autocomplete selection should start at " + + tests[currentTest].start); + + is(autocomplete.selectionEnd, tests[currentTest].end, + "Test " + currentTest + ": autocomplete selection should end at " + + tests[currentTest].end); + + ++currentTest; + + if (currentTest < tests.length) { + setTimeout(checkNext, 0); + } else { + // TODO (bug 494809): Autocomplete-in-the-middle should take in count RTL + // and complete on KEY_ArrowRight or KEY_ArrowLeft based on that. It should also revert + // what user has typed to far if he moves in the opposite direction. + if (!autocomplete.value.includes(">>")) { + // Test result if user accepts autocomplete suggestion. + synthesizeKey("KEY_ArrowRight"); + is( + autocomplete.value, + "Result", + "Test complete: autocomplete.value should equal 'Result'" + ); + is( + autocomplete.selectionStart, + 6, + "Test complete: autocomplete selection should start at 6" + ); + is( + autocomplete.selectionEnd, + 6, + "Test complete: autocomplete selection should end at 6" + ); + } + + setTimeout(function () { + // Unregister the factory so that we don't get in the way of other tests + componentManager.unregisterFactory( + autoCompleteSimpleID, + autoCompleteSimple + ); + SimpleTest.finish(); + }, 0); + } +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete4.xhtml b/toolkit/content/tests/chrome/test_autocomplete4.xhtml new file mode 100644 index 0000000000..bb16194e55 --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete4.xhtml @@ -0,0 +1,280 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Autocomplete Widget Test 4" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<html:input id="autocomplete" + is="autocomplete-input" + completedefaultindex="true" + autocompletesearch="simple"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +// Set to indicate whether or not we want autoCompleteSimple to return a result +var returnResult = true; + +const IS_MAC = navigator.platform.includes("Mac"); + +const ACR = Ci.nsIAutoCompleteResult; + +// This result can't be constructed in-line, because otherwise we leak memory. +function nsAutoCompleteSimpleResult(aString) +{ + this.searchString = aString; + if (returnResult) { + this.searchResult = ACR.RESULT_SUCCESS; + this.matchCount = 1; + this._param = "Result"; + } +} + +nsAutoCompleteSimpleResult.prototype = { + _param: "", + searchString: null, + searchResult: ACR.RESULT_FAILURE, + defaultIndex: 0, + errorDescription: null, + matchCount: 0, + getValueAt() { return this._param; }, + getCommentAt() { return null; }, + getStyleAt() { return null; }, + getImageAt() { return null; }, + getFinalCompleteValueAt() { return this.getValueAt(); }, + getLabelAt() { return null; }, + removeValueAt() {} +}; + +// A basic autocomplete implementation that either returns one result or none +var autoCompleteSimpleID = Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"); +var autoCompleteSimpleName = "@mozilla.org/autocomplete/search;1?name=simple" +var autoCompleteSimple = { + QueryInterface: ChromeUtils.generateQI(["nsIFactory", "nsIAutoCompleteSearch"]), + + createInstance(iid) { + return this.QueryInterface(iid); + }, + + startSearch(aString, aParam, aResult, aListener) { + var result = new nsAutoCompleteSimpleResult(aString); + aListener.onSearchResult(this, result); + }, + + stopSearch() {} +}; + +var componentManager = Components.manager + .QueryInterface(Ci.nsIComponentRegistrar); +componentManager.registerFactory(autoCompleteSimpleID, "Test Simple Autocomplete", + autoCompleteSimpleName, autoCompleteSimple); + +let element = document.getElementById("autocomplete"); + +// Create stub to intercept `onSearchComplete` event. +element.onSearchComplete = function(original) { + return function() { + original.apply(this, arguments); + searchComplete(); + }; +}(element.onSearchComplete); + +// Test Bug 325842 - completeDefaultIndex + +SimpleTest.waitForExplicitFinish(); + +setTimeout(nextTest, 0); + +var currentTest = null; + +// Note the entries for these tests (key) are incremental. +const tests = [ + { + desc: "HOME key remove selection", + key: "KEY_Home", + removeSelection: true, + result: "re", + start: 0, end: 0 + }, + { + desc: "LEFT key remove selection", + key: "KEY_ArrowLeft", + removeSelection: true, + result: "re", + start: 1, end: 1 + }, + { desc: "RIGHT key remove selection", + key: "KEY_ArrowRight", + removeSelection: true, + result: "re", + start: 2, end: 2 + }, + { desc: "ENTER key remove selection", + key: "KEY_Enter", + removeSelection: true, + result: "re", + start: 2, end: 2 + }, + { + desc: "HOME key", + key: "KEY_Home", + removeSelection: false, + result: "Result", + start: 0, end: 0 + }, + { + desc: "LEFT key", + key: "KEY_ArrowLeft", + removeSelection: false, + result: "Result", + start: 5, end: 5 + }, + { desc: "RIGHT key", + key: "KEY_ArrowRight", + removeSelection: false, + result: "Result", + start: 6, end: 6 + }, + { desc: "RETURN key", + key: "KEY_Enter", + removeSelection: false, + result: "Result", + start: 6, end: 6 + }, + { desc: "TAB key should confirm suggestion when forcecomplete is set", + key: "KEY_Tab", + removeSelection: false, + forceComplete: true, + result: "Result", + start: 6, end: 6 + }, + + { desc: "RIGHT key complete from middle", + key: "KEY_ArrowRight", + forceComplete: true, + completeFromMiddle: true, + result: "Result", + start: 6, end: 6 + }, + { + desc: "RIGHT key w/ minResultsForPopup=2", + key: "KEY_ArrowRight", + removeSelection: false, + minResultsForPopup: 2, + result: "Result", + start: 6, end: 6 + }, +]; + +function nextTest() { + if (!tests.length) { + // No more tests to run, finish. + setTimeout(function() { + // Unregister the factory so that we don't get in the way of other tests + componentManager.unregisterFactory(autoCompleteSimpleID, autoCompleteSimple); + SimpleTest.finish(); + }, 0); + return; + } + + var autocomplete = $("autocomplete"); + autocomplete.value = ""; + currentTest = tests.shift(); + + // HOME key works differently on Mac, so we skip tests using it. + if (currentTest.key == "KEY_Home" && IS_MAC) + nextTest(); + else + setTimeout(runCurrentTest, 0); +} + +function runCurrentTest() { + var autocomplete = $("autocomplete"); + if ("minResultsForPopup" in currentTest) + autocomplete.setAttribute("minresultsforpopup", currentTest.minResultsForPopup) + else + autocomplete.removeAttribute("minresultsforpopup"); + + autocomplete.focus(); + + if (!currentTest.completeFromMiddle) { + sendString("re"); + } + else { + sendString("lt"); + } +} + +function searchComplete() { + var autocomplete = $("autocomplete"); + autocomplete.setAttribute("forcecomplete", currentTest.forceComplete); + + if (currentTest.completeFromMiddle) { + if (!currentTest.forceComplete) { + synthesizeKey(currentTest.key); + } + else if (!/ >> /.test(autocomplete.value)) { + // At this point we should have a value like "lt >> Result" showing. + throw new Error("Expected an middle-completed value, got " + autocomplete.value); + } + + // For forceComplete a blur should cause a value from the results to get + // completed to. E.g. "lt >> Result" will turn into "Result". + if (currentTest.forceComplete) + autocomplete.blur(); + + checkResult(); + return; + } + + is(autocomplete.value, "result", + "Test '" + currentTest.desc + "': autocomplete.value should equal 'result'"); + + if (autocomplete.selectionStart == 2) { // Finished inserting "re" string. + if (currentTest.removeSelection) { + // remove current selection + synthesizeKey("KEY_Delete"); + } + + synthesizeKey(currentTest.key); + + checkResult(); + } +} + +function checkResult() { + var autocomplete = $("autocomplete"); + + is(autocomplete.value, currentTest.result, + "Test '" + currentTest.desc + "': autocomplete.value should equal '" + + currentTest.result + "'"); + + is(autocomplete.selectionStart, currentTest.start, + "Test '" + currentTest.desc + "': autocomplete selection should start at " + + currentTest.start); + + is(autocomplete.selectionEnd, currentTest.end, + "Test '" + currentTest.desc + "': autocomplete selection should end at " + + currentTest.end); + + setTimeout(nextTest, 0); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete5.xhtml b/toolkit/content/tests/chrome/test_autocomplete5.xhtml new file mode 100644 index 0000000000..7c252f355e --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete5.xhtml @@ -0,0 +1,164 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Autocomplete Widget Test 5" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<html:input id="autocomplete" + is="autocomplete-input" + autocompletesearch="simple" + notifylegacyevents="true"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +const ACR = Ci.nsIAutoCompleteResult; + +// This result can't be constructed in-line, because otherwise we leak memory. +function nsAutoCompleteSimpleResult(aString) +{ + this.searchString = aString; + this.searchResult = ACR.RESULT_SUCCESS; + this.matchCount = 1; + this._param = "SUCCESS"; +} + +nsAutoCompleteSimpleResult.prototype = { + _param: "", + searchString: null, + searchResult: ACR.RESULT_FAILURE, + defaultIndex: -1, + errorDescription: null, + matchCount: 0, + getValueAt() { return this._param; }, + getCommentAt() { return null; }, + getStyleAt() { return null; }, + getImageAt() { return null; }, + getFinalCompleteValueAt() { return this.getValueAt(); }, + getLabelAt() { return null; }, + removeValueAt() {} +}; + +// A basic autocomplete implementation that either returns one result or none +var autoCompleteSimpleID = Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"); +var autoCompleteSimpleName = "@mozilla.org/autocomplete/search;1?name=simple" +var autoCompleteSimple = { + QueryInterface: ChromeUtils.generateQI(["nsIFactory", "nsIAutoCompleteSearch"]), + + createInstance(iid) { + return this.QueryInterface(iid); + }, + + startSearch(aString, aParam, aResult, aListener) { + var result = new nsAutoCompleteSimpleResult(aString); + aListener.onSearchResult(this, result); + }, + + stopSearch() {} +}; + + +let element = document.getElementById("autocomplete"); + +// Create stub to intercept `onSearchBegin` event. +element.onSearchBegin = function(original) { + return function() { + original.apply(this, arguments); + checkSearchBegin(); + }; +}(element.onSearchBegin); + +// Create stub to intercept `onSearchComplete` event. +element.onSearchComplete = function(original) { + return function() { + original.apply(this, arguments); + checkSearchCompleted(); + }; +}(element.onSearchComplete); + +element.addEventListener("textEntered", checkTextEntered); +element.addEventListener("textReverted", checkTextReverted); + +var componentManager = Components.manager + .QueryInterface(Ci.nsIComponentRegistrar); +componentManager.registerFactory(autoCompleteSimpleID, "Test Simple Autocomplete", + autoCompleteSimpleName, autoCompleteSimple); + +SimpleTest.waitForExplicitFinish(); +setTimeout(startTest, 0); + +function startTest() { + let autocomplete = $("autocomplete"); + + // blur the field to ensure that the popup is closed and that the previous + // search has stopped, then start a new search. + autocomplete.blur(); + autocomplete.focus(); + sendString("r"); +} + +let hasTextEntered = false; +let hasSearchBegun = false; + +function checkSearchBegin() { + hasSearchBegun = true; +} + +let test = 0; +function checkSearchCompleted() { + is(hasSearchBegun, true, "onsearchbegin handler has been correctly called."); + + if (test == 0) { + hasSearchBegun = false; + synthesizeKey("KEY_Enter"); + } else if (test == 1) { + hasSearchBegun = false; + synthesizeKey("KEY_Escape"); + } else { + throw new Error("checkSearchCompleted should only be called twice."); + } +} + +function checkTextEntered() { + is(test, 0, "checkTextEntered should be reached from first test."); + is(hasSearchBegun, false, "onsearchbegin handler should not be called on text revert."); + + // fire second test + test++; + + let autocomplete = $("autocomplete"); + autocomplete.textValue = ""; + autocomplete.blur(); + autocomplete.focus(); + sendString("r"); +} + +function checkTextReverted() { + is(test, 1, "checkTextReverted should be the second test reached."); + is(hasSearchBegun, false, "onsearchbegin handler should not be called on text revert."); + + setTimeout(function() { + // Unregister the factory so that we don't get in the way of other tests + componentManager.unregisterFactory(autoCompleteSimpleID, autoCompleteSimple); + SimpleTest.finish(); + }, 0); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete_emphasis.xhtml b/toolkit/content/tests/chrome/test_autocomplete_emphasis.xhtml new file mode 100644 index 0000000000..20eb96323f --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete_emphasis.xhtml @@ -0,0 +1,180 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Autocomplete emphasis test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<html:input id="richautocomplete" + is="autocomplete-input" + autocompletesearch="simple" + autocompletepopup="richpopup"/> +<panel is="autocomplete-richlistbox-popup" + id="richpopup" + type="autocomplete-richlistbox" + noautofocus="true"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +const ACR = Ci.nsIAutoCompleteResult; + +// A global variable to hold the search result for the current search. +var resultText = ""; + +// This result can't be constructed in-line, because otherwise we leak memory. +function nsAutoCompleteSimpleResult(aString) +{ + this.searchString = aString; + this.searchResult = ACR.RESULT_SUCCESS; + this.matchCount = 1; +} + +nsAutoCompleteSimpleResult.prototype = { + searchString: null, + searchResult: ACR.RESULT_FAILURE, + defaultIndex: -1, + errorDescription: null, + matchCount: 0, + getValueAt() { return resultText; }, + getCommentAt() { return this.getValueAt(); }, + getStyleAt() { return null; }, + getImageAt() { return null; }, + getFinalCompleteValueAt() { return this.getValueAt(); }, + getLabelAt() { return this.getValueAt(); }, + removeValueAt() {} +}; + +// A basic autocomplete implementation that returns the string contained in 'resultText'. +var autoCompleteSimpleID = Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"); +var autoCompleteSimpleName = "@mozilla.org/autocomplete/search;1?name=simple" +var autoCompleteSimple = { + QueryInterface: ChromeUtils.generateQI(["nsIFactory", "nsIAutoCompleteSearch"]), + + createInstance(iid) { + return this.QueryInterface(iid); + }, + + startSearch(aString, aParam, aResult, aListener) { + var result = new nsAutoCompleteSimpleResult(aString); + aListener.onSearchResult(this, result); + }, + + stopSearch() {} +}; + +var componentManager = Components.manager + .QueryInterface(Ci.nsIComponentRegistrar); +componentManager.registerFactory(autoCompleteSimpleID, "Test Simple Autocomplete", + autoCompleteSimpleName, autoCompleteSimple); + +var element = document.getElementById("richautocomplete"); + +// Create stub to intercept `onSearchComplete` event. +element.onSearchComplete = function(original) { + return function() { + original.apply(this, arguments); + checkSearchCompleted(); + }; +}(element.onSearchComplete); + +SimpleTest.waitForExplicitFinish(); +setTimeout(nextTest, 0); + +/* Test cases have the following attributes: + * - search: A search string, to be emphasized in the result. + * - result: A fixed result string, so we can hardcode the expected emphasis. + * - emphasis: A list of chunks that should be emphasized or not, in strict alternation. + * - emphasizeFirst: Whether the first element of 'emphasis' should be emphasized; + * The emphasis of the other elements is defined by the strict alternation rule. + */ +let testcases = [ + { search: "test", + result: "A test string", + emphasis: ["A ", "test", " string"], + emphasizeFirst: false + }, + { search: "tea two", + result: "Tea for two, and two for tea...", + emphasis: ["Tea", " for ", "two", ", and ", "two", " for ", "tea", "..."], + emphasizeFirst: true + }, + { search: "tat", + result: "tatatat", + emphasis: ["tatatat"], + emphasizeFirst: true + }, + { search: "cheval valise", + result: "chevalise", + emphasis: ["chevalise"], + emphasizeFirst: true + } +]; +let test = -1; +let currentTest = null; + +function nextTest() { + test++; + + if (test >= testcases.length) { + // Unregister the factory so that we don't get in the way of other tests + componentManager.unregisterFactory(autoCompleteSimpleID, autoCompleteSimple); + SimpleTest.finish(); + return; + } + + // blur the field to ensure that the popup is closed and that the previous + // search has stopped, then start a new search. + let autocomplete = $("richautocomplete"); + autocomplete.blur(); + autocomplete.focus(); + + currentTest = testcases[test]; + resultText = currentTest.result; + autocomplete.value = currentTest.search; + synthesizeKey("KEY_ArrowDown"); +} + +function checkSearchCompleted() { + let autocomplete = $("richautocomplete"); + let result = autocomplete.popup.richlistbox.firstChild; + + for (let attribute of [result._titleText, result._urlText]) { + is(attribute.childNodes.length, currentTest.emphasis.length, + "The element should have the expected number of children."); + for (let i = 0; i < currentTest.emphasis.length; i++) { + let node = attribute.childNodes[i]; + // Emphasized parts strictly alternate. + if ((i % 2 == 0) == currentTest.emphasizeFirst) { + // Check that this part is correctly emphasized. + is(node.nodeName, "span", ". That child should be a span node"); + ok(node.classList.contains("ac-emphasize-text"), ". That child should be emphasized"); + is(node.textContent, currentTest.emphasis[i], ". That emphasis should be as expected."); + } else { + // Check that this part is _not_ emphasized. + is(node.nodeName, "#text", ". That child should be a text node"); + is(node.textContent, currentTest.emphasis[i], ". That text should be as expected."); + } + } + } + + setTimeout(nextTest, 0); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete_mac_caret.xhtml b/toolkit/content/tests/chrome/test_autocomplete_mac_caret.xhtml new file mode 100644 index 0000000000..b49f8a1d5e --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete_mac_caret.xhtml @@ -0,0 +1,80 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Autocomplete Widget Test" + onload="setTimeout(keyCaretTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<html:input id="autocomplete" is="autocomplete-input"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function keyCaretTest() +{ + var autocomplete = $("autocomplete"); + + autocomplete.focus(); + checkKeyCaretTest("KEY_ArrowUp", 0, 0, true, "no value up"); + checkKeyCaretTest("KEY_ArrowDown", 0, 0, true, "no value down"); + + autocomplete.value = "Sample"; + + autocomplete.selectionStart = 3; + autocomplete.selectionEnd = 3; + checkKeyCaretTest("KEY_ArrowUp", 0, 0, true, "value up with caret in middle"); + checkKeyCaretTest("KEY_ArrowUp", 0, 0, true, "value up with caret in middle again"); + + autocomplete.selectionStart = 2; + autocomplete.selectionEnd = 2; + checkKeyCaretTest("KEY_ArrowDown", 6, 6, true, "value down with caret in middle"); + checkKeyCaretTest("KEY_ArrowDown", 6, 6, true, "value down with caret in middle again"); + + autocomplete.selectionStart = 1; + autocomplete.selectionEnd = 4; + checkKeyCaretTest("KEY_ArrowUp", 0, 0, true, "value up with selection"); + + autocomplete.selectionStart = 1; + autocomplete.selectionEnd = 4; + checkKeyCaretTest("KEY_ArrowDown", 6, 6, true, "value down with selection"); + + SimpleTest.finish(); +} + +function checkKeyCaretTest(key, expectedStart, expectedEnd, result, testid) +{ + var autocomplete = $("autocomplete"); + var keypressFired = false; + function listener(event) { + if (event.target == autocomplete) { + keypressFired = true; + } + } + SpecialPowers.addSystemEventListener(window, "keypress", listener, false); + synthesizeKey(key, {}); + SpecialPowers.removeSystemEventListener(window, "keypress", listener, false); + is(keypressFired, result, `${testid} keypress event should${result ? "" : " not"} be fired`); + is(autocomplete.selectionStart, expectedStart, testid + " selectionStart"); + is(autocomplete.selectionEnd, expectedEnd, testid + " selectionEnd"); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete_placehold_last_complete.xhtml b/toolkit/content/tests/chrome/test_autocomplete_placehold_last_complete.xhtml new file mode 100644 index 0000000000..33fca83c32 --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete_placehold_last_complete.xhtml @@ -0,0 +1,303 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Autocomplete Widget Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + onload="runTest();"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" + src="chrome://global/content/globalOverlay.js"/> + +<html:input id="autocomplete" + is="autocomplete-input" + completedefaultindex="true" + timeout="0" + autocompletesearch="simple"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +function autoCompleteSimpleResult(aString, searchId) { + this.searchString = aString; + this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS; + this.matchCount = 1; + if (aString.startsWith('ret')) { + this._param = autoCompleteSimpleResult.retireCompletion; + } else { + this._param = "Result"; + } + this._searchId = searchId; +} +autoCompleteSimpleResult.retireCompletion = "Retire"; +autoCompleteSimpleResult.prototype = { + _param: "", + searchString: null, + searchResult: Ci.nsIAutoCompleteResult.RESULT_FAILURE, + defaultIndex: 0, + errorDescription: null, + matchCount: 0, + getValueAt() { return this._param; }, + getCommentAt() { return null; }, + getStyleAt() { return null; }, + getImageAt() { return null; }, + getLabelAt() { return null; }, + removeValueAt() {} +}; + +var searchCounter = 0; + +// A basic autocomplete implementation that returns one result. +let autoCompleteSimple = { + classID: Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"), + contractID: "@mozilla.org/autocomplete/search;1?name=simple", + searchAsync: false, + pendingSearch: null, + + QueryInterface: ChromeUtils.generateQI([ + "nsIFactory", + "nsIAutoCompleteSearch" + ]), + createInstance(iid) { + return this.QueryInterface(iid); + }, + + registerFactory() { + let registrar = + Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + registrar.registerFactory(this.classID, "Test Simple Autocomplete", + this.contractID, this); + }, + unregisterFactory() { + let registrar = + Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + registrar.unregisterFactory(this.classID, this); + }, + + startSearch(aString, aParam, aResult, aListener) { + let result = new autoCompleteSimpleResult(aString); + + if (this.searchAsync) { + // Simulate an async search by using a timeout before invoking the + // |onSearchResult| callback. + // Store the searchTimeout such that it can be canceled if stopSearch is called. + this.pendingSearch = setTimeout(() => { + this.pendingSearch = null; + + aListener.onSearchResult(this, result); + + // Move to the next step in the async test. + asyncTest.next(); + }, 0); + } else { + aListener.onSearchResult(this, result); + } + }, + stopSearch() { + clearTimeout(this.pendingSearch); + } +}; + +SimpleTest.waitForExplicitFinish(); + +let gACTimer; +let gAutoComplete; +let asyncTest; + +let searchCompleteTimeoutId = null; + +function finishTest() { + // Unregister the factory so that we don't get in the way of other tests + autoCompleteSimple.unregisterFactory(); + SimpleTest.finish(); +} + +function runTest() { + autoCompleteSimple.registerFactory(); + gAutoComplete = $("autocomplete"); + gAutoComplete.focus(); + + // Return the search results synchronous, which also makes the completion + // happen synchronous. + autoCompleteSimple.searchAsync = false; + + sendString("r"); + is(gAutoComplete.value, "result", "Value should be autocompleted immediately"); + + sendString("e"); + is(gAutoComplete.value, "result", "Value should be autocompleted immediately"); + + synthesizeKey("KEY_Delete"); + is(gAutoComplete.value, "re", "Deletion should not complete value"); + + synthesizeKey("KEY_Backspace"); + is(gAutoComplete.value, "r", "Backspace should not complete value"); + + synthesizeKey("KEY_ArrowLeft"); + is(gAutoComplete.value, "r", "Value should stay same when navigating with cursor"); + + runAsyncTest(); +} + +function* asyncTestGenerator() { + sendString("re"); + is(gAutoComplete.value, "re", "Value should not be autocompleted immediately"); + + // Calling |yield undefined| makes this generator function wait until + // |asyncTest.next();| is called. This happens from within the + // |autoCompleteSimple.startSearch()| function once the simulated async + // search has finished. + // Therefore, the effect of the |yield undefined;| here (and the ones) below + // is to wait until the async search result comes back. + yield undefined; + + is(gAutoComplete.value, "result", "Value should be autocompleted"); + + // Test if typing the `s` character completes directly based on the last + // completion + sendString("s"); + is(gAutoComplete.value, "result", "Value should be completed immediately"); + + yield undefined; + + is(gAutoComplete.value, "result", "Value should be autocompleted to same value"); + synthesizeKey("KEY_Delete"); + is(gAutoComplete.value, "res", "Deletion should not complete value"); + + // No |yield undefined| needed here as no completion is triggered by the deletion. + + is(gAutoComplete.value, "res", "Still no complete value after deletion"); + + synthesizeKey("KEY_Backspace"); + is(gAutoComplete.value, "re", "Backspace should not complete value"); + + yield undefined; + + is(gAutoComplete.value, "re", "Value after search due to backspace should stay the same"); (3) + + // Typing a character that is not like the previous match. In this case, the + // completion cannot happen directly and therefore the value will be completed + // only after the search has finished. + sendString("t"); + is(gAutoComplete.value, "ret", "Value should not be autocompleted immediately"); + + yield undefined; + + is(gAutoComplete.value, "retire", "Value should be autocompleted"); + + sendString("i"); + is(gAutoComplete.value, "retire", "Value should be autocompleted immediately"); + + yield undefined; + + is(gAutoComplete.value, "retire", "Value should be autocompleted to the same value"); + + // Setup the scene to test how the completion behaves once the placeholder + // completion and the result from the search do not agree with each other. + gAutoComplete.value = 'r'; + // Need to type two characters as the input was reset and the autocomplete + // controller things, ther user hit the backspace button, in which case + // no completion is performed. But as a completion is desired, another + // character `t` is typed afterwards. + sendString("e"); + yield undefined; + sendString("t"); + is(gAutoComplete.value, "ret", "Value should not be autocompleted"); + + yield undefined; + + is(gAutoComplete.value, "retire", "Value should be autocompleted"); + + // The placeholder string is now set to "retire". Changing the completion + // string to "retirement" and see what the completion will turn out like. + autoCompleteSimpleResult.retireCompletion = "Retirement"; + sendString("i"); + is(gAutoComplete.value, "retire", "Value should be autocompleted based on placeholder"); + + yield undefined; + + is(gAutoComplete.value, "retirement", "Value should be autocompleted based on search result"); + + // Change the search result to `Retire` again and see if the new result is + // complited. + autoCompleteSimpleResult.retireCompletion = "Retire"; + sendString("r"); + is(gAutoComplete.value, "retirement", "Value should be autocompleted based on placeholder"); + + yield undefined; + + is(gAutoComplete.value, "retire", "Value should be autocompleted based on search result"); + + // Complete the value + gAutoComplete.value = 're'; + sendString("t"); + yield undefined; + sendString("i"); + is(gAutoComplete.value, "reti", "Value should not be autocompleted"); + + yield undefined; + + is(gAutoComplete.value, "retire", "Value should be autocompleted"); + + // Remove the selected text "re" (1) and the "et" (2). Afterwards, add it again (3). + // This should not cause the completion to kick in. + synthesizeKey("KEY_Delete"); // (1) + + is(gAutoComplete.value, "reti", "Value should not complete after deletion"); + + gAutoComplete.selectionStart = 1; + gAutoComplete.selectionEnd = 3; + synthesizeKey("KEY_Delete"); // (2) + + is(gAutoComplete.value, "ri", "Value should stay unchanged after removing character in the middle"); + + yield undefined; + + sendString("e"); // (3.1) + is(gAutoComplete.value, "rei", "Inserting a character in the middle should not complete the value"); + + yield undefined; + + sendString("t"); // (3.2) + is(gAutoComplete.value, "reti", "Inserting a character in the middle should not complete the value"); + + yield undefined; + + // Adding a new character at the end should not cause the completion to happen again + // as the completion failed before. + gAutoComplete.selectionStart = 4; + gAutoComplete.selectionEnd = 4; + sendString("r"); + is(gAutoComplete.value, "retir", "Value should not be autocompleted immediately"); + + yield undefined; + + is(gAutoComplete.value, "retire", "Value should be autocompleted"); + + finishTest(); + yield undefined; +} + +function runAsyncTest() { + gAutoComplete.value = ''; + autoCompleteSimple.searchAsync = true; + + asyncTest = asyncTestGenerator(); + asyncTest.next(); +} +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete_with_composition_on_input.html b/toolkit/content/tests/chrome/test_autocomplete_with_composition_on_input.html new file mode 100644 index 0000000000..fbcd44e830 --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete_with_composition_on_input.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>autocomplete with composition tests on HTML input element</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="file_autocomplete_with_composition.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <iframe id="formTarget" name="formTarget"></iframe> + <form action="data:text/html," target="formTarget"> + <input name="test" id="input"><input type="submit"> + </form> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +function runTests() { + var formFillController = + SpecialPowers.getFormFillController() + .QueryInterface(Ci.nsIAutoCompleteInput); + var originalFormFillTimeout = formFillController.timeout; + + SpecialPowers.attachFormFillControllerTo(window); + var target = document.getElementById("input"); + + // Register a word to the form history. + let chromeScript = SpecialPowers.loadChromeScript(function addEntry() { + /* eslint-env mozilla/chrome-script */ + let {FormHistory} = ChromeUtils.importESModule( + "resource://gre/modules/FormHistory.sys.mjs" + ); + FormHistory.update({ op: "add", fieldname: "test", value: "Mozilla" }); + }); + chromeScript.destroy(); + target.focus(); + + new nsDoTestsForAutoCompleteWithComposition( + "Testing on HTML input (asynchronously search)", + window, target, formFillController.controller, is, + function() { return target.value; }, + function() { + target.setAttribute("timeout", 0); + new nsDoTestsForAutoCompleteWithComposition( + "Testing on HTML input (synchronously search)", + window, target, formFillController.controller, is, + function() { return target.value; }, + function() { + formFillController.timeout = originalFormFillTimeout; + SpecialPowers.detachFormFillControllerFrom(window); + SimpleTest.finish(); + }); + }); +} + +SimpleTest.waitForFocus(runTests); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/chrome/test_browser_drop.xhtml b/toolkit/content/tests/chrome/test_browser_drop.xhtml new file mode 100644 index 0000000000..3b0f0fdb2b --- /dev/null +++ b/toolkit/content/tests/chrome/test_browser_drop.xhtml @@ -0,0 +1,35 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Browser Drop Test" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script><![CDATA[ +SimpleTest.waitForExplicitFinish(); +function runTest() { + add_task(async function() { + let win = window.browsingContext.topChromeWindow.openDialog("window_browser_drop.xhtml", "_blank", "chrome,width=200,height=200", window); + await SimpleTest.promiseFocus(win); + for (let browserType of ["content", "remote-content"]) { + await win.dropLinksOnBrowser(win.document.getElementById(browserType + "child"), browserType); + } + await win.dropLinksOnBrowser(win.document.getElementById("chromechild"), "chrome"); + }); +} +//]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug1048178.xhtml b/toolkit/content/tests/chrome/test_bug1048178.xhtml new file mode 100644 index 0000000000..d9a34c1da3 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug1048178.xhtml @@ -0,0 +1,83 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1048178 +--> +<window title="Mozilla Bug 1048178" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"/> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1048178" + target="_blank">Mozilla Bug 1048178</a> + + <hbox> + <scrollbar id="scroller" + orient="horizontal" + curpos="0" + maxpos="500" + pageincrement="500" + style="width: 500px; margin:0"/> + </hbox> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +/** Test for Bug 1048178 **/ +var scrollbarTester = { + scrollbar: null, + startTest() { + this.scrollbar = $("scroller"); + this.setScrollToClick(false); + this.testThumbDragging(); + SimpleTest.finish(); + }, + testThumbDragging() { + var x = 400; // on the right half of the scroolbar + var y = 5; + + this.mousedown(x, y, 0); + this.mousedown(x, y, 2); + this.mouseup(x, y, 2); + this.mouseup(x, y, 0); + + var newPos = this.getPos(); // should be '500' + + this.mousedown(x, y, 0); + this.mousemove(x-1, y, 0); + this.mouseup(x-1, y, 0); + + var newPos2 = this.getPos(); + ok(newPos2 < newPos, + "Scrollbar thumb should follow the mouse when dragged."); + }, + setScrollToClick(value) { + SpecialPowers.Services.prefs.getBranch("ui.") + .setIntPref("scrollToClick", value ? 1 : 0); + }, + getPos() { + return this.scrollbar.getAttribute("curpos"); + }, + mousedown(x, y, button) { + synthesizeMouse(this.scrollbar, x, y, { type: "mousedown", 'button': button }); + }, + mousemove(x, y, button) { + synthesizeMouse(this.scrollbar, x, y, { type: "mousemove", 'button': button }); + }, + mouseup(x, y, button) { + synthesizeMouse(this.scrollbar, x, y, { type: "mouseup", 'button': button }); + } +} + +function doTest() { + setTimeout(function() { scrollbarTester.startTest(); }, 0); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(doTest); + +]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_bug263683.xhtml b/toolkit/content/tests/chrome/test_bug263683.xhtml new file mode 100644 index 0000000000..64c7df08ac --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug263683.xhtml @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=263683 +--> +<window title="Mozilla Bug 263683" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=263683"> + Mozilla Bug 263683 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <script class="testbody" type="application/javascript"> + <![CDATA[ + + /** Test for Bug 263683 **/ + SimpleTest.waitForExplicitFinish(); + window.openDialog("bug263683_window.xhtml", "263683test", + "chrome,width=600,height=600,noopener", window); + + ]]> + </script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug304188.xhtml b/toolkit/content/tests/chrome/test_bug304188.xhtml new file mode 100644 index 0000000000..1047528d20 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug304188.xhtml @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=304188 +--> +<window title="Mozilla Bug 304188" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=304188">Mozilla Bug 304188</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 304188 **/ +SimpleTest.waitForExplicitFinish(); +window.openDialog("bug304188_window.xhtml", "findbartest", + "chrome,width=600,height=600,noopener", window); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug331215.xhtml b/toolkit/content/tests/chrome/test_bug331215.xhtml new file mode 100644 index 0000000000..22560089c1 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug331215.xhtml @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=331215 +--> +<window title="Mozilla Bug 331215" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=331215">Mozilla Bug 331215</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 331215 **/ + +SimpleTest.waitForExplicitFinish(); +window.openDialog("bug331215_window.xhtml", "331215test", + "chrome,width=600,height=600,noopener", window); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug360220.xhtml b/toolkit/content/tests/chrome/test_bug360220.xhtml new file mode 100644 index 0000000000..a942e0acd6 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug360220.xhtml @@ -0,0 +1,61 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=360220 +--> +<window title="Mozilla Bug 360220" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=360220">Mozilla Bug 360220</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<menulist id="menulist"> + <menupopup> + <menuitem id="firstItem" label="foo" selected="true"/> + <menuitem id="secondItem" label="bar"/> + </menupopup> +</menulist> +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +/** Test for Bug 360220 **/ + +var menulist = document.getElementById("menulist"); +var secondItem = document.getElementById("secondItem"); +menulist.selectedItem = secondItem; + +is(menulist.label, "bar", "second item was not selected"); + +let mutObserver = new MutationObserver(() => { + is(menulist.label, "new label", "menulist label was not updated to the label of its selected item"); + done(); +}); +mutObserver.observe(menulist, { attributeFilter: ['label'] }); +secondItem.label = "new label"; + +let failureTimeout = setTimeout(function() { + ok(false, "menulist label should have updated"); + done(); +}, 2000); + +function done() { + mutObserver.disconnect(); + clearTimeout(failureTimeout); + SimpleTest.finish(); +} +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug360437.xhtml b/toolkit/content/tests/chrome/test_bug360437.xhtml new file mode 100644 index 0000000000..dc592e4141 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug360437.xhtml @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=360437 +--> +<window title="Mozilla Bug 360437" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=360437">Mozilla Bug 360437</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 360437 **/ +SimpleTest.waitForExplicitFinish(); +window.openDialog("bug360437_window.xhtml", "360437test", + "chrome,width=600,height=600,noopener", window); + + + + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug365773.xhtml b/toolkit/content/tests/chrome/test_bug365773.xhtml new file mode 100644 index 0000000000..e85a590608 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug365773.xhtml @@ -0,0 +1,67 @@ +<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=365773
+-->
+<window title="Mozilla Bug 365773"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=365773">Mozilla Bug 365773</a>
+<p id="display">
+ <radiogroup id="group" collapsed="true" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <radio id="item" label="Item"/>
+ </radiogroup>
+</p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 365773 **/
+
+function selectItem(item, isIndex, testName) {
+ var exception = null;
+ try {
+ if (isIndex)
+ document.getElementById("group").selectedIndex = item;
+ else
+ document.getElementById("group").selectedItem = item;
+ }
+ catch(e) {
+ exception = e;
+ }
+
+ ok(exception == null, testName);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+window.onload = function runTests() {
+ var item = document.getElementById("item");
+
+ selectItem(item, false, "Radio button selected with selectedItem (not focused)");
+ selectItem(null, false, "Radio button deselected with selectedItem (not focused)");
+ selectItem(0, true, "Radio button selected with selectedIndex (not focused)");
+ selectItem(-1, true, "Radio button deselected with selectedIndex (not focused)");
+
+ document.getElementById("group").focus();
+
+ selectItem(item, false, "Radio button selected with selectedItem (focused)");
+ selectItem(null, false, "Radio button deselected with selectedItem (focused)");
+ selectItem(0, true, "Radio button selected with selectedIndex (focused)");
+ selectItem(-1, true, "Radio button deselected with selectedIndex (focused)");
+
+ SimpleTest.finish();
+};
+]]>
+</script>
+
+</window>
diff --git a/toolkit/content/tests/chrome/test_bug366992.xhtml b/toolkit/content/tests/chrome/test_bug366992.xhtml new file mode 100644 index 0000000000..9b178f1abe --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug366992.xhtml @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=366992 +--> +<window title="Mozilla Bug 366992" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=366992">Mozilla Bug 366992</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 366992 **/ +SimpleTest.waitForExplicitFinish(); +window.openDialog("bug366992_window.xhtml", "findbartest", + "chrome,width=600,height=600,noopener", window); + + + + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug382990.xhtml b/toolkit/content/tests/chrome/test_bug382990.xhtml new file mode 100644 index 0000000000..1f937ac1b6 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug382990.xhtml @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=382990 +--> +<window title="Mozilla Bug 382990" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="startThisTest()"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=382990" + target="_blank">Mozilla Bug 382990</a> + </body> + + <tree id="testTree" height="200px"> + <treecols> + <treecol flex="1" label="Name" id="name"/> + </treecols> + <treechildren> + <treeitem><treerow><treecell label="a"/></treerow></treeitem> + <treeitem><treerow><treecell label="z"/></treerow></treeitem> + </treechildren> + </tree> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /** Test for Bug 382990 **/ + + SimpleTest.waitForExplicitFinish(); + function startThisTest() + { + var treeElem = document.getElementById("testTree"); + treeElem.view.selection.select(0); + treeElem.focus(); + synthesizeKey("z", {ctrlKey: true}); + ok(!treeElem.view.selection.isSelected(1), "Tree selection should not change for key events with ctrl pressed."); + SimpleTest.finish(); + } + ]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_bug409624.xhtml b/toolkit/content/tests/chrome/test_bug409624.xhtml new file mode 100644 index 0000000000..6aa5f2ded9 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug409624.xhtml @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=409624 +--> +<window title="Mozilla Bug 409624" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=409624"> + Mozilla Bug 409624 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <script class="testbody" type="application/javascript"> + <![CDATA[ + + /** Test for Bug 409624 **/ + SimpleTest.waitForExplicitFinish(); + window.openDialog("bug409624_window.xhtml", "409624test", + "chrome,width=600,height=600,noopener", window); + + ]]> + </script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug418874.xhtml b/toolkit/content/tests/chrome/test_bug418874.xhtml new file mode 100644 index 0000000000..d91289f11b --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug418874.xhtml @@ -0,0 +1,64 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Textbox with placeholder test" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <hbox> + <html:input id="t1" placeholder="empty"/> + </hbox> + + <hbox> + <html:input id="t2" placeholder="empty"/> + </hbox> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"> + <p id="display"> + </p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + function doTest() { + var t1 = $("t1"); + var t2 = $("t2"); + setTextboxValue(t1, "1"); + is(t1.editor.canUndo, true, + "undo correctly enabled when placeholder was not changed through property"); + + t2.placeholder = "reallyempty"; + setTextboxValue(t2, "2"); + is(t2.editor.canUndo, true, + "undo correctly enabled when placeholder explicitly changed through property"); + + SimpleTest.finish(); + } + + function setTextboxValue(textbox, value) { + textbox.focus(); + sendString(value); + textbox.blur(); + } + + SimpleTest.waitForFocus(doTest); + + ]]></script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug429723.xhtml b/toolkit/content/tests/chrome/test_bug429723.xhtml new file mode 100644 index 0000000000..865f0d66f8 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug429723.xhtml @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=429723 +--> +<window title="Mozilla Bug 429723" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=429723">Mozilla Bug 429723</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 429723 **/ +SimpleTest.waitForExplicitFinish(); +window.openDialog("bug429723_window.xhtml", "429723test", + "chrome,width=600,height=600,noopener", window); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug451540.xhtml b/toolkit/content/tests/chrome/test_bug451540.xhtml new file mode 100644 index 0000000000..debcadfe05 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug451540.xhtml @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=451540 +--> +<window title="Mozilla Bug 451540" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=451540"> + Mozilla Bug 451540 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <script class="testbody" type="application/javascript"> + <![CDATA[ + + /** Test for Bug 451540 **/ + SimpleTest.waitForExplicitFinish(); + window.openDialog("bug451540_window.xhtml", "451540test", + "chrome,width=600,height=600,noopener", window); + + ]]> + </script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug457632.xhtml b/toolkit/content/tests/chrome/test_bug457632.xhtml new file mode 100644 index 0000000000..12267c1dec --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug457632.xhtml @@ -0,0 +1,160 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for bug 457632 + --> +<window title="Bug 457632" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <vbox id="nb"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;" + onload="test()"/> + + <!-- test code goes here --> +<script type="application/javascript"> +<![CDATA[ +var gNotificationBox; + +function completeAnimation(nextTest) { + if (!gNotificationBox._animating) { + nextTest(); + return; + } + + setTimeout(completeAnimation, 50, nextTest); +} + +async function test() { + SimpleTest.waitForExplicitFinish(); + gNotificationBox = new MozElements.NotificationBox(e => { + document.getElementById("nb").appendChild(e); + }); + + is(gNotificationBox.allNotifications.length, 0, "There should be no initial notifications"); + await gNotificationBox.appendNotification("notification1", + { label: "Test notification", priority: gNotificationBox.PRIORITY_INFO_LOW }); + is(gNotificationBox.allNotifications.length, 1, "Notification exists while animating in"); + let notification = gNotificationBox.getNotificationWithValue("notification1"); + ok(notification, "Notification should exist while animating in"); + + // Wait for the notificaton to finish displaying + completeAnimation(test1); +} + +// Tests that a notification that is fully animated in gets removed immediately +async function test1() { + let notification = gNotificationBox.getNotificationWithValue("notification1"); + gNotificationBox.removeNotification(notification); + notification = gNotificationBox.getNotificationWithValue("notification1"); + ok(!notification, "Test 1 showed notification was still present"); + ok(!gNotificationBox.currentNotification, "Test 1 said there was still a current notification"); + is(gNotificationBox.allNotifications.length, 0, "Test 1 should show no notifications present"); + + // Wait for the notificaton to finish hiding + completeAnimation(test2); +} + +// Tests that a notification that is animating in gets removed immediately +async function test2() { + let notification = await gNotificationBox.appendNotification("notification2", + { label: "Test notification", priority: gNotificationBox.PRIORITY_INFO_LOW }); + gNotificationBox.removeNotification(notification); + notification = gNotificationBox.getNotificationWithValue("notification2"); + ok(!notification, "Test 2 showed notification was still present"); + ok(!gNotificationBox.currentNotification, "Test 2 said there was still a current notification"); + is(gNotificationBox.allNotifications.length, 0, "Test 2 should show no notifications present"); + + // Get rid of the hiding notifications + gNotificationBox.removeAllNotifications(true); + test3(); +} + +// Tests that a background notification goes away immediately +async function test3() { + let notification = await gNotificationBox.appendNotification("notification3", + { label: "Test notification", priority: gNotificationBox.PRIORITY_INFO_LOW }); + let notification2 = await gNotificationBox.appendNotification("notification4", + { label: "Test notification", priority: gNotificationBox.PRIORITY_INFO_LOW }); + is(gNotificationBox.allNotifications.length, 2, "Test 3 should show 2 notifications present"); + gNotificationBox.removeNotification(notification); + is(gNotificationBox.allNotifications.length, 1, "Test 3 should show 1 notifications present"); + notification = gNotificationBox.getNotificationWithValue("notification3"); + ok(!notification, "Test 3 showed notification was still present"); + gNotificationBox.removeNotification(notification2); + is(gNotificationBox.allNotifications.length, 0, "Test 3 should show 0 notifications present"); + notification2 = gNotificationBox.getNotificationWithValue("notification4"); + ok(!notification2, "Test 3 showed notification2 was still present"); + ok(!gNotificationBox.currentNotification, "Test 3 said there was still a current notification"); + + // Get rid of the hiding notifications + gNotificationBox.removeAllNotifications(true); + test4(); +} + +// Tests that a foreground notification hiding a background one goes away +async function test4() { + let notification = await gNotificationBox.appendNotification("notification5", + { label: "Test notification", priority: gNotificationBox.PRIORITY_INFO_LOW }); + let notification2 = await gNotificationBox.appendNotification("notification6", + { label: "Test notification", priority: gNotificationBox.PRIORITY_INFO_LOW }); + gNotificationBox.removeNotification(notification2); + notification2 = gNotificationBox.getNotificationWithValue("notification6"); + ok(!notification2, "Test 4 showed notification2 was still present"); + is(gNotificationBox.currentNotification, notification, "Test 4 said the current notification was wrong"); + is(gNotificationBox.allNotifications.length, 1, "Test 4 should show 1 notifications present"); + gNotificationBox.removeNotification(notification); + notification = gNotificationBox.getNotificationWithValue("notification5"); + ok(!notification, "Test 4 showed notification was still present"); + ok(!gNotificationBox.currentNotification, "Test 4 said there was still a current notification"); + is(gNotificationBox.allNotifications.length, 0, "Test 4 should show 0 notifications present"); + + // Get rid of the hiding notifications + gNotificationBox.removeAllNotifications(true); + test5(); +} + +// Tests that removeAllNotifications gets rid of everything +async function test5() { + let notification = await gNotificationBox.appendNotification("notification7", + { label: "Test notification", priority: gNotificationBox.PRIORITY_INFO_LOW }); + let notification2 = await gNotificationBox.appendNotification("notification8", + { label: "Test notification", priority: gNotificationBox.PRIORITY_INFO_LOW }); + gNotificationBox.removeAllNotifications(); + notification = gNotificationBox.getNotificationWithValue("notification7"); + notification2 = gNotificationBox.getNotificationWithValue("notification8"); + ok(!notification, "Test 5 showed notification was still present"); + ok(!notification2, "Test 5 showed notification2 was still present"); + ok(!gNotificationBox.currentNotification, "Test 5 said there was still a current notification"); + is(gNotificationBox.allNotifications.length, 0, "Test 5 should show 0 notifications present"); + + await gNotificationBox.appendNotification("notification9", + { label: "Test notification", priority: gNotificationBox.PRIORITY_INFO_LOW }); + + // Wait for the notificaton to finish displaying + completeAnimation(test6); +} + +// Tests whether removing an already removed notification doesn't break things +async function test6() { + let notification = gNotificationBox.getNotificationWithValue("notification9"); + ok(notification, "Test 6 should have an initial notification"); + gNotificationBox.removeNotification(notification); + gNotificationBox.removeNotification(notification); + + ok(!gNotificationBox.currentNotification, "Test 6 shouldn't be any current notification"); + is(gNotificationBox.allNotifications.length, 0, "Test 6 allNotifications.length should be 0"); + notification = await gNotificationBox.appendNotification("notification10", + { label: "Test notification", priority: gNotificationBox.PRIORITY_INFO_LOW }); + is(notification, gNotificationBox.currentNotification, "Test 6 should have made the current notification"); + gNotificationBox.removeNotification(notification); + + SimpleTest.finish(); +} +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug460942.xhtml b/toolkit/content/tests/chrome/test_bug460942.xhtml new file mode 100644 index 0000000000..53f33302be --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug460942.xhtml @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=460942 +--> +<window title="Mozilla Bug 460942" + onload="runTests()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=460942" + target="_blank">Mozilla Bug 460942</a> + </body> + + <!-- test code goes here --> + + <richlistbox> + <richlistitem id="item1"> + <label value="one"/> + <box> + <label value="two"/> + </box> + </richlistitem> + <richlistitem id="item2"><description>one</description><description>two</description></richlistitem> + </richlistbox> + + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 460942 **/ + function runTests() { + is ($("item1").label, "one two"); + is ($("item2").label, ""); + SimpleTest.finish(); + } + SimpleTest.waitForExplicitFinish(); + ]]> + </script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug471776.xhtml b/toolkit/content/tests/chrome/test_bug471776.xhtml new file mode 100644 index 0000000000..dcd45bfd77 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug471776.xhtml @@ -0,0 +1,45 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Textbox with placeholder undo test" width="500" height="600" + onload="doTest();" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <hbox> + <html:input id="t1" placeholder="empty"/> + </hbox> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"> + <p id="display"> + </p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + function doTest() { + var t1 = $("t1"); + t1.focus(); + ok(!t1.editor.canUndo, "undo correctly disabled when no user edits"); + SimpleTest.finish(); + } + + ]]></script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug509732.xhtml b/toolkit/content/tests/chrome/test_bug509732.xhtml new file mode 100644 index 0000000000..7e340322b2 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug509732.xhtml @@ -0,0 +1,53 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for bug 509732 + --> +<window title="Bug 509732" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <vbox id="nb" hidden="true"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;" + onload="test()"/> + + <!-- test code goes here --> +<script type="application/javascript"> +<![CDATA[ +var gNotificationBox; + +// Tests that a notification that is added in an hidden box didn't throw the animation +async function test() { + SimpleTest.waitForExplicitFinish(); + gNotificationBox = new MozElements.NotificationBox(e => { + document.getElementById("nb").appendChild(e); + }); + + is(gNotificationBox.allNotifications.length, 0, "There should be no initial notifications"); + + await gNotificationBox.appendNotification("notification1", + { label: "Test notification", priority: gNotificationBox.PRIORITY_INFO_LOW }); + + is(gNotificationBox.allNotifications.length, 1, "Notification exists"); + is(gNotificationBox._animating, false, "Notification shouldn't be animating"); + + test1(); +} + +// Tests that a notification that is removed from an hidden box didn't throw the animation +function test1() { + let notification = gNotificationBox.getNotificationWithValue("notification1"); + gNotificationBox.removeNotification(notification); + ok(!gNotificationBox.currentNotification, "Test 1 should show no current animation"); + is(gNotificationBox._animating, false, "Notification shouldn't be animating"); + is(gNotificationBox.allNotifications.length, 0, "Test 1 should show no notifications present"); + + SimpleTest.finish(); +} +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug557987.xhtml b/toolkit/content/tests/chrome/test_bug557987.xhtml new file mode 100644 index 0000000000..6af1b13700 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug557987.xhtml @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for bug 557987 + --> +<window title="Bug 557987" width="400" height="400" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <toolbarbutton id="button" type="menu" label="Test bug 557987" + onclick="eventReceived('click');" + oncommand="eventReceived('command');"> + <menupopup onpopupshowing="eventReceived('popupshowing'); return false;" /> + </toolbarbutton> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + +<script type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +SimpleTest.waitForFocus(test); + +// Tests that mouse events are correctly dispatched to <toolbarbutton type="menu"/> +// This used to test menu buttons, and was updated when this button type was removed. +function test() { + disableNonTestMouseEvents(true); + + let button = $("button"); + let rightEdge = button.getBoundingClientRect().width - 2; + let centerX = button.getBoundingClientRect().width / 2; + let centerY = button.getBoundingClientRect().height / 2; + + synthesizeMouse(button, rightEdge, centerY, {}, window); + synthesizeMouse(button, centerX, centerY, {}, window); + + synthesizeMouse(document.getElementsByTagName("body")[0], 0, 0, {}, window); + + disableNonTestMouseEvents(false); + SimpleTest.executeSoon(finishTest); + +} + +function finishTest() { + is(eventCount.command, 0, "Correct number of command events received"); + is(eventCount.popupshowing, 2, "Correct number of popupshowing events received"); + is(eventCount.click, 2, "Correct number of click events received"); + + SimpleTest.finish(); +} + +let eventCount = { + command: 0, + popupshowing: 0, + click: 0, +}; + +function eventReceived(eventName) { + eventCount[eventName]++; +} + +]]> +</script> +</window> diff --git a/toolkit/content/tests/chrome/test_bug562554.xhtml b/toolkit/content/tests/chrome/test_bug562554.xhtml new file mode 100644 index 0000000000..a822bf59dd --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug562554.xhtml @@ -0,0 +1,81 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for bug 562554 + --> +<window title="Bug 562554" width="400" height="400" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <toolbarbutton type="menu" id="toolbarmenu" style="height: 200px; justify-content: flex-start; align-items: flex-start"> + <menupopup id="menupopup" onpopupshowing="eventReceived('popupshowing'); return false;"/> + <stack style="pointer-events: none"> + <button style="pointer-events: auto; width: 100px; height: 30px; margin-left: 0; margin-top: 0;" allowevents="true" + onclick="eventReceived('clickbutton1'); return false;"/> + <button style="width: 100px; height: 30px; margin-left: 70px; margin-top: 0;" + onclick="eventReceived('clickbutton2'); return false;"/> + </stack> + </toolbarbutton> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + +<script type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(test); + +// Tests that mouse events are correctly dispatched to <toolbarbutton type="menu"/> +function test() { + disableNonTestMouseEvents(true); + nextTest(); +} + +let tests = [ + // Click on the toolbarbutton itself - should call popupshowing + () => synthesizeMouse($("toolbarmenu"), 10, 50, {}, window), + () => is(eventCount.popupshowing, 1, "Got first popupshowing event"), + + // Click on button1 which has allowevents="true" - should call clickbutton1 + () => synthesizeMouse($("toolbarmenu"), 10, 15, {}, window), + () => is(eventCount.clickbutton1, 1, "Button 1 clicked"), + + // Click on button2 outside of intersection - should call popupshowing + () => synthesizeMouse($("toolbarmenu"), 150, 15, {}, window) +]; + +function nextTest() { + if (tests.length) { + let func = tests.shift(); + func(); + SimpleTest.executeSoon(nextTest); + } else { + disableNonTestMouseEvents(false); + SimpleTest.executeSoon(finishTest); + } +} + +function finishTest() { + is(eventCount.clickbutton1, 1, "Correct number of clicks on button 1"); + is(eventCount.clickbutton2, 0, "Correct number of clicks on button 2"); + is(eventCount.popupshowing, 2, "Correct number of popupshowing events received"); + + SimpleTest.finish(); +} + +let eventCount = { + popupshowing: 0, + clickbutton1: 0, + clickbutton2: 0 +}; + +function eventReceived(eventName) { + eventCount[eventName]++; +} + +]]> +</script> +</window> diff --git a/toolkit/content/tests/chrome/test_bug624329.xhtml b/toolkit/content/tests/chrome/test_bug624329.xhtml new file mode 100644 index 0000000000..26b7d2ea53 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug624329.xhtml @@ -0,0 +1,169 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=624329 +--> +<window title="Mozilla Bug 624329 context menu position" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" + onload="beginTest()"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=624329" + target="_blank">Mozilla Bug 624329</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 624329 **/ + +SimpleTest.waitForExplicitFinish(); + +var win; +var timeoutID; +var menu; + +const {AppConstants} = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +function beginTest() { + if (AppConstants.platform == "macosx" && SpecialPowers.getBoolPref("widget.macos.native-context-menus", false)) { + // This test does not apply with native context menus. + ok(true, "macOS positions native menus, so we don't need to test this behaviour."); + SimpleTest.finish(); + return; + } + + openTestWindow(); +} + +function openTestWindow() { + win = window.browsingContext.topChromeWindow.openDialog("bug624329_window.xhtml", "_blank", "width=300,resizable=yes,chrome", window); + // Close our window if the test times out so that it doesn't interfere + // with later tests. + timeoutID = setTimeout(function () { + ok(false, "Test timed out."); + // Provide some time for a screenshot + setTimeout(finish, 1000); + }, 20000); +} + +function listenOnce(event, callback) { + win.addEventListener(event, function listener() { + callback(); + }, { once: true}); +} + +function childFocused() { + // maximizing the window is a simple way to ensure that the menu is near + // the right edge of the screen. + + listenOnce("resize", childResized); + win.maximize(); +} + +function childResized() { + const isOSXMavericks = navigator.userAgent.includes("Mac OS X 10.9"); + const isOSXYosemite = navigator.userAgent.includes("Mac OS X 10.10"); + if (isOSXMavericks || isOSXYosemite) { + todo_is(win.windowState, win.STATE_MAXIMIZED, + "A resize before being maximized breaks this test on 10.9 and 10.10"); + finish(); + return; + } + + is(win.windowState, win.STATE_MAXIMIZED, + "window should be maximized"); + + isnot(win.innerWidth, 300, + "window inner width should have changed"); + + openContextMenu(); +} + +function openContextMenu() { + var mouseX = win.innerWidth - 10; + var mouseY = 10; + + menu = win.document.getElementById("menu"); + var screenX = menu.screenX; + var screenY = menu.screenY; + var utils = win.windowUtils; + + utils.sendMouseEvent("contextmenu", mouseX, mouseY, 2, 0, 0); + + var interval = setInterval(checkMoved, 200); + function checkMoved() { + if (menu.screenX != screenX || + menu.screenY != screenY) { + clearInterval(interval); + // Wait further to check that the window does not move again. + setTimeout(checkPosition, 1000); + } + } + + function checkPosition() { + var rootElement = win.document.documentElement; + var platformIsMac = navigator.userAgent.indexOf("Mac") > -1; + + var x = menu.screenX - rootElement.screenX - parseFloat(getComputedStyle(menu).marginLeft); + var y = menu.screenY - rootElement.screenY - parseFloat(getComputedStyle(menu).marginTop); + + if (platformIsMac) + { + // This check is alterered slightly for OSX which adds padding to the top + // and bottom of its context menus. The menu position calculation must + // be changed to allow for the pointer to be outside this padding + // when the menu opens. + // (Bug 1075089) + ok(y + 6 >= mouseY, + "menu top " + (y + 6) + " should be below click point " + mouseY); + } + else + { + ok(y >= mouseY, + "menu top " + y + " should be below click point " + mouseY); + } + + ok(y <= mouseY + 20, + "menu top " + y + " should not be too far below click point " + mouseY); + + ok(x < mouseX, + "menu left " + x + " should be left of click point " + mouseX); + var right = x + menu.getBoundingClientRect().width; + + if (platformIsMac) { + // Rather than be constrained by the right hand screen edge, OSX menus flip + // horizontally and appear to the left of the mouse pointer + ok(right < mouseX, + "menu right " + right + " should be left of click point " + mouseX); + } + else { + ok(right > mouseX, + "menu right " + right + " should be right of click point " + mouseX); + } + + clearTimeout(timeoutID); + finish(); + } + +} + +function finish() { + if (menu && navigator.platform.includes("Win")) { + todo(false, "Should not have to hide popup before closing its window"); + // This avoids mochitest "Unable to restore focus" errors (bug 670053). + menu.hidePopup(); + } + win.close(); + SimpleTest.finish(); +} + + ]]> + </script> +</window> diff --git a/toolkit/content/tests/chrome/test_bug792324.xhtml b/toolkit/content/tests/chrome/test_bug792324.xhtml new file mode 100644 index 0000000000..ddedf5907b --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug792324.xhtml @@ -0,0 +1,74 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=792324 +--> +<window title="Mozilla Bug 792324" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> +<body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=792324">Mozilla Bug 792324</a> + + <p id="display"></p> +<div id="content" style="display: none"> +</div> +</body> + +<panel id="panel-1"> + <button label="just a normal button"/> + <button id="button-1" + accesskey="X" + oncommand="clicked(event)" + label="Button in panel 1" + /> +</panel> + +<panel id="panel-2"> + <button label="just a normal button"/> + <button id="button-2" + accesskey="X" + oncommand="clicked(event)" + label="Button in panel 2" + /> +</panel> + +<script class="testbody" type="application/javascript"><![CDATA[ + +/** Test for Bug 792324 **/ +let after_click; + +function clicked(event) { + after_click(event); +} + +function checkAccessKeyOnPanel(panelid, buttonid, cb) { + let panel = document.getElementById(panelid); + panel.addEventListener("popupshown", function onpopupshown() { + panel.firstChild.focus(); + after_click = function(event) { + is(event.target.id, buttonid, "Accesskey was directed to the button '" + buttonid + "'"); + panel.hidePopup(); + cb(); + } + sendString("X"); + }); + panel.openPopup(null, "", 100, 100, false, false); +} + +function test() { + checkAccessKeyOnPanel("panel-1", "button-1", function() { + checkAccessKeyOnPanel("panel-2", "button-2", function() { + SimpleTest.finish(); + }); + }); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(test, window); + +]]></script> + +</window> diff --git a/toolkit/content/tests/chrome/test_button.xhtml b/toolkit/content/tests/chrome/test_button.xhtml new file mode 100644 index 0000000000..fa1b7cacd0 --- /dev/null +++ b/toolkit/content/tests/chrome/test_button.xhtml @@ -0,0 +1,81 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for button + --> +<window title="Button Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<button id="one" label="One" /> +<button id="two" label="Two"/> +<hbox> + <button id="three" label="Three" open="true"/> +</hbox> +<hbox> + <button id="four" type="menu" label="Four"/> + <button id="five" label="Five"/> +</hbox> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + +<script type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +add_task(async function test_button() +{ + await SimpleTest.promiseFocus(); + + // Click on the button. + let commandPromise = new Promise(resolve => { + addEventListener("command", event => resolve(event), { once: true }); + }); + + synthesizeMouseAtCenter($("one"), {}); + let event = await commandPromise; + is(event.button, 0, "button for mouse"); + is(event.inputSource, MouseEvent.MOZ_SOURCE_MOUSE, "input source for mouse"); + + // Press space while to button is focused. + commandPromise = new Promise(resolve => { + addEventListener("command", event => resolve(event), { once: true }); + }); + + $("one").focus(); + synthesizeKey("VK_SPACE", { }); + event = await commandPromise; + is(event.button, 0, "button for keyboard"); + is(event.inputSource, MouseEvent.MOZ_SOURCE_KEYBOARD, "input source for keyboard"); + + $("two").disabled = true; + synthesizeMouseExpectEvent($("two"), 2, 2, {}, $("two"), "!command", "button press command when disabled"); + synthesizeMouseExpectEvent($("two"), 2, 2, {}, $("two"), "click", "button press click when disabled"); + + if (!navigator.platform.includes("Mac")) { + $("one").focus(); + synthesizeKey("KEY_ArrowDown"); + is(document.activeElement, $("three"), "key cursor down on button"); + + synthesizeKey("KEY_ArrowRight"); + is(document.activeElement, $("four"), "key cursor right on button"); + synthesizeKey("KEY_ArrowDown"); + is(document.activeElement, $("four"), "key cursor down on menu button"); + + $("three").focus(); + synthesizeKey("KEY_ArrowUp"); + is(document.activeElement, $("one"), "key cursor up on button"); + } + + $("two").focus(); + ok(document.activeElement != $("two"), "focus disabled button"); +}); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_chromemargin.xhtml b/toolkit/content/tests/chrome/test_chromemargin.xhtml new file mode 100644 index 0000000000..d1a6a568be --- /dev/null +++ b/toolkit/content/tests/chrome/test_chromemargin.xhtml @@ -0,0 +1,35 @@ +<?xml version="1.0"?> +<!-- 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/. --> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Custom chrome margin tests" + onload="setTimeout(runTest, 0);" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<script> + +// Tests parsing of the chrome margin attrib on a window. + +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.openDialog("window_chromemargin.xhtml", "_blank", "chrome,width=600,height=600,noopener", window); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_closemenu_attribute.xhtml b/toolkit/content/tests/chrome/test_closemenu_attribute.xhtml new file mode 100644 index 0000000000..7b29bd6c5d --- /dev/null +++ b/toolkit/content/tests/chrome/test_closemenu_attribute.xhtml @@ -0,0 +1,96 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu closemenu Attribute Tests" + onload="setTimeout(nextTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<button id="menu" type="menu" label="Menu" onpopuphidden="popupHidden(event)"> + <menupopup id="p1" onpopupshown="if (event.target == this) this.firstChild.open = true"> + <menu id="l1" label="One"> + <menupopup id="p2" onpopupshown="if (event.target == this) this.firstChild.open = true"> + <menu id="l2" label="Two"> + <menupopup id="p3" onpopupshown="executeMenuItem()"> + <menuitem id="l3" label="Three"/> + </menupopup> + </menu> + </menupopup> + </menu> + </menupopup> +</button> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var gExpectedId = "p3"; +var gMode = -1; +var gModes = ["", "auto", "single", "none"]; + +function nextTest() +{ + gMode++; + if (gModes[gMode] != "none") + gExpectedId = "p3"; + + if (gMode != 0) + $("l3").setAttribute("closemenu", gModes[gMode]); + if (gModes[gMode] == "none") + $("l2").open = true; + else + $("menu").open = true; +} + +function executeMenuItem() +{ + synthesizeKey("KEY_ArrowDown"); + synthesizeKey("KEY_Enter"); + // after a couple of seconds, end the test, as the 'none' closemenu value + // should not hide any popups + if (gModes[gMode] == "none") + setTimeout(function() { $("menu").open = false; }, 2000); +} + +function popupHidden(event) +{ + if (gModes[gMode] == "none") { + if (event.target.id == "p1") + SimpleTest.finish() + return; + } + + is(event.target.id, gExpectedId, + "Expected event " + gModes[gMode] + " " + gExpectedId); + + gExpectedId = ""; + if (event.target.id == "p3") { + if (gModes[gMode] == "" || gModes[gMode] == "auto") + gExpectedId = "p2"; + } + else if (event.target.id == "p2") { + if (gModes[gMode] == "" || gModes[gMode] == "auto") + gExpectedId = "p1"; + } + + if (!gExpectedId) + nextTest(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_contextmenu_list.xhtml b/toolkit/content/tests/chrome/test_contextmenu_list.xhtml new file mode 100644 index 0000000000..d5d2b1a10b --- /dev/null +++ b/toolkit/content/tests/chrome/test_contextmenu_list.xhtml @@ -0,0 +1,296 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Context Menu on List Tests" + onload="setTimeout(startTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<spacer style="height: 5px"/> + +<hbox style="padding-left: 10px;"> + <spacer style="width: 5ps"/> + <richlistbox id="list" context="themenu" style="padding: 0;" oncontextmenu="checkContextMenu(event)"> + <richlistitem id="item1" style="padding-top: 4px; margin: 0;"><button label="One"/></richlistitem> + <richlistitem id="item2" style="height: 22px"><checkbox label="Checkbox"/></richlistitem> + <richlistitem id="item3"><button label="Three"/></richlistitem> + <richlistitem id="item4"><checkbox label="Four"/></richlistitem> + </richlistbox> + + <tree id="tree" rows="5" flex="1" context="themenu" style="-moz-appearance: none; border: 0"> + <treecols> + <treecol label="Name" flex="1"/> + <splitter class="tree-splitter"/> + <treecol label="Moons"/> + </treecols> + <treechildren id="treechildren"> + <treeitem> + <treerow> + <treecell label="Mercury"/> + <treecell label="0"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Venus"/> + <treecell label="0"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Earth"/> + <treecell label="1"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Mars"/> + <treecell label="2"/> + </treerow> + </treeitem> + </treechildren> + </tree> + + <menu id="menu" label="Menu"> + <menupopup id="menupopup" onpopupshown="menuTests()" onpopuphidden="nextTest()" + oncontextmenu="checkContextMenuForMenu(event)"> + <menuitem id="menu1" label="Menu 1"/> + <menuitem id="menu2" label="Menu 2"/> + <menuitem id="menu3" label="Menu 3"/> + </menupopup> + </menu> + +</hbox> + +<menupopup id="themenu" onpopupshowing="if (gTestId == -1) event.preventDefault()" + onpopupshown="checkPopup()" onpopuphidden="setTimeout(nextTest, 0);"> + <menuitem label="Item"/> +</menupopup> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var gTestId = -1; +var gTestElement = "list"; +var gSelectionStep = 0; +var gContextMenuFired = false; + +async function startTest() +{ + // These tests check behavior of non-native menus, and use anchored and non-anchored popups + // somewhat interchangeably. So disable native context menus for this test. + // We will have separate tests for native context menu behavior, see bug 1700727. + await SpecialPowers.pushPrefEnv({ set: [["widget.macos.native-context-menus", false]] }); + + // first, check if the richlistbox selection changes on a contextmenu mouse event + var element = $("list"); + synthesizeMouse(element.getItemAtIndex(3), 7, 1, { type : "mousedown", button: 2, ctrlKey: true }); + synthesizeMouse(element, 7, 4, { type : "contextmenu", button: 2 }); + + gSelectionStep++; + synthesizeMouse(element.getItemAtIndex(1), 7, 1, { type : "mousedown", button: 2, ctrlKey: true, shiftKey: true }); + synthesizeMouse(element, 7, 4, { type : "contextmenu", button: 2 }); + + gSelectionStep++; + synthesizeMouse(element.getItemAtIndex(1), 7, 1, { type : "mousedown", button: 2 }); + synthesizeMouse(element, 7, 4, { type : "contextmenu", button: 2 }); + + $("menu").open = true; +} + +function menuTests() +{ + gSelectionStep = 0; + var element = $("menu"); + synthesizeMouse(element, 0, 0, { type : "contextmenu", button: 0 }); + is(gContextMenuFired, true, "context menu fired when menu open"); + + gSelectionStep = 1; + $("menu").activeChild = $("menu2"); + synthesizeMouse(element, 0, 0, { type : "contextmenu", button: 0 }); + + $("menu").open = false; +} + +function nextTest() +{ + gTestId++; + if (gTestId > 2) { + if (gTestElement == "list") { + gTestElement = "tree"; + gTestId = 0; + } + else { + SimpleTest.finish(); + return; + } + } + var element = $(gTestElement); + element.focus(); + if (gTestId == 0) { + if (gTestElement == "list") + element.selectedIndex = 2; + element.currentIndex = 2; + synthesizeMouse(element, 0, 0, { type : "contextmenu", button: 0 }); + } + else if (gTestId == 1) { + synthesizeMouse(element, 7, 4, { type : "contextmenu", button: 2 }); + } + else { + element.currentIndex = -1; + element.selectedIndex = -1; + synthesizeMouse(element, 0, 0, { type : "contextmenu", button: 0 }); + } +} + +// This is nasty so I'd better explain what's going on. +// The basic problem is that the synthetic mouse coordinate generated +// by DOMWindowUtils.sendMouseEvent and also the synthetic mouse coordinate +// generated internally when contextmenu events are redirected to the focused +// element are rounded to the nearest device pixel. But this rounding is done +// while the coordinates are relative to the nearest widget. When this test +// is run in the mochitest harness, the nearest widget is the main mochitest +// window, and our document can have a fractional position within that +// mochitest window. So when we round coordinates for comparison in this +// test, we need to do so very carefully, especially if the target element +// also has a fractional position within our document. +// +// For example, if the y-offset of our containing IFRAME is 100.4px, +// and the offset of our expected point is 10.3px in our document, the actual +// mouse event is dispatched to round(110.7) == 111px. This comes back +// with a clientY of round(111 - 100.4) == round(10.6) == 11. This is not +// equal to round(10.3) as you might expect. + +function isRoundedX(a, b, msg) +{ + is(Math.round(a + window.mozInnerScreenX), Math.round(b + window.mozInnerScreenX), msg); +} + +function isRoundedY(a, b, msg) +{ + is(Math.round(a + window.mozInnerScreenY), Math.round(b + window.mozInnerScreenY), msg); +} + +function checkContextMenu(event) +{ + var rect = $(gTestElement).getBoundingClientRect(); + + var frombase = (gTestId == -1 || gTestId == 1); + if (!frombase) + rect = event.originalTarget.getBoundingClientRect(); + var left = frombase ? rect.left + 7 : rect.left; + var top = frombase ? rect.top + 4 : rect.bottom; + + isRoundedX(event.clientX, left, gTestElement + " clientX " + gSelectionStep + " " + gTestId + "," + frombase); + isRoundedY(event.clientY, top, gTestElement + " clientY " + gSelectionStep + " " + gTestId); + ok(event.screenX > left, gTestElement + " screenX " + gSelectionStep + " " + gTestId); + ok(event.screenY > top, gTestElement + " screenY " + gSelectionStep + " " + gTestId); + + // context menu from mouse click + switch (gTestId) { + case -1: + // eslint-disable-next-line no-nested-ternary + var expected = gSelectionStep == 2 ? 1 : (platformIsMac() ? 3 : 0); + is($(gTestElement).selectedIndex, expected, "index after click " + gSelectionStep); + break; + case 0: + if (gTestElement == "list") + is(event.originalTarget, $("item3"), "list selection target"); + else + is(event.originalTarget, $("treechildren"), "tree selection target"); + break; + case 1: + is(event.originalTarget.id, $("item1").id, "list mouse selection target"); + break; + case 2: + is(event.originalTarget, $("list"), "list no selection target"); + break; + } +} + +function checkContextMenuForMenu(event) +{ + gContextMenuFired = true; + + var popuprect = (gSelectionStep ? $("menu2") : $("menupopup")).getBoundingClientRect(); + is(event.clientX, Math.round(popuprect.left), "menu left " + gSelectionStep); + // the clientY is off by one sometimes on Windows (when loaded in the testing iframe + // but not when loaded separately) so just check for both cases for now + ok(event.clientY == Math.round(popuprect.bottom) || + event.clientY - 1 == Math.round(popuprect.bottom), "menu top " + gSelectionStep); +} + +function checkPopup() +{ + var menurect = $("themenu").getBoundingClientRect(); + + // Context menus are offset by a number of pixels from the mouse click + // which activates them. This is so that they don't appear exactly + // under the mouse which can cause them to be mistakenly dismissed. + // The number of pixels depends on the platform and is defined in + // each platform's nsLookAndFeel + var contextMenuOffsetX = platformIsMac() ? 1 : 2; + var contextMenuOffsetY = platformIsMac() ? -6 : 2; + contextMenuOffsetY += parseFloat(getComputedStyle($("themenu")).marginTop); + contextMenuOffsetX += parseFloat(getComputedStyle($("themenu")).marginLeft); + + if (gTestId == 0) { + if (gTestElement == "list") { + var itemrect = $("item3").getBoundingClientRect(); + isRoundedX(menurect.left, itemrect.left + contextMenuOffsetX, + "list selection keyboard left"); + isRoundedY(menurect.top, itemrect.bottom + contextMenuOffsetY, + "list selection keyboard top"); + } + else { + var tree = $("tree"); + var bodyrect = $("treechildren").getBoundingClientRect(); + isRoundedX(menurect.left, bodyrect.left + contextMenuOffsetX, + "tree selection keyboard left"); + isRoundedY(menurect.top, bodyrect.top + + tree.rowHeight * 3 + contextMenuOffsetY, + "tree selection keyboard top"); + } + } + else if (gTestId == 1) { + // activating a context menu with the mouse from position (7, 4). + let elementrect = $(gTestElement).getBoundingClientRect(); + isRoundedX(menurect.left, elementrect.left + 7 + contextMenuOffsetX, + gTestElement + " mouse left"); + isRoundedY(menurect.top, elementrect.top + 4 + contextMenuOffsetY, + gTestElement + " mouse top"); + } + else { + let elementrect = $(gTestElement).getBoundingClientRect(); + isRoundedX(menurect.left, elementrect.left + contextMenuOffsetX, + gTestElement + " no selection keyboard left"); + isRoundedY(menurect.top, elementrect.bottom + contextMenuOffsetY, + gTestElement + " no selection keyboard top"); + } + + $("themenu").hidePopup(); +} + +function platformIsMac() +{ + return navigator.platform.indexOf("Mac") > -1; +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_contextmenu_rtl.xhtml b/toolkit/content/tests/chrome/test_contextmenu_rtl.xhtml new file mode 100644 index 0000000000..45649c8c2d --- /dev/null +++ b/toolkit/content/tests/chrome/test_contextmenu_rtl.xhtml @@ -0,0 +1,35 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<window title="Context Menu RTL position" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> +<menupopup id="context-menu" style="max-width: 100px; direction: rtl"> + <menuitem label="Item"/> +</menupopup> +<script> +<![CDATA[ + +add_task(async function() { + // This test checks behavior of non-native menus, + // so disable native context menus for this test. + await SpecialPowers.pushPrefEnv({ set: [["widget.macos.native-context-menus", false]] }); + const menu = document.getElementById("context-menu"); + const shown = new Promise(resolve => menu.addEventListener("popupshown", resolve, { once: true })); + const point = { + x: window.screenX + screen.width / 2, + y: window.screenY + screen.height / 2, + }; + menu.openPopupAtScreen(point.x, point.y, true, null); + await shown; + const rect = menu.getBoundingClientRect(); + const margin = parseFloat(getComputedStyle(menu).marginRight); + info(`${menu.screenX} + ${rect.width} + ${margin} < ${point.x}`); + ok(menu.screenX + rect.width + margin < point.x, "Should be right-aligned"); + menu.hidePopup(); +}); + +]]> +</script> +</window> diff --git a/toolkit/content/tests/chrome/test_cursorsnap.xhtml b/toolkit/content/tests/chrome/test_cursorsnap.xhtml new file mode 100644 index 0000000000..170e0ab378 --- /dev/null +++ b/toolkit/content/tests/chrome/test_cursorsnap.xhtml @@ -0,0 +1,123 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Cursor snapping test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" /> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +const kMaxRetryCount = 4; +const kTimeoutTime = [ + 100, 100, 1000, 1000, 5000 +]; + +var gRetryCount; + +var gTestingCount = 0; +var gTestingIndex = -1; +var gDisable = false; +var gHidden = false; + +function canRetryTest() +{ + return gRetryCount <= kMaxRetryCount; +} + +function getTimeoutTime() +{ + return kTimeoutTime[gRetryCount]; +} + +function runNextTest() +{ + gRetryCount = 0; + gTestingIndex++; + runCurrentTest(); +} + +function retryCurrentTest() +{ + ok(canRetryTest(), "retry the current test..."); + gRetryCount++; + runCurrentTest(); +} + +function runCurrentTest() +{ + var position = "top=" + gTestingCount + ",left=" + gTestingCount + ","; + gTestingCount++; + switch (gTestingIndex) { + case 0: + gDisable = false; + gHidden = false; + window.openDialog("window_cursorsnap_dialog.xhtml", "_blank", + position + "chrome,width=100,height=100,noopener", window); + break; + case 1: + gDisable = true; + gHidden = false; + window.openDialog("window_cursorsnap_dialog.xhtml", "_blank", + position + "chrome,width=100,height=100,noopener", window); + break; + case 2: + gDisable = false; + gHidden = true; + window.openDialog("window_cursorsnap_dialog.xhtml", "_blank", + position + "chrome,width=100,height=100,noopener", window); + break; + case 3: + gDisable = false; + gHidden = false; + window.openDialog("window_cursorsnap_wizard.xhtml", "_blank", + position + "chrome,width=100,height=100,noopener", window); + break; + case 4: + gDisable = true; + gHidden = false; + window.openDialog("window_cursorsnap_wizard.xhtml", "_blank", + position + "chrome,width=100,height=100,noopener", window); + break; + case 5: + gDisable = false; + gHidden = true; + window.openDialog("window_cursorsnap_wizard.xhtml", "_blank", + position + "chrome,width=100,height=100,noopener", window); + break; + default: + SetPrefs(false); + SimpleTest.finish(); + } +} + +function SetPrefs(aSet) +{ + var prefSvc = SpecialPowers.Services.prefs; + const kPrefName = "ui.cursor_snapping.always_enabled"; + if (aSet) { + prefSvc.setBoolPref(kPrefName, true); + } else if (prefSvc.prefHasUserValue(kPrefName)) { + prefSvc.clearUserPref(kPrefName); + } +} + +SetPrefs(true); +runNextTest(); + +]]> +</script> +</window> diff --git a/toolkit/content/tests/chrome/test_custom_element_base.xhtml b/toolkit/content/tests/chrome/test_custom_element_base.xhtml new file mode 100644 index 0000000000..77cc8819a3 --- /dev/null +++ b/toolkit/content/tests/chrome/test_custom_element_base.xhtml @@ -0,0 +1,363 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Custom Element Base Class Tests" + onload="runTests();" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <button id="one"/> + <simpleelement id="two" style="-moz-user-focus: normal;"/> + <simpleelement id="three" disabled="true" style="-moz-user-focus: normal;"/> + <button id="four"/> + <inherited-element-declarative foo="fuagra" empty-string=""></inherited-element-declarative> + <inherited-element-derived foo="fuagra"></inherited-element-derived> + <inherited-element-shadowdom-declarative foo="fuagra" empty-string=""></inherited-element-shadowdom-declarative> + <inherited-element-imperative foo="fuagra" empty-string=""></inherited-element-imperative> + <inherited-element-beforedomloaded foo="fuagra" empty-string=""></inherited-element-beforedomloaded> + + <!-- test code running before page load goes here --> + <script type="application/javascript"><![CDATA[ + class InheritAttrsChangeBeforDOMLoaded extends MozXULElement { + static get inheritedAttributes() { + return { + "label": "foo", + }; + } + connectedCallback() { + this.append(MozXULElement.parseXULToFragment(`<label />`)); + this.label = this.querySelector("label"); + + this.initializeAttributeInheritance(); + is(this.label.getAttribute("foo"), "fuagra", + "InheritAttrsChangeBeforDOMLoaded: attribute should be propagated #1"); + + this.setAttribute("foo", "chuk&gek"); + is(this.label.getAttribute("foo"), "chuk&gek", + "InheritAttrsChangeBeforDOMLoaded: attribute should be propagated #2"); + } + } + customElements.define("inherited-element-beforedomloaded", + InheritAttrsChangeBeforDOMLoaded); + ]]> + </script> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + SimpleTest.waitForExplicitFinish(); + + async function runTests() { + ok(MozXULElement, "MozXULElement defined on the window"); + testMixin(); + testBaseControl(); + testBaseControlMixin(); + testBaseText(); + testParseXULToFragment(); + testInheritAttributes(); + await testCustomInterface(); + + let htmlWin = await new Promise(resolve => { + let htmlIframe = document.createXULElement("iframe"); + htmlIframe.src = "file_empty.xhtml"; + htmlIframe.onload = () => resolve(htmlIframe.contentWindow); + document.documentElement.appendChild(htmlIframe); + }); + + ok(htmlWin.MozXULElement, "MozXULElement defined on a chrome HTML window"); + SimpleTest.finish(); + } + + function testMixin() { + ok(MozElements.MozElementMixin, "Mixin exists"); + let MixedHTMLElement = MozElements.MozElementMixin(HTMLElement); + ok(MixedHTMLElement.insertFTLIfNeeded, "Mixed in class contains helper functions"); + } + + function testBaseControl() { + ok(MozElements.BaseControl, "BaseControl exists"); + ok("disabled" in MozElements.BaseControl.prototype, + "BaseControl prototype contains base control attributes"); + } + + function testBaseControlMixin() { + ok(MozElements.BaseControlMixin, "Mixin exists"); + let MixedHTMLSpanElement = MozElements.MozElementMixin(HTMLSpanElement); + let HTMLSpanBaseControl = MozElements.BaseControlMixin(MixedHTMLSpanElement); + ok("disabled" in HTMLSpanBaseControl.prototype, "Mixed in class prototype contains base control attributes"); + } + + function testBaseText() { + ok(MozElements.BaseText, "BaseText exists"); + ok("label" in MozElements.BaseText.prototype, + "BaseText prototype inherits BaseText attributes"); + ok("disabled" in MozElements.BaseText.prototype, + "BaseText prototype inherits BaseControl attributes"); + } + + function testParseXULToFragment() { + ok(MozXULElement.parseXULToFragment, "parseXULToFragment helper exists"); + + let frag = MozXULElement.parseXULToFragment(`<deck id='foo' />`); + ok(DocumentFragment.isInstance(frag)); + + document.documentElement.appendChild(frag); + + let deck = document.documentElement.lastChild; + ok(deck instanceof MozXULElement, "instance of MozXULElement"); + ok(XULElement.isInstance(deck), "instance of XULElement"); + is(deck.id, "foo", "attribute set"); + is(deck.selectedIndex, 0, "Custom Element is property attached"); + deck.remove(); + + info("Checking that whitespace text is removed but non-whitespace text isn't"); + let boxWithWhitespaceText = MozXULElement.parseXULToFragment(`<box> </box>`).querySelector("box"); + is(boxWithWhitespaceText.textContent, "", "Whitespace removed"); + let boxWithNonWhitespaceText = MozXULElement.parseXULToFragment(`<box>foo</box>`).querySelector("box"); + is(boxWithNonWhitespaceText.textContent, "foo", "Non-whitespace not removed"); + + try { + // we didn't encode the & as & + MozXULElement.parseXULToFragment(`<box id="foo=1&bar=2"/>`); + ok(false, "parseXULToFragment should've thrown an exception for not-well-formed XML"); + } + catch (ex) { + is(ex.message, "not well-formed XML", "parseXULToFragment threw the wrong message"); + } + } + + function testInheritAttributes() { + class InheritsElementDeclarative extends MozXULElement { + static get inheritedAttributes() { + return { + "label": "text=label,foo,empty-string,bardo=bar", + "unmatched": "foo", // Make sure we don't throw on unmatched selectors + }; + } + + connectedCallback() { + this.textContent = ""; + this.append(MozXULElement.parseXULToFragment(`<label />`)); + this.label = this.querySelector("label"); + this.initializeAttributeInheritance(); + } + } + customElements.define("inherited-element-declarative", InheritsElementDeclarative); + let declarativeEl = document.querySelector("inherited-element-declarative"); + ok(declarativeEl, "declarative inheritance element exists"); + + class InheritsElementDerived extends InheritsElementDeclarative { + static get inheritedAttributes() { + return { label: "renamedfoo=foo" }; + } + } + customElements.define("inherited-element-derived", InheritsElementDerived); + + class InheritsElementShadowDOMDeclarative extends MozXULElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }); + } + static get inheritedAttributes() { + return { + "label": "text=label,foo,empty-string,bardo=bar", + "unmatched": "foo", // Make sure we don't throw on unmatched selectors + }; + } + + connectedCallback() { + this.shadowRoot.textContent = ""; + this.shadowRoot.append(MozXULElement.parseXULToFragment(`<label />`)); + this.label = this.shadowRoot.querySelector("label"); + this.initializeAttributeInheritance(); + } + } + customElements.define("inherited-element-shadowdom-declarative", InheritsElementShadowDOMDeclarative); + let shadowDOMDeclarativeEl = document.querySelector("inherited-element-shadowdom-declarative"); + ok(shadowDOMDeclarativeEl, "declarative inheritance element with shadow DOM exists"); + + class InheritsElementImperative extends MozXULElement { + static get observedAttributes() { + return [ "label", "foo", "empty-string", "bar" ]; + } + + attributeChangedCallback(name, oldValue, newValue) { + if (this.label && oldValue != newValue) { + this.inherit(); + } + } + + inherit() { + let map = { + "label": [[ "label", "text" ]], + "foo": [[ "label", "foo" ]], + "empty-string": [[ "label", "empty-string" ]], + "bar": [[ "label", "bardo" ]], + }; + for (let attr of InheritsElementImperative.observedAttributes) { + this.inheritAttribute(map[attr], attr); + } + } + + connectedCallback() { + // Typically `initializeAttributeInheritance` handles this for us: + this._inheritedElements = null; + + this.textContent = ""; + this.append(MozXULElement.parseXULToFragment(`<label />`)); + this.label = this.querySelector("label"); + this.inherit(); + } + } + + customElements.define("inherited-element-imperative", InheritsElementImperative); + let imperativeEl = document.querySelector("inherited-element-imperative"); + ok(imperativeEl, "imperative inheritance element exists"); + + function checkElement(el) { + is(el.label.getAttribute("foo"), "fuagra", "predefined attribute @foo"); + ok(el.label.hasAttribute("empty-string"), "predefined attribute @empty-string"); + ok(!el.label.hasAttribute("bardo"), "predefined attribute @bardo"); + ok(!el.label.textContent, "predefined attribute @label"); + + el.setAttribute("empty-string", "not-empty-anymore"); + is(el.label.getAttribute("empty-string"), "not-empty-anymore", + "attribute inheritance: empty-string"); + + el.setAttribute("label", "label-test"); + is(el.label.textContent, "label-test", + "attribute inheritance: text=label attribute change"); + + el.setAttribute("bar", "bar-test"); + is(el.label.getAttribute("bardo"), "bar-test", + "attribute inheritance: `=` mapping"); + + el.label.setAttribute("bardo", "changed-from-child"); + is(el.label.getAttribute("bardo"), "changed-from-child", + "attribute inheritance: doesn't apply when host attr hasn't changed and child attr was changed"); + + el.label.removeAttribute("bardo"); + ok(!el.label.hasAttribute("bardo"), + "attribute inheritance: doesn't apply when host attr hasn't changed and child attr was removed"); + + el.setAttribute("bar", "changed-from-host"); + is(el.label.getAttribute("bardo"), "changed-from-host", + "attribute inheritance: does apply when host attr has changed and child attr was changed"); + + el.removeAttribute("bar"); + ok(!el.label.hasAttribute("bardo"), + "attribute inheritance: does apply when host attr has been removed"); + + el.setAttribute("bar", "changed-from-host-2"); + is(el.label.getAttribute("bardo"), "changed-from-host-2", + "attribute inheritance: does apply when host attr has changed after being removed"); + + // Restore to the original state so this can be ran again with the same element: + el.removeAttribute("label"); + el.removeAttribute("bar"); + } + + for (let el of [declarativeEl, shadowDOMDeclarativeEl, imperativeEl]) { + info(`Running checks for ${el.tagName}`); + checkElement(el); + info(`Remove and re-add ${el.tagName} to make sure attribute inheritance still works`); + el.replaceWith(el); + checkElement(el); + } + + let derivedEl = document.querySelector("inherited-element-derived"); + ok(derivedEl, "derived inheritance element exists"); + ok(!derivedEl.label.hasAttribute("foo"), + "attribute inheritance: base class attribute is not applied in derived class that overrides it"); + ok(derivedEl.label.hasAttribute("renamedfoo"), + "attribute inheritance: attribute defined in derived class is present"); + } + + async function testCustomInterface() { + class SimpleElement extends MozXULElement { + get disabled() { + return this.getAttribute("disabled") == "true"; + } + + set disabled(val) { + if (val) this.setAttribute("disabled", "true"); + else this.removeAttribute("disabled"); + } + + get tabIndex() { + return parseInt(this.getAttribute("tabIndex")) || 0; + } + + set tabIndex(val) { + if (val) this.setAttribute("tabIndex", val); + else this.removeAttribute("tabIndex"); + } + } + + MozXULElement.implementCustomInterface(SimpleElement, [Ci.nsIDOMXULControlElement]); + customElements.define("simpleelement", SimpleElement); + + let twoElement = document.getElementById("two"); + + is(document.documentElement.getCustomInterfaceCallback, undefined, + "No getCustomInterfaceCallback on non-custom element"); + is(typeof twoElement.getCustomInterfaceCallback, "function", + "getCustomInterfaceCallback available on custom element when set"); + is(document.documentElement.QueryInterface, undefined, + "Non-custom element should not have a QueryInterface implementation"); + + // Try various ways to get the custom interface. + + let asControl = twoElement.getCustomInterfaceCallback(Ci.nsIDOMXULControlElement); + + // XXX: Switched to from ok() to todo_is() in Bug 1467712. Follow up in 1500967 + // Not sure if this was suppose to simply check for existence or equality? + todo_is(asControl, twoElement, "getCustomInterface returns interface implementation "); + + asControl = twoElement.QueryInterface(Ci.nsIDOMXULControlElement); + ok(asControl, "QueryInterface to nsIDOMXULControlElement"); + ok(Node.isInstance(asControl), "Control is a Node"); + + // Now make sure that the custom element handles focus/tabIndex as needed by shitfing + // focus around and enabling/disabling the simple elements. + + // Enable Full Keyboard Access emulation on Mac + await SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]}); + + ok(!twoElement.disabled, "two is enabled"); + ok(document.getElementById("three").disabled, "three is disabled"); + + await SimpleTest.promiseFocus(); + ok(document.hasFocus(), "has focus"); + + // This should skip the disabled simpleelement. + synthesizeKey("VK_TAB"); + is(document.activeElement.id, "one", "Tab 1"); + synthesizeKey("VK_TAB"); + is(document.activeElement.id, "two", "Tab 2"); + synthesizeKey("VK_TAB"); + is(document.activeElement.id, "four", "Tab 3"); + + twoElement.disabled = true; + is(twoElement.getAttribute("disabled"), "true", "two disabled after change"); + + synthesizeKey("VK_TAB", { shiftKey: true }); + is(document.activeElement.id, "one", "Tab 1"); + + info("Checking that interfaces get inherited automatically with implementCustomInterface"); + class ExtendedElement extends SimpleElement { } + MozXULElement.implementCustomInterface(ExtendedElement, [Ci.nsIDOMXULSelectControlElement]); + customElements.define("extendedelement", ExtendedElement); + const extendedInstance = document.createXULElement("extendedelement"); + ok(extendedInstance.QueryInterface(Ci.nsIDOMXULSelectControlElement), "interface applied"); + ok(extendedInstance.QueryInterface(Ci.nsIDOMXULControlElement), "inherited interface applied"); + } + ]]> + </script> +</window> diff --git a/toolkit/content/tests/chrome/test_custom_element_delay_connection.xhtml b/toolkit/content/tests/chrome/test_custom_element_delay_connection.xhtml new file mode 100644 index 0000000000..f40277d198 --- /dev/null +++ b/toolkit/content/tests/chrome/test_custom_element_delay_connection.xhtml @@ -0,0 +1,110 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Custom Element Base Delayed Connected" + onload="runTests();" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <script type="application/javascript"><![CDATA[ + let nativeDOMContentLoadedFired = false; + document.addEventListener("DOMContentLoaded", () => { + nativeDOMContentLoadedFired = true; + }, { once: true }); + + // To test `delayConnectedCallback` and `isConnectedAndReady` we have to run this before + // DOMContentLoaded, which is why this is done in a separate script that runs + // immediately and not in `runTests`. + let delayedConnectionPromise = new Promise(resolve => { + + let numSkippedAttributeChanges = 0; + let numDelayedConnections = 0; + let numDelayedDisconnections = 0; + let finishedWaitingForDOMReady = false; + + // Register this custom element before DOMContentLoaded has fired and before it's parsed in + // the markup: + customElements.define("delayed-connection", class DelayedConnection extends MozXULElement { + static get observedAttributes() { return ["foo"]; } + attributeChangedCallback() { + ok(!this.isConnectedAndReady, + "attributeChangedCallback fires before isConnectedAndReady"); + ok(!nativeDOMContentLoadedFired, + "attributeChangedCallback fires before nativeDOMContentLoadedFired"); + numSkippedAttributeChanges++; + } + connectedCallback() { + if (this.delayConnectedCallback()) { + ok(!finishedWaitingForDOMReady, + "connectedCallback with delayConnectedCallback fires before finishedWaitingForDOMReady"); + ok(!this.isConnectedAndReady, + "connectedCallback with delayConnectedCallback fires before isConnectedAndReady"); + ok(!nativeDOMContentLoadedFired, + "connectedCallback with delayConnectedCallback fires before nativeDOMContentLoadedFired"); + numDelayedConnections++; + return; + } + + ok(!finishedWaitingForDOMReady, + "connectedCallback only fires once when DOM is ready"); + ok(this.isConnectedAndReady, + "isConnectedAndReady during connectedCallback"); + ok(!nativeDOMContentLoadedFired, + "delayed connectedCallback fires before nativeDOMContentLoadedFired"); + + is(numSkippedAttributeChanges, 2, + "Correct number of skipped attribute changes"); + is(numDelayedConnections, 2, + "Correct number of delayed connections"); + is(numDelayedDisconnections, 1, + "Correct number of delated disconnections"); + + finishedWaitingForDOMReady = true; + resolve(); + } + disconnectedCallback() { + ok(this.delayConnectedCallback(), + "disconnectedCallback while DOM not ready"); + is(numDelayedDisconnections, 0, + "disconnectedCallback fired only once"); + numDelayedDisconnections++; + } + }); + }); + + // This should be called after the element is parsed below this. + function mutateDelayedConnection() { + // Fire connectedCallback and attributeChangedCallback twice before DOMContentLoaded + // fires. The first connectedCallback is due to the parse and the second due to re-appending. + let delayedConnection = document.querySelector("delayed-connection"); + delayedConnection.setAttribute("foo", "bar"); + delayedConnection.remove(); + delayedConnection.setAttribute("foo", "bat"); + document.documentElement.append(delayedConnection); + } + ]]> + </script> + + <delayed-connection></delayed-connection> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + mutateDelayedConnection(); + + async function runTests() { + info("Waiting for delayed connection to fire"); + ok(nativeDOMContentLoadedFired, + "nativeDOMContentLoadedFired is true in runTests"); + await delayedConnectionPromise; + SimpleTest.finish(); + } + ]]> + </script> +</window>
\ No newline at end of file diff --git a/toolkit/content/tests/chrome/test_custom_element_parts.html b/toolkit/content/tests/chrome/test_custom_element_parts.html new file mode 100644 index 0000000000..e46e93e206 --- /dev/null +++ b/toolkit/content/tests/chrome/test_custom_element_parts.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title><!-- Shadow Parts issue with xul/xbl domparser --></title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <style>custom-button::part(foo) { background: red; }</style> + <script> + add_task(async function test() { + // A simplified version of what MozXULElement.parseXULToFragment does + let parser = new DOMParser(); + parser.forceEnableXULXBL(); + let doc = parser.parseFromString(`<box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><box part="foo">there</box></box>`, `application/xml`); + let range = doc.createRange(); + range.selectNodeContents(doc.querySelector("box")); + let frag = range.extractContents(); + + customElements.define("custom-button", class extends HTMLElement { + constructor() { + super(); + this.attachShadow({mode: "open"}); + this.shadowRoot.appendChild(document.importNode(frag, true)); + } + }); + let button = document.createElement("custom-button"); + document.body.appendChild(button); + + let box = button.shadowRoot.querySelector("box"); + + // XXX: this fixes it + // box.removeAttribute("part"); + // box.setAttribute("part", "foo"); + + is(window.getComputedStyle(box).getPropertyValue("background-color"), "rgb(255, 0, 0)", "part applied"); + }); + </script> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +</body> +</html> diff --git a/toolkit/content/tests/chrome/test_deck.xhtml b/toolkit/content/tests/chrome/test_deck.xhtml new file mode 100644 index 0000000000..d6f824b357 --- /dev/null +++ b/toolkit/content/tests/chrome/test_deck.xhtml @@ -0,0 +1,133 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for deck + --> +<window title="Deck Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<deck id="deck1" style="padding-top: 5px; padding-bottom: 12px;"> + <button id="d1b1" label="Button One"/> + <button id="d1b2" label="Button Two is larger" style="height: 80px; margin: 1px;"/> +</deck> +<deck id="deck2" selectedIndex="1"> + <button id="d2b1" label="Button One"/> + <button id="d2b2" label="Button Two"/> +</deck> +<deck id="deck3" selectedIndex="1"> + <button id="d3b1" label="Remove me"/> + <button id="d3b2" label="Keep me selected"/> +</deck> +<deck id="deck4" selectedIndex="5"> + <button id="d4b1" label="Remove me"/> + <button id="d4b2" label="Remove me"/> + <button id="d4b3" label="Remove me"/> + <button id="d4b4" label="Button 4"/> + <button id="d4b5" label="Button 5"/> + <button id="d4b6" label="Keep me selected"/> + <button id="d4b7" label="Button 7"/> +</deck> +<deck id="deck5" selectedIndex="2"> + <button id="d5b1" label="Button 1"/> + <button id="d5b2" label="Button 2"/> + <button id="d5b3" label="Keep me selected"/> + <button id="d5b4" label="Remove me"/> + <button id="d5b5" label="Remove me"/> + <button id="d5b6" label="Remove me"/> +</deck> + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ +add_task(async function run_tests() { + test_deck(); + await test_deck_child_removal(); +}); + +function test_deck() +{ + var deck = $("deck1"); + is(deck.selectedIndex, 0, "deck one selectedIndex"); + // this size is the button height, 80, plus the button padding of 1px on each side, + // plus the deck's 5px top padding and the 12px bottom padding. + var rect = deck.getBoundingClientRect(); + is(Math.round(rect.bottom) - Math.round(rect.top), 99, "deck size of largest child"); + synthesizeMouseExpectEvent(deck, 12, 12, { }, $("d1b1"), "click", "mouse on deck one"); + + // change the selected page of the deck and ensure that the mouse click goes + // to the button on that page + deck.selectedIndex = 1; + is(deck.selectedIndex, 1, "deck one selectedIndex after change"); + synthesizeMouseExpectEvent(deck, 9, 9, { }, $("d1b2"), "click", "mouse on deck one after change"); + + deck = $("deck2"); + is(deck.selectedIndex, 1, "deck two selectedIndex"); + synthesizeMouseExpectEvent(deck, 9, 9, { }, $("d2b2"), "click", "mouse on deck two"); +} + +async function test_deck_child_removal() +{ + // Start with a simple case where we have two child nodes in a deck, with + // the second child (index 1) selected. Removing the first node should + // automatically set the selectedIndex at 0. + let deck = $("deck3"); + let child = $("d3b1"); + is(deck.selectedIndex, 1, "Should have the deck element at index 1 selected"); + + // Remove the child at the 0th index. The deck should automatically + // set the selectedIndex to "0". + child.remove(); + + await Promise.resolve(); + + is(deck.selectedIndex, 0, "Should have the deck element at index 0 selected"); + + // Now scale it up by using a deck with 7 child nodes, and remove the + // first three, making sure that the selectedIndex is decremented + // each time. + deck = $("deck4"); + let expectedIndex = 5; + is(deck.selectedIndex, expectedIndex, + "Should have the deck element at index " + expectedIndex + " selected"); + + for (let i = 0; i < 3; ++i) { + deck.firstChild.remove(); + expectedIndex--; + await Promise.resolve(); + is(deck.selectedIndex, expectedIndex, + "Should have the deck element at index " + expectedIndex + " selected"); + } + + // Check that removing the currently selected node doesn't change + // behaviour. + deck.childNodes[expectedIndex].remove(); + await Promise.resolve(); + is(deck.selectedIndex, expectedIndex, + "The selectedIndex should not change when removing the node " + + "at the selected index."); + + // Finally, make sure we haven't changed the behaviour when removing + // nodes at indexes greater than the selected node. + deck = $("deck5"); + expectedIndex = 2; + await Promise.resolve(); + is(deck.selectedIndex, expectedIndex, + "Should have the deck element at index " + expectedIndex + " selected"); + + // And then remove all of the nodes, starting from last to first, making + // sure that the selectedIndex does not change. + while (deck.lastChild) { + deck.lastChild.remove(); + await Promise.resolve(); + is(deck.selectedIndex, expectedIndex, + "Should have the deck element at index " + expectedIndex + " selected"); + } +} +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_dialog_button.xhtml b/toolkit/content/tests/chrome/test_dialog_button.xhtml new file mode 100644 index 0000000000..8c973839c1 --- /dev/null +++ b/toolkit/content/tests/chrome/test_dialog_button.xhtml @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta charset="utf-8" /> + <title><!-- Test with dialog & buttons --></title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script><![CDATA[ + add_task(async function test_dialog_button_accesskey() { + var win = window.browsingContext.topChromeWindow.openDialog( + "dialog_button.xhtml", + "_new", + "chrome,dialog" + ); + await new Promise((r) => win.addEventListener("load", r, { once: true })); + + let dialogClosed = new Promise((r) => { + win.document.addEventListener("dialogclosing", r, { once: true }); + }); + // https://bugzilla.mozilla.org/show_bug.cgi?id=1625632 + // When pressing an accesskey for a built in dialog button while a regular button is focused, + // it should forward to the dialog. + win.document.querySelector("#button").focus(); + synthesizeKey("a", {}, win); + await dialogClosed; + ok(true, "Accesskey on focused button"); + }); + ]]></script> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +</body> +</html> diff --git a/toolkit/content/tests/chrome/test_dialogfocus.xhtml b/toolkit/content/tests/chrome/test_dialogfocus.xhtml new file mode 100644 index 0000000000..10e60ac776 --- /dev/null +++ b/toolkit/content/tests/chrome/test_dialogfocus.xhtml @@ -0,0 +1,137 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<button id="test" label="Test"/> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestCompleteLog(); + +var expected = [ "one", "_extra2", "tab", "one", "tabbutton2", "tabbutton", "two", "textbox-yes", "one", "root" ]; +// non-Mac will always focus the default button if any of the dialog buttons +// would be focused +if (!navigator.platform.includes("Mac")) + expected[1] = "_accept"; + +let extraDialog = "data:application/xhtml+xml,<window id='root'><dialog " + + "buttons='none' " + + "xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'>" + + "<button id='nonbutton' noinitialfocus='true'/></dialog></window>"; + +var step = 0; +var fullKeyboardAccess = false; + +function startTest() +{ + var testButton = document.getElementById("test"); + synthesizeKey("KEY_Tab"); + fullKeyboardAccess = (document.activeElement == testButton); + info("We " + (fullKeyboardAccess ? "have" : "don't have") + " full keyboard access"); + runTest(); +} + +function runTest() +{ + step++; + info("runTest(), step = " + step + ", expected = " + expected[step - 1]); + if (step > expected.length || (!fullKeyboardAccess && step == 2)) { + info("finishing"); + SimpleTest.finish(); + return; + } + + var expectedFocus = expected[step - 1]; + let filename = expectedFocus == "root" ? "dialog_dialogfocus2.xhtml" : "dialog_dialogfocus.xhtml"; + var win = window.browsingContext.topChromeWindow.openDialog(filename, "_new", "chrome,dialog", step); + + function checkDialogFocus(event) + { + info(`checkDialogFocus()`); + let match = false; + let activeElement = win.document.activeElement; + let dialog = win.document.getElementById("dialog-focus"); + + if (activeElement == dialog) { + let shadowActiveElement = + dialog.shadowRoot.activeElement; + if (shadowActiveElement) { + activeElement = shadowActiveElement; + } + } + // if full keyboard access is not on, just skip the tests + if (fullKeyboardAccess) { + if (!(Element.isInstance(event.target))) { + info("target not an Element"); + return; + } + + if (expectedFocus[0] == "_") + match = (activeElement.getAttribute("dlgtype") == expectedFocus.substring(1)); + else + match = (activeElement.id == expectedFocus); + info("match = " + match); + if (!match) + return; + } + else { + match = (activeElement == win.document.documentElement); + info("match = " + match); + } + + win.removeEventListener("focus", checkDialogFocusEvent, true); + dialog.shadowRoot.removeEventListener( + "focus", checkDialogFocusEvent, true); + ok(match, "focus step " + step); + + win.close(); + SimpleTest.waitForFocus(runTest, window); + } + + let finalCheckInitiated = false; + function checkDialogFocusEvent(event) { + // Delay to have time for focus/blur to occur. + if (expectedFocus == "root") { + if (!finalCheckInitiated) { + setTimeout(() => { + is(win.document.activeElement, win.document.documentElement, + "No other focus but root"); + win.close(); + SimpleTest.waitForFocus(runTest, window); + }, 0); + finalCheckInitiated = true; + } + } else { + checkDialogFocus(event); + } + } + win.addEventListener("focus", checkDialogFocusEvent, true); + win.addEventListener("load", () => { + win.document.getElementById("dialog-focus").shadowRoot.addEventListener( + "focus", checkDialogFocusEvent, true); + }); +} + +SimpleTest.waitForFocus(startTest, window); + +]]> + +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_edit_contextmenu.html b/toolkit/content/tests/chrome/test_edit_contextmenu.html new file mode 100644 index 0000000000..88140efa9c --- /dev/null +++ b/toolkit/content/tests/chrome/test_edit_contextmenu.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1513343 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1513343</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + async function runTest() { + let win = window.browsingContext.topChromeWindow.open("file_edit_contextmenu.xhtml", "_blank", "chrome,width=600,height=600"); + await new Promise(r => win.addEventListener("load", r, { once: true })); + await SimpleTest.promiseFocus(win); + + const elements = [ + win.document.querySelector("textarea"), + win.document.querySelector("input"), + win.document.querySelector("search-textbox"), + win.document.querySelector("shadow-input").shadowRoot.querySelector("input"), + ]; + for (const element of elements) { + await testElement(element, win); + } + win.close(); + SimpleTest.finish(); + } + + async function testElement(element, win) { + ok(element, "element exists"); + + info("Synthesizing a key so 'Undo' will be enabled"); + element.focus(); + synthesizeKey("x", {}, win); + is(element.value, "x", "initial value"); + + element.select(); + synthesizeKey("c", { accelKey: true }, win); // copy to clipboard + synthesizeKey("KEY_ArrowRight", {}, win); // drop selection to disable cut and copy context menu items + + win.document.addEventListener("contextmenu", (e) => { + info("Calling prevent default on the first contextmenu event"); + e.preventDefault(); + }, { once: true }); + synthesizeMouseAtCenter(element, {type: "contextmenu"}, win); + ok(!win.document.getElementById("textbox-contextmenu"), "contextmenu with preventDefault() doesn't run"); + + let popupshown = new Promise(r => win.addEventListener("popupshown", r, { once: true })); + synthesizeMouseAtCenter(element, {type: "contextmenu"}, win); + let contextmenu = win.document.getElementById("textbox-contextmenu"); + ok(contextmenu, "context menu exists after right click"); + await popupshown; + + // Check that we only got the one context menu, and not two. + let outerContextmenu = win.document.getElementById("outer-context-menu"); + ok(outerContextmenu.state == "closed", "the outer context menu state is is not closed, it's: " + outerContextmenu.state); + + ok(!contextmenu.querySelector("[command=cmd_undo]").hasAttribute("disabled"), "undo enabled"); + ok(contextmenu.querySelector("[command=cmd_cut]").hasAttribute("disabled"), "cut disabled"); + ok(contextmenu.querySelector("[command=cmd_copy]").hasAttribute("disabled"), "copy disabled"); + ok(!contextmenu.querySelector("[command=cmd_paste]").hasAttribute("disabled"), "paste enabled"); + ok(contextmenu.querySelector("[command=cmd_delete]").hasAttribute("disabled"), "delete disabled"); + ok(!contextmenu.querySelector("[command=cmd_selectAll]").hasAttribute("disabled"), "select all enabled"); + + let popuphidden = new Promise(r => win.addEventListener("popuphidden", r, { once: true })); + + contextmenu.activateItem(contextmenu.querySelector("[command=cmd_undo]")); + + await popuphidden; + + is(element.value, "", "undo worked"); + contextmenu.remove(); + } + </script> +</head> +<body onload="runTest()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1513343">Mozilla Bug 1513343</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/chrome/test_editor_for_input_with_autocomplete.html b/toolkit/content/tests/chrome/test_editor_for_input_with_autocomplete.html new file mode 100644 index 0000000000..91f0f159b4 --- /dev/null +++ b/toolkit/content/tests/chrome/test_editor_for_input_with_autocomplete.html @@ -0,0 +1,83 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Basic editor behavior for HTML input element with autocomplete</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="file_editor_with_autocomplete.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <iframe id="formTarget" name="formTarget"></iframe> + <form action="data:text/html," target="formTarget"> + <input name="test" id="input"><input type="submit"> + </form> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +async function registerWord(aTarget, aAutoCompleteController) { + // Register a word to the form history. + let chromeScript = SpecialPowers.loadChromeScript(function addEntry() { +/* eslint-env mozilla/chrome-script */ + let {FormHistory} = ChromeUtils.importESModule( + "resource://gre/modules/FormHistory.sys.mjs" + ); + FormHistory.update({ op: "add", fieldname: "test", value: "Mozilla" }); + }); + aTarget.focus(); + aTarget.value = "Mozilla"; + + await waitForCondition(() => { + if (aAutoCompleteController.searchStatus == aAutoCompleteController.STATUS_NONE || + aAutoCompleteController.searchStatus == aAutoCompleteController.STATUS_COMPLETE_NO_MATCH) { + aAutoCompleteController.startSearch("Mozilla"); + } + return aAutoCompleteController.matchCount > 0; + }); + chromeScript.destroy(); + aTarget.value = ""; + synthesizeKey("KEY_Escape"); +} + +async function runTests() { + var formFillController = + SpecialPowers.getFormFillController() + .QueryInterface(Ci.nsIAutoCompleteInput); + var originalFormFillTimeout = formFillController.timeout; + + SpecialPowers.attachFormFillControllerTo(window); + var target = document.getElementById("input"); + + // Register a word to the form history. + await registerWord(target, formFillController.controller); + + let tests1 = new nsDoTestsForEditorWithAutoComplete( + "Testing on HTML input (asynchronously search)", + window, target, formFillController.controller, is, todo_is, + function() { return target.value; }); + await tests1.run(); + + target.setAttribute("timeout", 0); + let tests2 = new nsDoTestsForEditorWithAutoComplete( + "Testing on HTML input (synchronously search)", + window, target, formFillController.controller, is, todo_is, + function() { return target.value; }); + await tests2.run(); + + formFillController.timeout = originalFormFillTimeout; + SpecialPowers.detachFormFillControllerFrom(window); + SimpleTest.finish(); +} + +SimpleTest.waitForFocus(runTests); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/chrome/test_findbar.xhtml b/toolkit/content/tests/chrome/test_findbar.xhtml new file mode 100644 index 0000000000..a0329adfbd --- /dev/null +++ b/toolkit/content/tests/chrome/test_findbar.xhtml @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=257061 +https://bugzilla.mozilla.org/show_bug.cgi?id=288254 +--> +<window title="Mozilla Bug 257061 and Bug 288254" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=257061">Mozilla Bug 257061</a> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=288254">Mozilla Bug 288254</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 257061 and Bug 288254 **/ +SimpleTest.waitForExplicitFinish(); + +// Since bug 978861, this pref is set to `false` on OSX. For this test, we'll +// set it `true` to disable the find clipboard on OSX, which interferes with +// our tests. +SpecialPowers.pushPrefEnv({ + set: [["accessibility.typeaheadfind.prefillwithselection", true]] +}, () => { + window.openDialog("findbar_window.xhtml", "findbartest", "chrome,width=600,height=600,noopener", window); +}); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_findbar_entireword.xhtml b/toolkit/content/tests/chrome/test_findbar_entireword.xhtml new file mode 100644 index 0000000000..641e407aa0 --- /dev/null +++ b/toolkit/content/tests/chrome/test_findbar_entireword.xhtml @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=269442 +--> +<window title="Mozilla Bug 269442" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=269442"> + Mozilla Bug 269442 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <script class="testbody" type="application/javascript"> + <![CDATA[ + + /** Test for Bug 269442 **/ + SimpleTest.waitForExplicitFinish(); + window.openDialog("findbar_entireword_window.xhtml", "269442test", + "chrome,width=600,height=600,noopener", window); + + ]]> + </script> + +</window> diff --git a/toolkit/content/tests/chrome/test_findbar_events.xhtml b/toolkit/content/tests/chrome/test_findbar_events.xhtml new file mode 100644 index 0000000000..f8e96d8ba8 --- /dev/null +++ b/toolkit/content/tests/chrome/test_findbar_events.xhtml @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=793275 +--> +<window title="Mozilla Bug 793275" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=793275"> + Mozilla Bug 793275 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <script class="testbody" type="application/javascript"> + <![CDATA[ + + /** Test for Bug 793275 **/ + SimpleTest.waitForExplicitFinish(); + window.openDialog("findbar_events_window.xhtml", "793275test", + "chrome,width=600,height=600,noopener", window); + + ]]> + </script> + +</window> diff --git a/toolkit/content/tests/chrome/test_frames.xhtml b/toolkit/content/tests/chrome/test_frames.xhtml new file mode 100644 index 0000000000..a849b1d6b0 --- /dev/null +++ b/toolkit/content/tests/chrome/test_frames.xhtml @@ -0,0 +1,61 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script><![CDATA[ +SimpleTest.waitForExplicitFinish(); + +function runTest() { + for (let i = 1; i <= 3; i++) { + let frame = document.getElementById("frame" + i); + ok(XULFrameElement.isInstance(frame), "XULFrameElement " + i); + + // Check the various fields to ensure that they have the correct type. + ok(frame.docShell instanceof Ci.nsIDocShell, "docShell " + i); + ok(frame.webNavigation instanceof Ci.nsIWebNavigation, "webNavigation " + i); + + let contentWindow = frame.contentWindow; + let contentDocument = frame.contentDocument; + ok(Window.isInstance(contentWindow), "contentWindow " + i); + ok(Document.isInstance(contentDocument), "contentDocument " + i); + is(contentDocument.body.id, "thechildbody" + i, "right document body " + i); + + // These fields should all be read-only. + frame.docShell = null; + ok(frame.docShell instanceof Ci.nsIDocShell, "docShell after set " + i); + frame.webNavigation = null; + ok(frame.webNavigation instanceof Ci.nsIWebNavigation, "webNavigation after set " + i); + frame.contentWindow = window; + is(frame.contentWindow, contentWindow, "contentWindow after set " + i); + frame.contentDocument = document; + is(frame.contentDocument, contentDocument, "contentDocument after set " + i); + } + + // A non-frame element should not have these fields. + let button = document.getElementById("nonframe"); + ok(!(XULFrameElement.isInstance(button)), "XULFrameElement non frame"); + is(button.docShell, undefined, "docShell non frame"); + is(button.webNavigation, undefined, "webNavigation non frame"); + is(button.contentWindow, undefined, "contentWindow non frame"); + is(button.contentDocument, undefined, "contentDocument non frame"); + + SimpleTest.finish(); +} +]]> +</script> + +<iframe id="frame1" src="data:text/html,<body id='thechildbody1'>"/> +<browser id="frame2" src="data:text/html,<body id='thechildbody2'>"/> +<editor id="frame3" src="data:text/html,<body id='thechildbody3'>"/> +<button id="nonframe"/> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<div id="content" style="display: none"></div> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_hiddenitems.xhtml b/toolkit/content/tests/chrome/test_hiddenitems.xhtml new file mode 100644 index 0000000000..79f06c8890 --- /dev/null +++ b/toolkit/content/tests/chrome/test_hiddenitems.xhtml @@ -0,0 +1,76 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=317422 +--> +<window title="Mozilla Bug 317422" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=317422" + target="_blank">Mozilla Bug 317422</a> + </body> + + <richlistbox id="richlistbox" seltype="multiple"> + <richlistitem id="richlistbox_item1"><label value="Item 1"/></richlistitem> + <richlistitem id="richlistbox_item2"><label value="Item 2"/></richlistitem> + <richlistitem id="richlistbox_item3" hidden="true"><label value="Item 3"/></richlistitem> + <richlistitem id="richlistbox_item4"><label value="Item 4"/></richlistitem> + <richlistitem id="richlistbox_item5" collapsed="true"><label value="Item 5"/></richlistitem> + <richlistitem id="richlistbox_item6"><label value="Item 6"/></richlistitem> + <richlistitem id="richlistbox_item7" hidden="true"><label value="Item 7"/></richlistitem> + </richlistbox> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +/** Test for Bug 317422 **/ +SimpleTest.waitForExplicitFinish(); + +function testListbox(id) +{ + var listbox = document.getElementById(id); + listbox.focus(); + is(listbox.getRowCount(), 7, id + ": Returned the wrong number of rows"); + is(listbox.getItemAtIndex(2).id, id + "_item3", id + ": Should still return hidden items"); + listbox.selectedIndex = 0; + is(listbox.selectedItem.id, id + "_item1", id + ": First item was not selected"); + sendKey("DOWN"); + is(listbox.selectedItem.id, id + "_item2", id + ": Down didn't move to second item"); + sendKey("DOWN"); + is(listbox.selectedItem.id, id + "_item4", id + ": Down didn't skip hidden item"); + sendKey("DOWN"); + is(listbox.selectedItem.id, id + "_item6", id + ": Down didn't skip collapsed item"); + sendKey("UP"); + is(listbox.selectedItem.id, id + "_item4", id + ": Up didn't skip collapsed item"); + sendKey("UP"); + is(listbox.selectedItem.id, id + "_item2", id + ": Up didn't skip hidden item"); + listbox.selectAll(); + is(listbox.selectedItems.length, 7, id + ": Should have still selected all items"); + listbox.selectedIndex = 2; + ok(listbox.selectedItem == listbox.getItemAtIndex(2), id + ": Should have selected the hidden item"); + listbox.selectedIndex = 0; + sendKey("END"); + is(listbox.selectedItem.id, id + "_item6", id + ": Should have moved to the last unhidden item"); + sendMouseEvent({type: 'click'}, id + "_item1"); + ok(listbox.selectedItem == listbox.getItemAtIndex(0), id + ": Should have selected the first item"); + is(listbox.selectedItems.length, 1, id + ": Should only be one selected item"); + sendMouseEvent({type: 'click', shiftKey: true}, id + "_item6"); + is(listbox.selectedItems.length, 4, id + ": Should have selected all visible items"); + listbox.selectedIndex = 0; + sendKey("PAGE_DOWN"); + is(listbox.selectedItem.id, id + "_item6", id + ": Page down should go to the last visible item"); + sendKey("PAGE_UP"); + is(listbox.selectedItem.id, id + "_item1", id + ": Page up should go to the first visible item"); +} + +window.onload = function runTests() { + testListbox("richlistbox"); + SimpleTest.finish(); +}; + ]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_hiddenpaging.xhtml b/toolkit/content/tests/chrome/test_hiddenpaging.xhtml new file mode 100644 index 0000000000..59bcd0e432 --- /dev/null +++ b/toolkit/content/tests/chrome/test_hiddenpaging.xhtml @@ -0,0 +1,83 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=317422 +--> +<window title="Mozilla Bug 317422" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <style xmlns="http://www.w3.org/1999/xhtml"> + /* This makes the richlistbox about 4.5 rows high */ + richlistitem:not([collapsed]) { + min-height: 30px; + } + richlistbox { + height: 135px; + } + </style> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=317422" + target="_blank">Mozilla Bug 317422</a> + </body> + + <richlistbox id="richlistbox" seltype="multiple"> + <richlistitem id="richlistbox_item1"><label value="Item 1"/></richlistitem> + <richlistitem id="richlistbox_item2"><label value="Item 2"/></richlistitem> + <richlistitem id="richlistbox_item3" hidden="true"><label value="Item 3"/></richlistitem> + <richlistitem id="richlistbox_item4"><label value="Item 4"/></richlistitem> + <richlistitem id="richlistbox_item5" collapsed="true"><label value="Item 5"/></richlistitem> + <richlistitem id="richlistbox_item6"><label value="Item 6"/></richlistitem> + <richlistitem id="richlistbox_item7"><label value="Item 7"/></richlistitem> + <richlistitem id="richlistbox_item8"><label value="Item 8"/></richlistitem> + <richlistitem id="richlistbox_item9"><label value="Item 9"/></richlistitem> + <richlistitem id="richlistbox_item10"><label value="Item 10"/></richlistitem> + <richlistitem id="richlistbox_item11"><label value="Item 11"/></richlistitem> + <richlistitem id="richlistbox_item12"><label value="Item 12"/></richlistitem> + <richlistitem id="richlistbox_item13"><label value="Item 13"/></richlistitem> + <richlistitem id="richlistbox_item14"><label value="Item 14"/></richlistitem> + <richlistitem id="richlistbox_item15" hidden="true"><label value="Item 15"/></richlistitem> + </richlistbox> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +/** Test for Bug 317422 **/ +SimpleTest.waitForExplicitFinish(); + +function testRichlistbox() +{ + var id = "richlistbox"; + var listbox = document.getElementById(id); + listbox.focus(); + listbox.selectedIndex = 0; + sendKey("PAGE_DOWN"); + is(listbox.selectedItem.id, id + "_item7", id + ": Page down should go to the item one visible page away"); + is(listbox.getIndexOfFirstVisibleRow(), 6, id + ": Page down should have scrolled down a visible page"); + sendKey("PAGE_DOWN"); + is(listbox.selectedItem.id, id + "_item11", id + ": Second page down should go to the item two visible pages away"); + is(listbox.getIndexOfFirstVisibleRow(), 9, id + ": Second page down should not scroll beyond the end"); + sendKey("PAGE_DOWN"); + is(listbox.selectedItem.id, id + "_item14", id + ": Third page down should go to the last visible item"); + is(listbox.getIndexOfFirstVisibleRow(), 9, id + ": Third page down should not have scrolled at all"); + sendKey("PAGE_UP"); + is(listbox.selectedItem.id, id + "_item10", id + ": Page up should go to the item one visible page away"); + is(listbox.getIndexOfFirstVisibleRow(), 5, id + ": Page up should scroll up a visible page"); + sendKey("PAGE_UP"); + is(listbox.selectedItem.id, id + "_item6", id + ": Second page up should go to the item two visible pages away"); + is(listbox.getIndexOfFirstVisibleRow(), 0, id + ": Second page up should not scroll beyond the start"); + sendKey("PAGE_UP"); + is(listbox.selectedItem.id, id + "_item1", id + ": Third page up should return to the first visible item"); + is(listbox.getIndexOfFirstVisibleRow(), 0, id + ": Third page up should not have scrolled at all"); +} + +window.onload = function runTests() { + testRichlistbox(); + SimpleTest.finish(); +}; + ]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_keys.xhtml b/toolkit/content/tests/chrome/test_keys.xhtml new file mode 100644 index 0000000000..4d4c1ae8f0 --- /dev/null +++ b/toolkit/content/tests/chrome/test_keys.xhtml @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Keys Test" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.openDialog("window_keys.xhtml", "_blank", "chrome,width=200,height=200,noopener", window); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_labelcontrol.xhtml b/toolkit/content/tests/chrome/test_labelcontrol.xhtml new file mode 100644 index 0000000000..06e7f96105 --- /dev/null +++ b/toolkit/content/tests/chrome/test_labelcontrol.xhtml @@ -0,0 +1,54 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for label control="value" + --> +<window title="tabindex" width="500" height="600" + onload="runTests()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<label id="textbox-label" control="textbox" /> +<html:input id="textbox" value="Test"/> +<label id="checkbox-label" control="checkbox" /> +<checkbox id="checkbox" value="Checkbox"/> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function runTests() +{ + let textbox = $("textbox"); + let textboxLabel = $("textbox-label"); + is(textboxLabel.control, "textbox", "textbox control"); + is(textboxLabel.labeledControlElement, textbox, "textbox labeledControlElement"); + + let checkbox = $("checkbox"); + let checkboxLabel = $("checkbox-label"); + is(checkboxLabel.control, "checkbox", "checkbox control"); + is(checkboxLabel.labeledControlElement, checkbox, "checkbox labeledControlElement"); + is(checkbox.accessKey, "", "checkbox accessKey not set"); + checkboxLabel.accessKey = "C"; + is(checkbox.accessKey, "C", "checkbox accessKey set"); + + SimpleTest.finish(); +} + +]]> + +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_largemenu.html b/toolkit/content/tests/chrome/test_largemenu.html new file mode 100644 index 0000000000..eb6b6eff8e --- /dev/null +++ b/toolkit/content/tests/chrome/test_largemenu.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Large Menu Tests</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script> + SimpleTest.waitForExplicitFinish(); + async function runTest() { + // This test exercises non-native menu code. So disable native context menus for this test. + // If we ever get to a point where we don't use any non-native menus on macOS any more, we can + // disable this test on macOS. + await SpecialPowers.pushPrefEnv({ set: [["widget.macos.native-context-menus", false]] }); + + window.openDialog("window_largemenu.xhtml", "_blank", "chrome,width=200,height=200,noopener", window); + } + </script> +</head> +<body onload="setTimeout(runTest, 0);"> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/chrome/test_maximized_persist.xhtml b/toolkit/content/tests/chrome/test_maximized_persist.xhtml new file mode 100644 index 0000000000..1e2e648e4d --- /dev/null +++ b/toolkit/content/tests/chrome/test_maximized_persist.xhtml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<window title="Window Open Test" + onload="runTest('window_maximized_persist.xhtml')" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> +<script src="file_maximized_persist.js"/> +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> +</window> diff --git a/toolkit/content/tests/chrome/test_maximized_persist_with_no_titlebar.xhtml b/toolkit/content/tests/chrome/test_maximized_persist_with_no_titlebar.xhtml new file mode 100644 index 0000000000..3bc36c8e82 --- /dev/null +++ b/toolkit/content/tests/chrome/test_maximized_persist_with_no_titlebar.xhtml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<window title="Window Open Test" + onload="runTest('window_maximized_persist_with_no_titlebar.xhtml')" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> +<script src="file_maximized_persist.js"/> +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> +</window> diff --git a/toolkit/content/tests/chrome/test_menu.xhtml b/toolkit/content/tests/chrome/test_menu.xhtml new file mode 100644 index 0000000000..8ef5c4ca15 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menu.xhtml @@ -0,0 +1,75 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu Destruction Test" + onload="runTests();" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <menubar> + <menu label="top" id="top"> + <menupopup> + <menuitem label="top item"/> + + <menu label="hello" id="nested"> + <menupopup> + <menuitem label="item1"/> + <menuitem label="item2" id="item2"/> + </menupopup> + </menu> + </menupopup> + </menu> + </menubar> + + <script class="testbody" type="application/javascript"> + <![CDATA[ + + SimpleTest.waitForExplicitFinish(); + + function runTests() + { + var menu = document.getElementById("nested"); + + // nsIDOMXULContainerElement::getIndexOfItem(); + var item = document.getElementById("item2"); + is(menu.getIndexOfItem(item), 1, + "nsIDOMXULContainerElement::getIndexOfItem() failed."); + + // nsIDOMXULContainerElement::getItemAtIndex(); + var itemAtIdx = menu.getItemAtIndex(1); + is(itemAtIdx, item, + "nsIDOMXULContainerElement::getItemAtIndex() failed."); + + // nsIDOMXULContainerElement::itemCount + is(menu.itemCount, 2, "nsIDOMXULContainerElement::itemCount failed."); + + // nsIDOMXULContainerElement::parentContainer + var topmenu = document.getElementById("top"); + is(menu.parentContainer, topmenu, + "nsIDOMXULContainerElement::parentContainer failed."); + + // nsIDOMXULContainerElement::appendItem(); + item = menu.appendItem("item3"); + is(menu.getIndexOfItem(item), 2, + "nsIDOMXULContainerElement::appendItem() failed."); + + SimpleTest.finish(); + } + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"> + </p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + +</window> + diff --git a/toolkit/content/tests/chrome/test_menu_activateitem.xhtml b/toolkit/content/tests/chrome/test_menu_activateitem.xhtml new file mode 100644 index 0000000000..8b4bff89d6 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menu_activateitem.xhtml @@ -0,0 +1,169 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu Activation Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <label id="label" value="label" context="contextmenu"/> + <menupopup id="contextmenu"> + <menuitem id="item1" label="Item 1"/> + <menu label="Submenu"> + <menupopup id="submenu"> + <menuitem id="item2" label="Item 2"/> + <menuitem id="item4" label="Item 4" hidden="true"/> + <menu label="Inner Submenu"> + <menupopup id="innersubmenu"> + <menuitem id="item3" label="Item 3"/> + </menupopup> + </menu> + </menupopup> + </menu> + </menupopup> + + <script class="testbody" type="application/javascript"> + <![CDATA[ + +function waitForEvent(subject, eventName) { + return new Promise(resolve => { + subject.addEventListener(eventName, function listener(event) { + if (event.target == subject) { + subject.removeEventListener(eventName, listener); + resolve(); + } + }); + }); +} + +let doNothing = () => {}; + +function checkActivate(desc, menu, item, expectedResult) +{ + desc = desc + " for " + menu.id + ".activateItem(" + item.id + ")"; + try { + menu.activateItem(item); + ok(expectedResult, desc); + } catch (ex) { + ok(!expectedResult, `${desc}: ${ex}`); + } +} + +async function checkActivateItems(desc, openFn, expectedResults) +{ + // Iterate over each menu/submenu and try activating the item from that menu. + // This should only pass when the item is a descendant of that menu and the + // menu is open. + let menus = [ "contextmenu", "submenu", "innersubmenu" ]; + let items = [ "item1", "item2", "item3", "item4" ]; + + let needToOpen = true; + + let contextmenu = document.getElementById("contextmenu"); + + for (let m = 0; m < menus.length; m++) { + let menu = document.getElementById(menus[m]); + + for (let i = 0; i < items.length; i++) { + if (needToOpen) { + await openFn(); + } + + // If an activation is expected, the popup will hide. Wait for it to + // hide after calling checkActivate. If the popup is hidden, it will + // need to be reopened for the next step, so set needToOpen accordingly. + let popupHiddenPromise; + needToOpen = expectedResults[i]; + if (expectedResults[i]) { + popupHiddenPromise = waitForEvent(contextmenu, "popuphidden"); + } + + checkActivate(desc, menu, document.getElementById(items[i]), expectedResults[i]); + + await popupHiddenPromise; + } + + // For the next iteration, we will be using the next submenu. The item + // in the first menu will never be activatable since it is in a higher + // part of the menu hierarchy. + expectedResults[m] = false; + } + + if (!needToOpen && openFn != doNothing) { + // Hide the popup if it is still expected to be open. If doNothing is + // the opening function, then the popup would never have been opened. + let popupHiddenPromise = waitForEvent(contextmenu, "popuphidden"); + contextmenu.hidePopup(); + await popupHiddenPromise; + } +} + +add_task(async function () { + await checkActivateItems("expected failure when no menus open", + doNothing, [false, false, false, false]); + + let openContextMenu = async () => { + // Open the first level of the context menu + let contextmenu = document.getElementById("contextmenu"); + let popupShownPromise = waitForEvent(contextmenu, "popupshown"); + synthesizeMouseAtCenter(document.getElementById("label"), { + type: "contextmenu", + button: 2, + }); + await popupShownPromise; + } + + await checkActivateItems("one menu open", openContextMenu, [true, false, false, false]); + + // Open the second level of the context menu + let openSubmenu = async () => { + await openContextMenu(); + let submenu = document.getElementById("submenu"); + let popupShownPromise = waitForEvent(submenu, "popupshown"); + submenu.parentNode.openMenu(true); + await popupShownPromise; + } + + await checkActivateItems("submenu menu open", openSubmenu, [true, true, false, false]); + + // Open the last level of the context menu + let openInnerSubmenu = async () => { + await openSubmenu(); + + let innersubmenu = document.getElementById("innersubmenu"); + let popupShownPromise = waitForEvent(innersubmenu, "popupshown"); + innersubmenu.parentNode.openMenu(true); + await popupShownPromise; + }; + await checkActivateItems("inner submenu menu open", openInnerSubmenu, [true, true, true, false]); + + // Check that an item not in the menu is not valid. + await openInnerSubmenu(); + + let innersubmenu = document.getElementById("innersubmenu"); + await checkActivate("item not in menu", innersubmenu, document.getElementById("item1"), false); + + // Now check with all menus closed again + let contextmenu = document.getElementById("contextmenu"); + let popupHiddenPromise = waitForEvent(contextmenu, "popuphidden"); + contextmenu.hidePopup(); + await popupHiddenPromise; + + await checkActivateItems("all menus closed", doNothing, [false, false, false, false]); +}); + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"> + </p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menu_hide.xhtml b/toolkit/content/tests/chrome/test_menu_hide.xhtml new file mode 100644 index 0000000000..c8698bf572 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menu_hide.xhtml @@ -0,0 +1,80 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu Destruction Test" + onload="setTimeout(runTests, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<menu id="menu"> + <menupopup onpopuphidden="if (event.target == this) done()"> + <menu id="submenu" label="One"> + <menupopup onpopupshown="submenuOpened();"> + <menuitem label="Two"/> + </menupopup> + </menu> + </menupopup> +</menu> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function runTests() +{ + let menu = $("menu"); + let menuitemAddedWhileHidden = menu.appendItem("Added while hidden"); + ok(!menuitemAddedWhileHidden.querySelector(".menu-text"), "hidden menuitem hasn't rendered yet."); + + menu.menupopup.addEventListener("popupshown", () => { + is(menuitemAddedWhileHidden.querySelector(".menu-text").value, "Added while hidden", + "menuitemAddedWhileHidden item has rendered after shown."); + let menuitemAddedWhileShown = menu.appendItem("Added while shown"); + is(menuitemAddedWhileShown.querySelector(".menu-text").value, "Added while shown", + "menuitemAddedWhileShown item has eagerly rendered."); + + let submenu = $("submenu"); + is(submenu.querySelector(".menu-text").value, "One", "submenu has rendered."); + + let submenuDynamic = document.createXULElement("menu"); + submenuDynamic.setAttribute("label", "Dynamic"); + ok(!submenuDynamic.querySelector(".menu-text"), "dynamic submenu hasn't rendered yet."); + menu.menupopup.append(submenuDynamic); + is(submenuDynamic.querySelector(".menu-text").value, "Dynamic", "dynamic submenu has rendered."); + + menu.menupopup.firstElementChild.open = true; + }, { once: true }); + menu.open = true; +} + +function submenuOpened() +{ + let submenu = $("submenu"); + is(submenu.getAttribute('_moz-menuactive'), "true", "menu highlighted"); + submenu.hidden = true; + $("menu").open = false; +} + +function done() +{ + ok($("submenu").hasAttribute('_moz-menuactive'), "menu still highlighted"); + SimpleTest.finish(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menu_mouse_menuactive.xhtml b/toolkit/content/tests/chrome/test_menu_mouse_menuactive.xhtml new file mode 100644 index 0000000000..edd7f582b5 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menu_mouse_menuactive.xhtml @@ -0,0 +1,91 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu Activation Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <label id="label" value="label" context="contextmenu"/> + <menupopup id="contextmenu"> + <menuitem id="item1" label="Item 1"/> + <menuitem id="item2" label="Item 2"/> + </menupopup> + <script><![CDATA[ + +function waitForEvent(subject, eventName) { + return new Promise(resolve => { + subject.addEventListener(eventName, function listener(event) { + if (event.target == subject) { + subject.removeEventListener(eventName, listener); + resolve(); + } + }); + }); +} + +const menu = document.getElementById("contextmenu"); +const label = document.getElementById("label"); +const item1 = document.getElementById("item1"); +const item2 = document.getElementById("item2"); + +function openContextMenu() { + // Open the first level of the context menu + let promise = waitForEvent(menu, "popupshown"); + synthesizeMouseAtCenter(label, { + type: "contextmenu", + button: 2, + }); + return promise; +} + +function isActive(item) { + return item.hasAttribute("_moz-menuactive"); +} + +async function activateItem(item) { + info(`Activating ${item.id}`); + ok(!isActive(item), "Shouldn't be already active"); + let activated = waitForEvent(item, "DOMMenuItemActive"); + synthesizeMouse(item, 5, 5, { type: "mousemove" }); + await new Promise(r => setTimeout(r, 0)); + synthesizeMouse(item, 7, 7, { type: "mousemove" }); + await activated; +} + +add_task(async function() { + // Disable macOS native context menus, since we can't control activation of those here. + await SpecialPowers.pushPrefEnv({ set: [["widget.macos.native-context-menus", false]] }); + info(`Opening context-menu`); + await openContextMenu(); + is(menu.state, "open", "Menu should be open"); + + await activateItem(item1); + ok(isActive(item1), "item1 should be active"); + ok(!isActive(item2), "item2 should be inactive"); + + await activateItem(item2); + ok(isActive(item2), "item2 should be active"); + ok(!isActive(item1), "item1 should be inactive"); + + await activateItem(item1); + ok(isActive(item1), "item1 should be active"); + ok(!isActive(item2), "item2 should be inactive"); + + info(`Leaving context-menu`); + + let deactivated = waitForEvent(item1, "DOMMenuItemInactive"); + synthesizeMouse(label, 0, 0, { type: "mousemove" }); + + await deactivated; + ok(!isActive(item1), "item1 should be inactive"); + ok(!isActive(item2), "item2 should be inactive"); + + is(menu.state, "open", "Menu should still be open"); + menu.hidePopup(); +}); + + ]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_menu_withcapture.xhtml b/toolkit/content/tests/chrome/test_menu_withcapture.xhtml new file mode 100644 index 0000000000..fe90e3201c --- /dev/null +++ b/toolkit/content/tests/chrome/test_menu_withcapture.xhtml @@ -0,0 +1,61 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu with Mouse Capture" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <menulist id="menulist"> + <menupopup onpopupshown="shown(this)" onpopuphidden="done();"> + <menuitem id="menuitem" label="Menu Item" + onmousemove="moveHappened = true;" + onmouseup="upHappened = true;"/> + </menupopup> + </menulist> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(startTest); + +let moveHappened = false, upHappened = false; + +function startTest() { + disableNonTestMouseEvents(true); + document.getElementById("menulist"). open = true; +} + +function shown(popup) +{ + popup.setCaptureAlways(); + setTimeout(function() { + synthesizeMouseAtCenter(document.getElementById("menuitem"), { type: "mousemove" }); + synthesizeMouseAtCenter(document.getElementById("menuitem"), { type: "mouseup" }); + }, 200); +} + +function done() +{ + ok(moveHappened, "mousemove happened"); + ok(upHappened, "mouseup happened"); + disableNonTestMouseEvents(false); + SimpleTest.finish(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menuchecks.xhtml b/toolkit/content/tests/chrome/test_menuchecks.xhtml new file mode 100644 index 0000000000..8e67c9bb10 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menuchecks.xhtml @@ -0,0 +1,155 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu Checkbox and Radio Tests" + onload="runTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <hbox> + <button id="menu" type="menu" label="View"> + <menupopup id="popup" onpopupshown="popupShown()" onpopuphidden="popupHidden()"> + <menuitem id="toolbar" label="Show Toolbar" type="checkbox"/> + <menuitem id="statusbar" label="Show Status Bar" type="checkbox" checked="true"/> + <menuitem id="bookmarks" label="Show Bookmarks" type="checkbox" autocheck="false"/> + <menuitem id="history" label="Show History" type="checkbox" autocheck="false" checked="true"/> + <menuseparator/> + <menuitem id="byname" label="By Name" type="radio" name="sort"/> + <menuitem id="bydate" label="By Date" type="radio" name="sort" checked="true"/> + <menuseparator/> + <menuitem id="ascending" label="Ascending" type="radio" name="order" checked="true"/> + <menuitem id="descending" label="Descending" type="radio" name="order" autocheck="false"/> + <menuitem id="bysubject" label="By Subject" type="radio" name="sort"/> + </menupopup> + </button> + + </hbox> + + <!-- + This test checks that checkbox and radio menu items work properly + --> + <script class="testbody" type="application/javascript"> + <![CDATA[ + + SimpleTest.waitForExplicitFinish(); + var gTestIndex = 0; + + // tests to perform + var tests = [ + { + testname: "select unchecked checkbox", + item: "toolbar", + checked: ["toolbar", "statusbar", "history", "bydate", "ascending"] + }, + { + testname: "select checked checkbox", + item: "statusbar", + checked: ["toolbar", "history", "bydate", "ascending"] + }, + { + testname: "select unchecked autocheck checkbox", + item: "bookmarks", + checked: ["toolbar", "history", "bydate", "ascending"] + }, + { + testname: "select checked autocheck checkbox", + item: "history", + checked: ["toolbar", "history", "bydate", "ascending"] + }, + { + testname: "select unchecked radio", + item: "byname", + checked: ["toolbar", "history", "byname", "ascending"] + }, + { + testname: "select checked radio", + item: "byname", + checked: ["toolbar", "history", "byname", "ascending"] + }, + { + testname: "select out of order checked radio", + item: "bysubject", + scroll: true, + checked: ["toolbar", "history", "bysubject", "ascending"] + }, + { + testname: "select first radio again", + item: "byname", + checked: ["toolbar", "history", "byname", "ascending"] + }, + { + testname: "select autocheck radio", + item: "descending", + checked: ["toolbar", "history", "byname", "ascending"] + } + ]; + + function runTest() + { + checkMenus(["statusbar", "history", "bydate", "ascending"], "initial"); + document.getElementById("menu").open = true; + } + + function checkMenus(checkedItems, testname) + { + var isok = true; + var children = document.getElementById("popup").childNodes; + for (var c = 0; c < children.length; c++) { + var child = children[c]; + if ((checkedItems.includes(child.id) && child.getAttribute("checked") != "true") || + (!checkedItems.includes(child.id) && child.hasAttribute("checked"))) { + isok = false; + break; + } + } + + ok(isok, testname); + } + + function popupShown() + { + var test = tests[gTestIndex]; + + if (test.scroll) { + // On Windows 10, the menu is larger than the test frame. Scroll the later + // items into view. Since we are just testing the checked state of the items, + // and not their positions, this doesn't affect the behaviour of the test. + document.getElementById(test.item).scrollIntoView({ block: 'nearest' }); + } + synthesizeMouse(document.getElementById(test.item), 4, 4, { }); + } + + function popupHidden() + { + if (gTestIndex < tests.length) { + var test = tests[gTestIndex]; + checkMenus(test.checked, test.testname); + gTestIndex++; + if (gTestIndex < tests.length) { + document.getElementById("menu").open = true; + } + else { + // manually setting the checkbox should also update the radio state + document.getElementById("bydate").setAttribute("checked", "true"); + checkMenus(["toolbar", "history", "bydate", "ascending"], "set checked attribute on radio"); + SimpleTest.finish(); + } + } + } + + ]]> + </script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menuitem_blink.xhtml b/toolkit/content/tests/chrome/test_menuitem_blink.xhtml new file mode 100644 index 0000000000..700a8a7465 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menuitem_blink.xhtml @@ -0,0 +1,76 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Blinking Context Menu Item Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <menulist id="menulist"> + <menupopup id="menupopup"> + <menuitem label="Menu Item" id="menuitem"/> + </menupopup> + </menulist> +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(startTest); + +function startTest() { + if (!/Mac/.test(navigator.platform)) { + ok(true, "Nothing to test on non-Mac."); + SimpleTest.finish(); + return; + } + test_crash(); +} + +function test_crash() { + var menupopup = document.getElementById("menupopup"); + var menuitem = document.getElementById("menuitem"); + menupopup.addEventListener("popupshown", function () { + menuitem.addEventListener("mouseup", function (e) { + const observer = new MutationObserver((aMutationList, aObserver) => { + for (const mutation of aMutationList) { + if (mutation.attributeName != "_moz-menuactive") { + continue; + } + menuitem.hidden = true; + menuitem.getBoundingClientRect(); + ok(true, "Didn't crash on _moz-menuactive mutation") + menuitem.hidden = false; + aObserver.disconnect(); + SimpleTest.executeSoon(function () { + menupopup.hidePopup(); + }); + } + }); + observer.observe(menuitem, { attributes: true }); + }, {once: true}); + menupopup.addEventListener("popuphidden", SimpleTest.finish, {once: true}); + synthesizeMouse(menuitem, 10, 5, { type : "mousemove" }); + synthesizeMouse(menuitem, 10, 5, { type : "mousemove" }); + synthesizeMouse(menuitem, 10, 5, { type : "mousedown" }); + SimpleTest.executeSoon(function () { + synthesizeMouse(menuitem, 10, 5, { type : "mouseup" }); + }); + }, {once: true}); + document.getElementById("menulist").open = true; +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menuitem_commands.xhtml b/toolkit/content/tests/chrome/test_menuitem_commands.xhtml new file mode 100644 index 0000000000..60a35b36c5 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menuitem_commands.xhtml @@ -0,0 +1,102 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menuitem Commands Test" + onload="runOrOpen()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<script> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +function checkAttributes(elem, label, accesskey, disabled, hidden, isAfter) +{ + var is = window.arguments[0].SimpleTest.is; + + is(elem.getAttribute("label"), label, elem.id + " label " + (isAfter ? "after" : "before") + " open"); + is(elem.getAttribute("accesskey"), accesskey, elem.id + " accesskey " + (isAfter ? "after" : "before") + " open"); + is(elem.getAttribute("disabled"), disabled, elem.id + " disabled " + (isAfter ? "after" : "before") + " open"); + is(elem.hidden, hidden, elem.id + " hidden " + (isAfter ? "after" : "before") + " open"); +} + +function runOrOpen() +{ + if (window.arguments) { + SimpleTest.waitForFocus(runTest); + } + else { + window.openDialog("test_menuitem_commands.xhtml", "", "chrome,noopener", window); + } +} + +function runTest() +{ + runTestSet(""); + runTestSet("bar"); + window.close(); + window.arguments[0].SimpleTest.finish(); +} + +function runTestSet(suffix) +{ + var isMac = (navigator.platform.includes("Mac")); + + var one = $("one" + suffix); + var two = $("two" + suffix); + var three = $("three" + suffix); + var four = $("four" + suffix); + + checkAttributes(one, "One", "", "", true, false); + checkAttributes(two, "", "", "false", false, false); + checkAttributes(three, "Three", "T", "true", false, false); + checkAttributes(four, "Four", "F", "", false, false); + + if (isMac && suffix) { + var utils = window.windowUtils; + utils.forceUpdateNativeMenuAt("0"); + } + else { + $("menu" + suffix).open = true; + } + + checkAttributes(one, "One", "", "", false, true); + checkAttributes(two, "Cat", "C", "", false, true); + checkAttributes(three, "Dog", "D", "false", true, true); + checkAttributes(four, "Four", "F", "true", false, true); + + $("menu" + suffix).open = false; +} +]]> +</script> + +<command id="cmd_one" hidden="false"/> +<command id="cmd_two" label="Cat" accesskey="C"/> +<command id="cmd_three" label="Dog" accesskey="D" disabled="false" hidden="true"/> +<command id="cmd_four" disabled="true"/> + +<button id="menu" type="menu"> + <menupopup> + <menuitem id="one" label="One" hidden="true" command="cmd_one"/> + <menuitem id="two" disabled="false" command="cmd_two"/> + <menuitem id="three" label="Three" accesskey="T" disabled="true" command="cmd_three"/> + <menuitem id="four" label="Four" accesskey="F" command="cmd_four"/> + </menupopup> +</button> + +<menubar> + <menu id="menubar" label="Sample"> + <menupopup> + <menuitem id="onebar" label="One" hidden="true" command="cmd_one"/> + <menuitem id="twobar" disabled="false" command="cmd_two"/> + <menuitem id="threebar" label="Three" accesskey="T" disabled="true" command="cmd_three"/> + <menuitem id="fourbar" label="Four" accesskey="F" command="cmd_four"/> + </menupopup> + </menu> +</menubar> + +<body xmlns="http://www.w3.org/1999/xhtml"><p id="display"/></body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menulist.xhtml b/toolkit/content/tests/chrome/test_menulist.xhtml new file mode 100644 index 0000000000..5917785b56 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menulist.xhtml @@ -0,0 +1,350 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menulist Tests" + onload="setTimeout(testtag_menulists, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="xul_selectcontrol.js"></script> + +<vbox id="scroller" style="overflow: auto; height: 60px"> + <menulist id="menulist" onpopupshown="test_menulist_open(this, this.parentNode)" + onpopuphidden="$('menulist-in-listbox').open = true;"> + <menupopup id="menulist-popup"/> + </menulist> + <button label="Two"/> + <button label="Three"/> +</vbox> +<richlistbox id="scroller-in-listbox" style="overflow: auto; height: 60px"> + <richlistitem allowevents="true"> + <menulist id="menulist-in-listbox" onpopupshown="test_menulist_open(this, this.parentNode.parentNode)" + onpopuphidden="SimpleTest.executeSoon(() => checkScrollAndFinish().catch(ex => ok(false, ex)));"> + <menupopup id="menulist-in-listbox-popup"> + <menuitem label="One" value="one"/> + <menuitem label="Two" value="two"/> + </menupopup> + </menulist> + </richlistitem> + <richlistitem><label value="Two"/></richlistitem> + <richlistitem><label value="Three"/></richlistitem> + <richlistitem><label value="Four"/></richlistitem> + <richlistitem><label value="Five"/></richlistitem> + <richlistitem><label value="Six"/></richlistitem> +</richlistbox> + +<hbox> + <menulist id="menulist-size"> + <menupopup> + <menuitem label="Menuitem Label" style="width: 200px"/> + </menupopup> + </menulist> +</hbox> + +<menulist id="menulist-initwithvalue" value="two"> + <menupopup> + <menuitem label="One" value="one"/> + <menuitem label="Two" value="two"/> + <menuitem label="Three" value="three"/> + </menupopup> +</menulist> +<menulist id="menulist-initwithselected" value="two"> + <menupopup> + <menuitem label="One" value="one"/> + <menuitem label="Two" value="two"/> + <menuitem label="Three" value="three" selected="true"/> + </menupopup> +</menulist> + +<menulist id="menulist-clipped"> + <menupopup style="height: 65px"> + <menuitem label="One" value="one"/> + <menuitem label="Two" value="two"/> + <menuitem label="Three" value="three"/> + <menuitem label="Four" value="four"/> + <menuitem label="Five" value="five" selected="true"/> + <menuitem label="Six" value="six"/> + <menuitem label="Seven" value="seven"/> + <menuitem label="Eight" value="eight"/> + </menupopup> +</menulist> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +function waitForEvent(subject, eventName, checkFn) { + return new Promise(resolve => { + subject.addEventListener(eventName, function listener(event) { + if (checkFn && !checkFn(event)) { + return; + } + subject.removeEventListener(eventName, listener); + SimpleTest.executeSoon(() => resolve(event)); + }); + }); +} + +SimpleTest.waitForExplicitFinish(); + +function testtag_menulists() +{ + testtag_menulist_UI_start($("menulist"), false); +} + +function testtag_menulist_UI_start(element) +{ + // check the menupopup property + var popup = element.menupopup; + ok(popup && popup.localName == "menupopup" && + popup.parentNode == element, "menupopup"); + + // test the interfaces that menulist implements + test_nsIDOMXULMenuListElement(element); +} + +function testtag_menulist_UI_finish(element) +{ + element.value = ""; + + test_nsIDOMXULSelectControlElement(element, "menuitem", null); + + $("menulist").open = true; +} + +function test_nsIDOMXULMenuListElement(element) +{ + is(element.open, false, "open"); + + element.appendItem("Item One", "one"); + var seconditem = element.appendItem("Item Two", "two"); + var thirditem = element.appendItem("Item Three", "three"); + element.appendItem("Item Four", "four"); + + seconditem.image = "happy.png"; + seconditem.setAttribute("description", "This is the second description"); + thirditem.image = "happy.png"; + thirditem.setAttribute("description", "This is the third description"); + + // check the image and description properties + element.selectedIndex = 1; + is(element.image, "happy.png", "image set to selected"); + is(element.description, "This is the second description", "description set to selected"); + element.selectedIndex = -1; + is(element.image, "", "image set when none selected"); + is(element.description, "", "description set when none selected"); + element.selectedIndex = 2; + is(element.image, "happy.png", "image set to selected again"); + is(element.description, "This is the third description", "description set to selected again"); + + // check that changing the properties of the selected item changes the menulist's properties + let properties = [{attr: "label", value: "Item Number Three"}, + {attr: "value", value: "item-three"}, + {attr: "image", value: "smile.png"}, + {attr: "description", value: "Changed description"}]; + test_nsIDOMXULMenuListElement_properties(element, thirditem, properties); +} + +function test_nsIDOMXULMenuListElement_properties(element, thirditem, properties) +{ + let {attr, value} = properties.shift(); + let last = !properties.length; + + let mutObserver = new MutationObserver(() => { + is(element.getAttribute(attr), value, `${attr} modified`); + done(); + }); + mutObserver.observe(element, { attributeFilter: [attr] }); + + let failureTimeout = setTimeout(() => { + ok(false, `${attr} should have updated`); + done(); + }, 2000); + + function done() + { + clearTimeout(failureTimeout); + mutObserver.disconnect(); + if (!last) { + test_nsIDOMXULMenuListElement_properties(element, thirditem, properties); + } + else { + test_nsIDOMXULMenuListElement_unselected(element, thirditem); + } + } + + thirditem.setAttribute(attr, value) +} + +function test_nsIDOMXULMenuListElement_unselected(element, thirditem) +{ + let seconditem = thirditem.previousElementSibling; + seconditem.label = "Changed Label 2"; + is(element.label, "Item Number Three", "label of another item modified"); + + element.selectedIndex = 0; + is(element.image, "", "image set to selected with no image"); + is(element.description, "", "description set to selected with no description"); + test_nsIDOMXULMenuListElement_finish(element); +} + +function test_nsIDOMXULMenuListElement_finish(element) +{ + // check the removeAllItems method + element.appendItem("An Item", "anitem"); + element.appendItem("Another Item", "anotheritem"); + element.removeAllItems(); + is(element.itemCount, 0, "removeAllItems"); + + testtag_menulist_UI_finish(element); +} + +function test_menulist_open(element, scroller) +{ + element.appendItem("Scroll Item 1", "scrollitem1"); + element.appendItem("Scroll Item 2", "scrollitem2"); + element.focus(); + element.selectedIndex = 0; + +/* + // bug 530504, mousewheel while menulist is open should not scroll menulist + // items or parent + var scrolled = false; + var mouseScrolled = function (event) { scrolled = true; } + window.addEventListener("DOMMouseScroll", mouseScrolled, false); + synthesizeWheel(element, 2, 2, { deltaY: 10, + deltaMode: WheelEvent.DOM_DELTA_LINE }); + is(scrolled, true, "mousescroll " + element.id); + is(scroller.scrollTop, 0, "scroll position on mousescroll " + element.id); + window.removeEventListener("DOMMouseScroll", mouseScrolled, false); +*/ + + // bug 543065, hovering the mouse over an item should highlight it, not + // scroll the parent, and not change the selected index. + var item = element.menupopup.childNodes[1]; + + synthesizeMouse(element.menupopup.childNodes[1], 2, 2, { type: "mousemove" }); + synthesizeMouse(element.menupopup.childNodes[1], 6, 6, { type: "mousemove" }); + is(element.activeChild, item, "activeChild after menu highlight " + element.id); + is(element.selectedIndex, 0, "selectedIndex after menu highlight " + element.id); + is(scroller.scrollTop, 0, "scroll position after menu highlight " + element.id); + + element.open = false; +} + +async function checkScrollAndFinish() +{ + is($("scroller").scrollTop, 0, "mousewheel on menulist does not scroll vbox parent"); + is($("scroller-in-listbox").scrollTop, 0, "mousewheel on menulist does not scroll listbox parent"); + + let menulist = $("menulist-size"); + let shownPromise = waitForEvent(menulist, "popupshown"); + menulist.open = true; + await shownPromise; + + sendKey("ALT"); + is(menulist.menupopup.state, "open", "alt doesn't close menulist"); + menulist.open = false; + + await dragScroll(); +} + +async function dragScroll() +{ + let menulist = $("menulist-clipped"); + + let shownPromise = waitForEvent(menulist, "popupshown"); + menulist.open = true; + await shownPromise; + + let popup = menulist.menupopup; + let getScrollPos = () => popup.scrollBox.scrollbox.scrollTop; + let scrollPos = getScrollPos(); + let popupRect = popup.getBoundingClientRect(); + + // First, check that scrolling does not occur when the mouse is moved over the + // anchor button but not the popup yet. + synthesizeMouseAtPoint(popupRect.left + 5, popupRect.top - 10, { type: "mousemove" }); + is(getScrollPos(), scrollPos, "scroll position after mousemove over button should not change"); + + synthesizeMouseAtPoint(popupRect.left + 20, popupRect.top + 10, { type: "mousemove" }); + synthesizeMouseAtPoint(popupRect.left + 20, popupRect.top + 10, { type: "mousedown", buttons: 1 }); + + // Dragging above the popup scrolls it up. + let scrolledPromise = waitForEvent(popup, "scroll", false, + () => getScrollPos() < scrollPos - 5); + synthesizeMouseAtPoint(popupRect.left + 20, popupRect.top - 20, { type: "mousemove", buttons: 1 }); + await scrolledPromise; + ok(true, "scroll position at drag up"); + + // Dragging below the popup scrolls it down. + scrollPos = getScrollPos(); + scrolledPromise = waitForEvent(popup, "scroll", false, + () => getScrollPos() > scrollPos + 5); + synthesizeMouseAtPoint(popupRect.left + 20, popupRect.bottom + 20, { type: "mousemove", buttons: 1 }); + await scrolledPromise; + ok(true, "scroll position at drag down"); + + // Releasing the mouse button and moving the mouse does not change the scroll position. + scrollPos = getScrollPos(); + synthesizeMouseAtPoint(popupRect.left + 20, popupRect.bottom + 25, { type: "mouseup" }); + is(getScrollPos(), scrollPos, "scroll position at mouseup should not change"); + + synthesizeMouseAtPoint(popupRect.left + 20, popupRect.bottom + 20, { type: "mousemove" }); + is(getScrollPos(), scrollPos, "scroll position at mousemove after mouseup should not change"); + + // Now check dragging with a mousedown on an item. Make sure the element is + // visible, as the asynchronous scrolling may have moved it out of view. + popup.childNodes[4].scrollIntoView({ block: "nearest", behavior: "instant" }); + let menuRect = popup.childNodes[4].getBoundingClientRect(); + synthesizeMouseAtPoint(menuRect.left + 5, menuRect.top + 5, { type: "mousedown", buttons: 1 }); + + // Dragging below the popup scrolls it down. + scrolledPromise = waitForEvent(popup, "scroll", false, + () => getScrollPos() > scrollPos + 5); + synthesizeMouseAtPoint(popupRect.left + 20, popupRect.bottom + 20, { type: "mousemove", buttons: 1 }); + await scrolledPromise; + ok(true, "scroll position at drag down from item"); + + // Dragging above the popup scrolls it up. + scrollPos = getScrollPos(); + scrolledPromise = waitForEvent(popup, "scroll", false, + () => getScrollPos() < scrollPos - 5); + synthesizeMouseAtPoint(popupRect.left + 20, popupRect.top - 20, { type: "mousemove", buttons: 1 }); + await scrolledPromise; + ok(true, "scroll position at drag up from item"); + + scrollPos = getScrollPos(); + synthesizeMouseAtPoint(popupRect.left + 20, popupRect.bottom + 25, { type: "mouseup" }); + is(getScrollPos(), scrollPos, "scroll position at mouseup should not change"); + + synthesizeMouseAtPoint(popupRect.left + 20, popupRect.bottom + 20, { type: "mousemove" }); + is(getScrollPos(), scrollPos, "scroll position at mousemove after mouseup should not change"); + + menulist.open = false; + + let mouseMoveTarget = null; + popup.childNodes[4].click(); + addEventListener("mousemove", function checkMouseMove(event) { + mouseMoveTarget = event.target; + }, {once: true}); + synthesizeMouseAtPoint(popupRect.left + 20, popupRect.bottom + 20, { type: "mousemove" }); + isnot(mouseMoveTarget, popup, "clicking on item when popup closed doesn't start dragging"); + + SimpleTest.finish(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menulist_in_popup.xhtml b/toolkit/content/tests/chrome/test_menulist_in_popup.xhtml new file mode 100644 index 0000000000..971fe90322 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menulist_in_popup.xhtml @@ -0,0 +1,57 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menulist position Test" + onload="setTimeout(runTest, 0)" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> +<script> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +async function runTest() { + let panel = document.querySelector("panel"); + let menulist = document.getElementById("menulist"); + let menulistPopup = document.getElementById("menulistpopup"); + + menulistPopup.addEventListener("popupshown", function(e) { + ok(false, "Menulist popup shown"); + }); + + let panelShown = new Promise(r => panel.addEventListener("popupshown", r, { once: true })); + info("opening panel"); + panel.openPopup(null, { x: 0, y: 0 }); + await panelShown; + info("panel opened"); + + info("hovering menulist"); + synthesizeMouseAtCenter(menulist, { type: "mousemove" }); + info("waiting for a bit"); + await new Promise(r => setTimeout(r, 500)); + + isnot(menulist.open, "menulist should not be open on hover when inside a popup"); + + SimpleTest.finish(); +} + +]]> +</script> + +<panel style="width: 500px; height: 500px"> + <menulist style="width: 200px" id="menulist"> + <menupopup style="max-height: 90px;" id="menulistpopup"> + <menuitem label="One"/> + <menuitem label="Two"/> + <menuitem label="Three"/> + <menuitem label="Four"/> + <menuitem label="Five"/> + <menuitem label="Six"/> + <menuitem label="Seven"/> + </menupopup> + </menulist> +</panel> + +</window> diff --git a/toolkit/content/tests/chrome/test_menulist_keynav.xhtml b/toolkit/content/tests/chrome/test_menulist_keynav.xhtml new file mode 100644 index 0000000000..86e86b6510 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menulist_keynav.xhtml @@ -0,0 +1,316 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menulist Key Navigation Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<button id="button1" label="One"/> +<menulist id="list"> + <menupopup id="popup" onpopupshowing="return gShowPopup;"> + <menuitem id="i1" label="One"/> + <menuitem id="i2" label="Two"/> + <menuitem id="i2b" disabled="true" label="Two and a Half"/> + <menuitem id="i3" label="Three"/> + <menuitem id="i4" label="Four"/> + </menupopup> +</menulist> +<button id="button2" label="Two"/> +<menulist id="list2"> + <menupopup id="popup" onpopupshown="checkCursorNavigation();"> + <menuitem id="b1" label="One"/> + <menuitem id="b2" label="Two" selected="true"/> + <menuitem id="b3" label="Three"/> + <menuitem id="b4" label="Four"/> + </menupopup> +</menulist> +<menulist id="list3" sizetopopup="none"> + <menupopup> + <menuitem id="s1" label="One"/> + <menuitem id="s2" label="Two"/> + <menuitem id="s3" label="Three"/> + <menuitem id="s4" label="Four"/> + </menupopup> +</menulist> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var gShowPopup = false; +var gModifiers = 0; +var gOpenPhase = false; + +var list = $("list"); +let expectCommandEvent; + +var iswin = (navigator.platform.indexOf("Win") == 0); +var ismac = (navigator.platform.indexOf("Mac") == 0); + +function runTests() +{ + list.focus(); + + // on Mac, up and cursor keys open the menu, but on other platforms, the + // cursor keys navigate between items without opening the menu + if (!ismac) { + expectCommandEvent = true; + keyCheck(list, "KEY_ArrowDown", 2, 1, "cursor down"); + keyCheck(list, "KEY_ArrowDown", 3, 1, "cursor down skip disabled"); + keyCheck(list, "KEY_ArrowUp", 2, 1, "cursor up skip disabled"); + keyCheck(list, "KEY_ArrowUp", 1, 1, "cursor up"); + + // On Windows, wrapping doesn't occur. + expectCommandEvent = !iswin; + keyCheck(list, "KEY_ArrowUp", iswin ? 1 : 4, 1, "cursor up wrap"); + + list.selectedIndex = 4; + list.activeChild = list.selectedItem; + keyCheck(list, "KEY_ArrowDown", iswin ? 4 : 1, 4, "cursor down wrap"); + + list.selectedIndex = 0; + list.activeChild = list.selectedItem; + } + + // check that attempting to open the menulist does not change the selection + synthesizeKey("KEY_ArrowDown", {altKey: !ismac}); + is(list.selectedItem, $("i1"), "open menulist down selectedItem"); + synthesizeKey("KEY_ArrowUp", {altKey: !ismac}); + is(list.selectedItem, $("i1"), "open menulist up selectedItem"); + + list.selectedItem = $("i1"); + + pressLetter(); +} + +function pressLetter() +{ + // A command event should be fired only if the menulist is closed. + expectCommandEvent = !gOpenPhase || iswin; + + sendString("G"); + is(list.selectedItem, $("i1"), "letter pressed not found selectedItem"); + + keyCheck(list, "T", 2, 1, "letter pressed"); + + setTimeout(pressedAgain, 1200); +} + +function pressedAgain() +{ + keyCheck(list, "T", 3, 1, "letter pressed again"); + SpecialPowers.setIntPref("ui.menu.incremental_search.timeout", 0); // prevent to timeout + keyCheck(list, "W", 2, 1, "second letter pressed"); + SpecialPowers.clearUserPref("ui.menu.incremental_search.timeout"); + setTimeout(differentPressed, 1200); +} + +function differentPressed() +{ + keyCheck(list, "O", 1, 1, "different letter pressed"); + + if (gOpenPhase) { + list.open = false; + tabAndScroll(); + } + else { + // Run the letter tests again with the popup open + info("list open phase"); + + list.selectedItem = $("i1"); + + // Hide and show the list to avoid using any existing incremental key state. + list.hidden = true; + list.clientWidth; + list.hidden = false; + + gShowPopup = true; + gOpenPhase = true; + + list.addEventListener("popupshown", function popupShownListener() { + pressLetter(); + }, { once: true}); + + list.open = true; + } +} + +function inputMargin(el) { + let cs = getComputedStyle(el); + // XXX Internal properties are not exposed in getComputedStyle, so we have to + // use margin and rely on our knowledge of them matching negative margins + // where appropriate. + // return parseFloat(cs.getPropertyValue("-moz-window-input-region-margin")); + return ismac ? 0 : Math.max(-parseFloat(cs.marginLeft), 0); +} + +function tabAndScroll() +{ + list = $("list"); + + if (!ismac) { + $("button1").focus(); + synthesizeKeyExpectEvent("KEY_Tab", {}, list, "focus", "focus to menulist"); + synthesizeKeyExpectEvent("KEY_Tab", {}, $("button2"), "focus", "focus to button"); + is(document.activeElement, $("button2"), "tab from menulist focused button"); + } + + // now make sure that using a key scrolls the menu correctly + + for (let i = 0; i < 65; i++) { + list.appendItem("Item" + i, "item" + i); + } + list.open = true; + is(list.getBoundingClientRect().width, list.menupopup.getBoundingClientRect().width - 2 * inputMargin(list.menupopup), + "menu and popup width match"); + var minScrollbarWidth = window.matchMedia("(-moz-overlay-scrollbars)").matches ? 0 : 3; + ok(list.getBoundingClientRect().width >= list.getItemAtIndex(0).getBoundingClientRect().width + minScrollbarWidth, + "menuitem width accounts for scrollbar"); + list.open = false; + + list.menupopup.style.maxHeight = "100px"; + list.open = true; + + var rowdiff = list.getItemAtIndex(1).getBoundingClientRect().top - + list.getItemAtIndex(0).getBoundingClientRect().top; + + var item = list.getItemAtIndex(10); + var originalPosition = item.getBoundingClientRect().top; + + list.activeChild = item; + ok(item.getBoundingClientRect().top < originalPosition, + "position of item 1: " + item.getBoundingClientRect().top + " -> " + originalPosition); + + originalPosition = item.getBoundingClientRect().top; + + synthesizeKey("KEY_ArrowDown"); + is(item.getBoundingClientRect().top, originalPosition - rowdiff, "position of item 10"); + + list.open = false; + + checkEnter(); +} + +function keyCheck(list, key, index, defaultindex, testname) +{ + info(`keyCheck(${index}, ${key}, ${index}, ${defaultindex}, ${testname}, ${expectCommandEvent})`); + var item = $("i" + index); + var defaultitem = $("i" + defaultindex || 1); + + synthesizeKeyExpectEvent(key, { }, item, expectCommandEvent ? "command" : "!command", testname); + is(list.selectedItem, expectCommandEvent ? item : defaultitem, testname + " selectedItem----" + list.selectedItem.id); +} + +function checkModifiers(event) +{ + var expectedModifiers = (gModifiers == 1); + is(event.shiftKey, expectedModifiers, "shift key pressed"); + is(event.ctrlKey, expectedModifiers, "ctrl key pressed"); + is(event.altKey, expectedModifiers, "alt key pressed"); + is(event.metaKey, expectedModifiers, "meta key pressed"); + gModifiers++; +} + +function checkEnter() +{ + list.addEventListener("popuphidden", checkEnterWithModifiers); + list.addEventListener("command", checkModifiers); + list.open = true; + synthesizeKey("KEY_Enter"); +} + +function checkEnterWithModifiers() +{ + is(gModifiers, 1, "modifiers checked when not set"); + + ok(!list.open, "list closed on enter press"); + list.removeEventListener("popuphidden", checkEnterWithModifiers); + + list.addEventListener("popuphidden", verifyPopupOnClose); + list.open = true; + + synthesizeKey("KEY_Enter", {shiftKey: true, ctrlKey: true, altKey: true, metaKey: true}); +} + +function verifyPopupOnClose() +{ + is(gModifiers, 2, "modifiers checked when set"); + + ok(!list.open, "list closed on enter press with modifiers"); + list.removeEventListener("popuphidden", verifyPopupOnClose); + + list = $("list2"); + list.focus(); + list.open = true; +} + +function checkCursorNavigation() +{ + var commandEventsCount = 0; + list.addEventListener("command", event => { + is(event.target, list.selectedItem, "command event fired on selected item"); + commandEventsCount++; + }); + + is(list.selectedIndex, 1, "selectedIndex before cursor down"); + synthesizeKey("KEY_ArrowDown"); + is(list.selectedIndex, iswin ? 2 : 1, "selectedIndex after cursor down"); + is(commandEventsCount, iswin ? 1 : 0, "selectedIndex after cursor down command event"); + is(list.menupopup.state, "open", "cursor down popup state"); + synthesizeKey("KEY_PageDown"); + is(list.selectedIndex, iswin ? 2 : 1, "selectedIndex after page down"); + is(commandEventsCount, iswin ? 1 : 0, "selectedIndex after page down command event"); + is(list.menupopup.state, "open", "page down popup state"); + + // Check whether cursor up and down wraps. + list.selectedIndex = 0; + list.activeChild = list.selectedItem; + synthesizeKey("KEY_ArrowUp"); + is(list.activeChild, + document.getElementById(iswin || ismac ? "b1" : "b4"), "cursor up wrap while open"); + + list.selectedIndex = 3; + list.activeChild = list.selectedItem; + synthesizeKey("KEY_ArrowDown"); + is(list.activeChild, + document.getElementById(iswin || ismac ? "b4" : "b1"), "cursor down wrap while open"); + + synthesizeKey("KEY_ArrowUp", {altKey: true}); + is(list.open, ismac, "alt+up closes popup"); + + if (ismac) { + list.open = false; + } + + // Finally, test a menulist with sizetopopup="none" to ensure keyboard navigation + // still works when the popup has not been opened. + if (!ismac) { + let unsizedMenulist = document.getElementById("list3"); + unsizedMenulist.focus(); + synthesizeKey("KEY_ArrowDown"); + is(unsizedMenulist.selectedIndex, 1, "correct menulist index on keydown"); + is(unsizedMenulist.label, "Two", "correct menulist label on keydown"); + } + + SimpleTest.finish(); +} + +SimpleTest.waitForFocus(runTests); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menulist_null_value.xhtml b/toolkit/content/tests/chrome/test_menulist_null_value.xhtml new file mode 100644 index 0000000000..9312c236dc --- /dev/null +++ b/toolkit/content/tests/chrome/test_menulist_null_value.xhtml @@ -0,0 +1,96 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menulist value property" + onload="setTimeout(runTests, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<menulist id="list"> + <menupopup> + <menuitem id="i0" label="Zero" value="0"/> + <menuitem id="i1" label="One" value="item1"/> + <menuitem id="i2" label="Two" value="item2"/> + <menuitem id="ifalse" label="False" value="false"/> + <menuitem id="iempty" label="Empty" value=""/> + </menupopup> +</menulist> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function runTests() +{ + var list = document.getElementById("list"); + + list.value = "item2"; + is(list.value, "item2", "Check list value after setting value"); + is(list.getAttribute("label"), "Two", "Check list label after setting value"); + + list.selectedItem = null; + is(list.value, "", "Check list value after setting selectedItem to null"); + is(list.getAttribute("label"), "", "Check list label after setting selectedItem to null"); + + // select something again to make sure the label is not already empty + list.selectedIndex = 1; + is(list.value, "item1", "Check list value after setting selectedIndex"); + is(list.getAttribute("label"), "One", "Check list label after setting selectedIndex"); + + // check that an item can have the "false" value + list.value = false; + is(list.value, "false", "Check list value after setting it to false"); + is(list.getAttribute("label"), "False", "Check list labem after setting value to false"); + + // check that an item can have the "0" value + list.value = 0; + is(list.value, "0", "Check list value after setting it to 0"); + is(list.getAttribute("label"), "Zero", "Check list label after setting value to 0"); + + // check that an item can have the empty string value. + list.value = ""; + is(list.value, "", "Check list value after setting it to an empty string"); + is(list.getAttribute("label"), "Empty", "Check list label after setting value to an empty string"); + + // select something again to make sure the label is not already empty + list.selectedIndex = 1; + // set the value to null and test it (bug 408940) + list.value = null; + is(list.value, "", "Check list value after setting value to null"); + is(list.getAttribute("label"), "", "Check list label after setting value to null"); + + // select something again to make sure the label is not already empty + list.selectedIndex = 1; + // set the value to undefined and test it (bug 408940) + list.value = undefined; + is(list.value, "", "Check list value after setting value to undefined"); + is(list.getAttribute("label"), "", "Check list label after setting value to undefined"); + + // select something again to make sure the label is not already empty + list.selectedIndex = 1; + // set the value to something that does not exist in any menuitem of the list + // and make sure the previous label is removed + list.value = "this does not exist"; + is(list.value, "this does not exist", "Check the list value after setting it to something not associated witn an existing menuitem"); + is(list.getAttribute("label"), "", "Check that the list label is empty after selecting a nonexistent item"); + + SimpleTest.finish(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menulist_paging.xhtml b/toolkit/content/tests/chrome/test_menulist_paging.xhtml new file mode 100644 index 0000000000..7a0c6f3b5d --- /dev/null +++ b/toolkit/content/tests/chrome/test_menulist_paging.xhtml @@ -0,0 +1,178 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menulist Tests" + onload="setTimeout(runTest, 0);" + onpopupshown="menulistShown()" onpopuphidden="runTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<menulist id="menulist1"> + <menupopup id="menulist-popup1"> + <menuitem label="One"/> + <menuitem label="Two"/> + <menuitem label="Three"/> + <menuitem label="Four"/> + <menuitem label="Five"/> + <menuitem label="Six"/> + <menuitem label="Seven"/> + <menuitem label="Eight"/> + <menuitem label="Nine"/> + <menuitem label="Ten"/> + </menupopup> +</menulist> + +<menulist id="menulist2"> + <menupopup id="menulist-popup2"> + <menuitem label="One" disabled="true"/> + <menuitem label="Two" selected="true"/> + <menuitem label="Three"/> + <menuitem label="Four"/> + <menuitem label="Five"/> + <menuitem label="Six"/> + <menuitem label="Seven"/> + <menuitem label="Eight"/> + <menuitem label="Nine"/> + <menuitem label="Ten" disabled="true"/> + </menupopup> +</menulist> + +<menulist id="menulist3"> + <menupopup id="menulist-popup3"> + <label value="One"/> + <menuitem label="Two" selected="true"/> + <menuitem label="Three"/> + <menuitem label="Four"/> + <menuitem label="Five" disabled="true"/> + <menuitem label="Six" disabled="true"/> + <menuitem label="Seven"/> + <menuitem label="Eight"/> + <menuitem label="Nine"/> + <label value="Ten"/> + </menupopup> +</menulist> + +<menulist id="menulist4"> + <menupopup id="menulist-popup4"> + <label value="One"/> + <menuitem label="Two"/> + <menuitem label="Three"/> + <menuitem label="Four"/> + <menuitem label="Five"/> + <menuitem label="Six" selected="true"/> + <menuitem label="Seven"/> + <menuitem label="Eight"/> + <menuitem label="Nine"/> + <label value="Ten"/> + </menupopup> +</menulist> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +let test; + +// Fields: +// list - menulist id +// initial - initial selected index +// scroll - index of item at top of the visible scrolled area, -1 to skip this test +// downs - array of indicies that will be selected when pressing down in sequence +// ups - array of indicies that will be selected when pressing up in sequence +let tests = [ + { list: "menulist1", initial: 0, scroll: 0, downs: [3, 6, 9, 9], + ups: [6, 3, 0, 0] }, + { list: "menulist2", initial: 1, scroll: 0, downs: [4, 7, 8, 8], + ups: [5, 2, 1] }, + { list: "menulist3", initial: 1, scroll: -1, downs: [3, 6, 8, 8], + ups: [6, 3, 1] }, + { list: "menulist4", initial: 5, scroll: 2, downs: [], ups: [] } +]; + +let gMeasured = false; +function measureMenuItemHeightIfNeeded() { + if (gMeasured) { + return; + } + gMeasured = true; + + let popup = document.getElementById("menulist-popup1"); + let menuitemHeight = popup.firstChild.getBoundingClientRect().height; + + let cs = window.getComputedStyle(popup); + let csArrow = window.getComputedStyle(popup.scrollBox); + let bpmTop = parseFloat(cs.paddingTop) + parseFloat(cs.borderTopWidth) + + parseFloat(csArrow.paddingTop) + parseFloat(csArrow.borderTopWidth) + + parseFloat(csArrow.marginTop); + let bpmBottom = parseFloat(cs.paddingBottom) + parseFloat(cs.borderBottomWidth) + + parseFloat(csArrow.paddingBottom) + parseFloat(csArrow.borderBottomWidth) + + parseFloat(csArrow.marginBottom); + + // First, set the height of each popup to the height of four menuitems plus + // any padding / border / margin on the menupopup. + let height = menuitemHeight * 4 + bpmTop + bpmBottom; + + popup.style.height = height + "px"; + document.getElementById("menulist-popup2").style.height = height + "px"; + document.getElementById("menulist-popup3").style.height = height + "px"; + document.getElementById("menulist-popup4").style.height = height + "px"; +} + +function runTest() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + test = tests.shift(); + document.getElementById(test.list).open = true; +} + +function menulistShown() +{ + measureMenuItemHeightIfNeeded(); + + let menulist = document.getElementById(test.list); + is(menulist.activeChild.label, menulist.getItemAtIndex(test.initial).label, test.list + " initial selection"); + + let cs = window.getComputedStyle(menulist.menupopup); + let csArrow = window.getComputedStyle(menulist.menupopup.scrollBox); + let bpmTop = parseFloat(cs.paddingTop) + parseFloat(cs.borderTopWidth) + + parseFloat(csArrow.paddingTop) + parseFloat(csArrow.borderTopWidth) + + parseFloat(csArrow.marginTop); + + // Skip menulist3 as it has a label that scrolling doesn't need normally deal with. + if (test.scroll >= 0) { + is(menulist.menupopup.childNodes[test.scroll].getBoundingClientRect().top, + menulist.menupopup.getBoundingClientRect().top + bpmTop, + "Popup scroll at correct position"); + } + + for (let i = 0; i < test.downs.length; i++) { + sendKey("PAGE_DOWN"); + is(menulist.activeChild.label, menulist.getItemAtIndex(test.downs[i]).label, test.list + " page down " + i); + } + + for (let i = 0; i < test.ups.length; i++) { + sendKey("PAGE_UP"); + is(menulist.activeChild.label, menulist.getItemAtIndex(test.ups[i]).label, test.list + " page up " + i); + } + + menulist.open = false; +} +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menulist_position.xhtml b/toolkit/content/tests/chrome/test_menulist_position.xhtml new file mode 100644 index 0000000000..b055c1cdb4 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menulist_position.xhtml @@ -0,0 +1,118 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menulist position Test" + onload="setTimeout(init, 0)" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<!-- + This test checks the position of a menulist's popup. + --> + +<script> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +var menulist; + +function init() +{ + menulist = document.getElementById("menulist"); + menulist.open = true; +} + +function isWithinHalfPixel(a, b) +{ + return Math.abs(a - b) <= 0.5; +} + +const ismac = navigator.platform.indexOf("Mac") == 0; +function inputMargin(el) { + let cs = getComputedStyle(el); + // XXX Internal properties are not exposed in getComputedStyle, so we have to + // use margin and rely on our knowledge of them matching negative margins + // where appropriate. + // return parseFloat(cs.getPropertyValue("-moz-window-input-region-margin")); + return ismac ? 0 : Math.max(-parseFloat(cs.marginLeft), 0); +} + +function popupShown() +{ + var menurect = menulist.getBoundingClientRect(); + var popuprect = menulist.menupopup.getBoundingClientRect(); + + let marginLeft = parseFloat(getComputedStyle(menulist.menupopup).marginLeft); + ok(isWithinHalfPixel(menurect.left + marginLeft, popuprect.left), `left position: ${menurect.left}, ${popuprect.left}`); + ok(isWithinHalfPixel(menurect.right + marginLeft + 2 * inputMargin(menulist.menupopup), popuprect.right), `right position: ${menurect.right}, ${popuprect.right}`); + + let index = menulist.selectedIndex; + if (menulist.selectedItem && navigator.platform.includes("Mac")) { + let menulistlabelrect = menulist.shadowRoot.getElementById("label").getBoundingClientRect(); + let mitemlabelrect = menulist.selectedItem.querySelector(".menu-iconic-text").getBoundingClientRect(); + + ok(isWithinHalfPixel(menulistlabelrect.top, mitemlabelrect.top), + `Labels vertically aligned for ${index} : ${menulistlabelrect.top} vs. ${mitemlabelrect.top}`); + + // Store the current value and reset it afterwards. + let current = menulist.selectedIndex; + + // Cycle through the items to ensure that the popup doesn't move when the selection changes. + for (let i = 0; i < menulist.itemCount; i++) { + menulist.selectedIndex = i; + + let newpopuprect = menulist.menupopup.getBoundingClientRect(); + is(newpopuprect.x, popuprect.x, "Popup remained horizontally for index " + i + " starting at " + current); + is(newpopuprect.y, popuprect.y, "Popup remained vertically for index " + i + " starting at " + current); + } + menulist.selectedIndex = current; + } + else { + let marginTop = parseFloat(getComputedStyle(menulist.menupopup).marginTop); + ok(isWithinHalfPixel(menurect.bottom + marginTop, popuprect.top), + "Vertical alignment with no selection for index " + index); + } + + menulist.open = false; +} + +function popupHidden() +{ + if (!menulist.selectedItem) { + SimpleTest.finish(); + } + else { + menulist.selectedItem = menulist.selectedItem.nextSibling; + menulist.open = true; + } +} +]]> +</script> + +<hbox align="center" pack="center" style="margin-top: 140px;"> + <menulist style="width: 200px" id="menulist" onpopupshown="popupShown();" onpopuphidden="popupHidden();" native="true"> + <menupopup style="max-height: 90px;"> + <menuitem label="One"/> + <menuitem label="Two"/> + <menuitem label="Three"/> + <menuitem label="Four"/> + <menuitem label="Five"/> + <menuitem label="Six"/> + <menuitem label="Seven"/> + </menupopup> + </menulist> +</hbox> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_mousescroll.xhtml b/toolkit/content/tests/chrome/test_mousescroll.xhtml new file mode 100644 index 0000000000..875ca4ac98 --- /dev/null +++ b/toolkit/content/tests/chrome/test_mousescroll.xhtml @@ -0,0 +1,291 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=378028 +--> +<window title="Mozilla Bug 378028" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=378028" + target="_blank">Mozilla Bug 378028</a> + </body> + + <!-- richlistbox currently has no way of giving us a defined number of + rows, so we just choose an arbitrary height limit that should give + us plenty of vertical scrollability --> + <richlistbox id="richlistbox" style="height:50px;"> + <richlistitem id="richlistbox_item0" hidden="true"><label value="Item 0"/></richlistitem> + <richlistitem id="richlistbox_item1"><label value="Item 1"/></richlistitem> + <richlistitem id="richlistbox_item2"><label value="Item 2"/></richlistitem> + <richlistitem id="richlistbox_item3"><label value="Item 3"/></richlistitem> + <richlistitem id="richlistbox_item4"><label value="Item 4"/></richlistitem> + <richlistitem id="richlistbox_item5"><label value="Item 5"/></richlistitem> + <richlistitem id="richlistbox_item6"><label value="Item 6"/></richlistitem> + <richlistitem id="richlistbox_item7"><label value="Item 7"/></richlistitem> + <richlistitem id="richlistbox_item8"><label value="Item 8"/></richlistitem> + </richlistbox> + + <box orient="horizontal"> + <arrowscrollbox id="hscrollbox" clicktoscroll="true" orient="horizontal" + smoothscroll="false" style="max-width:80px;" flex="1"> + <hbox style="min-width:40px; min-height:20px; background:black;" hidden="true"/> + <hbox style="min-width:40px; min-height:20px; background:white;"/> + <hbox style="min-width:40px; min-height:20px; background:black;"/> + <hbox style="min-width:40px; min-height:20px; background:white;"/> + <hbox style="min-width:40px; min-height:20px; background:black;"/> + <hbox style="min-width:40px; min-height:20px; background:white;"/> + <hbox style="min-width:40px; min-height:20px; background:black;"/> + <hbox style="min-width:40px; min-height:20px; background:white;"/> + <hbox style="min-width:40px; min-height:20px; background:black;"/> + </arrowscrollbox> + </box> + + <arrowscrollbox id="vscrollbox" clicktoscroll="true" orient="vertical" + smoothscroll="false" style="max-height:80px;" flex="1"> + <vbox style="min-width:100px; min-height:40px; background:black;" hidden="true"/> + <vbox style="min-width:100px; min-height:40px; background:white;"/> + <vbox style="min-width:100px; min-height:40px; background:black;"/> + <vbox style="min-width:100px; min-height:40px; background:white;"/> + <vbox style="min-width:100px; min-height:40px; background:black;"/> + <vbox style="min-width:100px; min-height:40px; background:white;"/> + <vbox style="min-width:100px; min-height:40px; background:black;"/> + <vbox style="min-width:100px; min-height:40px; background:white;"/> + <vbox style="min-width:100px; min-height:40px; background:black;"/> + <vbox style="min-width:100px; min-height:40px; background:white;"/> + <vbox style="min-width:100px; min-height:40px; background:black;"/> + </arrowscrollbox> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +/** Test for Bug 378028 **/ +/* and for Bug 350471 **/ +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(prepareRunningTests); + +// Some tests need to wait until stopping scroll completely. At this time, +// setTimeout() will retry to check up to MAX_RETRY_COUNT times. +const MAX_RETRY_COUNT = 5; + +const deltaModes = [ + WheelEvent.DOM_DELTA_PIXEL, // 0 + WheelEvent.DOM_DELTA_LINE, // 1 + WheelEvent.DOM_DELTA_PAGE // 2 +]; + +function sendWheelAndWait(aScrollTaget, aX, aY, aEvent, aChecker) +{ + function continueTestsIfScrolledAsExpected() { + if (!aChecker()) + SimpleTest.executeSoon(()=>{ continueTestsIfScrolledAsExpected(aChecker) }); + else + runTests(); + } + + sendWheelAndPaint(aScrollTaget, aX, aY, aEvent, ()=>{ + // sendWheelAndPaint may wait not enough for <scrollbox>. + // Let's check the position before using is() for avoiding random orange. + // So, this test may detect regressions with timeout. + continueTestsIfScrolledAsExpected(aChecker); + }); +} + +function* testRichListbox(id) +{ + var listbox = document.getElementById(id); + + function* helper(aStart, aDelta, aIntDelta, aDeltaMode) { + listbox.ensureElementIsVisible(listbox.getItemAtIndex(aStart),true); + + let event = { + deltaMode: aDeltaMode, + deltaY: aDelta, + lineOrPageDeltaY: aIntDelta + }; + // We don't need to wait for finishing the scroll in this test. + yield sendWheelAndWait(listbox, 10, 10, event, ()=>{ return true; }); + var change = listbox.getIndexOfFirstVisibleRow() - aStart; + var direction = (change > 0) - (change < 0); + var expected = (aDelta > 0) - (aDelta < 0); + is(direction, expected, + "testRichListbox(" + id + "): vertical, starting " + aStart + + " delta " + aDelta + " lineOrPageDeltaY " + aIntDelta + + " aDeltaMode " + aDeltaMode); + + // Check that horizontal scrolling has no effect + event = { + deltaMode: aDeltaMode, + deltaX: aDelta, + lineOrPageDeltaX: aIntDelta + }; + + listbox.ensureElementIsVisible(listbox.getItemAtIndex(aStart),true); + yield sendWheelAndWait(listbox, 10, 10, event, ()=>{ return true; }); + is(listbox.getIndexOfFirstVisibleRow(), aStart, + "testRichListbox(" + id + "): horizontal, starting " + aStart + + " delta " + aDelta + " lineOrPageDeltaX " + aIntDelta + + " aDeltaMode " + aDeltaMode); + } + + // richlistbox currently uses native XUL scrolling, so the "line" + // amounts don't necessarily correspond 1-to-1 with listbox items. So + // we just check that scrolling up/down scrolls in the right direction. + for (let i = 0; i < deltaModes.length; i++) { + let delta = (deltaModes[i] == WheelEvent.DOM_DELTA_PIXEL) ? 32.0 : 2.0; + yield* helper(5, -delta, -1, deltaModes[i]); + yield* helper(5, -delta, 0, deltaModes[i]); + yield* helper(5, delta, 1, deltaModes[i]); + yield* helper(5, delta, 0, deltaModes[i]); + } +} + +function* testArrowScrollbox(id) +{ + var arrowscrollbox = document.getElementById(id); + var scrollbox = arrowscrollbox.scrollbox; + var orient = scrollbox.getAttribute("orient"); + var orientIsHorizontal = (orient == "horizontal"); + + function* helper(aStart, aDelta, aDeltaMode, aExpected) + { + var lineOrPageDelta = (aDeltaMode == WheelEvent.DOM_DELTA_PIXEL) ? aDelta / 10 : aDelta; + + scrollbox.scrollTo(aStart, aStart); + for (let i = orientIsHorizontal ? 2 : 0; i >= 0; i--) { + // Note, vertical mouse scrolling is allowed to scroll horizontal + // arrowscrollboxes, because many users have no horizontal mouse scroll + // capability + let expected = !i ? aExpected : aStart; + let getPos = ()=>{ + return orientIsHorizontal ? scrollbox.scrollLeft : + scrollbox.scrollTop; + }; + let oldPos = -1; + let retry = 0; + yield sendWheelAndWait(scrollbox, 5, 5, + { deltaMode: aDeltaMode, deltaY: aDelta, + lineOrPageDeltaY: lineOrPageDelta }, + ()=>{ + if (getPos() == expected) { + return true; + } + if (oldPos == getPos()) { + // If scroll stopped completely, let's continue the test. + return ++retry == MAX_RETRY_COUNT; + } + oldPos = getPos(); + retry = 0; + return false; + }); + is(getPos(), expected, + "testArrowScrollbox(" + id + "): vertical, starting " + aStart + + " delta " + aDelta + " lineOrPageDelta " + lineOrPageDelta + + " aDeltaMode " + aDeltaMode); + } + + scrollbox.scrollTo(aStart, aStart); + for (let i = orientIsHorizontal ? 2 : 0; i >= 0; i--) { + // horizontal mouse scrolling is never allowed to scroll vertical + // arrowscrollboxes + let expected = (!i && orientIsHorizontal) ? aExpected : aStart; + let getPos = ()=>{ + return orientIsHorizontal ? scrollbox.scrollLeft : + scrollbox.scrollTop; + }; + let oldPos = -1; + let retry = 0; + yield sendWheelAndWait(scrollbox, 5, 5, + { deltaMode: aDeltaMode, deltaX: aDelta, + lineOrPageDeltaX: lineOrPageDelta }, + ()=>{ + if (getPos() == expected) { + return true; + } + if (oldPos == getPos()) { + // If scroll stopped completely, let's continue the test. + return ++retry == MAX_RETRY_COUNT; + } + oldPos = getPos(); + retry = 0; + return false; + }); + is(getPos(), expected, + "testArrowScrollbox(" + id + "): horizontal, starting " + aStart + + " delta " + aDelta + " lineOrPageDelta " + lineOrPageDelta + + " aDeltaMode " + aDeltaMode); + } + } + + var line = arrowscrollbox.lineScrollAmount; + var scrolledWidth = scrollbox.scrollWidth; + var scrolledHeight = scrollbox.scrollHeight; + var scrollMaxX = scrolledWidth - scrollbox.getBoundingClientRect().width; + var scrollMaxY = scrolledHeight - scrollbox.getBoundingClientRect().height; + var scrollMax = orientIsHorizontal ? scrollMaxX : scrollMaxY; + + for (let deltaMode of deltaModes) { + const start = 50; + const delta = 1000; + let expectedNegative = 0; + let expectedPositive = scrollMax; + if (deltaMode == WheelEvent.DOM_DELTA_LINE) { + let maxDelta = Math.floor(Math.max(1, arrowscrollbox.scrollClientSize / line)) * line; + expectedNegative = Math.max(0, start - maxDelta); + expectedPositive = Math.min(scrollMax, start + maxDelta); + } + yield* helper(start, -delta, deltaMode, expectedNegative); + yield* helper(start, delta, deltaMode, expectedPositive); + } +} + +var gTestContinuation = null; + +function runTests() +{ + if (!gTestContinuation) { + gTestContinuation = testBody(); + } + var ret = gTestContinuation.next(); + if (ret.done) { + var winUtils = SpecialPowers.getDOMWindowUtils(window); + winUtils.restoreNormalRefresh(); + SimpleTest.finish(); + } +} + +async function prepareRunningTests() +{ + // Before actually running tests, we disable auto-dir scrolling, becasue the + // horizontal scrolling tests in this file are mostly meant to ensure that the + // tested controls in the default style should only have one scrollbar and it + // must always be in the block-flow direction so they are not really meant to + // test default actions for wheel events, so we simply disabled auto-dir + // scrolling, which are well tested in + // dom/events/test/window_wheel_default_action.html. + await SpecialPowers.pushPrefEnv({"set": [["mousewheel.autodir.enabled", + false]]}); + + runTests(); +} + +function* testBody() +{ + yield* testRichListbox("richlistbox"); + + // Perform a mousedown to ensure the wheel transaction from the previous test + // does not impact the next test. + synthesizeMouse(document.scrollingElement, 0, 0, {type: "mousedown"}, window); + yield* testArrowScrollbox("hscrollbox"); + + synthesizeMouse(document.scrollingElement, -1, -1, {type: "mousedown"}, window); + yield* testArrowScrollbox("vscrollbox"); +} + + ]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_mozinputbox_dictionary.xhtml b/toolkit/content/tests/chrome/test_mozinputbox_dictionary.xhtml new file mode 100644 index 0000000000..c2ed8a4787 --- /dev/null +++ b/toolkit/content/tests/chrome/test_mozinputbox_dictionary.xhtml @@ -0,0 +1,100 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for textbox with Add and Undo Add to Dictionary + --> +<window title="Textbox Add and Undo Add to Dictionary Test" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <hbox> + <moz-input-box id="t1" oncontextmenu="runContextMenuTest()" spellcheck="true"> + <html:input class="textbox-input" value="Hellop" spellcheck="true"/> + </moz-input-box> + </hbox> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var inputBox; +var testNum; + +function bringUpContextMenu(element) +{ + synthesizeMouseAtCenter(element, { type: "contextmenu", button: 2}); +} + +function leftClickElement(element) +{ + synthesizeMouseAtCenter(element, { button: 0 }); +} + +var onSpellCheck; +function startTests() +{ + inputBox = document.getElementById("t1"); + inputBox._input.focus(); + testNum = 0; + + ({onSpellCheck} = ChromeUtils.importESModule( + "resource://testing-common/AsyncSpellCheckTestHelper.sys.mjs")); + onSpellCheck(inputBox._input, function () { + bringUpContextMenu(inputBox); + }); +} + +function runContextMenuTest() +{ + SimpleTest.executeSoon(function() { + var contextMenu = inputBox.menupopup; + + switch(testNum) + { + case 0: // "Add to Dictionary" button + var addToDict = inputBox.getMenuItem("spell-add-to-dictionary"); + ok(!addToDict.hidden, "Is Add to Dictionary visible?"); + + var separator = inputBox.getMenuItem("spell-suggestions-separator"); + ok(!separator.hidden, "Is separator visible?"); + + addToDict.doCommand(); + + contextMenu.hidePopup(); + testNum++; + + onSpellCheck(inputBox._input, function () { + bringUpContextMenu(inputBox); + }); + break; + + case 1: // "Undo Add to Dictionary" button + var undoAddDict = inputBox.getMenuItem("spell-undo-add-to-dictionary"); + ok(!undoAddDict.hidden, "Is Undo Add to Dictioanry visible?"); + + separator = inputBox.getMenuItem("spell-suggestions-separator"); + ok(!separator.hidden, "Is separator hidden?"); + + undoAddDict.doCommand(); + + contextMenu.hidePopup(); + onSpellCheck(inputBox._input, function () { + SimpleTest.finish(); + }); + break; + } + }); +} + +SimpleTest.waitForFocus(startTests); + + ]]></script> + +</window> diff --git a/toolkit/content/tests/chrome/test_named_deck.html b/toolkit/content/tests/chrome/test_named_deck.html new file mode 100644 index 0000000000..3445ef05c7 --- /dev/null +++ b/toolkit/content/tests/chrome/test_named_deck.html @@ -0,0 +1,251 @@ +<!DOCTYPE HTML> +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> + <meta charset="utf-8"> + <title><!-- Shadow Parts issue with xul/xbl domparser --></title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script> +const { BrowserTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" +); +const DEFAULT_SECTION_NAMES = ["one", "two", "three"]; + +function makeButton({ name, deckId }) { + let button = document.createElement("button", { is: "named-deck-button" }); + button.setAttribute("name", name); + button.deckId = deckId; + button.textContent = name.toUpperCase(); + return button; +} + +function makeSection({ name }) { + let view = document.createElement("section"); + view.setAttribute("name", name); + view.textContent = name + name; + return view; +} + +function addSection({ name, deck, buttons }) { + let button = makeButton({ name, deckId: deck.id }); + buttons.appendChild(button); + let view = makeSection({ name }); + deck.appendChild(view); + return { button, view }; +} + +async function runTests({ deck, buttons }) { + const selectedSlot = deck.shadowRoot.querySelector('slot[name="selected"]'); + const getButtonByName = name => buttons.querySelector(`[name="${name}"]`); + + function checkState(name, count, empty = false) { + // Check that the right view is selected. + is(deck.selectedViewName, name, "The right view is selected"); + + // Verify there's one element in the slot. + let slottedEls = selectedSlot.assignedElements(); + if (empty) { + is(slottedEls.length, 0, "The deck is empty"); + } else { + is(slottedEls.length, 1, "There's one visible view"); + is( + slottedEls[0].getAttribute("name"), + name, + "The correct view is in the slot" + ); + } + + // Check that the hidden properties are set. + let sections = deck.querySelectorAll("section"); + is(sections.length, count, "There are the right number of sections"); + for (let section of sections) { + let sectionName = section.getAttribute("name"); + if (sectionName == name) { + is(section.slot, "selected", `${sectionName} is visible`); + } else { + is(section.slot, "", `${sectionName} is hidden`); + } + } + + // Check the right button is selected. + is(buttons.children.length, count, "There are the right number of buttons"); + for (let button of buttons.children) { + let buttonName = button.getAttribute("name"); + let selected = buttonName == name; + is( + button.hasAttribute("selected"), + selected, + `${buttonName} is ${selected ? "selected" : "not selected"}` + ); + } + } + + // Check that the first view is selected by default. + checkState("one", 3); + + // Switch to the third view. + info("Switch to section three"); + getButtonByName("three").click(); + checkState("three", 3); + + // Add a new section, nothing changes. + info("Add section last"); + let last = addSection({ name: "last", deck, buttons }); + checkState("three", 4); + + // We can switch to the new section. + last.button.click(); + info("Switch to section last"); + checkState("last", 4); + + info("Switch view with selectedViewName"); + let shown = BrowserTestUtils.waitForEvent(deck, "view-changed"); + deck.selectedViewName = "two"; + await shown; + checkState("two", 4); + + info("Switch back to the last view to test removing selected view"); + shown = BrowserTestUtils.waitForEvent(deck, "view-changed"); + deck.setAttribute("selected-view", "last"); + await shown; + checkState("last", 4); + + // Removing the selected section leaves the selected slot empty. + info("Remove section last"); + last.button.remove(); + last.view.remove(); + + info("Should not have any selected views"); + checkState("last", 3, true); + + // Setting a missing view will give a "view-changed" event. + info("Set view to a missing name"); + let hidden = BrowserTestUtils.waitForEvent(deck, "view-changed"); + deck.selectedViewName = "missing"; + await hidden; + checkState("missing", 3, true); + + // Adding the view won't trigger "view-changed", but the view will slotted. + info("Add the missing view, it should be shown"); + shown = BrowserTestUtils.waitForEvent(selectedSlot, "slotchange"); + let viewChangedEvent = false; + let viewChangedFn = () => { + viewChangedEvent = true; + }; + deck.addEventListener("view-changed", viewChangedFn); + addSection({ name: "missing", deck, buttons }); + await shown; + deck.removeEventListener("view-changed", viewChangedFn); + ok(!viewChangedEvent, "The view-changed event didn't fire"); + checkState("missing", 4); +} + +async function setup({ beAsync, first, deckId }) { + // Make the deck and buttons. + const deck = document.createElement("named-deck"); + deck.id = deckId; + for (let name of DEFAULT_SECTION_NAMES) { + deck.appendChild(makeSection({ name })); + } + const buttons = document.createElement("button-group"); + for (let name of DEFAULT_SECTION_NAMES) { + buttons.appendChild(makeButton({ name, deckId })); + } + + let ordered; + if (first == "deck") { + ordered = [deck, buttons]; + } else if (first == "buttons") { + ordered = [buttons, deck]; + } else { + throw new Error("Invalid order"); + } + + // Insert them in the specified order, possibly async. + document.body.appendChild(ordered.shift()); + if (beAsync) { + await new Promise(resolve => requestAnimationFrame(resolve)); + } + document.body.appendChild(ordered.shift()); + + return { deck, buttons }; +} + +add_task(async function testNamedDeckAndButtons() { + // Check adding the deck first. + dump("Running deck first tests synchronously"); + await runTests(await setup({ beAsync: false, first: "deck", deckId: "deck-sync" })); + dump("Running deck first tests asynchronously"); + await runTests(await setup({ beAsync: true, first: "deck", deckId: "deck-async" })); + + // Check adding the buttons first. + dump("Running buttons first tests synchronously"); + await runTests(await setup({ beAsync: false, first: "buttons", deckId: "buttons-sync" })); + dump("Running buttons first tests asynchronously"); + await runTests(await setup({ beAsync: true, first: "buttons", deckId: "buttons-async" })); +}); + +add_task(async function testFocusAndClickMixing() { + const waitForAnimationFrame = () => + new Promise(r => requestAnimationFrame(r)); + const sendTab = (e = {}) => { + synthesizeKey("VK_TAB", e); + return waitForAnimationFrame(); + }; + + const firstButton = document.createElement("button"); + document.body.append(firstButton); + + const { deck, buttons: buttonGroup } = await setup({ + beAsync: false, + first: "buttons", + deckId: "focus-click-mixing", + }); + const buttons = buttonGroup.children; + firstButton.focus(); + const secondButton = document.createElement("button"); + document.body.append(secondButton); + + await sendTab(); + is(document.activeElement, buttons[0], "first deck button is focused"); + is(deck.selectedViewName, "one", "first view is shown"); + + await sendTab(); + is(document.activeElement, secondButton, "focus moves out of group"); + + await sendTab({ shiftKey: true }); + is(document.activeElement, buttons[0], "focus moves back to first button"); + + // Click on another tab button, this should make it the focusable button. + synthesizeMouseAtCenter(buttons[1], {}); + await waitForAnimationFrame(); + + is(deck.selectedViewName, "two", "second view is shown"); + + if (document.activeElement != buttons[1]) { + // On Mac the button isn't focused on click, but it is on Windows/Linux. + await sendTab(); + } + is(document.activeElement, buttons[1], "second deck button is focusable"); + + await sendTab(); + is(document.activeElement, secondButton, "focus moved to second plain button"); + + await sendTab({ shiftKey: true }); + is(document.activeElement, buttons[1], "second deck button is focusable"); + + await sendTab({ shiftKey: true }); + is( + document.activeElement, + firstButton, + "next shift-tab moves out of button group" + ); +}); + </script> +</head> +<body> +</body> +</html> diff --git a/toolkit/content/tests/chrome/test_navigate_persist.html b/toolkit/content/tests/chrome/test_navigate_persist.html new file mode 100644 index 0000000000..2ef84ddb15 --- /dev/null +++ b/toolkit/content/tests/chrome/test_navigate_persist.html @@ -0,0 +1,87 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1460639 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1460639</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + function navigateWindowTo(win, url) { + return new Promise(resolve => { + Services.obs.addObserver(function listener(document) { + Services.obs.removeObserver(listener, "document-element-inserted"); + document.addEventListener("DOMContentLoaded", () => { + resolve(); + }, { once: true } ); + }, "document-element-inserted"); + win.location = url; + }); + } + + function promiseMaybeResizeEvent(win, expectedSize) { + return new Promise(resolve => { + // If the size is already as expected, then there may be no resize + // event. + if (win.outerWidth === expectedSize + && win.outerHeight === expectedSize) { + resolve(); + } + win.addEventListener("resize", () => { + resolve(); + }, {once: true}); + }); + } + + function resize(win, size) { + const resizePromise = promiseMaybeResizeEvent(win, size); + win.resizeTo(size, size); + return resizePromise; + } + + async function runTest() { + // Test that persisted window attributes are loaded when a top level + // window is navigated. This mimics the behavior of early first paint by + // first loading about:blank and then navigating to window_navigate_persist.html. + const PERSIST_SIZE = 200; + // First, load the document and resize it so the size is persisted. + let win = window.browsingContext.topChromeWindow + .openDialog("window_navigate_persist.html", "_blank", `chrome,all,dialog=no`); + await SimpleTest.promiseFocus(win); + await resize(win, PERSIST_SIZE); + is(win.outerWidth, PERSIST_SIZE, "Window is resized to desired width"); + is(win.outerHeight, PERSIST_SIZE, "Window is resized to desired height"); + win.close(); + + // Now mimic early first paint. + win = window.browsingContext.topChromeWindow + .openDialog("about:blank", "_blank", `chrome,all,dialog=no`); + await SimpleTest.promiseFocus(win, true); + isnot(win.outerWidth, PERSIST_SIZE, "Initial window width is not the persisted size"); + isnot(win.outerHeight, PERSIST_SIZE, "Initial window height is not the persisted size"); + + await navigateWindowTo(win, "window_navigate_persist.html"); + await promiseMaybeResizeEvent(win, PERSIST_SIZE); + is(win.outerWidth, PERSIST_SIZE, "Window width is persisted"); + is(win.outerHeight, PERSIST_SIZE, "Window height is persisted"); + win.close(); + SimpleTest.finish(); + } + + </script> +</head> +<body onload="runTest()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1460639">Mozilla Bug 1460639</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/chrome/test_notificationbox.xhtml b/toolkit/content/tests/chrome/test_notificationbox.xhtml new file mode 100644 index 0000000000..8de985175a --- /dev/null +++ b/toolkit/content/tests/chrome/test_notificationbox.xhtml @@ -0,0 +1,731 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for notificationbox + --> +<window title="Notification Box" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <vbox id="nb"/> + <menupopup id="menupopup" onpopupshown="this.hidePopup()" onpopuphidden="checkPopupClosed()"> + <menuitem label="One"/> + </menupopup> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ +const NOTIFICATION_LOCAL_NAME = "notification-message" +SimpleTest.waitForExplicitFinish(); + +var testtag_notificationbox_buttons = [ + { + label: "Button 1", + accesskey: "u", + callback: testtag_notificationbox_buttonpressed, + popup: "menupopup" + } +]; + +var testtag_notificationbox_buttons_nopopup = [ + { + label: "Button 1 No Popup", + accesskey: "u", + callback: testtag_notificationbox_button1pressed, + }, + { + label: "Button 2 No Popup", + accesskey: "u", + callback: testtag_notificationbox_button2pressed, + } +]; + +let testtag_notificationbox_button_l10n = [ + { + "l10n-id": "test-id" + } +]; + +var testtag_notificationbox_links = [ + { + label: "Link 1", + callback: testtag_notificationbox_buttonpressed, + link: "about:mozilla" + }, + { + label: "Button 2", + accesskey: "u", + callback: testtag_notificationbox_buttonpressed, + } +]; + +var testtag_notificationbox_supportpage = [ + { + supportPage: "test1", + }, + { + label: "This is an existing label", + supportPage: "test2", + }, + { + supportPage: "test3", + "l10n-id": "more-specific-id", + }, + { + supportPage: "test4", + label: "legacy label call", + "l10n-id": "modern-fluent-id" + } +]; + +function testtag_notificationbox_buttonpressed(notification, button) +{ + SimpleTest.is(button.localName, "button"); + return false; +} + +let buttonsPressedLog = ""; +function testtag_notificationbox_button1pressed(notification, button) { buttonsPressedLog += "button1"; return true; } +function testtag_notificationbox_button2pressed(notification, button) { buttonsPressedLog += "button2"; return true; } + +function testtag_notificationbox(nb) +{ + testtag_notificationbox_State(nb, "initial", null, 0); + + SimpleTest.is(nb.removeAllNotifications(false), undefined, "initial removeAllNotifications"); + testtag_notificationbox_State(nb, "initial removeAllNotifications", null, 0); + SimpleTest.is(nb.removeAllNotifications(true), undefined, "initial removeAllNotifications immediate"); + testtag_notificationbox_State(nb, "initial removeAllNotifications immediate", null, 0); + + runTimedTests(tests, -1, nb, null); +} + +var notification_last_events = []; +function notification_eventCallback(event) +{ + notification_last_events.push({ actualEvent: event , item: this }); +} + +/** + * For any notifications that have the notification_eventCallback on + * them, we will have recorded instances of those callbacks firing + * and stored them. This checks to see that the expected event types + * are being fired in order, and targeting the right item. + * + * @param {Array<string>} expectedEvents + * The list of event types, in order, that we expect to have been + * fired on the item. + * @param {<xul:notification>} ntf + * The notification we expect the callback to have been fired from. + * @param {string} testName + * The name of the current test, for logging. + */ +function testtag_notification_eventCallback(expectedEvents, ntf, testName) +{ + for (let i = 0; i < expectedEvents; ++i) { + let expected = expectedEvents[i]; + let { actualEvent, item } = notification_last_events[i]; + SimpleTest.is(actualEvent, expected, testName + ": event name"); + SimpleTest.is(item, ntf, testName + ": event item"); + } + notification_last_events = []; +} + +var tests = +[ + { + async test(nb, ntf) { + ntf = await nb.appendNotification("mutable", { + label: "Original", + priority: nb.PRIORITY_INFO_LOW, + }, testtag_notificationbox_buttons); + + ntf.label = "Changed string"; + await ntf.updateComplete; + SimpleTest.is(ntf.messageText.textContent.trim(), "Changed string", "set notification label with string"); + return ntf; + }, + result(nb, ntf) { + nb.removeNotification(ntf); + testtag_notificationbox_State(nb, "set notification label", null, 0); + } + }, + /* + Ensures that buttons created with the "label" parameter have their + label attribute set correctly. + */ + { + async test(nb, ntf) { + ntf = await nb.appendNotification("note", { + label: "Notification", + image: "happy.png", + priority: nb.PRIORITY_INFO_LOW, + }, testtag_notificationbox_buttons); + SimpleTest.is(ntf && ntf.localName == NOTIFICATION_LOCAL_NAME, true, "append notification"); + const button = ntf.buttonContainer.querySelector("button"); + SimpleTest.is(button.label, "Button 1", "set button label with the 'label' parameter"); + return ntf; + }, + result(nb, ntf) { + nb.removeNotification(ntf); + testtag_notificationbox_State(nb, "set notification label", null, 0); + } + }, + { + /* + Ensures that buttons created with the "l10n-id" parameter have + their "l10n-id" assigned correctly. + */ + async test(nb, ntf) { + ntf = await nb.appendNotification("note", { + label: "Notification", + image: "happy.png", + priority: nb.PRIORITY_INFO_LOW, + }, testtag_notificationbox_button_l10n); + SimpleTest.is(ntf && ntf.localName == NOTIFICATION_LOCAL_NAME, true, "append notification"); + const button = ntf.buttonContainer.querySelector("button"); + SimpleTest.is(button.dataset.l10nId, "test-id", "create notification button with correctly assigned l10n id"); + return ntf; + }, + result(nb, ntf) { + nb.removeNotification(ntf); + testtag_notificationbox_State(nb, "set notification label", null, 0); + } + }, + { + async test(nb, ntf) { + // append a new notification + ntf = await nb.appendNotification("note", { + label: "Notification", + image: "happy.png", + priority: nb.PRIORITY_INFO_LOW, + }, testtag_notificationbox_buttons); + SimpleTest.is(ntf && ntf.localName == NOTIFICATION_LOCAL_NAME, true, "append notification"); + return ntf; + }, + result(nb, ntf) { + testtag_notificationbox_State(nb, "append", ntf, 1); + testtag_notification_State(nb, ntf, "append", "Notification", "note", + "happy.png", nb.PRIORITY_INFO_LOW); + + // check the getNotificationWithValue method + var ntf_found = nb.getNotificationWithValue("note"); + SimpleTest.is(ntf, ntf_found, "getNotificationWithValue note"); + + var none_found = nb.getNotificationWithValue("notenone"); + SimpleTest.is(none_found, null, "getNotificationWithValue null"); + return ntf; + } + }, + { + test(nb, ntf) { + // check that notifications can be removed properly + nb.removeNotification(ntf); + return ntf; + }, + result(nb, ntf) { + testtag_notificationbox_State(nb, "removeNotification", null, 0); + } + }, + { + async test(nb, ntf) { + // append a new notification, but now with an event callback + ntf = await nb.appendNotification("note", { + label: "Notification", + image: "happy.png", + priority: nb.PRIORITY_INFO_LOW, + eventCallback: notification_eventCallback, + }, testtag_notificationbox_buttons); + SimpleTest.is(ntf && ntf.localName == NOTIFICATION_LOCAL_NAME, true, "append notification with callback"); + return ntf; + }, + result(nb, ntf) { + testtag_notificationbox_State(nb, "append with callback", ntf, 1); + return ntf; + } + }, + { + test(nb, ntf) { + nb.removeNotification(ntf); + return ntf; + }, + result(nb, ntf) { + testtag_notificationbox_State(nb, "removeNotification with callback", + null, 0); + + testtag_notification_eventCallback(["removed"], ntf, "removeNotification()"); + return ntf; + } + }, + { + async test(nb, ntf) { + ntf = await nb.appendNotification("note", { + label: "Notification", + image: "happy.png", + priority: nb.PRIORITY_INFO_MEDIUM, + eventCallback: notification_eventCallback, + }, testtag_notificationbox_buttons); + SimpleTest.is(ntf && ntf.localName == NOTIFICATION_LOCAL_NAME, true, "append notification with object"); + return ntf; + }, + result(nb, ntf) { + testtag_notificationbox_State(nb, "append with callback", ntf, 1); + testtag_notificationbox_State(nb, "append using object", ntf, 1); + testtag_notification_State(nb, ntf, "append object", "Notification", "note", + "happy.png", nb.PRIORITY_INFO_MEDIUM); + return ntf; + } + }, + { + test(rb, ntf) { + // Dismissing the notification instead of removing it should + // fire a dismissed "event" on the callback, followed by + // a removed "event". + ntf.dismiss(); + return ntf; + }, + result(nb, ntf) { + testtag_notificationbox_State(nb, "called dismiss()", null, 0); + testtag_notification_eventCallback(["dismissed", "removed"], ntf, + "dismiss()"); + return ntf; + } + }, + { + async test(nb, ntf) { + ntf = await nb.appendNotification("note", { + label: "Notification", + image: "happy.png", + priority: nb.PRIORITY_WARNING_LOW, + eventCallback: notification_eventCallback, + }, [{ + label: "Button", + }]); + return ntf; + }, + result(nb, ntf) { + testtag_notificationbox_State(nb, "append", ntf, 1); + testtag_notification_State(nb, ntf, "append", "Notification", "note", + "happy.png", nb.PRIORITY_WARNING_LOW); + nb.removeNotification(ntf); + + return [1, null]; + } + }, + { + repeat: true, + async test(nb, arr) { + var idx = arr[0]; + var ntf = arr[1]; + switch (idx) { + case 1: + // append a new notification + ntf = await nb.appendNotification("note", { + label: "Notification", + image: "happy.png", + priority: nb.PRIORITY_INFO_LOW, + }, testtag_notificationbox_buttons); + SimpleTest.is(ntf && ntf.localName == NOTIFICATION_LOCAL_NAME, true, "append notification"); + + // Test persistence + ntf.persistence++; + + return [idx, ntf]; + case 2: + case 3: + nb.removeTransientNotifications(); + + return [idx, ntf]; + } + return ntf; + }, + result(nb, arr) { + var idx = arr[0]; + var ntf = arr[1]; + switch (idx) { + case 1: + testtag_notificationbox_State(nb, "notification added", ntf, 1); + testtag_notification_State(nb, ntf, "append", "Notification", "note", + "happy.png", nb.PRIORITY_INFO_LOW); + SimpleTest.is(ntf.persistence, 1, "persistence is 1"); + + return [++idx, ntf]; + case 2: + testtag_notificationbox_State(nb, "first removeTransientNotifications", ntf, 1); + testtag_notification_State(nb, ntf, "append", "Notification", "note", + "happy.png", nb.PRIORITY_INFO_LOW); + SimpleTest.is(ntf.persistence, 0, "persistence is now 0"); + + return [++idx, ntf]; + case 3: + testtag_notificationbox_State(nb, "second removeTransientNotifications", null, 0); + + this.repeat = false; + } + return ntf; + } + }, + { + async test(nb, ntf) { + // append another notification + ntf = await nb.appendNotification("note", { + label: "Notification", + image: "happy.png", + priority: nb.PRIORITY_INFO_MEDIUM, + }, testtag_notificationbox_buttons); + SimpleTest.is(ntf && ntf.localName == NOTIFICATION_LOCAL_NAME, true, "append notification again"); + return ntf; + }, + result(nb, ntf) { + // check that appending a second notification after removing the first one works + testtag_notificationbox_State(nb, "append again", ntf, 1); + testtag_notification_State(nb, ntf, "append again", "Notification", "note", + "happy.png", nb.PRIORITY_INFO_MEDIUM); + return ntf; + } + }, + { + test(nb, ntf) { + // check the removeCurrentNotification method + nb.removeCurrentNotification(); + return ntf; + }, + result(nb, ntf) { + testtag_notificationbox_State(nb, "removeCurrentNotification", null, 0); + } + }, + { + async test(nb, ntf) { + ntf = await nb.appendNotification("note", { + label: "Notification", + image: "happy.png", + priority: nb.PRIORITY_INFO_HIGH, + }, testtag_notificationbox_buttons); + return ntf; + }, + result(nb, ntf) { + // test the removeAllNotifications method + testtag_notificationbox_State(nb, "append info_high", ntf, 1); + SimpleTest.is(ntf.priority, nb.PRIORITY_INFO_HIGH, + "notification.priority " + nb.PRIORITY_INFO_HIGH); + SimpleTest.is(nb.removeAllNotifications(false), undefined, "removeAllNotifications"); + } + }, + { + async test(nb, ntf) { + ntf = await nb.appendNotification("note", { + label: "Notification", + image: "happy.png", + priority: nb.PRIORITY_INFO_LOW, + eventCallback: notification_eventCallback, + }, testtag_notificationbox_links); + SimpleTest.is(ntf && ntf.localName == NOTIFICATION_LOCAL_NAME, true, "append link notification with callback"); + return ntf; + }, + result(nb, ntf) { + testtag_notificationbox_State(nb, "append link with callback", ntf, 1); + + let buttonContainer = ntf.buttonContainer; + let button = buttonContainer.lastElementChild; + SimpleTest.is(button.localName, "button", "button is a button"); + SimpleTest.ok(!button.href, "button href is not set"); + + let link = ntf.querySelector(".notification-link"); + SimpleTest.is(link.localName, "label", "link is a label"); + SimpleTest.is(link.href, "about:mozilla", "link href is correct"); + + SimpleTest.is(nb.removeAllNotifications(false), undefined, "removeAllNotifications"); + } + }, + { + async test(nb, ntf) { + // append a new notification + ntf = await nb.appendNotification("note", { + label: "Notification", + image: "happy.png", + priority: nb.PRIORITY_INFO_LOW, + }, testtag_notificationbox_buttons_nopopup); + return ntf; + }, + result(nb, ntf) { + let buttons = nb.currentNotification.buttonContainer.querySelectorAll("* button"); + + buttons[0].focus(); + synthesizeKey(" ", {}); + SimpleTest.is(buttonsPressedLog, "button1", "button 1 with keyboard"); + buttons[1].focus(); + synthesizeKey(" ", {}); + SimpleTest.is(buttonsPressedLog, "button1button2", "button 2 with keyboard"); + + synthesizeMouseAtCenter(buttons[0], {}); + SimpleTest.is(buttonsPressedLog, "button1button2button1", "button 1 with mouse"); + synthesizeMouseAtCenter(buttons[1], {}); + SimpleTest.is(buttonsPressedLog, "button1button2button1button2", "button 2 with mouse"); + + nb.removeAllNotifications(true); + } + }, + { + async test(nb, ntf) { + ntf = await nb.appendNotification("note", { + label: "Notification", + image: "happy.png", + priority: nb.PRIORITY_INFO_LOW, + eventCallback: notification_eventCallback, + }, testtag_notificationbox_supportpage); + await ntf.updateComplete; + SimpleTest.is(ntf && ntf.localName == NOTIFICATION_LOCAL_NAME, true, "append support page notification"); + return ntf; + }, + result(nb, ntf) { + testtag_notificationbox_State(nb, "append link with callback", ntf, 1); + + let link = ntf.querySelector(".notification-link"); + SimpleTest.is(link.localName, "a", "link 1 is an anchor"); + SimpleTest.is(link.dataset.l10nId, "moz-support-link-text", "link 1 Fluent ID is set"); + SimpleTest.ok(link.href.endsWith("/test1"), "link 1 href is set"); + + link = link.nextElementSibling; + SimpleTest.is(link.localName, "a", "link 2 is an anchor"); + SimpleTest.is(link.dataset.l10nId, "moz-support-link-text", "link 2 Fluent ID is set"); + SimpleTest.ok(!link.value, "label is not assigned to value when using supportPage"); + SimpleTest.ok(link.href.endsWith("/test2"), "link 2 href is set"); + + link = link.nextElementSibling; + SimpleTest.is(link.localName, "a", "link 3 is an anchor"); + SimpleTest.is(link.dataset.l10nId, "more-specific-id", "link 3 Fluent ID is the passed l10n-id"); + SimpleTest.ok(link.href.endsWith("/test3"), "link 3 href is set"); + + link = link.nextElementSibling; + SimpleTest.is(link.localName, "a", "link 4 is an anchor"); + SimpleTest.is(link.dataset.l10nId, "modern-fluent-id", "link 4 Fluent ID is the passed l10n-id"); + SimpleTest.ok(!link.value, "label is not assigned to value when using supportPage"); + SimpleTest.ok(link.href.endsWith("/test4"), "link 4 href is set"); + + SimpleTest.is(nb.removeAllNotifications(false), undefined, "removeAllNotifications"); + } + }, + { + async test(nb, unused) { + // add a number of notifications and check that they are added in order + await nb.appendNotification("4", { label: "Four", priority: nb.PRIORITY_INFO_HIGH }, + testtag_notificationbox_buttons); + await nb.appendNotification("7", { label: "Seven", priority: nb.PRIORITY_WARNING_HIGH }, + testtag_notificationbox_buttons); + await nb.appendNotification("2", { label: "Two", priority: nb.PRIORITY_INFO_LOW }); + await nb.appendNotification("8", { label: "Eight", priority: nb.PRIORITY_CRITICAL_LOW }); + await nb.appendNotification("5", { label: "Five", priority: nb.PRIORITY_WARNING_LOW }); + await nb.appendNotification("6", { label: "Six", priority: nb.PRIORITY_WARNING_HIGH }); + await nb.appendNotification("1", { label: "One", priority: nb.PRIORITY_INFO_LOW }); + await nb.appendNotification("9", { label: "Nine", priority: nb.PRIORITY_CRITICAL_MEDIUM }); + let ntf = await nb.appendNotification("10", { label: "Ten", priority: nb.PRIORITY_CRITICAL_HIGH }); + await nb.appendNotification("3", { label: "Three", priority: nb.PRIORITY_INFO_MEDIUM }); + return ntf; + }, + result(nb, ntf) { + let expectedValue = "3"; + ntf = nb.getNotificationWithValue(expectedValue); + is(nb.currentNotification, ntf, "appendNotification last notification"); + is(nb.currentNotification.getAttribute("value"), expectedValue, "appendNotification order"); + return 1; + } + }, + { + // test closing notifications to make sure that the current notification is still set properly + repeat: true, + test(nb, testidx) { + this.repeat = false; + return undefined; + }, + result(nb, arr) { + let notificationOrder = [4, 7, 2, 8, 5, 6, 1, 9, 10, 3]; + let allNotificationValues = [...nb.stack.children].map(n => n.getAttribute("value")); + is(allNotificationValues.length, notificationOrder.length, "Expected number of notifications"); + for (let i = 0; i < allNotificationValues.length; i++) { + is( + allNotificationValues[i], + notificationOrder[i].toString(), + `Notification ${i} matches` + ); + } + return undefined; + } + }, + { + async test(nb, ntf) { + var exh = false; + try { + await nb.appendNotification("no", { label: "no", priority: -1 }); + } catch (ex) { exh = true; } + SimpleTest.is(exh, true, "appendNotification priority too low"); + + exh = false; + try { + await nb.appendNotification("no", { label: "no", priority: 11 }); + } catch (ex) { exh = true; } + SimpleTest.is(exh, true, "appendNotification priority too high"); + + // check that the other priority types work properly + runTimedTests(appendPriorityTests, -1, nb, nb.PRIORITY_WARNING_LOW); + } + } +]; + +var appendPriorityTests = [ + { + async test(nb, priority) { + let ntf = await nb.appendNotification("note", { + label: "Notification", + image: "happy.png", + priority, + }, testtag_notificationbox_buttons); + SimpleTest.is(ntf && ntf.localName == NOTIFICATION_LOCAL_NAME, true, "append notification " + priority); + return [ntf, priority]; + }, + result(nb, obj) { + SimpleTest.is(obj[0].priority, obj[1], "notification.priority " + obj[1]); + return obj[1]; + } + }, + { + test(nb, priority) { + nb.removeCurrentNotification(); + return priority; + }, + async result(nb, priority) { + if (priority == nb.PRIORITY_CRITICAL_HIGH) { + let ntf = await nb.appendNotification("note", { + label: "Notification", + image: "happy.png", + priority: nb.PRIORITY_INFO_LOW, + }, testtag_notificationbox_buttons); + setTimeout(checkPopupTest, 50, nb, ntf); + } + else { + runTimedTests(appendPriorityTests, -1, nb, ++priority); + } + } + }, +]; + +function testtag_notificationbox_State(nb, testid, expecteditem, expectedcount) +{ + SimpleTest.is(nb.currentNotification, expecteditem, testid + " currentNotification"); + SimpleTest.is(nb.allNotifications ? nb.allNotifications.length : "no value", + expectedcount, testid + " allNotifications"); +} + +function testtag_notification_State(nb, ntf, testid, label, value, image, priority) +{ + is(ntf.messageText.textContent.trim(), label, testid + " notification label"); + is(ntf.getAttribute("value"), value, testid + " notification value"); + is(ntf.priority, priority, testid + " notification priority"); + + var type; + switch (priority) { + case nb.PRIORITY_INFO_LOW: + case nb.PRIORITY_INFO_MEDIUM: + case nb.PRIORITY_INFO_HIGH: + type = "info"; + break; + case nb.PRIORITY_WARNING_LOW: + case nb.PRIORITY_WARNING_MEDIUM: + case nb.PRIORITY_WARNING_HIGH: + type = "warning"; + break; + case nb.PRIORITY_CRITICAL_LOW: + case nb.PRIORITY_CRITICAL_MEDIUM: + case nb.PRIORITY_CRITICAL_HIGH: + type = "critical"; + break; + } + + is(ntf.getAttribute("type"), type, testid + " notification type"); + + let icons = { + info: "chrome://global/skin/icons/info-filled.svg", + warning: "chrome://global/skin/icons/warning.svg", + critical: "chrome://global/skin/icons/error.svg", + }; + let icon = icons[type]; + is(ntf.messageImage.src, icon, "notification image is set"); +} + +function checkPopupTest(nb, ntf) +{ + if (nb._animating) { + setTimeout(checkPopupTest, 50, nb, ntf); + } else { + var evt = new Event(""); + ntf.dispatchEvent(evt); + evt.target.buttonInfo = testtag_notificationbox_buttons[0]; + ntf.handleEvent(evt); + } +} + +function checkPopupClosed() +{ + SimpleTest.finish(); +} + +/** + * run one or more tests which perform a test operation, wait for a delay, + * then perform a result operation. + * + * tests - array of objects where each object is : + * { + * test: test function, + * result: result function + * repeat: true to repeat the test + * } + * idx - starting index in tests + * element - element to run tests on + * arg - argument to pass between test functions + * + * If, after executing the result part, the repeat property of the test is + * true, then the test is repeated. If the repeat property is not true, + * continue on to the next test. + * + * The test and result functions take two arguments, the element and the arg. + * The test function may return a value which will passed to the result + * function as its arg. The result function may also return a value which + * will be passed to the next repetition or the next test in the array. + */ +async function runTimedTests(tests, idx, element, arg) +{ + if (idx >= 0 && "result" in tests[idx]) + arg = tests[idx].result(element, arg); + + // if not repeating, move on to the next test + if (idx == -1 || !tests[idx].repeat) + idx++; + + if (idx < tests.length) { + let result = await tests[idx].test(element, arg); + setTimeout(runTimedTestsWait, 50, tests, idx, element, result); + } +} + +function runTimedTestsWait(tests, idx, element, arg) +{ + // use this secret property to check if the animation is still running. If it + // is, then the notification hasn't fully opened or closed yet + if (element._animating) + setTimeout(runTimedTestsWait, 50, tests, idx, element, arg); + else + runTimedTests(tests, idx, element, arg); +} + +setTimeout(() => { + testtag_notificationbox(new MozElements.NotificationBox(e => { + document.getElementById("nb").appendChild(e); + })); +}, 0); +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_panel.xhtml b/toolkit/content/tests/chrome/test_panel.xhtml new file mode 100644 index 0000000000..66cba3f232 --- /dev/null +++ b/toolkit/content/tests/chrome/test_panel.xhtml @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Panel Tests" + onload="runTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.openDialog("window_panel.xhtml", "_blank", "chrome,left=200,top=200,width=200,height=200,noopener", window); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_panel_anchoradjust.xhtml b/toolkit/content/tests/chrome/test_panel_anchoradjust.xhtml new file mode 100644 index 0000000000..4bcf3292e7 --- /dev/null +++ b/toolkit/content/tests/chrome/test_panel_anchoradjust.xhtml @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Test Panel Position When Anchor Changes" + onload="runTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.openDialog("window_panel_anchoradjust.xhtml", "_blank", "chrome,left=200,top=200,width=200,height=200,noopener", window); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_panel_focus.xhtml b/toolkit/content/tests/chrome/test_panel_focus.xhtml new file mode 100644 index 0000000000..87dc6ec140 --- /dev/null +++ b/toolkit/content/tests/chrome/test_panel_focus.xhtml @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Panel Focus Tests" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<script> +// use a chrome window for this test as the focus in content windows can be +// adjusted by the current selection position + +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + // move the mouse so any tooltips that might be open go away, otherwise this + // test can fail on Mac + synthesizeMouse(document.documentElement, 1, 1, { type: "mousemove" }); + + window.openDialog("window_panel_focus.xhtml", "_blank", "chrome,width=600,height=600,noopener", window); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_panel_hover_menu.xhtml b/toolkit/content/tests/chrome/test_panel_hover_menu.xhtml new file mode 100644 index 0000000000..4c5d8589cb --- /dev/null +++ b/toolkit/content/tests/chrome/test_panel_hover_menu.xhtml @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<head> + <meta charset="utf-8" /> + <title><!-- Test with dialog & buttons --></title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <link rel="stylesheet" href="chrome://global/skin"/> + <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script><![CDATA[ + add_task(async function test_panel_submenu_hover() { + let panel = document.getElementById("panel"); + let menu = document.getElementById("menu"); + let menupopup = document.getElementById("menupopup"); + + let panelShown = new Promise(r => panel.addEventListener("popupshown", r, { once: true })); + info("opening panel"); + panel.openPopupAtScreen(window.screenX, window.screenY); + await panelShown; + info("panel shown"); + + info("hovering menu button"); + synthesizeMouseAtCenter(menu, { type: "mousemove" }); + // Wait for at least the submenu delay. + await new Promise(r => setTimeout(r, 1000)); + is(menupopup.state, "closed", "menu shouldn't have opened"); + + info("clicking menu button"); + let menupopupShown = new Promise(r => menupopup.addEventListener("popupshown", r, { once: true })); + synthesizeMouseAtCenter(menu, {}); + await menupopupShown; + + ok(true, "Menupopup was shown on click"); + }); + ]]></script> +</head> +<body> + <xul:panel id="panel"> + <xul:button type="menu" id="menu" label="Open menu"> + <xul:menupopup id="menupopup"> + <xul:menuitem label="foo"/> + </xul:menupopup> + </xul:button> + </xul:panel> +</body> +</html> diff --git a/toolkit/content/tests/chrome/test_panel_open.xhtml b/toolkit/content/tests/chrome/test_panel_open.xhtml new file mode 100644 index 0000000000..27707a4f34 --- /dev/null +++ b/toolkit/content/tests/chrome/test_panel_open.xhtml @@ -0,0 +1,83 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + Test for panel 'open' state on the anchor. + --> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<label id="outerlabel" value="Label"/> +<panel id="panel" type="arrow"> + <label id="innerlabel" value="Inner" context="menupopup"/> + <menupopup id="menupopup"> + <menuitem label="One"/> + </menupopup> +</panel> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +add_task(async () => { + // Open a panel and check the open state. The open state should only be assigned + // for arrow panels and not the context menu. + + let panel = document.getElementById("panel"); + let menupopup = document.getElementById("menupopup"); + let innerlabel = document.getElementById("innerlabel"); + let outerlabel = document.getElementById("outerlabel"); + + // Two iterations are used, one with type="arrow" and the second without. + for (let iter = 0; iter < 2; iter++) { + await new Promise(resolve => { + panel.addEventListener("popupshown", resolve, { once: true }); + panel.openPopup(outerlabel, "after_start"); + }); + + // The open state should only be set for arrow panels. + if (panel.getAttribute("type") == "arrow") { + is(outerlabel.getAttribute("open"), "true", "outer label open state when panel opened"); + } else { + ok(!outerlabel.hasAttribute("open"), "outer label open state when panel opened"); + } + ok(!innerlabel.hasAttribute("open"), "inner label open state when panel opened"); + + await new Promise(resolve => { + menupopup.addEventListener("popupshown", resolve, { once: true }); + synthesizeMouse(innerlabel, 4, 4, { type: "contextmenu", button: 2 }); + }); + + // The open state should only be set for arrow panels. + if (panel.getAttribute("type") == "arrow") { + is(outerlabel.getAttribute("open"), "true", "outer label open state when context menu opened"); + } else { + ok(!outerlabel.hasAttribute("open"), "outer label open state when context menu opened"); + } + ok(!innerlabel.hasAttribute("open"), "inner label open state when context menu opened"); + + await new Promise(resolve => { + menupopup.addEventListener("popuphidden", resolve, { once: true }); + menupopup.hidePopup(); + }); + + await new Promise(resolve => { + panel.addEventListener("popuphidden", resolve, { once: true }); + panel.hidePopup(); + }); + + ok(!outerlabel.hasAttribute("open"), "outer label open state when panel closed"); + ok(!innerlabel.hasAttribute("open"), "inner label open state when panel closed"); + + // Clear the type attribute for the second iteration. + panel.removeAttribute("type"); + } +}); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_panelfrommenu.xhtml b/toolkit/content/tests/chrome/test_panelfrommenu.xhtml new file mode 100644 index 0000000000..4a00dc58ab --- /dev/null +++ b/toolkit/content/tests/chrome/test_panelfrommenu.xhtml @@ -0,0 +1,118 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Open panel from menuitem" + onload="setTimeout(runTests, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<!-- + This test does the following: + 1. Opens the menu, causing the popupshown event to fire, which will call menuOpened. + 2. Keyboard events are fired to cause the first item on the menu to be executed. + 3. The command event handler for the first menuitem opens the panel. + 4. As a menuitem was executed, the menu will roll up, hiding it. + 5. The popuphidden event for the menu calls menuClosed which tests the popup states. + 6. The panelOpened function tests the popup states again and hides the popup. + 7. Once the panel's popuphidden event fires, tests are performed to see if + panels inside buttons and toolbarbuttons work. Each is opened and the closed. + --> + +<menu id="menu" onpopupshown="menuOpened()" onpopuphidden="menuClosed();"> + <menupopup> + <menuitem id="i1" label="One" oncommand="$('panel').openPopup($('menu'), 'after_start');"/> + <menuitem id="i2" label="Two"/> + </menupopup> +</menu> + +<panel id="hiddenpanel" hidden="true"/> + +<panel id="panel" onpopupshown="panelOpened()" + onpopuphidden="$('button').focus(); $('button').open = true"> + <html:input/> +</panel> + +<button id="button" type="menu" label="Button"> + <panel onpopupshown="panelOnButtonOpened(this)" + onpopuphidden="$('tbutton').open = true;"> + <button label="OK" oncommand="this.parentNode.parentNode.open = false"/> + </panel> +</button> + +<toolbarbutton id="tbutton" type="menu" label="Toolbarbutton"> + <panel onpopupshown="panelOnToolbarbuttonOpened(this)" + onpopuphidden="SimpleTest.finish()"> + <html:input/> + </panel> +</toolbarbutton> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function runTests() +{ + is($("hiddenpanel").state, "closed", "hidden popup is closed"); + + var menu = $("menu"); + menu.open = true; +} + +function menuOpened() +{ + synthesizeKey("KEY_ArrowDown"); + synthesizeKey("KEY_Enter"); +} + +function menuClosed() +{ + // the panel will be open at this point, but the popupshown event + // still needs to fire + is($("panel").state, "showing", "panel is open after menu hide"); + is($("menu").menupopup.state, "closed", "menu is closed after menu hide"); +} + +function panelOpened() +{ + is($("panel").state, "open", "panel is open"); + is($("menu").menupopup.state, "closed", "menu is closed"); + $("panel").hidePopup(); +} + +function panelOnButtonOpened(panel) +{ + is(panel.state, 'open', 'button panel is open'); + is(document.activeElement, document.documentElement, "focus blurred on panel from button open"); + synthesizeKey("KEY_ArrowDown"); + is(document.activeElement, document.documentElement, "focus not modified on cursor down from button"); + panel.firstChild.doCommand() +} + +function panelOnToolbarbuttonOpened(panel) +{ + is(panel.state, 'open', 'toolbarbutton panel is open'); + is(document.activeElement, document.documentElement, "focus blurred on panel from toolbarbutton open"); + panel.firstChild.focus(); + synthesizeKey("KEY_ArrowDown"); + is(document.activeElement, panel.firstChild, "focus not modified on cursor down from toolbarbutton"); + panel.parentNode.open = false; +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_anchor.xhtml b/toolkit/content/tests/chrome/test_popup_anchor.xhtml new file mode 100644 index 0000000000..8825e8fd14 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_anchor.xhtml @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Anchor Tests" + onload="setTimeout(runTest, 0);" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.openDialog("window_popup_anchor.xhtml", "_blank", "chrome,width=600,height=600,noopener", window); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_anchoratrect.xhtml b/toolkit/content/tests/chrome/test_popup_anchoratrect.xhtml new file mode 100644 index 0000000000..cc5141fa0b --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_anchoratrect.xhtml @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu Button Popup Tests" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.openDialog("window_popup_anchoratrect.xhtml", "_blank", "chrome,width=200,height=200,noopener", window); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_attribute.xhtml b/toolkit/content/tests/chrome/test_popup_attribute.xhtml new file mode 100644 index 0000000000..bda23a930c --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_attribute.xhtml @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Attribute Tests" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> +SimpleTest.waitForExplicitFinish(); +async function runTest() +{ + // This test exercises non-native menu code. So disable native context menus for this test. + // If we ever get to a point where we don't use any non-native menus on macOS any more, we can + // disable this test on macOS. + await SpecialPowers.pushPrefEnv({ set: [["widget.macos.native-context-menus", false]] }); + + window.open("window_popup_attribute.xhtml", "_blank", "width=600,height=800"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_button.xhtml b/toolkit/content/tests/chrome/test_popup_button.xhtml new file mode 100644 index 0000000000..d6d77a82da --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_button.xhtml @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu Button Popup Tests" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> +SimpleTest.waitForExplicitFinish(); +async function runTest() +{ + // This test exercises non-native menu code. So disable native context menus for this test. + // If we ever get to a point where we don't use any non-native menus on macOS any more, we can + // disable this test on macOS. + await SpecialPowers.pushPrefEnv({ set: [["widget.macos.native-context-menus", false]] }); + + window.open("window_popup_button.xhtml", "_blank", "width=700,height=800"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_coords.xhtml b/toolkit/content/tests/chrome/test_popup_coords.xhtml new file mode 100644 index 0000000000..bc9e042cc5 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_coords.xhtml @@ -0,0 +1,92 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Coordinate Tests" + onload="setTimeout(openThePopup, 0, 'outer');" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<deck style="margin-top: 5px; padding-top: 5px;"> + <label id="outer" style="display: block" popup="outerpopup" value="Popup"/> +</deck> + +<panel id="outerpopup" + onpopupshowing="popupShowingEventOccurred(event);" + onpopupshown="eventOccurred(event); openThePopup('inner')" + onpopuphiding="eventOccurred(event);" + onpopuphidden="eventOccurred(event); SimpleTest.finish();"> + <button id="item1" label="First"/> + <label id="inner" value="Second" popup="innerpopup"/> + <button id="item2" label="Third"/> +</panel> + +<menupopup id="innerpopup" + onpopupshowing="popupShowingEventOccurred(event);" + onpopupshown="eventOccurred(event); event.target.hidePopup();" + onpopuphiding="eventOccurred(event);" + onpopuphidden="eventOccurred(event); document.getElementById('outerpopup').hidePopup();"> + <menuitem id="inner1" label="Inner First"/> + <menuitem id="inner2" label="Inner Second"/> +</menupopup> + +<script> +SimpleTest.waitForExplicitFinish(); + +function openThePopup(id) +{ + if (id == "inner") + document.getElementById("item1").focus(); + + var trigger = document.getElementById(id); + synthesizeMouse(trigger, 4, 5, { }); +} + +function eventOccurred(event) +{ + var testname = event.type + " on " + event.target.id + " "; + ok(MouseEvent.isInstance(event), testname + "is a mouse event"); + is(event.clientX, 0, testname + "clientX"); + is(event.clientY, 0, testname + "clientY"); + is(event.rangeParent, null, testname + "rangeParent"); + is(event.rangeOffset, 0, testname + "rangeOffset"); +} + +function popupShowingEventOccurred(event) +{ + // the popupshowing event should have the event coordinates and + // range position filled in. + var testname = "popupshowing on " + event.target.id + " "; + ok(MouseEvent.isInstance(event), testname + "is a mouse event"); + + var trigger = document.getElementById(event.target.id == "outerpopup" ? "outer" : "inner"); + var rect = trigger.getBoundingClientRect(); + is(event.clientX, Math.round(rect.left + 4), testname + "clientX"); + is(event.clientY, Math.round(rect.top + 5), testname + "clientY"); + // rangeOffset should be just at the trigger element, since they are labels + // they don't have any childrens so the offset should be zero. + is(event.rangeParent, trigger, testname + "rangeParent"); + is(event.rangeOffset, 0, testname + "rangeOffset"); + + var popuprect = event.target.getBoundingClientRect(); + var marginLeft = parseFloat(getComputedStyle(event.target).marginLeft); + var marginTop = parseFloat(getComputedStyle(event.target).marginTop); + is(Math.round(popuprect.left - marginLeft), Math.round(rect.left + 4), "popup left"); + is(Math.round(popuprect.top - marginTop), Math.round(rect.top + 5), "popup top"); + ok(popuprect.width > 0, "popup width"); + ok(popuprect.height > 0, "popup height"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_keys.xhtml b/toolkit/content/tests/chrome/test_popup_keys.xhtml new file mode 100644 index 0000000000..6b8dd31143 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_keys.xhtml @@ -0,0 +1,167 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu ignorekeys Test" + onkeydown="keyDown()" onkeypress="gKeyPressCount++; event.stopPropagation(); event.preventDefault();" + onload="runTests();" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<!-- + This test checks that the ignorekeys attribute can be used on a menu to + disable key navigation. The test is performed twice by opening the menu, + simulating a cursor down key, and closing the popup. When keys are enabled, + the first item on the menu should be highlighted, otherwise the first item + should not be highlighted. + --> + +<menupopup id="popup"> + <menuitem id="i1" label="One"/> + <menuitem id="i2" label="Two"/> + <menuitem id="i3" label="Three"/> + <menuitem id="i4" label="Four"/> +</menupopup> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +let gIgnoreKeys = false; +let gKeyPressCount = 0; +let gLastFirstMenuActiveValue = null; + + +function waitForEvent(target, eventName) { + return new Promise(resolve => { + target.addEventListener(eventName, function eventOccurred(event) { + resolve(); + }, { once: true}); + }); +} + +function runTests() +{ + function promiseFlushingMutationObserver() { + return new Promise(SimpleTest.executeSoon); + } + + (async function() { + const observer = new MutationObserver(checkIfFirstMenuItemActive); + observer.observe($("i1"), { attributes: true }); + + var popup = $("popup"); + is(popup.hasAttribute("ignorekeys"), false, "keys enabled"); + + let popupShownPromise = waitForEvent(popup, "popupshown"); + popup.openPopup(null, "after_start"); + await popupShownPromise; + + let popupHiddenPromise = waitForEvent(popup, "popuphidden"); + info("Synthesizing ArrowDown (no ignorekeys)..."); + synthesizeKey("KEY_ArrowDown"); + await popupHiddenPromise; + + is(gKeyPressCount, 0, "keypresses with ignorekeys='false'"); + + gIgnoreKeys = true; + popup.setAttribute("ignorekeys", "true"); + // clear this first to avoid confusion + observer.disconnect(); + $("i1").removeAttribute("_moz-menuactive") + await promiseFlushingMutationObserver(); + observer.observe($("i1"), { attributes: true }); + + popupShownPromise = waitForEvent(popup, "popupshown"); + popup.openPopup(null, "after_start"); + await popupShownPromise; + + info("Synthesizing ArrowDown (ignorekeys=\"true\")..."); + synthesizeKey("KEY_ArrowDown"); + + await new Promise(resolve => setTimeout(() => resolve(), 1000)); + popupHiddenPromise = waitForEvent(popup, "popuphidden"); + popup.hidePopup(); + await popupHiddenPromise; + + is(gKeyPressCount, 1, "keypresses with ignorekeys='true'"); + + popup.setAttribute("ignorekeys", "shortcuts"); + // clear this first to avoid confusion + observer.disconnect(); + $("i1").removeAttribute("_moz-menuactive") + await promiseFlushingMutationObserver(); + observer.observe($("i1"), { attributes: true }); + + popupShownPromise = waitForEvent(popup, "popupshown"); + popup.openPopup(null, "after_start"); + await popupShownPromise; + + // When ignorekeys="shortcuts", T should be handled but accel+T should propagate. + info("Synthesizing \"t\"..."); + sendString("t"); + is(gKeyPressCount, 1, "keypresses after t pressed with ignorekeys='shortcuts'"); + + info("Synthesizing Accel-T..."); + synthesizeKey("t", { accelKey: true }); + is(gKeyPressCount, 2, "keypresses after accel+t pressed with ignorekeys='shortcuts'"); + + popupHiddenPromise = waitForEvent(popup, "popuphidden"); + popup.hidePopup(); + await popupHiddenPromise; + + observer.disconnect(); + SimpleTest.finish(); + })(); +} + +function checkIfFirstMenuItemActive(aMutationList) { + for (const mutation of aMutationList) { + if (mutation.type != "attributes" || mutation.attributeName != "_moz-menuactive") { + continue; + } + + // the attribute should not be changed when ignorekeys is enabled + if (gIgnoreKeys) { + ok(false, "move key with keys disabled"); + return; + } + + is( + $("popup").hasAttribute("ignorekeys") + ? gLastFirstMenuActiveValue + : $("i1").getAttribute("_moz-menuactive"), + "true", + "move key with keys enabled" + ); + $("popup").hidePopup(); + gLastFirstMenuActiveValue = null; + break; + } +} + +function keyDown() { + // when keys are enabled, the menu should have stopped propagation of the + // event, so a bubbling listener for a keydown event should only occur + // when keys are disabled. + ok(gIgnoreKeys, "key listener fired with keys " + + (gIgnoreKeys ? "disabled" : "enabled")); + gLastFirstMenuActiveValue = $("i1").getAttribute("_moz-menuactive"); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_moveToAnchor.xhtml b/toolkit/content/tests/chrome/test_popup_moveToAnchor.xhtml new file mode 100644 index 0000000000..a7800caaad --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_moveToAnchor.xhtml @@ -0,0 +1,86 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<vbox align="start" style="margin-left: 80px;"> + <button id="button1" label="Button 1" style="margin-top: 80px;"/> + <button id="button2" label="Button 2" style="margin-top: 70px;"/> +</vbox> + +<menupopup id="popup" onpopupshown="popupshown()" onpopuphidden="SimpleTest.finish()"> + <menuitem label="One"/> + <menuitem label="Two"/> +</menupopup> + +<script> +SimpleTest.waitForExplicitFinish(); + +function runTest(id) +{ + $("popup").openPopup($("button1"), "after_start"); +} + +function popupshown() +{ + var popup = $("popup"); + var popupheight = popup.getBoundingClientRect().height; + var button1rect = $("button1").getBoundingClientRect(); + var button2rect = $("button2").getBoundingClientRect(); + var marginLeft = parseFloat(getComputedStyle(popup).marginLeft); + var marginTop = parseFloat(getComputedStyle(popup).marginTop); + + checkCoords(popup, button1rect.left + marginLeft, button1rect.bottom + marginTop, "initial"); + + popup.moveToAnchor($("button1"), "after_start", 0, 8); + checkCoords(popup, button1rect.left + marginLeft, button1rect.bottom + 8 + marginTop, "move anchor top + 8"); + + popup.moveToAnchor($("button1"), "after_start", 6, -10); + checkCoords(popup, button1rect.left + 6 + marginLeft, button1rect.bottom - 10 + marginTop, "move anchor left + 6, top - 10"); + + popup.moveToAnchor($("button1"), "before_start", -2, 0); + checkCoords(popup, button1rect.left - 2 + marginLeft, button1rect.top - popupheight - marginTop, "move anchor before_start"); + + popup.moveToAnchor($("button2"), "before_start"); + checkCoords(popup, button2rect.left + marginLeft, button2rect.top - popupheight - marginTop, "move button2"); + + popup.moveToAnchor($("button1"), "end_before"); + checkCoords(popup, button1rect.right + marginLeft, button1rect.top + marginTop, "move anchor end_before"); + + popup.moveToAnchor($("button2"), "after_start", 5, 4); + checkCoords(popup, button2rect.left + 5 + marginLeft, button2rect.bottom + 4 + marginTop, "move button2 left + 5, top + 4"); + + popup.moveTo($("button1").screenX + 10, $("button1").screenY + 12); + checkCoords(popup, button1rect.left + 10, button1rect.top + 12, "move to button1 screen with offset"); + + popup.moveToAnchor($("button1"), "after_start", 1, 2); + checkCoords(popup, button1rect.left + 1 + marginLeft, button1rect.bottom + 2 + marginTop, "move button2 after screen"); + + popup.hidePopup(); +} + +function checkCoords(popup, expectedx, expectedy, testid) +{ + var rect = popup.getBoundingClientRect(); + is(Math.round(rect.left), Math.round(expectedx), testid + " left"); + is(Math.round(rect.top), Math.round(expectedy), testid + " top"); +} + +SimpleTest.waitForFocus(runTest); + +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_preventdefault.xhtml b/toolkit/content/tests/chrome/test_popup_preventdefault.xhtml new file mode 100644 index 0000000000..916fb7eb86 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_preventdefault.xhtml @@ -0,0 +1,76 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Prevent Default Tests" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<!-- + This tests checks that preventDefault can be called on a popupshowing + event and that preventDefault has no effect for the popuphiding event. + --> + +<script> +SimpleTest.waitForExplicitFinish(); + +var gBlockShowing = true; +var gShownNotAllowed = true; + +function runTest() +{ + document.getElementById("menu").open = true; +} + +function popupShowing(event) +{ + if (gBlockShowing) { + event.preventDefault(); + gBlockShowing = false; + setTimeout(function() { + gShownNotAllowed = false; + document.getElementById("menu").open = true; + }, 3000, true); + } +} + +function popupShown() +{ + ok(!gShownNotAllowed, "popupshowing preventDefault"); + document.getElementById("menu").open = false; +} + +function popupHiding(event) +{ + // since this is a content test, preventDefault should have no effect + event.preventDefault(); +} + +function popupHidden() +{ + ok(true, "popuphiding preventDefault not allowed"); + SimpleTest.finish(); +} +</script> + +<button id="menu" type="menu" label="Menu"> + <menupopup onpopupshowing="popupShowing(event);" + onpopupshown="popupShown();" + onpopuphiding="popupHiding(event);" + onpopuphidden="popupHidden();"> + <menuitem label="Item"/> + </menupopup> +</button> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_preventdefault_chrome.xhtml b/toolkit/content/tests/chrome/test_popup_preventdefault_chrome.xhtml new file mode 100644 index 0000000000..d3e62d05c7 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_preventdefault_chrome.xhtml @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Attribute Tests" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.openDialog("window_popup_preventdefault_chrome.xhtml", "_blank", "chrome,width=600,height=600,noopener", window); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_recreate.xhtml b/toolkit/content/tests/chrome/test_popup_recreate.xhtml new file mode 100644 index 0000000000..b1922ea51b --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_recreate.xhtml @@ -0,0 +1,93 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Recreate Test" + onload="setTimeout(init, 0)" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<!-- + This is a test for bug 388361. + + This test checks that a menulist's popup is properly created and sized when + the popup node is removed and another added in its place. + + --> + +<script> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +var gState = "before"; + +function init() +{ + document.getElementById("menulist").open = true; +} + +function isWithinHalfPixel(a, b) +{ + return Math.abs(a - b) <= 0.5; +} + +const ismac = navigator.platform.indexOf("Mac") == 0; +function inputMargin(el) { + let cs = getComputedStyle(el); + // XXX Internal properties are not exposed in getComputedStyle, so we have to + // use margin and rely on our knowledge of them matching negative margins + // where appropriate. + // return parseFloat(cs.getPropertyValue("-moz-window-input-region-margin")); + return ismac ? 0 : Math.max(-parseFloat(cs.marginLeft), 0); +} + +function recreate() +{ + if (gState == "before") { + var element = document.getElementById("menulist"); + while (element.hasChildNodes()) + element.firstChild.remove(); + element.appendItem("Cat"); + gState = "after"; + document.getElementById("menulist").open = true; + } + else { + SimpleTest.finish(); + } +} + +function checkSize() +{ + var menulist = document.getElementById("menulist"); + var menurect = menulist.getBoundingClientRect(); + var popuprect = menulist.menupopup.getBoundingClientRect(); + + let marginLeft = parseFloat(getComputedStyle(menulist.menupopup).marginLeft); + ok(isWithinHalfPixel(menurect.left + marginLeft, popuprect.left), "left position " + gState); + ok(isWithinHalfPixel(menurect.right + marginLeft + 2 * inputMargin(menulist.menupopup), popuprect.right), "right position " + gState); + ok(Math.round(popuprect.right) - Math.round(popuprect.left) > 0, "height " + gState) + document.getElementById("menulist").open = false; +} +]]> +</script> + +<hbox align="center" pack="center"> + <menulist id="menulist" onpopupshown="checkSize();" onpopuphidden="recreate();" style="width: 200px"> + <menupopup position="after_start"> + <menuitem label="Cat"/> + </menupopup> + </menulist> +</hbox> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_scaled.xhtml b/toolkit/content/tests/chrome/test_popup_scaled.xhtml new file mode 100644 index 0000000000..eda4d7231e --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_scaled.xhtml @@ -0,0 +1,98 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popups in Scaled Content" + onload="setTimeout(runTests, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<!-- This test checks that the position is correct in two cases: + - a popup anchored at an element in a scaled document + - a popup opened at a screen coordinate in a scaled window + --> + +<iframe id="frame" width="60" height="140" + src="data:text/html,<html><body><input size='4' id='one'><input size='4' id='two'></body></html>"/> + +<menupopup id="popup" onpopupshown="shown()" onpopuphidden="nextTest()"> + <menuitem label="One"/> +</menupopup> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +var screenTest = false; +var screenx = -1, screeny = -1; + +SimpleTest.waitForExplicitFinish(); + +function runTests() +{ + setScale($("frame").contentWindow, 2); + + var anchor = $("frame").contentDocument.getElementById("two"); + anchor.getBoundingClientRect(); // flush to update display after scale change + $("popup").openPopup(anchor, "after_start"); +} + +function setScale(win, scale) +{ + SpecialPowers.setFullZoom(win, scale); +} + +function shown() +{ + var popup = $("popup"); + var marginLeft = parseFloat(getComputedStyle(popup).marginLeft); + var marginTop = parseFloat(getComputedStyle(popup).marginTop); + if (screenTest) { + is(popup.screenX - marginLeft, screenx, "screen left position"); + is(popup.screenY - marginTop, screeny, "screen top position"); + } else { + var anchor = $("frame").contentDocument.getElementById("two"); + is(Math.round(anchor.getBoundingClientRect().left * 2), + Math.round(popup.getBoundingClientRect().left - marginLeft), "anchored left position"); + is(Math.round(anchor.getBoundingClientRect().bottom * 2), + Math.round(popup.getBoundingClientRect().top - marginTop), "anchored top position"); + } + + popup.hidePopup(); +} + +function nextTest() +{ + if (screenTest) { + setScale(window, 1); + SimpleTest.finish(); + } + else { + screenTest = true; + var rootElement = document.documentElement; + + setScale(window, 2); + // - the iframe will be at 4×, but out here css pixels are only 2× device pixels + + requestAnimationFrame(() => requestAnimationFrame(() => { + screenx = rootElement.screenX + 20; + screeny = rootElement.screenY + 20; + $("popup").openPopupAtScreen(screenx, screeny); + })); + } +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_tree.xhtml b/toolkit/content/tests/chrome/test_popup_tree.xhtml new file mode 100644 index 0000000000..23a37e339d --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_tree.xhtml @@ -0,0 +1,72 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Tree in Popup Test" + onload="setTimeout(runTests, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<panel id="panel" onpopupshown="treeClick()" onpopuphidden="SimpleTest.finish()"> + <tree id="tree" width="350" rows="5"> + <treecols> + <treecol id="name" label="Name" flex="1"/> + <treecol id="address" label="Street" flex="1"/> + </treecols> + <treechildren id="treechildren"> + <treeitem> + <treerow> + <treecell label="Justin Thyme"/> + <treecell label="800 Bay Street"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Mary Goround"/> + <treecell label="47 University Avenue"/> + </treerow> + </treeitem> + </treechildren> + </tree> +</panel> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function runTests() +{ + $("panel").openPopup(null, "overlap", 2, 2); +} + +function treeClick() +{ + var tree = $("tree"); + is(tree.currentIndex, -1, "selectedIndex before click"); + synthesizeMouseExpectEvent($("treechildren"), 2, 2, { }, $("treechildren"), "click", ""); + is(tree.currentIndex, 0, "selectedIndex after click"); + + var rect = tree.getCoordsForCellItem(1, tree.columns.address, ""); + synthesizeMouseExpectEvent($("treechildren"), rect.x, rect.y + 2, + { }, $("treechildren"), "click", ""); + is(tree.currentIndex, 1, "selectedIndex after second click " + rect.x + "," + rect.y); + + $("panel").hidePopup(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popuphidden.xhtml b/toolkit/content/tests/chrome/test_popuphidden.xhtml new file mode 100644 index 0000000000..aa4b5aff1e --- /dev/null +++ b/toolkit/content/tests/chrome/test_popuphidden.xhtml @@ -0,0 +1,86 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Hidden Popup Test" + onload="setTimeout(runTests, 0, $('popup'));" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<menupopup id="popup" hidden="true" onpopupshown="ok(true, 'popupshown'); this.hidePopup()" + onpopuphidden="$('popup-hideonshow').openPopup(null, 'after_start')"> + <menuitem id="i1" label="One"/> + <menuitem id="i2" label="Two"/> +</menupopup> + +<menupopup id="popup-hideonshow" onpopupshowing="hidePopupWhileShowing(this)" + onpopupshown="ok(false, 'popupshown when hidden')"> + <menuitem id="i1" label="One"/> + <menuitem id="i2" label="Two"/> +</menupopup> + +<button id="button" type="menu" label="Menu"> + <menupopup id="popupinbutton" hidden="true" + onpopupshown="ok(true, 'popupshown'); ok($('button').open, 'open'); this.hidden = true;"> + <menuitem id="i1" label="One"/> + <menuitem id="i2" label="Two"/> + </menupopup> +</button> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function runTests(popup) +{ + const observer = new MutationObserver(checkEndTest); + observer.observe($("button"), { attributes: true }); + popup.hidden = false; + popup.openPopup(null, "after_start"); +} + +function hidePopupWhileShowing(popup) +{ + popup.hidden = true; + popup.clientWidth; // flush layout + is(popup.state, 'closed', 'popupshowing hidden'); + SimpleTest.executeSoon(() => runTests($('popupinbutton'))); +} + +let finished = false; +function checkEndTest(aMutationList, aObserver) +{ + if (finished) { + return; // XXX I don't know why this is necessary. + } + const button = $("button"); + for (const mutation of aMutationList) { + if (mutation.attributeName != "open" || button.hasAttribute("open")) { + continue; + } + + ok($("popupinbutton").hidden, "popup hidden"); + is($("popupinbutton").state, "closed", "popup state"); + ok(!button.open, "not open after hidden"); + aObserver.disconnect(); + SimpleTest.finish(); + finished = true; + return; + } +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popupincontent.xhtml b/toolkit/content/tests/chrome/test_popupincontent.xhtml new file mode 100644 index 0000000000..9721566372 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popupincontent.xhtml @@ -0,0 +1,137 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup in Content Positioning Tests" + onload="setTimeout(nextTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<!-- + This test checks that popups in content areas don't extend past the content area. + --> + +<hbox> + <spacer width="100"/> + <menu id="menu" label="Menu"> + <menupopup style="margin:10px;-moz-window-input-region-margin:0;" id="popup" onpopupshown="popupShown()" onpopuphidden="nextTest()"> + <menuitem label="One"/> + <menuitem label="Two"/> + <menuitem label="Three"/> + <menuitem label="A final longer label that is actually quite long. Very long indeed."/> + </menupopup> + </menu> +</hbox> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var step = ""; +var originalHeight = -1; + +function nextTest() +{ + // there are five tests here: + // openPopupAtScreen - checks that opening a popup using openPopupAtScreen + // constrains the popup to the content area + // left and top - check with the left and top attributes set + // open near bottom - open the menu near the bottom of the window + // large menu - try with a menu that is very large and should be scaled + // shorter menu again - try with a menu that is shorter again. It should have + // the same height as the 'left and top' test + var popup = $("popup"); + var menu = $("menu"); + switch (step) { + case "": + step = "openPopupAtScreen"; + popup.openPopupAtScreen(1000, 1200); + break; + case "openPopupAtScreen": + step = "left and top"; + popup.setAttribute("left", "800"); + popup.setAttribute("top", "2900"); + synthesizeMouse(menu, 2, 2, { }); + break; + case "left and top": + step = "open near bottom"; + // request that the menu be opened with a target point near the bottom of the window, + // so that the menu's top margin will push it completely outside the window. + popup.setAttribute("top", document.documentElement.screenY + window.innerHeight - 5); + synthesizeMouse(menu, 2, 2, { }); + break; + case "open near bottom": + step = "large menu"; + popup.removeAttribute("left"); + popup.removeAttribute("top"); + for (let i = 0; i < 80; i++) + menu.appendItem("Test", ""); + synthesizeMouse(menu, 2, 2, { }); + break; + case "large menu": + step = "shorter menu again"; + for (let i = 0; i < 80; i++) + popup.lastChild.remove(); + synthesizeMouse(menu, 2, 2, { }); + break; + case "shorter menu again": + SimpleTest.finish(); + break; + } +} + +async function popupShown() +{ + // Popup may have wrong initial size in non e10s mode tests, because + // layout is not yet ready for popup content lazy population on + // popupshowing event. + await new Promise(r => + requestAnimationFrame(() => requestAnimationFrame(r)) + ); + + var windowrect = document.documentElement.getBoundingClientRect(); + var popuprect = $("popup").getBoundingClientRect(); + + // subtract one off the edge due to a rounding issue + ok(popuprect.left >= windowrect.left, step + " left"); + ok(popuprect.right - 1 <= windowrect.right, step + " right"); + + if (step == "left and top") { + originalHeight = popuprect.bottom - popuprect.top; + } + else if (step == "open near bottom") { + // check that the menu flipped up so it's above our requested point + ok(popuprect.bottom - 1 <= windowrect.bottom - 5, step + " bottom"); + } + else if (step == "large menu") { + // add 10 to account for the margin + is(popuprect.top, $("menu").getBoundingClientRect().bottom + 10, step + " top"); + ok(popuprect.bottom == windowrect.bottom || + popuprect.bottom - 1 == windowrect.bottom, step + " bottom"); + } + else { + ok(popuprect.top >= windowrect.top, step + " top"); + ok(popuprect.bottom - 1 <= windowrect.bottom, step + " bottom"); + if (step == "shorter menu again") + is(popuprect.bottom - popuprect.top, originalHeight, step + " height shortened"); + } + + $("menu").open = false; +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popupremoving.xhtml b/toolkit/content/tests/chrome/test_popupremoving.xhtml new file mode 100644 index 0000000000..babd62c18b --- /dev/null +++ b/toolkit/content/tests/chrome/test_popupremoving.xhtml @@ -0,0 +1,180 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Removing Tests" + onload="setTimeout(nextTest, 0)" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<!-- + This test checks that popup elements can be removed in various ways without + crashing. It tests two situations, one with menus that are 'separate', and + one with menus that are 'nested'. In each case, there are four levels of menu. + + The nextTest function starts the process by opening the first menu. A set of + popupshown event listeners are used to open the next menu until all four are + showing. This last one calls removePopup to remove the menu node from the + tree. This should hide the popups as they are no longer in a document. + + A mutation listener is triggered when the fourth menu closes by having its + open attribute cleared. This listener hides the third popup which causes + its frame to be removed. Naturally, we want to ensure that this doesn't + crash when the third menu is removed. + --> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<hbox> + +<menu id="nestedmenu1" label="1"> + <menupopup id="nestedpopup1" onpopupshown="if (event.target == this) this.firstChild.open = true"> + <menu id="nestedmenu2" label="2"> + <menupopup id="nestedpopup2" onpopupshown="if (event.target == this) this.firstChild.open = true"> + <menu id="nestedmenu3" label="3"> + <menupopup id="nestedpopup3" onpopupshown="if (event.target == this) this.firstChild.open = true"> + <menu id="nestedmenu4" label="4"> + <menupopup id="nestedpopup4"> + <menuitem label="Nested 1"/> + <menuitem label="Nested 2"/> + <menuitem label="Nested 3"/> + </menupopup> + </menu> + </menupopup> + </menu> + </menupopup> + </menu> + </menupopup> +</menu> + +<menu id="separatemenu1" label="1"> + <menupopup id="separatepopup1" onpopupshown="$('separatemenu2').open = true"> + <menuitem label="L1 One"/> + <menuitem label="L1 Two"/> + <menuitem label="L1 Three"/> + </menupopup> +</menu> + +<menu id="separatemenu2" label="2"> + <menupopup id="separatepopup2" onpopupshown="$('separatemenu3').open = true" + onpopuphidden="popup2Hidden()"> + <menuitem label="L2 One"/> + <menuitem label="L2 Two"/> + <menuitem label="L2 Three"/> + </menupopup> +</menu> + +<menu id="separatemenu3" label="3" onpopupshown="$('separatemenu4').open = true"> + <menupopup id="separatepopup3"> + <menuitem label="L3 One"/> + <menuitem label="L3 Two"/> + <menuitem label="L3 Three"/> + </menupopup> +</menu> + +<menu id="separatemenu4" label="4" onpopuphidden="$('separatemenu2').open = false"> + <menupopup id="separatepopup3"> + <menuitem label="L4 One"/> + <menuitem label="L4 Two"/> + <menuitem label="L4 Three"/> + </menupopup> +</menu> + +</hbox> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +let gKey = ""; +let gTriggerMutation = null; +let gChangeMutation = null; +let gResolveRemovePopups = null; +let gResolvePopup2Hidden = null; + +function nextTest() +{ + let promiseRemovePopups, promisePopup2Hidden; + if (gKey == "") { + gKey = "separate"; + $("separatemenu4").addEventListener("popupshown", removePopups); + promiseRemovePopups = new Promise(resolve => gResolveRemovePopups = resolve); + promisePopup2Hidden = new Promise(resolve => gResolvePopup2Hidden = resolve); + } + else if (gKey == "separate") { + gKey = "nested"; + $("nestedmenu4").addEventListener("popupshown", removePopups); + promiseRemovePopups = new Promise(resolve => gResolveRemovePopups = resolve); + gResolvePopup2Hidden = null; + } + else { + SimpleTest.finish(); + return; + } + + SimpleTest.executeSoon(async () => { + $(gKey + "menu1").open = true; + await Promise.all([promiseRemovePopups, promisePopup2Hidden]); + nextTest(); + }); +} + +function modified(aMutationList, aObserver) { + // use this mutation listener to hide the third popup, destroying its frame. + // It gets triggered when the open attribute is cleared on the fourth menu. + for (const mutation of aMutationList) { + if (mutation.attributeName != "open") { + continue; + } + gChangeMutation.hidden = true; + // force a layout flush + document.documentElement.clientWidth; + gChangeMutation = null; + aObserver.disconnect(); + break; + } +} + +async function removePopups() +{ + var menu2 = $(gKey + "menu2"); + var menu3 = $(gKey + "menu3"); + is(menu2.getAttribute("open"), "true", gKey + " menu 2 open before"); + is(menu3.getAttribute("open"), "true", gKey + " menu 3 open before"); + + const observer = new MutationObserver(modified); + observer.observe(menu3, { attributes: true }); + gChangeMutation = $(gKey + "menu4"); + var menu = $(gKey + "menu1"); + menu.remove(); + const key = gKey; + await new Promise (SimpleTest.executeSoon); + if (key == "nested") { + // the 'separate' test checks this during the popup2 hidden event handler + is(menu2.hasAttribute("open"), false, gKey + " menu 2 open after"); + is(menu3.hasAttribute("open"), false, gKey + " menu 3 open after"); + } + gResolveRemovePopups(); +} + +function popup2Hidden() { + is($(gKey + "menu2").hasAttribute("open"), false, gKey + " menu 2 open after"); + if (gResolvePopup2Hidden) { + gResolvePopup2Hidden(); + } +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_position.xhtml b/toolkit/content/tests/chrome/test_position.xhtml new file mode 100644 index 0000000000..8c9a0e1b61 --- /dev/null +++ b/toolkit/content/tests/chrome/test_position.xhtml @@ -0,0 +1,130 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for positioning + --> +<window title="position" width="500" height="600" + onload="setTimeout(runTest, 0);" + style="margin: 0; border: 0; padding; 0;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + +<hbox id="box1"> + <button label="0" style="width: 100px; height: 40px; margin: 3px;"/> +</hbox> +<scrollbox id="box2" orient="vertical" align="start" + style="height: 50px; overflow: hidden; margin-left: 2px; padding: 1px;"> + <deck> + <scrollbox id="box3" orient="vertical" align="start" + style="height: 100px; overflow: scroll; margin: 1px; padding: 0;"> + <vbox id="innerscroll" style="width: 200px" align="start"> + <button id="button1" label="1" style="width: 90px; max-width: 100px; min-width: 80px; min-height: 25px; height: 35px; max-height: 50px; margin: 5px; border: 4px; padding: 7px; appearance: none;"/> + <menu id="menu"> + <menupopup id="popup" style="appearance: none; margin:0; border: 0; padding: 0;" + onpopupshown="menuOpened()" + onpopuphidden="if (event.target == this) SimpleTest.finish()"> + <menuitem label="One"/> + <menu id="submenu" label="Three"> + <menupopup id="subpopup" style="appearance: none; margin:0; border: 0; padding: 0;" + onpopupshown="submenuOpened()"> + <menuitem label="Four"/> + </menupopup> + </menu> + </menupopup> + </menu> + <button label="2" style="max-width: 100px; max-height: 20px; margin: 5px;"/> + <button label="3" style="max-width: 100px; max-height: 20px; margin: 5px;"/> + <button label="4" style="max-width: 100px; max-height: 20px; margin: 5px;"/> + </vbox> + <box style="height: 200px"/> + </scrollbox> + </deck> +</scrollbox> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function runTest() +{ + var winwidth = document.documentElement.getBoundingClientRect().width; + + var box1 = $("box1"); + checkPosition("box1", box1, 0, 0, winwidth, 46); + + var box2 = $("box2"); + checkPosition("box2", box2, 2, 46, winwidth, 96); + + // height is height(box1) = 46 + margin-top(box3) = 1 + margin-top(button1) = 5 + var button1 = $("button1"); + checkPosition("button1", button1, 9, 53, 99, 88); + + box2.scrollTo(7, 16); + + // clientRect height is offset from root so is 16 pixels vertically less + checkPosition("button1 scrolled", button1, 9, 37, 99, 72); + + var box3 = $("box3"); + box3.scrollTo(1, 2); + + checkPosition("button1 scrolled", button1, 9, 35, 99, 70); + + $("menu").open = true; +} + +function menuOpened() +{ + $("submenu").open = true; +} + +function submenuOpened() +{ + var menu = $("menu"); + var menuleft = Math.round(menu.getBoundingClientRect().left); + var menubottom = Math.round(menu.getBoundingClientRect().bottom); + + var submenu = $("submenu"); + var submenutop = Math.round(submenu.getBoundingClientRect().top); + var submenuright = Math.round(submenu.getBoundingClientRect().right); + + checkPosition("popup", $("popup"), menuleft, menubottom, -1, -1); + checkPosition("subpopup", $("subpopup"), submenuright, submenutop, -1, -1); + + menu.open = false; +} + +function checkPosition(testid, elem, cleft, ctop, cright, cbottom) +{ + // -1 for right or bottom means that the exact size should not be + // checked, just ensure it is larger then the left or top position + var rect = elem.getBoundingClientRect(); + is(Math.round(rect.left), cleft, testid + " client rect left"); + if (testid != "popup") + is(Math.round(rect.top), ctop, testid + " client rect top"); + if (cright >= 0) + is(Math.round(rect.right), cright, testid + " client rect right"); + else + ok(rect.right - rect.left > 20, testid + " client rect right"); + if (cbottom >= 0) + is(Math.round(rect.bottom), cbottom, testid + " client rect bottom"); + else + ok(rect.bottom - rect.top > 15, testid + " client rect bottom"); +} + +]]> + +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_preferences.xhtml b/toolkit/content/tests/chrome/test_preferences.xhtml new file mode 100644 index 0000000000..0b3ddf88be --- /dev/null +++ b/toolkit/content/tests/chrome/test_preferences.xhtml @@ -0,0 +1,525 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Preferences Window Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="RunTest();"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script type="application/javascript"> + <![CDATA[ + SimpleTest.waitForExplicitFinish(); + + const kPref = SpecialPowers.Services.prefs; + + // preference values, set 1 + const kPrefValueSet1 = { + int: 23, + bool: true, + string: "rheeet!", + unichar: "äöüßÄÖÜ", + wstring_data: "日本語", + file_data: "/", + + wstring: Cc["@mozilla.org/pref-localizedstring;1"].createInstance( + Ci.nsIPrefLocalizedString + ), + file: Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile), + }; + kPrefValueSet1.wstring.data = kPrefValueSet1.wstring_data; + SafeFileInit(kPrefValueSet1.file, kPrefValueSet1.file_data); + + // preference values, set 2 + const kPrefValueSet2 = { + int: 42, + bool: false, + string: "Mozilla", + unichar: "áôùšŽ", + wstring_data: "헤드라인A", + file_data: "/home", + + wstring: Cc["@mozilla.org/pref-localizedstring;1"].createInstance( + Ci.nsIPrefLocalizedString + ), + file: Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile), + }; + kPrefValueSet2.wstring.data = kPrefValueSet2.wstring_data; + SafeFileInit(kPrefValueSet2.file, kPrefValueSet2.file_data); + + function SafeFileInit(aFile, aPath) { + // set file path without dying for exceptions + try { + aFile.initWithPath(aPath); + } catch {} + } + + function CreateEmptyPrefValueSet() { + var result = { + int: undefined, + bool: undefined, + string: undefined, + unichar: undefined, + wstring_data: undefined, + file_data: undefined, + wstring: undefined, + file: undefined, + }; + return result; + } + + function WritePrefsToSystem(aPrefValueSet) { + // write preference data via XPCOM + kPref.setIntPref("tests.static_preference_int", aPrefValueSet.int); + kPref.setBoolPref("tests.static_preference_bool", aPrefValueSet.bool); + kPref.setCharPref("tests.static_preference_string", aPrefValueSet.string); + kPref.setStringPref("tests.static_preference_unichar", aPrefValueSet.unichar); + kPref.setComplexValue( + "tests.static_preference_wstring", + Ci.nsIPrefLocalizedString, + aPrefValueSet.wstring + ); + kPref.setComplexValue( + "tests.static_preference_file", + Ci.nsIFile, + aPrefValueSet.file + ); + } + + function ReadPrefsFromSystem() { + // read preference data via XPCOM + var result = CreateEmptyPrefValueSet(); + // eslint-disable-next-line mozilla/use-default-preference-values + try { + result.int = kPref.getIntPref("tests.static_preference_int"); + } catch {} + // eslint-disable-next-line mozilla/use-default-preference-values + try { + result.bool = kPref.getBoolPref("tests.static_preference_bool"); + } catch {} + // eslint-disable-next-line mozilla/use-default-preference-values + try { + result.string = kPref.getCharPref("tests.static_preference_string"); + } catch {} + try { + result.unichar = kPref.getStringPref("tests.static_preference_unichar"); + } catch {} + try { + result.wstring = kPref.getComplexValue( + "tests.static_preference_wstring", + Ci.nsIPrefLocalizedString + ); + result.wstring_data = result.wstring.data; + } catch {} + try { + result.file = kPref.getComplexValue( + "tests.static_preference_file", + Ci.nsIFile + ); + result.file_data = result.file.data; + } catch {} + return result; + } + + function GetXULElement(aPrefWindow, aID) { + return aPrefWindow.document.getElementById(aID); + } + + function GetPreference(aPrefWindow, aID) { + return aPrefWindow.Preferences.get(aID); + } + + function WritePrefsToPreferences(aPrefWindow, aPrefValueSet) { + // write preference data into Preference instances + GetPreference(aPrefWindow, "tests.static_preference_int").value = + aPrefValueSet.int; + GetPreference(aPrefWindow, "tests.static_preference_bool").value = + aPrefValueSet.bool; + GetPreference(aPrefWindow, "tests.static_preference_string").value = + aPrefValueSet.string; + GetPreference(aPrefWindow, "tests.static_preference_unichar").value = + aPrefValueSet.unichar; + GetPreference(aPrefWindow, "tests.static_preference_wstring").value = + aPrefValueSet.wstring_data; + GetPreference(aPrefWindow, "tests.static_preference_file").value = + aPrefValueSet.file_data; + } + + function ReadPrefsFromPreferences(aPrefWindow) { + // read preference data from Preference instances + var result = { + int: GetPreference(aPrefWindow, "tests.static_preference_int").value, + bool: GetPreference(aPrefWindow, "tests.static_preference_bool").value, + string: GetPreference(aPrefWindow, "tests.static_preference_string").value, + unichar: GetPreference(aPrefWindow, "tests.static_preference_unichar") + .value, + wstring_data: GetPreference(aPrefWindow, "tests.static_preference_wstring") + .value, + file_data: GetPreference(aPrefWindow, "tests.static_preference_file").value, + wstring: Cc["@mozilla.org/pref-localizedstring;1"].createInstance( + Ci.nsIPrefLocalizedString + ), + file: Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile), + }; + result.wstring.data = result.wstring_data; + SafeFileInit(result.file, result.file_data); + return result; + } + + function WritePrefsToUI(aPrefWindow, aPrefValueSet) { + // write preference data into UI elements + GetXULElement(aPrefWindow, "static_element_int").value = aPrefValueSet.int; + GetXULElement(aPrefWindow, "static_element_bool").checked = + aPrefValueSet.bool; + GetXULElement(aPrefWindow, "static_element_string").value = + aPrefValueSet.string; + GetXULElement(aPrefWindow, "static_element_unichar").value = + aPrefValueSet.unichar; + GetXULElement(aPrefWindow, "static_element_wstring").value = + aPrefValueSet.wstring_data; + GetXULElement(aPrefWindow, "static_element_file").value = + aPrefValueSet.file_data; + } + + function ReadPrefsFromUI(aPrefWindow) { + // read preference data from Preference instances + var result = { + int: GetXULElement(aPrefWindow, "static_element_int").value, + bool: GetXULElement(aPrefWindow, "static_element_bool").checked, + string: GetXULElement(aPrefWindow, "static_element_string").value, + unichar: GetXULElement(aPrefWindow, "static_element_unichar").value, + wstring_data: GetXULElement(aPrefWindow, "static_element_wstring").value, + file_data: GetXULElement(aPrefWindow, "static_element_file").value, + wstring: Cc["@mozilla.org/pref-localizedstring;1"].createInstance( + Ci.nsIPrefLocalizedString + ), + file: Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile), + }; + result.wstring.data = result.wstring_data; + SafeFileInit(result.file, result.file_data); + return result; + } + + function RunInstantPrefTest(aPrefWindow) { + // remark: there's currently no UI element binding for files + + // were all Preference instances correctly initialized? + var expected = kPrefValueSet1; + var found = ReadPrefsFromPreferences(aPrefWindow); + ok(found.int === expected.int, "instant pref init int"); + ok(found.bool === expected.bool, "instant pref init bool"); + ok(found.string === expected.string, "instant pref init string"); + ok(found.unichar === expected.unichar, "instant pref init unichar"); + ok(found.wstring_data === expected.wstring_data, "instant pref init wstring"); + todo(found.file_data === expected.file_data, "instant pref init file"); + + // were all elements correctly initialized? (loose check) + found = ReadPrefsFromUI(aPrefWindow); + is(found.int, "" + expected.int, "instant element init int"); + is(found.bool, expected.bool, "instant element init bool"); + is(found.string, expected.string, "instant element init string"); + is(found.unichar, expected.unichar, "instant element init unichar"); + is( + found.wstring_data, expected.wstring_data, + "instant element init wstring" + ); + todo_is(found.file_data == expected.file_data, "instant element init file"); + + // do some changes in the UI + expected = kPrefValueSet2; + WritePrefsToUI(aPrefWindow, expected); + + // UI changes should get passed to the Preference instances, + // but currently they aren't if the changes are made programmatically + // (the handlers preference.change/prefpane.input and prefpane.change + // are called for manual changes, though). + found = ReadPrefsFromPreferences(aPrefWindow); + todo(found.int === expected.int, "instant change pref int"); + todo(found.bool === expected.bool, "instant change pref bool"); + todo(found.string === expected.string, "instant change pref string"); + todo(found.unichar === expected.unichar, "instant change pref unichar"); + todo( + found.wstring_data === expected.wstring_data, + "instant change pref wstring" + ); + todo(found.file_data === expected.file_data, "instant change pref file"); + + // and these changes should get passed to the system instantly + // (which obviously can't pass with the above failing) + found = ReadPrefsFromSystem(); + todo(found.int === expected.int, "instant change element int"); + todo(found.bool === expected.bool, "instant change element bool"); + todo(found.string === expected.string, "instant change element string"); + todo(found.unichar === expected.unichar, "instant change element unichar"); + todo( + found.wstring_data === expected.wstring_data, + "instant change element wstring" + ); + todo(found.file_data === expected.file_data, "instant change element file"); + + // try resetting the prefs to default values (which should be empty here) + GetPreference(aPrefWindow, "tests.static_preference_int").reset(); + GetPreference(aPrefWindow, "tests.static_preference_bool").reset(); + GetPreference(aPrefWindow, "tests.static_preference_string").reset(); + GetPreference(aPrefWindow, "tests.static_preference_unichar").reset(); + GetPreference(aPrefWindow, "tests.static_preference_wstring").reset(); + GetPreference(aPrefWindow, "tests.static_preference_file").reset(); + + // check system + expected = CreateEmptyPrefValueSet(); + found = ReadPrefsFromSystem(); + ok(found.int === expected.int, "instant reset system int"); + ok(found.bool === expected.bool, "instant reset system bool"); + ok(found.string === expected.string, "instant reset system string"); + ok(found.unichar === expected.unichar, "instant reset system unichar"); + ok( + found.wstring_data === expected.wstring_data, + "instant reset system wstring" + ); + ok(found.file_data === expected.file_data, "instant reset system file"); + + // check UI + expected = { + // alas, we don't have XUL elements with typeof(value) == int :( + // int: 0, + int: "", + bool: false, + string: "", + unichar: "", + wstring_data: "", + file_data: "", + wstring: {}, + file: {}, + }; + found = ReadPrefsFromUI(aPrefWindow); + ok(found.int === expected.int, "instant reset element int"); + ok(found.bool === expected.bool, "instant reset element bool"); + ok(found.string === expected.string, "instant reset element string"); + ok(found.unichar === expected.unichar, "instant reset element unichar"); + ok( + found.wstring_data === expected.wstring_data, + "instant reset element wstring" + ); + ok(found.file_data === expected.file_data, "instant reset element file"); + + // check hasUserValue + ok( + !GetPreference(aPrefWindow, "tests.static_preference_int").hasUserValue, + "instant reset hasUserValue int" + ); + ok( + !GetPreference(aPrefWindow, "tests.static_preference_bool").hasUserValue, + "instant reset hasUserValue bool" + ); + ok( + !GetPreference(aPrefWindow, "tests.static_preference_string").hasUserValue, + "instant reset hasUserValue string" + ); + ok( + !GetPreference(aPrefWindow, "tests.static_preference_unichar").hasUserValue, + "instant reset hasUserValue unichar" + ); + ok( + !GetPreference(aPrefWindow, "tests.static_preference_wstring").hasUserValue, + "instant reset hasUserValue wstring" + ); + ok( + !GetPreference(aPrefWindow, "tests.static_preference_file").hasUserValue, + "instant reset hasUserValue file" + ); + } + + function RunCheckCommandRedirect(aPrefWindow) { + ok( + GetPreference(aPrefWindow, "tests.static_preference_bool").value, + "redirected command bool" + ); + GetXULElement(aPrefWindow, "checkbox").click(); + ok( + !GetPreference(aPrefWindow, "tests.static_preference_bool").value, + "redirected command bool" + ); + GetXULElement(aPrefWindow, "checkbox").click(); + ok( + GetPreference(aPrefWindow, "tests.static_preference_bool").value, + "redirected command bool" + ); + } + + function RunCheckDisabled(aPrefWindow) { + ok( + !GetXULElement(aPrefWindow, "disabled_checkbox").disabled, + "Checkbox should be enabled" + ); + GetPreference( + aPrefWindow, + "tests.disabled_preference_bool" + ).updateControlDisabledState(true); + ok( + GetXULElement(aPrefWindow, "disabled_checkbox").disabled, + "Checkbox should be disabled" + ); + GetPreference( + aPrefWindow, + "tests.locked_preference_bool" + ).updateControlDisabledState(false); + ok( + GetXULElement(aPrefWindow, "locked_checkbox").disabled, + "Locked checkbox should stay disabled" + ); + SimpleTest.finish(); + } + + function RunResetPrefTest(aPrefWindow) { + // try resetting the prefs to default values + GetPreference(aPrefWindow, "tests.static_preference_int").reset(); + GetPreference(aPrefWindow, "tests.static_preference_bool").reset(); + GetPreference(aPrefWindow, "tests.static_preference_string").reset(); + GetPreference(aPrefWindow, "tests.static_preference_unichar").reset(); + GetPreference(aPrefWindow, "tests.static_preference_wstring").reset(); + GetPreference(aPrefWindow, "tests.static_preference_file").reset(); + } + + function RunTestApplyPref() { + // Test in parent window. + WritePrefsToSystem(kPrefValueSet1); + window.browsingContext.topChromeWindow.openDialog( + "window_preferences.xhtml", + "", + "modal", + RunInstantPrefTest, + false + ); + + // Test deferred reset in child window. + WritePrefsToSystem(kPrefValueSet1); + window.browsingContext.topChromeWindow.openDialog( + "window_preferences2.xhtml", + "", + "modal", + RunResetPrefTest, + false + ); + let expected = kPrefValueSet1; + let found = ReadPrefsFromSystem(); + is(found.int, expected.int, "instant reset deferred int"); + is(found.bool, expected.bool, "instant reset deferred bool"); + is(found.string, expected.string, "instant reset deferred string"); + is(found.unichar, expected.unichar, "instant reset deferred unichar"); + is( + found.wstring_data, expected.wstring_data, + "instant reset deferred wstring" + ); + todo_is(found.file_data, expected.file_data, "instant reset deferred file"); + + // Test cancel in child window. + WritePrefsToSystem(kPrefValueSet1); + window.browsingContext.topChromeWindow.openDialog( + "window_preferences2.xhtml", + "", + "modal", + aPrefWindow => WritePrefsToPreferences(aPrefWindow, kPrefValueSet2), + false + ); + expected = kPrefValueSet1; + found = ReadPrefsFromSystem(); + ok(found.int === expected.int, "non-instant cancel system int"); + ok(found.bool === expected.bool, "non-instant cancel system bool"); + ok(found.string === expected.string, "non-instant cancel system string"); + ok(found.unichar === expected.unichar, "non-instant cancel system unichar"); + ok( + found.wstring_data === expected.wstring_data, + "non-instant cancel system wstring" + ); + todo( + found.file_data === expected.file_data, + "non-instant cancel system file" + ); + + // Test accept in child window. + WritePrefsToSystem(kPrefValueSet1); + window.browsingContext.topChromeWindow.openDialog( + "window_preferences2.xhtml", + "", + "modal", + aPrefWindow => WritePrefsToPreferences(aPrefWindow, kPrefValueSet2), + true + ); + expected = kPrefValueSet2; + found = ReadPrefsFromSystem(); + ok(found.int === expected.int, "non-instant accept system int"); + ok(found.bool === expected.bool, "non-instant accept system bool"); + ok(found.string === expected.string, "non-instant accept system string"); + ok(found.unichar === expected.unichar, "non-instant accept system unichar"); + ok( + found.wstring_data === expected.wstring_data, + "non-instant accept system wstring" + ); + todo( + found.file_data === expected.file_data, + "non-instant accept system file" + ); + + // Test deferred reset in child window. + WritePrefsToSystem(kPrefValueSet1); + window.browsingContext.topChromeWindow.openDialog( + "window_preferences2.xhtml", + "", + "modal", + RunResetPrefTest, + true + ); + expected = CreateEmptyPrefValueSet(); + found = ReadPrefsFromSystem(); + ok(found.int === expected.int, "non-instant reset deferred int"); + ok(found.bool === expected.bool, "non-instant reset deferred bool"); + ok(found.string === expected.string, "non-instant reset deferred string"); + ok(found.unichar === expected.unichar, "non-instant reset deferred unichar"); + ok( + found.wstring_data === expected.wstring_data, + "non-instant reset deferred wstring" + ); + ok(found.file_data === expected.file_data, "non-instant reset deferred file"); + } + + function RunTestCommandRedirect() { + WritePrefsToSystem(kPrefValueSet1); + window.browsingContext.topChromeWindow.openDialog( + "window_preferences_commandretarget.xhtml", + "", + "modal", + RunCheckCommandRedirect, + true + ); + } + + function RunTestDisabled() { + // Because this pref is on the default branch and locked, we need to set it before opening the dialog. + const defaultBranch = kPref.getDefaultBranch(""); + defaultBranch.setBoolPref("tests.locked_preference_bool", true); + defaultBranch.lockPref("tests.locked_preference_bool"); + window.browsingContext.topChromeWindow.openDialog( + "window_preferences_disabled.xhtml", + "", + "modal", + RunCheckDisabled, + true + ); + } + + function RunTest() { + RunTestApplyPref(); + RunTestCommandRedirect(); + RunTestDisabled(); + } + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + </body> + +</window> diff --git a/toolkit/content/tests/chrome/test_preferences_beforeaccept.xhtml b/toolkit/content/tests/chrome/test_preferences_beforeaccept.xhtml new file mode 100644 index 0000000000..cde1982c0d --- /dev/null +++ b/toolkit/content/tests/chrome/test_preferences_beforeaccept.xhtml @@ -0,0 +1,65 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Preferences Window beforeaccept Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script type="application/javascript"> + <![CDATA[ + SimpleTest.waitForExplicitFinish(); + SimpleTest.registerCleanupFunction(() => { + SpecialPowers.clearUserPref("tests.beforeaccept.dialogShown"); + SpecialPowers.clearUserPref("tests.beforeaccept.called"); + }); + + // No instant-apply for this test because type="child". + var prefWindow = window.browsingContext.topChromeWindow.openDialog( + "window_preferences_beforeaccept.xhtml", + "", + "", + windowOnload + ); + + function windowOnload() { + var dialogShown = prefWindow.Preferences.get( + "tests.beforeaccept.dialogShown" + ); + var called = prefWindow.Preferences.get("tests.beforeaccept.called"); + is(dialogShown.value, true, "dialog opened, shown pref set"); + is(dialogShown.valueFromPreferences, null, "shown pref not committed"); + is(called.value, null, "beforeaccept not yet called"); + is( + called.valueFromPreferences, + null, + "beforeaccept not yet called, pref not committed" + ); + + // try to accept the dialog, should fail the first time + prefWindow.document.getElementById("beforeaccept_dialog").acceptDialog(); + is(prefWindow.closed, false, "window not closed"); + is(dialogShown.value, true, "shown pref still set"); + is(dialogShown.valueFromPreferences, null, "shown pref still not committed"); + is(called.value, true, "beforeaccept called"); + is(called.valueFromPreferences, null, "called pref not committed"); + + // try again, this one should succeed + prefWindow.document.getElementById("beforeaccept_dialog").acceptDialog(); + is(prefWindow.closed, true, "window now closed"); + is(dialogShown.valueFromPreferences, true, "shown pref committed"); + is(called.valueFromPreferences, true, "called pref committed"); + + SimpleTest.finish(); + } + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + </body> + +</window> diff --git a/toolkit/content/tests/chrome/test_preferences_onsyncfrompreference.xhtml b/toolkit/content/tests/chrome/test_preferences_onsyncfrompreference.xhtml new file mode 100644 index 0000000000..b3c618eac0 --- /dev/null +++ b/toolkit/content/tests/chrome/test_preferences_onsyncfrompreference.xhtml @@ -0,0 +1,59 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- 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/. --> +<window title="Preferences Window beforeaccept Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script type="application/javascript"> + <![CDATA[ + const PREFS = ['tests.onsyncfrompreference.pref1', + 'tests.onsyncfrompreference.pref2', + 'tests.onsyncfrompreference.pref3']; + + SimpleTest.waitForExplicitFinish(); + + for (let pref of PREFS) { + Services.prefs.setIntPref(pref, 1); + } + + let counter = 0; + let prefWindow = window.browsingContext.topChromeWindow.openDialog("window_preferences_onsyncfrompreference.xhtml", "", "", onSync); + + SimpleTest.registerCleanupFunction(() => { + for (let pref of PREFS) { + Services.prefs.clearUserPref(pref); + } + prefWindow.close(); + }); + + // Onsyncfrompreference handler for the prefs + function onSync() { + for (let pref of PREFS) { + // The `value` field of each <preference> element should be initialized by now. + + is(Services.prefs.getIntPref(pref), prefWindow.Preferences.get(pref).value, + "Pref constructor was called correctly") + } + + counter++; + + if (counter == PREFS.length) { + SimpleTest.finish(); + } + return true; + } + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + </body> + +</window> diff --git a/toolkit/content/tests/chrome/test_props.xhtml b/toolkit/content/tests/chrome/test_props.xhtml new file mode 100644 index 0000000000..6fcee90270 --- /dev/null +++ b/toolkit/content/tests/chrome/test_props.xhtml @@ -0,0 +1,87 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for basic properties - this test checks that the basic + properties defined in general.js and inherited by a number of elements + work properly. + --> +<window title="Basic Properties Test" + onload="setTimeout(test_props, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<command id="cmd_nothing"/> +<command id="cmd_action"/> + +<button id="button" label="Button" accesskey="B" + crop="end" image="happy.png" command="cmd_nothing"/> +<checkbox id="checkbox" label="Checkbox" accesskey="B" + crop="end" image="happy.png" command="cmd_nothing"/> +<radiogroup> + <radio id="radio" label="Radio Button" value="rb1" accesskey="B" + crop="end" image="happy.png" command="cmd_nothing"/> +</radiogroup> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function test_props() +{ + test_props_forelement($("button"), "Button", null); + test_props_forelement($("checkbox"), "Checkbox", null); + test_props_forelement($("radio"), "Radio Button", "rb1"); + + SimpleTest.finish(); +} + +function test_props_forelement(element, label, value) +{ + // check the initial values + is(element.label, label, "element label"); + if (value) + is(element.value, value, "element value"); + is(element.accessKey, "B", "element accessKey"); + is(element.image, "happy.png", "element image"); + is(element.command, "cmd_nothing", "element command"); + ok(element.tabIndex === 0, "element tabIndex"); + + synthesizeMouseExpectEvent(element, 4, 4, { }, $("cmd_nothing"), "command", "element"); + + // make sure that setters return the value + is(element.label = "Label", "Label", "element label setter return"); + if (value) + is(element.value = "lb", "lb", "element value setter return"); + is(element.accessKey = "L", "L", "element accessKey setter return"); + is(element.image = "sad.png", "sad.png", "element image setter return"); + is(element.command = "cmd_action", "cmd_action", "element command setter return"); + + // check the value after it was changed + is(element.label, "Label", "element label after set"); + if (value) + is(element.value, "lb", "element value after set"); + is(element.accessKey, "L", "element accessKey after set"); + is(element.image, "sad.png", "element image after set"); + is(element.command, "cmd_action", "element command after set"); + + synthesizeMouseExpectEvent(element, 4, 4, { }, $("cmd_action"), "command", "element"); + + // check that clicks on disabled items don't fire a command event + // eslint-disable-next-line no-constant-binary-expression + ok((element.disabled = true) === true, "element disabled setter return"); + ok(element.disabled === true, "element disabled after set"); + synthesizeMouseExpectEvent(element, 4, 4, { }, $("cmd_action"), "!command", "element"); + + element.disabled = false; +} + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_radio.xhtml b/toolkit/content/tests/chrome/test_radio.xhtml new file mode 100644 index 0000000000..3f222a8daa --- /dev/null +++ b/toolkit/content/tests/chrome/test_radio.xhtml @@ -0,0 +1,83 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for radio buttons + --> +<window title="Radio Buttons" width="500" height="600" + onload="setTimeout(test_radio, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="xul_selectcontrol.js"/> + +<radiogroup id="radiogroup"/> + +<radiogroup id="radiogroup-initwithvalue" value="two"> + <radio label="One" value="one"/> + <radio label="Two" value="two"/> + <radio label="Three" value="three"/> +</radiogroup> +<radiogroup id="radiogroup-initwithselected" value="two"> + <radio id="one" label="One" value="one" accesskey="o"/> + <radio id="two" label="Two" value="two" accesskey="t"/> + <radio label="Three" value="three" selected="true"/> +</radiogroup> + +<radiogroup id="radio-creation" hidden="true" /> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +async function test_radio() +{ + var element = document.getElementById("radiogroup"); + test_nsIDOMXULSelectControlElement(element, "radio", null); + test_nsIDOMXULSelectControlElement_UI(element, null); + + window.blur(); + + var accessKeyDetails = (navigator.platform.includes("Mac")) ? + { altKey : true, ctrlKey : true } : + { altKey : true, shiftKey: true }; + synthesizeKey("t", accessKeyDetails); + + var radiogroup = $("radiogroup-initwithselected"); + is(document.activeElement, radiogroup, "accesskey focuses radiogroup"); + is(radiogroup.selectedItem, $("two"), "accesskey selects radio"); + + $("radiogroup-initwithvalue").focus(); + + $("one").disabled = true; + synthesizeKey("o", accessKeyDetails); + + is(document.activeElement, $("radiogroup-initwithvalue"), "accesskey on disabled radio doesn't focus"); + is(radiogroup.selectedItem, $("two"), "accesskey on disabled radio doesn't change selection"); + + info("Testing appending child"); + var dynamicRadiogroup = document.querySelector("#radio-creation"); + var radio = document.createXULElement("radio"); + radio.setAttribute("selected", "true"); + radio.setAttribute("label", "one"); + radio.setAttribute("value", "one"); + dynamicRadiogroup.appendChild(radio); + dynamicRadiogroup.appendChild(document.createXULElement("radio")); + + dynamicRadiogroup.hidden = false; + info("Waiting for condition"); + await SimpleTest.promiseWaitForCondition(() => dynamicRadiogroup.value == "one", + "Value gets set once child is constructed"); + is(dynamicRadiogroup._radioChildren.length, 2, "Correct number of children"); + + SimpleTest.finish(); +} + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_richlistbox.xhtml b/toolkit/content/tests/chrome/test_richlistbox.xhtml new file mode 100644 index 0000000000..48303e0172 --- /dev/null +++ b/toolkit/content/tests/chrome/test_richlistbox.xhtml @@ -0,0 +1,117 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for listbox direction + --> +<window title="Listbox direction test" + onload="test_richlistbox()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <richlistbox seltype="multiple" id="richlistbox" flex="1" style="min-height: 80px; max-height: 80px; height: 80px"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + +<script type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var richListBox = document.getElementById("richlistbox"); + +function getScrollIndexAmount(aDirection) { + return (4 * aDirection + richListBox.currentIndex); +} + +function test_richlistbox() +{ + var height = richListBox.clientHeight; + var item; + do { + item = richListBox.appendItem("Test", ""); + item.style.height = item.style.minHeight = item.style.maxHeight = Math.floor(height / 4) + "px"; + } while (item.getBoundingClientRect().bottom < (height * 2)) + richListBox.appendItem("Test", ""); + richListBox.firstChild.nextSibling.id = "list-box-first"; + richListBox.lastChild.previousSibling.id = "list-box-last"; + + var count = richListBox.itemCount; + richListBox.focus(); + + // Test that dir="reverse" is ignored and behaves the same as dir="normal". + for (let dir of ["reverse", "normal"]) { + richListBox.style.MozBoxDirection = dir; + richListBox.selectedIndex = 0; + sendKey("DOWN"); + is(richListBox.currentIndex, 1, "Selection should move to the next item"); + sendKey("UP"); + is(richListBox.currentIndex, 0, "Selection should move to the previous item"); + sendKey("END"); + is(richListBox.currentIndex, count - 1, "Selection should move to the last item"); + sendKey("HOME"); + is(richListBox.currentIndex, 0, "Selection should move to the first item"); + var currentIndex = richListBox.currentIndex; + var index = richListBox.scrollOnePage(1); + sendKey("PAGE_DOWN"); + is(richListBox.currentIndex, index, "Selection should move to one page down"); + ok(richListBox.currentIndex > currentIndex, "Selection should move downwards"); + sendKey("END"); + currentIndex = richListBox.currentIndex; + index = richListBox.scrollOnePage(-1) + richListBox.currentIndex; + sendKey("PAGE_UP"); + is(richListBox.currentIndex, index, "Selection should move to one page up"); + ok(richListBox.currentIndex < currentIndex, "Selection should move upwards"); + richListBox.selectedItem = richListBox.firstChild; + richListBox.focus(); + synthesizeKey("KEY_ArrowDown", {shiftKey: true}, window); + let items = [richListBox.selectedItems[0], + richListBox.selectedItems[1]]; + is(items[0], richListBox.firstChild, "The last element should still be selected"); + is(items[1], richListBox.firstChild.nextSibling, "Both elements should now be selected"); + richListBox.clearSelection(); + richListBox.selectedItem = richListBox.firstChild; + sendMouseEvent({type: "click", shiftKey: true, clickCount: 1}, + "list-box-first", + window); + items = [richListBox.selectedItems[0], + richListBox.selectedItems[1]]; + is(items[0], richListBox.firstChild, "The last element should still be selected"); + is(items[1], richListBox.firstChild.nextSibling, "Both elements should now be selected"); + richListBox.addEventListener("keypress", function(aEvent) { + aEvent.preventDefault(); + }, { useCapture: true, once: true }); + richListBox.selectedIndex = 1; + sendKey("HOME"); + is(richListBox.selectedIndex, 1, "A stopped event should return indexing to normal"); + } + + // Test attempting to select a disabled item. + richListBox.clearSelection(); + richListBox.selectedItem = richListBox.firstChild; + richListBox.firstChild.nextSibling.setAttribute("disabled", true); + richListBox.focus(); + synthesizeKey("KEY_ArrowDown", {}, window); + is(richListBox.selectedItems.length, 1, "one item selected"); + is(richListBox.selectedItems[0], richListBox.firstChild, "first item selected"); + + // Selected item re-insertion should keep the item selected. + richListBox.clearSelection(); + item = richListBox.firstElementChild; + richListBox.selectedItem = item; + is(richListBox.selectedItems.length, 1, "one item selected"); + is(richListBox.selectedItems[0], item, "first item selected"); + item.remove(); + richListBox.append(item); + is(richListBox.selectedItems.length, 1, "one item selected"); + is(richListBox.selectedItems[0], item, "last (previosly first) item selected"); + + SimpleTest.finish(); +} + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_screenPersistence.xhtml b/toolkit/content/tests/chrome/test_screenPersistence.xhtml new file mode 100644 index 0000000000..667dcf9590 --- /dev/null +++ b/toolkit/content/tests/chrome/test_screenPersistence.xhtml @@ -0,0 +1,62 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Window Open Test" + onload="runTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<script class="testbody" type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + let win; + var left = 60 + screen.availLeft; + var upper = 60 + screen.availTop; + + function runTest() { + win = window.browsingContext.topChromeWindow + .openDialog("window_screenPosSize.xhtml", + "_blank", + "chrome,dialog=no,all,screenX=" + left + ",screenY=" + upper + ",outerHeight=200,outerWidth=200"); + SimpleTest.waitForFocus(checkTest, win); + } + function checkTest() { + is(win.screenX, left, "The window should be placed now at x=" + left + "px"); + is(win.screenY, upper, "The window should be placed now at y=" + upper + "px"); + is(win.outerHeight, 200, "The window size should be height=200px"); + is(win.outerWidth, 200, "The window size should be width=200px"); + runTest2(); + } + function runTest2() { + win.close(); + win = window.browsingContext.topChromeWindow + .openDialog("window_screenPosSize.xhtml", + "_blank", + "chrome,dialog=no,all"); + SimpleTest.waitForFocus(checkTest2, win); + } + function checkTest2() { + let runTime = SpecialPowers.Services.appinfo; + if (runTime.OS != "Linux") { + is(win.screenX, 80, "The window should be placed now at x=80px"); + is(win.screenY, 80, "The window should be placed now at y=80px"); + } + is(win.innerHeight, 300, "The window size should be height=300px"); + is(win.innerWidth, 300, "The window size should be width=300px"); + win.close(); + SimpleTest.finish(); + } +]]></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_scrollbar.xhtml b/toolkit/content/tests/chrome/test_scrollbar.xhtml new file mode 100644 index 0000000000..c16e9f2980 --- /dev/null +++ b/toolkit/content/tests/chrome/test_scrollbar.xhtml @@ -0,0 +1,131 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for scrollbars + --> +<window title="Scrollbar" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"/> + + <hbox> + <scrollbar orient="horizontal" + id="scroller" + curpos="0" + maxpos="600" + pageincrement="400" + style="width: 500px; margin: 0"/> + </hbox> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +/** Test for Scrollbar **/ +var scrollbarTester = { + scrollbar: null, + middlePref: false, + startTest() { + this.scrollbar = $("scroller"); + this.middlePref = this.getMiddlePref(); + var self = this; + [0, 1, 2].map(function(button) { + [false, true].map(function(alt) { + [false, true].map(function(shift) { + self.testThumbDragging(button, alt, shift); + }) + }) + }); + SimpleTest.finish(); + }, + testThumbDragging(button, withAlt, withShift) { + this.reset(); + var x = 160; // on the right half of the thumb + var y = 5; + + var isMac = navigator.platform.includes("Mac"); + let runtime = SpecialPowers.Services.appinfo; + var isGtk = runtime.widgetToolkit.includes("gtk"); + + // Start the drag. + this.mousedown(x, y, button, withAlt, withShift); + var newPos = this.getPos(); + var scrollToClick = (newPos != 0); + if (isMac || isGtk) { + ok(!scrollToClick, "On Linux and Mac OS X, clicking the scrollbar thumb "+ + "should never move it."); + } else if (button == 0 && withShift) { + ok(scrollToClick, "On platforms other than Linux and Mac OS X, holding "+ + "shift should enable scroll-to-click on the scrollbar thumb."); + } else if (button == 1 && this.middlePref) { + ok(scrollToClick, "When middlemouse.scrollbarPosition is on, clicking the "+ + "thumb with the middle mouse button should center it "+ + "around the cursor.") + } + + // Move one pixel to the right. + this.mousemove(x+1, y, button, withAlt, withShift); + var newPos2 = this.getPos(); + if (newPos2 != newPos) { + ok(newPos2 > newPos, "Scrollbar thumb should follow the mouse when dragged."); + ok(newPos2 - newPos < 3, "Scrollbar shouldn't move further than the mouse when dragged."); + ok(button == 0 || (button == 1 && this.middlePref) || (button == 2 && isGtk), + "Dragging the scrollbar should only be possible with the left mouse button."); + } else if (button == 0) { + // Dragging had no effect. + ok(false, "Dragging the scrollbar thumb should work."); + } else if (button == 1 && this.middlePref && (!isGtk && !isMac)) { + ok(false, "When middlemouse.scrollbarPosition is on, dragging the "+ + "scrollbar thumb should be possible using the middle mouse button."); + } else { + ok(true, "Dragging works correctly."); + } + + // Release the mouse button. + this.mouseup(x+1, y, button, withAlt, withShift); + var newPos3 = this.getPos(); + ok(newPos3 == newPos2, + "Releasing the mouse button after dragging the thumb shouldn't move it."); + }, + getMiddlePref() { + // It would be better to test with different middlePref settings, + // but the setting is only queried once, at browser startup, so + // changing it here wouldn't have any effect + var mouseBranch = SpecialPowers.Services.prefs.getBranch("middlemouse."); + return mouseBranch.getBoolPref("scrollbarPosition"); + }, + setPos(pos) { + this.scrollbar.setAttribute("curpos", pos); + }, + getPos() { + return this.scrollbar.getAttribute("curpos"); + }, + reset() { + this.setPos(0); + }, + mousedown(x, y, button, alt, shift) { + synthesizeMouse(this.scrollbar, x, y, { type: "mousedown", 'button': button, + altKey: alt, shiftKey: shift }); + }, + mousemove(x, y, button, alt, shift) { + synthesizeMouse(this.scrollbar, x, y, { type: "mousemove", 'button': button, + altKey: alt, shiftKey: shift }); + }, + mouseup(x, y, button, alt, shift) { + synthesizeMouse(this.scrollbar, x, y, { type: "mouseup", 'button': button, + altKey: alt, shiftKey: shift }); + } +} + +function doTest() { + setTimeout(function() { scrollbarTester.startTest(); }, 0); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(doTest); + +]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_showcaret.xhtml b/toolkit/content/tests/chrome/test_showcaret.xhtml new file mode 100644 index 0000000000..eb344589fc --- /dev/null +++ b/toolkit/content/tests/chrome/test_showcaret.xhtml @@ -0,0 +1,99 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Show Caret Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<iframe id="f1" width="100" height="100" onload="frameLoaded()" + src="data:text/html,%3Cbody%20style='height:%208000px'%3E%3Cp%3EHello%3C/p%3EGoodbye%3C/body%3E"/> +<!-- <body style='height: 8000px'><p>Hello</p><span id='s'>Goodbye<span></body> --> +<iframe id="f2" type="content" showcaret="true" width="100" height="100" onload="frameLoaded()" + src="data:text/html,%3Cbody%20style%3D%27height%3A%208000px%27%3E%3Cp%3EHello%3C%2Fp%3E%3Cspan%20id%3D%27s%27%3EGoodbye%3Cspan%3E%3C%2Fbody%3E"/> + +<script> +<![CDATA[ + +var framesLoaded = 0; +var otherWindow = null; + +function frameLoaded() { if (++framesLoaded == 2) SimpleTest.waitForFocus(runTest); } + +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + var sel1 = frames[0].getSelection(); + sel1.collapse(frames[0].document.body, 0); + + var sel2 = frames[1].getSelection(); + sel2.collapse(frames[1].document.body, 0); + window.frames[0].focus(); + document.commandDispatcher.getControllerForCommand("cmd_moveBottom").doCommand("cmd_moveBottom"); + + var listener = function() { + if (!(frames[0].scrollY > 0)) { + window.content.removeEventListener("scroll", listener); + } + } + window.frames[0].addEventListener("scroll", listener); + + sel1 = frames[0].getSelection(); + sel1.collapse(frames[0].document.body, 0); + + sel2 = frames[1].getSelection(); + sel2.collapse(frames[1].document.body, 0); + + window.frames[0].focus(); + document.commandDispatcher.getControllerForCommand("cmd_moveBottom").doCommand("cmd_moveBottom"); + is(sel1.focusNode, frames[0].document.body, "focusNode for non-showcaret"); + is(sel1.focusOffset, 0, "focusOffset for non-showcaret"); + + window.frames[1].focus(); + document.commandDispatcher.getControllerForCommand("cmd_moveBottom").doCommand("cmd_moveBottom"); + + ok(frames[1].scrollY < + frames[1].document.getElementById('s').getBoundingClientRect().top, + "scrollY for showcaret"); + isnot(sel2.focusNode, frames[1].document.body, "focusNode for showcaret"); + ok(sel2.anchorOffset > 0, "focusOffset for showcaret"); + + otherWindow = window.browsingContext.topChromeWindow.open("window_showcaret.xhtml", "_blank", "chrome,width=400,height=200"); + otherWindow.addEventListener("focus", otherWindowFocused); +} + +function otherWindowFocused() +{ + otherWindow.removeEventListener("focus", otherWindowFocused); + + // enable caret browsing temporarily to test caret movement + let prefs = SpecialPowers.Services.prefs; + prefs.setBoolPref("accessibility.browsewithcaret", true); + + var hbox = otherWindow.document.documentElement.firstChild; + hbox.focus(); + is(otherWindow.document.activeElement, hbox, "hbox in other window is focused"); + + document.commandDispatcher.getControllerForCommand("cmd_lineNext").doCommand("cmd_lineNext"); + is(otherWindow.document.activeElement, hbox, "hbox still focused in other window after down movement"); + + prefs.setBoolPref("accessibility.browsewithcaret", false); + + otherWindow.close(); + SimpleTest.finish(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_subframe_origin.xhtml b/toolkit/content/tests/chrome/test_subframe_origin.xhtml new file mode 100644 index 0000000000..65b7190baf --- /dev/null +++ b/toolkit/content/tests/chrome/test_subframe_origin.xhtml @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<!-- 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/. --> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Subframe Event Tests" + onload="setTimeout(runTest, 0);" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<script> + +// Added after content child widgets were removed from ui windows. Tests sub frame +// event client coordinate offsets. + +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.openDialog("window_subframe_origin.xhtml", "_blank", "chrome,width=600,height=600,noopener", window); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_tabbox.xhtml b/toolkit/content/tests/chrome/test_tabbox.xhtml new file mode 100644 index 0000000000..bca919f8c5 --- /dev/null +++ b/toolkit/content/tests/chrome/test_tabbox.xhtml @@ -0,0 +1,223 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for tabboxes + --> +<window title="Tabbox Test" width="500" height="600" + onload="setTimeout(test_tabbox, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="xul_selectcontrol.js"/> + +<vbox id="tabboxes"> + +<tabbox id="tabbox"> + <tabs id="tabs"> + <tab id="tab1" label="Tab 1"/> + <tab id="tab2" label="Tab 2"/> + </tabs> + <tabpanels id="tabpanels"> + <button id="panel1" label="Panel 1"/> + <button id="panel2" label="Panel 2"/> + </tabpanels> +</tabbox> + +<tabbox id="tabbox-initwithvalue"> + <tabs id="tabs-initwithvalue" value="two"> + <tab label="Tab 1" value="one"/> + <tab label="Tab 2" value="two"/> + <tab label="Tab 3" value="three"/> + </tabs> + <tabpanels id="tabpanels-initwithvalue"> + <button label="Panel 1"/> + <button label="Panel 2"/> + <button label="Panel 3"/> + </tabpanels> +</tabbox> + +<tabbox id="tabbox-initwithselected"> + <tabs id="tabs-initwithselected" value="two"> + <tab label="Tab 1" value="one"/> + <tab label="Tab 2" value="two"/> + <tab label="Tab 3" value="three" selected="true"/> + </tabs> + <tabpanels id="tabpanels-initwithselected"> + <button label="Panel 1"/> + <button label="Panel 2"/> + <button label="Panel 3"/> + </tabpanels> +</tabbox> + +</vbox> + +<tabbox id="tabbox-nofocus"> + <html:input id="textbox-extra" hidden="true"/> + <tabs> + <tab label="Tab 1" value="one"/> + <tab id="tab-nofocus" label="Tab 2" value="two"/> + </tabs> + <tabpanels> + <tabpanel> + <button id="tab-nofocus-button" label="Label"/> + </tabpanel> + <tabpanel id="tabpanel-nofocusinpaneltab"> + <label id="tablabel" value="Label"/> + </tabpanel> + </tabpanels> +</tabbox> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function test_tabbox() +{ + var tabbox = document.getElementById("tabbox"); + var tabs = document.getElementById("tabs"); + var tabpanels = document.getElementById("tabpanels"); + + test_tabbox_State(tabbox, "tabbox initial", 0, tabs.allTabs[0], tabpanels.firstChild); + + // check the selectedIndex property + tabbox.selectedIndex = 1; + test_tabbox_State(tabbox, "tabbox selectedIndex 1", 1, tabs.allTabs[tabs.allTabs.length - 1], tabpanels.lastChild); + + tabbox.selectedIndex = 2; + test_tabbox_State(tabbox, "tabbox selectedIndex 2", 1, tabs.allTabs[tabs.allTabs.length - 1], tabpanels.lastChild); + + // tabbox must have a selection, so setting to -1 should do nothing + tabbox.selectedIndex = -1; + test_tabbox_State(tabbox, "tabbox selectedIndex -1", 1, tabs.allTabs[tabs.allTabs.length - 1], tabpanels.lastChild); + + // check the selectedTab property + tabbox.selectedTab = tabs.allTabs[0]; + test_tabbox_State(tabbox, "tabbox selected", 0, tabs.allTabs[0], tabpanels.firstChild); + + // setting selectedTab to null should not do anything + tabbox.selectedTab = null; + test_tabbox_State(tabbox, "tabbox selectedTab null", 0, tabs.allTabs[0], tabpanels.firstChild); + + // check the selectedPanel property + tabbox.selectedPanel = tabpanels.lastChild; + test_tabbox_State(tabbox, "tabbox selectedPanel", 0, tabs.allTabs[0], tabpanels.lastChild); + + // setting selectedPanel to null should not do anything + tabbox.selectedPanel = null; + test_tabbox_State(tabbox, "tabbox selectedPanel null", 0, tabs.allTabs[0], tabpanels.lastChild); + + tabbox.selectedIndex = 0; + test_tabpanels(tabpanels, tabbox); + + tabs.firstChild.remove(); + tabs.firstChild.remove(); + + test_tabs(tabs); + + test_tabbox_focus(); +} + +function test_tabpanels(tabpanels, tabbox) +{ + var tab = tabbox.selectedTab; + + // changing the selection on the tabpanels should not affect the tabbox + // or tabs within + // check the selectedIndex property + tabpanels.selectedIndex = 1; + test_tabbox_State(tabbox, "tabpanels tabbox selectedIndex 1", 0, tab, tabpanels.lastChild); + test_tabpanels_State(tabpanels, "tabpanels selectedIndex 1", 1, tabpanels.lastChild); + + tabpanels.selectedIndex = 0; + test_tabbox_State(tabbox, "tabpanels tabbox selectedIndex 2", 0, tab, tabpanels.firstChild); + test_tabpanels_State(tabpanels, "tabpanels selectedIndex 2", 0, tabpanels.firstChild); + + // setting selectedIndex to -1 should do nothing + tabpanels.selectedIndex = 1; + tabpanels.selectedIndex = -1; + test_tabbox_State(tabbox, "tabpanels tabbox selectedIndex -1", 0, tab, tabpanels.lastChild); + test_tabpanels_State(tabpanels, "tabpanels selectedIndex -1", 1, tabpanels.lastChild); + + // check the tabpanels.selectedPanel property + tabpanels.selectedPanel = tabpanels.lastChild; + test_tabbox_State(tabbox, "tabpanels tabbox selectedPanel", 0, tab, tabpanels.lastChild); + test_tabpanels_State(tabpanels, "tabpanels selectedPanel", 1, tabpanels.lastChild); + + // check setting the tabpanels.selectedPanel property to null + tabpanels.selectedPanel = null; + test_tabbox_State(tabbox, "tabpanels selectedPanel null", 0, tab, tabpanels.lastChild); +} + +function test_tabs(tabs) +{ + test_nsIDOMXULSelectControlElement(tabs, "tab", "tabs"); + // XXXndeakin would test the UI aspect of tabs, but the mouse + // events on tabs are fired in a timeout causing the generic + // test_nsIDOMXULSelectControlElement_UI method not to work + // test_nsIDOMXULSelectControlElement_UI(tabs, null); +} + +function test_tabbox_State(tabbox, testid, index, tab, panel) +{ + is(tabbox.selectedIndex, index, testid + " selectedIndex"); + is(tabbox.selectedTab, tab, testid + " selectedTab"); + is(tabbox.selectedPanel, panel, testid + " selectedPanel"); +} + +function test_tabpanels_State(tabpanels, testid, index, panel) +{ + is(tabpanels.selectedIndex, index, testid + " selectedIndex"); + is(tabpanels.selectedPanel, panel, testid + " selectedPanel"); +} + +function test_tabbox_focus() +{ + $("tabboxes").hidden = true; + $(document.activeElement).blur(); + + var tabbox = $("tabbox-nofocus"); + var tab = $("tab-nofocus"); + + when_tab_focused(tab, function () { + is(document.activeElement, tab, "focus in tab with no focusable elements"); + + tabbox.selectedIndex = 0; + $("tab-nofocus-button").focus(); + + when_tab_focused(tab, function () { + is(document.activeElement, tab, "focus in tab with no focusable elements, but with something in another tab focused"); + + var textboxExtra = $("textbox-extra"); + textboxExtra.addEventListener("focus", function () { + is(document.activeElement, textboxExtra, "focus in tab with focus currently in textbox that is sibling of tabs"); + + SimpleTest.finish(); + }, {once: true}); + + tabbox.selectedIndex = 0; + textboxExtra.hidden = false; + synthesizeMouseAtCenter(tab, { }); + }); + + synthesizeMouseAtCenter(tab, { }); + }); + + synthesizeMouseAtCenter(tab, { }); +} + +function when_tab_focused(tab, callback) { + tab.addEventListener("focus", function onFocused() { + SimpleTest.executeSoon(callback); + }, {once: true}); +} + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_tabindex.xhtml b/toolkit/content/tests/chrome/test_tabindex.xhtml new file mode 100644 index 0000000000..9828728c63 --- /dev/null +++ b/toolkit/content/tests/chrome/test_tabindex.xhtml @@ -0,0 +1,136 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for tabindex + --> +<window title="tabindex" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<!-- + Elements are navigated in the following order: + 1. tabindex > 0 in tree order + 2. tabindex = 0 in tree order + Elements with tabindex = -1 are focusable, but not in the tab order + --> +<hbox> + <button id="t7" label="One"/> + <checkbox id="f1" label="Two" tabindex="-1"/> + <button id="t8" label="Three" tabindex="0"/> + <checkbox id="t1" label="Four" tabindex="1"/> +</hbox> +<hbox> + <html:input id="t9" idmod="t5" size="3"/> + <html:input id="f2" size="3" tabindex="-1"/> + <html:input id="t10" idmod="t6" size="3" tabindex="0"/> + <html:input id="t2" idmod="t1" size="3" tabindex="1"/> +</hbox> +<hbox> + <button id="n1" style="-moz-user-focus: ignore;" label="One"/> + <checkbox id="f3" style="-moz-user-focus: ignore;" label="Two" tabindex="-1"/> + <button id="t11" style="-moz-user-focus: ignore;" label="Three" tabindex="0"/> + <checkbox id="t3" style="-moz-user-focus: ignore;" label="Four" tabindex="1"/> +</hbox> +<hbox> + <html:input id="t12" idmod="t7" style="-moz-user-focus: ignore;" size="3"/> + <html:input id="f4" style="-moz-user-focus: ignore;" size="3" tabindex="-1"/> + <html:input id="t13" idmod="t8" style="-moz-user-focus: ignore;" size="3" tabindex="0"/> + <html:input id="t4" idmod="t2" style="-moz-user-focus: ignore;" size="3" tabindex="1"/> +</hbox> +<richlistbox id="t14" idmod="t9"> + <richlistitem><label value="Item One"/></richlistitem> +</richlistbox> + +<hbox> + <!-- the tabindex attribute applies to non-controls as well. They are not + focusable unless tabindex is explicitly specified. + --> + <dropmarker id="n2"/> + <dropmarker id="f5" tabindex="-1"/> + <dropmarker id="t15" tabindex="0"/> + <dropmarker id="t5" idmod="t3" tabindex="1"/> + <dropmarker id="t16" style="-moz-user-focus: normal;"/> + <dropmarker id="f6" style="-moz-user-focus: normal;" tabindex="-1"/> + <dropmarker id="t17" style="-moz-user-focus: normal;" tabindex="0"/> + <dropmarker id="t6" idmod="t4" style="-moz-user-focus: normal;" tabindex="1"/> +</hbox> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function checkFocusability(aId, aFocusable) +{ + document.activeElement.blur(); + let testNode = document.getElementById(aId); + testNode.focus(); + let newFocus = document.activeElement; + let check = aFocusable ? is : isnot; + let focusableText = aFocusable ? "focusable " : "unfocusable "; + check(newFocus, testNode, + ".focus() call on " + focusableText + aId); +} + +var gAdjustedTabFocusModel = false; +var gTestCount = 17; +var gTestsOccurred = 0; +let gFocusableNotTabbableCount = 6; +let gNotFocusableCount = 2; + +function runTests() +{ + var t; + function onFocus(event) { + if (t == 1 && event.target.id == "t2") { + // looks to be using the MacOSX Full Keyboard Access set to Textboxes + // and lists only so use the idmod attribute instead + gAdjustedTabFocusModel = true; + gTestCount = 9; + } + + var attrcompare = gAdjustedTabFocusModel ? "idmod" : "id"; + + // check for the last test which should wrap aorund to the first item + // consider the focus event on the inner input of textboxes instead + is(event.target.getAttribute(attrcompare), "t" + t, + "tab " + t + " to " + event.target.localName); + gTestsOccurred++; + } + window.addEventListener("focus", onFocus, true); + + for (t = 1; t <= gTestCount; t++) + synthesizeKey("KEY_Tab"); + + is(gTestsOccurred, gTestCount, "test count"); + window.removeEventListener("focus", onFocus, true); + + for (let i = 1; i <= gFocusableNotTabbableCount; ++i) { + checkFocusability("f" + i, true); + } + + for (let i = 1; i <= gNotFocusableCount; ++i) { + checkFocusability("n" + i, false); + } + + SimpleTest.finish(); +} + +SimpleTest.waitForFocus(runTests); + +]]> + +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_textbox_search.xhtml b/toolkit/content/tests/chrome/test_textbox_search.xhtml new file mode 100644 index 0000000000..216caae6f4 --- /dev/null +++ b/toolkit/content/tests/chrome/test_textbox_search.xhtml @@ -0,0 +1,175 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for search textbox + --> +<window title="Search textbox test" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <hbox id="searchbox-container"> + <search-textbox id="searchbox" + oncommand="doSearch(this.value);" + placeholder="random placeholder" + timeout="1"/> + </hbox> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var gExpectedValue; +var gLastTest; + +function doTests() { + var textbox = $("searchbox"); + + // Reconnect the searchbox to ensure connectedCallback only runs once + // (bug 1650486). + textbox.remove(); + $("searchbox-container").append(textbox); + is(textbox.shadowRoot.querySelectorAll(".textbox-search-sign").length, 1, + "only one search icon in the search box"); + + var icons = textbox._searchIcons; + var searchIcon = icons.querySelector(".textbox-search-icon"); + var clearIcon = icons.querySelector(".textbox-search-clear"); + + ok(icons, "icon deck found"); + ok(searchIcon, "search icon found"); + ok(clearIcon, "clear icon found"); + is(icons.selectedPanel, searchIcon, "search icon is displayed"); + + is(textbox.placeholder, "random placeholder", "search textbox supports placeholder"); + is(textbox.value, "", "placeholder doesn't interfere with the real value"); + is(textbox.shadowRoot.querySelector("input").getAttribute("inputmode"), "search", "inputmode of search textbox is search by default"); + + function iconClick(aIcon) { + is(icons.selectedPanel, aIcon, aIcon.className + " icon must be displayed in order to be clickable"); + + //XXX synthesizeMouse worked on Linux but failed on Windows an Mac + // for unknown reasons. Manually dispatch the event for now. + //synthesizeMouse(aIcon, 0, 0, {}); + + var event = document.createEvent("MouseEvent"); + event.initMouseEvent("click", true, true, window, 1, + 0, 0, 0, 0, + false, false, false, false, + 0, null); + aIcon.dispatchEvent(event); + } + + iconClick(searchIcon); + is(textbox.getAttribute("focused"), "true", "clicking the search icon focuses the textbox"); + + textbox.value = "foo"; + is(icons.selectedPanel, clearIcon, "clear icon is displayed when setting a value"); + + textbox.value = ""; + is(textbox.defaultValue, "", "defaultValue is empty"); + is(textbox.value, "", "reset method clears the textbox"); + is(icons.selectedPanel, searchIcon, "search icon is displayed after clearing value"); + + textbox.value = "foo"; + gExpectedValue = ""; + iconClick(clearIcon); + is(textbox.value, "", "clicking the clear icon clears the textbox"); + ok(gExpectedValue == null, "search triggered when clearing the textbox with the clear icon"); + + textbox.value = "foo"; + gExpectedValue = ""; + synthesizeKey("KEY_Escape"); + is(textbox.value, "", "escape key clears the textbox"); + ok(gExpectedValue == null, "search triggered when clearing the textbox with the escape key"); + + textbox.value = "bar"; + gExpectedValue = "bar"; + textbox.doCommand(); + ok(gExpectedValue == null, "search triggered with doCommand"); + + gExpectedValue = "bar"; + synthesizeKey("KEY_Enter"); + ok(gExpectedValue == null, "search triggered with enter key"); + + textbox.value = ""; + textbox.searchButton = true; + is(textbox.getAttribute("searchbutton"), "true", "searchbutton attribute set on the textbox"); + + textbox.value = "foo"; + is(icons.selectedPanel, searchIcon, "search icon displayed in search button mode if there's a value"); + + gExpectedValue = "foo"; + iconClick(searchIcon); + ok(gExpectedValue == null, "search triggered when clicking the search icon in search button mode"); + is(icons.selectedPanel, clearIcon, "clear icon displayed in search button mode after submitting"); + + sendString("o"); + is(icons.selectedPanel, searchIcon, "search icon displayed in search button mode when typing a key"); + + gExpectedValue = "fooo"; + iconClick(searchIcon); // display the clear icon (tested above) + + textbox.value = "foo"; + is(icons.selectedPanel, searchIcon, "search icon displayed in search button mode when the value is changed"); + + gExpectedValue = "foo"; + synthesizeKey("KEY_Enter"); + ok(gExpectedValue == null, "search triggered with enter key in search button mode"); + is(icons.selectedPanel, clearIcon, "clear icon displayed in search button mode after submitting with enter key"); + + textbox.value = "x"; + synthesizeKey("KEY_Backspace"); + is(icons.selectedPanel, searchIcon, "search icon displayed in search button mode when deleting the value with the backspace key"); + + gExpectedValue = ""; + synthesizeKey("KEY_Enter"); + ok(gExpectedValue == null, "search triggered with enter key in search button mode"); + is(icons.selectedPanel, searchIcon, "search icon displayed in search button mode after submitting an empty string"); + + textbox.readOnly = true; + gExpectedValue = "foo"; + textbox.value = "foo"; + iconClick(searchIcon); + ok(gExpectedValue == null, "search triggered when clicking the search icon in search button mode while the textbox is read-only"); + is(icons.selectedPanel, searchIcon, "search icon persists in search button mode after submitting while the textbox is read-only"); + textbox.readOnly = false; + + textbox.disabled = true; + is(searchIcon.getAttribute("disabled"), "true", "disabled attribute inherited to the search icon"); + is(clearIcon.getAttribute("disabled"), "true", "disabled attribute inherited to the clear icon"); + gExpectedValue = false; + textbox.value = "foo"; + iconClick(searchIcon); + ok(!gExpectedValue, "search *not* triggered when clicking the search icon in search button mode while the textbox is disabled"); + is(icons.selectedPanel, searchIcon, "search icon persists in search button mode when trying to submit while the textbox is disabled"); + textbox.disabled = false; + ok(!searchIcon.hasAttribute("disabled"), "disabled attribute removed from the search icon"); + ok(!clearIcon.hasAttribute("disabled"), "disabled attribute removed from the clear icon"); + + textbox.searchButton = false; + ok(!textbox.hasAttribute("searchbutton"), "searchbutton attribute removed from the textbox"); + + gLastTest = true; + gExpectedValue = "123"; + textbox.value = "1"; + sendString("23"); +} + +function doSearch(aValue) { + is(aValue, gExpectedValue, "search triggered with expected value"); + gExpectedValue = null; + if (gLastTest) + SimpleTest.finish(); +} + +SimpleTest.waitForFocus(doTests); + + ]]></script> + +</window> diff --git a/toolkit/content/tests/chrome/test_tooltip.xhtml b/toolkit/content/tests/chrome/test_tooltip.xhtml new file mode 100644 index 0000000000..ec4855131d --- /dev/null +++ b/toolkit/content/tests/chrome/test_tooltip.xhtml @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Tooltip Tests" + onload="setTimeout(runTest, 0)" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.openDialog("window_tooltip.xhtml", "_blank", "chrome,width=700,height=700,noopener", window); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_tooltip_noautohide.xhtml b/toolkit/content/tests/chrome/test_tooltip_noautohide.xhtml new file mode 100644 index 0000000000..99216809bf --- /dev/null +++ b/toolkit/content/tests/chrome/test_tooltip_noautohide.xhtml @@ -0,0 +1,56 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Tooltip Noautohide Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<tooltip id="thetooltip" noautohide="true" + onpopupshown="setTimeout(tooltipStillShown, 6000)" + onpopuphidden="ok(gChecked, 'tooltip did not hide'); SimpleTest.finish()"> + <label id="label" value="This is a tooltip"/> +</tooltip> + +<button id="button" label="Tooltip Text" tooltip="thetooltip"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +var gChecked = false; + +function runTests() +{ + var button = document.getElementById("button"); + var windowUtils = window.windowUtils; + windowUtils.disableNonTestMouseEvents(true); + synthesizeMouse(button, 2, 2, { type: "mouseover" }); + synthesizeMouse(button, 4, 4, { type: "mousemove" }); + synthesizeMouse(button, 6, 6, { type: "mousemove" }); + windowUtils.disableNonTestMouseEvents(false); +} + +function tooltipStillShown() +{ + gChecked = true; + document.getElementById("thetooltip").hidePopup(); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(runTests); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_tree.xhtml b/toolkit/content/tests/chrome/test_tree.xhtml new file mode 100644 index 0000000000..d35d6354ab --- /dev/null +++ b/toolkit/content/tests/chrome/test_tree.xhtml @@ -0,0 +1,84 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for tree using multiple row selection + --> +<window title="Tree" width="500" height="600" + onload="setTimeout(testtag_tree, 0, 'tree-simple', 'treechildren-simple', 'multiple', 'simple', 'tree');" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<script src="tree_shared.js"/> + +<tree id="tree-simple" rows="4"> + <treecols> + <treecol id="name" label="Name" sort="label" properties="one two" flex="1"/> + <treecol id="address" label="Address" flex="1"/> + </treecols> + <treechildren id="treechildren-simple"> + <treeitem> + <treerow> + <treecell label="Mary"/> + <treecell label="206 Garden Avenue"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Chris"/> + <treecell label="19 Marion Street"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue" editable="false"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Mary"/> + <treecell label="206 Garden Avenue"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Chris"/> + <treecell label="19 Marion Street"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + </treerow> + </treeitem> + </treechildren> +</tree> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +</window> + diff --git a/toolkit/content/tests/chrome/test_tree_hier.xhtml b/toolkit/content/tests/chrome/test_tree_hier.xhtml new file mode 100644 index 0000000000..fd4198c804 --- /dev/null +++ b/toolkit/content/tests/chrome/test_tree_hier.xhtml @@ -0,0 +1,136 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for hierarchical tree + --> +<window title="Hierarchical Tree" width="500" height="600" + onload="setTimeout(testtag_tree, 0, 'tree-hier', 'treechildren-hier', 'multiple', '', 'hierarchical tree');" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<script src="tree_shared.js"/> + +<tree id="tree-hier" rows="4"> + <treecols> + <treecol id="name" label="Name" primary="true" + sort="label" properties="one two" flex="1"/> + <treecol id="address" label="Address" style="flex-grow: 2; flex-shrink: 2"/> + <treecol id="planet" label="Planet" flex="1"/> + <treecol id="gender" label="Gender" flex="1" cycler="true"/> + </treecols> + <treechildren id="treechildren-hier"> + <treeitem> + <treerow properties="firstrow"> + <treecell label="Mary" value="mary" properties="firstname"/> + <treecell label="206 Garden Avenue" value="206ga"/> + <treecell label="Earth"/> + <treecell label="Female" value="f"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell/> + <treecell value="19ms"/> + <treecell label="Earth"/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + <treeitem container="true"> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue" editable="false"/> + <treecell label="Saturn"/> + <treecell label="Female" value="f"/> + </treerow> + <treechildren> + <treeitem> + <treerow> + <treecell label="Mary"/> + <treecell label="206 Garden Avenue"/> + <treecell label="Female" value="f"/> + <treecell label="Neptune"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Chris"/> + <treecell label="19 Marion Street"/> + <treecell label="Omicron Persei 8"/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue" editable="false"/> + <treecell label="Earth"/> + <treecell label="Female" value="f"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + <treecell label="Neptune"/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + </treechildren> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + <treecell/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Mary"/> + <treecell label="206 Garden Avenue" selectable="false"/> + <treecell label=""/> + <treecell label="Female" value="f"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Chris"/> + <treecell label="19 Marion Street"/> + <treecell label="Neptune"/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue"/> + <treecell label="Earth"/> + <treecell label="Female" value="f"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + <treecell label="Mars"/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + </treechildren> +</tree> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_tree_scroll.xhtml b/toolkit/content/tests/chrome/test_tree_scroll.xhtml new file mode 100644 index 0000000000..08b6d9141a --- /dev/null +++ b/toolkit/content/tests/chrome/test_tree_scroll.xhtml @@ -0,0 +1,93 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for scrolling behavior of tree +--> +<window title="Tree" width="500" height="600" + onload="setTimeout(testtag_tree_scroll);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" +> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script> + + <script src="chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script> + <script src="chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js"></script> + <script src="tree_shared.js"/> + +<html:div style="height: 200px; overflow-y: scroll;"> + <html:div id="top" style="height: 50px; background: cyan;"></html:div> + <tree rows="3"> + <treecols> + <treecol id="name" label="label" sort="label" flex="1"/> + </treecols> + <treechildren> + <treeitem> + <treerow> + <treecell label="0"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="1"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="2"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="3"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="4"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="5"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="6"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="7"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="8"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="9"/> + </treerow> + </treeitem> + </treechildren> + </tree> + <html:div id="bottom" style="height: 150px; background: orange;"></html:div> +</html:div> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_tree_single.xhtml b/toolkit/content/tests/chrome/test_tree_single.xhtml new file mode 100644 index 0000000000..d5bae73ccb --- /dev/null +++ b/toolkit/content/tests/chrome/test_tree_single.xhtml @@ -0,0 +1,110 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for single selection tree + --> +<window title="Single Selection Tree" width="500" height="600" + onload="setTimeout(testtag_tree, 0, 'tree-single', 'treechildren-single', + 'single', 'simple', 'single selection tree');" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<script src="tree_shared.js"/> + +<tree id="tree-single" rows="4" seltype="single"> + <treecols> + <treecol id="name" label="Name" sort="label" properties="one two" flex="1"/> + <treecol id="address" label="Address" flex="1"/> + </treecols> + <treechildren id="treechildren-single"> + <treeitem> + <treerow properties="firstrow"> + <treecell label="Mary" value="mary" properties="firstname"/> + <treecell label="206 Garden Avenue" value="206ga"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell/> + <treecell value="19ms"/> + </treerow> + </treeitem> + <treeitem container="true"> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue" editable="false"/> + </treerow> + <treechildren> + <treeitem> + <treerow> + <treecell label="Mary"/> + <treecell label="206 Garden Avenue"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Chris"/> + <treecell label="19 Marion Street"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue" editable="false"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + </treerow> + </treeitem> + </treechildren> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Mary"/> + <treecell label="206 Garden Avenue" selectable="false"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Chris"/> + <treecell label="19 Marion Street"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + </treerow> + </treeitem> + </treechildren> +</tree> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_tree_view.xhtml b/toolkit/content/tests/chrome/test_tree_view.xhtml new file mode 100644 index 0000000000..5e45161c6c --- /dev/null +++ b/toolkit/content/tests/chrome/test_tree_view.xhtml @@ -0,0 +1,113 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for tree using a custom nsITreeView + --> +<window title="Tree" onload="init()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<script src="tree_shared.js"/> + +<script> +<![CDATA[ +/* import-globals-from ../widgets/tree_shared.js */ +// This is our custom view, based on the treeview interface +var view = +{ + treeData: [["Mary", "206 Garden Avenue"], + ["Chris", "19 Marion Street"], + ["Sarah", "702 Fern Avenue"], + ["John", "99 Westminster Avenue"]], + value: "", + rowCount: 8, + getCellText(row, column) { return this.treeData[row % 4][column.index]; }, + getCellValue(row, column) { return this.value; }, + setCellText(row, column, val) { this.treeData[row % 4][column.index] = val; }, + setCellValue(row, column, val) { this.value = val; }, + setTree(tree) { this.tree = tree; }, + isContainer(row) { return false; }, + isContainerOpen(row) { return false; }, + isContainerEmpty(row) { return false; }, + isSeparator(row) { return false; }, + isSorted(row) { return false; }, + isEditable(row, column) { return row != 2 || column.index != 1; }, + getParentIndex(row, column) { return -1; }, + getLevel(row) { return 0; }, + hasNextSibling(row, column) { return row != this.rowCount - 1; }, + getImageSrc(row, column) { return ""; }, + cycleHeader(column) { }, + getRowProperties(row) { return ""; }, + getCellProperties(row, column) { return ""; }, + getColumnProperties(column) + { + if (!column.index) { + return "one two"; + } + + return ""; + } +} + +function getCustomTreeViewCellInfo() +{ + var obj = { rows: [] }; + + for (var row = 0; row < view.rowCount; row++) { + var cellInfo = [ ]; + for (var column = 0; column < 1; column++) { + cellInfo.push({ label: "" + view.treeData[row % 4][column], + value: "", + properties: "", + editable: row != 2 || column.index != 1, + selectable: true, + image: "" }); + } + + obj.rows.push({ cells: cellInfo, + properties: "", + container: false, + separator: false, + children: null, + level: 0, + parent: -1 }); + } + + return obj; +} + +function init() +{ + var tree = document.getElementById("tree-view"); + tree.view = view; + tree.ensureRowIsVisible(0); + is(tree.getFirstVisibleRow(), 0, "first visible after ensureRowIsVisible on load"); + + setTimeout(testtag_tree, 0, "tree-view", "treechildren-view", "multiple", "simple", "tree view"); +} + +]]> +</script> + +<tree id="tree-view" rows="4"> + <treecols> + <treecol id="name" label="Name" sort="label" flex="1"/> + <treecol id="address" label="Address" flex="1"/> + </treecols> + <treechildren id="treechildren-view"/> +</tree> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_window_intrinsic_size.xhtml b/toolkit/content/tests/chrome/test_window_intrinsic_size.xhtml new file mode 100644 index 0000000000..451d2f0cce --- /dev/null +++ b/toolkit/content/tests/chrome/test_window_intrinsic_size.xhtml @@ -0,0 +1,43 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<window title="Window Open Test" + onload="runTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> +<script class="testbody" type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + function openWindow(features = "") { + return window.browsingContext.topChromeWindow + .openDialog("window_intrinsic_size.xhtml", + "", "chrome,dialog=no,all," + features); + } + + function checkWindowSize(win, width, height, msg) { + is(win.innerWidth, width, "width should match " + msg); + is(win.innerHeight, height, "height should match " + msg); + } + + async function runTest() { + let win = openWindow(); + await SimpleTest.promiseFocus(win); + checkWindowSize(win, 300, 500, "with width attribute specified"); + + win = openWindow("width=400"); + await SimpleTest.promiseFocus(win); + checkWindowSize(win, 400, 500, "with width feature specified"); + + SimpleTest.finish(); + } +]]></script> +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> +</window> diff --git a/toolkit/content/tests/chrome/window_browser_drop.xhtml b/toolkit/content/tests/chrome/window_browser_drop.xhtml new file mode 100644 index 0000000000..cb23ef74de --- /dev/null +++ b/toolkit/content/tests/chrome/window_browser_drop.xhtml @@ -0,0 +1,249 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Browser Drop Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<script> +<![CDATA[ + +const { ContentTask } = ChromeUtils.importESModule( + "resource://testing-common/ContentTask.sys.mjs" +); + +function dropOnRemoteBrowserAsync(browser, data, shouldExpectStateChange) { + ContentTask.setTestScope(window); // Need this so is/isnot/ok are available inside the contenttask + return ContentTask.spawn(browser, {data, shouldExpectStateChange}, async function({data, shouldExpectStateChange}) { + if (!content.document.documentElement) { + // Wait until the testing document gets loaded. + await new Promise(resolve => { + let onload = function() { + content.window.removeEventListener("load", onload); + resolve(); + }; + content.window.addEventListener("load", onload); + }); + } + + let dataTransfer = new content.DataTransfer(); + for (let i = 0; i < data.length; i++) { + let types = data[i]; + for (let j = 0; j < types.length; j++) { + dataTransfer.mozSetDataAt(types[j].type, types[j].data, i); + } + } + let event = content.document.createEvent("DragEvent"); + event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0, + false, false, false, false, 0, null, dataTransfer); + content.document.body.dispatchEvent(event); + + let links = []; + try { + links = Services.droppedLinkHandler.dropLinks(event, true); + } catch (ex) { + if (shouldExpectStateChange) { + ok(false, "Should not have gotten an exception from the dropped link handler, but got: " + ex); + console.error(ex); + } + } + + return links; + }); +} + +async function expectLink(browser, expectedLinks, data, testid, onbody=false) { + let lastLinks = []; + let lastLinksPromise = new Promise(resolve => { + browser.droppedLinkHandler = function(event, links) { + info(`droppedLinkHandler called, received links ${JSON.stringify(links)}`); + if (!expectedLinks.length) { + ok(false, `droppedLinkHandler called for ${JSON.stringify(links)} which we didn't expect.`); + } + lastLinks = links; + resolve(links); + }; + }); + + function dropOnBrowserSync() { + let dropEl = onbody ? browser.contentDocument.body : browser; + synthesizeDrop(dropEl, dropEl, data, null, dropEl.ownerGlobal); + } + let links; + if (browser.isRemoteBrowser) { + let remoteLinks = await dropOnRemoteBrowserAsync(browser, data, expectedLinks.length); + is(remoteLinks.length, expectedLinks.length, testid + " remote links length"); + for (let i = 0, length = remoteLinks.length; i < length; i++) { + is(remoteLinks[i].url, expectedLinks[i].url, testid + "[" + i + "] remote link"); + is(remoteLinks[i].name, expectedLinks[i].name, testid + "[" + i + "] remote name"); + } + + if (!expectedLinks.length) { + // There is no way to check if nothing happens asynchronously. + return; + } + + links = await lastLinksPromise; + } else { + dropOnBrowserSync(); + links = lastLinks; + } + + is(links.length, expectedLinks.length, testid + " links length"); + for (let i = 0, length = links.length; i < length; i++) { + is(links[i].url, expectedLinks[i].url, testid + "[" + i + "] link"); + is(links[i].name, expectedLinks[i].name, testid + "[" + i + "] name"); + } +}; + +async function dropLinksOnBrowser(browser, type) { + // Dropping single text/plain item with single link should open single + // page. + await expectLink(browser, + [ { url: "http://www.mozilla.org/", + name: "http://www.mozilla.org/" } ], + [ [ { type: "text/plain", + data: "http://www.mozilla.org/" } ] ], + "text/plain drop on browser " + type); + + // Dropping single text/plain item with multiple links should open + // multiple pages. + await expectLink(browser, + [ { url: "http://www.mozilla.org/", + name: "http://www.mozilla.org/" }, + { url: "http://www.example.com/", + name: "http://www.example.com/" } ], + [ [ { type: "text/plain", + data: "http://www.mozilla.org/\nhttp://www.example.com/" } ] ], + "text/plain with 2 URLs drop on browser " + type); + + // Dropping sinlge unsupported type item should not open anything. + await expectLink(browser, + [], + [ [ { type: "text/link", + data: "http://www.mozilla.org/" } ] ], + "text/link drop on browser " + type); + + // Dropping single text/uri-list item with single link should open single + // page. + await expectLink(browser, + [ { url: "http://www.example.com/", + name: "http://www.example.com/" } ], + [ [ { type: "text/uri-list", + data: "http://www.example.com/" } ] ], + "text/uri-list drop on browser " + type); + + // Dropping single text/uri-list item with multiple links should open + // multiple pages. + await expectLink(browser, + [ { url: "http://www.example.com/", + name: "http://www.example.com/" }, + { url: "http://www.mozilla.org/", + name: "http://www.mozilla.org/" }], + [ [ { type: "text/uri-list", + data: "http://www.example.com/\nhttp://www.mozilla.org/" } ] ], + "text/uri-list with 2 URLs drop on browser " + type); + + // Name in text/x-moz-url should be handled. + await expectLink(browser, + [ { url: "http://www.example.com/", + name: "Example.com" } ], + [ [ { type: "text/x-moz-url", + data: "http://www.example.com/\nExample.com" } ] ], + "text/x-moz-url drop on browser " + type); + + await expectLink(browser, + [ { url: "http://www.mozilla.org/", + name: "Mozilla.org" }, + { url: "http://www.example.com/", + name: "Example.com" } ], + [ [ { type: "text/x-moz-url", + data: "http://www.mozilla.org/\nMozilla.org\nhttp://www.example.com/\nExample.com" } ] ], + "text/x-moz-url with 2 URLs drop on browser " + type); + + // Dropping single item with multiple types should open single page. + await expectLink(browser, + [ { url: "http://www.example.org/", + name: "Example.com" } ], + [ [ { type: "text/plain", + data: "http://www.mozilla.org/" }, + { type: "text/x-moz-url", + data: "http://www.example.org/\nExample.com" } ] ], + "text/plain and text/x-moz-url drop on browser " + type); + + // Dropping javascript or data: URLs should fail: + await expectLink(browser, + [], + [ [ { type: "text/plain", + data: "javascript:'bad'" } ] ], + "text/plain javascript url drop on browser " + type); + await expectLink(browser, + [], + [ [ { type: "text/plain", + data: "jAvascript:'also bad'" } ] ], + "text/plain mixed-case javascript url drop on browser " + type); + await expectLink(browser, + [], + [ [ { type: "text/plain", + data: "data:text/html,bad" } ] ], + "text/plain data url drop on browser " + type); + + // Dropping a chrome url should fail as we don't have a source node set, + // defaulting to a source of file:/// + await expectLink(browser, + [], + [ [ { type: "text/x-moz-url", + data: "chrome://browser/content/browser.xhtml" } ] ], + "text/x-moz-url chrome url drop on browser " + type); + + if (browser.type == "content") { + await SpecialPowers.spawn(browser, [], function() { + content.window.stopMode = true; + }); + + // stopPropagation should not prevent the browser link handling from occuring + await expectLink(browser, + [ { url: "http://www.mozilla.org/", + name: "http://www.mozilla.org/" } ], + [ [ { type: "text/uri-list", + data: "http://www.mozilla.org/" } ] ], + "text/x-moz-url drop on browser with stopPropagation drop event", true); + + await SpecialPowers.spawn(browser, [], function() { + content.window.cancelMode = true; + }); + + // Canceling the event, however, should prevent the link from being handled. + await expectLink(browser, + [], + [ [ { type: "text/uri-list", data: "http://www.mozilla.org/" } ] ], + "text/x-moz-url drop on browser with cancelled drop event", true); + } +} + +function info(msg) { window.arguments[0].SimpleTest.info(msg); } +function is(l, r, n) { window.arguments[0].SimpleTest.is(l,r,n); } +function ok(v, n) { window.arguments[0].SimpleTest.ok(v,n); } + +]]> +</script> + +<html:style> + /* FIXME: This is just preserving behavior from before bug 1656081. + * Without this there's one subtest that fails, but the browser + * elements were zero-sized before so... */ + browser { + min-width: 0; + min-height: 0; + } +</html:style> + +<browser id="chromechild" src="about:blank"/> +<browser id="contentchild" type="content" style="width: 100px; height: 100px" + src="data:text/html,<html draggable='true'><body draggable='true' style='width: 100px; height: 100px;' ondragover='event.preventDefault()' ondrop='if (window.stopMode) event.stopPropagation(); if (window.cancelMode) event.preventDefault();'></body></html>"/> +<browser id="remote-contentchild" type="content" remote="true" style="width: 100px; height: 100px" + src="data:text/html,<html draggable='true'><body draggable='true' style='width: 100px; height: 100px;' ondragover='event.preventDefault()' ondrop='if (window.stopMode) event.stopPropagation(); if (window.cancelMode) event.preventDefault();'></body></html>"/> +</window> diff --git a/toolkit/content/tests/chrome/window_chromemargin.xhtml b/toolkit/content/tests/chrome/window_chromemargin.xhtml new file mode 100644 index 0000000000..81bcba62fe --- /dev/null +++ b/toolkit/content/tests/chrome/window_chromemargin.xhtml @@ -0,0 +1,69 @@ +<?xml version="1.0"?> +<!-- 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/. --> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="window" title="Subframe Origin Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> +chrome margins rock! +<script> + +// Tests parsing of the chrome margin attrib on a window. + +function ok(condition, message) { + window.arguments[0].SimpleTest.ok(condition, message); +} + +function doSingleTest(param) +{ + var exception = null; + try { + document.documentElement.removeAttribute("chromemargin"); + document.documentElement.setAttribute("chromemargin", param); + ok(document. + documentElement. + getAttribute("chromemargin") == param, "couldn't set/get chromemargin?"); + } catch (ex) { + exception = ex; + } + ok(!exception, "failed for param:'" + param + "'"); + return true; +} + +function runTests() +{ + var doc = document.documentElement; + + // make sure we can set and get + doc.setAttribute("chromemargin", "0,0,0,0"); + ok(doc.getAttribute("chromemargin") == "0,0,0,0", "couldn't set/get chromemargin?"); + doc.setAttribute("chromemargin", "-1,-1,-1,-1"); + ok(doc.getAttribute("chromemargin") == "-1,-1,-1,-1", "couldn't set/get chromemargin?"); + + // test remove + doc.removeAttribute("chromemargin"); + ok(doc.getAttribute("chromemargin") == "", "couldn't remove chromemargin?"); + + // we already test these really well in a c++ test in widget + doSingleTest("1,2,3,4"); + doSingleTest("-2,-2,-2,-2"); + doSingleTest("1,1,1,1"); + doSingleTest(""); + doSingleTest("12123123"); + doSingleTest("0,-1,-1,-1"); + doSingleTest("-1,0,-1,-1"); + doSingleTest("-1,-1,0,-1"); + doSingleTest("-1,-1,-1,0"); + doSingleTest("1234567890,1234567890,1234567890,1234567890"); + doSingleTest("-1,-1,-1,-1"); + + window.arguments[0].SimpleTest.finish(); + window.close(); +} + +window.arguments[0].SimpleTest.waitForFocus(runTests, window); + +</script> +</window> diff --git a/toolkit/content/tests/chrome/window_cursorsnap_dialog.xhtml b/toolkit/content/tests/chrome/window_cursorsnap_dialog.xhtml new file mode 100644 index 0000000000..d5c0e2753e --- /dev/null +++ b/toolkit/content/tests/chrome/window_cursorsnap_dialog.xhtml @@ -0,0 +1,104 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<window title="Cursor snapping test" + width="600" height="600" + onload="onload();" + onunload="onunload();" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<dialog id="dialog" + buttons="accept,cancel"> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +function ok(aCondition, aMessage) +{ + window.arguments[0].SimpleTest.ok(aCondition, aMessage); +} + +function is(aLeft, aRight, aMessage) +{ + window.arguments[0].SimpleTest.is(aLeft, aRight, aMessage); +} + +function isnot(aLeft, aRight, aMessage) +{ + window.arguments[0].SimpleTest.isnot(aLeft, aRight, aMessage); +} + +function canRetryTest() +{ + return window.arguments[0].canRetryTest(); +} + +function getTimeoutTime() +{ + return window.arguments[0].getTimeoutTime(); +} + +var gTimer; +var gRetry; + +function finishByTimeout() +{ + var button = document.getElementById("dialog").getButton("accept"); + if (button.disabled) { + ok(true, "cursor is NOT snapped to the disabled button (dialog)"); + } else if (button.hidden) { + ok(true, "cursor is NOT snapped to the hidden button (dialog)"); + } else if (!canRetryTest()) { + ok(false, "cursor is NOT snapped to the default button (dialog)"); + } else { + // otherwise, this may be unexpected timeout, we should retry the test. + gRetry = true; + } + finish(); +} + +function finish() +{ + window.close(); +} + +function onMouseMove(aEvent) +{ + var button = document.getElementById("dialog").getButton("accept"); + if (button.disabled) + ok(false, "cursor IS snapped to the disabled button (dialog)"); + else if (button.hidden) + ok(false, "cursor IS snapped to the hidden button (dialog)"); + else + ok(true, "cursor IS snapped to the default button (dialog)"); + clearTimeout(gTimer); + finish(); +} + +function onload() +{ + var button = document.getElementById("dialog").getButton("accept"); + button.addEventListener("mousemove", onMouseMove); + + if (window.arguments[0].gDisable) { + button.disabled = true; + } + if (window.arguments[0].gHidden) { + button.hidden = true; + } + gRetry = false; + gTimer = setTimeout(finishByTimeout, getTimeoutTime()); +} + +function onunload() +{ + if (gRetry) { + window.arguments[0].retryCurrentTest(); + } else { + window.arguments[0].runNextTest(); + } +} + +]]> +</script> + +</dialog> +</window> diff --git a/toolkit/content/tests/chrome/window_cursorsnap_wizard.xhtml b/toolkit/content/tests/chrome/window_cursorsnap_wizard.xhtml new file mode 100644 index 0000000000..661e9c4e65 --- /dev/null +++ b/toolkit/content/tests/chrome/window_cursorsnap_wizard.xhtml @@ -0,0 +1,109 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<wizard title="Cursor snapping test" id="wizard" + width="600" height="600" + onload="onload();" + onunload="onunload();" + buttons="accept,cancel" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <wizardpage> + <label value="first page"/> + </wizardpage> + + <wizardpage> + <label value="second page"/> + </wizardpage> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +function ok(aCondition, aMessage) +{ + window.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage); +} + +function is(aLeft, aRight, aMessage) +{ + window.opener.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage); +} + +function isnot(aLeft, aRight, aMessage) +{ + window.opener.wrappedJSObject.SimpleTest.isnot(aLeft, aRight, aMessage); +} + +function canRetryTest() +{ + return window.opener.wrappedJSObject.canRetryTest(); +} + +function getTimeoutTime() +{ + return window.opener.wrappedJSObject.getTimeoutTime(); +} + +var gTimer; +var gRetry = false; + +function finishByTimeout() +{ + var button = document.getElementById("wizard").getButton("next"); + if (button.disabled) { + ok(true, "cursor is NOT snapped to the disabled button (wizard)"); + } else if (button.hidden) { + ok(true, "cursor is NOT snapped to the hidden button (wizard)"); + } else if (!canRetryTest()) { + ok(false, "cursor is NOT snapped to the default button (wizard)"); + } else { + // otherwise, this may be unexpected timeout, we should retry the test. + gRetry = true; + } + finish(); +} + +function finish() +{ + window.close(); +} + +function onMouseMove() +{ + var button = document.getElementById("wizard").getButton("next"); + if (button.disabled) + ok(false, "cursor IS snapped to the disabled button (wizard)"); + else if (button.hidden) + ok(false, "cursor IS snapped to the hidden button (wizard)"); + else + ok(true, "cursor IS snapped to the default button (wizard)"); + clearTimeout(gTimer); + finish(); +} + +function onload() +{ + var button = document.getElementById("wizard").getButton("next"); + button.addEventListener("mousemove", onMouseMove); + + if (window.opener.wrappedJSObject.gDisable) { + button.disabled = true; + } + if (window.opener.wrappedJSObject.gHidden) { + button.hidden = true; + } + gTimer = setTimeout(finishByTimeout, getTimeoutTime()); +} + +function onunload() +{ + if (gRetry) { + window.opener.wrappedJSObject.retryCurrentTest(); + } else { + window.opener.wrappedJSObject.runNextTest(); + } +} + +]]> +</script> + +</wizard> diff --git a/toolkit/content/tests/chrome/window_intrinsic_size.xhtml b/toolkit/content/tests/chrome/window_intrinsic_size.xhtml new file mode 100644 index 0000000000..cae3d78594 --- /dev/null +++ b/toolkit/content/tests/chrome/window_intrinsic_size.xhtml @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<window title="Window Open Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="300" + style="min-height: 500px"> +</window> diff --git a/toolkit/content/tests/chrome/window_keys.xhtml b/toolkit/content/tests/chrome/window_keys.xhtml new file mode 100644 index 0000000000..77a098ef0b --- /dev/null +++ b/toolkit/content/tests/chrome/window_keys.xhtml @@ -0,0 +1,205 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Key Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<script> +<![CDATA[ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +SimpleTest.waitForExplicitFinish(); + +var gExpected = null; + + +const kIsWin = AppConstants.platform == "win"; +const kIsMac = AppConstants.platform == "macosx"; + +// Only on Windows, metaKey state is ignored when there is no shortcut key handler +// which exactly matches with metaKey state. Therefore the following tests +// checks kIsWin in some cases which has metaKey. +var keysToTest = [ + ["k-v", "V", { } ], + ["", "V", { shiftKey: true } ], + ["k-v-scy", "V", { ctrlKey: true } ], + ["", "V", { altKey: true } ], + [kIsWin ? "k-v" : "", "V", { metaKey: true } ], + ["k-v-scy", "V", { shiftKey: true, ctrlKey: true } ], + ["", "V", { shiftKey: true, ctrlKey: true, altKey: true } ], + ["k-e-y", "E", { } ], + ["", "E", { shiftKey: true } ], + ["", "E", { ctrlKey: true } ], + ["", "E", { altKey: true } ], + [kIsWin ? "k-e-y" : "", "E", { metaKey: true } ], + ["k-d-a", "D", { altKey: true } ], + ["k-8-m", "8", { metaKey: true } ], + ["", "B", {} ], + ["k-c-scaym", "C", { metaKey: true } ], + ["k-c-scaym", "C", { shiftKey: true, ctrlKey: true, altKey: true, metaKey: true } ], + ["", "V", { shiftKey: true, ctrlKey: true, altKey: true } ], + ["k-h-l", "H", { accelKey: true } ], + [kIsWin || kIsMac ? "k-h-l" : "", "H", { accelKey: true, metaKey: true } ], +// ["k-j-s", "J", { accessKey: true } ], + ["", "T", { } ], + ["k-g-c", "G", { ctrlKey: true } ], + ["k-g-cm", "G", { ctrlKey: true, metaKey: true } ], + ["scommand", "Y", { } ], + ["", "U", { } ], + ["k-z-c", "Z", { ctrlKey: true } ], +]; + +function runTest() +{ + let nonInlineKeyFired = false; + document.getElementById("k-z-c").addEventListener("command", event => { + nonInlineKeyFired = true; + checkKey(event); + }); + + iterateKeys(true, "normal"); + + ok(nonInlineKeyFired, "non-inline command listener fired"); + + var keyset = document.getElementById("keyset"); + keyset.setAttribute("disabled", "true"); + iterateKeys(false, "disabled"); + + keyset = document.getElementById("keyset"); + keyset.removeAttribute("disabled"); + iterateKeys(true, "reenabled"); + + keyset.remove(); + iterateKeys(false, "removed"); + + document.documentElement.appendChild(keyset); + iterateKeys(true, "appended"); + + var accelText = menuitem => menuitem.getAttribute("acceltext").toLowerCase(); + + $("menubutton").open = true; + + // now check if a menu updates its accelerator text when a key attribute is changed + var menuitem1 = $("menuitem1"); + ok(accelText(menuitem1).includes("d"), "menuitem1 accelText before"); + if (kIsWin) { + ok(accelText(menuitem1).includes("alt"), "menuitem1 accelText modifier before"); + } + + menuitem1.setAttribute("key", "k-s-c"); + ok(accelText(menuitem1).includes("s"), "menuitem1 accelText after"); + if (kIsWin) { + ok(accelText(menuitem1).includes("ctrl"), "menuitem1 accelText modifier after"); + } + + menuitem1.setAttribute("acceltext", "custom"); + is(accelText(menuitem1), "custom", "menuitem1 accelText set custom"); + menuitem1.removeAttribute("acceltext"); + ok(accelText(menuitem1).includes("s"), "menuitem1 accelText remove"); + if (kIsWin) { + ok(accelText(menuitem1).includes("ctrl"), "menuitem1 accelText modifier remove"); + } + + var menuitem2 = $("menuitem2"); + is(accelText(menuitem2), "", "menuitem2 accelText before"); + menuitem2.setAttribute("key", "k-s-c"); + ok(accelText(menuitem2).includes("s"), "menuitem2 accelText before"); + if (kIsWin) { + ok(accelText(menuitem2).includes("ctrl"), "menuitem2 accelText modifier before"); + } + + menuitem2.setAttribute("key", "k-h-l"); + ok(accelText(menuitem2).includes("h"), "menuitem2 accelText after"); + if (kIsWin) { + ok(accelText(menuitem2).includes("ctrl"), "menuitem2 accelText modifier after"); + } + + menuitem2.removeAttribute("key"); + is(accelText(menuitem2), "", "menuitem2 accelText after remove"); + + $("menubutton").open = false; + + window.close(); + window.arguments[0].SimpleTest.finish(); +} + +function iterateKeys(enabled, testid) +{ + for (var k = 0; k < keysToTest.length; k++) { + gExpected = keysToTest[k]; + var expectedKey = gExpected[0]; + if (!gExpected[2].accessKey || !navigator.platform.includes("Mac")) { + synthesizeKey(gExpected[1], gExpected[2]); + ok((enabled && expectedKey) || expectedKey == "k-d-a" ? + !gExpected : gExpected, testid + " key step " + (k + 1)); + } + } +} + +function checkKey(event) +{ + // the first element of the gExpected array holds the id of the <key> element + // that was expected. If this is empty, a handler wasn't expected to be called + if (gExpected[0]) + is(event.originalTarget.id, gExpected[0], "key " + gExpected[1]); + else + is("key " + event.originalTarget.id + " was activated", "", "key " + gExpected[1]); + gExpected = null; +} + +function is(l, r, n) { window.arguments[0].SimpleTest.is(l,r,n); } +function ok(v, n) { window.arguments[0].SimpleTest.ok(v,n); } + +SimpleTest.waitForFocus(runTest); + +]]> +</script> + +<command id="scommand" oncommand="checkKey(event)"/> +<command id="scommand-disabled" disabled="true"/> + +<keyset id="keyset"> + <key id="k-v" key="v" oncommand="checkKey(event)"/> + <key id="k-v-scy" key="v" modifiers="shift any control" oncommand="checkKey(event)"/> + <key id="k-e-y" key="e" modifiers="any" oncommand="checkKey(event)"/> + <key id="k-8-m" key="8" modifiers="meta" oncommand="checkKey(event)"/> + <key id="k-c-scaym" key="c" modifiers="shift control alt any meta" oncommand="checkKey(event)"/> + <key id="k-h-l" key="h" modifiers="accel" oncommand="checkKey(event)"/> + <key id="k-j-s" key="j" modifiers="access" oncommand="checkKey(event)"/> + <key id="k-t-y" disabled="true" key="t" oncommand="checkKey(event)"/> + <key id="k-g-c" key="g" modifiers="control" oncommand="checkKey(event)"/> + <key id="k-g-cm" key="g" modifiers="control meta" oncommand="checkKey(event)"/> + <key id="k-y" key="y" command="scommand"/> + <key id="k-u" key="u" command="scommand-disabled"/> + <key id="k-z-c" key="z" modifiers="control"/> +</keyset> + +<keyset id="keyset2"> + <key id="k-d-a" key="d" modifiers="alt" oncommand="checkKey(event)"/> + <key id="k-s-c" key="s" modifiers="control" oncommand="checkKey(event)"/> +</keyset> + +<button id="menubutton" label="Menu" type="menu"> + <menupopup> + <menuitem id="menuitem1" label="Item 1" key="k-d-a"/> + <menuitem id="menuitem2" label="Item 2"/> + </menupopup> +</button> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/window_largemenu.xhtml b/toolkit/content/tests/chrome/window_largemenu.xhtml new file mode 100644 index 0000000000..d84b045e78 --- /dev/null +++ b/toolkit/content/tests/chrome/window_largemenu.xhtml @@ -0,0 +1,430 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Large Menu Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<!-- + This test checks that a large menu is displayed with arrow buttons + and is on the screen. + --> + +<script> +<![CDATA[ + +var gOverflowed = false, gUnderflowed = false; +var gContextMenuTests = false; +var gScreenY = -1; +var gTestIndex = 0; +var gTests = ["open normal", "open when bottom would overlap", "open with scrolling", + "open after scrolling", "open small again", + "menu movement", "panel movement", + "context menu enough space below", + "context menu more space above", + "context menu too big either side", + "context menu larger than screen", + "context menu flips horizontally on osx"]; +function getScreenXY(element) +{ + var screenX, screenY; + var mouseFn = function(event) { + screenX = event.screenX - 1; + screenY = event.screenY - 1; + } + + // a hacky way to get the screen position of an element without using the box object + window.addEventListener("mousedown", mouseFn); + synthesizeMouse(element, 1, 1, { }); + window.removeEventListener("mousedown", mouseFn); + + return [screenX, screenY]; +} + +function hidePopup() { + window.requestAnimationFrame( + function() { + setTimeout( + function() { + document.getElementById("popup").hidePopup(); + }, 0); + }); +} + +function runTests() +{ + [, gScreenY] = getScreenXY(document.documentElement); + nextTest(); +} + +function nextTest() +{ + gOverflowed = false; gUnderflowed = false; + + var y = screen.height; + if (gTestIndex == 1) // open with bottom overlap test: + y -= 100; + else + y /= 2; + + var popup = document.getElementById("popup"); + if (gTestIndex == 2) { + // add some more menuitems so that scrolling will be necessary + var moreItemCount = Math.round(screen.height / popup.firstChild.getBoundingClientRect().height); + for (var t = 1; t <= moreItemCount; t++) { + var menu = document.createXULElement("menuitem"); + menu.setAttribute("label", "More" + t); + popup.appendChild(menu); + } + } + else if (gTestIndex == 4) { + // remove the items added in test 2 above + while (popup.childNodes.length > 15) + popup.removeChild(popup.lastChild); + } + + window.requestAnimationFrame(function() { + setTimeout( + function() { + popup.openPopupAtScreen(100, y, false); + }, 0); + }); +} + +function popupShown() +{ + if (gTests[gTestIndex] == "menu movement") + return testPopupMovement(); + + if (gContextMenuTests) + return contextMenuPopupShown(); + + var popup = document.getElementById("popup"); + var rect = popup.getBoundingClientRect(); + var marginTop = parseFloat(getComputedStyle(popup).marginTop); + var marginBottom = parseFloat(getComputedStyle(popup).marginBottom); + var scrollbox = popup.scrollBox.scrollbox; + var expectedScrollPos = 0; + + info(`${gTests[gTestIndex]}: ${JSON.stringify(rect)} | ${screen.width}x${screen.height} | ${gScreenY}`); + if (gTestIndex == 0) { + // the popup should be in the center of the screen + // note that if the height is odd, the y-offset will have been rounded + // down when we pass the fractional value to openPopupAtScreen above. + is(Math.round(rect.top - marginTop) + gScreenY, Math.floor(screen.height / 2), + gTests[gTestIndex] + " top"); + ok(Math.round(rect.bottom - marginBottom) + gScreenY < screen.height, + gTests[gTestIndex] + " bottom"); + ok(!gOverflowed && !gUnderflowed, gTests[gTestIndex] + " overflow") + } + else if (gTestIndex == 1) { + // the popup was supposed to open 100 pixels from the bottom, but that + // would put it off screen so ... + if (platformIsMac()) { + // On OSX the popup is constrained so it remains within the + // bounds of the screen + ok(Math.round(rect.top) + gScreenY >= screen.top, gTests[gTestIndex] + " top"); + is(Math.round(rect.bottom) + gScreenY, screen.availTop + screen.availHeight, gTests[gTestIndex] + " bottom"); + ok(!gOverflowed && !gUnderflowed, gTests[gTestIndex] + " overflow"); + } + else { + // On other platforms the menu should be flipped to have its bottom + // edge 100 pixels from the bottom + ok(Math.round(rect.top - marginTop) + gScreenY >= screen.top, gTests[gTestIndex] + " top"); + is(Math.round(rect.bottom + marginBottom) + gScreenY, screen.height - 100, + gTests[gTestIndex] + " bottom"); + ok(!gOverflowed && !gUnderflowed, gTests[gTestIndex] + " overflow"); + } + } + else if (gTestIndex == 2) { + // the popup is too large so ensure that it is on screen + ok(Math.round(rect.top - marginTop) + gScreenY >= screen.top, gTests[gTestIndex] + " top"); + ok(Math.round(rect.bottom + marginBottom) + gScreenY <= screen.height, gTests[gTestIndex] + " bottom"); + ok(gOverflowed && !gUnderflowed, gTests[gTestIndex] + " overflow") + + scrollbox.scrollTo(0, 40); + expectedScrollPos = 40; + } + else if (gTestIndex == 3) { + expectedScrollPos = 40; + if (scrollbox.scrollTop != expectedScrollPos) { + // TODO(bug 1815608): This never worked on Wayland, but regressed with flexbox emulation. + todo_is(scrollbox.scrollTop, expectedScrollPos, "menu scroll position after reopening large menu should not reset"); + expectedScrollPos = 0; + } + } + else if (gTestIndex == 4) { + // note that if the height is odd, the y-offset will have been rounded + // down when we pass the fractional value to openPopupAtScreen above. + is(Math.round(rect.top - marginTop) + gScreenY, Math.floor(screen.height / 2), + gTests[gTestIndex] + " top"); + ok(Math.round(rect.bottom) + gScreenY < screen.height, + gTests[gTestIndex] + " bottom"); + ok(!gOverflowed && gUnderflowed, gTests[gTestIndex] + " overflow"); + } + + is(scrollbox.scrollTop, expectedScrollPos, "menu scroll position " + gTests[gTestIndex]) + + return hidePopup(); +} + +function is(l, r, n) { window.arguments[0].SimpleTest.is(l,r,n); } +function ok(v, n) { window.arguments[0].SimpleTest.ok(v,n); } + +var oldx, oldy, waitSteps = 0; +function moveWindowTo(x, y, callback, arg) +{ + if (!waitSteps) { + oldx = window.screenX; + oldy = window.screenY; + window.moveTo(x, y); + + waitSteps++; + setTimeout(moveWindowTo, 100, x, y, callback, arg); + return; + } + + if (window.screenX == oldx && window.screenY == oldy) { + if (waitSteps++ > 10) { + ok(false, "Window never moved properly to " + x + "," + y); + window.arguments[0].SimpleTest.finish(); + window.close(); + } + + setTimeout(moveWindowTo, 100, x, y, callback, arg); + } + else { + waitSteps = 0; + callback(arg); + } +} + +function popupHidden() +{ + gTestIndex++; + if (gTestIndex == gTests.length) { + window.arguments[0].SimpleTest.finish(); + window.close(); + } + else if (gTests[gTestIndex] == "context menu enough space below") { + gContextMenuTests = true; + moveWindowTo(window.screenX, screen.availTop + 10, + () => synthesizeMouse(document.getElementById("label"), 4, 4, { type: "contextmenu", button: 2 })); + } + else if (gTests[gTestIndex] == "menu movement") { + document.getElementById("popup").openPopup( + document.getElementById("label"), "after_start", 0, 0, false, false); + } + else if (gTests[gTestIndex] == "panel movement") { + document.getElementById("panel").openPopup( + document.getElementById("label"), "after_start", 0, 0, false, false); + } + else if (gContextMenuTests) { + contextMenuPopupHidden(); + } + else { + nextTest(); + } +} + +function contextMenuPopupShown() +{ + var popup = document.getElementById("popup"); + var rect = popup.getBoundingClientRect(); + var marginTop = parseFloat(getComputedStyle(popup).marginTop); + var marginLeft = parseFloat(getComputedStyle(popup).marginLeft); + var labelrect = document.getElementById("label").getBoundingClientRect(); + + // Click to open popup in popupHidden() occurs at (4,4) in label's coordinate space + var clickX = 4; + var clickY = 4; + + info(`${gTests[gTestIndex]}: ${JSON.stringify(rect)}`); + + var testPopupAppearedRightOfCursor = true; + switch (gTests[gTestIndex]) { + case "context menu enough space below": + is(rect.top - marginTop, labelrect.top + clickY + (platformIsMac() ? -6 : 2), gTests[gTestIndex] + " top"); + break; + case "context menu more space above": + if (platformIsMac()) { + let screenY; + [, screenY] = getScreenXY(popup); + // Macs constrain their popup menus vertically rather than flip them. + is(screenY, screen.availTop + screen.availHeight - rect.height, gTests[gTestIndex] + " top"); + } else { + is(rect.top + marginTop, labelrect.top + clickY - rect.height - 2, gTests[gTestIndex] + " top"); + } + + break; + case "context menu too big either side": + [, gScreenY] = getScreenXY(document.documentElement); + // compare against the available size as well as the total size, as some + // platforms allow the menu to overlap os chrome and others do not + var pos = (screen.availTop + screen.availHeight - rect.height) - gScreenY - marginTop; + var availPos = (screen.top + screen.height - rect.height) - gScreenY - marginTop; + ok(rect.top == pos || rect.top == availPos, + gTests[gTestIndex] + ` top (${pos}/${availPos})`); + break; + case "context menu larger than screen": + ok(rect.top == -(gScreenY - screen.availTop) + marginTop || rect.top == -(gScreenY - screen.top) + marginTop, + `${gTests[gTestIndex]} top (top = ${rect.top} screenY = ${gScreenY} screenAvailTop = ${screen.availTop} screenTop = ${screen.top})`); + break; + case "context menu flips horizontally on osx": + testPopupAppearedRightOfCursor = false; + if (platformIsMac()) { + is(Math.round(rect.right), labelrect.left + clickX - 1, gTests[gTestIndex] + " right"); + } + break; + } + + if (testPopupAppearedRightOfCursor) { + is(rect.left - marginLeft, labelrect.left + clickX + (platformIsMac() ? 1 : 2), gTests[gTestIndex] + " left"); + } + + hidePopup(); +} + +function contextMenuPopupHidden() +{ + var screenAvailBottom = screen.availTop + screen.availHeight; + + if (gTests[gTestIndex] == "context menu more space above") { + moveWindowTo(window.screenX, screenAvailBottom - 80, nextContextMenuTest, -1); + } + else if (gTests[gTestIndex] == "context menu too big either side") { + moveWindowTo(window.screenX, screenAvailBottom / 2 - 80, nextContextMenuTest, screenAvailBottom / 2 + 120); + } + else if (gTests[gTestIndex] == "context menu larger than screen") { + nextContextMenuTest(screen.availHeight + 80); + } + else if (gTests[gTestIndex] == "context menu flips horizontally on osx") { + var popup = document.getElementById("popup"); + var popupWidth = popup.getBoundingClientRect().width; + moveWindowTo(screen.availLeft + screen.availWidth - popupWidth, 100, nextContextMenuTest, -1); + } +} + +function nextContextMenuTest(desiredHeight) +{ + if (desiredHeight >= 0) { + var popup = document.getElementById("popup"); + var height = popup.getBoundingClientRect().height; + var itemheight = document.getElementById("firstitem").getBoundingClientRect().height; + while (height < desiredHeight) { + var menu = document.createXULElement("menuitem"); + menu.setAttribute("label", "Item"); + popup.appendChild(menu); + height += itemheight; + } + } + + synthesizeMouse(document.getElementById("label"), 4, 4, { type: "contextmenu", button: 2 }); +} + +function testPopupMovement() +{ + var isPanelTest = (gTests[gTestIndex] == "panel movement"); + var popup = document.getElementById(isPanelTest ? "panel" : "popup"); + + var screenX, screenY; + var rect = popup.getBoundingClientRect(); + var marginBottom = parseFloat(getComputedStyle(popup).marginBottom); + var marginLeft = parseFloat(getComputedStyle(popup).marginLeft); + var marginTop = parseFloat(getComputedStyle(popup).marginTop); + + var panelIsTop = SpecialPowers.getBoolPref("ui.panel.default_level_parent"); + var overlapOSChrome = !platformIsMac() && (!isPanelTest || panelIsTop); + popup.moveTo(1, 1); + [screenX, screenY] = getScreenXY(popup); + + var expectedx = 1, expectedy = 1; + if (!overlapOSChrome) { + if (screen.availLeft >= 1) expectedx = screen.availLeft; + if (screen.availTop >= 1) expectedy = screen.availTop; + } + is(screenX, expectedx, gTests[gTestIndex] + " (1, 1) x"); + is(screenY, expectedy, gTests[gTestIndex] + " (1, 1) y"); + + popup.moveTo(100, 8000); + expectedy = (overlapOSChrome ? screen.height + screen.top : screen.availHeight + screen.availTop) - + Math.round(rect.height) - marginBottom; + [screenX, screenY] = getScreenXY(popup); + is(screenX, 100, gTests[gTestIndex] + " (100, 8000) x"); + is(screenY, expectedy, gTests[gTestIndex] + " (100, 8000) y"); + + popup.moveTo(6000, 100); + + expectedx = (overlapOSChrome ? screen.width + screen.left : screen.availWidth + screen.availLeft) - + Math.round(rect.width) - marginLeft; + [screenX, screenY] = getScreenXY(popup); + is(screenX, expectedx, gTests[gTestIndex] + " (6000, 100) x"); + is(screenY, 100, gTests[gTestIndex] + " (6000, 100) y"); + + is(popup.getAttribute("left"), "", gTests[gTestIndex] + " left is empty after moving"); + is(popup.getAttribute("top"), "", gTests[gTestIndex] + " top is empty after moving"); + popup.setAttribute("left", "80"); + popup.setAttribute("top", "82"); + [screenX, screenY] = getScreenXY(popup); + is(screenX, 80, gTests[gTestIndex] + " set left and top x"); + is(screenY, 82, gTests[gTestIndex] + " set left and top y"); + popup.moveTo(95, 98); + [screenX, screenY] = getScreenXY(popup); + is(screenX, 95, gTests[gTestIndex] + " move after set left and top x"); + is(screenY, 98, gTests[gTestIndex] + " move after set left and top y"); + is(popup.getAttribute("left"), "95", gTests[gTestIndex] + " left is set after moving"); + is(popup.getAttribute("top"), "98", gTests[gTestIndex] + " top is set after moving"); + popup.removeAttribute("left"); + popup.removeAttribute("top"); + + popup.moveTo(-1 + marginLeft, -1 + marginTop); + [screenX, screenY] = getScreenXY(popup); + + expectedx = (overlapOSChrome ? screen.left : screen.availLeft) + marginLeft; + expectedy = (overlapOSChrome ? screen.top : screen.availTop) + marginTop; + + is(screenX, expectedx, gTests[gTestIndex] + " move after set left and top x to -1"); + is(screenY, expectedy, gTests[gTestIndex] + " move after set left and top y to -1"); + is(popup.getAttribute("left"), "", gTests[gTestIndex] + " left is not set after moving to -1"); + is(popup.getAttribute("top"), "", gTests[gTestIndex] + " top is not set after moving to -1"); + + popup.hidePopup(); +} + +function platformIsMac() +{ + return navigator.platform.indexOf("Mac") > -1; +} + +window.arguments[0].SimpleTest.waitForFocus(runTests, window); + +]]> +</script> + +<button id="label" label="OK" context="popup"/> +<menupopup id="popup" onpopupshown="popupShown();" onpopuphidden="popupHidden();" + onoverflow="gOverflowed = true" onunderflow="gUnderflowed = true;"> + <menuitem id="firstitem" label="1"/> + <menuitem label="2"/> + <menuitem label="3"/> + <menuitem label="4"/> + <menuitem label="5"/> + <menuitem label="6"/> + <menuitem label="7"/> + <menuitem label="8"/> + <menuitem label="9"/> + <menuitem label="10"/> + <menuitem label="11"/> + <menuitem label="12"/> + <menuitem label="13"/> + <menuitem label="14"/> + <menuitem label="15"/> +</menupopup> + +<panel id="panel" onpopupshown="testPopupMovement();" onpopuphidden="popupHidden();" style="margin: 0; -moz-window-input-region-margin: 0;"> + <button label="OK"/> +</panel> + +</window> diff --git a/toolkit/content/tests/chrome/window_maximized_persist.xhtml b/toolkit/content/tests/chrome/window_maximized_persist.xhtml new file mode 100644 index 0000000000..f7eb695f0f --- /dev/null +++ b/toolkit/content/tests/chrome/window_maximized_persist.xhtml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<window title="Window Open Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + height="300" + width="300" + sizemode="normal" + id="window" + persist="height width sizemode"> +<script type="application/javascript"><![CDATA[ + window.addEventListener("sizemodechange", evt => { + window.arguments[0].postMessage("sizemodechange", "*"); + }); +]]></script> +</window> diff --git a/toolkit/content/tests/chrome/window_maximized_persist_with_no_titlebar.xhtml b/toolkit/content/tests/chrome/window_maximized_persist_with_no_titlebar.xhtml new file mode 100644 index 0000000000..83fede7fae --- /dev/null +++ b/toolkit/content/tests/chrome/window_maximized_persist_with_no_titlebar.xhtml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<window title="Window Open Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + height="300" + width="300" + sizemode="normal" + chromemargin="0,2,2,2" + id="window" + persist="height width sizemode"> +<script type="application/javascript"><![CDATA[ + window.addEventListener("sizemodechange", evt => { + window.arguments[0].postMessage("sizemodechange", "*"); + }); +]]></script> +</window> diff --git a/toolkit/content/tests/chrome/window_navigate_persist.html b/toolkit/content/tests/chrome/window_navigate_persist.html new file mode 100644 index 0000000000..8b45212bed --- /dev/null +++ b/toolkit/content/tests/chrome/window_navigate_persist.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html dir="" + id="persist-window" + width="300" height="300" + persist="screenX screenY width height sizemode"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + </head> + <body> + </body> +</html> diff --git a/toolkit/content/tests/chrome/window_panel.xhtml b/toolkit/content/tests/chrome/window_panel.xhtml new file mode 100644 index 0000000000..e13d15b390 --- /dev/null +++ b/toolkit/content/tests/chrome/window_panel.xhtml @@ -0,0 +1,294 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for panels + --> +<window title="Titlebar" width="200" height="200" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<html:style> +<![CDATA[ + panel, panel::part(content) { + border: 0; + margin: 0; + padding: 0; + } +]]> +</html:style> + +<tree id="tree" seltype="single" width="100" height="100"> + <treecols> + <treecol flex="1"/> + <treecol flex="1"/> + </treecols> + <treechildren id="treechildren"> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + </treechildren> +</tree> + + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var currentTest = null; + +function ok(condition, message) { + window.arguments[0].SimpleTest.ok(condition, message); +} + +function is(left, right, message) { + window.arguments[0].SimpleTest.is(left, right, message); +} + +function test_panels() +{ + checkTreeCoords(); + + addEventListener("popupshowing", popupShowing, false); + addEventListener("popupshown", popupShown, false); + addEventListener("popuphidden", nextTest, false); + nextTest(); +} + +function nextTest() +{ + if (!tests.length) { + window.close(); + window.arguments[0].SimpleTest.finish(); + return; + } + + currentTest = tests.shift(); + var panel = createPanel(currentTest.attrs); + currentTest.test(panel); +} + +function popupShowing(event) +{ + var rect = event.target.getOuterScreenRect(); + ok(!rect.left && !rect.top && !rect.width && !rect.height, + currentTest.testname + " empty rectangle during popupshowing"); +} + +var waitSteps = 0; +function popupShown(event) +{ + var panel = event.target; + + if (waitSteps > 0 && navigator.platform.includes("Linux") && + panel.screenY == 210) { + waitSteps--; + setTimeout(popupShown, 10, event); + return; + } + + currentTest.result(currentTest.testname + " ", panel); + panel.hidePopup(); +} + +function createPanel(attrs) +{ + var panel = document.createXULElement("panel"); + for (var a in attrs) { + panel.setAttribute(a, attrs[a]); + } + + var button = document.createXULElement("button"); + panel.appendChild(button); + button.label = "OK"; + button.setAttribute("style", "appearance: none; border: 0; margin: 0; width: 120px; height: 40px;"); + panel.setAttribute("style", "appearance: none; border: 0; margin: 0;"); + return document.documentElement.appendChild(panel); +} + +function checkTreeCoords() +{ + var tree = $("tree"); + var treechildren = $("treechildren"); + tree.currentIndex = 0; + tree.scrollToRow(0); + synthesizeMouse(treechildren, 10, tree.rowHeight + 2, { }); + is(tree.currentIndex, 1, "tree selection"); + + tree.scrollToRow(2); + synthesizeMouse(treechildren, 10, tree.rowHeight + 2, { }); + is(tree.currentIndex, 3, "tree selection after scroll"); +} + +var tests = [ + { + testname: "normal panel", + attrs: { }, + test(panel) { + var screenRect = panel.getOuterScreenRect(); + is(screenRect.left, 0, this.testname + " screen left before open"); + is(screenRect.top, 0, this.testname + " screen top before open"); + is(screenRect.width, 0, this.testname + " screen width before open"); + is(screenRect.height, 0, this.testname + " screen height before open"); + + panel.openPopupAtScreen(200, 210); + }, + result(testname, panel) { + var panelrect = panel.getBoundingClientRect(); + is(panelrect.left, 200 - window.mozInnerScreenX, testname + "left"); + is(panelrect.top, 210 - window.mozInnerScreenY, testname + "top"); + is(panelrect.width, 120, testname + "width"); + is(panelrect.height, 40, testname + "height"); + + var screenRect = panel.getOuterScreenRect(); + is(screenRect.left, 200, testname + " screen left"); + is(screenRect.top, 210, testname + " screen top"); + is(screenRect.width, 120, testname + " screen width"); + is(screenRect.height, 40, testname + " screen height"); + } + }, + { + // only noautohide panels support titlebars, so one shouldn't be shown here + testname: "autohide panel with titlebar", + attrs: { titlebar: "normal" }, + test(panel) { + var screenRect = panel.getOuterScreenRect(); + is(screenRect.left, 0, this.testname + " screen left before open"); + is(screenRect.top, 0, this.testname + " screen top before open"); + is(screenRect.width, 0, this.testname + " screen width before open"); + is(screenRect.height, 0, this.testname + " screen height before open"); + + panel.openPopupAtScreen(200, 210); + }, + result(testname, panel) { + var panelrect = panel.getBoundingClientRect(); + is(panelrect.left, 200 - window.mozInnerScreenX, testname + "left"); + is(panelrect.top, 210 - window.mozInnerScreenY, testname + "top"); + is(panelrect.width, 120, testname + "width"); + is(panelrect.height, 40, testname + "height"); + + var screenRect = panel.getOuterScreenRect(); + is(screenRect.left, 200, testname + " screen left"); + is(screenRect.top, 210, testname + " screen top"); + is(screenRect.width, 120, testname + " screen width"); + is(screenRect.height, 40, testname + " screen height"); + } + }, + { + testname: "noautohide panel with titlebar", + attrs: { noautohide: true, titlebar: "normal" }, + test(panel) { + waitSteps = 25; + + var screenRect = panel.getOuterScreenRect(); + is(screenRect.left, 0, this.testname + " screen left before open"); + is(screenRect.top, 0, this.testname + " screen top before open"); + is(screenRect.width, 0, this.testname + " screen width before open"); + is(screenRect.height, 0, this.testname + " screen height before open"); + + panel.openPopupAtScreen(200, 210); + }, + result(testname, panel) { + var panelrect = panel.getBoundingClientRect(); + ok(panelrect.left >= 200 - window.mozInnerScreenX, testname + "left"); + if (!navigator.platform.includes("Linux")) { + ok(panelrect.top >= 210 - window.mozInnerScreenY, testname + "top greater " + panelrect.top + " " + window.mozInnerScreenY); + } + ok(panelrect.top <= 210 - window.mozInnerScreenY + 36, testname + "top less"); + is(panelrect.width, 120, testname + "width"); + is(panelrect.height, 40, testname + "height"); + + var screenRect = panel.getOuterScreenRect(); + if (!navigator.platform.includes("Linux")) { + is(screenRect.left, 200, testname + " screen left"); + is(screenRect.top, 210, testname + " screen top"); + } + ok(screenRect.width >= 120 && screenRect.width <= 140, testname + " screen width"); + ok(screenRect.height >= 40 && screenRect.height <= 118, testname + " screen height"); + + var gotMouseEvent = false; + function mouseMoved(event) + { + is(event.clientY, panelrect.top + 10, + "popup clientY"); + is(event.screenY, panel.screenY + 10, + "popup screenY"); + is(event.originalTarget, panel.firstChild, "popup target"); + gotMouseEvent = true; + } + + panel.addEventListener("mousemove", mouseMoved, true); + synthesizeMouse(panel, 10, 10, { type: "mousemove" }); + ok(gotMouseEvent, "mouse event on panel"); + panel.removeEventListener("mousemove", mouseMoved, true); + + var tree = $("tree"); + tree.currentIndex = 0; + panel.appendChild(tree); + checkTreeCoords(); + } + }, + { + // The panel should be allowed to appear and remain offscreen + testname: "normal panel with flip='none' off-screen", + attrs: { "flip": "none" }, + test(panel) { + panel.openPopup(document.documentElement, "", -100 - window.mozInnerScreenX, -100 - window.mozInnerScreenY, false, false, null); + }, + result(testname, panel) { + var panelrect = panel.getBoundingClientRect(); + is(panelrect.left, -100 - window.mozInnerScreenX, testname + "left"); + is(panelrect.top, -100 - window.mozInnerScreenY, testname + "top"); + is(panelrect.width, 120, testname + "width"); + is(panelrect.height, 40, testname + "height"); + + var screenRect = panel.getOuterScreenRect(); + is(screenRect.left, -100, testname + " screen left"); + is(screenRect.top, -100, testname + " screen top"); + is(screenRect.width, 120, testname + " screen width"); + is(screenRect.height, 40, testname + " screen height"); + } + }, + { + // The panel should be allowed to remain offscreen after moving and it should follow the anchor + testname: "normal panel with flip='none' moved off-screen", + attrs: { "flip": "none" }, + test(panel) { + panel.openPopup(document.documentElement, "", -100 - window.mozInnerScreenX, -100 - window.mozInnerScreenY, false, false, null); + window.moveBy(-50, -50); + }, + result(testname, panel) { + if (navigator.platform.includes("Linux")) { + // The window position doesn't get updated immediately on Linux. + return; + } + var panelrect = panel.getBoundingClientRect(); + is(panelrect.left, -150 - window.mozInnerScreenX, testname + "left"); + is(panelrect.top, -150 - window.mozInnerScreenY, testname + "top"); + is(panelrect.width, 120, testname + "width"); + is(panelrect.height, 40, testname + "height"); + + var screenRect = panel.getOuterScreenRect(); + is(screenRect.left, -150, testname + " screen left"); + is(screenRect.top, -150, testname + " screen top"); + is(screenRect.width, 120, testname + " screen width"); + is(screenRect.height, 40, testname + " screen height"); + } + }, +]; + +window.arguments[0].SimpleTest.waitForFocus(test_panels, window); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/window_panel_anchoradjust.xhtml b/toolkit/content/tests/chrome/window_panel_anchoradjust.xhtml new file mode 100644 index 0000000000..735ecf26b6 --- /dev/null +++ b/toolkit/content/tests/chrome/window_panel_anchoradjust.xhtml @@ -0,0 +1,193 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window width="200" height="200" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<deck id="deck"> + <hbox id="container"> + <button id="anchor" label="Anchor"/> + </hbox> + <button id="anchor3" label="Anchor3"/> +</deck> + +<hbox id="container2"> + <button id="anchor2" label="Anchor2"/> +</hbox> + +<button id="anchor4" label="Anchor4"/> + +<panel id="panel" type="arrow"> + <button label="OK"/> +</panel> + +<menupopup id="menupopup"> + <menuitem label="One"/> + <menuitem id="menuanchor" label="Two"/> + <menuitem label="Three"/> +</menupopup> + +<script><![CDATA[ + + +SimpleTest.waitForExplicitFinish(); + +function next() +{ + return new Promise(r => { + requestAnimationFrame(() => requestAnimationFrame(r)); + }) +} + +function waitForPanel(panel, event) +{ + return new Promise(resolve => { + panel.addEventListener(event, () => { resolve(); }, { once: true }); + }); +} + +function isWithinHalfPixel(a, b, message) +{ + ok(Math.abs(a - b) <= 0.5, `${message}: ${a}, ${b}`); +} + +function getPanelPos(panel) { + let {left, top, bottom, right} = panel.getBoundingClientRect(); + left -= parseFloat(getComputedStyle(panel).marginLeft); + top -= parseFloat(getComputedStyle(panel).marginTop); + bottom += parseFloat(getComputedStyle(panel).marginBottom); + right += parseFloat(getComputedStyle(panel).marginRight); + return {left, top, bottom, right}; +} + +async function runTests() { + let panel = document.getElementById("panel"); + let anchor = document.getElementById("anchor"); + + let popupshown = waitForPanel(panel, "popupshown"); + panel.openPopup(anchor, "after_start"); + info("popupshown"); + await popupshown; + + let anchorrect = anchor.getBoundingClientRect(); + let panelpos = getPanelPos(panel); + let xarrowdiff = panelpos.left - anchorrect.left; + + // When the anchor is moved in some manner, the panel should be adjusted + let popuppositioned = waitForPanel(panel, "popuppositioned"); + document.getElementById("anchor").style.marginLeft = "50px" + info("before popuppositioned"); + await popuppositioned; + info("after popuppositioned"); + + anchorrect = anchor.getBoundingClientRect(); + panelpos = getPanelPos(panel); + isWithinHalfPixel(anchorrect.left, panelpos.left - xarrowdiff, "anchor moved x"); + isWithinHalfPixel(anchorrect.bottom, panelpos.top, "anchor moved y"); + + // moveToAnchor is used to change the anchor + let anchor2 = document.getElementById("anchor2"); + popuppositioned = waitForPanel(panel, "popuppositioned"); + panel.moveToAnchor(anchor2, "after_end"); + info("before popuppositioned 2"); + await popuppositioned; + info("after popuppositioned 2"); + + let anchor2rect = anchor2.getBoundingClientRect(); + panelpos = getPanelPos(panel); + isWithinHalfPixel(anchor2rect.right, panelpos.right + xarrowdiff, "new anchor x"); + isWithinHalfPixel(anchor2rect.bottom, panelpos.top, "new anchor y"); + + // moveToAnchor is used to change the anchor with an x and y offset + popuppositioned = waitForPanel(panel, "popuppositioned"); + panel.moveToAnchor(anchor2, "after_end", 7, 9); + await popuppositioned; + + anchor2rect = anchor2.getBoundingClientRect(); + panelpos = getPanelPos(panel); + isWithinHalfPixel(anchor2rect.right + 7, panelpos.right + xarrowdiff, "new anchor with offset x"); + isWithinHalfPixel(anchor2rect.bottom + 9, panelpos.top, "new anchor with offset y"); + + // When the container of the anchor is collapsed, the panel should be hidden. + let popuphidden = waitForPanel(panel, "popuphidden"); + anchor2.parentNode.collapsed = true; + await popuphidden; + + popupshown = waitForPanel(panel, "popupshown"); + panel.openPopup(anchor, "after_start"); + await popupshown; + + // When the deck containing the anchor changes to a different page, the panel should be hidden. + popuphidden = waitForPanel(panel, "popuphidden"); + document.getElementById("deck").selectedIndex = 1; + await popuphidden; + + let anchor3 = document.getElementById("anchor3"); + popupshown = waitForPanel(panel, "popupshown"); + panel.openPopup(anchor3, "after_start"); + await popupshown; + + // When the anchor is hidden; the panel should be hidden. + popuphidden = waitForPanel(panel, "popuphidden"); + anchor3.parentNode.hidden = true; + await popuphidden; + + // When the panel is anchored to an element in a popup, the panel should + // also be hidden when that popup is hidden. + let menupopup = document.getElementById("menupopup"); + popupshown = waitForPanel(menupopup, "popupshown"); + menupopup.openPopupAtScreen(200, 200); + await popupshown; + + popupshown = waitForPanel(panel, "popupshown"); + panel.openPopup(document.getElementById("menuanchor"), "after_start"); + await popupshown; + + popuphidden = waitForPanel(panel, "popuphidden"); + let menupopuphidden = waitForPanel(menupopup, "popuphidden"); + menupopup.hidePopup(); + await popuphidden; + await menupopuphidden; + + // The panel should no longer follow anchors. + panel.setAttribute("followanchor", "false"); + + let anchor4 = document.getElementById("anchor4"); + popupshown = waitForPanel(panel, "popupshown"); + panel.openPopup(anchor4, "after_start"); + await popupshown; + + let anchor4rect = anchor4.getBoundingClientRect(); + + anchor4.style.marginLeft = "50px" + await next(); + + panelpos = getPanelPos(panel); + isWithinHalfPixel(anchor4rect.left, panelpos.left - xarrowdiff, "no follow anchor x"); + isWithinHalfPixel(anchor4rect.bottom, panelpos.top, "no follow anchor y"); + + popuphidden = waitForPanel(panel, "popuphidden"); + panel.hidePopup(); + await popuphidden; + + window.close(); + window.arguments[0].SimpleTest.finish(); +} + +function ok(condition, message) { + window.arguments[0].SimpleTest.ok(condition, message); +} + +function is(left, right, message) { + window.arguments[0].SimpleTest.is(left, right, message); +} + +window.arguments[0].SimpleTest.waitForFocus(runTests, window); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/window_panel_focus.xhtml b/toolkit/content/tests/chrome/window_panel_focus.xhtml new file mode 100644 index 0000000000..962e43db58 --- /dev/null +++ b/toolkit/content/tests/chrome/window_panel_focus.xhtml @@ -0,0 +1,132 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Panel Focus Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<checkbox id="b1" label="Item 1"/> + +<!-- Focus should be in this order: 2 6 3 8 1 4 5 7 9 --> +<panel id="panel" norestorefocus="true" onpopupshown="panelShown()" onpopuphidden="panelHidden()"> + <button id="t1" label="Button One"/> + <button id="t2" tabindex="1" label="Button Two" onblur="gButtonBlur++;"/> + <button id="t3" tabindex="2" label="Button Three"/> + <button id="t4" tabindex="0" label="Button Four"/> + <button id="t5" label="Button Five"/> + <button id="t6" tabindex="1" label="Button Six"/> + <button id="t7" label="Button Seven"/> + <button id="t8" tabindex="4" label="Button Eight"/> + <button id="t9" label="Button Nine"/> +</panel> + +<panel id="noautofocusPanel" noautofocus="true" + onpopupshown="noautofocusPanelShown()" onpopuphidden="noautofocusPanelHidden()"> + <html:input id="tb3"/> +</panel> + +<checkbox id="b2" label="Item 2" popup="panel" onblur="gButtonBlur++;"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +var gButtonBlur = 0; + +function showPanel() +{ + // click on the document so that the window has focus + synthesizeMouse(document.documentElement, 1, 1, { }); + + // focus the button + synthesizeKeyExpectEvent("KEY_Tab", {}, $("b1"), "focus", "button focus"); + // tabbing again should skip the popup + synthesizeKeyExpectEvent("KEY_Tab", {}, $("b2"), "focus", "popup skipped in focus navigation"); + + $("panel").openPopup(null, "", 10, 10, false, false); +} + +function panelShown() +{ + // the focus on the button should have been removed when the popup was opened + is(gButtonBlur, 1, "focus removed when popup opened"); + + // press tab numerous times to cycle through the buttons. The t2 button will + // be blurred twice, so gButtonBlur will be 3 afterwards. + synthesizeKeyExpectEvent("KEY_Tab", {}, $("t2"), "focus", "tabindex 1"); + synthesizeKeyExpectEvent("KEY_Tab", {}, $("t6"), "focus", "tabindex 2"); + synthesizeKeyExpectEvent("KEY_Tab", {}, $("t3"), "focus", "tabindex 3"); + synthesizeKeyExpectEvent("KEY_Tab", {}, $("t8"), "focus", "tabindex 4"); + synthesizeKeyExpectEvent("KEY_Tab", {}, $("t1"), "focus", "tabindex 5"); + synthesizeKeyExpectEvent("KEY_Tab", {}, $("t4"), "focus", "tabindex 6"); + synthesizeKeyExpectEvent("KEY_Tab", {}, $("t5"), "focus", "tabindex 7"); + synthesizeKeyExpectEvent("KEY_Tab", {}, $("t7"), "focus", "tabindex 8"); + synthesizeKeyExpectEvent("KEY_Tab", {}, $("t9"), "focus", "tabindex 9"); + synthesizeKeyExpectEvent("KEY_Tab", {}, $("t2"), "focus", "tabindex 10"); + + synthesizeKeyExpectEvent("KEY_Tab", {shiftKey: true}, $("t9"), "focus", "back tabindex 1"); + synthesizeKeyExpectEvent("KEY_Tab", {shiftKey: true}, $("t7"), "focus", "back tabindex 2"); + synthesizeKeyExpectEvent("KEY_Tab", {shiftKey: true}, $("t5"), "focus", "back tabindex 3"); + synthesizeKeyExpectEvent("KEY_Tab", {shiftKey: true}, $("t4"), "focus", "back tabindex 4"); + synthesizeKeyExpectEvent("KEY_Tab", {shiftKey: true}, $("t1"), "focus", "back tabindex 5"); + synthesizeKeyExpectEvent("KEY_Tab", {shiftKey: true}, $("t8"), "focus", "back tabindex 6"); + synthesizeKeyExpectEvent("KEY_Tab", {shiftKey: true}, $("t3"), "focus", "back tabindex 7"); + synthesizeKeyExpectEvent("KEY_Tab", {shiftKey: true}, $("t6"), "focus", "back tabindex 8"); + synthesizeKeyExpectEvent("KEY_Tab", {shiftKey: true}, $("t2"), "focus", "back tabindex 9"); + + is(gButtonBlur, 3, "blur events fired within popup"); + + synthesizeKey("KEY_Escape"); +} + +function ok(condition, message) { + window.arguments[0].SimpleTest.ok(condition, message); +} + +function is(left, right, message) { + window.arguments[0].SimpleTest.is(left, right, message); +} + +function panelHidden() +{ + // closing the popup should have blurred the focused element + is(gButtonBlur, 4, "focus removed when popup closed"); + + // now that the panel is hidden, pressing tab should focus the elements in + // the main window again + synthesizeKeyExpectEvent("KEY_Tab", {}, $("b1"), "focus", "focus after popup closed"); + + $("noautofocusPanel").openPopup(null, "", 10, 10, false, false); +} + +function noautofocusPanelShown() +{ + // with noautofocus="true", the focus should not be removed when the panel is + // opened, so key events should still be fired at the checkbox. + synthesizeKeyExpectEvent(" ", {}, $("b1"), "command", "noautofocus"); + $("noautofocusPanel").hidePopup(); +} + +function noautofocusPanelHidden() +{ + window.close(); + window.arguments[0].SimpleTest.finish(); +} + +window.arguments[0].SimpleTest.waitForFocus(showPanel, window); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/window_popup_anchor.xhtml b/toolkit/content/tests/chrome/window_popup_anchor.xhtml new file mode 100644 index 0000000000..4f8e88035d --- /dev/null +++ b/toolkit/content/tests/chrome/window_popup_anchor.xhtml @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Popup Anchor Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script> +function runTests() +{ + frames[0].openPopup(); +} + +window.arguments[0].SimpleTest.waitForFocus(runTests, window); +</script> + +<spacer style="height: 13px"/> +<button id="outerbutton" label="Button One" style="margin-left: 6px; -moz-appearance: none;"/> +<hbox> + <spacer style="width: 20px"/> + <deck> + <vbox> + <iframe id="frame" style="margin-left: 60px; margin-top: 10px; border-left: 17px solid red; padding-left: 0 !important; padding-top: 3px; width: 250px; height: 80px" + src="frame_popup_anchor.xhtml"/> + </vbox> + </deck> +</hbox> + +</window> diff --git a/toolkit/content/tests/chrome/window_popup_anchoratrect.xhtml b/toolkit/content/tests/chrome/window_popup_anchoratrect.xhtml new file mode 100644 index 0000000000..524a95b643 --- /dev/null +++ b/toolkit/content/tests/chrome/window_popup_anchoratrect.xhtml @@ -0,0 +1,130 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onpopupshown="popupshown(event.target)" onpopuphidden="nextTest()"> + +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<label value="Popup Test"/> + +<menupopup id="popup"> + <menuitem label="One"/> + <menuitem label="Two"/> +</menupopup> + +<panel id="panel" noautohide="true" height="20"> + <label value="OK"/> +</panel> + +<script> +<![CDATA[ + +let menupopup; +function margins(popup) { + let ret = {}; + let cs = getComputedStyle(popup); + for (let side of ["top", "right", "bottom", "left"]) { + ret[side] = parseFloat(cs.getPropertyValue("margin-" + side)); + } + return ret; +} + +let tests = [ + { + test: () => menupopup.openPopupAtScreenRect("after_start", 150, 250, 30, 40), + verify: popup => { + let rect = popup.getOuterScreenRect(); + let margin = margins(popup); + is(rect.left - margin.left, 150, "popup at screen position x"); + is(rect.top - margin.top, 290, "popup at screen position y"); + } + }, + { + test: () => menupopup.openPopupAtScreenRect("after_start", 150, 350, 30, 9000), + verify: popup => { + let rect = popup.getOuterScreenRect(); + let margin = margins(popup); + is(rect.left - margin.left, 150, "flipped popup at screen position x"); + is(rect.bottom + margin.bottom, 350, "flipped popup at screen position y"); + } + }, + { + test: () => menupopup.openPopupAtScreenRect("end_before", 150, 250, 30, 40), + verify: popup => { + let rect = popup.getOuterScreenRect(); + let margin = margins(popup); + is(rect.left - margin.left, 180, "popup at end_before screen position x"); + is(rect.top - margin.top, 250, "popup at end_before screen position y"); + } + }, + { + test: () => $("panel").openPopupAtScreenRect("after_start", 150, 250, 30, 40), + verify: popup => { + let rect = popup.getOuterScreenRect(); + let margin = margins(popup); + is(rect.left - margin.left, 150, "panel at screen position x"); + is(rect.top - margin.top, 290, "panel at screen position y"); + } + }, + { + test: () => $("panel").openPopupAtScreenRect("before_start", 150, 250, 30, 40), + verify: popup => { + let rect = popup.getOuterScreenRect(); + let margin = margins(popup); + is(rect.left - margin.left, 150, "panel at before_start screen position x"); + is(rect.bottom + margin.bottom, 250, "panel at before_start screen position y"); + } + }, +]; + +function runTest(id) +{ + menupopup = $("popup"); + nextTest(); +} + +function nextTest() +{ + if (!tests.length) { + window.close(); + window.arguments[0].SimpleTest.finish(); + return; + } + + tests[0].test(); +} + +function popupshown(popup) +{ + tests[0].verify(popup); + tests.shift(); + popup.hidePopup(); +} + +function is(left, right, message) +{ + window.arguments[0].SimpleTest.is(left, right, message); +} + +function ok(value, message) +{ + window.arguments[0].SimpleTest.ok(value, message); +} + +window.arguments[0].SimpleTest.waitForFocus(runTest, window); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/window_popup_attribute.xhtml b/toolkit/content/tests/chrome/window_popup_attribute.xhtml new file mode 100644 index 0000000000..00c8e5a721 --- /dev/null +++ b/toolkit/content/tests/chrome/window_popup_attribute.xhtml @@ -0,0 +1,45 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Popup Attribute Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script src="popup_shared.js"></script> + <script src="popup_trigger.js"></script> + +<html:style> + menupopup { + margin: 0; + --panel-padding: 0; + -moz-window-input-region-margin: 0; + } +</html:style> + +<script> +window.opener.SimpleTest.waitForFocus(runTests, window); +</script> + +<hbox style="margin-left: 200px; margin-top: 340px;"> + <label id="trigger" popup="thepopup" value="Popup" height="60"/> +</hbox> + +<menupopup id="thepopup"> + <menuitem id="item1" label="First"/> + <menuitem id="item2" label="Main Item"/> + <menuitem id="amenu" label="A Menu" accesskey="M"/> + <menuitem id="item3" label="Third"/> + <menuitem id="one" label="One"/> + <menuitem id="fancier" label="Fancier Menu"/> + <menu id="submenu" label="Only Menu"> + <menupopup id="submenupopup"> + <menuitem id="submenuitem" label="Test Submenu"/> + </menupopup> + </menu> + <menuitem id="other" disabled="true" label="Other Menu"/> + <menuitem id="secondlast" label="Second Last Menu" accesskey="T"/> + <menuitem id="last" label="One Other Menu"/> +</menupopup> + +</window> diff --git a/toolkit/content/tests/chrome/window_popup_button.xhtml b/toolkit/content/tests/chrome/window_popup_button.xhtml new file mode 100644 index 0000000000..f2456ca5a9 --- /dev/null +++ b/toolkit/content/tests/chrome/window_popup_button.xhtml @@ -0,0 +1,45 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Popup Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> + +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> +<script src="popup_shared.js"></script> +<script src="popup_trigger.js"></script> + +<html:style> + menupopup { + margin: 0; + --panel-padding: 0; + -moz-window-input-region-margin: 0; + } +</html:style> + +<script> +window.opener.SimpleTest.waitForFocus(runTests, window); +</script> + +<hbox style="margin-left: 200px; margin-top: 340px;"> + <button id="trigger" type="menu" label="Popup" width="100" height="50"> + <menupopup id="thepopup"> + <menuitem id="item1" label="First"/> + <menuitem id="item2" label="Main Item"/> + <menuitem id="amenu" label="A Menu" accesskey="M"/> + <menuitem id="item3" label="Third"/> + <menuitem id="one" label="One"/> + <menuitem id="fancier" label="Fancier Menu"/> + <menu id="submenu" label="Only Menu"> + <menupopup id="submenupopup"> + <menuitem id="submenuitem" label="Test Submenu"/> + </menupopup> + </menu> + <menuitem id="other" disabled="true" label="Other Menu"/> + <menuitem id="secondlast" label="Second Last Menu" accesskey="T"/> + <menuitem id="last" label="One Other Menu"/> + </menupopup> + </button> +</hbox> + +</window> diff --git a/toolkit/content/tests/chrome/window_popup_preventdefault_chrome.xhtml b/toolkit/content/tests/chrome/window_popup_preventdefault_chrome.xhtml new file mode 100644 index 0000000000..43dffc2a76 --- /dev/null +++ b/toolkit/content/tests/chrome/window_popup_preventdefault_chrome.xhtml @@ -0,0 +1,126 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Popup Prevent Default Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<!-- + This tests checks that preventDefault can be called on a popupshowing + event or popuphiding event to prevent the default behaviour. + --> + +<script> + +var gBlockShowing = true; +var gBlockHiding = true; +var gShownNotAllowed = true; +var gHiddenNotAllowed = true; + +var fm = Services.focus; + +var is = function(l, r, v) { window.arguments[0].SimpleTest.is(l, r, v); } +var isnot = function(l, r, v) { window.arguments[0].SimpleTest.isnot(l, r, v); } + +const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" +); + +async function runTest() +{ + var menu = document.getElementById("menu"); + + is(fm.activeWindow, window, "active window at start"); + is(fm.focusedWindow, window, "focused window at start"); + + is(window.windowState, window.STATE_NORMAL, "window is normal"); + // the minimizing test sometimes fails on Linux so don't test it there + if (navigator.platform.indexOf("Lin") == 0) { + menu.open = true; + return; + } + let promiseSizeModeChange = BrowserTestUtils.waitForEvent( + window, + "sizemodechange" + ); + window.minimize(); + await promiseSizeModeChange; + is(window.windowState, window.STATE_MINIMIZED, "window is minimized"); + + isnot(fm.activeWindow, window, "active window after minimize"); + isnot(fm.focusedWindow, window, "focused window after minimize"); + + menu.open = true; + + setTimeout(runTestAfterMinimize, 0); +} + +async function runTestAfterMinimize() +{ + var menu = document.getElementById("menu"); + is(menu.firstChild.state, "closed", "popup not opened when window minimized"); + + let promiseSizeModeChange = BrowserTestUtils.waitForEvent( + window, + "sizemodechange" + ); + window.restore(); + await promiseSizeModeChange; + is(window.windowState, window.STATE_NORMAL, "window is restored"); + + is(fm.activeWindow, window, "active window after restore"); + is(fm.focusedWindow, window, "focused window after restore"); + + menu.open = true; +} + +function popupShowing(event) +{ + if (gBlockShowing) { + event.preventDefault(); + gBlockShowing = false; + setTimeout(function() { + gShownNotAllowed = false; + document.getElementById("menu").open = true; + }, 3000, true); + } +} + +function popupShown() +{ + window.arguments[0].SimpleTest.ok(!gShownNotAllowed, "popupshowing preventDefault"); + document.getElementById("menu").open = false; +} + +function popupHiding(event) +{ + if (gBlockHiding) { + event.preventDefault(); + gBlockHiding = false; + setTimeout(function() { + gHiddenNotAllowed = false; + document.getElementById("menu").open = false; + }, 3000, true); + } +} + +function popupHidden() +{ + window.arguments[0].SimpleTest.ok(!gHiddenNotAllowed, "popuphiding preventDefault"); + window.arguments[0].SimpleTest.finish(); + window.close(); +} + +window.arguments[0].SimpleTest.waitForFocus(runTest, window); +</script> + +<button id="menu" type="menu" label="Menu"> + <menupopup onpopupshowing="popupShowing(event);" + onpopupshown="popupShown();" + onpopuphiding="popupHiding(event);" + onpopuphidden="popupHidden();"> + <menuitem label="Item"/> + </menupopup> +</button> + + +</window> diff --git a/toolkit/content/tests/chrome/window_preferences.xhtml b/toolkit/content/tests/chrome/window_preferences.xhtml new file mode 100644 index 0000000000..273bd7060e --- /dev/null +++ b/toolkit/content/tests/chrome/window_preferences.xhtml @@ -0,0 +1,70 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<!-- + XUL Widget Test for preferences window +--> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + class="prefwindow" + title="preferences window" + windowtype="test:preferences" + onload="RunTest(window.arguments)"> +<dialog id="window_preferences_dialog" + buttons="accept,cancel"> + <script type="application/javascript" src="chrome://global/content/preferencesBindings.js"/> + <script type="application/javascript"> + <![CDATA[ + /* import-globals-from ../../preferencesBindings.js */ + function RunTest(aArgs) + { + setTimeout(() => { + // run test + aArgs[0](this); + // close dialog + let dialog = document.getElementById("window_preferences_dialog"); + dialog[aArgs[1] ? "acceptDialog" : "cancelDialog"](); + }); + } + + Preferences.addAll([ + // one of each type known to Preference.valueFromPreferences + { id: "tests.static_preference_int", type: "int" }, + { id: "tests.static_preference_bool", type: "bool" }, + { id: "tests.static_preference_string", type: "string" }, + { id: "tests.static_preference_wstring", type: "wstring" }, + { id: "tests.static_preference_unichar", type: "unichar" }, + { id: "tests.static_preference_file", type: "file" }, + ]); + ]]> + </script> + + <vbox id="sample_pane" class="prefpane" label="Sample Prefpane"> + + <!-- one element for each preference type above --> + <hbox> + <label flex="1" value="int"/> + <html:input id="static_element_int" preference="tests.static_preference_int"/> + </hbox> + <hbox> + <label flex="1" value="bool"/> + <checkbox id="static_element_bool" preference="tests.static_preference_bool"/> + </hbox> + <hbox> + <label flex="1" value="string"/> + <html:input id="static_element_string" preference="tests.static_preference_string"/> + </hbox> + <hbox> + <label flex="1" value="wstring"/> + <html:input id="static_element_wstring" preference="tests.static_preference_wstring"/> + </hbox> + <hbox> + <label flex="1" value="unichar"/> + <html:input id="static_element_unichar" preference="tests.static_preference_unichar"/> + </hbox> + <hbox> + <label flex="1" value="file"/> + <html:input id="static_element_file" preference="tests.static_preference_file"/> + </hbox> + </vbox> +</dialog> +</window> diff --git a/toolkit/content/tests/chrome/window_preferences2.xhtml b/toolkit/content/tests/chrome/window_preferences2.xhtml new file mode 100644 index 0000000000..af51f5df34 --- /dev/null +++ b/toolkit/content/tests/chrome/window_preferences2.xhtml @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<!-- + XUL Widget Test for preferences window +--> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + class="prefwindow" + title="pw 2" + windowtype="test:preferences2" + onload="RunTest(window.arguments)"> +<dialog id="window_preferences2_dialog" + buttons="accept,cancel"> + <script type="application/javascript" src="chrome://global/content/preferencesBindings.js"/> + <script type="application/javascript"> + <![CDATA[ + function RunTest(aArgs) + { + // open child + openDialog("window_preferences3.xhtml", "", "modal,centerscreen,resizable=no", {test: aArgs[0], accept: aArgs[1]}); + // close dialog + let dialog = document.getElementById("window_preferences2_dialog"); + dialog[aArgs[1] ? "acceptDialog" : "cancelDialog"](); + } + ]]> + </script> + + <vbox id="sample_pane" class="prefpane" label="Sample Prefpane"/> +</dialog> +</window> diff --git a/toolkit/content/tests/chrome/window_preferences3.xhtml b/toolkit/content/tests/chrome/window_preferences3.xhtml new file mode 100644 index 0000000000..719c66a737 --- /dev/null +++ b/toolkit/content/tests/chrome/window_preferences3.xhtml @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<!-- + XUL Widget Test for preferences window +--> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + class="prefwindow" + title="pw 3" + windowtype="test:preferences3" + onload="RunTest(window.arguments)" + type="child"> +<dialog id="window_preferences3_dialog" + buttons="accept,cancel"> + <script type="application/javascript" src="chrome://global/content/preferencesBindings.js"/> + <script type="application/javascript"> + <![CDATA[ + /* import-globals-from ../../preferencesBindings.js */ + function RunTest(aArgs) + { + // run test + aArgs[0].test(this); + // close dialog + let dialog = document.getElementById("window_preferences3_dialog"); + dialog[aArgs[0].accept ? "acceptDialog" : "cancelDialog"](); + } + + Preferences.addAll([ + // one of each type known to Preference.valueFromPreferences + { id: "tests.static_preference_int", type: "int" }, + { id: "tests.static_preference_bool", type: "bool" }, + { id: "tests.static_preference_string", type: "string" }, + { id: "tests.static_preference_wstring", type: "wstring" }, + { id: "tests.static_preference_unichar", type: "unichar" }, + { id: "tests.static_preference_file", type: "file" }, + ]); + ]]> + </script> + + <vbox id="sample_pane" class="prefpane" label="Sample Prefpane"> + <!-- one element for each preference type above --> + <hbox> + <label flex="1" value="int"/> + <html:input id="static_element_int" preference="tests.static_preference_int"/> + </hbox> + <hbox> + <label flex="1" value="bool"/> + <checkbox id="static_element_bool" preference="tests.static_preference_bool"/> + </hbox> + <hbox> + <label flex="1" value="string"/> + <html:input id="static_element_string" preference="tests.static_preference_string"/> + </hbox> + <hbox> + <label flex="1" value="wstring"/> + <html:input id="static_element_wstring" preference="tests.static_preference_wstring"/> + </hbox> + <hbox> + <label flex="1" value="unichar"/> + <html:input id="static_element_unichar" preference="tests.static_preference_unichar"/> + </hbox> + <hbox> + <label flex="1" value="file"/> + <html:input id="static_element_file" preference="tests.static_preference_file"/> + </hbox> + </vbox> +</dialog> +</window> diff --git a/toolkit/content/tests/chrome/window_preferences_beforeaccept.xhtml b/toolkit/content/tests/chrome/window_preferences_beforeaccept.xhtml new file mode 100644 index 0000000000..8d2e54d54d --- /dev/null +++ b/toolkit/content/tests/chrome/window_preferences_beforeaccept.xhtml @@ -0,0 +1,49 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<!-- + XUL Widget Test for preferences window with beforeaccept +--> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + class="prefwindow" + title="pw beforeaccept" + width="300" height="300" + windowtype="test:preferences" + type="child" + onload="onDialogLoad();"> +<dialog id="beforeaccept_dialog" + buttons="accept,cancel"> + <script type="application/javascript" src="chrome://global/content/preferencesBindings.js"/> + <script type="application/javascript"> + <![CDATA[ + /* import-globals-from ../../preferencesBindings.js */ + function onDialogLoad() { + document.addEventListener("beforeaccept", beforeAccept); + var pref = Preferences.get("tests.beforeaccept.dialogShown"); + pref.value = true; + + // call the onload handler we were passed + window.arguments[0](); + } + + function beforeAccept(event) { + var beforeAcceptPref = window.Preferences.get("tests.beforeaccept.called"); + var oldValue = beforeAcceptPref.value; + beforeAcceptPref.value = true; + + if (!oldValue) { + event.preventDefault(); + } + } + + Preferences.addAll([ + { id: "tests.beforeaccept.called", type: "bool" }, + { id: "tests.beforeaccept.dialogShown", type: "bool" }, + ]); + ]]> + </script> + + <vbox id="sample_pane" class="prefpane" label="Sample Prefpane"> + </vbox> + <label>Test Prefpane</label> +</dialog> +</window> diff --git a/toolkit/content/tests/chrome/window_preferences_commandretarget.xhtml b/toolkit/content/tests/chrome/window_preferences_commandretarget.xhtml new file mode 100644 index 0000000000..52942d9da1 --- /dev/null +++ b/toolkit/content/tests/chrome/window_preferences_commandretarget.xhtml @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<!-- + XUL Widget Test for preferences window. This particular test ensures that + a checkbox with a command attribute properly updates even though the command + event gets retargeted. +--> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + class="prefwindow" + title="pw commandretarget" + windowtype="test:preferences" + onload="RunTest(window.arguments)"> +<dialog id="commandretarget_dialog" + buttons="accept,cancel"> + <script type="application/javascript" src="chrome://global/content/preferencesBindings.js"/> + <script type="application/javascript"> + <![CDATA[ + /* import-globals-from ../../preferencesBindings.js */ + function RunTest(aArgs) + { + aArgs[0](this); + let dialog = document.getElementById("commandretarget_dialog"); + dialog.cancelDialog(); + } + + Preferences.addAll([ + { id: "tests.static_preference_bool", type: "bool" }, + ]); + ]]> + </script> + + <vbox id="sample_pane" class="prefpane" label="Sample Prefpane"> + <commandset> + <command id="cmd_test" preference="tests.static_preference_bool"/> + </commandset> + + <checkbox id="checkbox" label="Enable Option" preference="tests.static_preference_bool" command="cmd_test"/> + </vbox> +</dialog> +</window> diff --git a/toolkit/content/tests/chrome/window_preferences_disabled.xhtml b/toolkit/content/tests/chrome/window_preferences_disabled.xhtml new file mode 100644 index 0000000000..4bb17bbeac --- /dev/null +++ b/toolkit/content/tests/chrome/window_preferences_disabled.xhtml @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<!-- + XUL Widget Test for preferences window. This particular test ensures that + when a preference is disabled, the checkbox disabled and when a preference + is locked, the checkbox can't be enabled. +--> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + class="prefwindow" + title="pw disabled" + windowtype="test:preferences" + onload="RunTest(window.arguments)"> +<dialog id="disabled_dialog" + buttons="accept,cancel"> + <script type="application/javascript" src="chrome://global/content/preferencesBindings.js"/> + <script type="application/javascript"> + <![CDATA[ + /* import-globals-from ../../preferencesBindings.js */ + function RunTest(aArgs) + { + setTimeout(() => { + aArgs[0](this); + let dialog = document.getElementById("disabled_dialog"); + dialog.cancelDialog(); + }); + } + + Preferences.addAll([ + { id: "tests.disabled_preference_bool", type: "bool" }, + { id: "tests.locked_preference_bool", type: "bool" }, + ]); + ]]> + </script> + + <vbox id="sample_pane" class="prefpane" label="Sample Prefpane"> + <checkbox id="disabled_checkbox" label="Disabled" preference="tests.disabled_preference_bool"/> + <checkbox id="locked_checkbox" label="Locked" preference="tests.locked_preference_bool"/> + </vbox> +</dialog> +</window> diff --git a/toolkit/content/tests/chrome/window_preferences_onsyncfrompreference.xhtml b/toolkit/content/tests/chrome/window_preferences_onsyncfrompreference.xhtml new file mode 100644 index 0000000000..6ff07e1620 --- /dev/null +++ b/toolkit/content/tests/chrome/window_preferences_onsyncfrompreference.xhtml @@ -0,0 +1,55 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<!-- 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/. --> +<!-- + XUL Widget Test for preferences window with onsyncfrompreference + This test ensures that onsyncfrompreference handlers are called after all the + values of the corresponding preference element have been set correctly +--> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + class="prefwindow" + title="pw onsyncfrompreference" + width="300" height="300" + windowtype="test:preferences" + onload="onLoad()"> +<dialog> + + <script type="application/javascript" src="chrome://global/content/preferencesBindings.js"/> + <script type="application/javascript"> + <![CDATA[ + /* import-globals-from ../../preferencesBindings.js */ + Preferences.addAll([ + { id: "tests.onsyncfrompreference.pref1", type: "int" }, + { id: "tests.onsyncfrompreference.pref2", type: "int" }, + { id: "tests.onsyncfrompreference.pref3", type: "int" }, + ]); + + function onLoad() { + Preferences.addSyncFromPrefListener(document.getElementById("check1"), + () => window.arguments[0]()); + Preferences.addSyncFromPrefListener(document.getElementById("check2"), + () => window.arguments[0]()); + Preferences.addSyncFromPrefListener(document.getElementById("check3"), + () => window.arguments[0]()); + Preferences.addSyncToPrefListener(document.getElementById("check1"), + () => 1); + Preferences.addSyncToPrefListener(document.getElementById("check2"), + () => 1); + Preferences.addSyncToPrefListener(document.getElementById("check3"), + () => 1); + } + ]]> + </script> + <vbox id="sample_pane" class="prefpane" label="Sample Prefpane"> + </vbox> + <label>Test Prefpane</label> + <checkbox id="check1" label="Label1" + preference="tests.onsyncfrompreference.pref1"/> + <checkbox id="check2" label="Label2" + preference="tests.onsyncfrompreference.pref2"/> + <checkbox id="check3" label="Label3" + preference="tests.onsyncfrompreference.pref3"/> +</dialog> +</window> diff --git a/toolkit/content/tests/chrome/window_screenPosSize.xhtml b/toolkit/content/tests/chrome/window_screenPosSize.xhtml new file mode 100644 index 0000000000..accc10d8f1 --- /dev/null +++ b/toolkit/content/tests/chrome/window_screenPosSize.xhtml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Window Open Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + screenX="80" + screenY="80" + height="300" + width="300" + persist="screenX screenY height width"> + +<body xmlns="http://www.w3.org/1999/xhtml"> + +</body> + +</window> diff --git a/toolkit/content/tests/chrome/window_showcaret.xhtml b/toolkit/content/tests/chrome/window_showcaret.xhtml new file mode 100644 index 0000000000..4c508d52a0 --- /dev/null +++ b/toolkit/content/tests/chrome/window_showcaret.xhtml @@ -0,0 +1,11 @@ +<?xml version='1.0'?> + +<?xml-stylesheet href='chrome://global/skin' type='text/css'?> + +<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' + xmlns:html="http://www.w3.org/1999/xhtml"> + +<hbox style='-moz-user-focus: normal;' width='20' height='20'/> +<html:input/> + +</window> diff --git a/toolkit/content/tests/chrome/window_subframe_origin.xhtml b/toolkit/content/tests/chrome/window_subframe_origin.xhtml new file mode 100644 index 0000000000..65d0a043fd --- /dev/null +++ b/toolkit/content/tests/chrome/window_subframe_origin.xhtml @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<!-- 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/. --> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="window" title="Subframe Origin Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<iframe + style="margin-left:20px; margin-top:20px; min-height:300px; max-width:300px; max-height:300px; border:solid 1px black;" + src="frame_subframe_origin_subframe1.xhtml"></iframe> +<caption id="parentcap" label=""/> + +<script> + +// Fire a mouse move event aimed at this window, and check to be +// sure the client coords translate from widget to the dom correctly. + +function runTests() +{ + synthesizeMouse(document.getElementById("window"), 1, 2, { type: "mousemove" }); +} + +window.arguments[0].SimpleTest.waitForFocus(runTests, window); + +function mouseMove(e) { + var el = document.getElementById("parentcap"); + el.label = "client: (" + e.clientX + "," + e.clientY + ")"; + window.arguments[0].SimpleTest.is(e.clientX, 1, "mouse event clientX"); + window.arguments[0].SimpleTest.is(e.clientY, 2, "mouse event clientY"); + // fire the next test on the sub frame + frames[0].runTests(); +} + +window.addEventListener("mousemove",mouseMove); + +</script> +</window> diff --git a/toolkit/content/tests/chrome/window_tooltip.xhtml b/toolkit/content/tests/chrome/window_tooltip.xhtml new file mode 100644 index 0000000000..6a573f0bd9 --- /dev/null +++ b/toolkit/content/tests/chrome/window_tooltip.xhtml @@ -0,0 +1,375 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Tooltip Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="popup_shared.js"></script> + +<tooltip id="thetooltip"> + <label id="label" class="tooltip-label" value="This is a tooltip"/> +</tooltip> + +<box id="parent" tooltiptext="Box Tooltip" style="margin: 10px"> + <button id="withtext" label="Tooltip Text" tooltiptext="Button Tooltip" + style="-moz-appearance: none; padding: 0;"/> + <button id="without" label="No Tooltip" style="-moz-appearance: none; padding: 0;"/> + <!-- remove the native theme and borders to avoid some platform + specific sizing differences --> + <button id="withtooltip" label="Tooltip Element" tooltip="thetooltip" + class="plain" style="-moz-appearance: none; padding: 0;"/> +</box> + +<script class="testbody" type="application/javascript"> +<![CDATA[ +/* import-globals-from ../widgets/popup_shared.js */ +var gOriginalWidth = -1; +var gOriginalHeight = -1; +var gButton = null; + +function runTest() +{ + startPopupTests(popupTests); +} + +function checkCoords(event) +{ + // all but one test open the tooltip at the button location offset by 6 + // in each direction. Test 5 opens it at 4 in each direction. + var mod = (gTestIndex == 5) ? 4 : 6; + + var rect = gButton.getBoundingClientRect(); + is(event.clientX, Math.round(rect.left + mod), + "step " + (gTestIndex + 1) + " clientX"); + is(event.clientY, Math.round(rect.top + mod), + "step " + (gTestIndex + 1) + " clientY"); + ok(event.screenX > 0, "step " + (gTestIndex + 1) + " screenX"); + ok(event.screenY > 0, "step " + (gTestIndex + 1) + " screenY"); +} + +var popupTests = [ +{ + testname: "hover tooltiptext attribute", + events: [ "popupshowing #tooltip", "popupshown #tooltip" ], + test() { + gButton = document.getElementById("withtext"); + disableNonTestMouse(true); + synthesizeMouse(gButton, 2, 2, { type: "mouseover" }); + synthesizeMouse(gButton, 4, 4, { type: "mousemove" }); + synthesizeMouse(gButton, 6, 6, { type: "mousemove" }); + disableNonTestMouse(false); + } +}, +{ + testname: "close tooltip", + events: [ "popuphiding #tooltip", "popuphidden #tooltip", + "DOMMenuInactive #tooltip" ], + test() { + disableNonTestMouse(true); + synthesizeMouse(document.documentElement, 2, 2, { type: "mousemove" }); + disableNonTestMouse(false); + } +}, +{ + testname: "hover inherited tooltip", + events: [ "popupshowing #tooltip", "popupshown #tooltip" ], + test() { + gButton = document.getElementById("without"); + disableNonTestMouse(true); + synthesizeMouse(gButton, 2, 2, { type: "mouseover" }); + synthesizeMouse(gButton, 4, 4, { type: "mousemove" }); + synthesizeMouse(gButton, 6, 6, { type: "mousemove" }); + disableNonTestMouse(false); + } +}, +{ + testname: "hover tooltip attribute", + events: [ "popuphiding #tooltip", "popuphidden #tooltip", + "DOMMenuInactive #tooltip", + "popupshowing thetooltip", "popupshown thetooltip" ], + test() { + gButton = document.getElementById("withtooltip"); + gExpectedTriggerNode = gButton; + disableNonTestMouse(true); + synthesizeMouse(gButton, 2, 2, { type: "mouseover" }); + synthesizeMouse(gButton, 4, 4, { type: "mousemove" }); + synthesizeMouse(gButton, 6, 6, { type: "mousemove" }); + disableNonTestMouse(false); + }, + result(testname) { + var tooltip = document.getElementById("thetooltip"); + gExpectedTriggerNode = null; + is(tooltip.triggerNode, gButton, testname + " triggerNode"); + + var buttonrect = document.getElementById("withtooltip").getBoundingClientRect(); + var rect = tooltip.getBoundingClientRect(); + var popupstyle = window.getComputedStyle(document.getElementById("thetooltip")); + + is(Math.round(rect.left), + Math.round(buttonrect.left + parseFloat(popupstyle.marginLeft) + 6), + testname + " left position of tooltip"); + is(Math.round(rect.top), + Math.round(buttonrect.top + parseFloat(popupstyle.marginTop) + 6), + testname + " top position of tooltip"); + + var labelrect = document.getElementById("label").getBoundingClientRect(); + ok(labelrect.right < rect.right, testname + " tooltip width"); + ok(labelrect.bottom < rect.bottom, testname + " tooltip height"); + + gOriginalWidth = rect.right - rect.left; + gOriginalHeight = rect.bottom - rect.top; + } +}, +{ + testname: "click to close tooltip", + events: [ "popuphiding thetooltip", "popuphidden thetooltip", + "command withtooltip", "DOMMenuInactive thetooltip" ], + test() { + gButton = document.getElementById("withtooltip"); + synthesizeMouse(gButton, 2, 2, { }); + }, + result(testname) { + var tooltip = document.getElementById("thetooltip"); + is(tooltip.triggerNode, null, testname + " triggerNode"); + } +}, +{ + testname: "hover tooltip after size increased", + events: [ "popupshowing thetooltip", "popupshown thetooltip" ], + test() { + var label = document.getElementById("label"); + label.removeAttribute("value"); + label.textContent = "This is a longer tooltip than before\nIt has multiple lines\nIt is testing tooltip sizing\n"; + gButton = document.getElementById("withtooltip"); + disableNonTestMouse(true); + synthesizeMouse(gButton, 2, 2, { type: "mouseover" }); + synthesizeMouse(gButton, 6, 6, { type: "mousemove" }); + synthesizeMouse(gButton, 4, 4, { type: "mousemove" }); + disableNonTestMouse(false); + }, + result(testname) { + var buttonrect = document.getElementById("withtooltip").getBoundingClientRect(); + var rect = document.getElementById("thetooltip").getBoundingClientRect(); + var popupstyle = window.getComputedStyle(document.getElementById("thetooltip")); + + is(Math.round(rect.left), + Math.round(buttonrect.left + parseFloat(popupstyle.marginLeft) + 4), + testname + " left position of tooltip"); + is(Math.round(rect.top), + Math.round(buttonrect.top + parseFloat(popupstyle.marginTop) + 4), + testname + " top position of tooltip"); + + var labelrect = document.getElementById("label").getBoundingClientRect(); + ok(labelrect.right < rect.right, testname + " tooltip width"); + ok(labelrect.bottom < rect.bottom, testname + " tooltip height"); + + // make sure that the tooltip is larger than it was before by just + // checking against the original height plus an arbitrary 15 pixels + ok(gOriginalWidth + 15 < rect.right - rect.left, testname + " tooltip is wider"); + ok(gOriginalHeight + 15 < rect.bottom - rect.top, testname + " tooltip is taller"); + } +}, +{ + testname: "close tooltip with hidePopup", + events: [ "popuphiding thetooltip", "popuphidden thetooltip", + "DOMMenuInactive thetooltip" ], + test() { + document.getElementById("thetooltip").hidePopup(); + }, +}, +{ + testname: "hover tooltip after size decreased", + events: [ "popupshowing thetooltip", "popupshown thetooltip" ], + autohide: "thetooltip", + test() { + var label = document.getElementById("label"); + label.value = "This is a tooltip"; + label.textContent = ""; + gButton = document.getElementById("withtooltip"); + disableNonTestMouse(true); + synthesizeMouse(gButton, 2, 2, { type: "mouseover" }); + synthesizeMouse(gButton, 4, 4, { type: "mousemove" }); + synthesizeMouse(gButton, 6, 6, { type: "mousemove" }); + disableNonTestMouse(false); + }, + result(testname) { + var buttonrect = document.getElementById("withtooltip").getBoundingClientRect(); + var rect = document.getElementById("thetooltip").getBoundingClientRect(); + var popupstyle = window.getComputedStyle(document.getElementById("thetooltip")); + + is(Math.round(rect.left), + Math.round(buttonrect.left + parseFloat(popupstyle.marginLeft) + 6), + testname + " left position of tooltip"); + is(Math.round(rect.top), + Math.round(buttonrect.top + parseFloat(popupstyle.marginTop) + 6), + testname + " top position of tooltip"); + + var labelrect = document.getElementById("label").getBoundingClientRect(); + ok(labelrect.right < rect.right, testname + " tooltip width"); + ok(labelrect.bottom < rect.bottom, testname + " tooltip height"); + + is(gOriginalWidth, rect.right - rect.left, testname + " tooltip is original width"); + is(gOriginalHeight, rect.bottom - rect.top, testname + " tooltip is original height"); + } +}, +{ + testname: "hover tooltip at bottom edge of screen", + events: [ "popupshowing thetooltip", "popupshown thetooltip" ], + autohide: "thetooltip", + condition() { + // Only checking OSX here because on other platforms popups and tooltips behave the same way + // when there's not enough space to show them below (by flipping vertically) + // However, on OSX most popups are not flipped but tooltips are. + return navigator.platform.indexOf("Mac") > -1; + }, + test() { + var buttonRect = document.getElementById("withtext").getBoundingClientRect(); + var windowY = screen.height - + (window.mozInnerScreenY - window.screenY ) - buttonRect.bottom; + + moveWindowTo(window.screenX, windowY, function() { + gButton = document.getElementById("withtooltip"); + disableNonTestMouse(true); + synthesizeMouse(gButton, 2, 2, { type: "mouseover" }); + synthesizeMouse(gButton, 6, 6, { type: "mousemove" }); + synthesizeMouse(gButton, 4, 4, { type: "mousemove" }); + disableNonTestMouse(false); + }); + }, + result(testname) { + var buttonrect = document.getElementById("withtooltip").getBoundingClientRect(); + var rect = document.getElementById("thetooltip").getBoundingClientRect(); + var popupstyle = window.getComputedStyle(document.getElementById("thetooltip")); + + is(Math.round(rect.y + rect.height), + Math.round(buttonrect.top + 4 - parseFloat(popupstyle.marginTop)), + testname + " position of tooltip above button"); + } +}, +{ + testname: "open tooltip for keyclose", + events: [ "popupshowing thetooltip", "popupshown thetooltip" ], + test() { + gButton = document.getElementById("withtooltip"); + gExpectedTriggerNode = gButton; + disableNonTestMouse(true); + synthesizeMouse(gButton, 2, 2, { type: "mouseover" }); + synthesizeMouse(gButton, 4, 4, { type: "mousemove" }); + synthesizeMouse(gButton, 6, 6, { type: "mousemove" }); + disableNonTestMouse(false); + }, +}, +{ + testname: "close tooltip with modifiers", + test() { + // Press all of the modifiers; the tooltip should remain open on all platforms. + synthesizeKey("KEY_Shift"); + synthesizeKey("KEY_Control"); + synthesizeKey("KEY_Alt"); + synthesizeKey("KEY_Alt"); + }, + result() { + is(document.getElementById("thetooltip").state, "open", "tooltip still open after modifiers pressed") + } +}, +{ + testname: "close tooltip with key", + events() { + if (navigator.platform.indexOf("Win") > -1) { + return []; + } + return [ "popuphiding thetooltip", "popuphidden thetooltip", + "DOMMenuInactive thetooltip" ]; + }, + test() { + sendString("a"); + }, + result() { + let expectedState = (navigator.platform.indexOf("Win") > -1) ? "open" : "closed"; + is(document.getElementById("thetooltip").state, expectedState, "tooltip closed after key pressed") + } +}, +{ + testname: "close tooltip with hidePopup again", + condition() { return navigator.platform.indexOf("Win") > -1; }, + events: [ "popuphiding thetooltip", "popuphidden thetooltip", + "DOMMenuInactive thetooltip" ], + test() { + document.getElementById("thetooltip").hidePopup(); + }, +}, +{ + testname: "hover tooltip with long unbreakable text", + events: [ "popupshowing thetooltip", "popupshown thetooltip" ], + autohide: "thetooltip", + test() { + var label = document.getElementById("label"); + label.removeAttribute("value"); + label.textContent = "This_tooltip_contains_no_whitespace_and_is_longer_than_the_maximum_tooltip_width_It_should_wrap_onto_a_second_line_instead_of_being_truncated"; + gButton = document.getElementById("withtooltip"); + disableNonTestMouse(true); + synthesizeMouse(gButton, 2, 2, { type: "mouseover" }); + synthesizeMouse(gButton, 4, 4, { type: "mousemove" }); + synthesizeMouse(gButton, 6, 6, { type: "mousemove" }); + disableNonTestMouse(false); + }, + result(testname) { + var rect = document.getElementById("thetooltip").getBoundingClientRect(); + + var labelrect = document.getElementById("label").getBoundingClientRect(); + ok(labelrect.right < rect.right, testname + " tooltip width"); + ok(labelrect.bottom < rect.bottom, testname + " tooltip height"); + + // make sure that the tooltip contains more than one line of text, by checking + // that the original height has increased by at least 10 pixels + ok(gOriginalHeight + 10 < rect.bottom - rect.top, testname + " tooltip is wrapped"); + } +} +]; + +var waitSteps = 0; +var oldx, oldy; +function moveWindowTo(x, y, callback, arg) +{ + if (!waitSteps) { + oldx = window.screenX; + oldy = window.screenY; + window.moveTo(x, y); + + waitSteps++; + setTimeout(moveWindowTo, 100, x, y, callback, arg); + return; + } + + if (window.screenX == oldx && window.screenY == oldy) { + if (waitSteps++ > 10) { + ok(false, "Window never moved properly to " + x + "," + y); + window.arguments[0].SimpleTest.finish(); + window.close(); + } + + setTimeout(moveWindowTo, 100, x, y, callback, arg); + } + else { + waitSteps = 0; + callback(arg); + } +} + +window.arguments[0].SimpleTest.waitForFocus(runTest, window); +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/xul_selectcontrol.js b/toolkit/content/tests/chrome/xul_selectcontrol.js new file mode 100644 index 0000000000..310ab5c3fa --- /dev/null +++ b/toolkit/content/tests/chrome/xul_selectcontrol.js @@ -0,0 +1,689 @@ +// This script is used to test elements that implement +// nsIDOMXULSelectControlElement. This currently is the following elements: +// listbox, menulist, radiogroup, richlistbox, tabs +// +// flag behaviours that differ for certain elements +// allow-other-value - alternate values for the value property may be used +// besides those in the list +// other-value-clears-selection - alternative values for the value property +// clears the selected item +// selection-required - an item must be selected in the list, unless there +// aren't any to select +// activate-disabled-menuitem - disabled menuitems can be highlighted +// select-keynav-wraps - key navigation over a selectable list wraps +// select-extended-keynav - home, end, page up and page down keys work to +// navigate over a selectable list +// keynav-leftright - key navigation is left/right rather than up/down +// The win:, mac: and gtk: or other prefixes may be used for platform specific behaviour +var behaviours = { + menu: "win:activate-disabled-menuitem activate-disabled-menuitem-mousemove select-keynav-wraps select-extended-keynav", + menulist: "allow-other-value other-value-clears-selection", + listbox: "select-extended-keynav", + richlistbox: "select-extended-keynav", + radiogroup: "select-keynav-wraps dont-select-disabled allow-other-value", + tabs: "select-extended-keynav mac:select-keynav-wraps allow-other-value selection-required keynav-leftright", +}; + +function behaviourContains(tag, behaviour) { + var platform = "none:"; + if (navigator.platform.includes("Mac")) { + platform = "mac:"; + } else if (navigator.platform.includes("Win")) { + platform = "win:"; + } else if (navigator.platform.includes("X")) { + platform = "gtk:"; + } + + var re = new RegExp( + "\\s" + platform + behaviour + "\\s|\\s" + behaviour + "\\s" + ); + return re.test(" " + behaviours[tag] + " "); +} + +function test_nsIDOMXULSelectControlElement(element, childtag, testprefix) { + var testid = testprefix ? testprefix + " " : ""; + testid += element.localName + " nsIDOMXULSelectControlElement "; + + // 'initial' - check if the initial state of the element is correct + test_nsIDOMXULSelectControlElement_States( + element, + testid + "initial", + 0, + null, + -1, + "" + ); + + test_nsIDOMXULSelectControlElement_init(element, testid); + + // 'appendItem' - check if appendItem works to add a new item + var firstitem = element.appendItem("First Item", "first"); + is( + firstitem.localName, + childtag, + testid + "appendItem - first item is " + childtag + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "appendItem", + 1, + null, + -1, + "" + ); + + is(firstitem.control, element, testid + "control"); + + // 'selectedIndex' - check if an item may be selected + element.selectedIndex = 0; + test_nsIDOMXULSelectControlElement_States( + element, + testid + "selectedIndex", + 1, + firstitem, + 0, + "first" + ); + + // 'appendItem 2' - check if a second item may be added + var seconditem = element.appendItem("Second Item", "second"); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "appendItem 2", + 2, + firstitem, + 0, + "first" + ); + + // 'selectedItem' - check if the second item may be selected + element.selectedItem = seconditem; + test_nsIDOMXULSelectControlElement_States( + element, + testid + "selectedItem", + 2, + seconditem, + 1, + "second" + ); + + // 'selectedIndex 2' - check if selectedIndex may be set to -1 to deselect items + var selectionRequired = behaviourContains( + element.localName, + "selection-required" + ); + element.selectedIndex = -1; + test_nsIDOMXULSelectControlElement_States( + element, + testid + "selectedIndex 2", + 2, + selectionRequired ? seconditem : null, + selectionRequired ? 1 : -1, + selectionRequired ? "second" : "" + ); + + // 'selectedItem 2' - check if the selectedItem property may be set to null + element.selectedIndex = 1; + element.selectedItem = null; + test_nsIDOMXULSelectControlElement_States( + element, + testid + "selectedItem 2", + 2, + selectionRequired ? seconditem : null, + selectionRequired ? 1 : -1, + selectionRequired ? "second" : "" + ); + + // 'getIndexOfItem' - check if getIndexOfItem returns the right index + is( + element.getIndexOfItem(firstitem), + 0, + testid + "getIndexOfItem - first item at index 0" + ); + is( + element.getIndexOfItem(seconditem), + 1, + testid + "getIndexOfItem - second item at index 1" + ); + + var otheritem = element.ownerDocument.createXULElement(childtag); + is( + element.getIndexOfItem(otheritem), + -1, + testid + "getIndexOfItem - other item not found" + ); + + // 'getItemAtIndex' - check if getItemAtIndex returns the right item + is( + element.getItemAtIndex(0), + firstitem, + testid + "getItemAtIndex - index 0 is first item" + ); + is( + element.getItemAtIndex(1), + seconditem, + testid + "getItemAtIndex - index 0 is second item" + ); + is( + element.getItemAtIndex(-1), + null, + testid + "getItemAtIndex - index -1 is null" + ); + is( + element.getItemAtIndex(2), + null, + testid + "getItemAtIndex - index 2 is null" + ); + + // check if setting the value changes the selection + element.value = "first"; + test_nsIDOMXULSelectControlElement_States( + element, + testid + "set value 1", + 2, + firstitem, + 0, + "first" + ); + element.value = "second"; + test_nsIDOMXULSelectControlElement_States( + element, + testid + "set value 2", + 2, + seconditem, + 1, + "second" + ); + // setting the value attribute to one not in the list doesn't change the selection. + // The value is only changed for elements which support having a value other than the + // selection. + element.value = "other"; + var allowOtherValue = behaviourContains( + element.localName, + "allow-other-value" + ); + var otherValueClearsSelection = behaviourContains( + element.localName, + "other-value-clears-selection" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "set value other", + 2, + otherValueClearsSelection ? null : seconditem, + otherValueClearsSelection ? -1 : 1, + allowOtherValue ? "other" : "second" + ); + if (allowOtherValue) { + element.value = ""; + } + + var fourthitem = element.appendItem("Fourth Item", "fourth"); + element.selectedIndex = 0; + fourthitem.disabled = true; + element.selectedIndex = 2; + test_nsIDOMXULSelectControlElement_States( + element, + testid + "selectedIndex disabled", + 3, + fourthitem, + 2, + "fourth" + ); + + element.selectedIndex = 0; + element.selectedItem = fourthitem; + test_nsIDOMXULSelectControlElement_States( + element, + testid + "selectedItem disabled", + 3, + fourthitem, + 2, + "fourth" + ); + + if (element.menupopup) { + element.menupopup.textContent = ""; + } else { + element.textContent = ""; + } +} + +function test_nsIDOMXULSelectControlElement_init(element, testprefix) { + var id = element.id; + element = document.getElementById(id + "-initwithvalue"); + if (element) { + var seconditem = element.getItemAtIndex(1); + test_nsIDOMXULSelectControlElement_States( + element, + testprefix + " value initialization", + 3, + seconditem, + 1, + seconditem.value + ); + } + + element = document.getElementById(id + "-initwithselected"); + if (element) { + var thirditem = element.getItemAtIndex(2); + test_nsIDOMXULSelectControlElement_States( + element, + testprefix + " selected initialization", + 3, + thirditem, + 2, + thirditem.value + ); + } +} + +function test_nsIDOMXULSelectControlElement_States( + element, + testid, + expectedcount, + expecteditem, + expectedindex, + expectedvalue +) { + // need an itemCount property here + var count = element.itemCount; + is(count, expectedcount, testid + " item count"); + is(element.selectedItem, expecteditem, testid + " selectedItem"); + is(element.selectedIndex, expectedindex, testid + " selectedIndex"); + is(element.value, expectedvalue, testid + " value"); + if (element.selectedItem) { + is( + element.selectedItem.selected, + true, + testid + " selectedItem marked as selected" + ); + } +} + +/** test_nsIDOMXULSelectControlElement_UI + * + * Test the UI aspects of an element which implements nsIDOMXULSelectControlElement + * + * Parameters: + * element - element to test + */ +function test_nsIDOMXULSelectControlElement_UI(element, testprefix) { + var testid = testprefix ? testprefix + " " : ""; + testid += element.localName + " nsIDOMXULSelectControlElement UI "; + + if (element.menupopup) { + element.menupopup.textContent = ""; + } else { + element.textContent = ""; + } + + var firstitem = element.appendItem("First Item", "first"); + var seconditem = element.appendItem("Second Item", "second"); + + // 'mouse select' - check if clicking an item selects it + synthesizeMouseExpectEvent( + firstitem, + 2, + 2, + {}, + element, + "select", + testid + "mouse select" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "mouse select", + 2, + firstitem, + 0, + "first" + ); + + synthesizeMouseExpectEvent( + seconditem, + 2, + 2, + {}, + element, + "select", + testid + "mouse select 2" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "mouse select 2", + 2, + seconditem, + 1, + "second" + ); + + // make sure the element is focused so keyboard navigation will apply + element.selectedIndex = 1; + element.focus(); + + var navLeftRight = behaviourContains(element.localName, "keynav-leftright"); + var backKey = navLeftRight ? "VK_LEFT" : "VK_UP"; + var forwardKey = navLeftRight ? "VK_RIGHT" : "VK_DOWN"; + + // 'key select' - check if keypresses move between items + synthesizeKeyExpectEvent(backKey, {}, element, "select", testid + "key up"); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key up", + 2, + firstitem, + 0, + "first" + ); + + var keyWrap = behaviourContains(element.localName, "select-keynav-wraps"); + + var expectedItem = keyWrap ? seconditem : firstitem; + var expectedIndex = keyWrap ? 1 : 0; + var expectedValue = keyWrap ? "second" : "first"; + synthesizeKeyExpectEvent( + backKey, + {}, + keyWrap ? element : null, + "select", + testid + "key up 2" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key up 2", + 2, + expectedItem, + expectedIndex, + expectedValue + ); + + element.selectedIndex = 0; + synthesizeKeyExpectEvent( + forwardKey, + {}, + element, + "select", + testid + "key down" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key down", + 2, + seconditem, + 1, + "second" + ); + + expectedItem = keyWrap ? firstitem : seconditem; + expectedIndex = keyWrap ? 0 : 1; + expectedValue = keyWrap ? "first" : "second"; + synthesizeKeyExpectEvent( + forwardKey, + {}, + keyWrap ? element : null, + "select", + testid + "key down 2" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key down 2", + 2, + expectedItem, + expectedIndex, + expectedValue + ); + + var thirditem = element.appendItem("Third Item", "third"); + var fourthitem = element.appendItem("Fourth Item", "fourth"); + if (behaviourContains(element.localName, "select-extended-keynav")) { + element.appendItem("Fifth Item", "fifth"); + var sixthitem = element.appendItem("Sixth Item", "sixth"); + + synthesizeKeyExpectEvent( + "VK_END", + {}, + element, + "select", + testid + "key end" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key end", + 6, + sixthitem, + 5, + "sixth" + ); + + synthesizeKeyExpectEvent( + "VK_HOME", + {}, + element, + "select", + testid + "key home" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key home", + 6, + firstitem, + 0, + "first" + ); + + synthesizeKeyExpectEvent( + "VK_PAGE_DOWN", + {}, + element, + "select", + testid + "key page down" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key page down", + 6, + fourthitem, + 3, + "fourth" + ); + synthesizeKeyExpectEvent( + "VK_PAGE_DOWN", + {}, + element, + "select", + testid + "key page down to end" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key page down to end", + 6, + sixthitem, + 5, + "sixth" + ); + + synthesizeKeyExpectEvent( + "VK_PAGE_UP", + {}, + element, + "select", + testid + "key page up" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key page up", + 6, + thirditem, + 2, + "third" + ); + synthesizeKeyExpectEvent( + "VK_PAGE_UP", + {}, + element, + "select", + testid + "key page up to start" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key page up to start", + 6, + firstitem, + 0, + "first" + ); + + element.getItemAtIndex(5).remove(); + element.getItemAtIndex(4).remove(); + } + + // now test whether a disabled item works. + element.selectedIndex = 0; + seconditem.disabled = true; + + var dontSelectDisabled = behaviourContains( + element.localName, + "dont-select-disabled" + ); + + // 'mouse select' - check if clicking an item selects it + synthesizeMouseExpectEvent( + seconditem, + 2, + 2, + {}, + element, + dontSelectDisabled ? "!select" : "select", + testid + "mouse select disabled" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "mouse select disabled", + 4, + dontSelectDisabled ? firstitem : seconditem, + dontSelectDisabled ? 0 : 1, + dontSelectDisabled ? "first" : "second" + ); + + if (dontSelectDisabled) { + // test whether disabling an item won't allow it to be selected + synthesizeKeyExpectEvent( + forwardKey, + {}, + element, + "select", + testid + "key down disabled" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key down disabled", + 4, + thirditem, + 2, + "third" + ); + + synthesizeKeyExpectEvent( + backKey, + {}, + element, + "select", + testid + "key up disabled" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key up disabled", + 4, + firstitem, + 0, + "first" + ); + + element.selectedIndex = 2; + firstitem.disabled = true; + + synthesizeKeyExpectEvent( + backKey, + {}, + keyWrap ? element : null, + "select", + testid + "key up disabled 2" + ); + expectedItem = keyWrap ? fourthitem : thirditem; + expectedIndex = keyWrap ? 3 : 2; + expectedValue = keyWrap ? "fourth" : "third"; + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key up disabled 2", + 4, + expectedItem, + expectedIndex, + expectedValue + ); + } else { + // in this case, disabled items should behave the same as non-disabled items. + element.selectedIndex = 0; + synthesizeKeyExpectEvent( + forwardKey, + {}, + element, + "select", + testid + "key down disabled" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key down disabled", + 4, + seconditem, + 1, + "second" + ); + synthesizeKeyExpectEvent( + forwardKey, + {}, + element, + "select", + testid + "key down disabled again" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key down disabled again", + 4, + thirditem, + 2, + "third" + ); + + synthesizeKeyExpectEvent( + backKey, + {}, + element, + "select", + testid + "key up disabled" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key up disabled", + 4, + seconditem, + 1, + "second" + ); + synthesizeKeyExpectEvent( + backKey, + {}, + element, + "select", + testid + "key up disabled again" + ); + test_nsIDOMXULSelectControlElement_States( + element, + testid + "key up disabled again", + 4, + firstitem, + 0, + "first" + ); + } +} |