summaryrefslogtreecommitdiffstats
path: root/widget/tests
diff options
context:
space:
mode:
Diffstat (limited to 'widget/tests')
-rw-r--r--widget/tests/TestChromeMargin.cpp130
-rw-r--r--widget/tests/browser/browser.ini66
-rw-r--r--widget/tests/browser/browser_test_ContentCache.js296
-rw-r--r--widget/tests/browser/browser_test_InputContextURI.js156
-rw-r--r--widget/tests/browser/browser_test_clipboardcache.js141
-rw-r--r--widget/tests/browser/browser_test_fullscreen_size.js66
-rw-r--r--widget/tests/browser/browser_test_ime_state_in_contenteditable_on_focus_move_in_remote_content.js122
-rw-r--r--widget/tests/browser/browser_test_ime_state_in_contenteditable_on_readonly_change_in_remote_content.js261
-rw-r--r--widget/tests/browser/browser_test_ime_state_in_designMode_on_focus_move_in_remote_content.js116
-rw-r--r--widget/tests/browser/browser_test_ime_state_in_plugin_in_remote_content.js120
-rw-r--r--widget/tests/browser/browser_test_ime_state_in_text_control_on_reframe_in_remote_content.js78
-rw-r--r--widget/tests/browser/browser_test_ime_state_on_editable_state_change_in_remote_content.js297
-rw-r--r--widget/tests/browser/browser_test_ime_state_on_focus_move_in_remote_content.js128
-rw-r--r--widget/tests/browser/browser_test_ime_state_on_input_type_change_in_remote_content.js70
-rw-r--r--widget/tests/browser/browser_test_ime_state_on_readonly_change_in_remote_content.js68
-rw-r--r--widget/tests/browser/browser_test_scrollbar_colors.js146
-rw-r--r--widget/tests/browser/browser_test_swipe_gesture.js1275
-rw-r--r--widget/tests/browser/file_ime_state_tests.html48
-rw-r--r--widget/tests/browser/helper_scrollbar_colors.html22
-rw-r--r--widget/tests/browser/helper_swipe_gesture.html20
-rw-r--r--widget/tests/bug586713_window.xhtml50
-rw-r--r--widget/tests/chrome.ini134
-rw-r--r--widget/tests/clipboard_helper.js83
-rw-r--r--widget/tests/empty_window.xhtml4
-rw-r--r--widget/tests/file_bug596600.html4
-rw-r--r--widget/tests/file_ime_state_test_helper.js197
-rw-r--r--widget/tests/file_input_events_on_deactive_window.html5
-rw-r--r--widget/tests/file_secure_input.html1
-rw-r--r--widget/tests/file_test_ime_state_in_contenteditable_on_readonly_change.js616
-rw-r--r--widget/tests/file_test_ime_state_in_text_control_on_reframe.js190
-rw-r--r--widget/tests/file_test_ime_state_on_focus_move.js1588
-rw-r--r--widget/tests/file_test_ime_state_on_input_type_change.js315
-rw-r--r--widget/tests/file_test_ime_state_on_readonly_change.js242
-rw-r--r--widget/tests/gtest/MockWinWidget.cpp77
-rw-r--r--widget/tests/gtest/MockWinWidget.h85
-rw-r--r--widget/tests/gtest/TestTimeConverter.cpp265
-rw-r--r--widget/tests/gtest/TestTouchResampler.cpp941
-rw-r--r--widget/tests/gtest/TestWinHeaderOnlyUtils.cpp37
-rw-r--r--widget/tests/gtest/TestWinMessageLoggingUtils.cpp101
-rw-r--r--widget/tests/gtest/TestWinWindowOcclusionTracker.cpp167
-rw-r--r--widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp402
-rw-r--r--widget/tests/gtest/moz.build27
-rw-r--r--widget/tests/mochitest.ini27
-rw-r--r--widget/tests/moz.build125
-rw-r--r--widget/tests/native_menus_window.xhtml282
-rw-r--r--widget/tests/standalone_native_menu_window.xhtml374
-rw-r--r--widget/tests/system_font_changes.xhtml63
-rw-r--r--widget/tests/taskbar_previews.xhtml116
-rw-r--r--widget/tests/test_AltGr_key_events_in_web_content_on_windows.html106
-rw-r--r--widget/tests/test_actionhint.html114
-rw-r--r--widget/tests/test_alwaysontop_focus.xhtml38
-rw-r--r--widget/tests/test_assign_event_data.html708
-rw-r--r--widget/tests/test_autocapitalize.html65
-rw-r--r--widget/tests/test_bug1123480.xhtml153
-rw-r--r--widget/tests/test_bug343416.xhtml191
-rw-r--r--widget/tests/test_bug413277.html35
-rw-r--r--widget/tests/test_bug428405.xhtml166
-rw-r--r--widget/tests/test_bug429954.xhtml42
-rw-r--r--widget/tests/test_bug444800.xhtml97
-rw-r--r--widget/tests/test_bug466599.xhtml103
-rw-r--r--widget/tests/test_bug478536.xhtml33
-rw-r--r--widget/tests/test_bug485118.xhtml72
-rw-r--r--widget/tests/test_bug517396.xhtml53
-rw-r--r--widget/tests/test_bug522217.xhtml35
-rw-r--r--widget/tests/test_bug538242.xhtml55
-rw-r--r--widget/tests/test_bug565392.html62
-rw-r--r--widget/tests/test_bug586713.xhtml29
-rw-r--r--widget/tests/test_bug593307.xhtml40
-rw-r--r--widget/tests/test_bug596600.xhtml190
-rw-r--r--widget/tests/test_bug673301.xhtml33
-rw-r--r--widget/tests/test_bug760802.xhtml83
-rw-r--r--widget/tests/test_clipboard.xhtml111
-rw-r--r--widget/tests/test_clipboard_asyncSetData.xhtml173
-rw-r--r--widget/tests/test_clipboard_cache.xhtml56
-rw-r--r--widget/tests/test_clipboard_owner.xhtml80
-rw-r--r--widget/tests/test_composition_text_querycontent.xhtml34
-rw-r--r--widget/tests/test_ime_state_in_contenteditable_on_readonly_change_in_parent.html72
-rw-r--r--widget/tests/test_ime_state_in_plugin_in_parent.html92
-rw-r--r--widget/tests/test_ime_state_in_text_control_on_reframe_in_parent.html42
-rw-r--r--widget/tests/test_ime_state_on_editable_state_change_in_parent.html263
-rw-r--r--widget/tests/test_ime_state_on_focus_move_in_parent.html88
-rw-r--r--widget/tests/test_ime_state_on_input_type_change_in_parent.html39
-rw-r--r--widget/tests/test_ime_state_on_readonly_change_in_parent.html31
-rw-r--r--widget/tests/test_ime_state_others_in_parent.html153
-rw-r--r--widget/tests/test_input_events_on_deactive_window.xhtml233
-rw-r--r--widget/tests/test_key_event_counts.xhtml90
-rw-r--r--widget/tests/test_keycodes.xhtml5626
-rw-r--r--widget/tests/test_keypress_event_with_alt_on_mac.html106
-rw-r--r--widget/tests/test_mouse_event_with_control_on_mac.html116
-rw-r--r--widget/tests/test_mouse_scroll.xhtml35
-rw-r--r--widget/tests/test_native_key_bindings_mac.html336
-rw-r--r--widget/tests/test_native_menus.xhtml29
-rw-r--r--widget/tests/test_panel_mouse_coords.xhtml78
-rw-r--r--widget/tests/test_picker_no_crash.html30
-rw-r--r--widget/tests/test_platform_colors.xhtml106
-rw-r--r--widget/tests/test_position_on_resize.xhtml90
-rw-r--r--widget/tests/test_secure_input.html141
-rw-r--r--widget/tests/test_sizemode_events.xhtml148
-rw-r--r--widget/tests/test_standalone_native_menu.xhtml29
-rw-r--r--widget/tests/test_system_font_changes.xhtml28
-rw-r--r--widget/tests/test_system_status_bar.xhtml53
-rw-r--r--widget/tests/test_taskbar_progress.xhtml117
-rw-r--r--widget/tests/test_textScaleFactor_system_font.html139
-rw-r--r--widget/tests/test_transferable_overflow.xhtml151
-rw-r--r--widget/tests/test_wheeltransaction.xhtml27
-rw-r--r--widget/tests/unit/test_macsharingservice.js61
-rw-r--r--widget/tests/unit/test_macwebapputils.js34
-rw-r--r--widget/tests/unit/test_taskbar_jumplistitems.js282
-rw-r--r--widget/tests/unit/xpcshell.ini11
-rw-r--r--widget/tests/window_bug429954.xhtml44
-rw-r--r--widget/tests/window_bug478536.xhtml211
-rw-r--r--widget/tests/window_bug522217.xhtml80
-rw-r--r--widget/tests/window_bug538242.xhtml3
-rw-r--r--widget/tests/window_bug593307_centerscreen.xhtml26
-rw-r--r--widget/tests/window_bug593307_offscreen.xhtml33
-rw-r--r--widget/tests/window_composition_text_querycontent.xhtml10977
-rw-r--r--widget/tests/window_imestate_iframes.html358
-rw-r--r--widget/tests/window_mouse_scroll_win.html1516
-rw-r--r--widget/tests/window_mouse_scroll_win_2.html6
-rw-r--r--widget/tests/window_picker_no_crash_child.html6
-rw-r--r--widget/tests/window_state_windows.xhtml80
-rw-r--r--widget/tests/window_wheeltransaction.xhtml1569
122 files changed, 36886 insertions, 0 deletions
diff --git a/widget/tests/TestChromeMargin.cpp b/widget/tests/TestChromeMargin.cpp
new file mode 100644
index 0000000000..0eed86b208
--- /dev/null
+++ b/widget/tests/TestChromeMargin.cpp
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/* This tests the margin parsing functionality in nsAttrValue.cpp, which
+ * is accessible via nsContentUtils, and is used in setting chromemargins
+ * to widget windows. It's located here due to linking issues in the
+ * content directory.
+ */
+
+/* This test no longer compiles now that we've removed nsIContentUtils (bug
+ * 647273). We need to be internal code in order to include nsContentUtils.h,
+ * but defining MOZILLA_INTERNAL_API is not enough to make us internal.
+ */
+
+#include "TestHarness.h"
+
+#ifndef MOZILLA_INTERNAL_API
+# error This test needs MOZILLA_INTERNAL_API (see bug 652123)
+#endif
+
+#include "nscore.h"
+#include "nsContentUtils.h"
+#include "nsString.h"
+
+struct DATA {
+ bool shouldfail;
+ const char* margins;
+ int top;
+ int right;
+ int bottom;
+ int left;
+};
+
+const bool SHOULD_FAIL = true;
+const int SHOULD_PASS = false;
+
+const DATA Data[] = {
+ {SHOULD_FAIL, "", 1, 2, 3, 4},
+ {SHOULD_FAIL, "1,0,0,0", 1, 2, 3, 4},
+ {SHOULD_FAIL, "1,2,0,0", 1, 2, 3, 4},
+ {SHOULD_FAIL, "1,2,3,0", 1, 2, 3, 4},
+ {SHOULD_FAIL, "4,3,2,1", 1, 2, 3, 4},
+ {SHOULD_FAIL, "azsasdasd", 0, 0, 0, 0},
+ {SHOULD_FAIL, ",azsasdasd", 0, 0, 0, 0},
+ {SHOULD_FAIL, " ", 1, 2, 3, 4},
+ {SHOULD_FAIL,
+ "azsdfsdfsdfsdfsdfsasdasd,asdasdasdasdasdasd,asdadasdasd,asdasdasdasd", 0,
+ 0, 0, 0},
+ {SHOULD_FAIL, "as,as,as,as", 0, 0, 0, 0},
+ {SHOULD_FAIL, "0,0,0", 0, 0, 0, 0},
+ {SHOULD_FAIL, "0,0", 0, 0, 0, 0},
+ {SHOULD_FAIL, "4.6,1,1,1", 0, 0, 0, 0},
+ {SHOULD_FAIL, ",,,,", 0, 0, 0, 0},
+ {SHOULD_FAIL, "1, , , ,", 0, 0, 0, 0},
+ {SHOULD_FAIL, "1, , ,", 0, 0, 0, 0},
+ {SHOULD_FAIL, "@!@%^&^*()", 1, 2, 3, 4},
+ {SHOULD_PASS, "4,3,2,1", 4, 3, 2, 1},
+ {SHOULD_PASS, "-4,-3,-2,-1", -4, -3, -2, -1},
+ {SHOULD_PASS, "10000,3,2,1", 10000, 3, 2, 1},
+ {SHOULD_PASS, "4 , 3 , 2 , 1", 4, 3, 2, 1},
+ {SHOULD_PASS, "4, 3 ,2,1", 4, 3, 2, 1},
+ {SHOULD_FAIL, "4,3,2,10000000000000 --", 4, 3, 2, 10000000000000},
+ {SHOULD_PASS, "4,3,2,1000", 4, 3, 2, 1000},
+ {SHOULD_PASS, "2147483647,3,2,1000", 2147483647, 3, 2, 1000},
+ {SHOULD_PASS, "2147483647,2147483647,2147483647,2147483647", 2147483647,
+ 2147483647, 2147483647, 2147483647},
+ {SHOULD_PASS, "-2147483647,3,2,1000", -2147483647, 3, 2, 1000},
+ {SHOULD_FAIL, "2147483648,3,2,1000", 1, 3, 2, 1000},
+ {0, nullptr, 0, 0, 0, 0}};
+
+void DoAttrValueTest() {
+ int idx = -1;
+ bool didFail = false;
+ while (Data[++idx].margins) {
+ nsAutoString str;
+ str.AssignLiteral(Data[idx].margins);
+ nsIntMargin values(99, 99, 99, 99);
+ bool result = nsContentUtils::ParseIntMarginValue(str, values);
+
+ // if the parse fails
+ if (!result) {
+ if (Data[idx].shouldfail) continue;
+ fail(Data[idx].margins);
+ didFail = true;
+ printf("*1\n");
+ continue;
+ }
+
+ if (Data[idx].shouldfail) {
+ if (Data[idx].top == values.top && Data[idx].right == values.right &&
+ Data[idx].bottom == values.bottom && Data[idx].left == values.left) {
+ // not likely
+ fail(Data[idx].margins);
+ didFail = true;
+ printf("*2\n");
+ continue;
+ }
+ // good failure, parse failed and that's what we expected.
+ continue;
+ }
+#if 0
+ printf("%d==%d %d==%d %d==%d %d==%d\n",
+ Data[idx].top, values.top,
+ Data[idx].right, values.right,
+ Data[idx].bottom, values.bottom,
+ Data[idx].left, values.left);
+#endif
+ if (Data[idx].top == values.top && Data[idx].right == values.right &&
+ Data[idx].bottom == values.bottom && Data[idx].left == values.left) {
+ // good parse results
+ continue;
+ } else {
+ fail(Data[idx].margins);
+ didFail = true;
+ printf("*3\n");
+ continue;
+ }
+ }
+
+ if (!didFail) passed("nsAttrValue margin parsing tests passed.");
+}
+
+int main(int argc, char** argv) {
+ ScopedXPCOM xpcom("");
+ if (xpcom.failed()) return 1;
+ DoAttrValueTest();
+ return 0;
+}
diff --git a/widget/tests/browser/browser.ini b/widget/tests/browser/browser.ini
new file mode 100644
index 0000000000..5bd1ce0dc1
--- /dev/null
+++ b/widget/tests/browser/browser.ini
@@ -0,0 +1,66 @@
+[DEFAULT]
+skip-if = os == 'andriod'
+
+[browser_test_ContentCache.js]
+[browser_test_InputContextURI.js]
+[browser_test_clipboardcache.js]
+skip-if =
+ (os == 'linux' && ccov) || tsan # Bug 1613516, the test consistently timeouts on Linux coverage builds.
+ os == "win" && bits == 32 && !debug # Bug 1759422
+ os == "linux" # Bug 1792749
+[browser_test_fullscreen_size.js]
+[browser_test_ime_state_in_contenteditable_on_focus_move_in_remote_content.js]
+support-files =
+ file_ime_state_tests.html
+ ../file_ime_state_test_helper.js
+ ../file_test_ime_state_on_focus_move.js
+[browser_test_ime_state_in_contenteditable_on_readonly_change_in_remote_content.js]
+support-files =
+ file_ime_state_tests.html
+ ../file_ime_state_test_helper.js
+ ../file_test_ime_state_in_contenteditable_on_readonly_change.js
+[browser_test_ime_state_in_designMode_on_focus_move_in_remote_content.js]
+support-files =
+ file_ime_state_tests.html
+ ../file_ime_state_test_helper.js
+ ../file_test_ime_state_on_focus_move.js
+[browser_test_ime_state_in_plugin_in_remote_content.js]
+support-files =
+ ../file_ime_state_test_helper.js
+[browser_test_ime_state_in_text_control_on_reframe_in_remote_content.js]
+support-files =
+ ../file_ime_state_test_helper.js
+ ../file_test_ime_state_in_text_control_on_reframe.js
+[browser_test_ime_state_on_editable_state_change_in_remote_content.js]
+support-files =
+ ../file_ime_state_test_helper.js
+[browser_test_ime_state_on_focus_move_in_remote_content.js]
+support-files =
+ file_ime_state_tests.html
+ ../file_ime_state_test_helper.js
+ ../file_test_ime_state_on_focus_move.js
+[browser_test_ime_state_on_input_type_change_in_remote_content.js]
+skip-if = true # Bug 1817704
+support-files =
+ file_ime_state_tests.html
+ ../file_ime_state_test_helper.js
+ ../file_test_ime_state_on_input_type_change.js
+[browser_test_ime_state_on_readonly_change_in_remote_content.js]
+support-files =
+ file_ime_state_tests.html
+ ../file_ime_state_test_helper.js
+ ../file_test_ime_state_on_readonly_change.js
+[browser_test_scrollbar_colors.js]
+skip-if = (os == 'linux') # bug 1460109
+support-files =
+ helper_scrollbar_colors.html
+[browser_test_swipe_gesture.js]
+run-if = (os == 'mac' || os == 'win' || os == 'linux')
+skip-if =
+ os == "win" && bits == 32 && !debug # Bug 1759422
+ verify # Bug 1800022
+ os == "linux" # Bug 1784772
+support-files =
+ helper_swipe_gesture.html
+ !/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
+ !/gfx/layers/apz/test/mochitest/apz_test_utils.js
diff --git a/widget/tests/browser/browser_test_ContentCache.js b/widget/tests/browser/browser_test_ContentCache.js
new file mode 100644
index 0000000000..7b64e00f13
--- /dev/null
+++ b/widget/tests/browser/browser_test_ContentCache.js
@@ -0,0 +1,296 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function () {
+ const TIP = Cc["@mozilla.org/text-input-processor;1"].createInstance(
+ Ci.nsITextInputProcessor
+ );
+ let notifications = [];
+ const observer = (aTIP, aNotification) => {
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ case "notify-end-input-transaction":
+ case "notify-focus":
+ case "notify-blur":
+ case "notify-text-change":
+ case "notify-selection-change":
+ notifications.push(aNotification);
+ break;
+ }
+ return true;
+ };
+
+ function checkNotifications(aExpectedNotifications, aDescription) {
+ for (const expectedNotification of aExpectedNotifications) {
+ const notification = notifications.find(
+ element => element.type == expectedNotification.type
+ );
+ if (expectedNotification.expected) {
+ isnot(
+ notification,
+ undefined,
+ `"${expectedNotification.type}" should be notified ${aDescription}`
+ );
+ } else {
+ is(
+ notification,
+ undefined,
+ `"${expectedNotification.type}" should not be notified ${aDescription}`
+ );
+ }
+ }
+ }
+
+ ok(
+ TIP.beginInputTransaction(window, observer),
+ "nsITextInputProcessor.beingInputTransaction should return true"
+ );
+ ok(
+ TIP.beginInputTransactionForTests(window, observer),
+ "nsITextInputProcessor.beginInputTransactionForTests should return true"
+ );
+
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/toolkit/content/tests/browser/file_empty.html",
+ async function (browser) {
+ ok(browser.isRemoteBrowser, "This test passes only in e10s mode");
+
+ // IMEContentObserver flushes pending IME notifications at next vsync
+ // after something happens. Therefore, after doing something in content
+ // process, we need to guarantee that IMEContentObserver has a change to
+ // send IME notifications to the main process with calling this function.
+ function waitForSendingIMENotificationsInContent() {
+ return SpecialPowers.spawn(browser, [], async () => {
+ await new Promise(resolve =>
+ content.requestAnimationFrame(() =>
+ content.requestAnimationFrame(resolve)
+ )
+ );
+ });
+ }
+
+ /**
+ * Test when empty editor gets focus
+ */
+ notifications = [];
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.innerHTML = "<div contenteditable><br></div>";
+ const editor = content.document.querySelector("div[contenteditable]");
+ editor.focus();
+ });
+
+ await waitForSendingIMENotificationsInContent();
+
+ (function () {
+ checkNotifications(
+ [
+ { type: "notify-focus", expected: true },
+ { type: "notify-blur", expected: false },
+ { type: "notify-end-input-transaction", expected: false },
+ { type: "notify-text-change", expected: false },
+ { type: "notify-selection-change", expected: false },
+ ],
+ "after empty editor gets focus"
+ );
+ const text = EventUtils.synthesizeQueryTextContent(0, 1000);
+ ok(
+ text?.succeeded,
+ "query text content should succeed after empty editor gets focus"
+ );
+ if (text?.succeeded) {
+ is(
+ text.text.replace(/[\r\n]/g, ""),
+ "",
+ "text should be only line breaks after empty editor gets focus"
+ );
+ }
+ const selection = EventUtils.synthesizeQuerySelectedText();
+ ok(
+ selection?.succeeded,
+ "query selected text should succeed after empty editor gets focus"
+ );
+ if (selection?.succeeded) {
+ ok(
+ !selection.notFound,
+ "query selected text should find a selection range after empty editor gets focus"
+ );
+ if (!selection.notFound) {
+ is(
+ selection.text,
+ "",
+ "selection should be collapsed after empty editor gets focus"
+ );
+ }
+ }
+ })();
+
+ /**
+ * Test when there is non-collapsed selection
+ */
+ notifications = [];
+ await SpecialPowers.spawn(browser, [], () => {
+ const editor = content.document.querySelector("div[contenteditable]");
+ editor.innerHTML = "<p>abc</p><p>def</p>";
+ content
+ .getSelection()
+ .setBaseAndExtent(
+ editor.querySelector("p").firstChild,
+ 2,
+ editor.querySelector("p + p").firstChild,
+ 1
+ );
+ });
+
+ await waitForSendingIMENotificationsInContent();
+
+ (function () {
+ checkNotifications(
+ [
+ { type: "notify-focus", expected: false },
+ { type: "notify-blur", expected: false },
+ { type: "notify-end-input-transaction", expected: false },
+ { type: "notify-text-change", expected: true },
+ { type: "notify-selection-change", expected: true },
+ ],
+ "after modifying focused editor"
+ );
+ const text = EventUtils.synthesizeQueryTextContent(0, 1000);
+ ok(
+ text?.succeeded,
+ "query text content should succeed after modifying focused editor"
+ );
+ if (text?.succeeded) {
+ is(
+ text.text.trim().replace(/\r\n/g, "\n").replace(/\n\n+/g, "\n"),
+ "abc\ndef",
+ "text should include the both paragraph's text after modifying focused editor"
+ );
+ }
+ const selection = EventUtils.synthesizeQuerySelectedText();
+ ok(
+ selection?.succeeded,
+ "query selected text should succeed after modifying focused editor"
+ );
+ if (selection?.succeeded) {
+ ok(
+ !selection.notFound,
+ "query selected text should find a selection range after modifying focused editor"
+ );
+ if (!selection.notFound) {
+ is(
+ selection.text
+ .trim()
+ .replace(/\r\n/g, "\n")
+ .replace(/\n\n+/g, "\n"),
+ "c\nd",
+ "selection should have the selected characters in the both paragraphs after modifying focused editor"
+ );
+ }
+ }
+ })();
+
+ /**
+ * Test when there is no selection ranges
+ */
+ notifications = [];
+ await SpecialPowers.spawn(browser, [], () => {
+ content.getSelection().removeAllRanges();
+ });
+
+ await waitForSendingIMENotificationsInContent();
+
+ (function () {
+ checkNotifications(
+ [
+ { type: "notify-focus", expected: false },
+ { type: "notify-blur", expected: false },
+ { type: "notify-end-input-transaction", expected: false },
+ { type: "notify-text-change", expected: false },
+ { type: "notify-selection-change", expected: true },
+ ],
+ "after removing all selection ranges from the focused editor"
+ );
+ const text = EventUtils.synthesizeQueryTextContent(0, 1000);
+ ok(
+ text?.succeeded,
+ "query text content should succeed after removing all selection ranges from the focused editor"
+ );
+ if (text?.succeeded) {
+ is(
+ text.text.trim().replace(/\r\n/g, "\n").replace(/\n\n+/g, "\n"),
+ "abc\ndef",
+ "text should include the both paragraph's text after removing all selection ranges from the focused editor"
+ );
+ }
+ const selection = EventUtils.synthesizeQuerySelectedText();
+ ok(
+ selection?.succeeded,
+ "query selected text should succeed after removing all selection ranges from the focused editor"
+ );
+ if (selection?.succeeded) {
+ ok(
+ selection.notFound,
+ "query selected text should find no selection range after removing all selection ranges from the focused editor"
+ );
+ }
+ })();
+
+ /**
+ * Test when no editable element has focus.
+ */
+ notifications = [];
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.innerHTML = "abcdef";
+ });
+
+ await waitForSendingIMENotificationsInContent();
+
+ (function () {
+ checkNotifications(
+ [
+ { type: "notify-focus", expected: false },
+ { type: "notify-blur", expected: true },
+ ],
+ "removing editor should make ContentCacheInParent not have any data"
+ );
+ const text = EventUtils.synthesizeQueryTextContent(0, 1000);
+ ok(
+ !text?.succeeded,
+ "query text content should fail because no editable element has focus"
+ );
+ const selection = EventUtils.synthesizeQuerySelectedText();
+ ok(
+ !selection?.succeeded,
+ "query selected text should fail because no editable element has focus"
+ );
+ const caret = EventUtils.synthesizeQueryCaretRect(0);
+ ok(
+ !caret?.succeeded,
+ "query caret rect should fail because no editable element has focus"
+ );
+ const textRect = EventUtils.synthesizeQueryTextRect(0, 5, false);
+ ok(
+ !textRect?.succeeded,
+ "query text rect should fail because no editable element has focus"
+ );
+ const textRectArray = EventUtils.synthesizeQueryTextRectArray(0, 5);
+ ok(
+ !textRectArray?.succeeded,
+ "query text rect array should fail because no editable element has focus"
+ );
+ const editorRect = EventUtils.synthesizeQueryEditorRect();
+ todo(
+ !editorRect?.succeeded,
+ "query editor rect should fail because no editable element has focus"
+ );
+ })();
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_InputContextURI.js b/widget/tests/browser/browser_test_InputContextURI.js
new file mode 100644
index 0000000000..52f05d90f9
--- /dev/null
+++ b/widget/tests/browser/browser_test_InputContextURI.js
@@ -0,0 +1,156 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const gDOMWindowUtils = EventUtils._getDOMWindowUtils(window);
+
+function promiseURLBarFocus() {
+ const waitForFocusInURLBar = BrowserTestUtils.waitForEvent(gURLBar, "focus");
+ gURLBar.blur();
+ gURLBar.focus();
+ return Promise.all([
+ waitForFocusInURLBar,
+ TestUtils.waitForCondition(
+ () =>
+ gDOMWindowUtils.IMEStatus === Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED &&
+ gDOMWindowUtils.inputContextOrigin ===
+ Ci.nsIDOMWindowUtils.INPUT_CONTEXT_ORIGIN_MAIN
+ ),
+ ]);
+}
+
+function promiseIMEStateEnabledByRemote() {
+ return TestUtils.waitForCondition(
+ () =>
+ gDOMWindowUtils.IMEStatus === Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED &&
+ gDOMWindowUtils.inputContextOrigin ===
+ Ci.nsIDOMWindowUtils.INPUT_CONTEXT_ORIGIN_CONTENT
+ );
+}
+
+async function test_url_bar_url(aDesc) {
+ await promiseURLBarFocus();
+
+ is(
+ gDOMWindowUtils.inputContextURI,
+ null,
+ `When the search bar has focus, input context URI should be null because of in chrome document (${aDesc})`
+ );
+}
+
+async function test_input_in_http_or_https(aIsHTTPS) {
+ await promiseURLBarFocus();
+
+ const scheme = aIsHTTPS ? "https" : "http";
+ const url = `${scheme}://example.com/browser/toolkit/content/tests/browser/file_empty.html`;
+ await BrowserTestUtils.withNewTab(url, async browser => {
+ ok(browser.isRemoteBrowser, "This test passes only in e10s mode");
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.document.body.innerHTML = "<input>";
+ const input = content.document.querySelector("input");
+ input.focus();
+
+ // Wait for a tick for flushing IMEContentObserver's pending notifications.
+ await new Promise(resolve =>
+ content.requestAnimationFrame(() =>
+ content.requestAnimationFrame(resolve)
+ )
+ );
+ });
+
+ await promiseIMEStateEnabledByRemote();
+ if (!gDOMWindowUtils.inputContextURI) {
+ ok(
+ false,
+ `Input context should have valid URI when the scheme of focused tab's URL is ${scheme}`
+ );
+ return;
+ }
+ is(
+ gDOMWindowUtils.inputContextURI.spec,
+ url,
+ `Input context should have the document URI when the scheme of focused tab's URL is ${scheme}`
+ );
+ });
+}
+
+add_task(async () => {
+ await test_url_bar_url("first check");
+});
+add_task(async () => {
+ await test_input_in_http_or_https(true);
+});
+add_task(async () => {
+ await test_url_bar_url("check after remote content sets the URI");
+});
+add_task(async () => {
+ await test_input_in_http_or_https(false);
+});
+
+add_task(async function test_input_in_data() {
+ await BrowserTestUtils.withNewTab("data:text/html,<input>", async browser => {
+ ok(browser.isRemoteBrowser, "This test passes only in e10s mode");
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ const input = content.document.querySelector("input");
+ input.focus();
+
+ // Wait for a tick for flushing IMEContentObserver's pending notifications.
+ await new Promise(resolve =>
+ content.requestAnimationFrame(() =>
+ content.requestAnimationFrame(resolve)
+ )
+ );
+ });
+
+ await promiseIMEStateEnabledByRemote();
+ is(
+ gDOMWindowUtils.inputContextURI,
+ null,
+ "Input context should not have data URI"
+ );
+ });
+});
+
+add_task(async function test_omit_private_things_in_URL() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.auth.confirmAuth.enabled", false]],
+ });
+ await promiseURLBarFocus();
+
+ await BrowserTestUtils.withNewTab(
+ "https://username:password@example.com/browser/toolkit/content/tests/browser/file_empty.html?query=some#ref",
+ async browser => {
+ ok(browser.isRemoteBrowser, "This test passes only in e10s mode");
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.document.body.innerHTML = "<input>";
+ const input = content.document.querySelector("input");
+ input.focus();
+
+ // Wait for a tick for flushing IMEContentObserver's pending notifications.
+ await new Promise(resolve =>
+ content.requestAnimationFrame(() =>
+ content.requestAnimationFrame(resolve)
+ )
+ );
+ });
+
+ await promiseIMEStateEnabledByRemote();
+ if (!gDOMWindowUtils.inputContextURI) {
+ ok(
+ false,
+ `Input context should have valid URI even when the URL contains some private things`
+ );
+ return;
+ }
+ is(
+ gDOMWindowUtils.inputContextURI.spec,
+ "https://example.com/browser/toolkit/content/tests/browser/file_empty.html",
+ `Input context should have the document URI which omit some private things in the URL`
+ );
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_clipboardcache.js b/widget/tests/browser/browser_test_clipboardcache.js
new file mode 100644
index 0000000000..bce0b9a918
--- /dev/null
+++ b/widget/tests/browser/browser_test_clipboardcache.js
@@ -0,0 +1,141 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// Note: widget/tests/test_bug1123480.xhtml checks whether nsTransferable behaves
+// as expected with regards to private browsing mode and the clipboard cache,
+// i.e. that the clipboard is not cached to the disk when private browsing mode
+// is enabled.
+//
+// This test tests that the clipboard is not cached to the disk by IPC,
+// as a regression test for bug 1396224.
+// It indirectly uses nsTransferable, via the async navigator.clipboard API.
+
+// Create over 1 MB of sample garbage text. JavaScript strings are represented
+// by UTF16 strings, so the size is twice as much as the actual string length.
+// This value is chosen such that the size of the memory for the string exceeds
+// the kLargeDatasetSize threshold in nsTransferable.h.
+// It is also not a round number to reduce the odds of having an accidental
+// collisions with another file (since the test below looks at the file size
+// to identify the file).
+var Ipsum = "0123456789".repeat(1234321);
+var IpsumByteLength = Ipsum.length * 2;
+var SHORT_STRING_NO_CACHE = "short string that will not be cached to the disk";
+
+// Get a list of open file descriptors that refer to a file with the same size
+// as the expected data (and assume that any mutations in file descriptor counts
+// are caused by our test).
+// TODO: This logic only counts file descriptors that are still open (e.g. when
+// data persists after a copy). It does not detect cache files that exist only
+// temporarily (e.g. after a paste).
+function getClipboardCacheFDCount() {
+ let dir;
+ if (AppConstants.platform === "win") {
+ // On Windows, nsAnonymousTemporaryFile does not immediately delete a file.
+ // Instead, the Windows-specific FILE_FLAG_DELETE_ON_CLOSE flag is used,
+ // which means that the file is deleted when the last handle is closed.
+ // Apparently, this flag is unreliable (e.g. when the application crashes),
+ // so nsAnonymousTemporaryFile stores the temporary files in a subdirectory,
+ // which is cleaned up some time after start-up.
+
+ // This is just a test, and during the test we deterministically close the
+ // handles, so if FILE_FLAG_DELETE_ON_CLOSE does the thing it promises, the
+ // file is actually removed when the handle is closed.
+
+ let { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+ );
+ // Path from nsAnonymousTemporaryFile.cpp, GetTempDir.
+ dir = FileUtils.getFile("TmpD", ["mozilla-temp-files"]);
+ } else {
+ dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dir.initWithPath("/dev/fd");
+ }
+ let count = 0;
+ for (let fdFile of dir.directoryEntries) {
+ let fileSize;
+ try {
+ fileSize = fdFile.fileSize;
+ } catch (e) {
+ // This can happen on macOS.
+ continue;
+ }
+ if (fileSize === IpsumByteLength) {
+ // Assume that the file was created by us if the size matches.
+ ++count;
+ }
+ }
+ return count;
+}
+
+async function testCopyPaste(isPrivate) {
+ let win = await BrowserTestUtils.openNewBrowserWindow({ private: isPrivate });
+ let tab = await BrowserTestUtils.openNewForegroundTab(win);
+ let browser = tab.linkedBrowser;
+
+ // Sanitize environment
+ await ContentTask.spawn(browser, SHORT_STRING_NO_CACHE, async shortStr => {
+ await content.navigator.clipboard.writeText(shortStr);
+ });
+
+ let initialFdCount = getClipboardCacheFDCount();
+
+ await SpecialPowers.spawn(browser, [Ipsum], async largeString => {
+ await content.navigator.clipboard.writeText(largeString);
+ });
+
+ let fdCountAfterCopy = getClipboardCacheFDCount();
+ if (isPrivate) {
+ is(fdCountAfterCopy, initialFdCount, "Private write");
+ } else {
+ is(fdCountAfterCopy, initialFdCount + 1, "Cached write");
+ }
+
+ let readStr = await SpecialPowers.spawn(browser, [], async () => {
+ let { document } = content;
+ document.body.contentEditable = true;
+ document.body.focus();
+ let pastePromise = new Promise(resolve => {
+ document.addEventListener(
+ "paste",
+ e => {
+ resolve(e.clipboardData.getData("text/plain"));
+ },
+ { once: true }
+ );
+ });
+ document.execCommand("paste");
+ return pastePromise;
+ });
+ ok(readStr === Ipsum, "Read what we pasted");
+
+ if (isPrivate) {
+ is(getClipboardCacheFDCount(), fdCountAfterCopy, "Private read");
+ } else {
+ // Upon reading from the clipboard, a temporary nsTransferable is used, for
+ // which the cache is disabled. The content process does not cache clipboard
+ // data either. So the file descriptor count should be identical.
+ is(getClipboardCacheFDCount(), fdCountAfterCopy, "Read not cached");
+ }
+
+ // Cleanup.
+ await SpecialPowers.spawn(
+ browser,
+ [SHORT_STRING_NO_CACHE],
+ async shortStr => {
+ await content.navigator.clipboard.writeText(shortStr);
+ }
+ );
+ is(getClipboardCacheFDCount(), initialFdCount, "Drop clipboard cache if any");
+
+ BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+}
+
+add_task(async function test_private() {
+ await testCopyPaste(true);
+});
+
+add_task(async function test_non_private() {
+ await testCopyPaste(false);
+});
diff --git a/widget/tests/browser/browser_test_fullscreen_size.js b/widget/tests/browser/browser_test_fullscreen_size.js
new file mode 100644
index 0000000000..c358d335d8
--- /dev/null
+++ b/widget/tests/browser/browser_test_fullscreen_size.js
@@ -0,0 +1,66 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function waitForReflow(aWindow) {
+ return new Promise(resolve => {
+ aWindow.requestAnimationFrame(() => {
+ aWindow.requestAnimationFrame(resolve);
+ });
+ });
+}
+
+add_task(async function fullscreen_size() {
+ let win = await BrowserTestUtils.openNewBrowserWindow({});
+ win.gBrowser.selectedBrowser.focus();
+
+ info("Enter browser fullscreen mode");
+ let promise = Promise.all([
+ BrowserTestUtils.waitForEvent(win, "fullscreen"),
+ BrowserTestUtils.waitForEvent(win, "resize"),
+ ]);
+ win.fullScreen = true;
+ await promise;
+
+ info("Await reflow of the chrome window");
+ await waitForReflow(win);
+
+ is(win.innerHeight, win.outerHeight, "Check height");
+ is(win.innerWidth, win.outerWidth, "Check width");
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1830721
+add_task(async function fullscreen_size_moz_appearance() {
+ const win = await BrowserTestUtils.openNewBrowserWindow({});
+ win.gBrowser.selectedBrowser.focus();
+
+ info("Add -moz-appearance style to chrome document");
+ const style = win.document.createElement("style");
+ style.innerHTML = `
+ #main-window {
+ -moz-appearance: -moz-win-borderless-glass;
+ }
+ `;
+ win.document.head.appendChild(style);
+
+ info("Await reflow of the chrome window");
+ await waitForReflow(win);
+
+ info("Enter browser fullscreen mode");
+ let promise = Promise.all([
+ BrowserTestUtils.waitForEvent(win, "fullscreen"),
+ BrowserTestUtils.waitForEvent(win, "resize"),
+ ]);
+ win.fullScreen = true;
+ await promise;
+
+ info("Await reflow of the chrome window");
+ await waitForReflow(win);
+
+ is(win.innerHeight, win.outerHeight, "Check height");
+ is(win.innerWidth, win.outerWidth, `Check width`);
+
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/widget/tests/browser/browser_test_ime_state_in_contenteditable_on_focus_move_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_in_contenteditable_on_focus_move_in_remote_content.js
new file mode 100644
index 0000000000..50b19f0cc3
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_in_contenteditable_on_focus_move_in_remote_content.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_on_focus_move.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_test_ime_state_on_focus_move.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/widget/tests/browser/file_ime_state_tests.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ // isnot is used in file_test_ime_state_on_focus_move.js, but it's not
+ // defined as the alias of Assert.notEqual in browser-chrome tests.
+ // Therefore, we need to define it here.
+ // eslint-disable-next-line no-unused-vars
+ const isnot = Assert.notEqual;
+
+ async function runIMEStateOnFocusMoveTests(aDescription) {
+ await (async function test_IMEState_without_focused_element() {
+ const checker = new IMEStateWhenNoActiveElementTester(aDescription);
+ const expectedData = await SpecialPowers.spawn(
+ browser,
+ [aDescription],
+ description => {
+ const runner =
+ content.wrappedJSObject.createIMEStateWhenNoActiveElementTester(
+ description
+ );
+ return runner.run(content.document, content.window);
+ }
+ );
+ checker.check(expectedData);
+ })();
+ for (
+ let index = 0;
+ index < IMEStateOnFocusMoveTester.numberOfTests;
+ ++index
+ ) {
+ const checker = new IMEStateOnFocusMoveTester(aDescription, index);
+ const expectedData = await SpecialPowers.spawn(
+ browser,
+ [aDescription, index],
+ (description, aIndex) => {
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateOnFocusMoveTester(
+ description,
+ aIndex,
+ content.window
+ );
+ return content.wrappedJSObject.runner.prepareToRun(
+ content.document.querySelector("div")
+ );
+ }
+ );
+ checker.prepareToCheck(expectedData, tipWrapper);
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.run();
+ });
+ checker.check(expectedData);
+
+ if (checker.canTestOpenCloseState(expectedData)) {
+ for (const defaultOpenState of [false, true]) {
+ const expectedOpenStateData = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.prepareToRunOpenCloseTest(
+ content.document.querySelector("div")
+ );
+ }
+ );
+ checker.prepareToCheckOpenCloseTest(
+ defaultOpenState,
+ expectedOpenStateData
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runOpenCloseTest();
+ });
+ checker.checkOpenCloseTest(expectedOpenStateData);
+ }
+ }
+ await SpecialPowers.spawn(browser, [], () => {
+ content.wrappedJSObject.runner.destroy();
+ content.wrappedJSObject.runner = undefined;
+ });
+ checker.destroy();
+ } // for loop iterating test of IMEStateOnFocusMoveTester
+ } // definition of runIMEStateOnFocusMoveTests
+
+ // test for contentEditable="true"
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.document
+ .querySelector("div")
+ .setAttribute("contenteditable", "true");
+ });
+ await runIMEStateOnFocusMoveTests("in div[contenteditable]");
+
+ // test for contentEditable="false"
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.document
+ .querySelector("div")
+ .setAttribute("contenteditable", "false");
+ });
+ await runIMEStateOnFocusMoveTests('in div[contenteditable="false"]');
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_in_contenteditable_on_readonly_change_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_in_contenteditable_on_readonly_change_in_remote_content.js
new file mode 100644
index 0000000000..33217d1d2c
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_in_contenteditable_on_readonly_change_in_remote_content.js
@@ -0,0 +1,261 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_in_contenteditable_on_readonly_change.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_test_ime_state_in_contenteditable_on_readonly_change.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/widget/tests/browser/file_ime_state_tests.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ await (async function test_ime_state_in_contenteditable_on_readonly_change() {
+ const expectedDataOfInitialization = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ content.document.body.innerHTML = "<div contenteditable><br></div>";
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateInContentEditableOnReadonlyChangeTester();
+ const editingHost = content.document.querySelector(
+ "div[contenteditable]"
+ );
+ return content.wrappedJSObject.runner.prepareToRun(
+ editingHost,
+ editingHost,
+ content.window
+ );
+ }
+ );
+ const tester = new IMEStateInContentEditableOnReadonlyChangeTester();
+ tester.checkResultOfPreparation(
+ expectedDataOfInitialization,
+ window,
+ tipWrapper
+ );
+ const expectedDataOfReadonly = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorReadonly();
+ }
+ );
+ tester.checkResultOfMakingHTMLEditorReadonly(expectedDataOfReadonly);
+ const expectedDataOfEditable = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorEditable();
+ }
+ );
+ tester.checkResultOfMakingHTMLEditorEditable(expectedDataOfEditable);
+ const expectedDataOfFinalization = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.runToRemoveContentEditableAttribute();
+ }
+ );
+ tester.checkResultOfRemovingContentEditableAttribute(
+ expectedDataOfFinalization
+ );
+ tester.clear();
+ })();
+
+ await (async function test_ime_state_in_button_in_contenteditable_on_readonly_change() {
+ const expectedDataOfInitialization = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ content.document.body.innerHTML =
+ "<div contenteditable><br><button>button</button></div>";
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateInContentEditableOnReadonlyChangeTester();
+ const editingHost = content.document.querySelector(
+ "div[contenteditable]"
+ );
+ return content.wrappedJSObject.runner.prepareToRun(
+ editingHost,
+ editingHost.querySelector("button"),
+ content.window
+ );
+ }
+ );
+ const tester = new IMEStateInContentEditableOnReadonlyChangeTester();
+ tester.checkResultOfPreparation(
+ expectedDataOfInitialization,
+ window,
+ tipWrapper
+ );
+ const expectedDataOfReadonly = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorReadonly();
+ }
+ );
+ tester.checkResultOfMakingHTMLEditorReadonly(expectedDataOfReadonly);
+ const expectedDataOfEditable = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorEditable();
+ }
+ );
+ tester.checkResultOfMakingHTMLEditorEditable(expectedDataOfEditable);
+ const expectedDataOfFinalization = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.runToRemoveContentEditableAttribute();
+ }
+ );
+ tester.checkResultOfRemovingContentEditableAttribute(
+ expectedDataOfFinalization
+ );
+ tester.clear();
+ })();
+
+ await (async function test_ime_state_of_text_controls_in_contenteditable_on_readonly_change() {
+ const tester =
+ new IMEStateOfTextControlInContentEditableOnReadonlyChangeTester();
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.innerHTML = "<div contenteditable></div>";
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateOfTextControlInContentEditableOnReadonlyChangeTester();
+ });
+ for (
+ let index = 0;
+ index <
+ IMEStateOfTextControlInContentEditableOnReadonlyChangeTester.numberOfTextControlTypes;
+ index++
+ ) {
+ const expectedDataOfInitialization = await SpecialPowers.spawn(
+ browser,
+ [index],
+ aIndex => {
+ const editingHost = content.document.querySelector("div");
+ return content.wrappedJSObject.runner.prepareToRun(
+ aIndex,
+ editingHost,
+ content.window
+ );
+ }
+ );
+ tester.checkResultOfPreparation(
+ expectedDataOfInitialization,
+ window,
+ tipWrapper
+ );
+ const expectedDataOfMakingParentEditingHost =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeParentEditingHost();
+ });
+ tester.checkResultOfMakingParentEditingHost(
+ expectedDataOfMakingParentEditingHost
+ );
+ const expectedDataOfMakingHTMLEditorReadonly =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorReadonly();
+ });
+ tester.checkResultOfMakingHTMLEditorReadonly(
+ expectedDataOfMakingHTMLEditorReadonly
+ );
+ const expectedDataOfMakingHTMLEditorEditable =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorEditable();
+ });
+ tester.checkResultOfMakingHTMLEditorEditable(
+ expectedDataOfMakingHTMLEditorEditable
+ );
+ const expectedDataOfMakingParentNonEditable =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeParentNonEditingHost();
+ });
+ tester.checkResultOfMakingParentNonEditable(
+ expectedDataOfMakingParentNonEditable
+ );
+ tester.clear();
+ }
+ })();
+
+ await (async function test_ime_state_outside_contenteditable_on_readonly_change() {
+ const tester =
+ new IMEStateOutsideContentEditableOnReadonlyChangeTester();
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.innerHTML = "<div contenteditable></div>";
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateOutsideContentEditableOnReadonlyChangeTester();
+ });
+ for (
+ let index = 0;
+ index <
+ IMEStateOutsideContentEditableOnReadonlyChangeTester.numberOfFocusTargets;
+ index++
+ ) {
+ const expectedDataOfInitialization = await SpecialPowers.spawn(
+ browser,
+ [index],
+ aIndex => {
+ const editingHost = content.document.querySelector("div");
+ return content.wrappedJSObject.runner.prepareToRun(
+ aIndex,
+ editingHost,
+ content.window
+ );
+ }
+ );
+ tester.checkResultOfPreparation(
+ expectedDataOfInitialization,
+ window,
+ tipWrapper
+ );
+ const expectedDataOfMakingParentEditingHost =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeParentEditingHost();
+ });
+ tester.checkResultOfMakingParentEditingHost(
+ expectedDataOfMakingParentEditingHost
+ );
+ const expectedDataOfMakingHTMLEditorReadonly =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorReadonly();
+ });
+ tester.checkResultOfMakingHTMLEditorReadonly(
+ expectedDataOfMakingHTMLEditorReadonly
+ );
+ const expectedDataOfMakingHTMLEditorEditable =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeHTMLEditorEditable();
+ });
+ tester.checkResultOfMakingHTMLEditorEditable(
+ expectedDataOfMakingHTMLEditorEditable
+ );
+ const expectedDataOfMakingParentNonEditable =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeParentNonEditingHost();
+ });
+ tester.checkResultOfMakingParentNonEditable(
+ expectedDataOfMakingParentNonEditable
+ );
+ tester.clear();
+ }
+ })();
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_in_designMode_on_focus_move_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_in_designMode_on_focus_move_in_remote_content.js
new file mode 100644
index 0000000000..5ea5990e96
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_in_designMode_on_focus_move_in_remote_content.js
@@ -0,0 +1,116 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_on_focus_move.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_test_ime_state_on_focus_move.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/widget/tests/browser/file_ime_state_tests.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ // isnot is used in file_test_ime_state_on_focus_move.js, but it's not
+ // defined as the alias of Assert.notEqual in browser-chrome tests.
+ // Therefore, we need to define it here.
+ // eslint-disable-next-line no-unused-vars
+ const isnot = Assert.notEqual;
+
+ async function runIMEStateOnFocusMoveTests(aDescription) {
+ await (async function test_IMEState_without_focused_element() {
+ const checker = new IMEStateWhenNoActiveElementTester(aDescription);
+ const expectedData = await SpecialPowers.spawn(
+ browser,
+ [aDescription],
+ description => {
+ const runner =
+ content.wrappedJSObject.createIMEStateWhenNoActiveElementTester(
+ description
+ );
+ return runner.run(content.document, content.window);
+ }
+ );
+ checker.check(expectedData);
+ })();
+ for (
+ let index = 0;
+ index < IMEStateOnFocusMoveTester.numberOfTests;
+ ++index
+ ) {
+ const checker = new IMEStateOnFocusMoveTester(aDescription, index);
+ const expectedData = await SpecialPowers.spawn(
+ browser,
+ [aDescription, index],
+ (description, aIndex) => {
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateOnFocusMoveTester(
+ description,
+ aIndex,
+ content.window
+ );
+ return content.wrappedJSObject.runner.prepareToRun(
+ content.document.querySelector("div")
+ );
+ }
+ );
+ checker.prepareToCheck(expectedData, tipWrapper);
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.run();
+ });
+ checker.check(expectedData);
+
+ if (checker.canTestOpenCloseState(expectedData)) {
+ for (const defaultOpenState of [false, true]) {
+ const expectedOpenStateData = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.prepareToRunOpenCloseTest(
+ content.document.querySelector("div")
+ );
+ }
+ );
+ checker.prepareToCheckOpenCloseTest(
+ defaultOpenState,
+ expectedOpenStateData
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runOpenCloseTest();
+ });
+ checker.checkOpenCloseTest(expectedOpenStateData);
+ }
+ }
+ await SpecialPowers.spawn(browser, [], () => {
+ content.wrappedJSObject.runner.destroy();
+ content.wrappedJSObject.runner = undefined;
+ });
+ checker.destroy();
+ } // for loop iterating test of IMEStateOnFocusMoveTester
+ } // definition of runIMEStateOnFocusMoveTests
+
+ // test designMode
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.document.designMode = "on";
+ });
+ await runIMEStateOnFocusMoveTests('in designMode="on"');
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.document.designMode = "off";
+ });
+ await runIMEStateOnFocusMoveTests('in designMode="off"');
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_in_plugin_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_in_plugin_in_remote_content.js
new file mode 100644
index 0000000000..0862b51080
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_in_plugin_in_remote_content.js
@@ -0,0 +1,120 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/toolkit/content/tests/browser/file_empty.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.wrappedJSObject.waitForIMEContentObserverSendingNotifications =
+ () => {
+ return new content.window.Promise(resolve =>
+ content.window.requestAnimationFrame(() =>
+ content.window.requestAnimationFrame(resolve)
+ )
+ );
+ };
+ content.document.body.innerHTML =
+ '<input><object type="application/x-test"></object>';
+ });
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.activeElement?.blur();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "IME enabled state should be disabled when no element has focus"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "IME should not have focus when no element has focus"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("object").focus();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "IME enabled state should be disabled when an <object> for plugin has focus"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "IME enabled state should not have focus when an <object> for plugin has focus"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("object").blur();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "IME enabled state should be disabled when an <object> for plugin gets blurred"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "IME should not have focus when an <object> for plugin gets blurred"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("object").focus();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "IME enabled state should be disabled when an <object> for plugin gets focused again"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "IME should not have focus when an <object> for plugin gets focused again"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("object").remove();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "IME enabled state should be disabled when focused <object> for plugin is removed from the document"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "IME should not have focus when focused <object> for plugin is removed from the document"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("input").focus();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ "IME enabled state should be enabled after <input> gets focus"
+ );
+ ok(
+ tipWrapper.IMEHasFocus,
+ "IME should have focus after <input> gets focus"
+ );
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_in_text_control_on_reframe_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_in_text_control_on_reframe_in_remote_content.js
new file mode 100644
index 0000000000..4d8acab1ff
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_in_text_control_on_reframe_in_remote_content.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_in_text_control_on_reframe.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_test_ime_state_in_text_control_on_reframe.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/widget/tests/browser/file_ime_state_tests.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ await (async function test_ime_state_outside_contenteditable_on_readonly_change() {
+ const tester = new IMEStateInTextControlOnReframeTester();
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.innerHTML = "<div contenteditable></div>";
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateInTextControlOnReframeTester();
+ });
+ for (
+ let index = 0;
+ index < IMEStateInTextControlOnReframeTester.numberOfTextControlTypes;
+ index++
+ ) {
+ tipWrapper.clearFocusBlurNotifications();
+ const expectedData1 = await SpecialPowers.spawn(
+ browser,
+ [index],
+ aIndex => {
+ return content.wrappedJSObject.runner.prepareToRun(
+ aIndex,
+ content.document,
+ content.window
+ );
+ }
+ );
+ tipWrapper.typeA();
+ await SpecialPowers.spawn(browser, [], () => {
+ return new Promise(resolve =>
+ content.window.requestAnimationFrame(() =>
+ content.window.requestAnimationFrame(resolve)
+ )
+ );
+ });
+ tester.checkResultAfterTypingA(expectedData1, window, tipWrapper);
+
+ const expectedData2 = await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.prepareToRun2();
+ });
+ tipWrapper.typeA();
+ await SpecialPowers.spawn(browser, [], () => {
+ return new Promise(resolve =>
+ content.window.requestAnimationFrame(() =>
+ content.window.requestAnimationFrame(resolve)
+ )
+ );
+ });
+ tester.checkResultAfterTypingA2(expectedData2);
+ tester.clear();
+ }
+ })();
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_on_editable_state_change_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_on_editable_state_change_in_remote_content.js
new file mode 100644
index 0000000000..8c38f97b72
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_on_editable_state_change_in_remote_content.js
@@ -0,0 +1,297 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/toolkit/content/tests/browser/file_empty.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.wrappedJSObject.waitForIMEContentObserverSendingNotifications =
+ () => {
+ return new content.window.Promise(resolve =>
+ content.window.requestAnimationFrame(() =>
+ content.window.requestAnimationFrame(resolve)
+ )
+ );
+ };
+ content.wrappedJSObject.resetIMEStateWithFocusMove = () => {
+ const input = content.document.createElement("input");
+ content.document.body.appendChild(input);
+ input.focus();
+ input.remove();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ };
+ content.document.body.innerHTML = "<div></div>";
+ });
+
+ function resetIMEStateWithFocusMove() {
+ return SpecialPowers.spawn(browser, [], () => {
+ const input = content.document.createElement("input");
+ content.document.body.appendChild(input);
+ input.focus();
+ input.remove();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ }
+
+ await (async function test_setting_contenteditable_of_focused_div() {
+ await SpecialPowers.spawn(browser, [], () => {
+ const div = content.document.querySelector("div");
+ div.setAttribute("tabindex", "0");
+ div.focus();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "test_setting_contenteditable_of_focused_div: IME should be disabled when non-editable <div> has focus"
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .querySelector("div")
+ .setAttribute("contenteditable", "");
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ "test_setting_contenteditable_of_focused_div: IME should be enabled when contenteditable of focused <div> is set"
+ );
+ ok(
+ tipWrapper.IMEHasFocus,
+ "test_setting_contenteditable_of_focused_div: IME should have focus when contenteditable of focused <div> is set"
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .querySelector("div")
+ .removeAttribute("contenteditable");
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "test_setting_contenteditable_of_focused_div: IME should be disabled when contenteditable of focused <div> is removed"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "test_setting_contenteditable_of_focused_div: IME should not have focus when contenteditable of focused <div> is removed"
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("div").removeAttribute("tabindex");
+ });
+ })();
+
+ await resetIMEStateWithFocusMove();
+
+ await (async function test_removing_contenteditable_of_non_last_editable_div() {
+ await SpecialPowers.spawn(browser, [], async () => {
+ const div = content.document.querySelector("div");
+ div.setAttribute("tabindex", "0");
+ div.setAttribute("contenteditable", "");
+ const anotherEditableDiv = content.document.createElement("div");
+ anotherEditableDiv.setAttribute("contenteditable", "");
+ div.parentElement.appendChild(anotherEditableDiv);
+ div.focus();
+ await content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ div.removeAttribute("contenteditable");
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "test_removing_contenteditable_of_non_last_editable_div: IME should be disabled when contenteditable of focused <div> is removed"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "test_removing_contenteditable_of_non_last_editable_div: IME should not have focus when contenteditable of focused <div> is removed"
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ const divs = content.document.querySelectorAll("div");
+ divs[1].remove();
+ divs[0].removeAttribute("tabindex");
+ });
+ })();
+
+ await resetIMEStateWithFocusMove();
+
+ await (async function test_setting_designMode() {
+ await SpecialPowers.spawn(browser, [], () => {
+ content.window.focus();
+ content.document.designMode = "on";
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ 'test_setting_designMode: IME should be enabled when designMode is set to "on"'
+ );
+ ok(
+ tipWrapper.IMEHasFocus,
+ 'test_setting_designMode: IME should have focus when designMode is set to "on"'
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.designMode = "off";
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ 'test_setting_designMode: IME should be disabled when designMode is set to "off"'
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ 'test_setting_designMode: IME should not have focus when designMode is set to "off"'
+ );
+ })();
+
+ await resetIMEStateWithFocusMove();
+
+ async function test_setting_content_editable_of_body_when_shadow_DOM_has_focus(
+ aMode
+ ) {
+ await SpecialPowers.spawn(browser, [aMode], mode => {
+ const div = content.document.querySelector("div");
+ const shadow = div.attachShadow({ mode });
+ content.wrappedJSObject.divInShadow =
+ content.document.createElement("div");
+ content.wrappedJSObject.divInShadow.setAttribute("tabindex", "0");
+ shadow.appendChild(content.wrappedJSObject.divInShadow);
+ content.wrappedJSObject.divInShadow.focus();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `test_setting_content_editable_of_body_when_shadow_DOM_has_focus(${aMode}): IME should be disabled when non-editable <div> in a shadow DOM has focus`
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.setAttribute("contenteditable", "");
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ // todo_is because of bug 1807597. Gecko does not update focus when focused
+ // element becomes an editable child. Therefore, cannot initialize
+ // HTMLEditor with the new editing host.
+ todo_is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ `test_setting_content_editable_of_body_when_shadow_DOM_has_focus(${aMode}): IME should be enabled when the <body> becomes editable`
+ );
+ todo(
+ tipWrapper.IMEHasFocus,
+ `test_setting_content_editable_of_body_when_shadow_DOM_has_focus(${aMode}): IME should have focus when the <body> becomes editable`
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.removeAttribute("contenteditable");
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `test_setting_content_editable_of_body_when_shadow_DOM_has_focus)${aMode}): IME should be disabled when the <body> becomes not editable`
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ `test_setting_content_editable_of_body_when_shadow_DOM_has_focus)${aMode}): IME should not have focus when the <body> becomes not editable`
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("div").remove();
+ content.document.body.appendChild(
+ content.document.createElement("div")
+ );
+ });
+ }
+
+ async function test_setting_designMode_when_shadow_DOM_has_focus(aMode) {
+ await SpecialPowers.spawn(browser, [aMode], mode => {
+ const div = content.document.querySelector("div");
+ const shadow = div.attachShadow({ mode });
+ content.wrappedJSObject.divInShadow =
+ content.document.createElement("div");
+ content.wrappedJSObject.divInShadow.setAttribute("tabindex", "0");
+ shadow.appendChild(content.wrappedJSObject.divInShadow);
+ content.wrappedJSObject.divInShadow.focus();
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should be disabled when non-editable <div> in a shadow DOM has focus`
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.designMode = "on";
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should stay disabled when designMode is set`
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should not have focus when designMode is set`
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.wrappedJSObject.divInShadow.setAttribute(
+ "contenteditable",
+ ""
+ );
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ // todo_is because of bug 1807597. Gecko does not update focus when focused
+ // document is into the design mode. Therefore, cannot initialize
+ // HTMLEditor with the document node properly.
+ todo_is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should be enabled when focused <div> in a shadow DOM becomes editable`
+ );
+ todo(
+ tipWrapper.IMEHasFocus,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should have focus when focused <div> in a shadow DOM becomes editable`
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.designMode = "off";
+ return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications();
+ });
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should be disabled when designMode is unset`
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should not have focus when designMode is unset`
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("div").remove();
+ content.document.body.appendChild(
+ content.document.createElement("div")
+ );
+ });
+ }
+
+ for (const mode of ["open", "closed"]) {
+ await test_setting_content_editable_of_body_when_shadow_DOM_has_focus(
+ mode
+ );
+ await resetIMEStateWithFocusMove();
+ await test_setting_designMode_when_shadow_DOM_has_focus(mode);
+ await resetIMEStateWithFocusMove();
+ }
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_on_focus_move_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_on_focus_move_in_remote_content.js
new file mode 100644
index 0000000000..3916d3d47c
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_on_focus_move_in_remote_content.js
@@ -0,0 +1,128 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_on_focus_move.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_test_ime_state_on_focus_move.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/widget/tests/browser/file_ime_state_tests.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ // isnot is used in file_test_ime_state_on_focus_move.js, but it's not
+ // defined as the alias of Assert.notEqual in browser-chrome tests.
+ // Therefore, we need to define it here.
+ // eslint-disable-next-line no-unused-vars
+ const isnot = Assert.notEqual;
+
+ async function runIMEStateOnFocusMoveTests(aDescription) {
+ await (async function test_IMEState_without_focused_element() {
+ const checker = new IMEStateWhenNoActiveElementTester(aDescription);
+ const expectedData = await SpecialPowers.spawn(
+ browser,
+ [aDescription],
+ description => {
+ const runner =
+ content.wrappedJSObject.createIMEStateWhenNoActiveElementTester(
+ description
+ );
+ return runner.run(content.document, content.window);
+ }
+ );
+ checker.check(expectedData);
+ })();
+ for (
+ let index = 0;
+ index < IMEStateOnFocusMoveTester.numberOfTests;
+ ++index
+ ) {
+ const checker = new IMEStateOnFocusMoveTester(aDescription, index);
+ const expectedData = await SpecialPowers.spawn(
+ browser,
+ [aDescription, index],
+ (description, aIndex) => {
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateOnFocusMoveTester(
+ description,
+ aIndex,
+ content.window
+ );
+ return content.wrappedJSObject.runner.prepareToRun(
+ content.document.querySelector("div")
+ );
+ }
+ );
+ checker.prepareToCheck(expectedData, tipWrapper);
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.run();
+ });
+ checker.check(expectedData);
+
+ if (checker.canTestOpenCloseState(expectedData)) {
+ for (const defaultOpenState of [false, true]) {
+ const expectedOpenStateData = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return content.wrappedJSObject.runner.prepareToRunOpenCloseTest(
+ content.document.querySelector("div")
+ );
+ }
+ );
+ checker.prepareToCheckOpenCloseTest(
+ defaultOpenState,
+ expectedOpenStateData
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runOpenCloseTest();
+ });
+ checker.checkOpenCloseTest(expectedOpenStateData);
+ }
+ }
+ await SpecialPowers.spawn(browser, [], () => {
+ content.wrappedJSObject.runner.destroy();
+ content.wrappedJSObject.runner = undefined;
+ });
+ checker.destroy();
+ } // for loop iterating test of IMEStateOnFocusMoveTester
+ } // definition of runIMEStateOnFocusMoveTests
+
+ // test for normal contents.
+ await runIMEStateOnFocusMoveTests("in non-editable container");
+
+ // test for removing contentEditable
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.document
+ .querySelector("div")
+ .setAttribute("contenteditable", "true");
+ content.document.querySelector("div").focus();
+ await new Promise(resolve =>
+ content.window.requestAnimationFrame(() =>
+ content.window.requestAnimationFrame(resolve)
+ )
+ );
+ content.document
+ .querySelector("div")
+ .removeAttribute("contenteditable");
+ });
+ await runIMEStateOnFocusMoveTests(
+ "after removing contenteditable from the container"
+ );
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_on_input_type_change_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_on_input_type_change_in_remote_content.js
new file mode 100644
index 0000000000..2a4bc4c332
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_on_input_type_change_in_remote_content.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_on_input_type_change.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_test_ime_state_on_input_type_change.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/widget/tests/browser/file_ime_state_tests.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ for (
+ let srcIndex = 0;
+ srcIndex < IMEStateOnInputTypeChangeTester.numberOfTests;
+ srcIndex++
+ ) {
+ const tester = new IMEStateOnInputTypeChangeTester(srcIndex);
+ for (
+ let destIndex = 0;
+ destIndex < IMEStateOnInputTypeChangeTester.numberOfTests;
+ destIndex++
+ ) {
+ const expectedResultBefore = await SpecialPowers.spawn(
+ browser,
+ [srcIndex, destIndex],
+ (aSrcIndex, aDestIndex) => {
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateOnInputTypeChangeTester(
+ aSrcIndex
+ );
+ return content.wrappedJSObject.runner.prepareToRun(
+ aDestIndex,
+ content.window,
+ content.document.body
+ );
+ }
+ );
+ if (expectedResultBefore === false) {
+ continue;
+ }
+ tester.checkBeforeRun(expectedResultBefore, tipWrapper);
+ const expectedResult = await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.run();
+ });
+ tester.checkResult(expectedResultBefore, expectedResult);
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.clear();
+ });
+ tipWrapper.clearFocusBlurNotifications();
+ }
+ tester.clear();
+ }
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_ime_state_on_readonly_change_in_remote_content.js b/widget/tests/browser/browser_test_ime_state_on_readonly_change_in_remote_content.js
new file mode 100644
index 0000000000..a0c0019328
--- /dev/null
+++ b/widget/tests/browser/browser_test_ime_state_on_readonly_change_in_remote_content.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_on_readonly_change.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/widget/tests/browser/file_test_ime_state_on_readonly_change.js",
+ this
+);
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/browser/widget/tests/browser/file_ime_state_tests.html",
+ async function (browser) {
+ const tipWrapper = new TIPWrapper(window);
+ ok(
+ tipWrapper.isAvailable(),
+ "TextInputProcessor should've been initialized"
+ );
+
+ const tester = new IMEStateOnReadonlyChangeTester();
+ for (
+ let i = 0;
+ i < IMEStateOnReadonlyChangeTester.numberOfTextControlTypes;
+ i++
+ ) {
+ const expectedResultBefore = await SpecialPowers.spawn(
+ browser,
+ [i],
+ aIndex => {
+ content.wrappedJSObject.runner =
+ content.wrappedJSObject.createIMEStateOnReadonlyChangeTester(
+ aIndex
+ );
+ return content.wrappedJSObject.runner.prepareToRun(
+ aIndex,
+ content.window,
+ content.document.body
+ );
+ }
+ );
+ tester.checkBeforeRun(expectedResultBefore, tipWrapper);
+ const expectedResultOfMakingTextControlReadonly =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeTextControlReadonly();
+ });
+ tester.checkResultOfMakingTextControlReadonly(
+ expectedResultOfMakingTextControlReadonly
+ );
+ const expectedResultOfMakingTextControlEditable =
+ await SpecialPowers.spawn(browser, [], () => {
+ return content.wrappedJSObject.runner.runToMakeTextControlEditable();
+ });
+ tester.checkResultOfMakingTextControlEditable(
+ expectedResultOfMakingTextControlEditable
+ );
+ tipWrapper.clearFocusBlurNotifications();
+ tester.clear();
+ }
+ }
+ );
+});
diff --git a/widget/tests/browser/browser_test_scrollbar_colors.js b/widget/tests/browser/browser_test_scrollbar_colors.js
new file mode 100644
index 0000000000..2152412071
--- /dev/null
+++ b/widget/tests/browser/browser_test_scrollbar_colors.js
@@ -0,0 +1,146 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+
+"use strict";
+
+add_task(async () => {
+ const URL_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://mochi.test:8888/"
+ );
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ URL_ROOT + "helper_scrollbar_colors.html"
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ ChromeUtils.defineESModuleGetters(this, {
+ WindowsVersionInfo:
+ "resource://gre/modules/components-utils/WindowsVersionInfo.sys.mjs",
+ });
+
+ Services.scriptloader.loadSubScript(
+ "chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js",
+ this
+ );
+
+ // == Native theme ==
+
+ const WIN_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 6889],
+ // Blue scrollbar face
+ ["0,0,255", 540],
+ // Cyan scrollbar track
+ ["0,255,255", 2487],
+ ];
+
+ const MAC_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 7225],
+ // Blue scrollbar face
+ ["0,0,255", 416],
+ // Cyan scrollbar track
+ ["0,255,255", 1760],
+ ];
+
+ // Values have been updated from 8100, 720, 1180 for linux1804
+ const LINUX_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 7744],
+ // Blue scrollbar face
+ ["0,0,255", 1104],
+ // Cyan scrollbar track
+ ["0,255,255", 1152],
+ ];
+
+ // == Non-native theme ==
+
+ const WIN10_NNT_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 6889],
+ // Blue scrollbar face
+ ["0,0,255", 612],
+ // Cyan scrollbar track
+ ["0,255,255", 2355],
+ ];
+
+ const WIN11_NNT_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 6889],
+ // Blue scrollbar face
+ ["0,0,255", 324],
+ // Cyan scrollbar track
+ ["0,255,255", 2787],
+ ];
+
+ const MAC_NNT_REFERENCES = MAC_REFERENCES;
+
+ const LINUX_NNT_REFERENCES = [
+ // Yellow background
+ ["255,255,0", 7744],
+ // Blue scrollbar face
+ ["0,0,255", 368],
+ // Cyan scrollbar track
+ ["0,255,255", 1852],
+ ];
+
+ function countPixels(canvas) {
+ let result = new Map();
+ let ctx = canvas.getContext("2d");
+ let image = ctx.getImageData(0, 0, canvas.width, canvas.height);
+ let data = image.data;
+ let size = image.width * image.height;
+ for (let i = 0; i < size; i++) {
+ let key = data.subarray(i * 4, i * 4 + 3).toString();
+ let value = result.get(key);
+ value = value ? value : 0;
+ result.set(key, value + 1);
+ }
+ return result;
+ }
+
+ let outer = content.document.querySelector(".outer");
+ let outerRect = outer.getBoundingClientRect();
+ if (
+ outerRect.width == outer.clientWidth &&
+ outerRect.height == outer.clientHeight
+ ) {
+ ok(true, "Using overlay scrollbar, skip this test");
+ return;
+ }
+ content.document.querySelector("#style").textContent = `
+ .outer { scrollbar-color: blue cyan; }
+ `;
+
+ let canvas = snapshotRect(content.window, outerRect);
+ let stats = countPixels(canvas);
+ let isNNT = SpecialPowers.getBoolPref("widget.non-native-theme.enabled");
+
+ let references;
+ if (content.navigator.platform.startsWith("Win")) {
+ if (!isNNT) {
+ references = WIN_REFERENCES;
+ } else if (WindowsVersionInfo.get().buildNumber >= 22000) {
+ // Windows 11 NNT
+ references = WIN11_NNT_REFERENCES;
+ } else {
+ // Windows 10 NNT
+ references = WIN10_NNT_REFERENCES;
+ }
+ } else if (content.navigator.platform.startsWith("Mac")) {
+ references = isNNT ? MAC_NNT_REFERENCES : MAC_REFERENCES;
+ } else if (content.navigator.platform.startsWith("Linux")) {
+ references = isNNT ? LINUX_NNT_REFERENCES : LINUX_REFERENCES;
+ } else {
+ ok(false, "Unsupported platform");
+ }
+ for (let [color, count] of references) {
+ let value = stats.get(color);
+ is(value, count, `Pixel count of color ${color}`);
+ }
+ });
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/widget/tests/browser/browser_test_swipe_gesture.js b/widget/tests/browser/browser_test_swipe_gesture.js
new file mode 100644
index 0000000000..e66e2f10b9
--- /dev/null
+++ b/widget/tests/browser/browser_test_swipe_gesture.js
@@ -0,0 +1,1275 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+
+"use strict";
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js",
+ this
+);
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js",
+ this
+);
+
+async function waitForWhile() {
+ await new Promise(resolve => {
+ requestIdleCallback(resolve, { timeout: 300 });
+ });
+ await new Promise(r => requestAnimationFrame(r));
+}
+
+requestLongerTimeout(2);
+
+add_task(async () => {
+ // Set the default values for an OS that supports swipe to nav, except for
+ // whole-page-pixel-size which varies by OS, we vary it in differente tests
+ // in this file.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ // Set the velocity-contribution to 0 so we can exactly control the
+ // values in the swipe tracker via the delta in the events that we send.
+ ["widget.swipe.success-velocity-contribution", 0.0],
+ ["widget.swipe.whole-page-pixel-size", 550.0],
+ ],
+ });
+
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!gBrowser.webNavigation.canGoForward);
+
+ let wheelEventCount = 0;
+ tab.linkedBrowser.addEventListener("wheel", () => {
+ wheelEventCount++;
+ });
+
+ // Send a pan that starts a navigate back but doesn't have enough delta to do
+ // anything. Don't send the pan end because we want to check the opacity
+ // before the MSD animation in SwipeTracker starts which can temporarily put
+ // us at 1 opacity.
+ await panLeftToRightBegin(tab.linkedBrowser, 100, 100, 0.9);
+ await panLeftToRightUpdate(tab.linkedBrowser, 100, 100, 0.9);
+
+ // Check both getComputedStyle instead of element.style.opacity because we use a transition on the opacity.
+ let computedOpacity = window
+ .getComputedStyle(gHistorySwipeAnimation._prevBox)
+ .getPropertyValue("opacity");
+ is(computedOpacity, "1", "opacity of prevbox is 1");
+ let opacity = gHistorySwipeAnimation._prevBox.style.opacity;
+ is(opacity, "", "opacity style isn't explicitly set");
+
+ const isTranslatingIcon =
+ Services.prefs.getIntPref(
+ "browser.swipe.navigation-icon-start-position",
+ 0
+ ) != 0 ||
+ Services.prefs.getIntPref(
+ "browser.swipe.navigation-icon-end-position",
+ 0
+ ) != 0;
+ if (isTranslatingIcon != 0) {
+ isnot(
+ window
+ .getComputedStyle(gHistorySwipeAnimation._prevBox)
+ .getPropertyValue("translate"),
+ "none",
+ "translate of prevbox is not `none` during gestures"
+ );
+ }
+
+ await panLeftToRightEnd(tab.linkedBrowser, 100, 100, 0.9);
+
+ // NOTE: We only get a wheel event for the beginPhase, rest of events have
+ // been captured by the swipe gesture module.
+ is(wheelEventCount, 1, "Received a wheel event");
+
+ await waitForWhile();
+ // Make sure any navigation didn't happen.
+ is(tab.linkedBrowser.currentURI.spec, secondPage);
+
+ // Try to navigate backward.
+ wheelEventCount = 0;
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 1);
+ // NOTE: We only get a wheel event for the beginPhase, rest of events have
+ // been captured by the swipe gesture module.
+ is(wheelEventCount, 1, "Received a wheel event");
+
+ // The element.style opacity will be 0 because we set it to 0 on successful navigation, however
+ // we have a tranisition on it so the computed style opacity will still be 1 because the transition hasn't started yet.
+ computedOpacity = window
+ .getComputedStyle(gHistorySwipeAnimation._prevBox)
+ .getPropertyValue("opacity");
+ ok(computedOpacity == 1, "computed opacity of prevbox is 1");
+ opacity = gHistorySwipeAnimation._prevBox.style.opacity;
+ ok(opacity == 0, "element.style opacity of prevbox 0");
+
+ if (isTranslatingIcon) {
+ // We don't have a transition for translate property so that we still have
+ // some amount of translate.
+ isnot(
+ window
+ .getComputedStyle(gHistorySwipeAnimation._prevBox)
+ .getPropertyValue("translate"),
+ "none",
+ "translate of prevbox is not `none` during the opacity transition"
+ );
+ }
+
+ // Make sure the gesture triggered going back to the previous page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+// Same test as above but whole-page-pixel-size is increased and the multipliers passed to panLeftToRight correspondingly increased.
+add_task(async () => {
+ // Set the default values for an OS that supports swipe to nav, except for
+ // whole-page-pixel-size which varies by OS, we vary it in differente tests
+ // in this file.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ // Set the velocity-contribution to 0 so we can exactly control the
+ // values in the swipe tracker via the delta in the events that we send.
+ ["widget.swipe.success-velocity-contribution", 0.0],
+ ["widget.swipe.whole-page-pixel-size", 1100.0],
+ ],
+ });
+
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!gBrowser.webNavigation.canGoForward);
+
+ let wheelEventCount = 0;
+ tab.linkedBrowser.addEventListener("wheel", () => {
+ wheelEventCount++;
+ });
+
+ // Send a pan that starts a navigate back but doesn't have enough delta to do
+ // anything. Don't send the pan end because we want to check the opacity
+ // before the MSD animation in SwipeTracker starts which can temporarily put
+ // us at 1 opacity.
+ await panLeftToRightBegin(tab.linkedBrowser, 100, 100, 1.8);
+ await panLeftToRightUpdate(tab.linkedBrowser, 100, 100, 1.8);
+
+ // Check both getComputedStyle instead of element.style.opacity because we use a transition on the opacity.
+ let computedOpacity = window
+ .getComputedStyle(gHistorySwipeAnimation._prevBox)
+ .getPropertyValue("opacity");
+ is(computedOpacity, "1", "opacity of prevbox is 1");
+ let opacity = gHistorySwipeAnimation._prevBox.style.opacity;
+ is(opacity, "", "opacity style isn't explicitly set");
+
+ await panLeftToRightEnd(tab.linkedBrowser, 100, 100, 1.8);
+
+ // NOTE: We only get a wheel event for the beginPhase, rest of events have
+ // been captured by the swipe gesture module.
+ is(wheelEventCount, 1, "Received a wheel event");
+
+ await waitForWhile();
+ // Make sure any navigation didn't happen.
+ is(tab.linkedBrowser.currentURI.spec, secondPage);
+
+ // Try to navigate backward.
+ wheelEventCount = 0;
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 2);
+ // NOTE: We only get a wheel event for the beginPhase, rest of events have
+ // been captured by the swipe gesture module.
+ is(wheelEventCount, 1, "Received a wheel event");
+
+ // The element.style opacity will be 0 because we set it to 0 on successful navigation, however
+ // we have a tranisition on it so the computed style opacity will still be 1 because the transition hasn't started yet.
+ computedOpacity = window
+ .getComputedStyle(gHistorySwipeAnimation._prevBox)
+ .getPropertyValue("opacity");
+ ok(computedOpacity == 1, "computed opacity of prevbox is 1");
+ opacity = gHistorySwipeAnimation._prevBox.style.opacity;
+ ok(opacity == 0, "element.style opacity of prevbox 0");
+
+ // Make sure the gesture triggered going back to the previous page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ // Set the default values for an OS that supports swipe to nav, except for
+ // whole-page-pixel-size which varies by OS, we vary it in different tests
+ // in this file.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ // Set the velocity-contribution to 1 (default 0.05f) so velocity is a
+ // large contribution to the success value in SwipeTracker.cpp so it
+ // pushes us into success territory without going into success territory
+ // purely from th deltas.
+ ["widget.swipe.success-velocity-contribution", 2.0],
+ ["widget.swipe.whole-page-pixel-size", 550.0],
+ ],
+ });
+
+ async function runTest() {
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!gBrowser.webNavigation.canGoForward);
+
+ let wheelEventCount = 0;
+ tab.linkedBrowser.addEventListener("wheel", () => {
+ wheelEventCount++;
+ });
+
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let startTime = performance.now();
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 0.2);
+ let endTime = performance.now();
+
+ // If sending the events took too long then we might not have been able
+ // to generate enough velocity.
+ // The value 230 was picked based on try runs, in particular test verify
+ // runs on mac were the long pole, and when we get times near this we can
+ // still achieve the required velocity.
+ if (endTime - startTime > 230) {
+ BrowserTestUtils.removeTab(tab);
+ return false;
+ }
+
+ // NOTE: We only get a wheel event for the beginPhase, rest of events have
+ // been captured by the swipe gesture module.
+ is(wheelEventCount, 1, "Received a wheel event");
+
+ // The element.style opacity will be 0 because we set it to 0 on successful navigation, however
+ // we have a tranisition on it so the computed style opacity will still be 1 because the transition hasn't started yet.
+ let computedOpacity = window
+ .getComputedStyle(gHistorySwipeAnimation._prevBox)
+ .getPropertyValue("opacity");
+ ok(computedOpacity == 1, "computed opacity of prevbox is 1");
+ let opacity = gHistorySwipeAnimation._prevBox.style.opacity;
+ ok(opacity == 0, "element.style opacity of prevbox 0");
+
+ // Make sure the gesture triggered going back to the previous page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ BrowserTestUtils.removeTab(tab);
+
+ return true;
+ }
+
+ let numTries = 15;
+ while (numTries > 0) {
+ await new Promise(r => requestAnimationFrame(r));
+ await new Promise(resolve => requestIdleCallback(resolve));
+ await new Promise(r => requestAnimationFrame(r));
+
+ // runTest return value indicates if test was able to run to the end.
+ if (await runTest()) {
+ break;
+ }
+ numTries--;
+ }
+ ok(numTries > 0, "never ran the test");
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ // Set the default values for an OS that supports swipe to nav, except for
+ // whole-page-pixel-size which varies by OS, we vary it in differente tests
+ // in this file.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ // Set the velocity-contribution to 0 so we can exactly control the
+ // values in the swipe tracker via the delta in the events that we send.
+ ["widget.swipe.success-velocity-contribution", 0.0],
+ ["widget.swipe.whole-page-pixel-size", 550.0],
+ ],
+ });
+
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!gBrowser.webNavigation.canGoForward);
+
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 2);
+
+ // Make sure the gesture triggered going back to the previous page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ while (
+ gHistorySwipeAnimation._prevBox != null ||
+ gHistorySwipeAnimation._nextBox != null
+ ) {
+ await new Promise(r => requestAnimationFrame(r));
+ }
+
+ ok(
+ gHistorySwipeAnimation._prevBox == null &&
+ gHistorySwipeAnimation._nextBox == null
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ // Set the velocity-contribution to 0 so we can exactly control the
+ // values in the swipe tracker via the delta in the events that we send.
+ ["widget.swipe.success-velocity-contribution", 0.0],
+ ["widget.swipe.whole-page-pixel-size", 550.0],
+ ],
+ });
+
+ function swipeGestureEndPromise() {
+ return new Promise(resolve => {
+ let promiseObserver = {
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "MozSwipeGestureEnd":
+ gBrowser.tabbox.removeEventListener(
+ "MozSwipeGestureEnd",
+ promiseObserver,
+ true
+ );
+ resolve();
+ break;
+ }
+ },
+ };
+ gBrowser.tabbox.addEventListener(
+ "MozSwipeGestureEnd",
+ promiseObserver,
+ true
+ );
+ });
+ }
+
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!gBrowser.webNavigation.canGoForward);
+
+ let numSwipeGestureEndEvents = 0;
+ var anObserver = {
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "MozSwipeGestureEnd":
+ numSwipeGestureEndEvents++;
+ break;
+ }
+ },
+ };
+
+ gBrowser.tabbox.addEventListener("MozSwipeGestureEnd", anObserver, true);
+
+ let gestureEndPromise = swipeGestureEndPromise();
+
+ is(
+ numSwipeGestureEndEvents,
+ 0,
+ "expected no MozSwipeGestureEnd got " + numSwipeGestureEndEvents
+ );
+
+ // Send a pan that starts a navigate back but doesn't have enough delta to do
+ // anything.
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 0.9);
+
+ await waitForWhile();
+ // Make sure any navigation didn't happen.
+ is(tab.linkedBrowser.currentURI.spec, secondPage);
+ // end event comes after a swipe that does not navigate
+ await gestureEndPromise;
+ is(
+ numSwipeGestureEndEvents,
+ 1,
+ "expected one MozSwipeGestureEnd got " + numSwipeGestureEndEvents
+ );
+
+ // Try to navigate backward.
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+
+ gestureEndPromise = swipeGestureEndPromise();
+
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 1);
+
+ // Make sure the gesture triggered going back to the previous page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ await gestureEndPromise;
+
+ is(
+ numSwipeGestureEndEvents,
+ 2,
+ "expected one MozSwipeGestureEnd got " + (numSwipeGestureEndEvents - 1)
+ );
+
+ gBrowser.tabbox.removeEventListener("MozSwipeGestureEnd", anObserver, true);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ // success-velocity-contribution is very high and whole-page-pixel-size is
+ // very low so that one swipe goes over the threshold asap.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ ["widget.swipe.success-velocity-contribution", 999999.0],
+ ["widget.swipe.whole-page-pixel-size", 1.0],
+ ],
+ });
+
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!gBrowser.webNavigation.canGoForward);
+
+ // Navigate backward.
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+
+ await panLeftToRightBegin(tab.linkedBrowser, 100, 100, 100);
+
+ ok(gHistorySwipeAnimation._prevBox != null, "should have prevbox");
+ let transitionCancelPromise = new Promise(resolve => {
+ gHistorySwipeAnimation._prevBox.addEventListener(
+ "transitioncancel",
+ event => {
+ if (
+ event.propertyName == "opacity" &&
+ event.target == gHistorySwipeAnimation._prevBox
+ ) {
+ resolve();
+ }
+ },
+ { once: true }
+ );
+ });
+ let transitionStartPromise = new Promise(resolve => {
+ gHistorySwipeAnimation._prevBox.addEventListener(
+ "transitionstart",
+ event => {
+ if (
+ event.propertyName == "opacity" &&
+ event.target == gHistorySwipeAnimation._prevBox
+ ) {
+ resolve();
+ }
+ },
+ { once: true }
+ );
+ });
+
+ await panLeftToRightUpdate(tab.linkedBrowser, 100, 100, 100);
+ await panLeftToRightEnd(tab.linkedBrowser, 100, 100, 100);
+
+ // Make sure the gesture triggered going back to the previous page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ await Promise.any([transitionStartPromise, transitionCancelPromise]);
+
+ await TestUtils.waitForCondition(() => {
+ return (
+ gHistorySwipeAnimation._prevBox == null &&
+ gHistorySwipeAnimation._nextBox == null
+ );
+ });
+
+ // Navigate forward and check the forward navigation icon box state.
+ startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ secondPage
+ );
+ stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ secondPage
+ );
+
+ await panRightToLeftBegin(tab.linkedBrowser, 100, 100, 100);
+
+ ok(gHistorySwipeAnimation._nextBox != null, "should have nextbox");
+ transitionCancelPromise = new Promise(resolve => {
+ gHistorySwipeAnimation._nextBox.addEventListener(
+ "transitioncancel",
+ event => {
+ if (
+ event.propertyName == "opacity" &&
+ event.target == gHistorySwipeAnimation._nextBox
+ ) {
+ resolve();
+ }
+ }
+ );
+ });
+ transitionStartPromise = new Promise(resolve => {
+ gHistorySwipeAnimation._nextBox.addEventListener(
+ "transitionstart",
+ event => {
+ if (
+ event.propertyName == "opacity" &&
+ event.target == gHistorySwipeAnimation._nextBox
+ ) {
+ resolve();
+ }
+ }
+ );
+ });
+
+ await panRightToLeftUpdate(tab.linkedBrowser, 100, 100, 100);
+ await panRightToLeftEnd(tab.linkedBrowser, 100, 100, 100);
+
+ // Make sure the gesture triggered going forward to the next page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoBack);
+
+ await Promise.any([transitionStartPromise, transitionCancelPromise]);
+
+ await TestUtils.waitForCondition(() => {
+ return (
+ gHistorySwipeAnimation._nextBox == null &&
+ gHistorySwipeAnimation._prevBox == null
+ );
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+// A simple test case on RTL.
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ ["widget.swipe.success-velocity-contribution", 0.5],
+ ["intl.l10n.pseudo", "bidi"],
+ ],
+ });
+
+ const newWin = await BrowserTestUtils.openNewBrowserWindow();
+
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ newWin.gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(newWin.gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!newWin.gBrowser.webNavigation.canGoForward);
+
+ // Make sure that our gesture support stuff has been initialized in the new
+ // browser window.
+ await TestUtils.waitForCondition(() => {
+ return newWin.gHistorySwipeAnimation.active;
+ });
+
+ // Try to navigate backward.
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+ await panRightToLeft(tab.linkedBrowser, 100, 100, 1);
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(newWin.gBrowser.webNavigation.canGoForward);
+
+ // Now try to navigate forward again.
+ startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ secondPage
+ );
+ stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ secondPage
+ );
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 1);
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(newWin.gBrowser.webNavigation.canGoBack);
+
+ await BrowserTestUtils.closeWindow(newWin);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ ["widget.swipe.success-velocity-contribution", 0.5],
+ ["apz.overscroll.enabled", true],
+ ["apz.test.logging_enabled", true],
+ ],
+ });
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:about",
+ true /* waitForLoad */
+ );
+
+ const URL_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://mochi.test:8888/"
+ );
+ BrowserTestUtils.loadURIString(
+ tab.linkedBrowser,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false /* includeSubFrames */,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ // Set `overscroll-behavior-x: contain` and flush it.
+ content.document.documentElement.style.overscrollBehaviorX = "contain";
+ content.document.documentElement.getBoundingClientRect();
+ await content.wrappedJSObject.promiseApzFlushedRepaints();
+ });
+
+ // Start a pan gesture but keep touching.
+ await panLeftToRightBegin(tab.linkedBrowser, 100, 100, 2);
+
+ // Flush APZ pending requests to make sure the pan gesture has been processed.
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await content.wrappedJSObject.promiseApzFlushedRepaints();
+ });
+
+ const isOverscrolled = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ const scrollId = SpecialPowers.DOMWindowUtils.getViewId(
+ content.document.scrollingElement
+ );
+ const data = SpecialPowers.DOMWindowUtils.getCompositorAPZTestData();
+ return data.additionalData.some(entry => {
+ return (
+ entry.key == scrollId &&
+ entry.value.split(",").includes("overscrolled")
+ );
+ });
+ }
+ );
+
+ ok(isOverscrolled, "The root scroller should have overscrolled");
+
+ // Finish the pan gesture.
+ await panLeftToRightUpdate(tab.linkedBrowser, 100, 100, 2);
+ await panLeftToRightEnd(tab.linkedBrowser, 100, 100, 2);
+
+ // And wait a while to give a chance to navigate.
+ await waitForWhile();
+
+ // Make sure any navigation didn't happen.
+ is(tab.linkedBrowser.currentURI.spec, URL_ROOT + "helper_swipe_gesture.html");
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+// A test case to make sure the short circuit path for swipe-to-navigations in
+// APZ works, i.e. cases where we know for sure that the target APZC for a given
+// pan-start event isn't scrollable in the pan-start event direction.
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["apz.overscroll.enabled", true],
+ ],
+ });
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:about",
+ true /* waitForLoad */
+ );
+
+ const URL_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://mochi.test:8888/"
+ );
+ BrowserTestUtils.loadURIString(
+ tab.linkedBrowser,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false /* includeSubFrames */,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+
+ // Make sure the content can allow both of overscrolling and
+ // swipe-to-navigations.
+ const overscrollBehaviorX = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ return content.window.getComputedStyle(content.document.documentElement)
+ .overscrollBehaviorX;
+ }
+ );
+ is(overscrollBehaviorX, "auto");
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+
+ // Start a pan gesture but keep touching.
+ await panLeftToRightBegin(tab.linkedBrowser, 100, 100, 2);
+
+ // The above pan event should invoke a SwipeGestureStart event immediately so
+ // that the swipe-to-navigation icon box should be uncollapsed to show it.
+ ok(!gHistorySwipeAnimation._prevBox.collapsed);
+
+ // Finish the pan gesture, i.e. sending a pan-end event, otherwise a new
+ // pan-start event in the next will also generate a pan-interrupt event which
+ // will break the test.
+ await panLeftToRightUpdate(tab.linkedBrowser, 100, 100, 2);
+ await panLeftToRightEnd(tab.linkedBrowser, 100, 100, 2);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ ["widget.swipe.success-velocity-contribution", 0.5],
+ ["apz.overscroll.enabled", true],
+ ["apz.test.logging_enabled", true],
+ ],
+ });
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:about",
+ true /* waitForLoad */
+ );
+
+ const URL_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://mochi.test:8888/"
+ );
+ BrowserTestUtils.loadURIString(
+ tab.linkedBrowser,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false /* includeSubFrames */,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+
+ // Start a pan gesture but keep touching.
+ await panLeftToRightBegin(tab.linkedBrowser, 100, 100, 2);
+
+ // Flush APZ pending requests to make sure the pan gesture has been processed.
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await content.wrappedJSObject.promiseApzFlushedRepaints();
+ });
+
+ const isOverscrolled = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ const scrollId = SpecialPowers.DOMWindowUtils.getViewId(
+ content.document.scrollingElement
+ );
+ const data = SpecialPowers.DOMWindowUtils.getCompositorAPZTestData();
+ return data.additionalData.some(entry => {
+ return entry.key == scrollId && entry.value.includes("overscrolled");
+ });
+ }
+ );
+
+ ok(!isOverscrolled, "The root scroller should not have overscrolled");
+
+ await panLeftToRightEnd(tab.linkedBrowser, 100, 100, 0);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ ["widget.swipe.success-velocity-contribution", 0.5],
+ ],
+ });
+
+ // Load three pages and go to the second page so that it can be navigated
+ // to both back and forward.
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:about",
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, "about:mozilla");
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false /* includeSubFrames */,
+ "about:mozilla"
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, "about:home");
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false /* includeSubFrames */,
+ "about:home"
+ );
+
+ gBrowser.goBack();
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false /* includeSubFrames */,
+ "about:mozilla"
+ );
+
+ // Make sure we can go back and go forward.
+ ok(gBrowser.webNavigation.canGoBack);
+ ok(gBrowser.webNavigation.canGoForward);
+
+ // Start a history back pan gesture but keep touching.
+ await panLeftToRightBegin(tab.linkedBrowser, 100, 100, 1);
+
+ ok(
+ !gHistorySwipeAnimation._prevBox.collapsed,
+ "The icon box for the previous navigation should NOT be collapsed"
+ );
+ ok(
+ gHistorySwipeAnimation._nextBox.collapsed,
+ "The icon box for the next navigation should be collapsed"
+ );
+
+ // Pan back to the opposite direction so that the gesture should be cancelled.
+ // eslint-disable-next-line no-undef
+ await NativePanHandler.promiseNativePanEvent(
+ tab.linkedBrowser,
+ 100,
+ 100,
+ // eslint-disable-next-line no-undef
+ NativePanHandler.delta,
+ 0,
+ // eslint-disable-next-line no-undef
+ NativePanHandler.updatePhase
+ );
+
+ ok(
+ gHistorySwipeAnimation._prevBox.collapsed,
+ "The icon box for the previous navigation should be collapsed"
+ );
+ ok(
+ gHistorySwipeAnimation._nextBox.collapsed,
+ "The icon box for the next navigation should be collapsed"
+ );
+
+ await panLeftToRightEnd(tab.linkedBrowser, 100, 100, 0);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ ["widget.swipe.success-velocity-contribution", 0.5],
+ ["apz.overscroll.enabled", true],
+ ["apz.overscroll.damping", 5.0],
+ ["apz.content_response_timeout", 0],
+ ],
+ });
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:about",
+ true /* waitForLoad */
+ );
+
+ const URL_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://mochi.test:8888/"
+ );
+
+ // Load a horizontal scrollable content.
+ BrowserTestUtils.loadURIString(
+ tab.linkedBrowser,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false /* includeSubFrames */,
+ URL_ROOT + "helper_swipe_gesture.html"
+ );
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+
+ // Shift the horizontal scroll position slightly to make the content
+ // overscrollable.
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ content.document.documentElement.scrollLeft = 1;
+ content.document.documentElement.getBoundingClientRect();
+ await content.wrappedJSObject.promiseApzFlushedRepaints();
+ });
+
+ // Swipe horizontally to overscroll.
+ await panLeftToRight(tab.linkedBrowser, 1, 100, 1);
+
+ // Swipe again over the overscroll gutter.
+ await panLeftToRight(tab.linkedBrowser, 1, 100, 1);
+
+ // Wait the overscroll gutter is restored.
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ // For some reasons using functions in apz_test_native_event_utils.js
+ // sometimes causes "TypeError content.wrappedJSObject.XXXX is not a
+ // function" error, so we observe "APZ:TransformEnd" instead of using
+ // promiseTransformEnd().
+ await new Promise((resolve, reject) => {
+ SpecialPowers.Services.obs.addObserver(function observer(
+ subject,
+ topic,
+ data
+ ) {
+ try {
+ SpecialPowers.Services.obs.removeObserver(observer, topic);
+ resolve([subject, data]);
+ } catch (ex) {
+ SpecialPowers.Services.obs.removeObserver(observer, topic);
+ reject(ex);
+ }
+ },
+ "APZ:TransformEnd");
+ });
+ });
+
+ // Set up an APZ aware event listener and...
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ content.document.documentElement.addEventListener("wheel", e => {}, {
+ passive: false,
+ });
+ await content.wrappedJSObject.promiseApzFlushedRepaints();
+ });
+
+ // Try to swipe back again without overscrolling to make sure swipe-navigation
+ // works with the APZ aware event listener.
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 1);
+
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ "about:about"
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ "about:about"
+ );
+
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+// NOTE: This test listens wheel events so that it causes an overscroll issue
+// (bug 1800022). To avoid the bug, we need to run this test case at the end
+// of this file.
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.gesture.swipe.left", "Browser:BackOrBackDuplicate"],
+ ["browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate"],
+ ["widget.disable-swipe-tracker", false],
+ ["widget.swipe.velocity-twitch-tolerance", 0.0000001],
+ ["widget.swipe.success-velocity-contribution", 0.5],
+ ],
+ });
+
+ const firstPage = "about:about";
+ const secondPage = "about:mozilla";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ firstPage,
+ true /* waitForLoad */
+ );
+
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, secondPage);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, secondPage);
+
+ // Make sure we can go back to the previous page.
+ ok(gBrowser.webNavigation.canGoBack);
+ // and we cannot go forward to the next page.
+ ok(!gBrowser.webNavigation.canGoForward);
+
+ let wheelEventCount = 0;
+ tab.linkedBrowser.addEventListener("wheel", () => {
+ wheelEventCount++;
+ });
+
+ // Try to navigate forward.
+ await panRightToLeft(tab.linkedBrowser, 100, 100, 1);
+ // NOTE: The last endPhase shouldn't fire a wheel event since
+ // its delta is zero.
+ is(wheelEventCount, 2, "Received 2 wheel events");
+
+ await waitForWhile();
+ // Make sure any navigation didn't happen.
+ is(tab.linkedBrowser.currentURI.spec, secondPage);
+
+ // Try to navigate backward.
+ wheelEventCount = 0;
+ let startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ firstPage
+ );
+ let stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ firstPage
+ );
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 1);
+ // NOTE: We only get a wheel event for the beginPhase, rest of events have
+ // been captured by the swipe gesture module.
+ is(wheelEventCount, 1, "Received a wheel event");
+
+ // Make sure the gesture triggered going back to the previous page.
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoForward);
+
+ // Now try to navigate forward again.
+ wheelEventCount = 0;
+ startLoadingPromise = BrowserTestUtils.browserStarted(
+ tab.linkedBrowser,
+ secondPage
+ );
+ stoppedLoadingPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ secondPage
+ );
+ await panRightToLeft(tab.linkedBrowser, 100, 100, 1);
+ is(wheelEventCount, 1, "Received a wheel event");
+
+ await Promise.all([startLoadingPromise, stoppedLoadingPromise]);
+
+ ok(gBrowser.webNavigation.canGoBack);
+
+ // Now try to navigate backward again but with preventDefault-ed event
+ // handler.
+ wheelEventCount = 0;
+ let wheelEventListener = event => {
+ event.preventDefault();
+ };
+ tab.linkedBrowser.addEventListener("wheel", wheelEventListener);
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 1);
+ is(wheelEventCount, 3, "Received all wheel events");
+
+ await waitForWhile();
+ // Make sure any navigation didn't happen.
+ is(tab.linkedBrowser.currentURI.spec, secondPage);
+
+ // Now drop the event handler and disable the swipe tracker and try to swipe
+ // again.
+ wheelEventCount = 0;
+ tab.linkedBrowser.removeEventListener("wheel", wheelEventListener);
+ await SpecialPowers.pushPrefEnv({
+ set: [["widget.disable-swipe-tracker", true]],
+ });
+
+ await panLeftToRight(tab.linkedBrowser, 100, 100, 1);
+ is(wheelEventCount, 3, "Received all wheel events");
+
+ await waitForWhile();
+ // Make sure any navigation didn't happen.
+ is(tab.linkedBrowser.currentURI.spec, secondPage);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/widget/tests/browser/file_ime_state_tests.html b/widget/tests/browser/file_ime_state_tests.html
new file mode 100644
index 0000000000..d6b63f1e52
--- /dev/null
+++ b/widget/tests/browser/file_ime_state_tests.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<html style="ime-mode: disabled;">
+<head>
+<meta charset="utf-8">
+<script src="file_ime_state_test_helper.js"></script>
+<script src="file_test_ime_state_in_contenteditable_on_readonly_change.js"></script>
+<script src="file_test_ime_state_in_text_control_on_reframe.js"></script>
+<script src="file_test_ime_state_on_focus_move.js"></script>
+<script src="file_test_ime_state_on_input_type_change.js"></script>
+<script src="file_test_ime_state_on_readonly_change.js"></script>
+<script>
+"use strict";
+
+/* import-globals-from ../file_ime_state_test_helper.js */
+/* import-globals-from ../file_test_ime_state_in_contenteditable_on_readonly_change.js */
+/* import-globals-from ../file_test_ime_state_in_text_control_on_reframe.js */
+/* import-globals-from ../file_test_ime_state_on_focus_move.js */
+/* import-globals-from ../file_test_ime_state_on_input_type_change.js */
+/* import-globals-from ../file_test_ime_state_on_readonly_change.js */
+
+function createIMEStateInContentEditableOnReadonlyChangeTester() {
+ return new IMEStateInContentEditableOnReadonlyChangeTester();
+}
+function createIMEStateOfTextControlInContentEditableOnReadonlyChangeTester() {
+ return new IMEStateOfTextControlInContentEditableOnReadonlyChangeTester();
+}
+function createIMEStateOutsideContentEditableOnReadonlyChangeTester() {
+ return new IMEStateOutsideContentEditableOnReadonlyChangeTester();
+}
+function createIMEStateInTextControlOnReframeTester() {
+ return new IMEStateInTextControlOnReframeTester();
+}
+function createIMEStateWhenNoActiveElementTester(aDescription) {
+ return new IMEStateWhenNoActiveElementTester(aDescription);
+}
+function createIMEStateOnFocusMoveTester(aDescription, aIndex, aWindow = window) {
+ return new IMEStateOnFocusMoveTester(aDescription, aIndex, aWindow);
+}
+function createIMEStateOnInputTypeChangeTester(aSrcIndex) {
+ return new IMEStateOnInputTypeChangeTester(aSrcIndex);
+}
+function createIMEStateOnReadonlyChangeTester() {
+ return new IMEStateOnReadonlyChangeTester();
+}
+</script>
+</head>
+<body style="ime-mode: disabled;"><div style="ime-mode: disabled;"></div></body>
+</html>
diff --git a/widget/tests/browser/helper_scrollbar_colors.html b/widget/tests/browser/helper_scrollbar_colors.html
new file mode 100644
index 0000000000..e6001906e2
--- /dev/null
+++ b/widget/tests/browser/helper_scrollbar_colors.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<meta charset="UTF-8">
+<title>Test for scrollbar-*-color properties</title>
+<style>
+ .outer {
+ width: 100px;
+ height: 100px;
+ background: yellow;
+ overflow: scroll;
+ }
+ .inner {
+ width: 200px;
+ height: 200px;
+ }
+</style>
+<style id="style"></style>
+<div class="outer">
+ <div class="inner">
+ </div>
+</div>
+</html>
diff --git a/widget/tests/browser/helper_swipe_gesture.html b/widget/tests/browser/helper_swipe_gesture.html
new file mode 100644
index 0000000000..1fa79dbbf3
--- /dev/null
+++ b/widget/tests/browser/helper_swipe_gesture.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<script src="/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+<script src="/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js"></script>
+<style>
+html {
+ overflow-x: scroll;
+}
+body {
+ margin: 0;
+}
+div {
+ height: 100vh;
+ width: 110vw;
+ background-color: blue;
+}
+</style>
+<div></div>
+</html>
diff --git a/widget/tests/bug586713_window.xhtml b/widget/tests/bug586713_window.xhtml
new file mode 100644
index 0000000000..c180c00235
--- /dev/null
+++ b/widget/tests/bug586713_window.xhtml
@@ -0,0 +1,50 @@
+<?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="bug586713_window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="300"
+ height="300"
+ onload="onLoad();"
+ title="Bug 586713 Test">
+
+ <menubar id="nativemenubar">
+ <menu id="foo" label="Foo">
+ <menupopup>
+ <menuitem label="FooItem0"/>
+ </menupopup>
+ </menu>
+ </menubar>
+
+ <script type="application/javascript"><![CDATA[
+ function ok(condition, message) {
+ window.arguments[0].SimpleTest.ok(condition, message);
+ }
+
+ function onTestsFinished() {
+ window.close();
+ window.arguments[0].SimpleTest.finish();
+ }
+
+ var fooCallCount = 0;
+ function foo() {
+ fooCallCount++;
+ let instruction = document.createProcessingInstruction("xml-stylesheet", 'href="chrome://foo.css" type="text/css"');
+ document.insertBefore(instruction, document.documentElement);
+ if (fooCallCount == 2) {
+ ok(true, "If we got here we didn't crash, excellent.");
+ onTestsFinished();
+ }
+ }
+
+ function onLoad() {
+ foo();
+ setTimeout(() => foo(), 0);
+ }
+ ]]></script>
+</window>
diff --git a/widget/tests/chrome.ini b/widget/tests/chrome.ini
new file mode 100644
index 0000000000..8395374ce0
--- /dev/null
+++ b/widget/tests/chrome.ini
@@ -0,0 +1,134 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ empty_window.xhtml
+ clipboard_helper.js
+
+[test_alwaysontop_focus.xhtml]
+
+# Privacy relevant
+[test_bug1123480.xhtml]
+skip-if = os == "win" && bits == 32
+
+[test_bug343416.xhtml]
+skip-if = debug
+[test_bug413277.html]
+skip-if = toolkit != "cocoa" # Cocoa widget test
+[test_bug428405.xhtml]
+skip-if = toolkit != "cocoa" # Cocoa widget test
+[test_bug429954.xhtml]
+support-files = window_bug429954.xhtml
+[test_bug444800.xhtml]
+[test_bug466599.xhtml]
+skip-if = toolkit != "cocoa" # Cocoa widget test
+[test_bug478536.xhtml]
+skip-if = true # Bug 561929
+support-files = window_bug478536.xhtml
+[test_bug485118.xhtml]
+skip-if = toolkit != "cocoa" # Cocoa widget test
+[test_bug517396.xhtml]
+skip-if = (verify && (os == 'win'))
+[test_bug522217.xhtml]
+tags = fullscreen
+skip-if = toolkit != "cocoa" # Cocoa widget test
+support-files = window_bug522217.xhtml
+[test_bug538242.xhtml]
+support-files = window_bug538242.xhtml
+[test_bug565392.html]
+skip-if = toolkit != "windows"
+[test_bug586713.xhtml]
+skip-if = toolkit != "cocoa" # Cocoa widget test
+support-files = bug586713_window.xhtml
+[test_bug593307.xhtml]
+support-files = window_bug593307_offscreen.xhtml window_bug593307_centerscreen.xhtml
+[test_bug596600.xhtml]
+support-files = file_bug596600.html
+skip-if = toolkit != "cocoa" # Cocoa widget test
+[test_bug673301.xhtml]
+skip-if = toolkit != "cocoa" # Cocoa widget test
+[test_bug760802.xhtml]
+[test_clipboard.xhtml]
+[test_clipboard_asyncSetData.xhtml]
+[test_clipboard_cache.xhtml]
+[test_clipboard_owner.xhtml]
+[test_composition_text_querycontent.xhtml]
+support-files = window_composition_text_querycontent.xhtml
+[test_ime_state_in_contenteditable_on_readonly_change_in_parent.html]
+support-files =
+ file_ime_state_test_helper.js
+ file_test_ime_state_in_contenteditable_on_readonly_change.js
+[test_ime_state_in_plugin_in_parent.html]
+support-files =
+ file_ime_state_test_helper.js
+[test_ime_state_in_text_control_on_reframe_in_parent.html]
+support-files =
+ file_ime_state_test_helper.js
+ file_test_ime_state_in_text_control_on_reframe.js
+[test_ime_state_on_editable_state_change_in_parent.html]
+support-files =
+ file_ime_state_test_helper.js
+[test_ime_state_on_focus_move_in_parent.html]
+support-files =
+ file_ime_state_test_helper.js
+ file_test_ime_state_on_focus_move.js
+[test_ime_state_on_input_type_change_in_parent.html]
+skip-if = true # Bug 1817704
+support-files =
+ file_ime_state_test_helper.js
+ file_test_ime_state_on_input_type_change.js
+[test_ime_state_on_readonly_change_in_parent.html]
+support-files =
+ file_ime_state_test_helper.js
+ file_test_ime_state_on_readonly_change.js
+[test_ime_state_others_in_parent.html]
+support-files = window_imestate_iframes.html
+[test_input_events_on_deactive_window.xhtml]
+support-files = file_input_events_on_deactive_window.html
+[test_key_event_counts.xhtml]
+skip-if = toolkit != "cocoa" # Cocoa widget test
+[test_keycodes.xhtml]
+[test_mouse_scroll.xhtml]
+skip-if = toolkit != "windows" # Windows widget test
+support-files =
+ window_mouse_scroll_win.html
+ window_mouse_scroll_win_2.html
+[test_native_key_bindings_mac.html]
+skip-if =
+ toolkit != "cocoa" # Cocoa widget test
+ verify
+[test_native_menus.xhtml]
+skip-if = toolkit != "cocoa" # Cocoa widget test
+support-files = native_menus_window.xhtml
+[test_panel_mouse_coords.xhtml]
+skip-if = toolkit == "windows" # bug 1009955
+[test_platform_colors.xhtml]
+#skip-if = toolkit != "cocoa" # Cocoa widget test
+skip-if = true # Bug 1207190
+[test_position_on_resize.xhtml]
+skip-if =
+ verify && (os == 'win')
+ (os == "linux" && bits == 64) # Bug 1616760
+[test_secure_input.html]
+support-files = file_secure_input.html
+skip-if = toolkit != "cocoa" # Cocoa widget test
+[test_sizemode_events.xhtml]
+[test_standalone_native_menu.xhtml]
+skip-if = toolkit != "cocoa" # Cocoa widget test
+support-files = standalone_native_menu_window.xhtml
+[test_system_font_changes.xhtml]
+support-files = system_font_changes.xhtml
+run-if = toolkit == 'gtk' # Currently the test works on only gtk3
+[test_system_status_bar.xhtml]
+skip-if = toolkit != "cocoa" # Cocoa widget test
+[test_taskbar_progress.xhtml]
+skip-if =
+ toolkit != "cocoa" && toolkit != "windows"
+ (os == "win" && os_version == "10.0" && !ccov) # Bug 1456811
+[test_transferable_overflow.xhtml]
+skip-if = (verify && (os == 'mac' || os == 'linux'))
+[test_wheeltransaction.xhtml]
+support-files = window_wheeltransaction.xhtml
+
+# Windows
+# taskbar_previews.xhtml
+# window_state_windows.xhtml
diff --git a/widget/tests/clipboard_helper.js b/widget/tests/clipboard_helper.js
new file mode 100644
index 0000000000..7315bd19ac
--- /dev/null
+++ b/widget/tests/clipboard_helper.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const clipboard = SpecialPowers.Services.clipboard;
+const clipboardTypes = [
+ clipboard.kGlobalClipboard,
+ clipboard.kSelectionClipboard,
+ clipboard.kFindClipboard,
+ clipboard.kSelectionCache,
+];
+
+function cleanupAllClipboard() {
+ clipboardTypes.forEach(function (type) {
+ if (clipboard.isClipboardTypeSupported(type)) {
+ info(`cleanup clipboard ${type}`);
+ clipboard.emptyClipboard(type);
+ }
+ });
+}
+
+function generateRandomString() {
+ return "random number: " + Math.random();
+}
+
+function writeStringToClipboard(
+ aStr,
+ aFlavor,
+ aClipboardType,
+ aClipboardOwner = null,
+ aAsync = false
+) {
+ let trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
+ Ci.nsITransferable
+ );
+ trans.init(null);
+ trans.addDataFlavor(aFlavor);
+
+ let supportsStr = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ supportsStr.data = aStr;
+ trans.setTransferData(aFlavor, supportsStr);
+
+ if (aAsync) {
+ let request = clipboard.asyncSetData(aClipboardType);
+ request.setData(trans, aClipboardOwner);
+ return;
+ }
+
+ clipboard.setData(trans, aClipboardOwner, aClipboardType);
+}
+
+function writeRandomStringToClipboard(
+ aFlavor,
+ aClipboardType,
+ aClipboardOwner = null,
+ aAsync = false
+) {
+ let randomString = generateRandomString();
+ writeStringToClipboard(
+ randomString,
+ aFlavor,
+ aClipboardType,
+ aClipboardOwner,
+ aAsync
+ );
+ return randomString;
+}
+
+function getClipboardData(aFlavor, aClipboardType) {
+ var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
+ Ci.nsITransferable
+ );
+ trans.init(null);
+ trans.addDataFlavor(aFlavor);
+ clipboard.getData(trans, aClipboardType);
+
+ var data = {};
+ trans.getTransferData(aFlavor, data);
+ return data.value.QueryInterface(SpecialPowers.Ci.nsISupportsString).data;
+}
diff --git a/widget/tests/empty_window.xhtml b/widget/tests/empty_window.xhtml
new file mode 100644
index 0000000000..f0e01761d2
--- /dev/null
+++ b/widget/tests/empty_window.xhtml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Empty window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
diff --git a/widget/tests/file_bug596600.html b/widget/tests/file_bug596600.html
new file mode 100644
index 0000000000..1b178a6b68
--- /dev/null
+++ b/widget/tests/file_bug596600.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+Content page
+</body>
diff --git a/widget/tests/file_ime_state_test_helper.js b/widget/tests/file_ime_state_test_helper.js
new file mode 100644
index 0000000000..0cee5c036f
--- /dev/null
+++ b/widget/tests/file_ime_state_test_helper.js
@@ -0,0 +1,197 @@
+"use strict";
+
+function IsIMEOpenStateSupported() {
+ // We support to control IME open state on Windows and Mac actually. However,
+ // we cannot test it on Mac if the current keyboard layout is not CJK. And also
+ // we cannot test it on Win32 if the system didn't be installed IME. So,
+ // currently we should not run the open state testing.
+ return false;
+}
+
+/**
+ * @param {Node} aNode
+ */
+function nodeIsInShadowDOM(aNode) {
+ for (let node = aNode; node; node = node.parentNode) {
+ if (node instanceof ShadowRoot) {
+ return true;
+ }
+ if (node == node.parentNode) {
+ break;
+ }
+ }
+ return false;
+}
+
+/**
+ * @param {Node} aNode
+ */
+function nodeIsInDesignMode(aNode) {
+ return (
+ aNode.isConnected &&
+ !nodeIsInShadowDOM(aNode) &&
+ aNode.ownerDocument.designMode == "on"
+ );
+}
+
+/**
+ * param {Node} aNode
+ */
+function getEditingHost(aNode) {
+ if (nodeIsInDesignMode(aNode)) {
+ return aNode.ownerDocument.documentElement;
+ }
+ for (
+ let element =
+ aNode.nodeType == Node.ELEMENT_NODE ? aNode : aNode.parentElement;
+ element;
+ element = element.parentElement
+ ) {
+ const contenteditable = element.getAttribute("contenteditable");
+ if (contenteditable === "true" || contenteditable === "") {
+ return element;
+ }
+ if (contenteditable === "false") {
+ return null;
+ }
+ }
+ return null;
+}
+
+/**
+ * @param {Node} aNode
+ */
+function nodeIsEditable(aNode) {
+ if (nodeIsInDesignMode(aNode)) {
+ return true;
+ }
+ if (!aNode.isConnected) {
+ return false;
+ }
+ return getEditingHost(aNode) != null;
+}
+
+/**
+ * @param {Element} aElement
+ */
+function elementIsEditingHost(aElement) {
+ return (
+ nodeIsEditable(aElement) &&
+ (!aElement.parentElement || !getEditingHost(aElement) == aElement)
+ );
+}
+
+/**
+ * @returns {Element} Retrieve focused element. If focused element is a element
+ * in UA widget, this returns its host element. E.g., when
+ * a button in the controls of <audio> or <video> has focus,
+ * this returns the <video> or <audio>.
+ */
+function getFocusedElementOrUAWidgetHost() {
+ const focusedElement = SpecialPowers.focusManager.focusedElement;
+ if (SpecialPowers.wrap(focusedElement)?.containingShadowRoot?.isUAWidget()) {
+ return focusedElement.containingShadowRoot.host;
+ }
+ return focusedElement;
+}
+
+class TIPWrapper {
+ #mTIP = null;
+ #mFocusBlurNotifications = [];
+ #mFocusBlurListener;
+ #mWindow;
+
+ constructor(aWindow) {
+ this.#mWindow = aWindow;
+ this.#mTIP = Cc["@mozilla.org/text-input-processor;1"].createInstance(
+ Ci.nsITextInputProcessor
+ );
+ if (!this.beginInputTransactionForTests()) {
+ this.#mTIP = null;
+ }
+ }
+
+ beginInputTransactionForTests() {
+ return this.#mTIP.beginInputTransactionForTests(
+ this.#mWindow,
+ this.#observer.bind(this)
+ );
+ }
+
+ typeA() {
+ const AKey = new this.#mWindow.KeyboardEvent("", {
+ key: "a",
+ code: "KeyA",
+ keyCode: this.#mWindow.KeyboardEvent.DOM_VK_A,
+ });
+ this.#mTIP.keydown(AKey);
+ this.#mTIP.keyup(AKey);
+ }
+
+ isAvailable() {
+ return this.#mTIP != null;
+ }
+
+ #observer(aTIP, aNotification) {
+ if (aTIP != this.#mTIP) {
+ return false;
+ }
+ switch (aNotification.type) {
+ case "request-to-commit":
+ this.#mTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ this.#mTIP.cancelComposition();
+ break;
+ case "notify-focus":
+ case "notify-blur":
+ this.#mFocusBlurNotifications.push(aNotification.type);
+ if (this.#mFocusBlurListener) {
+ this.#mFocusBlurListener(aNotification.type);
+ }
+ break;
+ }
+ return true;
+ }
+
+ get TIP() {
+ return this.#mTIP;
+ }
+
+ /**
+ * @param {Function} aListener
+ */
+ set onIMEFocusBlur(aListener) {
+ this.#mFocusBlurListener = aListener;
+ }
+
+ get focusBlurNotifications() {
+ return this.#mFocusBlurNotifications.concat();
+ }
+
+ get numberOfFocusNotifications() {
+ return this.#mFocusBlurNotifications.filter(t => t == "notify-focus")
+ .length;
+ }
+ get numberOfBlurNotifications() {
+ return this.#mFocusBlurNotifications.filter(t => t == "notify-blur").length;
+ }
+
+ get IMEHasFocus() {
+ return (
+ !!this.#mFocusBlurNotifications.length &&
+ this.#mFocusBlurNotifications[this.#mFocusBlurNotifications.length - 1] ==
+ "notify-focus"
+ );
+ }
+
+ clearFocusBlurNotifications() {
+ this.#mFocusBlurNotifications = [];
+ }
+
+ destroy() {
+ this.#mTIP = null;
+ this.#mFocusBlurListener = null;
+ this.#mFocusBlurNotifications = [];
+ }
+}
diff --git a/widget/tests/file_input_events_on_deactive_window.html b/widget/tests/file_input_events_on_deactive_window.html
new file mode 100644
index 0000000000..e733adbb9d
--- /dev/null
+++ b/widget/tests/file_input_events_on_deactive_window.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ this is an active window.
+</body>
+</html>
diff --git a/widget/tests/file_secure_input.html b/widget/tests/file_secure_input.html
new file mode 100644
index 0000000000..28fec7b44b
--- /dev/null
+++ b/widget/tests/file_secure_input.html
@@ -0,0 +1 @@
+<input id="text" type"text"><input id="password" type"password">
diff --git a/widget/tests/file_test_ime_state_in_contenteditable_on_readonly_change.js b/widget/tests/file_test_ime_state_in_contenteditable_on_readonly_change.js
new file mode 100644
index 0000000000..9f1ab2d305
--- /dev/null
+++ b/widget/tests/file_test_ime_state_in_contenteditable_on_readonly_change.js
@@ -0,0 +1,616 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from file_ime_state_test_helper.js */
+
+class IMEStateInContentEditableOnReadonlyChangeTester {
+ // Runner only fields.
+ #mEditingHost;
+ #mFocusElement;
+ #mWindow;
+
+ // Tester only fields.
+ #mTIPWrapper;
+ #mWindowUtils;
+
+ clear() {
+ this.#mTIPWrapper?.clearFocusBlurNotifications();
+ this.#mTIPWrapper = null;
+ }
+
+ #flushPendingIMENotifications() {
+ return new Promise(resolve =>
+ this.#mWindow.requestAnimationFrame(() =>
+ this.#mWindow.requestAnimationFrame(resolve)
+ )
+ );
+ }
+
+ #getExpectedIMEState() {
+ // Although if this.#mFocusElement is a <button>, its `.focus()` call
+ // focus it, but caret is not set into it and following typing is handled
+ // outside the <button>. Therefore, anyway the enabled state should be
+ // "enabled".
+ return SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED;
+ }
+
+ /**
+ * @param {Element} aEditingHost The editing host.
+ * @param {Element} aFocusElement Element which should have focus. This must
+ * be an inclusive descendant of the editing host and editable element.
+ * @param {Window} aWindow [optional] The window.
+ * @returns {object} Expected result of initial state.
+ */
+ async prepareToRun(aEditingHost, aFocusElement, aWindow = window) {
+ this.#mWindow = aWindow;
+ this.#mEditingHost = aEditingHost;
+ this.#mFocusElement = aFocusElement;
+
+ if (this.#mEditingHost.ownerDocument.activeElement) {
+ this.#mEditingHost.ownerDocument.activeElement.blur();
+ await this.#flushPendingIMENotifications();
+ }
+
+ this.#mWindow.focus();
+ this.#mEditingHost.setAttribute("contenteditable", "");
+ this.#mFocusElement.focus();
+
+ await this.#flushPendingIMENotifications();
+
+ const expectedIMEState = this.#getExpectedIMEState();
+ return {
+ description: `when initialized with setting focus to ${
+ this.#mFocusElement == this.#mEditingHost
+ ? "the editing host"
+ : `<${this.#mFocusElement.tagName.toLowerCase()}>`
+ }`,
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ };
+ }
+
+ /**
+ * @param {object} aExpectedResult The expected result of the test.
+ */
+ #checkResult(aExpectedResult) {
+ const description = `IMEStateInContentEditableOnReadonlyChangeTester`;
+ is(
+ this.#mWindowUtils.IMEStatus,
+ aExpectedResult.expectedIMEState,
+ `${description}: IME enabled state should be expected one ${aExpectedResult.description}`
+ );
+ is(
+ this.#mTIPWrapper.IMEHasFocus,
+ aExpectedResult.expectedIMEFocus,
+ `${description}: IME should ${
+ aExpectedResult.expectedIMEFocus ? "" : "not "
+ }have focus ${aExpectedResult.description}`
+ );
+ }
+
+ /**
+ * @param {object} aExpectedResult The expected result of prepareToRun().
+ * @param {Window} aWindow The window to check IME state.
+ * @param {TIPWrapper} aTIPWrapper The TIPWrapper for aWindow.
+ */
+ checkResultOfPreparation(aExpectedResult, aWindow, aTIPWrapper) {
+ this.#mWindowUtils = SpecialPowers.wrap(aWindow).windowUtils;
+ this.#mTIPWrapper = aTIPWrapper;
+ this.#checkResult(aExpectedResult);
+ }
+
+ /**
+ * @returns {object} The expected result.
+ */
+ async runToMakeHTMLEditorReadonly() {
+ const htmlEditor = SpecialPowers.wrap(this.#mWindow).docShell.editor;
+ htmlEditor.flags |= SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
+
+ await this.#flushPendingIMENotifications();
+
+ return {
+ description:
+ this.#mFocusElement == this.#mEditingHost
+ ? "when the editing host has focus"
+ : `when <${this.#mFocusElement.tagName.toLowerCase()}> has focus`,
+ expectedIMEState: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ expectedIMEFocus: false,
+ };
+ }
+
+ /**
+ * @param {object} aExpectedResult The expected result of runToMakeHTMLEditorReadonly().
+ */
+ checkResultOfMakingHTMLEditorReadonly(aExpectedResult) {
+ this.#checkResult(aExpectedResult);
+ }
+
+ /**
+ * @returns {object} The expected result.
+ */
+ async runToMakeHTMLEditorEditable() {
+ const htmlEditor = SpecialPowers.wrap(this.#mWindow).docShell.editor;
+ htmlEditor.flags &= ~SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
+
+ await this.#flushPendingIMENotifications();
+
+ const expectedIMEState = this.#getExpectedIMEState();
+ return {
+ description:
+ this.#mFocusElement == this.#mEditingHost
+ ? "when the editing host has focus"
+ : `when <${this.#mFocusElement.tagName.toLowerCase()}> has focus`,
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ };
+ }
+
+ /**
+ * @param {object} aExpectedResult The expected result of runToMakeHTMLEditorEditable().
+ */
+ checkResultOfMakingHTMLEditorEditable(aExpectedResult) {
+ this.#checkResult(aExpectedResult);
+ }
+
+ async runToRemoveContentEditableAttribute() {
+ this.#mEditingHost.removeAttribute("contenteditable");
+
+ await this.#flushPendingIMENotifications();
+
+ return {
+ description:
+ this.#mFocusElement == this.#mEditingHost
+ ? "after removing contenteditable attribute when the editing host has focus"
+ : `after removing contenteditable attribute when <${this.#mFocusElement.tagName.toLowerCase()}> has focus`,
+ expectedIMEState: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ expectedIMEFocus: false,
+ };
+ }
+
+ /**
+ * @param {object} aExpectedResult The expected result of runToRemoveContentEditableAttribute().
+ */
+ checkResultOfRemovingContentEditableAttribute(aExpectedResult) {
+ this.#checkResult(aExpectedResult);
+ }
+}
+
+class IMEStateOfTextControlInContentEditableOnReadonlyChangeTester {
+ static #sTextControls = [
+ {
+ tag: "input",
+ type: "text",
+ readonly: false,
+ },
+ {
+ tag: "input",
+ type: "text",
+ readonly: true,
+ },
+ {
+ tag: "textarea",
+ readonly: false,
+ },
+ {
+ tag: "textarea",
+ readonly: true,
+ },
+ ];
+
+ static get numberOfTextControlTypes() {
+ return IMEStateOfTextControlInContentEditableOnReadonlyChangeTester
+ .#sTextControls.length;
+ }
+
+ static #createElement(aDocument, aTextControl) {
+ const textControl = aDocument.createElement(aTextControl.tag);
+ if (aTextControl.type !== undefined) {
+ textControl.setAttribute("type", aTextControl.type);
+ }
+ if (aTextControl.readonly) {
+ textControl.setAttribute("readonly", "");
+ }
+ return textControl;
+ }
+
+ #getDescription() {
+ return `<${this.#mTextControl.tag}${
+ this.#mTextControl.type !== undefined
+ ? ` type=${this.#mTextControl.type}`
+ : ""
+ }${this.#mTextControl.readonly ? " readonly" : ""}>`;
+ }
+
+ #getExpectedIMEState() {
+ return this.#mTextControl.readonly
+ ? SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED
+ : SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED;
+ }
+
+ #flushPendingIMENotifications() {
+ return new Promise(resolve =>
+ this.#mWindow.requestAnimationFrame(() =>
+ this.#mWindow.requestAnimationFrame(resolve)
+ )
+ );
+ }
+
+ // Runner only fields.
+ #mEditingHost;
+ #mTextControl;
+ #mTextControlElement;
+ #mWindow;
+
+ // Checker only fields.
+ #mWindowUtils;
+ #mTIPWrapper;
+
+ clear() {
+ this.#mTIPWrapper?.clearFocusBlurNotifications();
+ this.#mTIPWrapper = null;
+ }
+
+ /**
+ * @param {number} aIndex Index of the test.
+ * @param {Element} aEditingHost The editing host which will have a text control.
+ * @param {Window} aWindow [optional] The DOM window containing aEditingHost.
+ * @returns {object} Expected result of initial state.
+ */
+ async prepareToRun(aIndex, aEditingHost, aWindow = window) {
+ this.#mWindow = aWindow;
+ this.#mEditingHost = aEditingHost;
+ this.#mEditingHost.ownerDocument.activeElement?.blur();
+ this.#mEditingHost.removeAttribute("contenteditable");
+ this.#mTextControlElement?.remove();
+ await this.#flushPendingIMENotifications();
+ this.#mTextControl =
+ IMEStateOfTextControlInContentEditableOnReadonlyChangeTester.#sTextControls[
+ aIndex
+ ];
+ this.#mTextControlElement =
+ IMEStateOfTextControlInContentEditableOnReadonlyChangeTester.#createElement(
+ this.#mEditingHost.ownerDocument,
+ this.#mTextControl
+ );
+ this.#mEditingHost.appendChild(this.#mTextControlElement);
+ this.#mTextControlElement.focus();
+ await this.#flushPendingIMENotifications();
+ const expectedIMEState = this.#getExpectedIMEState();
+ return {
+ description: `when ${this.#getDescription()} simply has focus`,
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ };
+ }
+
+ #checkResult(aExpectedResult) {
+ const description =
+ "IMEStateOfTextControlInContentEditableOnReadonlyChangeTester";
+ is(
+ this.#mWindowUtils.IMEStatus,
+ aExpectedResult.expectedIMEState,
+ `${description}: IME state should be proper one for the text control ${aExpectedResult.description}`
+ );
+ is(
+ this.#mTIPWrapper.IMEHasFocus,
+ aExpectedResult.expectedIMEFocus,
+ `${description}: IME should ${
+ aExpectedResult.expectedIMEFocus ? "" : "not "
+ }have focus ${aExpectedResult.description}`
+ );
+ }
+
+ /**
+ * @param {object} aExpectedResult The expected result returned by prepareToRun().
+ * @param {Window} aWindow The window whose IME state should be checked.
+ * @param {TIPWrapper} aTIPWrapper The TIP wrapper of aWindow.
+ */
+ checkResultOfPreparation(aExpectedResult, aWindow, aTIPWrapper) {
+ this.#mWindowUtils = SpecialPowers.wrap(aWindow).windowUtils;
+ this.#mTIPWrapper = aTIPWrapper;
+ this.#checkResult(aExpectedResult);
+ }
+
+ async runToMakeParentEditingHost() {
+ this.#mEditingHost.setAttribute("contenteditable", "");
+ await this.#flushPendingIMENotifications();
+ const expectedIMEState = this.#getExpectedIMEState();
+ return {
+ description: `when parent of ${this.#getDescription()} becomes contenteditable`,
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ };
+ }
+
+ checkResultOfMakingParentEditingHost(aExpectedResult) {
+ this.#checkResult(aExpectedResult);
+ }
+
+ async runToMakeHTMLEditorReadonly() {
+ const editor = SpecialPowers.wrap(this.#mWindow).docShell.editor;
+ editor.flags |= SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
+ await this.#flushPendingIMENotifications();
+ const expectedIMEState = this.#getExpectedIMEState();
+ return {
+ description: `when HTMLEditor for parent of ${this.#getDescription()} becomes readonly`,
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ };
+ }
+
+ checkResultOfMakingHTMLEditorReadonly(aExpectedResult) {
+ this.#checkResult(aExpectedResult);
+ }
+
+ async runToMakeHTMLEditorEditable() {
+ const editor = SpecialPowers.wrap(this.#mWindow).docShell.editor;
+ editor.flags &= ~SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
+ await this.#flushPendingIMENotifications();
+ const expectedIMEState = this.#getExpectedIMEState();
+ return {
+ description: `when HTMLEditor for parent of ${this.#getDescription()} becomes editable`,
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ };
+ }
+
+ checkResultOfMakingHTMLEditorEditable(aExpectedResult) {
+ this.#checkResult(aExpectedResult);
+ }
+
+ async runToMakeParentNonEditingHost() {
+ this.#mEditingHost.removeAttribute("contenteditable");
+ await this.#flushPendingIMENotifications();
+ const expectedIMEState = this.#getExpectedIMEState();
+ return {
+ description: `when parent of ${this.#getDescription()} becomes non-editable`,
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ };
+ }
+
+ checkResultOfMakingParentNonEditable(aExpectedResult) {
+ this.#checkResult(aExpectedResult);
+ }
+}
+
+class IMEStateOutsideContentEditableOnReadonlyChangeTester {
+ static #sFocusTargets = [
+ {
+ tag: "input",
+ type: "text",
+ readonly: false,
+ },
+ {
+ tag: "input",
+ type: "text",
+ readonly: true,
+ },
+ {
+ tag: "textarea",
+ readonly: false,
+ },
+ {
+ tag: "textarea",
+ readonly: true,
+ },
+ {
+ tag: "button",
+ },
+ {
+ tag: "body",
+ },
+ ];
+
+ static get numberOfFocusTargets() {
+ return IMEStateOutsideContentEditableOnReadonlyChangeTester.#sFocusTargets
+ .length;
+ }
+
+ static #maybeCreateElement(aDocument, aFocusTarget) {
+ if (aFocusTarget.tag == "body") {
+ return null;
+ }
+ const element = aDocument.createElement(aFocusTarget.tag);
+ if (aFocusTarget.type !== undefined) {
+ element.setAttribute("type", aFocusTarget.type);
+ }
+ if (aFocusTarget.readonly) {
+ element.setAttribute("readonly", "");
+ }
+ return element;
+ }
+
+ #getDescription() {
+ return `<${this.#mFocusTarget.tag}${
+ this.#mFocusTarget.type !== undefined
+ ? ` type=${this.#mFocusTarget.type}`
+ : ""
+ }${this.#mFocusTarget.readonly ? " readonly" : ""}>`;
+ }
+
+ #getExpectedIMEState() {
+ return this.#mFocusTarget.readonly ||
+ this.#mFocusTarget.tag == "button" ||
+ this.#mFocusTarget.tag == "body"
+ ? SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED
+ : SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED;
+ }
+
+ #flushPendingIMENotifications() {
+ return new Promise(resolve =>
+ this.#mWindow.requestAnimationFrame(() =>
+ this.#mWindow.requestAnimationFrame(resolve)
+ )
+ );
+ }
+
+ // Runner only fields.
+ #mBody;
+ #mEditingHost;
+ #mFocusTarget;
+ #mFocusTargetElement;
+ #mWindow;
+
+ // Checker only fields.
+ #mWindowUtils;
+ #mTIPWrapper;
+
+ clear() {
+ this.#mTIPWrapper?.clearFocusBlurNotifications();
+ this.#mTIPWrapper = null;
+ }
+
+ /**
+ * @param {number} aIndex Index of the test.
+ * @param {Element} aEditingHost The editing host.
+ * @param {Window} aWindow [optional] The DOM window containing aEditingHost.
+ * @returns {object} Expected result of initial state.
+ */
+ async prepareToRun(aIndex, aEditingHost, aWindow = window) {
+ this.#mWindow = aWindow;
+ this.#mEditingHost = aEditingHost;
+ this.#mEditingHost.removeAttribute("contenteditable");
+ this.#mBody = this.#mEditingHost.ownerDocument.body;
+ this.#mBody.ownerDocument.activeElement?.blur();
+ if (this.#mFocusTargetElement != this.#mBody) {
+ this.#mFocusTargetElement?.remove();
+ }
+ await this.#flushPendingIMENotifications();
+ this.#mFocusTarget =
+ IMEStateOutsideContentEditableOnReadonlyChangeTester.#sFocusTargets[
+ aIndex
+ ];
+ this.#mFocusTargetElement =
+ IMEStateOutsideContentEditableOnReadonlyChangeTester.#maybeCreateElement(
+ this.#mBody.ownerDocument,
+ this.#mFocusTarget
+ );
+ if (this.#mFocusTargetElement) {
+ this.#mBody.appendChild(this.#mFocusTargetElement);
+ this.#mFocusTargetElement.focus();
+ }
+ await this.#flushPendingIMENotifications();
+ const expectedIMEState = this.#getExpectedIMEState();
+ return {
+ description: `when ${this.#getDescription()} simply has focus`,
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ };
+ }
+
+ #checkResult(aExpectedResult) {
+ const description = "IMEStateOutsideContentEditableOnReadonlyChangeTester";
+ is(
+ this.#mWindowUtils.IMEStatus,
+ aExpectedResult.expectedIMEState,
+ `${description}: IME state should be proper one for the focused element ${aExpectedResult.description}`
+ );
+ is(
+ this.#mTIPWrapper.IMEHasFocus,
+ aExpectedResult.expectedIMEFocus,
+ `${description}: IME should ${
+ aExpectedResult.expectedIMEFocus ? "" : "not "
+ }have focus ${aExpectedResult.description}`
+ );
+ }
+
+ /**
+ * @param {object} aExpectedResult The expected result returned by prepareToRun().
+ * @param {Window} aWindow The window whose IME state should be checked.
+ * @param {TIPWrapper} aTIPWrapper The TIP wrapper of aWindow.
+ */
+ checkResultOfPreparation(aExpectedResult, aWindow, aTIPWrapper) {
+ this.#mWindowUtils = SpecialPowers.wrap(aWindow).windowUtils;
+ this.#mTIPWrapper = aTIPWrapper;
+ this.#checkResult(aExpectedResult);
+ }
+
+ async runToMakeParentEditingHost() {
+ this.#mEditingHost.setAttribute("contenteditable", "");
+ await this.#flushPendingIMENotifications();
+ const expectedIMEState = this.#getExpectedIMEState();
+ return {
+ description: `when parent of ${this.#getDescription()} becomes contenteditable`,
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ };
+ }
+
+ checkResultOfMakingParentEditingHost(aExpectedResult) {
+ this.#checkResult(aExpectedResult);
+ }
+
+ async runToMakeHTMLEditorReadonly() {
+ const editor = SpecialPowers.wrap(this.#mWindow).docShell.editor;
+ editor.flags |= SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
+ await this.#flushPendingIMENotifications();
+ const expectedIMEState = this.#getExpectedIMEState();
+ return {
+ description: `when HTMLEditor for parent of ${this.#getDescription()} becomes readonly`,
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ };
+ }
+
+ checkResultOfMakingHTMLEditorReadonly(aExpectedResult) {
+ this.#checkResult(aExpectedResult);
+ }
+
+ async runToMakeHTMLEditorEditable() {
+ const editor = SpecialPowers.wrap(this.#mWindow).docShell.editor;
+ editor.flags &= ~SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
+ await this.#flushPendingIMENotifications();
+ const expectedIMEState = this.#getExpectedIMEState();
+ return {
+ description: `when HTMLEditor for parent of ${this.#getDescription()} becomes editable`,
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ };
+ }
+
+ checkResultOfMakingHTMLEditorEditable(aExpectedResult) {
+ this.#checkResult(aExpectedResult);
+ }
+
+ async runToMakeParentNonEditingHost() {
+ this.#mEditingHost.removeAttribute("contenteditable");
+ await this.#flushPendingIMENotifications();
+ const expectedIMEState = this.#getExpectedIMEState();
+ return {
+ description: `when parent of ${this.#getDescription()} becomes non-editable`,
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ };
+ }
+
+ checkResultOfMakingParentNonEditable(aExpectedResult) {
+ this.#checkResult(aExpectedResult);
+ }
+}
diff --git a/widget/tests/file_test_ime_state_in_text_control_on_reframe.js b/widget/tests/file_test_ime_state_in_text_control_on_reframe.js
new file mode 100644
index 0000000000..719022b889
--- /dev/null
+++ b/widget/tests/file_test_ime_state_in_text_control_on_reframe.js
@@ -0,0 +1,190 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from file_ime_state_test_helper.js */
+
+// Bug 580388 and bug 808287
+class IMEStateInTextControlOnReframeTester {
+ static #sTextControls = [
+ {
+ tag: "input",
+ type: "text",
+ },
+ {
+ tag: "input",
+ type: "password",
+ },
+ {
+ tag: "textarea",
+ },
+ ];
+
+ static get numberOfTextControlTypes() {
+ return IMEStateInTextControlOnReframeTester.#sTextControls.length;
+ }
+
+ #createElement() {
+ const textControl = this.#mDocument.createElement(this.#mTextControl.tag);
+ if (this.#mTextControl.type !== undefined) {
+ textControl.setAttribute("type", this.#mTextControl.type);
+ }
+ return textControl;
+ }
+
+ #getDescription() {
+ return `<${this.#mTextControl.tag}${
+ this.#mTextControl.type !== undefined
+ ? ` type=${this.#mTextControl.type}`
+ : ""
+ }>`;
+ }
+
+ #getExpectedIMEState() {
+ return this.#mTextControl.type == "password"
+ ? SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_PASSWORD
+ : SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED;
+ }
+
+ #flushPendingIMENotifications() {
+ return new Promise(resolve =>
+ this.#mWindow.requestAnimationFrame(() =>
+ this.#mWindow.requestAnimationFrame(resolve)
+ )
+ );
+ }
+
+ // Runner only fields.
+ #mTextControl;
+ #mTextControlElement;
+ #mWindow;
+ #mDocument;
+
+ // Checker only fields.
+ #mWindowUtils;
+ #mTIPWrapper;
+
+ clear() {
+ this.#mTIPWrapper?.clearFocusBlurNotifications();
+ this.#mTIPWrapper = null;
+ }
+
+ /**
+ * @param {number} aIndex Index of the test.
+ * @param {Element} aDocument The document to run the test.
+ * @param {Window} aWindow [optional] The DOM window for aDocument.
+ * @returns {object} Expected result of initial state.
+ */
+ async prepareToRun(aIndex, aDocument, aWindow = window) {
+ this.#mWindow = aWindow;
+ this.#mDocument = aDocument;
+ this.#mDocument.activeElement?.blur();
+ this.#mTextControlElement?.remove();
+ await this.#flushPendingIMENotifications();
+ this.#mTextControl =
+ IMEStateInTextControlOnReframeTester.#sTextControls[aIndex];
+ this.#mTextControlElement = this.#createElement();
+ this.#mDocument.body.appendChild(this.#mTextControlElement);
+ this.#mTextControlElement.focus();
+ this.#mTextControlElement.style.overflow = "visible";
+ this.#mTextControlElement.addEventListener(
+ "input",
+ aEvent => {
+ aEvent.target.style.overflow = "hidden";
+ },
+ {
+ capture: true,
+ }
+ );
+ await this.#flushPendingIMENotifications();
+ const expectedIMEState = this.#getExpectedIMEState();
+ return {
+ description: `when ${this.#getDescription()} has focus`,
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ expectedNumberOfFocusNotifications: 1,
+ };
+ }
+
+ #checkResult(aExpectedResult) {
+ const description = "IMEStateInTextControlOnReframeTester";
+ is(
+ this.#mWindowUtils.IMEStatus,
+ aExpectedResult.expectedIMEState,
+ `${description}: IME state should be proper one for the text control ${aExpectedResult.description}`
+ );
+ is(
+ this.#mTIPWrapper.IMEHasFocus,
+ aExpectedResult.expectedIMEFocus,
+ `${description}: IME should ${
+ aExpectedResult.expectedIMEFocus ? "" : "not "
+ }have focus ${aExpectedResult.description}`
+ );
+ if (aExpectedResult.numberOfFocusNotifications !== undefined) {
+ is(
+ this.#mTIPWrapper.numberOfFocusNotifications,
+ aExpectedResult.numberOfFocusNotifications,
+ `${description}: focus notifications should've been received ${
+ this.#mTIPWrapper.numberOfFocusNotifications
+ } times ${aExpectedResult.description}`
+ );
+ }
+ if (aExpectedResult.numberOfBlurNotifications !== undefined) {
+ is(
+ this.#mTIPWrapper.numberOfBlurNotifications,
+ aExpectedResult.numberOfBlurNotifications,
+ `${description}: blur notifications should've been received ${
+ this.#mTIPWrapper.numberOfBlurNotifications
+ } times ${aExpectedResult.description}`
+ );
+ }
+ }
+
+ /**
+ * @param {object} aExpectedResult The expected result returned by prepareToRun().
+ * @param {Window} aWindow The window whose IME state should be checked.
+ * @param {TIPWrapper} aTIPWrapper The TIP wrapper of aWindow.
+ */
+ checkResultAfterTypingA(aExpectedResult, aWindow, aTIPWrapper) {
+ this.#mWindowUtils = SpecialPowers.wrap(aWindow).windowUtils;
+ this.#mTIPWrapper = aTIPWrapper;
+ this.#checkResult(aExpectedResult);
+
+ this.#mTIPWrapper.clearFocusBlurNotifications();
+ }
+
+ async prepareToRun2() {
+ this.#mTextControlElement.addEventListener("focus", aEvent => {
+ // Perform a style change and flush it to trigger reframing.
+ aEvent.target.style.overflow = "visible";
+ aEvent.target.getBoundingClientRect();
+ });
+ this.#mTextControlElement.blur();
+ this.#mTextControlElement.focus();
+
+ await this.#flushPendingIMENotifications();
+
+ const expectedIMEState = this.#getExpectedIMEState();
+ return {
+ description: `when ${this.#getDescription()} is reframed by focus event listener`,
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ expectedNumberOfFocusNotifications: 1,
+ expectedNumberOfBlurNotifications: 1,
+ };
+ }
+
+ /**
+ * @param {object} aExpectedResult The expected result returned by prepareToRun().
+ */
+ checkResultAfterTypingA2(aExpectedResult) {
+ this.#checkResult(aExpectedResult);
+
+ this.#mTIPWrapper.clearFocusBlurNotifications();
+ }
+}
diff --git a/widget/tests/file_test_ime_state_on_focus_move.js b/widget/tests/file_test_ime_state_on_focus_move.js
new file mode 100644
index 0000000000..f7760d8a09
--- /dev/null
+++ b/widget/tests/file_test_ime_state_on_focus_move.js
@@ -0,0 +1,1588 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from file_ime_state_test_helper.js */
+
+class IMEStateWhenNoActiveElementTester {
+ #mDescription;
+
+ constructor(aDescription) {
+ this.#mDescription = aDescription;
+ }
+
+ async run(aDocument, aWindow = window) {
+ aWindow.focus();
+ aDocument.activeElement?.blur();
+
+ await new Promise(resolve =>
+ requestAnimationFrame(() => requestAnimationFrame(resolve))
+ ); // wait for sending IME notifications
+
+ return { designModeValue: aDocument.designMode };
+ }
+
+ check(aExpectedData, aWindow = window) {
+ const winUtils = SpecialPowers.wrap(aWindow).windowUtils;
+ if (aExpectedData.designModeValue == "on") {
+ is(
+ winUtils.IMEStatus,
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ `IMEStateWhenNoActiveElementTester(${
+ this.#mDescription
+ }): When no element has focus, IME should stay enabled in design mode`
+ );
+ } else {
+ is(
+ winUtils.IMEStatus,
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `IMEStateWhenNoActiveElementTester(${
+ this.#mDescription
+ }): When no element has focus, IME should be disabled`
+ );
+ }
+ }
+}
+
+class IMEStateOnFocusMoveTester {
+ // Common fields
+ #mDescription;
+ #mTest;
+ #mWindow;
+ #mWindowUtils;
+
+ // Only runner fields
+ #mCreatedElement;
+ #mCreatedElementForPreviousFocusedElement;
+ #mElementToSetFocus;
+ #mContainerIsEditable;
+
+ // Only checker fields
+ #mTIPWrapper;
+
+ constructor(aDescription, aIndex, aWindow = window) {
+ this.#mTest = IMEStateOnFocusMoveTester.#sTestList[aIndex];
+ this.#mDescription = `IMEStateOnFocusMoveTester(${aDescription}): ${
+ this.#mTest.description
+ }`;
+ this.#mWindow = aWindow;
+ this.#mWindowUtils = SpecialPowers.wrap(this.#mWindow).windowUtils;
+ }
+
+ /**
+ * prepareToRun should be called before run only in the process which will run the test.
+ */
+ async prepareToRun(aContainer) {
+ const doc = aContainer.ownerDocument;
+ this.#mTest = this.#resolveTest(this.#mTest, aContainer);
+ this.#mContainerIsEditable = nodeIsEditable(aContainer);
+ this.#mCreatedElement = this.#mTest.createElement(doc);
+ const waitForLoadIfIFrame = new Promise(resolve => {
+ if (this.#mCreatedElement.tagName == "IFRAME") {
+ this.#mCreatedElement.addEventListener("load", resolve, {
+ capture: true,
+ once: true,
+ });
+ } else {
+ resolve();
+ }
+ });
+ aContainer.appendChild(this.#mCreatedElement);
+ await waitForLoadIfIFrame;
+ this.#mElementToSetFocus = this.#mCreatedElement.contentDocument
+ ? this.#mCreatedElement.contentDocument.documentElement
+ : this.#mCreatedElement;
+ if (doc.designMode == "on") {
+ doc.activeElement?.blur();
+ } else if (this.#mContainerIsEditable) {
+ getEditingHost(aContainer).focus(); // FIXME: use editing host instead
+ } else {
+ this.#mCreatedElementForPreviousFocusedElement =
+ doc.createElement("input");
+ this.#mCreatedElementForPreviousFocusedElement.setAttribute(
+ "type",
+ this.#mTest.expectedEnabledValue ==
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED
+ ? "password"
+ : "text"
+ );
+ aContainer.appendChild(this.#mCreatedElementForPreviousFocusedElement);
+ this.#mCreatedElementForPreviousFocusedElement.focus();
+ }
+
+ await new Promise(resolve =>
+ requestAnimationFrame(() => requestAnimationFrame(resolve))
+ ); // wait for sending IME notifications
+
+ return {
+ designModeValue: doc.designMode,
+ containerIsEditable: this.#mContainerIsEditable,
+ isFocusable: this.#mTest.isFocusable,
+ focusEventFired: this.#mTest.focusEventIsExpected,
+ enabledValue: this.#mTest.expectedEnabledValue,
+ testedSubDocumentInDesignMode:
+ this.#mCreatedElement.contentDocument?.designMode == "on",
+ };
+ }
+
+ /**
+ * prepareToCheck should be called before calling run only in the process which will check the result.
+ */
+ prepareToCheck(aExpectedData, aTIPWrapper) {
+ info(`Starting ${this.#mDescription} (enable state check)...`);
+ this.#mTIPWrapper = aTIPWrapper;
+ this.#mTIPWrapper.onIMEFocusBlur = aNotificationType => {
+ switch (aNotificationType) {
+ case "notify-focus":
+ info(aNotificationType);
+ is(
+ this.#mWindowUtils.IMEStatus,
+ aExpectedData.enabledValue,
+ `${
+ this.#mDescription
+ }, IME should receive a focus notification after IME state is updated`
+ );
+ break;
+ case "notify-blur":
+ info(aNotificationType);
+ const changingStatus = !(
+ aExpectedData.containerIsEditable &&
+ aExpectedData.enabledValue ==
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED
+ );
+ if (aExpectedData.designModeValue == "on") {
+ is(
+ // FIXME: This is odd, but #mWindowUtils.IMEStatus sometimes IME_STATUS_PASSWORD
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ aExpectedData.enabledValue,
+ `${
+ this.#mDescription
+ }, IME should receive a blur notification after IME state is updated`
+ );
+ } else if (changingStatus) {
+ isnot(
+ this.#mWindowUtils.IMEStatus,
+ aExpectedData.enabledValue,
+ `${
+ this.#mDescription
+ }, IME should receive a blur notification BEFORE IME state is updated`
+ );
+ } else {
+ is(
+ this.#mWindowUtils.IMEStatus,
+ aExpectedData.enabledValue,
+ `${
+ this.#mDescription
+ }, IME should receive a blur notification and its context has expected IME state if the state isn't being changed`
+ );
+ }
+ break;
+ }
+ };
+
+ this.#mTIPWrapper.clearFocusBlurNotifications();
+ }
+
+ /**
+ * @returns {bool} whether expected element has focus or not after moving focus.
+ */
+ async run() {
+ const previousFocusedElement = getFocusedElementOrUAWidgetHost();
+ if (this.#mTest.setFocusIntoUAWidget) {
+ this.#mTest.setFocusIntoUAWidget(this.#mElementToSetFocus);
+ } else {
+ this.#mElementToSetFocus.focus();
+ }
+
+ await new Promise(resolve =>
+ requestAnimationFrame(() => requestAnimationFrame(resolve))
+ ); // wait for sending IME notifications
+
+ const currentFocusedElement = getFocusedElementOrUAWidgetHost();
+ this.#mCreatedElementForPreviousFocusedElement?.remove();
+ if (this.#mTest.isFocusable) {
+ return this.#mElementToSetFocus == currentFocusedElement;
+ }
+ return previousFocusedElement == currentFocusedElement;
+ }
+
+ check(aExpectedData) {
+ this.#mTIPWrapper.onIMEFocusBlur = null;
+
+ if (aExpectedData.isFocusable) {
+ if (aExpectedData.focusEventFired) {
+ if (
+ aExpectedData.enabledValue ==
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED ||
+ aExpectedData.enabledValue ==
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_PASSWORD
+ ) {
+ ok(
+ this.#mTIPWrapper.numberOfFocusNotifications > 0,
+ `${this.#mDescription}, IME should receive a focus notification`
+ );
+ if (
+ aExpectedData.designModeValue == "on" &&
+ !aExpectedData.testedSubDocumentInDesignMode
+ ) {
+ is(
+ this.#mTIPWrapper.numberOfBlurNotifications,
+ 0,
+ `${
+ this.#mDescription
+ }, IME shouldn't receive a blur notification in designMode since focus isn't moved from another editor`
+ );
+ } else {
+ ok(
+ this.#mTIPWrapper.numberOfBlurNotifications > 0,
+ `${
+ this.#mDescription
+ }, IME should receive a blur notification for the previous focused editor`
+ );
+ }
+ ok(
+ this.#mTIPWrapper.IMEHasFocus,
+ `${this.#mDescription}, IME should have focus right now`
+ );
+ } else {
+ is(
+ this.#mTIPWrapper.numberOfFocusNotifications,
+ 0,
+ `${this.#mDescription}, IME shouldn't receive a focus notification`
+ );
+ ok(
+ this.#mTIPWrapper.numberOfBlurNotifications > 0,
+ `${this.#mDescription}, IME should receive a blur notification`
+ );
+ ok(
+ !this.#mTIPWrapper.IMEHasFocus,
+ `${this.#mDescription}, IME shouldn't have focus right now`
+ );
+ }
+ } else {
+ ok(true, `${this.#mDescription}, focus event should be fired`);
+ }
+ } else {
+ is(
+ this.#mTIPWrapper.numberOfFocusNotifications,
+ 0,
+ `${
+ this.#mDescription
+ }, IME shouldn't receive a focus notification at testing non-focusable element`
+ );
+ is(
+ this.#mTIPWrapper.numberOfBlurNotifications,
+ 0,
+ `${
+ this.#mDescription
+ }, IME shouldn't receive a blur notification at testing non-focusable element`
+ );
+ }
+
+ is(
+ this.#mWindowUtils.IMEStatus,
+ aExpectedData.enabledValue,
+ `${this.#mDescription}, wrong enabled state`
+ );
+ if (
+ this.#mTest.expectedInputElementType &&
+ aExpectedData.designModeValue != "on"
+ ) {
+ is(
+ this.#mWindowUtils.focusedInputType,
+ this.#mTest.expectedInputElementType,
+ `${this.#mDescription}, wrong input type`
+ );
+ } else if (aExpectedData.designModeValue == "on") {
+ is(
+ this.#mWindowUtils.focusedInputType,
+ "",
+ `${this.#mDescription}, wrong input type`
+ );
+ }
+ }
+
+ destroy() {
+ this.#mCreatedElement?.remove();
+ this.#mCreatedElementForPreviousFocusedElement?.remove();
+ this.#mTIPWrapper?.clearFocusBlurNotifications();
+ this.#mTIPWrapper = null;
+ }
+
+ /**
+ * Open/Close state test check
+ * Note that these tests are not run now.
+ * If these tests should run between `run` and `cleanUp` call of the above
+ * tests.
+ */
+ canTestOpenCloseState(aExpectedData) {
+ return (
+ IsIMEOpenStateSupported() &&
+ this.#mWindowUtils.IMEStatus ==
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED &&
+ aExpectedData.enabledValue ==
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED
+ );
+ }
+ async prepareToRunOpenCloseTest(aContainer) {
+ const doc = aContainer.ownerDocument;
+ this.#mCreatedElementForPreviousFocusedElement?.remove();
+ this.#mCreatedElementForPreviousFocusedElement = doc.createElement("input");
+ this.#mCreatedElementForPreviousFocusedElement.setAttribute("type", "text");
+ aContainer.appendChild(this.#mCreatedElementForPreviousFocusedElement);
+
+ this.#mContainerIsEditable = nodeIsEditable(aContainer);
+ this.#mCreatedElement = this.#mTest.createElement(doc);
+ const waitForLoadIfIFrame = new Promise(resolve => {
+ if (this.#mCreatedElement.tagName == "IFRAME") {
+ this.#mCreatedElement.addEventListener("load", resolve, {
+ capture: true,
+ once: true,
+ });
+ } else {
+ resolve();
+ }
+ });
+ aContainer.appendChild(this.#mCreatedElement);
+ await waitForLoadIfIFrame;
+ this.#mElementToSetFocus = this.#mCreatedElement.contentDocument
+ ? this.#mCreatedElement.contentDocument.documentElement
+ : this.#mCreatedElement;
+
+ this.#mCreatedElementForPreviousFocusedElement.focus();
+
+ return {};
+ }
+ prepareToCheckOpenCloseTest(aPreviousOpenState, aExpectedData) {
+ info(`Starting ${this.#mDescription} (open/close state check)...`);
+ this.#mWindowUtils.IMEIsOpen = aPreviousOpenState;
+ aExpectedData.defaultOpenState = this.#mWindowUtils.IMEIsOpen;
+ }
+ async runOpenCloseTest() {
+ return this.run();
+ }
+ checkOpenCloseTest(aExpectedData) {
+ const expectedOpenState =
+ this.#mTest.expectedOpenState != undefined
+ ? this.#mTest.expectedOpenState
+ : aExpectedData.defaultOpenState;
+ is(
+ this.#mWindowUtils.IMEIsOpen,
+ expectedOpenState,
+ `${this.#mDescription}, IME should ${
+ expectedOpenState != aExpectedData.defaultOpenState ? "become" : "keep"
+ } ${expectedOpenState ? "open" : "closed"}`
+ );
+ }
+
+ /**
+ * Utility methods for defining kIMEStateTestList.
+ */
+
+ /**
+ * @param {Element} aElement
+ */
+ static #elementIsConnectedAndNotInDesignMode(aElement) {
+ return aElement.isConnected && !nodeIsInDesignMode(aElement);
+ }
+
+ /**
+ * @param {Element} aElementContainer
+ * @param {bool} aElementIsEditingHost
+ */
+ static #elementIsFocusableIfEditingHost(
+ aElementContainer,
+ aElementIsEditingHost
+ ) {
+ return !nodeIsEditable(aElementContainer) && aElementIsEditingHost;
+ }
+
+ static #IMEStateEnabledAlways() {
+ return SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED;
+ }
+
+ /**
+ * @param {Element} aElement
+ * @param {bool} aElementIsEditingHost
+ */
+ static #IMEStateEnabledIfEditable(aElement, aElementIsEditingHost) {
+ return nodeIsEditable(aElement) || aElementIsEditingHost
+ ? SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED
+ : SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED;
+ }
+
+ /**
+ * @param {Element} aElement
+ */
+ static #IMEStateEnabledIfInDesignMode(aElement) {
+ return IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode(
+ aElement
+ )
+ ? SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED
+ : SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED;
+ }
+
+ /**
+ * @param {Element} aElement
+ */
+ static #IMEStatePasswordIfNotInDesignMode(aElement) {
+ return IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode(
+ aElement
+ )
+ ? SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_PASSWORD
+ : SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED;
+ }
+
+ /**
+ * @param {Element} aElement
+ */
+ static #elementIsConnectedAndNotEditable(aElement) {
+ return aElement.isConnected && !nodeIsEditable(aElement);
+ }
+
+ /**
+ * @param {Element} aElementContainer
+ */
+ static #focusEventIsExpectedUnlessEditableChild(aElementContainer) {
+ return !nodeIsEditable(aElementContainer);
+ }
+
+ // Form controls except text editable elements are "disable" in normal
+ // condition, however, if they are editable, they are "enabled".
+ // XXX Probably there are some bugs: If the form controls editable, they
+ // shouldn't be focusable.
+ #resolveTest(aTest, aContainer) {
+ const isFocusable = aTest.isFocusable(
+ aContainer,
+ aTest.isNewElementEditingHost
+ );
+ return {
+ // Description of the new element
+ description: aTest.description,
+ // Create element to check IME state
+ createElement: aTest.createElement,
+ // Whether the new element is an editing host if container is not editable
+ isNewElementEditingHost: aTest.isNewElementEditingHost,
+ // If the test wants to move focus into an element in UA widget, define
+ // this and set focus in it.
+ setFocusIntoUAWidget: aTest.setFocusIntoUAWidget,
+ // Whether the element is focusable or not
+ isFocusable,
+ // Whether focus events are fired on the element if it's focusable
+ focusEventIsExpected:
+ isFocusable &&
+ aTest.focusEventIsExpected(aContainer, aTest.isNewElementEditingHost),
+ // Expected IME enabled state when the element has focus
+ expectedEnabledValue: aTest.expectedEnabledValue(
+ aContainer,
+ aTest.isNewElementEditingHost
+ ),
+ // Expected IME open state when the element gets focus
+ // "undefined" means that IME open state should not be changed
+ expectedOpenState: aTest.expectedOpenState,
+ // Expected type of input element if it's an <input>
+ expectedInputElementType: aTest.expectedInputElementType,
+ };
+ }
+ static #sTestList = [
+ {
+ description: "input[type=text]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "text");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedInputElementType: "text",
+ },
+ {
+ description: "input[type=text][readonly]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "text");
+ element.setAttribute("readonly", "");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfInDesignMode,
+ },
+ {
+ description: "input[type=password]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "password");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStatePasswordIfNotInDesignMode,
+ expectedInputElementType: "password",
+ },
+ {
+ description: "input[type=password][readonly]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "password");
+ element.setAttribute("readonly", "");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfInDesignMode,
+ },
+ {
+ description: "input[type=checkbox]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "checkbox");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected:
+ IMEStateOnFocusMoveTester.#focusEventIsExpectedUnlessEditableChild,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfEditable,
+ },
+ {
+ description: "input[type=radio]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "radio");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected:
+ IMEStateOnFocusMoveTester.#focusEventIsExpectedUnlessEditableChild,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfEditable,
+ },
+ {
+ description: "input[type=submit]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "submit");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfEditable,
+ },
+ {
+ description: "input[type=reset]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "reset");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfEditable,
+ },
+ {
+ description: "input[type=file]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "file");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected:
+ IMEStateOnFocusMoveTester.#focusEventIsExpectedUnlessEditableChild,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfEditable,
+ },
+ {
+ description: "input[type=button]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "button");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfEditable,
+ },
+ {
+ description: "input[type=image]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "image");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfEditable,
+ },
+ {
+ description: "input[type=url]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "url");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedInputElementType: "url",
+ },
+ {
+ description: "input[type=email]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "email");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedInputElementType: "email",
+ },
+ {
+ description: "input[type=search]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "search");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedInputElementType: "search",
+ },
+ {
+ description: "input[type=tel]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "tel");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedInputElementType: "tel",
+ },
+ {
+ description: "input[type=number]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "number");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedInputElementType: "number",
+ },
+ {
+ description: "input[type=date]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "date");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfInDesignMode,
+ expectedInputElementType: "date",
+ },
+ {
+ description: "input[type=datetime-local]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "datetime-local");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfInDesignMode,
+ expectedInputElementType: "datetime-local",
+ },
+ {
+ description: "input[type=time]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "time");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfInDesignMode,
+ expectedInputElementType: "time",
+ },
+ // TODO(bug 1283382, bug 1283382): month and week
+
+ // form controls
+ {
+ description: "button",
+ createElement: aDocument => aDocument.createElement("button"),
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfEditable,
+ },
+ {
+ description: "textarea",
+ createElement: aDocument => aDocument.createElement("textarea"),
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: "textarea[readonly]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("textarea");
+ element.setAttribute("readonly", "");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfInDesignMode,
+ },
+ {
+ description: "select (dropdown list)",
+ createElement: aDocument => {
+ const select = aDocument.createElement("select");
+ const option1 = aDocument.createElement("option");
+ option1.textContent = "abc";
+ const option2 = aDocument.createElement("option");
+ option2.textContent = "def";
+ const option3 = aDocument.createElement("option");
+ option3.textContent = "ghi";
+ select.appendChild(option1);
+ select.appendChild(option2);
+ select.appendChild(option3);
+ return select;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected:
+ IMEStateOnFocusMoveTester.#focusEventIsExpectedUnlessEditableChild,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfEditable,
+ },
+ {
+ description: "select (list box)",
+ createElement: aDocument => {
+ const select = aDocument.createElement("select");
+ select.setAttribute("multiple", "multiple");
+ const option1 = aDocument.createElement("option");
+ option1.textContent = "abc";
+ const option2 = aDocument.createElement("option");
+ option2.textContent = "def";
+ const option3 = aDocument.createElement("option");
+ option3.textContent = "ghi";
+ select.appendChild(option1);
+ select.appendChild(option2);
+ select.appendChild(option3);
+ return select;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected:
+ IMEStateOnFocusMoveTester.#focusEventIsExpectedUnlessEditableChild,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfEditable,
+ },
+
+ // a element
+ {
+ id: "a_href",
+ description: "a[href]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("a");
+ element.setAttribute("href", "about:blank");
+ return element;
+ },
+ isFocusable: IMEStateOnFocusMoveTester.#elementIsConnectedAndNotEditable,
+ focusEventIsExpected:
+ IMEStateOnFocusMoveTester.#focusEventIsExpectedUnlessEditableChild,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfEditable,
+ },
+
+ // audio element
+ {
+ description: "audio[controls]",
+ createElement: aDocument => {
+ const element = aDocument.createElement("audio");
+ element.setAttribute("controls", "");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfEditable,
+ },
+ {
+ description: "playButton in audio",
+ createElement: aDocument => {
+ const element = aDocument.createElement("audio");
+ element.setAttribute("controls", "");
+ return element;
+ },
+ setFocusIntoUAWidget: aElement =>
+ SpecialPowers.wrap(aElement)
+ .openOrClosedShadowRoot.getElementById("playButton")
+ .focus(),
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfInDesignMode,
+ },
+ {
+ description: "scrubber in audio",
+ createElement: aDocument => {
+ const element = aDocument.createElement("audio");
+ element.setAttribute("controls", "");
+ return element;
+ },
+ setFocusIntoUAWidget: aElement =>
+ SpecialPowers.wrap(aElement)
+ .openOrClosedShadowRoot.getElementById("scrubber")
+ .focus(),
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfInDesignMode,
+ },
+ {
+ description: "muteButton in audio",
+ createElement: aDocument => {
+ const element = aDocument.createElement("audio");
+ element.setAttribute("controls", "");
+ return element;
+ },
+ setFocusIntoUAWidget: aElement =>
+ SpecialPowers.wrap(aElement)
+ .openOrClosedShadowRoot.getElementById("muteButton")
+ .focus(),
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfInDesignMode,
+ },
+ {
+ description: "volumeControl in audio",
+ createElement: aDocument => {
+ const element = aDocument.createElement("audio");
+ element.setAttribute("controls", "");
+ return element;
+ },
+ setFocusIntoUAWidget: aElement =>
+ SpecialPowers.wrap(aElement)
+ .openOrClosedShadowRoot.getElementById("volumeControl")
+ .focus(),
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfInDesignMode,
+ },
+
+ // video element
+ {
+ description: "video",
+ createElement: aDocument => {
+ const element = aDocument.createElement("video");
+ element.setAttribute("controls", "");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfEditable,
+ },
+ {
+ description: "playButton in video",
+ createElement: aDocument => {
+ const element = aDocument.createElement("video");
+ element.setAttribute("controls", "");
+ return element;
+ },
+ setFocusIntoUAWidget: aElement =>
+ SpecialPowers.wrap(aElement)
+ .openOrClosedShadowRoot.getElementById("playButton")
+ .focus(),
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfInDesignMode,
+ },
+ {
+ description: "scrubber in video",
+ createElement: aDocument => {
+ const element = aDocument.createElement("video");
+ element.setAttribute("controls", "");
+ return element;
+ },
+ setFocusIntoUAWidget: aElement =>
+ SpecialPowers.wrap(aElement)
+ .openOrClosedShadowRoot.getElementById("scrubber")
+ .focus(),
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfInDesignMode,
+ },
+ {
+ description: "muteButton in video",
+ createElement: aDocument => {
+ const element = aDocument.createElement("video");
+ element.setAttribute("controls", "");
+ return element;
+ },
+ setFocusIntoUAWidget: aElement =>
+ SpecialPowers.wrap(aElement)
+ .openOrClosedShadowRoot.getElementById("muteButton")
+ .focus(),
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfInDesignMode,
+ },
+ {
+ description: "volumeControl in video",
+ createElement: aDocument => {
+ const element = aDocument.createElement("video");
+ element.setAttribute("controls", "");
+ return element;
+ },
+ setFocusIntoUAWidget: aElement =>
+ SpecialPowers.wrap(aElement)
+ .openOrClosedShadowRoot.getElementById("volumeControl")
+ .focus(),
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStateEnabledIfInDesignMode,
+ },
+
+ // ime-mode
+ {
+ description: 'input[type=text][style="ime-mode: auto;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "text");
+ element.setAttribute("style", "ime-mode: auto;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: 'input[type=text][style="ime-mode: normal;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "text");
+ element.setAttribute("style", "ime-mode: normal;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: 'input[type=text][style="ime-mode: active;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "text");
+ element.setAttribute("style", "ime-mode: active;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedOpenState: true,
+ },
+ {
+ description: 'input[type=text][style="ime-mode: inactive;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "text");
+ element.setAttribute("style", "ime-mode: inactive;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedOpenState: false,
+ },
+ {
+ description: 'input[type=text][style="ime-mode: disabled;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "text");
+ element.setAttribute("style", "ime-mode: disabled;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStatePasswordIfNotInDesignMode,
+ },
+
+ {
+ description: 'input[type=url][style="ime-mode: auto;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "url");
+ element.setAttribute("style", "ime-mode: auto;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: 'input[type=url][style="ime-mode: normal;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "url");
+ element.setAttribute("style", "ime-mode: normal;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: 'input[type=url][style="ime-mode: active;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "url");
+ element.setAttribute("style", "ime-mode: active;");
+ return element;
+ },
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedOpenState: true,
+ },
+ {
+ description: 'input[type=url][style="ime-mode: inactive;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "url");
+ element.setAttribute("style", "ime-mode: inactive;");
+ return element;
+ },
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedOpenState: false,
+ },
+ {
+ description: 'input[type=url][style="ime-mode: disabled;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "url");
+ element.setAttribute("style", "ime-mode: disabled;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStatePasswordIfNotInDesignMode,
+ },
+
+ {
+ description: 'input[type=email][style="ime-mode: auto;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "email");
+ element.setAttribute("style", "ime-mode: auto;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: 'input[type=email][style="ime-mode: normal;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "email");
+ element.setAttribute("style", "ime-mode: normal;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: 'input[type=email][style="ime-mode: active;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "email");
+ element.setAttribute("style", "ime-mode: active;");
+ return element;
+ },
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedOpenState: true,
+ },
+ {
+ description: 'input[type=email][style="ime-mode: inactive;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "email");
+ element.setAttribute("style", "ime-mode: inactive;");
+ return element;
+ },
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedOpenState: false,
+ },
+ {
+ description: 'input[type=email][style="ime-mode: disabled;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "email");
+ element.setAttribute("style", "ime-mode: disabled;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStatePasswordIfNotInDesignMode,
+ },
+
+ {
+ description: 'input[type=search][style="ime-mode: auto;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "search");
+ element.setAttribute("style", "ime-mode: auto;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: 'input[type=search][style="ime-mode: normal;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "search");
+ element.setAttribute("style", "ime-mode: normal;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: 'input[type=search][style="ime-mode: active;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "search");
+ element.setAttribute("style", "ime-mode: active;");
+ return element;
+ },
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedOpenState: true,
+ },
+ {
+ description: 'input[type=search][style="ime-mode: inactive;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "search");
+ element.setAttribute("style", "ime-mode: inactive;");
+ return element;
+ },
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedOpenState: false,
+ },
+ {
+ description: 'input[type=search][style="ime-mode: disabled;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "search");
+ element.setAttribute("style", "ime-mode: disabled;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStatePasswordIfNotInDesignMode,
+ },
+
+ {
+ description: 'input[type=tel][style="ime-mode: auto;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "tel");
+ element.setAttribute("style", "ime-mode: auto;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: 'input[type=tel][style="ime-mode: normal;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "tel");
+ element.setAttribute("style", "ime-mode: normal;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: 'input[type=tel][style="ime-mode: active;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "tel");
+ element.setAttribute("style", "ime-mode: active;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedOpenState: true,
+ },
+ {
+ description: 'input[type=tel][style="ime-mode: inactive;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "tel");
+ element.setAttribute("style", "ime-mode: inactive;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedOpenState: false,
+ },
+ {
+ description: 'input[type=tel][style="ime-mode: disabled;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "tel");
+ element.setAttribute("style", "ime-mode: disabled;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStatePasswordIfNotInDesignMode,
+ },
+
+ {
+ description: 'input[type=number][style="ime-mode: auto;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "number");
+ element.setAttribute("style", "ime-mode: auto;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: 'input[type=number][style="ime-mode: normal;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "number");
+ element.setAttribute("style", "ime-mode: normal;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: 'input[type=number][style="ime-mode: active;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "number");
+ element.setAttribute("style", "ime-mode: active;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedOpenState: true,
+ },
+ {
+ description: 'input[type=number][style="ime-mode: inactive;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "number");
+ element.setAttribute("style", "ime-mode: inactive;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedOpenState: false,
+ },
+ {
+ description: 'input[type=number][style="ime-mode: disabled;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "number");
+ element.setAttribute("style", "ime-mode: disabled;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStatePasswordIfNotInDesignMode,
+ },
+
+ {
+ description: 'input[type=password][style="ime-mode: auto;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "password");
+ element.setAttribute("style", "ime-mode: auto;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStatePasswordIfNotInDesignMode,
+ },
+ {
+ description: 'input[type=password][style="ime-mode: normal;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "password");
+ element.setAttribute("style", "ime-mode: normal;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: 'input[type=password][style="ime-mode: active;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "password");
+ element.setAttribute("style", "ime-mode: active;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedOpenState: true,
+ },
+ {
+ description: 'input[type=password][style="ime-mode: inactive;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "password");
+ element.setAttribute("style", "ime-mode: inactive;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedOpenState: false,
+ },
+ {
+ description: 'input[type=password][style="ime-mode: disabled;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("input");
+ element.setAttribute("type", "password");
+ element.setAttribute("style", "ime-mode: disabled;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStatePasswordIfNotInDesignMode,
+ },
+ {
+ description: 'textarea[style="ime-mode: auto;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("textarea");
+ element.setAttribute("style", "ime-mode: auto;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: 'textarea[style="ime-mode: normal;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("textarea");
+ element.setAttribute("style", "ime-mode: normal;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: 'textarea[style="ime-mode: active;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("textarea");
+ element.setAttribute("style", "ime-mode: active;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedOpenState: true,
+ },
+ {
+ description: 'textarea[style="ime-mode: inactive;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("textarea");
+ element.setAttribute("style", "ime-mode: inactive;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ expectedOpenState: false,
+ },
+ {
+ description: 'textarea[style="ime-mode: disabled;"]',
+ createElement: aDocument => {
+ const element = aDocument.createElement("textarea");
+ element.setAttribute("style", "ime-mode: disabled;");
+ return element;
+ },
+ isFocusable:
+ IMEStateOnFocusMoveTester.#elementIsConnectedAndNotInDesignMode,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue:
+ IMEStateOnFocusMoveTester.#IMEStatePasswordIfNotInDesignMode,
+ },
+
+ // HTML editors
+ {
+ description: 'div[contenteditable="true"]',
+ createElement: aDocument => {
+ const div = aDocument.createElement("div");
+ div.setAttribute("contenteditable", "");
+ return div;
+ },
+ isNewElementEditingHost: true,
+ isFocusable: IMEStateOnFocusMoveTester.#elementIsFocusableIfEditingHost,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ {
+ description: "designMode editor",
+ createElement: aDocument => {
+ const iframe = aDocument.createElement("iframe");
+ iframe.srcdoc = "<!doctype html><html><body></body></html>";
+ iframe.addEventListener(
+ "load",
+ () => (iframe.contentDocument.designMode = "on"),
+ { capture: true, once: true }
+ );
+ return iframe;
+ },
+ isFocusable: () => true,
+ focusEventIsExpected: () => true,
+ expectedEnabledValue: IMEStateOnFocusMoveTester.#IMEStateEnabledAlways,
+ },
+ ];
+
+ static get numberOfTests() {
+ return IMEStateOnFocusMoveTester.#sTestList.length;
+ }
+}
diff --git a/widget/tests/file_test_ime_state_on_input_type_change.js b/widget/tests/file_test_ime_state_on_input_type_change.js
new file mode 100644
index 0000000000..38c139d6a2
--- /dev/null
+++ b/widget/tests/file_test_ime_state_on_input_type_change.js
@@ -0,0 +1,315 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from file_ime_state_test_helper.js */
+
+class IMEStateOnInputTypeChangeTester {
+ static #sInputElementList = [
+ {
+ type: "",
+ textControl: true,
+ },
+ {
+ type: "text",
+ textControl: true,
+ },
+ {
+ type: "text",
+ readonly: true,
+ textControl: true,
+ },
+ {
+ type: "password",
+ textControl: true,
+ },
+ {
+ type: "password",
+ readonly: true,
+ textControl: true,
+ },
+ {
+ type: "checkbox",
+ buttonControl: true,
+ },
+ {
+ type: "radio",
+ buttonControl: true,
+ },
+ {
+ type: "submit",
+ buttonControl: true,
+ },
+ {
+ type: "reset",
+ buttonControl: true,
+ },
+ {
+ type: "file",
+ buttonControl: true,
+ },
+ {
+ type: "button",
+ buttonControl: true,
+ },
+ {
+ type: "image",
+ alt: "image",
+ buttonControl: true,
+ },
+ {
+ type: "url",
+ textControl: true,
+ },
+ {
+ type: "email",
+ textControl: true,
+ },
+ {
+ type: "search",
+ textControl: true,
+ },
+ {
+ type: "tel",
+ textControl: true,
+ },
+ {
+ type: "number",
+ textControl: true,
+ },
+ {
+ type: "date",
+ dateTimeControl: true,
+ },
+ {
+ type: "datetime-local",
+ dateTimeControl: true,
+ },
+ {
+ type: "time",
+ dateTimeControl: true,
+ },
+ {
+ type: "range",
+ buttonControl: true,
+ },
+ {
+ type: "color",
+ buttonControl: true,
+ },
+ // TODO(bug 1283382, bug 1283382): month and week
+ ];
+
+ static get numberOfTests() {
+ return IMEStateOnInputTypeChangeTester.#sInputElementList.length;
+ }
+
+ /**
+ * @param {HTMLInputElement} aInputElement The input element.
+ * @returns {number} Expected IME state when aInputElement has focus.
+ */
+ static #getExpectedIMEEnabledState(aInputElement) {
+ if (aInputElement.readonly) {
+ return SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED;
+ }
+ switch (aInputElement.type) {
+ case "text":
+ case "url":
+ case "email":
+ case "search":
+ case "tel":
+ case "number":
+ return aInputElement.style.imeMode == "disabled"
+ ? SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_PASSWORD
+ : SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED;
+
+ case "password":
+ return aInputElement.style.imeMode == "" ||
+ aInputElement.style.imeMode == "auto" ||
+ aInputElement.style.imeMode == "disabled"
+ ? SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_PASSWORD
+ : SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED;
+
+ default:
+ return SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED;
+ }
+ }
+
+ static #getDescription(aInputElement) {
+ let desc = "<input";
+ if (aInputElement.getAttribute("type")) {
+ desc += ` type="${aInputElement.getAttribute("type")}"`;
+ }
+ if (aInputElement.readonly) {
+ desc += " readonly";
+ }
+ if (aInputElement.getAttribute("style")) {
+ desc += ` style="ime-mode: ${aInputElement.style.imeMode}"`;
+ }
+ return desc + ">";
+ }
+
+ // Only runner fields
+ #mSrcTypeIndex;
+ #mType;
+ #mInputElement;
+ #mNewType;
+ #mWindow;
+
+ // Only checker fields
+ #mWindowUtils;
+ #mTIPWrapper;
+
+ /**
+ * @param {number} aSrcTypeIndex `type` attribute value index in #sInputElementList
+ */
+ constructor(aSrcTypeIndex) {
+ this.#mSrcTypeIndex = aSrcTypeIndex;
+ this.#mType =
+ IMEStateOnInputTypeChangeTester.#sInputElementList[this.#mSrcTypeIndex];
+ }
+
+ clear() {
+ this.#mInputElement?.remove();
+ this.#mTIPWrapper?.clearFocusBlurNotifications();
+ this.#mTIPWrapper = null;
+ }
+
+ #flushPendingIMENotifications() {
+ return new Promise(resolve =>
+ this.#mWindow.requestAnimationFrame(() =>
+ this.#mWindow.requestAnimationFrame(resolve)
+ )
+ );
+ }
+
+ /**
+ * @param {number} aDestTypeIndex New type index.
+ * @param {Window} aWindow The window running tests.
+ * @param {Element} aContainer The element which should have new <input>.
+ * @param {undefined|string} aIMEModeValue [optional] ime-mode value if you want to specify.
+ * @returns {object|bool} Expected data before running tests. If the test should not run, returns false.
+ */
+ async prepareToRun(aDestTypeIndex, aWindow, aContainer, aIMEModeValue) {
+ this.#mWindow = aWindow;
+
+ if (aContainer.ownerDocument.activeElement) {
+ aContainer.ownerDocument.activeElement.blur();
+ await this.#flushPendingIMENotifications();
+ }
+ if (this.#mInputElement?.isConnected) {
+ this.#mInputElement.remove();
+ }
+ this.#mInputElement = null;
+
+ this.#mNewType =
+ IMEStateOnInputTypeChangeTester.#sInputElementList[aDestTypeIndex];
+ if (
+ aDestTypeIndex == this.#mSrcTypeIndex ||
+ this.#mNewType.readonly ||
+ (!this.#mType.textControl && !this.#mNewType.textControl)
+ ) {
+ return false;
+ }
+ this.#mInputElement = aContainer.ownerDocument.createElement("input");
+ if (this.#mType.type != "") {
+ this.#mInputElement.setAttribute("type", this.#mType.type);
+ }
+ if (this.#mType.readonly) {
+ this.#mInputElement.setAttribute("readonly", "");
+ }
+ if (aIMEModeValue && aIMEModeValue !== "") {
+ this.#mInputElement.setAttribute("style", `ime-mode: ${aIMEModeValue}`);
+ }
+ if (this.#mType.alt) {
+ this.#mInputElement.setAttribute("alt", this.#mType.alt);
+ }
+ const waitForFocus = new Promise(resolve =>
+ this.#mInputElement.addEventListener("focus", resolve, { once: true })
+ );
+ aContainer.appendChild(this.#mInputElement);
+ this.#mInputElement.focus();
+ await waitForFocus;
+
+ await this.#flushPendingIMENotifications();
+
+ const expectedIMEState =
+ IMEStateOnInputTypeChangeTester.#getExpectedIMEEnabledState(
+ this.#mInputElement
+ );
+ return {
+ description: IMEStateOnInputTypeChangeTester.#getDescription(
+ this.#mInputElement
+ ),
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ };
+ }
+
+ /**
+ * @param {object} aExpectedData The expected data which was returned by prepareToRun().
+ * @param {TIPWrapper} aTIPWrapper TIP wrapper in aWindow.
+ * @param {Window} aWindow [optional] The window object of which you want to check IME state.
+ */
+ checkBeforeRun(aExpectedData, aTIPWrapper, aWindow = window) {
+ this.#mWindowUtils = SpecialPowers.wrap(aWindow).windowUtils;
+ this.#mTIPWrapper = aTIPWrapper;
+ const description = `IMEStateOnInputTypeChangeTester(${aExpectedData.description})`;
+ is(
+ this.#mWindowUtils.IMEStatus,
+ aExpectedData.expectedIMEState,
+ `${description}: IME state should be set to expected value before running test`
+ );
+ is(
+ this.#mTIPWrapper.IMEHasFocus,
+ aExpectedData.expectedIMEFocus,
+ `${description}: IME state should ${
+ aExpectedData.expectedIMEFocus ? "" : "not"
+ }have focus before running test`
+ );
+ }
+
+ /**
+ * @returns {object} The expected results.
+ */
+ async run() {
+ if (this.#mNewType.type == "") {
+ this.#mInputElement.removeAttribute("type");
+ } else {
+ this.#mInputElement.setAttribute("type", this.#mNewType.type);
+ }
+
+ await this.#flushPendingIMENotifications();
+
+ const expectedIMEState =
+ IMEStateOnInputTypeChangeTester.#getExpectedIMEEnabledState(
+ this.#mInputElement
+ );
+ return {
+ newType: this.#mNewType.type,
+ expectedIMEState,
+ expectedIMEFocus:
+ expectedIMEState !=
+ SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ };
+ }
+
+ checkResult(aExpectedDataBeforeRun, aExpectedData) {
+ const description = `IMEStateOnInputTypeChangeTester(${
+ aExpectedDataBeforeRun.description
+ } -> type="${aExpectedData.newType || ""}")`;
+ is(
+ this.#mWindowUtils.IMEStatus,
+ aExpectedData.expectedIMEState,
+ `${description}: IME state should be set to expected value`
+ );
+ is(
+ this.#mTIPWrapper.IMEHasFocus,
+ aExpectedData.expectedIMEFocus,
+ `${description}: IME state should have focus`
+ );
+ }
+}
diff --git a/widget/tests/file_test_ime_state_on_readonly_change.js b/widget/tests/file_test_ime_state_on_readonly_change.js
new file mode 100644
index 0000000000..af21f538ba
--- /dev/null
+++ b/widget/tests/file_test_ime_state_on_readonly_change.js
@@ -0,0 +1,242 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from file_ime_state_test_helper.js */
+
+class IMEStateOnReadonlyChangeTester {
+ static #sTextControlTypes = [
+ {
+ description: "<input>",
+ createElement: aDoc => aDoc.createElement("input"),
+ expectedIMEState: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ },
+ {
+ description: `<input type="text">`,
+ createElement: aDoc => {
+ const input = aDoc.createElement("input");
+ input.setAttribute("type", "text");
+ return input;
+ },
+ expectedIMEState: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ },
+ {
+ description: `<input type="password">`,
+ createElement: aDoc => {
+ const input = aDoc.createElement("input");
+ input.setAttribute("type", "password");
+ return input;
+ },
+ expectedIMEState: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_PASSWORD,
+ },
+ {
+ description: `<input type="url">`,
+ createElement: aDoc => {
+ const input = aDoc.createElement("input");
+ input.setAttribute("type", "url");
+ return input;
+ },
+ expectedIMEState: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ },
+ {
+ description: `<input type="email">`,
+ createElement: aDoc => {
+ const input = aDoc.createElement("input");
+ input.setAttribute("type", "email");
+ return input;
+ },
+ expectedIMEState: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ },
+ {
+ description: `<input type="search">`,
+ createElement: aDoc => {
+ const input = aDoc.createElement("input");
+ input.setAttribute("type", "search");
+ return input;
+ },
+ expectedIMEState: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ },
+ {
+ description: `<input type="tel">`,
+ createElement: aDoc => {
+ const input = aDoc.createElement("input");
+ input.setAttribute("type", "tel");
+ return input;
+ },
+ expectedIMEState: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ },
+ {
+ description: `<input type="number">`,
+ createElement: aDoc => {
+ const input = aDoc.createElement("input");
+ input.setAttribute("type", "number");
+ return input;
+ },
+ expectedIMEState: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ },
+ {
+ description: `<textarea></textarea>`,
+ createElement: aDoc => aDoc.createElement("textarea"),
+ expectedIMEState: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ },
+ ];
+
+ static get numberOfTextControlTypes() {
+ return IMEStateOnReadonlyChangeTester.#sTextControlTypes.length;
+ }
+
+ // Only runner fields
+ #mTextControl;
+ #mTextControlElement;
+ #mWindow;
+
+ // Only checker fields
+ #mWindowUtils;
+ #mTIPWrapper;
+
+ clear() {
+ this.#mTextControlElement?.remove();
+ this.#mTIPWrapper?.clearFocusBlurNotifications();
+ this.#mTIPWrapper = null;
+ }
+
+ #flushPendingIMENotifications() {
+ return new Promise(resolve =>
+ this.#mWindow.requestAnimationFrame(() =>
+ this.#mWindow.requestAnimationFrame(resolve)
+ )
+ );
+ }
+
+ /**
+ * @param {number} aIndex The index of text control types.
+ * @param {Window} aWindow The window running tests.
+ * @param {Element} aContainer The element which should have new <input> or <textarea>.
+ * @returns {object} Expected data before running tests.
+ */
+ async prepareToRun(aIndex, aWindow, aContainer) {
+ this.#mWindow = aWindow;
+ this.#mTextControl =
+ IMEStateOnReadonlyChangeTester.#sTextControlTypes[aIndex];
+
+ if (aContainer.ownerDocument.activeElement) {
+ aContainer.ownerDocument.activeElement.blur();
+ await this.#flushPendingIMENotifications();
+ }
+ if (this.#mTextControlElement?.isConnected) {
+ this.#mTextControlElement.remove();
+ }
+ this.#mTextControlElement = this.#mTextControl.createElement(
+ aContainer.ownerDocument
+ );
+
+ const waitForFocus = new Promise(resolve =>
+ this.#mTextControlElement.addEventListener("focus", resolve, {
+ once: true,
+ })
+ );
+ aContainer.appendChild(this.#mTextControlElement);
+ this.#mTextControlElement.focus();
+ await waitForFocus;
+
+ await this.#flushPendingIMENotifications();
+
+ return {
+ description: this.#mTextControl.description,
+ expectedIMEState: this.#mTextControl.expectedIMEState,
+ expectedIMEFocus: true,
+ };
+ }
+
+ /**
+ * @param {object} aExpectedData The expected data which was returned by prepareToRun().
+ * @param {TIPWrapper} aTIPWrapper TIP wrapper in aWindow.
+ * @param {Window} aWindow [optional] The window object of which you want to check IME state.
+ */
+ checkBeforeRun(aExpectedData, aTIPWrapper, aWindow = window) {
+ this.#mWindowUtils = SpecialPowers.wrap(aWindow).windowUtils;
+ this.#mTIPWrapper = aTIPWrapper;
+ const description = `IMEStateOnReadonlyChangeTester(${aExpectedData.description})`;
+ is(
+ this.#mWindowUtils.IMEStatus,
+ aExpectedData.expectedIMEState,
+ `${description}: IME state should be set to expected value before setting readonly attribute`
+ );
+ is(
+ this.#mTIPWrapper.IMEHasFocus,
+ aExpectedData.expectedIMEFocus,
+ `${description}: IME state should ${
+ aExpectedData.expectedIMEFocus ? "" : "not"
+ } have focus before setting readonly attribute`
+ );
+ }
+
+ /**
+ * @returns {object} Expected result.
+ */
+ async runToMakeTextControlReadonly() {
+ this.#mTextControlElement.setAttribute("readonly", "");
+
+ await this.#flushPendingIMENotifications();
+
+ return {
+ description: this.#mTextControl.description,
+ expectedIMEState: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ expectedIMEFocus: false,
+ };
+ }
+
+ /**
+ * @param {object} aExpectedData Expected result which is returned by runToMakeTextControlReadonly().
+ */
+ checkResultOfMakingTextControlReadonly(aExpectedData) {
+ const description = `IMEStateOnReadonlyChangeTester(${aExpectedData.description})`;
+ is(
+ this.#mWindowUtils.IMEStatus,
+ aExpectedData.expectedIMEState,
+ `${description}: IME state should be set to expected value after setting readonly attribute`
+ );
+ is(
+ this.#mTIPWrapper.IMEHasFocus,
+ aExpectedData.expectedIMEFocus,
+ `${description}: IME state should ${
+ aExpectedData.expectedIMEFocus ? "" : "not"
+ } have focus after setting readonly attribute`
+ );
+ }
+
+ /**
+ * @returns {object} Expected result.
+ */
+ async runToMakeTextControlEditable() {
+ this.#mTextControlElement.removeAttribute("readonly");
+
+ await this.#flushPendingIMENotifications();
+
+ return {
+ description: this.#mTextControl.description,
+ expectedIMEState: this.#mTextControl.expectedIMEState,
+ expectedIMEFocus: true,
+ };
+ }
+
+ /**
+ * @param {object} aExpectedData Expected result which is returned by runToMakeTextControlEditable().
+ */
+ checkResultOfMakingTextControlEditable(aExpectedData) {
+ const description = `IMEStateOnReadonlyChangeTester(${aExpectedData.description})`;
+ is(
+ this.#mWindowUtils.IMEStatus,
+ aExpectedData.expectedIMEState,
+ `${description}: IME state should be set to expected value after removing readonly attribute`
+ );
+ is(
+ this.#mTIPWrapper.IMEHasFocus,
+ aExpectedData.expectedIMEFocus,
+ `${description}: IME state should ${
+ aExpectedData.expectedIMEFocus ? "" : "not"
+ } have focus after removing readonly attribute`
+ );
+ }
+}
diff --git a/widget/tests/gtest/MockWinWidget.cpp b/widget/tests/gtest/MockWinWidget.cpp
new file mode 100644
index 0000000000..ed7850076f
--- /dev/null
+++ b/widget/tests/gtest/MockWinWidget.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+#include "MockWinWidget.h"
+
+#include "mozilla/gfx/Logging.h"
+
+NS_IMPL_ISUPPORTS_INHERITED0(MockWinWidget, nsBaseWidget)
+
+// static
+RefPtr<MockWinWidget> MockWinWidget::Create(DWORD aStyle, DWORD aExStyle,
+ const LayoutDeviceIntRect& aRect) {
+ RefPtr<MockWinWidget> window = new MockWinWidget;
+ if (!window->Initialize(aStyle, aExStyle, aRect)) {
+ return nullptr;
+ }
+
+ return window;
+}
+
+MockWinWidget::MockWinWidget() {}
+
+MockWinWidget::~MockWinWidget() {
+ if (mWnd) {
+ ::DestroyWindow(mWnd);
+ mWnd = 0;
+ }
+}
+
+bool MockWinWidget::Initialize(DWORD aStyle, DWORD aExStyle,
+ const LayoutDeviceIntRect& aRect) {
+ WNDCLASSW wc;
+ const wchar_t className[] = L"MozillaMockWinWidget";
+ HMODULE hSelf = ::GetModuleHandle(nullptr);
+
+ if (!GetClassInfoW(hSelf, className, &wc)) {
+ ZeroMemory(&wc, sizeof(WNDCLASSW));
+ wc.hInstance = hSelf;
+ wc.lpfnWndProc = ::DefWindowProc;
+ wc.lpszClassName = className;
+ RegisterClassW(&wc);
+ }
+
+ mWnd = ::CreateWindowExW(aExStyle, className, className, aStyle, aRect.X(),
+ aRect.Y(), aRect.Width(), aRect.Height(), nullptr,
+ nullptr, hSelf, nullptr);
+ if (!mWnd) {
+ gfxCriticalNoteOnce << "GetClientRect failed " << ::GetLastError();
+ return false;
+ }
+
+ // First nccalcszie (during CreateWindow) for captioned windows is
+ // deliberately ignored so force a second one here to get the right
+ // non-client set up.
+ if (mWnd && (aStyle & WS_CAPTION)) {
+ ::SetWindowPos(mWnd, NULL, 0, 0, 0, 0,
+ SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
+ SWP_NOACTIVATE | SWP_NOREDRAW);
+ }
+
+ mBounds = aRect;
+ return true;
+}
+
+void MockWinWidget::NotifyOcclusionState(
+ mozilla::widget::OcclusionState aState) {
+ mCurrentState = aState;
+}
+
+nsSizeMode MockWinWidget::SizeMode() {
+ if (::IsIconic(mWnd)) {
+ return nsSizeMode_Minimized;
+ }
+ return nsSizeMode_Normal;
+}
diff --git a/widget/tests/gtest/MockWinWidget.h b/widget/tests/gtest/MockWinWidget.h
new file mode 100644
index 0000000000..891e1c942d
--- /dev/null
+++ b/widget/tests/gtest/MockWinWidget.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef GTEST_MockWinWidget_H
+#define GTEST_MockWinWidget_H
+
+#include <windows.h>
+
+#include "nsBaseWidget.h"
+#include "Units.h"
+
+class MockWinWidget : public nsBaseWidget {
+ public:
+ static RefPtr<MockWinWidget> Create(DWORD aStyle, DWORD aExStyle,
+ const LayoutDeviceIntRect& aRect);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ void NotifyOcclusionState(mozilla::widget::OcclusionState aState) override;
+
+ void SetExpectation(mozilla::widget::OcclusionState aExpectation) {
+ mExpectation = aExpectation;
+ }
+
+ bool IsExpectingCall() const { return mExpectation != mCurrentState; }
+
+ HWND GetWnd() { return mWnd; }
+
+ nsSizeMode SizeMode() override;
+ void SetSizeMode(nsSizeMode aMode) override {}
+
+ void* GetNativeData(uint32_t aDataType) override { return nullptr; }
+
+ virtual nsresult Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ InitData* aInitData = nullptr) override {
+ return NS_OK;
+ }
+ virtual nsresult Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const DesktopIntRect& aRect,
+ InitData* aInitData = nullptr) override {
+ return NS_OK;
+ }
+ virtual void Show(bool aState) override {}
+ virtual bool IsVisible() const override { return true; }
+ virtual void Move(double aX, double aY) override {}
+ virtual void Resize(double aWidth, double aHeight, bool aRepaint) override {}
+ virtual void Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) override {}
+
+ virtual void Enable(bool aState) override {}
+ virtual bool IsEnabled() const override { return true; }
+ virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override {}
+ virtual void Invalidate(const LayoutDeviceIntRect& aRect) override {}
+ virtual nsresult SetTitle(const nsAString& title) override { return NS_OK; }
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override {
+ return LayoutDeviceIntPoint(0, 0);
+ }
+ virtual nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override {
+ return NS_OK;
+ }
+ virtual void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override {}
+ virtual InputContext GetInputContext() override { abort(); }
+
+ private:
+ MockWinWidget();
+ ~MockWinWidget();
+
+ bool Initialize(DWORD aStyle, DWORD aExStyle,
+ const LayoutDeviceIntRect& aRect);
+
+ HWND mWnd = 0;
+
+ mozilla::widget::OcclusionState mExpectation =
+ mozilla::widget::OcclusionState::UNKNOWN;
+ mozilla::widget::OcclusionState mCurrentState =
+ mozilla::widget::OcclusionState::UNKNOWN;
+};
+
+#endif
diff --git a/widget/tests/gtest/TestTimeConverter.cpp b/widget/tests/gtest/TestTimeConverter.cpp
new file mode 100644
index 0000000000..22cbc3f9e6
--- /dev/null
+++ b/widget/tests/gtest/TestTimeConverter.cpp
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/TimeStamp.h"
+#include "SystemTimeConverter.h"
+
+using mozilla::SystemTimeConverter;
+using mozilla::TimeDuration;
+using mozilla::TimeStamp;
+
+namespace {
+
+// This class provides a mock implementation of the CurrentTimeGetter template
+// type used in SystemTimeConverter. It can be constructed with a particular
+// Time and always returns that Time.
+template <typename Time>
+class MockCurrentTimeGetter {
+ public:
+ MockCurrentTimeGetter() : mTime(0) {}
+ explicit MockCurrentTimeGetter(Time aTime) : mTime(aTime) {}
+
+ // Methods needed for CurrentTimeGetter compatibility
+ Time GetCurrentTime() const { return mTime; }
+ void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {}
+
+ private:
+ Time mTime;
+};
+
+// This is another mock implementation of the CurrentTimeGetter template
+// type used in SystemTimeConverter, except this asserts that it will not be
+// used. i.e. it should only be used in calls to SystemTimeConverter that we
+// know will not invoke it.
+template <typename Time>
+class UnusedCurrentTimeGetter {
+ public:
+ Time GetCurrentTime() const {
+ EXPECT_TRUE(false);
+ return 0;
+ }
+
+ void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
+ EXPECT_TRUE(false);
+ }
+};
+
+// This class provides a mock implementation of the TimeStampNowProvider
+// template type used in SystemTimeConverter. It also has other things in it
+// that allow the test to better control time for testing purposes.
+class MockTimeStamp {
+ public:
+ // This should generally be called at the start of every test function, as
+ // it will initialize this class's static fields to sane values. In particular
+ // it will initialize the baseline TimeStamp against which all other
+ // TimeStamps are compared.
+ static void Init() {
+ sBaseline = TimeStamp::Now();
+ sTimeStamp = sBaseline;
+ }
+
+ // Advance the timestamp returned by `MockTimeStamp::Now()`
+ static void Advance(double ms) {
+ sTimeStamp += TimeDuration::FromMilliseconds(ms);
+ }
+
+ // Returns the baseline TimeStamp, that is used as a fixed reference point
+ // in time against which other TimeStamps can be compared. This is needed
+ // because mozilla::TimeStamp itself doesn't provide any conversion to
+ // human-readable strings, and we need to convert it to a TimeDuration in
+ // order to get that. This baseline TimeStamp can be used to turn an
+ // arbitrary TimeStamp into a TimeDuration.
+ static TimeStamp Baseline() { return sBaseline; }
+
+ // This is the method needed for TimeStampNowProvider compatibility, and
+ // simulates `TimeStamp::Now()`
+ static TimeStamp Now() { return sTimeStamp; }
+
+ private:
+ static TimeStamp sTimeStamp;
+ static TimeStamp sBaseline;
+};
+
+TimeStamp MockTimeStamp::sTimeStamp;
+TimeStamp MockTimeStamp::sBaseline;
+
+// Could have platform-specific implementations of this using DWORD, guint32,
+// etc behind ifdefs. But this is sufficient for now.
+using GTestTime = uint32_t;
+using TimeConverter = SystemTimeConverter<GTestTime, MockTimeStamp>;
+
+} // namespace
+
+// Checks the expectation that the TimeStamp `ts` is exactly `ms` milliseconds
+// after the baseline timestamp. This is a macro so gtest still gives us useful
+// line numbers for failures.
+#define EXPECT_TS(ts, ms) \
+ EXPECT_EQ((ts)-MockTimeStamp::Baseline(), TimeDuration::FromMilliseconds(ms))
+
+#define EXPECT_TS_FUZZY(ts, ms) \
+ EXPECT_DOUBLE_EQ(((ts)-MockTimeStamp::Baseline()).ToMilliseconds(), ms)
+
+TEST(TimeConverter, SanityCheck)
+{
+ MockTimeStamp::Init();
+
+ MockCurrentTimeGetter timeGetter(10);
+ UnusedCurrentTimeGetter<GTestTime> unused;
+ TimeConverter converter;
+
+ // This call sets the reference time and timestamp
+ TimeStamp ts = converter.GetTimeStampFromSystemTime(10, timeGetter);
+ EXPECT_TS(ts, 0);
+
+ // Advance "TimeStamp::Now" by 10ms, use the same event time and OS time.
+ // Since the event time is the same as before, we expect to get back the
+ // same TimeStamp as before too, despite Now() changing.
+ MockTimeStamp::Advance(10);
+ ts = converter.GetTimeStampFromSystemTime(10, unused);
+ EXPECT_TS(ts, 0);
+
+ // Now let's use an event time 20ms after the old event. This will trigger
+ // forward skew detection and resync the TimeStamp for the new event to Now().
+ ts = converter.GetTimeStampFromSystemTime(30, unused);
+ EXPECT_TS(ts, 10);
+}
+
+TEST(TimeConverter, Overflow)
+{
+ // This tests wrapping time around the max value supported in the GTestTime
+ // type and ensuring it is handled properly.
+
+ MockTimeStamp::Init();
+
+ const GTestTime max = std::numeric_limits<GTestTime>::max();
+ const GTestTime min = std::numeric_limits<GTestTime>::min();
+ double fullRange = (double)max - (double)min;
+ double wrapPeriod = fullRange + 1.0;
+
+ GTestTime almostOverflowed = max - 100;
+ GTestTime overflowed = max + 100;
+ MockCurrentTimeGetter timeGetter(almostOverflowed);
+ UnusedCurrentTimeGetter<GTestTime> unused;
+ TimeConverter converter;
+
+ // Set reference time to 100ms before the overflow point
+ TimeStamp ts =
+ converter.GetTimeStampFromSystemTime(almostOverflowed, timeGetter);
+ EXPECT_TS(ts, 0);
+
+ // Advance everything by 200ms and verify we get back a TimeStamp 200ms from
+ // the baseline despite wrapping an overflow.
+ MockTimeStamp::Advance(200);
+ ts = converter.GetTimeStampFromSystemTime(overflowed, unused);
+ EXPECT_TS(ts, 200);
+
+ // Advance by another full wraparound of the time. This loses some precision
+ // so we have to do the FUZZY match
+ MockTimeStamp::Advance(wrapPeriod);
+ ts = converter.GetTimeStampFromSystemTime(overflowed, unused);
+ EXPECT_TS_FUZZY(ts, 200.0 + wrapPeriod);
+}
+
+TEST(TimeConverter, InvertedOverflow)
+{
+ // This tests time going from near the min value of GTestTime to the max
+ // value of GTestTime
+
+ MockTimeStamp::Init();
+
+ const GTestTime max = std::numeric_limits<GTestTime>::max();
+ const GTestTime min = std::numeric_limits<GTestTime>::min();
+ double fullRange = (double)max - (double)min;
+ double wrapPeriod = fullRange + 1.0;
+
+ GTestTime nearRangeMin = min + 100;
+ GTestTime nearRangeMax = max - 100;
+ double gap = (double)nearRangeMax - (double)nearRangeMin;
+
+ MockCurrentTimeGetter timeGetter(nearRangeMin);
+ UnusedCurrentTimeGetter<GTestTime> unused;
+ TimeConverter converter;
+
+ // Set reference time to value near min numeric limit
+ TimeStamp ts = converter.GetTimeStampFromSystemTime(nearRangeMin, timeGetter);
+ EXPECT_TS(ts, 0);
+
+ // Advance to value near max numeric limit
+ MockTimeStamp::Advance(gap);
+ ts = converter.GetTimeStampFromSystemTime(nearRangeMax, unused);
+ EXPECT_TS(ts, gap);
+
+ // Advance by another full wraparound of the time. This loses some precision
+ // so we have to do the FUZZY match
+ MockTimeStamp::Advance(wrapPeriod);
+ ts = converter.GetTimeStampFromSystemTime(nearRangeMax, unused);
+ EXPECT_TS_FUZZY(ts, gap + wrapPeriod);
+}
+
+TEST(TimeConverter, HalfRangeBoundary)
+{
+ MockTimeStamp::Init();
+
+ GTestTime max = std::numeric_limits<GTestTime>::max();
+ GTestTime min = std::numeric_limits<GTestTime>::min();
+ double fullRange = (double)max - (double)min;
+ double wrapPeriod = fullRange + 1.0;
+ GTestTime halfRange = (GTestTime)(fullRange / 2.0);
+ GTestTime halfWrapPeriod = (GTestTime)(wrapPeriod / 2.0);
+
+ TimeConverter converter;
+
+ GTestTime firstEvent = 10;
+ MockCurrentTimeGetter timeGetter(firstEvent);
+ UnusedCurrentTimeGetter<GTestTime> unused;
+
+ // Set reference time
+ TimeStamp ts = converter.GetTimeStampFromSystemTime(firstEvent, timeGetter);
+ EXPECT_TS(ts, 0);
+
+ // Advance event time by just under the half-period, to trigger about as big
+ // a forwards skew as we possibly can.
+ GTestTime secondEvent = firstEvent + (halfWrapPeriod - 1);
+ ts = converter.GetTimeStampFromSystemTime(secondEvent, unused);
+ EXPECT_TS(ts, 0);
+
+ // The above forwards skew will have reset the reference timestamp. Now
+ // advance Now time by just under the half-range, to trigger about as big
+ // a backwards skew as we possibly can.
+ MockTimeStamp::Advance(halfRange - 1);
+ ts = converter.GetTimeStampFromSystemTime(secondEvent, unused);
+ EXPECT_TS(ts, 0);
+}
+
+TEST(TimeConverter, FractionalMillisBug1626734)
+{
+ MockTimeStamp::Init();
+
+ TimeConverter converter;
+
+ GTestTime eventTime = 10;
+ MockCurrentTimeGetter timeGetter(eventTime);
+ UnusedCurrentTimeGetter<GTestTime> unused;
+
+ TimeStamp ts = converter.GetTimeStampFromSystemTime(eventTime, timeGetter);
+ EXPECT_TS(ts, 0);
+
+ MockTimeStamp::Advance(0.2);
+ ts = converter.GetTimeStampFromSystemTime(eventTime, unused);
+ EXPECT_TS(ts, 0);
+
+ MockTimeStamp::Advance(0.9);
+ TimeStamp ts2 = converter.GetTimeStampFromSystemTime(eventTime, unused);
+ EXPECT_TS(ts2, 0);
+
+ // Since ts2 came from a "future" call relative to ts, we expect ts2 to not
+ // be "before" ts. (i.e. time shouldn't go backwards, even by fractional
+ // milliseconds). This assertion is technically already implied by the
+ // EXPECT_TS checks above, but fixing this assertion is the end result that
+ // we wanted in bug 1626734 so it feels appropriate to recheck it explicitly.
+ EXPECT_TRUE(ts <= ts2);
+}
diff --git a/widget/tests/gtest/TestTouchResampler.cpp b/widget/tests/gtest/TestTouchResampler.cpp
new file mode 100644
index 0000000000..1a5b8e2430
--- /dev/null
+++ b/widget/tests/gtest/TestTouchResampler.cpp
@@ -0,0 +1,941 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include <initializer_list>
+#include "InputData.h"
+#include "Units.h"
+#include "gtest/gtest.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/TimeStamp.h"
+#include "TouchResampler.h"
+
+using namespace mozilla;
+using widget::TouchResampler;
+
+class TouchResamplerTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() { baseTimeStamp = TimeStamp::Now(); }
+
+ TimeStamp Time(double aMilliseconds) {
+ return baseTimeStamp + TimeDuration::FromMilliseconds(aMilliseconds);
+ }
+
+ uint64_t ProcessEvent(
+ MultiTouchInput::MultiTouchType aType,
+ std::initializer_list<std::pair<TimeStamp, ScreenIntPoint>>
+ aHistoricalData,
+ const TimeStamp& aTimeStamp, const ScreenIntPoint& aPosition) {
+ MultiTouchInput input(aType, 0, aTimeStamp, 0);
+ input.mTouches.AppendElement(SingleTouchData(1, aPosition, {}, 0.0f, 0.0f));
+ for (const auto& histData : aHistoricalData) {
+ input.mTouches[0].mHistoricalData.AppendElement(
+ SingleTouchData::HistoricalTouchData{
+ histData.first, histData.second, {}, {}, 0.0f, 0.0f});
+ }
+ return resampler.ProcessEvent(std::move(input));
+ }
+
+ void CheckTime(const TimeStamp& aTimeStamp,
+ const TimeStamp& aExpectedTimeStamp) {
+ EXPECT_EQ((aTimeStamp - baseTimeStamp).ToMilliseconds(),
+ (aExpectedTimeStamp - baseTimeStamp).ToMilliseconds());
+ }
+
+ void CheckEvent(const MultiTouchInput& aEvent,
+ MultiTouchInput::MultiTouchType aExpectedType,
+ std::initializer_list<std::pair<TimeStamp, ScreenIntPoint>>
+ aExpectedHistoricalData,
+ const TimeStamp& aExpectedTimeStamp,
+ const ScreenIntPoint& aExpectedPosition) {
+ EXPECT_EQ(aEvent.mType, aExpectedType);
+ EXPECT_EQ(aEvent.mTouches.Length(), size_t(1));
+ EXPECT_EQ(aEvent.mTouches[0].mHistoricalData.Length(),
+ aExpectedHistoricalData.size());
+ for (size_t i = 0; i < aExpectedHistoricalData.size(); i++) {
+ CheckTime(aEvent.mTouches[0].mHistoricalData[i].mTimeStamp,
+ aExpectedHistoricalData.begin()[i].first);
+ EXPECT_EQ(aEvent.mTouches[0].mHistoricalData[i].mScreenPoint,
+ aExpectedHistoricalData.begin()[i].second);
+ }
+ CheckTime(aEvent.mTimeStamp, aExpectedTimeStamp);
+ EXPECT_EQ(aEvent.mTouches[0].mScreenPoint, aExpectedPosition);
+ }
+
+ struct ExpectedOutgoingEvent {
+ Maybe<uint64_t> mEventId;
+ MultiTouchInput::MultiTouchType mType = MultiTouchInput::MULTITOUCH_START;
+ std::initializer_list<std::pair<TimeStamp, ScreenIntPoint>> mHistoricalData;
+ TimeStamp mTimeStamp;
+ ScreenIntPoint mPosition;
+ };
+
+ void CheckOutgoingEvents(
+ std::initializer_list<ExpectedOutgoingEvent> aExpectedEvents) {
+ auto outgoing = resampler.ConsumeOutgoingEvents();
+ EXPECT_EQ(outgoing.size(), aExpectedEvents.size());
+ for (const auto& expectedEvent : aExpectedEvents) {
+ auto outgoingEvent = std::move(outgoing.front());
+ outgoing.pop();
+
+ EXPECT_EQ(outgoingEvent.mEventId, expectedEvent.mEventId);
+ CheckEvent(outgoingEvent.mEvent, expectedEvent.mType,
+ expectedEvent.mHistoricalData, expectedEvent.mTimeStamp,
+ expectedEvent.mPosition);
+ }
+ }
+
+ TimeStamp baseTimeStamp;
+ TouchResampler resampler;
+};
+
+TEST_F(TouchResamplerTest, BasicExtrapolation) {
+ // Execute the following sequence:
+ //
+ // 0----------10-------16-----20---------------32------------
+ // * touchstart at (10, 10)
+ // * touchmove at (20, 20)
+ // * frame
+ // * touchend at (20, 20)
+ // * frame
+ //
+ // And expect the following output:
+ //
+ // 0----------10-------16-----20---------------32------------
+ // * touchstart at (10, 10)
+ // * touchmove at (26, 26)
+ // * touchmove at (20, 20)
+ // * touchend at (20, 20)
+ //
+ // The first frame should emit an extrapolated touchmove from the position
+ // data in the touchstart and touchmove events.
+ // The touchend should force a synthesized touchmove that returns back to a
+ // non-resampled position.
+
+ EXPECT_FALSE(resampler.InTouchingState());
+
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(10, 10));
+ EXPECT_TRUE(resampler.InTouchingState());
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
+ ScreenIntPoint(20, 20));
+
+ resampler.NotifyFrame(Time(16.0));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(10, 10)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(20, 20)}},
+ Time(16.0),
+ ScreenIntPoint(26, 26)},
+ });
+
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(20.0),
+ ScreenIntPoint(20, 20));
+
+ EXPECT_FALSE(resampler.InTouchingState());
+
+ CheckOutgoingEvents({
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(16.0),
+ ScreenIntPoint(20, 20)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(20.0),
+ ScreenIntPoint(20, 20)},
+ });
+
+ // No more events should be produced from here on out.
+ resampler.NotifyFrame(Time(32.0));
+ auto outgoing = resampler.ConsumeOutgoingEvents();
+ EXPECT_TRUE(outgoing.empty());
+}
+
+TEST_F(TouchResamplerTest, BasicInterpolation) {
+ // Same test as BasicExtrapolation, but with a frame time that's 10ms earlier.
+ //
+ // Execute the following sequence:
+ //
+ // 0------6---10-----------20--22------------30-------------
+ // * touchstart at (10, 10)
+ // * touchmove at (20, 20)
+ // * frame
+ // * touchend at (20, 20)
+ // * frame
+ //
+ // And expect the following output:
+ //
+ // 0------6---10-----------20--22------------30-------------
+ // * touchstart at (10, 10)
+ // * touchmove (16, 16)
+ // * touchmove (20, 20)
+ // * touchend at (20, 20)
+ //
+ // The first frame should emit an interpolated touchmove from the position
+ // data in the touchstart and touchmove events.
+ // The touchend should create a touchmove that returns back to a non-resampled
+ // position.
+
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(10, 10));
+ EXPECT_TRUE(resampler.InTouchingState());
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
+ ScreenIntPoint(20, 20));
+
+ resampler.NotifyFrame(Time(6.0));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(10, 10)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(6.0),
+ ScreenIntPoint(16, 16)},
+ });
+
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(20.0),
+ ScreenIntPoint(20, 20));
+ EXPECT_FALSE(resampler.InTouchingState());
+
+ CheckOutgoingEvents({
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(10.0),
+ ScreenIntPoint(20, 20)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(20.0),
+ ScreenIntPoint(20, 20)},
+ });
+
+ // No more events should be produced from here on out.
+ resampler.NotifyFrame(Time(22.0));
+ auto outgoing = resampler.ConsumeOutgoingEvents();
+ EXPECT_TRUE(outgoing.empty());
+}
+
+TEST_F(TouchResamplerTest, InterpolationFromHistoricalData) {
+ // Interpolate from the historical data in a touch move event.
+ //
+ // Execute the following sequence:
+ //
+ // 0----------10-------16-----20-----------30--32------------
+ // * touchstart at (10, 10)
+ // * [hist] at (20, 25) for
+ // `---------------* touchmove at (30, 30)
+ // * frame
+ // * touchend at (30, 30)
+ // * frame
+ //
+ // And expect the following output:
+ //
+ // 0----------10-------16-----20-----------30--32------------
+ // * touchstart at (10, 10)
+ // * [hist] at (20, 25) for
+ // `--------* touchmove at (26, 28)
+ // * touchmove at (30, 30)
+ // * touchend at (30, 30)
+ //
+ // The first frame should emit an interpolated touchmove from the position
+ // data in the touchmove event, and integrate the historical data point into
+ // the resampled event.
+ // The touchend should force a synthesized touchmove that returns back to a
+ // non-resampled position.
+
+ // This also tests that interpolation works for both x and y, by giving the
+ // historical datapoint different values for x and y.
+ // (26, 28) is 60% of the way from (20, 25) to (30, 30).
+
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(10, 10));
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(20, 25)}},
+ Time(20.0), ScreenIntPoint(30, 30));
+ resampler.NotifyFrame(Time(16.0));
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(30.0),
+ ScreenIntPoint(30, 30));
+ resampler.NotifyFrame(Time(32.0));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(10, 10)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(20, 25)}},
+ Time(16.0),
+ ScreenIntPoint(26, 28)},
+
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(20.0),
+ ScreenIntPoint(30, 30)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(30.0),
+ ScreenIntPoint(30, 30)},
+ });
+}
+
+TEST_F(TouchResamplerTest, MultipleTouches) {
+ EXPECT_FALSE(resampler.InTouchingState());
+
+ // Touch start
+ MultiTouchInput inputStart0(MultiTouchInput::MULTITOUCH_START, 0, Time(0.0),
+ 0);
+ inputStart0.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(10, 10), {}, 0.0f, 0.0f));
+ auto idStart0 = resampler.ProcessEvent(std::move(inputStart0));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Touch move
+ MultiTouchInput inputMove1(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(20.0),
+ 0);
+ inputMove1.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 30), {}, 0.0f, 0.0f));
+ inputMove1.mTouches[0].mHistoricalData.AppendElement(
+ SingleTouchData::HistoricalTouchData{
+ Time(10.0), ScreenIntPoint(20, 25), {}, {}, 0.0f, 0.0f});
+ auto idMove1 = resampler.ProcessEvent(std::move(inputMove1));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Frame
+ resampler.NotifyFrame(Time(16.0));
+
+ // Touch move
+ MultiTouchInput inputMove2(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(30.0),
+ 0);
+ inputMove2.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 40), {}, 0.0f, 0.0f));
+ auto idMove2 = resampler.ProcessEvent(std::move(inputMove2));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Touch start
+ MultiTouchInput inputStart3(MultiTouchInput::MULTITOUCH_START, 0, Time(30.0),
+ 0);
+ inputStart3.mTouches.AppendElement(
+ SingleTouchData(2, ScreenIntPoint(100, 10), {}, 0.0f, 0.0f));
+ inputStart3.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 40), {}, 0.0f, 0.0f));
+ auto idStart3 = resampler.ProcessEvent(std::move(inputStart3));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Touch move
+ MultiTouchInput inputMove4(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(40.0),
+ 0);
+ inputMove4.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 50), {}, 0.0f, 0.0f));
+ inputMove4.mTouches.AppendElement(
+ SingleTouchData(2, ScreenIntPoint(100, 30), {}, 0.0f, 0.0f));
+ auto idMove4 = resampler.ProcessEvent(std::move(inputMove4));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Frame
+ resampler.NotifyFrame(Time(32.0));
+
+ // Touch move
+ MultiTouchInput inputMove5(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(50.0),
+ 0);
+ inputMove5.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 60), {}, 0.0f, 0.0f));
+ inputMove5.mTouches.AppendElement(
+ SingleTouchData(2, ScreenIntPoint(100, 40), {}, 0.0f, 0.0f));
+ auto idMove5 = resampler.ProcessEvent(std::move(inputMove5));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Touch end
+ MultiTouchInput inputEnd6(MultiTouchInput::MULTITOUCH_END, 0, Time(50.0), 0);
+ // Touch point with identifier 1 is lifted
+ inputEnd6.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 60), {}, 0.0f, 0.0f));
+ auto idEnd6 = resampler.ProcessEvent(std::move(inputEnd6));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Frame
+ resampler.NotifyFrame(Time(48.0));
+
+ // Touch move
+ MultiTouchInput inputMove7(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(60.0),
+ 0);
+ inputMove7.mTouches.AppendElement(
+ SingleTouchData(2, ScreenIntPoint(100, 60), {}, 0.0f, 0.0f));
+ auto idMove7 = resampler.ProcessEvent(std::move(inputMove7));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Frame
+ resampler.NotifyFrame(Time(64.0));
+
+ // Touch end
+ MultiTouchInput inputEnd8(MultiTouchInput::MULTITOUCH_END, 0, Time(70.0), 0);
+ // Touch point with identifier 2 is lifted
+ inputEnd8.mTouches.AppendElement(
+ SingleTouchData(2, ScreenIntPoint(100, 60), {}, 0.0f, 0.0f));
+ auto idEnd8 = resampler.ProcessEvent(std::move(inputEnd8));
+ EXPECT_FALSE(resampler.InTouchingState());
+
+ // Check outgoing events
+ auto outgoing = resampler.ConsumeOutgoingEvents();
+ EXPECT_EQ(outgoing.size(), size_t(9));
+
+ auto outgoingStart0 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingStart0.mEventId, Some(idStart0));
+ CheckEvent(outgoingStart0.mEvent, MultiTouchInput::MULTITOUCH_START, {},
+ Time(0.0), ScreenIntPoint(10, 10));
+
+ auto outgoingMove1 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingMove1.mEventId, Some(idMove1));
+ // (26, 28) is 60% of the way from (20, 25) to (30, 30).
+ CheckEvent(outgoingMove1.mEvent, MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(20, 25)}}, Time(16.0),
+ ScreenIntPoint(26, 28));
+
+ auto outgoingMove2 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingMove2.mEventId, Some(idMove2));
+ CheckEvent(outgoingMove2.mEvent, MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(30, 30)}}, Time(30.0),
+ ScreenIntPoint(30, 40));
+
+ auto outgoingStart3 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingStart3.mEventId, Some(idStart3));
+ EXPECT_EQ(outgoingStart3.mEvent.mType, MultiTouchInput::MULTITOUCH_START);
+ CheckTime(outgoingStart3.mEvent.mTimeStamp, Time(30.0));
+ EXPECT_EQ(outgoingStart3.mEvent.mTouches.Length(), size_t(2));
+ // touch order should be taken from the original touch start event
+ EXPECT_EQ(outgoingStart3.mEvent.mTouches[0].mIdentifier, 2);
+ EXPECT_EQ(outgoingStart3.mEvent.mTouches[0].mScreenPoint,
+ ScreenIntPoint(100, 10));
+ EXPECT_EQ(outgoingStart3.mEvent.mTouches[1].mIdentifier, 1);
+ EXPECT_EQ(outgoingStart3.mEvent.mTouches[1].mScreenPoint,
+ ScreenIntPoint(30, 40));
+
+ auto outgoingMove4 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingMove4.mEventId, Some(idMove4));
+ EXPECT_EQ(outgoingMove4.mEvent.mType, MultiTouchInput::MULTITOUCH_MOVE);
+ CheckTime(outgoingMove4.mEvent.mTimeStamp, Time(32.0));
+ EXPECT_EQ(outgoingMove4.mEvent.mTouches.Length(), size_t(2));
+ // Touch order should be taken from the original touch move event.
+ // Both touches should be resampled.
+ EXPECT_EQ(outgoingMove4.mEvent.mTouches[0].mIdentifier, 1);
+ EXPECT_EQ(outgoingMove4.mEvent.mTouches[0].mScreenPoint,
+ ScreenIntPoint(30, 42));
+ EXPECT_EQ(outgoingMove4.mEvent.mTouches[1].mIdentifier, 2);
+ EXPECT_EQ(outgoingMove4.mEvent.mTouches[1].mScreenPoint,
+ ScreenIntPoint(100, 14));
+
+ auto outgoingMove5 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingMove5.mEventId, Some(idMove5));
+ EXPECT_EQ(outgoingMove5.mEvent.mType, MultiTouchInput::MULTITOUCH_MOVE);
+ CheckTime(outgoingMove5.mEvent.mTimeStamp, Time(50.0));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches.Length(), size_t(2));
+ // touch order should be taken from the original touch move event
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mIdentifier, 1);
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mScreenPoint,
+ ScreenIntPoint(30, 60));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mHistoricalData.Length(),
+ size_t(1));
+ CheckTime(outgoingMove5.mEvent.mTouches[0].mHistoricalData[0].mTimeStamp,
+ Time(40.0));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mHistoricalData[0].mScreenPoint,
+ ScreenIntPoint(30, 50));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mIdentifier, 2);
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mScreenPoint,
+ ScreenIntPoint(100, 40));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mHistoricalData.Length(),
+ size_t(1));
+ CheckTime(outgoingMove5.mEvent.mTouches[1].mHistoricalData[0].mTimeStamp,
+ Time(40.0));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mHistoricalData[0].mScreenPoint,
+ ScreenIntPoint(100, 30));
+
+ auto outgoingEnd6 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingEnd6.mEventId, Some(idEnd6));
+ CheckEvent(outgoingEnd6.mEvent, MultiTouchInput::MULTITOUCH_END, {},
+ Time(50.0), ScreenIntPoint(30, 60));
+
+ auto outgoingMove7 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingMove7.mEventId, Some(idMove7));
+ // No extrapolation because the frame at 64.0 cleared the data points because
+ // there was no pending touch move event at that point
+ CheckEvent(outgoingMove7.mEvent, MultiTouchInput::MULTITOUCH_MOVE, {},
+ Time(60.0), ScreenIntPoint(100, 60));
+ EXPECT_EQ(outgoingMove7.mEvent.mTouches[0].mIdentifier, 2);
+
+ auto outgoingEnd8 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingEnd8.mEventId, Some(idEnd8));
+ CheckEvent(outgoingEnd8.mEvent, MultiTouchInput::MULTITOUCH_END, {},
+ Time(70.0), ScreenIntPoint(100, 60));
+}
+
+TEST_F(TouchResamplerTest, MovingPauses) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(10, 10));
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
+ ScreenIntPoint(20, 20));
+ resampler.NotifyFrame(Time(16.0));
+ auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(30.0),
+ ScreenIntPoint(40, 40));
+ resampler.NotifyFrame(Time(32.0));
+ auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0),
+ ScreenIntPoint(50, 40));
+ resampler.NotifyFrame(Time(48.0));
+ resampler.NotifyFrame(Time(64.0));
+ auto idEnd4 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(50, 40));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(10, 10)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(20, 20)}},
+ Time(16.0),
+ ScreenIntPoint(26, 26)},
+
+ {Some(idMove2),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(30.0), ScreenIntPoint(40, 40)}},
+ Time(32.0),
+ ScreenIntPoint(42, 42)},
+
+ {Some(idMove3),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(40.0), ScreenIntPoint(50, 40)}},
+ Time(48.0),
+ ScreenIntPoint(58, 40)},
+
+ // There was no event between two frames here, so we expect a reset event,
+ // so that we pause at a non-resampled position.
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(48.0),
+ ScreenIntPoint(50, 40)},
+
+ {Some(idEnd4),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(50, 40)},
+ });
+}
+
+TEST_F(TouchResamplerTest, MixedInterAndExtrapolation) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
+ ScreenIntPoint(0, 10));
+ resampler.NotifyFrame(Time(11.0)); // 16 - 5
+ auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)}}, Time(30.0),
+ ScreenIntPoint(0, 30));
+ resampler.NotifyFrame(Time(27.0)); // 32 - 5
+ auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0),
+ ScreenIntPoint(0, 40));
+ resampler.NotifyFrame(Time(43.0)); // 48 - 5
+ auto idMove4 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(50.0), ScreenIntPoint(0, 50)}}, Time(60.0),
+ ScreenIntPoint(0, 60));
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd5 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 60));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}},
+ Time(11.0),
+ ScreenIntPoint(0, 11)},
+
+ {Some(idMove2),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)}},
+ Time(27.0),
+ ScreenIntPoint(0, 27)},
+
+ {Some(idMove3),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(30.0), ScreenIntPoint(0, 30)},
+ {Time(40.0), ScreenIntPoint(0, 40)}},
+ Time(43.0),
+ ScreenIntPoint(0, 43)},
+
+ {Some(idMove4),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(50.0), ScreenIntPoint(0, 50)}},
+ Time(59.0),
+ ScreenIntPoint(0, 59)},
+
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(60.0),
+ ScreenIntPoint(0, 60)},
+
+ {Some(idEnd5),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 60)},
+ });
+}
+
+TEST_F(TouchResamplerTest, MultipleMoveEvents) {
+ // Test what happens if multiple touch move events appear between two frames.
+ // This scenario shouldn't occur on Android but we should be able to deal with
+ // it anyway. Check that we don't discard any event IDs.
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
+ ScreenIntPoint(0, 10));
+ resampler.NotifyFrame(Time(11.0)); // 16 - 5
+ auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)}}, Time(30.0),
+ ScreenIntPoint(0, 30));
+ auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0),
+ ScreenIntPoint(0, 40));
+ auto idMove4 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(45.0), ScreenIntPoint(0, 45)}}, Time(50.0),
+ ScreenIntPoint(0, 50));
+ auto idMove5 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(55.0),
+ ScreenIntPoint(0, 55));
+ resampler.NotifyFrame(Time(43.0)); // 48 - 5
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd5 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 60));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}},
+ Time(11.0),
+ ScreenIntPoint(0, 11)},
+
+ {Some(idMove2),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)}},
+ Time(30.0),
+ ScreenIntPoint(0, 30)},
+
+ {Some(idMove3),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(40.0),
+ ScreenIntPoint(0, 40)},
+
+ {Some(idMove4),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(43.0),
+ ScreenIntPoint(0, 43)},
+
+ {Some(idMove5),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(45.0), ScreenIntPoint(0, 45)},
+ {Time(50.0), ScreenIntPoint(0, 50)},
+ {Time(55.0), ScreenIntPoint(0, 55)}},
+ Time(59.0),
+ ScreenIntPoint(0, 59)},
+
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(59.0),
+ ScreenIntPoint(0, 55)},
+
+ {Some(idEnd5),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 60)},
+ });
+}
+
+TEST_F(TouchResamplerTest, LimitFuturePrediction) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ // Fingers move until time 44, then pause. UI thread is occupied until 64.
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)},
+ {Time(32.0), ScreenIntPoint(0, 32)}},
+ Time(44.0), ScreenIntPoint(0, 44));
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 44));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ // kTouchResampleMaxPredictMs == 8
+ // Refuse to predict more than 8ms into the future, the fingers might have
+ // paused. Make an event for time 52 (= 44 + 8) instead of 59.
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)},
+ {Time(32.0), ScreenIntPoint(0, 32)},
+ {Time(44.0), ScreenIntPoint(0, 44)}},
+ Time(52.0),
+ ScreenIntPoint(0, 52)},
+
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(52.0),
+ ScreenIntPoint(0, 44)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 44)},
+ });
+}
+
+TEST_F(TouchResamplerTest, LimitBacksampling) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ // Fingers move until time 44, then pause. UI thread is occupied until 64.
+ // Then we get a frame callback with a very outdated frametime.
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)},
+ {Time(32.0), ScreenIntPoint(0, 32)}},
+ Time(44.0), ScreenIntPoint(0, 44));
+ resampler.NotifyFrame(Time(11.0)); // 16 - 5
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 44));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ // kTouchResampleMaxBacksampleMs == 20
+ // Refuse to sample further back than 20ms before the last data point.
+ // Make an event for time 24 (= 44 - 20) instead of time 11.
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)}},
+ Time(24.0),
+ ScreenIntPoint(0, 24)},
+
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(32.0), ScreenIntPoint(0, 32)}},
+ Time(44.0),
+ ScreenIntPoint(0, 44)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 44)},
+ });
+}
+
+TEST_F(TouchResamplerTest, DontExtrapolateFromOldTouch) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ // Fingers move until time 40, then pause. UI thread is occupied until 64.
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)},
+ {Time(30.0), ScreenIntPoint(0, 30)}},
+ Time(40.0), ScreenIntPoint(0, 40));
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 44));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ // kTouchResampleOldTouchThresholdMs == 17
+ // Refuse to extrapolate from a data point that's more than 17ms older
+ // than the frame time.
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)},
+ {Time(30.0), ScreenIntPoint(0, 30)}},
+ Time(40.0),
+ ScreenIntPoint(0, 40)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 44)},
+ });
+}
+
+TEST_F(TouchResamplerTest, DontExtrapolateIfTooOld) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ // Fingers move until time 10, pause, and move again at 55.
+ // UI thread is occupied until 64.
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(55.0),
+ ScreenIntPoint(0, 55));
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 60));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ // kTouchResampleWindowSize == 40
+ // Refuse to resample between two data points that are more than 40ms
+ // apart.
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}},
+ Time(55.0),
+ ScreenIntPoint(0, 55)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 60)},
+ });
+}
+
+TEST_F(TouchResamplerTest, DontInterpolateIfTooOld) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ // Fingers move until time 10, pause, and move again at 60.
+ // UI thread is occupied until 64.
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(60.0),
+ ScreenIntPoint(0, 60));
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 60));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ // kTouchResampleWindowSize == 40
+ // Refuse to resample between two data points that are more than 40ms
+ // apart.
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}},
+ Time(60.0),
+ ScreenIntPoint(0, 60)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 60)},
+ });
+}
+
+TEST_F(TouchResamplerTest, DiscardOutdatedHistoricalData) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(16.0),
+ ScreenIntPoint(0, 16));
+ resampler.NotifyFrame(Time(20.0));
+ auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(18.0), ScreenIntPoint(0, 18)}}, Time(25.0),
+ ScreenIntPoint(0, 25));
+ auto idEnd3 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(35.0),
+ ScreenIntPoint(0, 25));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)},
+ {Time(16.0), ScreenIntPoint(0, 16)}},
+ Time(20.0),
+ ScreenIntPoint(0, 20)},
+
+ // Discard the historical data point from time 18, because we've already
+ // sent out an event with time 20 and don't want to go back before that.
+ {Some(idMove2),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(25.0),
+ ScreenIntPoint(0, 25)},
+
+ {Some(idEnd3),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(35.0),
+ ScreenIntPoint(0, 25)},
+ });
+}
diff --git a/widget/tests/gtest/TestWinHeaderOnlyUtils.cpp b/widget/tests/gtest/TestWinHeaderOnlyUtils.cpp
new file mode 100644
index 0000000000..5ac361fea0
--- /dev/null
+++ b/widget/tests/gtest/TestWinHeaderOnlyUtils.cpp
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+
+#include <shlwapi.h>
+
+TEST(WinHeaderOnlyUtils, MozPathGetDriveNumber)
+{
+ constexpr auto TestPathGetDriveNumber = [](const wchar_t* aPath) {
+ return mozilla::MozPathGetDriveNumber(aPath) ==
+ ::PathGetDriveNumberW(aPath);
+ };
+ EXPECT_TRUE(TestPathGetDriveNumber(nullptr));
+ EXPECT_TRUE(TestPathGetDriveNumber(L""));
+ EXPECT_TRUE(TestPathGetDriveNumber(L" :"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"a:"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"C:\\file"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"x:/file"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"@:\\\\"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"B"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"abc:\\file"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\:A"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\\\"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\?"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\?\\"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\?\\\\"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\?\\c:\\"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\?\\A"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\?\\ \\"));
+}
diff --git a/widget/tests/gtest/TestWinMessageLoggingUtils.cpp b/widget/tests/gtest/TestWinMessageLoggingUtils.cpp
new file mode 100644
index 0000000000..b0ab09f866
--- /dev/null
+++ b/widget/tests/gtest/TestWinMessageLoggingUtils.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "gtest/gtest.h"
+#include "windows/nsWindowDbg.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_CombinationFlagsHandledFirst)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x3, "COMBO"}, {0x1, "ONE"}, {0x2, "TWO"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x3, flags, nullptr));
+ EXPECT_STREQ("COMBO", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_SingleFlag)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x4, flags, nullptr));
+ EXPECT_STREQ("FOUR", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_SingleFlagWithName)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x4, flags, "paramName"));
+ EXPECT_STREQ("paramName=FOUR", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_MultipleFlags)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x5, flags, nullptr));
+ EXPECT_STREQ("ONE|FOUR", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_NoFlags)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x8, flags, nullptr));
+ EXPECT_STREQ("Unknown (0x00000008)", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_OnlySomeFlagsMatch)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x9, flags, nullptr));
+ EXPECT_STREQ("ONE|Unknown (0x00000008)", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_FlagsMatch_NoZeroReturned)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}, {0x0, "ZERO"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x2, flags, nullptr));
+ EXPECT_STREQ("TWO", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_NoFlagsMatch_NoZeroReturned)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}, {0x0, "ZERO"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x8, flags, nullptr));
+ EXPECT_STREQ("Unknown (0x00000008)", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_NameAndNoFlagsMatch_NoZeroReturned)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}, {0x0, "ZERO"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x8, flags, "paramName"));
+ EXPECT_STREQ("paramName=Unknown (0x00000008)", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_ValueIsZero_ZeroReturned)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}, {0x0, "ZERO"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x0, flags, nullptr));
+ EXPECT_STREQ("ZERO", result.get());
+}
diff --git a/widget/tests/gtest/TestWinWindowOcclusionTracker.cpp b/widget/tests/gtest/TestWinWindowOcclusionTracker.cpp
new file mode 100644
index 0000000000..b1f1114a3a
--- /dev/null
+++ b/widget/tests/gtest/TestWinWindowOcclusionTracker.cpp
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "gtest/gtest.h"
+
+#include <dwmapi.h>
+#include <windows.h>
+
+#include "MockWinWidget.h"
+#include "mozilla/widget/WinWindowOcclusionTracker.h"
+#include "mozilla/WindowsVersion.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+class WinWindowOcclusionTrackerTest : public ::testing::Test {
+ protected:
+ HWND CreateNativeWindow(DWORD aStyle, DWORD aExStyle) {
+ mMockWinWidget =
+ MockWinWidget::Create(WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | aStyle,
+ aExStyle, LayoutDeviceIntRect(0, 0, 100, 100));
+ EXPECT_NE(nullptr, mMockWinWidget.get());
+ HWND hwnd = mMockWinWidget->GetWnd();
+ HRGN region = ::CreateRectRgn(0, 0, 0, 0);
+ EXPECT_NE(nullptr, region);
+ if (::GetWindowRgn(hwnd, region) == COMPLEXREGION) {
+ // On Windows 7, the newly created window has a complex region, which
+ // means it will be ignored during the occlusion calculation. So, force
+ // it to have a simple region so that we get test coverage on win 7.
+ RECT boundingRect;
+ EXPECT_TRUE(::GetWindowRect(hwnd, &boundingRect));
+ HRGN rectangularRegion = ::CreateRectRgnIndirect(&boundingRect);
+ EXPECT_NE(nullptr, rectangularRegion);
+ ::SetWindowRgn(hwnd, rectangularRegion, /* bRedraw = */ TRUE);
+ ::DeleteObject(rectangularRegion);
+ }
+ ::DeleteObject(region);
+
+ ::ShowWindow(hwnd, SW_SHOWNORMAL);
+ EXPECT_TRUE(UpdateWindow(hwnd));
+ return hwnd;
+ }
+
+ // Wrapper around IsWindowVisibleAndFullyOpaque so only the test class
+ // needs to be a friend of NativeWindowOcclusionTrackerWin.
+ bool CheckWindowVisibleAndFullyOpaque(HWND aHWnd,
+ LayoutDeviceIntRect* aWinRect) {
+ bool ret = WinWindowOcclusionTracker::IsWindowVisibleAndFullyOpaque(
+ aHWnd, aWinRect);
+ // In general, if IsWindowVisibleAndFullyOpaque returns false, the
+ // returned rect should not be altered.
+ if (!ret) {
+ EXPECT_EQ(*aWinRect, LayoutDeviceIntRect(0, 0, 0, 0));
+ }
+ return ret;
+ }
+
+ RefPtr<MockWinWidget> mMockWinWidget;
+};
+
+TEST_F(WinWindowOcclusionTrackerTest, VisibleOpaqueWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, /* aExStyle = */ 0);
+ LayoutDeviceIntRect returnedRect;
+ // Normal windows should be visible.
+ EXPECT_TRUE(CheckWindowVisibleAndFullyOpaque(hwnd, &returnedRect));
+
+ // Check that the returned rect == the actual window rect of the hwnd.
+ RECT winRect;
+ ASSERT_TRUE(::GetWindowRect(hwnd, &winRect));
+ EXPECT_EQ(returnedRect, LayoutDeviceIntRect(winRect.left, winRect.top,
+ winRect.right - winRect.left,
+ winRect.bottom - winRect.top));
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, MinimizedWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, /* aExStyle = */ 0);
+ LayoutDeviceIntRect winRect;
+ ::ShowWindow(hwnd, SW_MINIMIZE);
+ // Minimized windows are not considered visible.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, TransparentWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, WS_EX_TRANSPARENT);
+ LayoutDeviceIntRect winRect;
+ // Transparent windows are not considered visible and opaque.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, ToolWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, WS_EX_TOOLWINDOW);
+ LayoutDeviceIntRect winRect;
+ // Tool windows are not considered visible and opaque.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, LayeredAlphaWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, WS_EX_LAYERED);
+ LayoutDeviceIntRect winRect;
+ BYTE alpha = 1;
+ DWORD flags = LWA_ALPHA;
+ COLORREF colorRef = RGB(1, 1, 1);
+ SetLayeredWindowAttributes(hwnd, colorRef, alpha, flags);
+ // Layered windows with alpha < 255 are not considered visible and opaque.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, UpdatedLayeredAlphaWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, WS_EX_LAYERED);
+ LayoutDeviceIntRect winRect;
+ HDC hdc = ::CreateCompatibleDC(nullptr);
+ EXPECT_NE(nullptr, hdc);
+ BLENDFUNCTION blend = {AC_SRC_OVER, 0x00, 0xFF, AC_SRC_ALPHA};
+
+ ::UpdateLayeredWindow(hwnd, hdc, nullptr, nullptr, nullptr, nullptr,
+ RGB(0xFF, 0xFF, 0xFF), &blend, ULW_OPAQUE);
+ // Layered windows set up with UpdateLayeredWindow instead of
+ // SetLayeredWindowAttributes should not be considered visible and opaque.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+ ::DeleteDC(hdc);
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, LayeredNonAlphaWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, WS_EX_LAYERED);
+ LayoutDeviceIntRect winRect;
+ BYTE alpha = 1;
+ DWORD flags = 0;
+ COLORREF colorRef = RGB(1, 1, 1);
+ ::SetLayeredWindowAttributes(hwnd, colorRef, alpha, flags);
+ // Layered non alpha windows are considered visible and opaque.
+ EXPECT_TRUE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, ComplexRegionWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, /* aExStyle = */ 0);
+ LayoutDeviceIntRect winRect;
+ // Create a region with rounded corners, which should be a complex region.
+ HRGN region = CreateRoundRectRgn(1, 1, 100, 100, 5, 5);
+ EXPECT_NE(nullptr, region);
+ ::SetWindowRgn(hwnd, region, /* bRedraw = */ TRUE);
+ // Windows with complex regions are not considered visible and fully opaque.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+ DeleteObject(region);
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, PopupWindow) {
+ HWND hwnd = CreateNativeWindow(WS_POPUP, /* aExStyle = */ 0);
+ LayoutDeviceIntRect winRect;
+ // Normal Popup Windows are not considered visible.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, CloakedWindow) {
+ // Cloaking is only supported in Windows 8 and above.
+ if (!IsWin8OrLater()) {
+ return;
+ }
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, /* aExStyle = */ 0);
+ LayoutDeviceIntRect winRect;
+ BOOL cloak = TRUE;
+ ::DwmSetWindowAttribute(hwnd, DWMWA_CLOAK, &cloak, sizeof(cloak));
+ // Cloaked Windows are not considered visible.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+}
diff --git a/widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp b/widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp
new file mode 100644
index 0000000000..13cd95651c
--- /dev/null
+++ b/widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp
@@ -0,0 +1,402 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "gtest/gtest.h"
+
+#include "MockWinWidget.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/widget/WinEventObserver.h"
+#include "mozilla/widget/WinWindowOcclusionTracker.h"
+#include "nsThreadUtils.h"
+#include "Units.h"
+#include "WinUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#define PREF_DISPLAY_STATE \
+ "widget.windows.window_occlusion_tracking_display_state.enabled"
+#define PREF_SESSION_LOCK \
+ "widget.windows.window_occlusion_tracking_session_lock.enabled"
+
+class WinWindowOcclusionTrackerInteractiveTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Shut down WinWindowOcclusionTracker if it exists.
+ // This could happen when WinWindowOcclusionTracker was initialized by other
+ // gtest
+ if (WinWindowOcclusionTracker::Get()) {
+ WinWindowOcclusionTracker::ShutDown();
+ }
+ EXPECT_EQ(nullptr, WinWindowOcclusionTracker::Get());
+
+ WinWindowOcclusionTracker::Ensure();
+ EXPECT_NE(nullptr, WinWindowOcclusionTracker::Get());
+
+ WinWindowOcclusionTracker::Get()->EnsureDisplayStatusObserver();
+ WinWindowOcclusionTracker::Get()->EnsureSessionChangeObserver();
+ }
+
+ void TearDown() override {
+ WinWindowOcclusionTracker::ShutDown();
+ EXPECT_EQ(nullptr, WinWindowOcclusionTracker::Get());
+ }
+
+ void SetNativeWindowBounds(HWND aHWnd, const LayoutDeviceIntRect aBounds) {
+ RECT wr;
+ wr.left = aBounds.X();
+ wr.top = aBounds.Y();
+ wr.right = aBounds.XMost();
+ wr.bottom = aBounds.YMost();
+
+ ::AdjustWindowRectEx(&wr, ::GetWindowLong(aHWnd, GWL_STYLE), FALSE,
+ ::GetWindowLong(aHWnd, GWL_EXSTYLE));
+
+ // Make sure to keep the window onscreen, as AdjustWindowRectEx() may have
+ // moved part of it offscreen. But, if the original requested bounds are
+ // offscreen, don't adjust the position.
+ LayoutDeviceIntRect windowBounds(wr.left, wr.top, wr.right - wr.left,
+ wr.bottom - wr.top);
+
+ if (aBounds.X() >= 0) {
+ windowBounds.x = std::max(0, windowBounds.X());
+ }
+ if (aBounds.Y() >= 0) {
+ windowBounds.y = std::max(0, windowBounds.Y());
+ }
+ ::SetWindowPos(aHWnd, HWND_TOP, windowBounds.X(), windowBounds.Y(),
+ windowBounds.Width(), windowBounds.Height(),
+ SWP_NOREPOSITION);
+ EXPECT_TRUE(::UpdateWindow(aHWnd));
+ }
+
+ void CreateNativeWindowWithBounds(LayoutDeviceIntRect aBounds) {
+ mMockWinWidget = MockWinWidget::Create(
+ WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, /* aExStyle = */ 0, aBounds);
+ EXPECT_NE(nullptr, mMockWinWidget.get());
+ HWND hwnd = mMockWinWidget->GetWnd();
+ SetNativeWindowBounds(hwnd, aBounds);
+ HRGN region = ::CreateRectRgn(0, 0, 0, 0);
+ EXPECT_NE(nullptr, region);
+ if (::GetWindowRgn(hwnd, region) == COMPLEXREGION) {
+ // On Windows 7, the newly created window has a complex region, which
+ // means it will be ignored during the occlusion calculation. So, force
+ // it to have a simple region so that we get test coverage on win 7.
+ RECT boundingRect;
+ EXPECT_TRUE(::GetWindowRect(hwnd, &boundingRect));
+ HRGN rectangularRegion = ::CreateRectRgnIndirect(&boundingRect);
+ EXPECT_NE(nullptr, rectangularRegion);
+ ::SetWindowRgn(hwnd, rectangularRegion, /* bRedraw = */ TRUE);
+ ::DeleteObject(rectangularRegion);
+ }
+ ::DeleteObject(region);
+
+ ::ShowWindow(hwnd, SW_SHOWNORMAL);
+ EXPECT_TRUE(UpdateWindow(hwnd));
+ }
+
+ RefPtr<MockWinWidget> CreateTrackedWindowWithBounds(
+ LayoutDeviceIntRect aBounds) {
+ RefPtr<MockWinWidget> window = MockWinWidget::Create(
+ WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, /* aExStyle */ 0, aBounds);
+ EXPECT_NE(nullptr, window.get());
+ HWND hwnd = window->GetWnd();
+ ::ShowWindow(hwnd, SW_SHOWNORMAL);
+ WinWindowOcclusionTracker::Get()->Enable(window, window->GetWnd());
+ return window;
+ }
+
+ int GetNumVisibleRootWindows() {
+ return WinWindowOcclusionTracker::Get()->mNumVisibleRootWindows;
+ }
+
+ void OnDisplayStateChanged(bool aDisplayOn) {
+ WinWindowOcclusionTracker::Get()->OnDisplayStateChanged(aDisplayOn);
+ }
+
+ RefPtr<MockWinWidget> mMockWinWidget;
+};
+
+// Simple test completely covering a tracked window with a native window.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, SimpleOcclusion) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ window->SetExpectation(widget::OcclusionState::OCCLUDED);
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Simple test partially covering a tracked window with a native window.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, PartialOcclusion) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 200, 200));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(0, 0, 50, 50));
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Simple test that a partly off screen tracked window, with the on screen part
+// occluded by a native window, is considered occluded.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, OffscreenOcclusion) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ // Move the tracked window 50 pixels offscreen to the left.
+ int screenLeft = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
+ ::SetWindowPos(window->GetWnd(), HWND_TOP, screenLeft - 50, 0, 100, 100,
+ SWP_NOZORDER | SWP_NOSIZE);
+
+ // Create a native window that covers the onscreen part of the tracked window.
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(screenLeft, 0, 50, 100));
+ window->SetExpectation(widget::OcclusionState::OCCLUDED);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Simple test with a tracked window and native window that do not overlap.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, SimpleVisible) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(200, 0, 100, 100));
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Simple test with a minimized tracked window and native window.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, SimpleHidden) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(200, 0, 100, 100));
+ // Iconify the tracked window and check that its occlusion state is HIDDEN.
+ ::CloseWindow(window->GetWnd());
+ window->SetExpectation(widget::OcclusionState::HIDDEN);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Test that minimizing and restoring a tracked window results in the occlusion
+// tracker re-registering for win events and detecting that a native window
+// occludes the tracked window.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest,
+ OcclusionAfterVisibilityToggle) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ window->SetExpectation(widget::OcclusionState::HIDDEN);
+ WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
+ window, /* aVisible = */ false);
+
+ // This makes the window iconic.
+ ::CloseWindow(window->GetWnd());
+
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ // HIDDEN state is set synchronously by OnWindowVsiblityChanged notification,
+ // before occlusion is calculated, so the above expectation will be met w/o an
+ // occlusion calculation.
+ // Loop until an occlusion calculation has run with no non-hidden windows.
+ while (GetNumVisibleRootWindows() != 0) {
+ // Need to pump events in order for UpdateOcclusionState to get called, and
+ // update the number of non hidden windows. When that number is 0,
+ // occlusion has been calculated with no visible tracked windows.
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
+ window, /* aVisible = */ true);
+ // This opens the window made iconic above.
+ ::OpenIcon(window->GetWnd());
+
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ // Open a native window that occludes the visible tracked window.
+ window->SetExpectation(widget::OcclusionState::OCCLUDED);
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Test that locking the screen causes visible windows to become occluded.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, LockScreenVisibleOcclusion) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ window->SetExpectation(widget::OcclusionState::OCCLUDED);
+ // Unfortunately, this relies on knowing that NativeWindowOcclusionTracker
+ // uses SessionChangeObserver to listen for WM_WTSSESSION_CHANGE messages, but
+ // actually locking the screen isn't feasible.
+ DWORD currentSessionId = 0;
+ ::ProcessIdToSessionId(::GetCurrentProcessId(), &currentSessionId);
+ ::PostMessage(WinEventHub::Get()->GetWnd(), WM_WTSSESSION_CHANGE,
+ WTS_SESSION_LOCK, currentSessionId);
+
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ MSG msg;
+ bool gotMessage =
+ ::PeekMessageW(&msg, WinEventHub::Get()->GetWnd(), 0, 0, PM_REMOVE);
+ if (gotMessage) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ false);
+ PR_Sleep(PR_INTERVAL_NO_WAIT);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Test that locking the screen leaves hidden windows as hidden.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, LockScreenHiddenOcclusion) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ // Iconify the tracked window and check that its occlusion state is HIDDEN.
+ ::CloseWindow(window->GetWnd());
+ window->SetExpectation(widget::OcclusionState::HIDDEN);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ // Force the state to VISIBLE.
+ window->NotifyOcclusionState(widget::OcclusionState::VISIBLE);
+
+ window->SetExpectation(widget::OcclusionState::HIDDEN);
+
+ // Unfortunately, this relies on knowing that NativeWindowOcclusionTracker
+ // uses SessionChangeObserver to listen for WM_WTSSESSION_CHANGE messages, but
+ // actually locking the screen isn't feasible.
+ DWORD currentSessionId = 0;
+ ::ProcessIdToSessionId(::GetCurrentProcessId(), &currentSessionId);
+ PostMessage(WinEventHub::Get()->GetWnd(), WM_WTSSESSION_CHANGE,
+ WTS_SESSION_LOCK, currentSessionId);
+
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ MSG msg;
+ bool gotMessage =
+ ::PeekMessageW(&msg, WinEventHub::Get()->GetWnd(), 0, 0, PM_REMOVE);
+ if (gotMessage) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ false);
+ PR_Sleep(PR_INTERVAL_NO_WAIT);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Test that locking the screen from a different session doesn't mark window
+// as occluded.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, LockScreenDifferentSession) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 200, 200));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ // Force the state to OCCLUDED.
+ window->NotifyOcclusionState(widget::OcclusionState::OCCLUDED);
+
+ // Generate a session change lock screen with a session id that's not
+ // currentSessionId.
+ DWORD currentSessionId = 0;
+ ::ProcessIdToSessionId(::GetCurrentProcessId(), &currentSessionId);
+ ::PostMessage(WinEventHub::Get()->GetWnd(), WM_WTSSESSION_CHANGE,
+ WTS_SESSION_LOCK, currentSessionId + 1);
+
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ // Create a native window to trigger occlusion calculation.
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(0, 0, 50, 50));
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ MSG msg;
+ bool gotMessage =
+ ::PeekMessageW(&msg, WinEventHub::Get()->GetWnd(), 0, 0, PM_REMOVE);
+ if (gotMessage) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ false);
+ PR_Sleep(PR_INTERVAL_NO_WAIT);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Test that display off & on power state notification causes visible windows to
+// become occluded, then visible.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, DisplayOnOffHandling) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ window->SetExpectation(widget::OcclusionState::OCCLUDED);
+
+ // Turning display off and on isn't feasible, so send a notification.
+ OnDisplayStateChanged(/* aDisplayOn */ false);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ OnDisplayStateChanged(/* aDisplayOn */ true);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
diff --git a/widget/tests/gtest/moz.build b/widget/tests/gtest/moz.build
new file mode 100644
index 0000000000..613844fa78
--- /dev/null
+++ b/widget/tests/gtest/moz.build
@@ -0,0 +1,27 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+UNIFIED_SOURCES = [
+ "TestTimeConverter.cpp",
+ "TestTouchResampler.cpp",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "MockWinWidget.cpp",
+ "TestWinHeaderOnlyUtils.cpp",
+ "TestWinMessageLoggingUtils.cpp",
+ "TestWinWindowOcclusionTracker.cpp",
+ "TestWinWindowOcclusionTrackerInteractive.cpp",
+ ]
+
+FINAL_LIBRARY = "xul-gtest"
+
+LOCAL_INCLUDES += [
+ "/widget",
+]
+
+DisableStlWrapping()
diff --git a/widget/tests/mochitest.ini b/widget/tests/mochitest.ini
new file mode 100644
index 0000000000..40a7de7104
--- /dev/null
+++ b/widget/tests/mochitest.ini
@@ -0,0 +1,27 @@
+[DEFAULT]
+prefs =
+ dom.forms.enterkeyhint=true # only for test_actionhint.html
+
+[test_AltGr_key_events_in_web_content_on_windows.html]
+skip-if =
+ toolkit != 'windows' # Windows widget test
+ headless # bug 1410525
+[test_actionhint.html]
+[test_assign_event_data.html]
+skip-if =
+ toolkit == "cocoa" # bug 933303
+ (toolkit == 'android' && debug) # bug 1285414
+ android_version == '24'
+ (headless && os == "win")
+[test_autocapitalize.html]
+[test_keypress_event_with_alt_on_mac.html]
+skip-if = toolkit != "cocoa"
+[test_mouse_event_with_control_on_mac.html]
+skip-if = toolkit != "cocoa"
+support-files = !/gfx/layers/apz/test/mochitest/apz_test_utils.js
+[test_picker_no_crash.html]
+skip-if =
+ asan
+ debug # bug 1267491
+support-files = window_picker_no_crash_child.html
+[test_textScaleFactor_system_font.html]
diff --git a/widget/tests/moz.build b/widget/tests/moz.build
new file mode 100644
index 0000000000..b50a2949f7
--- /dev/null
+++ b/widget/tests/moz.build
@@ -0,0 +1,125 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Widget")
+
+with Files("browser/browser_test_ContentCache.html"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("unit/*macwebapputils*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("unit/*taskbar_jumplistitems*"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+
+with Files("TestChromeMargin.cpp"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+
+with Files("*413277*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*428405*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*429954*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*444800*"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+
+with Files("*466599*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*478536*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*485118*"):
+ BUG_COMPONENT = ("Toolkit", "XUL Widgets")
+
+with Files("*517396*"):
+ BUG_COMPONENT = ("Toolkit", "XUL Widgets")
+
+with Files("*522217*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*538242*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*565392*"):
+ BUG_COMPONENT = ("Core", "DOM: Serializers")
+
+with Files("*586713*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*593307*"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+
+with Files("*596600*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*673301*"):
+ BUG_COMPONENT = ("Firefox", "Bookmarks & History")
+
+with Files("test_assign_event_data.html"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("test_input_events_on_deactive_window.xhtml"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*chrome_context_menus_win*"):
+ BUG_COMPONENT = ("Core", "General")
+
+with Files("*composition_text_querycontent*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*key_event_counts*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*imestate*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*mouse_scroll*"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+
+with Files("*native*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*panel_mouse_coords*"):
+ BUG_COMPONENT = ("Core", "Widget: Gtk")
+
+with Files("*picker_no_crash*"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+
+with Files("*platform_colors*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*position_on_resize*"):
+ BUG_COMPONENT = ("Core", "Widget: Gtk")
+
+with Files("test_sizemode_events.xhtml"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*system_status_bar*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("*taskbar_progress*"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+
+with Files("*wheeltransaction*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+
+XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.ini"]
+MOCHITEST_MANIFESTS += ["mochitest.ini"]
+MOCHITEST_CHROME_MANIFESTS += ["chrome.ini"]
+BROWSER_CHROME_MANIFESTS += ["browser/browser.ini"]
+
+# if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+#
+# Test disabled because it requires the internal API. Re-enabling this test
+# is bug 652123.
+# CPP_UNIT_TESTS += ['TestChromeMargin']
diff --git a/widget/tests/native_menus_window.xhtml b/widget/tests/native_menus_window.xhtml
new file mode 100644
index 0000000000..2b3d3f2aa3
--- /dev/null
+++ b/widget/tests/native_menus_window.xhtml
@@ -0,0 +1,282 @@
+<?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="NativeMenuWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="300"
+ height="300"
+ onload="onLoad();"
+ title="Native Menu Test">
+
+ <command id="cmd_FooItem0" oncommand="executedCommandID = 'cmd_FooItem0';"/>
+ <command id="cmd_FooItem1" oncommand="executedCommandID = 'cmd_FooItem1';"/>
+ <command id="cmd_BarItem0" oncommand="executedCommandID = 'cmd_BarItem0';"/>
+ <command id="cmd_BarItem1" oncommand="executedCommandID = 'cmd_BarItem1';"/>
+ <command id="cmd_NewItem0" oncommand="executedCommandID = 'cmd_NewItem0';"/>
+ <command id="cmd_NewItem1" oncommand="executedCommandID = 'cmd_NewItem1';"/>
+ <command id="cmd_NewItem2" oncommand="executedCommandID = 'cmd_NewItem2';"/>
+ <command id="cmd_NewItem3" oncommand="executedCommandID = 'cmd_NewItem3';"/>
+ <command id="cmd_NewItem4" oncommand="executedCommandID = 'cmd_NewItem4';"/>
+ <command id="cmd_NewItem5" oncommand="executedCommandID = 'cmd_NewItem5';"/>
+
+ <!-- We do not modify any menus or menu items defined here in testing. These
+ serve as a baseline structure for us to test after other modifications.
+ We add children to the menubar defined here and test by modifying those
+ children. -->
+ <menubar id="nativemenubar">
+ <menu id="foo" label="Foo">
+ <menupopup>
+ <menuitem label="FooItem0" command="cmd_FooItem0"/>
+ <menuitem label="FooItem1" command="cmd_FooItem1"/>
+ <menuseparator/>
+ <menu label="Bar">
+ <menupopup>
+ <menuitem label="BarItem0" command="cmd_BarItem0"/>
+ <menuitem label="BarItem1" command="cmd_BarItem1"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menubar>
+
+ <script type="application/javascript"><![CDATA[
+
+ function ok(condition, message) {
+ window.arguments[0].SimpleTest.ok(condition, message);
+ }
+
+ function onTestsFinished() {
+ window.close();
+ window.arguments[0].SimpleTest.finish();
+ }
+
+ // Force a menu to update itself. All of the menus parents will be updated
+ // as well. An empty string will force a complete menu system reload.
+ function forceUpdateNativeMenuAt(location) {
+ var utils = window.windowUtils;
+ try {
+ utils.forceUpdateNativeMenuAt(location);
+ }
+ catch (e) {
+ dump(e + "\n");
+ }
+ }
+
+ var executedCommandID = "";
+
+ function testItem(location, targetID) {
+ var utils = window.windowUtils;
+ var correctCommandHandler = false;
+ try {
+ utils.activateNativeMenuItemAt(location);
+ correctCommandHandler = executedCommandID == targetID;
+ }
+ catch (e) {
+ dump(e + "\n");
+ }
+ finally {
+ executedCommandID = "";
+ }
+ return correctCommandHandler;
+ }
+
+ function createXULMenuPopup() {
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var item = document.createElementNS(XUL_NS, "menupopup");
+ return item;
+ }
+
+ function createXULMenu(aLabel) {
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var item = document.createElementNS(XUL_NS, "menu");
+ item.setAttribute("label", aLabel);
+ return item;
+ }
+
+ function createXULMenuItem(aLabel, aCommandId) {
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var item = document.createElementNS(XUL_NS, "menuitem");
+ item.setAttribute("label", aLabel);
+ item.setAttribute("command", aCommandId);
+ return item;
+ }
+
+ function runBaseMenuTests() {
+ forceUpdateNativeMenuAt("0|3");
+ return testItem("0|0", "cmd_FooItem0") &&
+ testItem("0|1", "cmd_FooItem1") &&
+ testItem("0|3|0", "cmd_BarItem0") &&
+ testItem("0|3|1", "cmd_BarItem1");
+ }
+
+ function onLoad() {
+ var _delayedOnLoad = function() {
+ // First let's run the base menu tests.
+ ok(runBaseMenuTests());
+
+ // Set up some nodes that we'll use.
+ var menubarNode = document.getElementById("nativemenubar");
+ var newMenu0 = createXULMenu("NewMenu0");
+ var newMenu1 = createXULMenu("NewMenu1");
+ var newMenuPopup0 = createXULMenuPopup();
+ var newMenuPopup1 = createXULMenuPopup();
+ var newMenuItem0 = createXULMenuItem("NewMenuItem0", "cmd_NewItem0");
+ var newMenuItem1 = createXULMenuItem("NewMenuItem1", "cmd_NewItem1");
+ var newMenuItem2 = createXULMenuItem("NewMenuItem2", "cmd_NewItem2");
+ var newMenuItem3 = createXULMenuItem("NewMenuItem3", "cmd_NewItem3");
+ var newMenuItem4 = createXULMenuItem("NewMenuItem4", "cmd_NewItem4");
+ var newMenuItem5 = createXULMenuItem("NewMenuItem5", "cmd_NewItem5");
+
+ // Create another submenu with hierarchy via DOM manipulation.
+ // ******************
+ // * Foo * NewMenu0 * <- Menu bar
+ // ******************
+ // ****************
+ // * NewMenuItem0 * <- NewMenu0 submenu
+ // ****************
+ // * NewMenuItem1 *
+ // ****************
+ // * NewMenuItem2 *
+ // *******************************
+ // * NewMenu1 > * NewMenuItem3 * <- NewMenu1 submenu
+ // *******************************
+ // * NewMenuItem4 *
+ // ****************
+ // * NewMenuItem5 *
+ // ****************
+ newMenu0.appendChild(newMenuPopup0);
+ newMenuPopup0.appendChild(newMenuItem0);
+ newMenuPopup0.appendChild(newMenuItem1);
+ newMenuPopup0.appendChild(newMenuItem2);
+ newMenuPopup0.appendChild(newMenu1);
+ newMenu1.appendChild(newMenuPopup1);
+ newMenuPopup1.appendChild(newMenuItem3);
+ newMenuPopup1.appendChild(newMenuItem4);
+ newMenuPopup1.appendChild(newMenuItem5);
+ //XXX - we have to append the menu to the top-level of the menu bar
+ // only after constructing it. If we append before construction, it is
+ // invalid because it has no children and we don't validate it if we add
+ // children later.
+ menubarNode.appendChild(newMenu0);
+ forceUpdateNativeMenuAt("1|3");
+ // Run basic tests again.
+ ok(runBaseMenuTests());
+
+ // Error strings.
+ var sa = "Command handler(s) should have activated";
+ var sna = "Command handler(s) should not have activated";
+
+ // Test middle items.
+ ok(testItem("1|1", "cmd_NewItem1"), sa);
+ ok(testItem("1|3|1", "cmd_NewItem4"), sa);
+
+ // Hide newMenu0.
+ newMenu0.setAttribute("hidden", "true");
+ ok(runBaseMenuTests(), sa); // the base menu should still be unhidden
+ ok(!testItem("1|0", ""), sna);
+ ok(!testItem("1|1", ""), sna);
+ ok(!testItem("1|2", ""), sna);
+ ok(!testItem("1|3|0", ""), sna);
+ ok(!testItem("1|3|1", ""), sna);
+ ok(!testItem("1|3|2", ""), sna);
+
+ // Show newMenu0.
+ newMenu0.setAttribute("hidden", "false");
+ forceUpdateNativeMenuAt("1|3");
+ ok(runBaseMenuTests(), sa);
+ ok(testItem("1|0", "cmd_NewItem0"), sa);
+ ok(testItem("1|1", "cmd_NewItem1"), sa);
+ ok(testItem("1|2", "cmd_NewItem2"), sa);
+ ok(testItem("1|3|0", "cmd_NewItem3"), sa);
+ ok(testItem("1|3|1", "cmd_NewItem4"), sa);
+ ok(testItem("1|3|2", "cmd_NewItem5"), sa);
+
+ // Hide items.
+ newMenuItem1.setAttribute("hidden", "true");
+ newMenuItem4.setAttribute("hidden", "true");
+ forceUpdateNativeMenuAt("1|2");
+ ok(runBaseMenuTests(), sa);
+ ok(testItem("1|0", "cmd_NewItem0"), sa);
+ ok(testItem("1|1", "cmd_NewItem2"), sa);
+ ok(!testItem("1|2", ""), sna);
+ ok(testItem("1|2|0", "cmd_NewItem3"), sa);
+ ok(testItem("1|2|1", "cmd_NewItem5"), sa);
+ ok(!testItem("1|2|2", ""), sna);
+
+ // Show items.
+ newMenuItem1.setAttribute("hidden", "false");
+ newMenuItem4.setAttribute("hidden", "false");
+ forceUpdateNativeMenuAt("1|3");
+ ok(runBaseMenuTests(), sa);
+ ok(testItem("1|0", "cmd_NewItem0"), sa);
+ ok(testItem("1|1", "cmd_NewItem1"), sa);
+ ok(testItem("1|2", "cmd_NewItem2"), sa);
+ ok(testItem("1|3|0", "cmd_NewItem3"), sa);
+ ok(testItem("1|3|1", "cmd_NewItem4"), sa);
+ ok(testItem("1|3|2", "cmd_NewItem5"), sa);
+
+ // At this point in the tests the state of the menus has been returned
+ // to the originally diagramed state.
+
+ // Test command disabling
+ var cmd_NewItem0 = document.getElementById("cmd_NewItem0");
+ ok(testItem("1|0", "cmd_NewItem0"), sa);
+ cmd_NewItem0.setAttribute("disabled", "true");
+ ok(!testItem("1|0", "cmd_NewItem0"), sna);
+ cmd_NewItem0.removeAttribute("disabled");
+ ok(testItem("1|0", "cmd_NewItem0"), sa);
+
+ // Remove menu.
+ menubarNode.removeChild(newMenu0);
+ ok(runBaseMenuTests(), sa);
+ ok(!testItem("1|0", ""), sna);
+ ok(!testItem("1|1", ""), sna);
+ ok(!testItem("1|2", ""), sna);
+ ok(!testItem("1|3|0", ""), sna);
+ ok(!testItem("1|3|1", ""), sna);
+ ok(!testItem("1|3|2", ""), sna);
+ // return state to original diagramed state
+ menubarNode.appendChild(newMenu0);
+
+ // Test for bug 447042, make sure that adding a menu node with no children
+ // to the menu bar and then adding another menu node with children works.
+ // Menus with no children don't get their native menu items shown and that
+ // caused internal arrays to get out of sync and an append crashed.
+ var tmpMenu0 = createXULMenu("tmpMenu0");
+ menubarNode.removeChild(newMenu0);
+ menubarNode.appendChild(tmpMenu0);
+ menubarNode.appendChild(newMenu0);
+ forceUpdateNativeMenuAt("1|3");
+ ok(runBaseMenuTests());
+ ok(testItem("1|0", "cmd_NewItem0"), sa);
+ ok(testItem("1|1", "cmd_NewItem1"), sa);
+ ok(testItem("1|2", "cmd_NewItem2"), sa);
+ ok(testItem("1|3|0", "cmd_NewItem3"), sa);
+ ok(testItem("1|3|1", "cmd_NewItem4"), sa);
+ ok(testItem("1|3|2", "cmd_NewItem5"), sa);
+ // return state to original diagramed state
+ menubarNode.removeChild(tmpMenu0);
+
+ // This test is basically a crash test for bug 433858.
+ newMenuItem1.setAttribute("hidden", "true");
+ newMenuItem2.setAttribute("hidden", "true");
+ newMenu1.setAttribute("hidden", "true");
+ forceUpdateNativeMenuAt("1");
+ newMenuItem1.setAttribute("hidden", "false");
+ newMenuItem2.setAttribute("hidden", "false");
+ newMenu1.setAttribute("hidden", "false");
+ forceUpdateNativeMenuAt("1");
+
+ onTestsFinished();
+ }
+
+ setTimeout(_delayedOnLoad, 1000);
+ }
+
+ ]]></script>
+</window>
diff --git a/widget/tests/standalone_native_menu_window.xhtml b/widget/tests/standalone_native_menu_window.xhtml
new file mode 100644
index 0000000000..4155126ad5
--- /dev/null
+++ b/widget/tests/standalone_native_menu_window.xhtml
@@ -0,0 +1,374 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="StandaloneNativeMenuWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="300"
+ height="300"
+ onload="onLoad();"
+ title="nsIStandaloneNativeMenu Test">
+
+ <command id="cmd_FooItem0" oncommand="gExecutedCommandID = 'cmd_FooItem0';"/>
+ <command id="cmd_FooItem1" oncommand="gExecutedCommandID = 'cmd_FooItem1';"/>
+ <command id="cmd_BarItem0" oncommand="gExecutedCommandID = 'cmd_BarItem0';"/>
+ <command id="cmd_BarItem1" oncommand="gExecutedCommandID = 'cmd_BarItem1';"/>
+ <command id="cmd_NewItem0" oncommand="gExecutedCommandID = 'cmd_NewItem0';"/>
+ <command id="cmd_NewItem1" oncommand="gExecutedCommandID = 'cmd_NewItem1';"/>
+ <command id="cmd_NewItem2" oncommand="gExecutedCommandID = 'cmd_NewItem2';"/>
+ <command id="cmd_NewItem3" oncommand="gExecutedCommandID = 'cmd_NewItem3';"/>
+ <command id="cmd_NewItem4" oncommand="gExecutedCommandID = 'cmd_NewItem4';"/>
+ <command id="cmd_NewItem5" oncommand="gExecutedCommandID = 'cmd_NewItem5';"/>
+
+ <!-- We do not modify any menus or menu items defined here in testing. These
+ serve as a baseline structure for us to test after other modifications.
+ We add children to the menubar defined here and test by modifying those
+ children. -->
+ <popupset>
+ <menupopup id="standalonenativemenu">
+ <menu id="foo" label="Foo">
+ <menupopup>
+ <menuitem label="FooItem0" command="cmd_FooItem0"/>
+ <menuitem label="FooItem1" command="cmd_FooItem1"/>
+ <menuseparator/>
+ <menu label="Bar">
+ <menupopup>
+ <menuitem label="BarItem0" command="cmd_BarItem0"/>
+ <menuitem label="BarItem1" command="cmd_BarItem1"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </popupset>
+
+ <script type="application/javascript"><![CDATA[
+
+ function ok(condition, message) {
+ window.arguments[0].SimpleTest.ok(condition, message);
+ }
+
+ function is(a, b, message) {
+ window.arguments[0].SimpleTest.is(a, b, message);
+ }
+
+ function isnot(a, b, message) {
+ window.arguments[0].SimpleTest.isnot(a, b, message);
+ }
+
+ function todo(condition, message) {
+ window.arguments[0].SimpleTest.todo(condition, message);
+ }
+
+ function todo_is(a, b, message) {
+ window.arguments[0].SimpleTest.todo_is(a, b, message);
+ }
+
+ function todo_isnot(a, b, message) {
+ window.arguments[0].SimpleTest.todo_isnot(a, b, message);
+ }
+
+ function onTestsFinished() {
+ window.close();
+ window.arguments[0].SimpleTest.finish();
+ }
+
+ var gExecutedCommandID;
+
+ // returns the executed command ID, or the empty string if nothing was executed
+ function activateItem(menu, location) {
+ try {
+ gExecutedCommandID = "";
+ menu.menuWillOpen();
+ menu.activateNativeMenuItemAt(location);
+ return gExecutedCommandID;
+ }
+ catch (e) {
+ // activateNativeMenuItemAt throws an exception if the item was not found
+ dump(e + "\n");
+ return "";
+ }
+ }
+
+ function testItem(menu, location, targetID) {
+ var activatedCommandID = activateItem(menu, location);
+ return activatedCommandID != "" && activatedCommandID == targetID;
+ }
+
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ function createXULMenuPopup() {
+ return document.createElementNS(XUL_NS, "menupopup");
+ }
+
+ function createXULMenu(aLabel) {
+ var item = document.createElementNS(XUL_NS, "menu");
+ item.setAttribute("label", aLabel);
+ return item;
+ }
+
+ function createXULMenuItem(aLabel, aCommandId) {
+ var item = document.createElementNS(XUL_NS, "menuitem");
+ item.setAttribute("label", aLabel);
+ item.setAttribute("command", aCommandId);
+ return item;
+ }
+
+ function runBaseMenuTests(menu) {
+ menu.forceUpdateNativeMenuAt("0|3");
+ return testItem(menu, "0|0", "cmd_FooItem0") &&
+ testItem(menu, "0|1", "cmd_FooItem1") &&
+ testItem(menu, "0|3|0", "cmd_BarItem0") &&
+ testItem(menu, "0|3|1", "cmd_BarItem1");
+ }
+
+ function createStandaloneNativeMenu(menuNode) {
+ try {
+ let menu = Cc["@mozilla.org/widget/standalonenativemenu;1"].createInstance(Ci.nsIStandaloneNativeMenu);
+ menu.init(menuNode);
+ return menu;
+ } catch (e) {
+ ok(false, "Failed creating nsIStandaloneNativeMenu instance");
+ throw e;
+ }
+ }
+
+ function runDetachedMenuTests(addMenupopupBeforeCreatingSNM) {
+ let menu = createXULMenu("Detached menu");
+ menu.setAttribute("image", 'data:image/svg+xml,<svg%20xmlns="http://www.w3.org/2000/svg"%20width="32"%20height="32"><circle%20cx="16"%20cy="16"%20r="16"/></svg>');
+ let menupopup = createXULMenuPopup();
+
+ let popupShowingFired = false;
+ let itemActivated = false;
+
+ menupopup.addEventListener("popupshowing", function (e) {
+ popupShowingFired = true;
+
+ let menuitem = document.createElementNS(XUL_NS, "menuitem");
+ menuitem.setAttribute("label", "detached menu item");
+ /* eslint-disable-next-line no-shadow */
+ menuitem.addEventListener("command", function (e) {
+ itemActivated = true;
+ })
+ menupopup.appendChild(menuitem);
+ })
+
+ // It shouldn't make a difference whether the menupopup is added to the
+ // menu element before or after we create the nsIStandaloneNativeMenu
+ // instance with it. We test both orders by calling this function twice
+ // with different values for addMenupopupBeforeCreatingSNM.
+
+ var menuSNM = null; // the nsIStandaloneNativeMenu object for "menu"
+ if (addMenupopupBeforeCreatingSNM) {
+ menu.appendChild(menupopup);
+ menuSNM = createStandaloneNativeMenu(menu);
+ } else {
+ menuSNM = createStandaloneNativeMenu(menu);
+ menu.appendChild(menupopup);
+ }
+
+ try {
+ ok(!popupShowingFired, "popupshowing shouldn't have fired before our call to menuWillOpen()");
+ menuSNM.menuWillOpen();
+ ok(popupShowingFired, "calling menuWillOpen() should have notified our popupshowing listener");
+
+ ok(!itemActivated, "our dynamically-added menuitem shouldn't have been activated yet");
+ menuSNM.activateNativeMenuItemAt("0");
+ ok(itemActivated, "the new menu item should have been activated now");
+ } catch (ex) {
+ ok(false, "dynamic menu test failed: " + ex);
+ }
+ }
+
+ function onLoad() {
+ var _delayedOnLoad = function() {
+ try {
+
+ var menuNode = document.getElementById("standalonenativemenu");
+ var menu = createStandaloneNativeMenu(menuNode);
+
+ // First let's run the base menu tests.
+ ok(runBaseMenuTests(menu), "base tests #1");
+
+ // Set up some nodes that we'll use.
+ var newMenu0 = createXULMenu("NewMenu0");
+ var newMenu1 = createXULMenu("NewMenu1");
+ var newMenuPopup0 = createXULMenuPopup();
+ var newMenuPopup1 = createXULMenuPopup();
+ var newMenuItem0 = createXULMenuItem("NewMenuItem0", "cmd_NewItem0");
+ var newMenuItem1 = createXULMenuItem("NewMenuItem1", "cmd_NewItem1");
+ var newMenuItem2 = createXULMenuItem("NewMenuItem2", "cmd_NewItem2");
+ var newMenuItem3 = createXULMenuItem("NewMenuItem3", "cmd_NewItem3");
+ var newMenuItem4 = createXULMenuItem("NewMenuItem4", "cmd_NewItem4");
+ var newMenuItem5 = createXULMenuItem("NewMenuItem5", "cmd_NewItem5");
+
+ // Create another submenu with hierarchy via DOM manipulation.
+ // ******************
+ // * Foo * NewMenu0 * <- Menu bar
+ // ******************
+ // ****************
+ // * NewMenuItem0 * <- NewMenu0 submenu
+ // ****************
+ // * NewMenuItem1 *
+ // ****************
+ // * NewMenuItem2 *
+ // *******************************
+ // * NewMenu1 > * NewMenuItem3 * <- NewMenu1 submenu
+ // *******************************
+ // * NewMenuItem4 *
+ // ****************
+ // * NewMenuItem5 *
+ // ****************
+ newMenu0.appendChild(newMenuPopup0);
+ newMenuPopup0.appendChild(newMenuItem0);
+ newMenuPopup0.appendChild(newMenuItem1);
+ newMenuPopup0.appendChild(newMenuItem2);
+ newMenuPopup0.appendChild(newMenu1);
+ newMenu1.appendChild(newMenuPopup1);
+ newMenuPopup1.appendChild(newMenuItem3);
+ newMenuPopup1.appendChild(newMenuItem4);
+ newMenuPopup1.appendChild(newMenuItem5);
+ //XXX - we have to append the menu to the top-level of the menu bar
+ // only after constructing it. If we append before construction, it is
+ // invalid because it has no children and we don't validate it if we add
+ // children later.
+ menuNode.appendChild(newMenu0);
+ menu.forceUpdateNativeMenuAt("1|3");
+ // Run basic tests again.
+ ok(runBaseMenuTests(menu), "base tests #2");
+
+ // Error strings.
+ var sa = "Command handler(s) should have activated";
+ var sna = "Command handler(s) should not have activated";
+
+ // Test middle items.
+ is(activateItem(menu, "1|1"), "cmd_NewItem1", "#1:" + sa);
+ is(activateItem(menu, "1|3|1"), "cmd_NewItem4", "#2:" + sa);
+
+ // Hide newMenu0.
+ newMenu0.setAttribute("hidden", "true");
+ ok(runBaseMenuTests(menu), "base tests #3: " + sa); // the base menu should still be unhidden
+ is(activateItem(menu, "1|0"), "", "#3:" + sna);
+ is(activateItem(menu, "1|1"), "", "#4:" + sna);
+ is(activateItem(menu, "1|2"), "", "#5:" + sna);
+ is(activateItem(menu, "1|3|0"), "", "#6:" + sna);
+ is(activateItem(menu, "1|3|1"), "", "#7:" + sna);
+ is(activateItem(menu, "1|3|2"), "", "#8:" + sna);
+
+ // Show newMenu0.
+ newMenu0.setAttribute("hidden", "false");
+ menu.forceUpdateNativeMenuAt("1|3");
+ ok(runBaseMenuTests(menu), "base tests #4:" + sa);
+ is(activateItem(menu, "1|0"), "cmd_NewItem0", "#9:" + sa);
+ is(activateItem(menu, "1|1"), "cmd_NewItem1", "#10:" + sa);
+ is(activateItem(menu, "1|2"), "cmd_NewItem2", "#11:" + sa);
+ is(activateItem(menu, "1|3|0"), "cmd_NewItem3", "#12:" + sa);
+ is(activateItem(menu, "1|3|1"), "cmd_NewItem4", "#13:" + sa);
+ is(activateItem(menu, "1|3|2"), "cmd_NewItem5", "#14:" + sa);
+
+ // Hide items.
+ newMenuItem1.setAttribute("hidden", "true");
+ newMenuItem4.setAttribute("hidden", "true");
+ menu.forceUpdateNativeMenuAt("1|2");
+ ok(runBaseMenuTests(menu), "base tests #5:" + sa);
+ is(activateItem(menu, "1|0"), "cmd_NewItem0", "#15:" + sa);
+ is(activateItem(menu, "1|1"), "cmd_NewItem2", "#16:" + sa);
+ is(activateItem(menu, "1|2"), "", "#17:" + sna);
+ is(activateItem(menu, "1|2|0"), "cmd_NewItem3", "#18:" + sa);
+ is(activateItem(menu, "1|2|1"), "cmd_NewItem5", "#19:" + sa);
+ is(activateItem(menu, "1|2|2"), "", "#20:" + sna);
+
+ // Show items.
+ newMenuItem1.setAttribute("hidden", "false");
+ newMenuItem4.setAttribute("hidden", "false");
+ //forceUpdateNativeMenuAt("1|3");
+ ok(runBaseMenuTests(menu), "base tests #6:" + sa);
+ is(activateItem(menu, "1|0"), "cmd_NewItem0", "#21:" + sa);
+ is(activateItem(menu, "1|1"), "cmd_NewItem1", "#22:" + sa);
+ is(activateItem(menu, "1|2"), "cmd_NewItem2", "#23:" + sa);
+ is(activateItem(menu, "1|3|0"), "cmd_NewItem3", "#24:" + sa);
+ is(activateItem(menu, "1|3|1"), "cmd_NewItem4", "#25:" + sa);
+ is(activateItem(menu, "1|3|2"), "cmd_NewItem5", "#26:" + sa);
+
+ // At this point in the tests the state of the menus has been returned
+ // to the originally diagramed state.
+
+ // Remove menu.
+ menuNode.removeChild(newMenu0);
+ ok(runBaseMenuTests(menu), "base tests #7:" + sa);
+ is(activateItem(menu, "1|0"), "", "#27:" + sna);
+ is(activateItem(menu, "1|1"), "", "#28:" + sna);
+ is(activateItem(menu, "1|2"), "", "#29:" + sna);
+ is(activateItem(menu, "1|3|0"), "", "#30:" + sna);
+ is(activateItem(menu, "1|3|1"), "", "#31:" + sna);
+ is(activateItem(menu, "1|3|2"), "", "#32:" + sna);
+ // return state to original diagramed state
+ menuNode.appendChild(newMenu0);
+
+ // The following is based on a similar test bug 447042 from the native
+ // menu bar test: Make sure that adding a menu node with no children
+ // to the menu bar and then adding another menu node with children works.
+ // In the menubar, root menus with no children are skipped - they're not
+ // visible in the menubar.
+ // Regular menus currently treat submenus without children differently:
+ // submenus without children *are* visible.
+ // We may want to change this in the future.
+ // After the mutation below we have the following root menu content:
+ // - [0] Foo (with submenu)
+ // - [1] tmpMenu0 (with empty submenu)
+ // - [2] NewMenu0 (with submenu)
+ // Since the empty tmpMenu0 item is not skipped, NewMenu0 has index 2,
+ // so we use "2|..." below, rather than the "1|..." that's used in the
+ // menubar test.
+ var tmpMenu0 = createXULMenu("tmpMenu0");
+ menuNode.removeChild(newMenu0);
+ menuNode.appendChild(tmpMenu0);
+ menuNode.appendChild(newMenu0);
+ menu.forceUpdateNativeMenuAt("2|3");
+ ok(runBaseMenuTests(menu), "base tests #8");
+ is(activateItem(menu, "2|0"), "cmd_NewItem0", "#33:" + sa);
+ is(activateItem(menu, "2|1"), "cmd_NewItem1", "#34:" + sa);
+ is(activateItem(menu, "2|2"), "cmd_NewItem2", "#35:" + sa);
+ is(activateItem(menu, "2|3|0"), "cmd_NewItem3", "#36:" + sa);
+ is(activateItem(menu, "2|3|1"), "cmd_NewItem4", "#37:" + sa);
+ is(activateItem(menu, "2|3|2"), "cmd_NewItem5", "#38:" + sa);
+ // return state to original diagramed state
+ menuNode.removeChild(tmpMenu0);
+
+ // This test is basically a crash test for bug 433858.
+ newMenuItem1.setAttribute("hidden", "true");
+ newMenuItem2.setAttribute("hidden", "true");
+ newMenu1.setAttribute("hidden", "true");
+ menu.forceUpdateNativeMenuAt("1");
+ newMenuItem1.setAttribute("hidden", "false");
+ newMenuItem2.setAttribute("hidden", "false");
+ newMenu1.setAttribute("hidden", "false");
+ menu.forceUpdateNativeMenuAt("1");
+
+ // Check that "path components" which are out-of-range are not ignored.
+ // There are only two menu items in the root menu (with index 0 and 1),
+ // so index 2 is out of range.
+ is(activateItem(menu, "2|1|0"), "", "#39:" + sna);
+
+ // Check that hiding and then un-hiding the root menu doesn't result in
+ // a cyclic native menu structure.
+ menuNode.setAttribute("collapsed", "true");
+ menuNode.removeAttribute("collapsed");
+ ok(runBaseMenuTests(menu), "base tests #9");
+
+ // Run tests where the menu nodes are not in the document's node tree.
+ runDetachedMenuTests(false);
+ runDetachedMenuTests(true);
+
+ } catch (e) {
+ ok(false, "Caught an exception: " + e);
+ } finally {
+ onTestsFinished();
+ }
+ }
+
+ setTimeout(_delayedOnLoad, 1000);
+ }
+
+ ]]></script>
+</window>
diff --git a/widget/tests/system_font_changes.xhtml b/widget/tests/system_font_changes.xhtml
new file mode 100644
index 0000000000..1cef650015
--- /dev/null
+++ b/widget/tests/system_font_changes.xhtml
@@ -0,0 +1,63 @@
+<?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="system_font_changes_window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="300"
+ height="300"
+ onload="start();">
+
+<span id="target" style="font:menu">Hello world</span>
+
+<script type="application/javascript"><![CDATA[
+ function is(condition, message) {
+ window.arguments[0].SimpleTest.is(condition, message);
+ }
+ function registerCleanupFunction(func) {
+ window.arguments[0].SimpleTest.registerCleanupFunction(func);
+ }
+
+ async function waitForFrame() {
+ return new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ }
+
+ let windowUtils = window.windowUtils;
+ async function start() {
+ await waitForFrame();
+
+ const originalSystemFont = windowUtils.systemFont;
+ registerCleanupFunction(() => {
+ windowUtils.systemFont = originalSystemFont;
+ });
+
+ windowUtils.systemFont = 'Sans 11';
+ is(windowUtils.systemFont, 'Sans 11');
+
+ // Wait for two frames for the safety since the notification for system
+ // font changes is asynchronously processed.
+ await waitForFrame();
+ await waitForFrame();
+
+ const target = document.getElementById('target');
+ is(getComputedStyle(target).fontFamily, 'Sans');
+
+ windowUtils.systemFont = 'Serif 11';
+ is(windowUtils.systemFont, 'Serif 11');
+
+ await waitForFrame();
+ await waitForFrame();
+
+ is(getComputedStyle(target).fontFamily, 'Serif');
+
+ window.close();
+ window.arguments[0].SimpleTest.finish();
+ }
+]]></script>
+</window>
diff --git a/widget/tests/taskbar_previews.xhtml b/widget/tests/taskbar_previews.xhtml
new file mode 100644
index 0000000000..b220d5662b
--- /dev/null
+++ b/widget/tests/taskbar_previews.xhtml
@@ -0,0 +1,116 @@
+<?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="Taskbar Previews Test"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="loaded();">
+
+ <title>Previews - yeah!</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+ let taskbar = Cc["@mozilla.org/windows-taskbar;1"].getService(Ci.nsIWinTaskbar);
+
+ function IsWin7OrHigher() {
+ try {
+ var ver = parseFloat(Services.sysinfo.getProperty("version"));
+ if (ver >= 6.1)
+ return true;
+ } catch (ex) { }
+ return false;
+ }
+ isnot(taskbar, null, "Taskbar service is defined");
+ is(taskbar.available, IsWin7OrHigher(), "Expected availability of taskbar");
+
+ SimpleTest.waitForExplicitFinish();
+
+ function stdPreviewSuite(p) {
+ p.visible = !p.visible;
+ p.visible = !p.visible;
+ p.visible = true;
+ p.invalidate();
+ p.visible = false;
+ }
+
+ function loaded()
+ {
+ if (!taskbar.available)
+ SimpleTest.finish();
+ let controller = {
+ width: 400,
+ height: 400,
+ thumbnailAspectRatio: 1.0,
+ get wrappedJSObject() { return this; }
+ }
+ // HACK from mconnor:
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let docShell = win.gBrowser.docShell;
+
+ let winPreview = taskbar.getTaskbarWindowPreview(docShell);
+ isnot(winPreview, null, "Window preview is not null");
+ winPreview.controller = controller;
+ let button = winPreview.getButton(0);
+ isnot(button, null, "Could get button at valid index");
+ try {
+ winPreview.getButton(-1);
+ ok(false, "Got button at negative index");
+ } catch (ex) {}
+ try {
+ winPreview.getButton(Ci.nsITaskbarWindowPreview.NUM_TOOLBAR_BUTTONS);
+ ok(false, "Got button at index that is too large");
+ } catch (ex) {}
+ button.image = null;
+ stdPreviewSuite(winPreview);
+ // Let's not perma-hide this window from the taskbar
+ winPreview.visible = true;
+
+ let tabP = taskbar.createTaskbarTabPreview(docShell, controller);
+ isnot(tabP, null, "Tab preview is not null");
+ is(tabP.controller.wrappedJSObject, controller, "Controllers match");
+ is(tabP.icon, null, "Default icon is null (windows default)");
+ tabP.icon = null;
+ tabP.move(null);
+ try {
+ tabP.move(tabP);
+ ok(false, "Moved a preview next to itself!");
+ } catch (ex) {}
+ stdPreviewSuite(tabP);
+
+ let tabP2 = taskbar.createTaskbarTabPreview(docShell, controller);
+ tabP.visible = true;
+ tabP2.visible = true;
+
+ isnot(tabP2, null, "2nd Tab preview is not null");
+ isnot(tabP,tabP2, "Tab previews are different");
+ tabP.active = true;
+ ok(tabP.active && !tabP2.active, "Only one tab is active (part 1)");
+ tabP2.active = true;
+ ok(!tabP.active && tabP2.active, "Only one tab is active (part 2)");
+ tabP.active = true;
+ ok(tabP.active && !tabP2.active, "Only one tab is active (part 3)");
+ tabP.active = false;
+ ok(!tabP.active && !tabP2.active, "Neither tab is active");
+ is(winPreview.active, false, "Window preview is not active");
+ tabP.active = true;
+ winPreview.active = true;
+ ok(winPreview.active && !tabP.active, "Tab preview takes activation from window");
+ tabP.active = true;
+ ok(tabP.active && !winPreview.active, "Tab preview takes activation from window");
+
+ tabP.visible = false;
+ tabP2.visible = 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/widget/tests/test_AltGr_key_events_in_web_content_on_windows.html b/widget/tests/test_AltGr_key_events_in_web_content_on_windows.html
new file mode 100644
index 0000000000..ff89f8fdad
--- /dev/null
+++ b/widget/tests/test_AltGr_key_events_in_web_content_on_windows.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Testing if AltGr keydown and keyup events are fired in web content on Windows</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/NativeKeyCodes.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="display">
+ <input id="input">
+</div>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function checkEvent(aEvent, aExpectedEvents, aDescription) {
+ if (!aExpectedEvents.length) {
+ ok(false, `${aDescription}: no more expected events ` +
+ `(type: ${aEvent.type}, code: ${aEvent.code}, key: ${aEvent.key}, keyCode: ${aEvent.keyCode}`);
+ }
+ let expectedEvent = aExpectedEvents.shift();
+ for (let property in expectedEvent) {
+ is(aEvent[property], expectedEvent[property], `${aDescription}: ${property}`);
+ }
+}
+
+async function runAltGrKeyTest() {
+ return new Promise(resolve => {
+ let target = document.getElementById("input");
+ target.focus();
+
+ let events = [
+ { type: "keydown", code: "ControlLeft", key: "Control", keyCode: KeyboardEvent.DOM_VK_CONTROL },
+ { type: "keydown", code: "AltRight", key: "AltGraph", keyCode: KeyboardEvent.DOM_VK_ALT },
+ { type: "keyup", code: "ControlLeft", key: "Control", keyCode: KeyboardEvent.DOM_VK_CONTROL },
+ { type: "keyup", code: "AltRight", key: "AltGraph", keyCode: KeyboardEvent.DOM_VK_ALT },
+ ];
+ function handleEvent(aEvent) {
+ checkEvent(aEvent, events, "runAltGrKeyTest");
+ if (aEvent.type === "keyup" && aEvent.code === "AltRight") {
+ is(events.length, 0, "runAltGrKeyTest: all expected events are fired");
+ SimpleTest.executeSoon(() => {
+ target.removeEventListener("keydown", handleEvent);
+ target.removeEventListener("keypress", handleEvent);
+ target.removeEventListener("keyup", handleEvent);
+ resolve();
+ });
+ }
+ }
+ target.addEventListener("keydown", handleEvent);
+ target.addEventListener("keypress", handleEvent);
+ target.addEventListener("keyup", handleEvent);
+
+ synthesizeNativeKey(KEYBOARD_LAYOUT_SPANISH, WIN_VK_RMENU, {},
+ "", "");
+ });
+}
+
+async function runEmulatingAltGrKeyTest() {
+ return new Promise(resolve => {
+ let target = document.getElementById("input");
+ target.focus();
+
+ let events = [
+ { type: "keydown", code: "ControlLeft", key: "Control", keyCode: KeyboardEvent.DOM_VK_CONTROL },
+ { type: "keydown", code: "AltLeft", key: "Alt", keyCode: KeyboardEvent.DOM_VK_ALT },
+ { type: "keyup", code: "AltLeft", key: "Alt", keyCode: KeyboardEvent.DOM_VK_ALT },
+ { type: "keyup", code: "ControlLeft", key: "Control", keyCode: KeyboardEvent.DOM_VK_CONTROL },
+ ];
+ function handleEvent(aEvent) {
+ checkEvent(aEvent, events, "runEmulatingAltGrKeyTest");
+ if (aEvent.type === "keyup" && aEvent.code === "ControlLeft") {
+ is(events.length, 0, "runAltGrKeyTest: all expected events are fired");
+ SimpleTest.executeSoon(() => {
+ target.removeEventListener("keydown", handleEvent);
+ target.removeEventListener("keypress", handleEvent);
+ target.removeEventListener("keyup", handleEvent);
+ resolve();
+ });
+ }
+ }
+ target.addEventListener("keydown", handleEvent);
+ target.addEventListener("keypress", handleEvent);
+ target.addEventListener("keyup", handleEvent);
+
+ synthesizeNativeKey(KEYBOARD_LAYOUT_SPANISH, WIN_VK_LMENU, { ctrlKey: true },
+ "", "");
+ });
+}
+
+async function runTests() {
+ await runAltGrKeyTest();
+ await runEmulatingAltGrKeyTest();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/widget/tests/test_actionhint.html b/widget/tests/test_actionhint.html
new file mode 100644
index 0000000000..6ab2cf40de
--- /dev/null
+++ b/widget/tests/test_actionhint.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Tests for action hint that is used by software keyboard</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/SpecialPowers.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<div>
+<form><input type="text" id="a1"><input type="text" id="a2"><input type="submit"></form>
+<form><input type="search" id="b1"><input type="submit"></form>
+<form><input type="text" id="c1"></form>
+<form><input type="text" id="d1"><textarea></textarea><input type="submit"></form>
+<form><input type="text" id="e1"><input type="number"><input type="submit"></form>
+<form><input type="text" id="f1"><input type="date"><input type="submit"></form>
+<form><input type="text" id="g1"><input type="radio"><input type="submit"></form>
+<form><input type="text" id="h1"><input type="text" readonly><input type="submit"></form>
+<form><input type="text" id="i1"><input type="text" disabled><input type="submit"></form>
+<input type="text" id="j1"><input type="text"><input type="button">
+<form><input type="text" id="k1"><a href="#foo">foo</a><input type="text"><input type="submit"></form>
+<form>
+ <input id="l1" enterkeyhint="enter">
+ <input id="l2" enterkeyhint="DONE">
+ <input id="l3" enterkeyhint="go">
+ <input id="l4" enterkeyhint="Next">
+ <input id="l5" enterkeyhint="Previous">
+ <input id="l6" enterkeyhint="search">
+ <textarea id="l7" enterkeyhint="send"></textarea>
+ <input id="l8" type="number" enterkeyhint="previous">
+ <input id="l9" type="date" enterkeyhint="done">
+ <input id="l10" type="time" enterkeyhint="done">
+ <input id="l11" enterkeyhint="NONE">
+</form>
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+add_task(async function setup() {
+ await new Promise(r => SimpleTest.waitForFocus(r));
+});
+
+add_task(async function basic() {
+ const tests = [
+ { id: "a1", hint: "maybenext", desc: "next element is type=text" },
+ { id: "a2", hint: "go", desc: "next element is type=submit" },
+ { id: "b1", hint: "search", desc: "current is type=search" },
+ { id: "c1", hint: "go", desc: "only this element" },
+ { id: "d1", hint: "maybenext", desc: "next element is textarea" },
+ { id: "e1", hint: "maybenext", desc: "next element is type=number" },
+ { id: "h1", hint: "go", desc: "next element is readonly" },
+ // XXX Feel free to change this result if you get some bugs reports
+ { id: "i1", hint: "go", desc: "next element is disabled" },
+ { id: "j1", hint: "", desc: "no form element" },
+ { id: "l1", hint: "enter", desc: "enterkeyhint=enter" },
+ { id: "l2", hint: "done", desc: "enterkeyhint=DONE" },
+ { id: "l3", hint: "go", desc: "enterkeyhint=go" },
+ { id: "l4", hint: "next", desc: "enterkeyhint=Next" },
+ { id: "l5", hint: "previous", desc: "enterkeyhint=Previous" },
+ { id: "l6", hint: "search", desc: "enterkeyhint=search" },
+ { id: "l7", hint: "send", desc: "enterkeyhint=send" },
+ { id: "l8", hint: "previous", desc: "type=number enterkeyhint=previous" },
+ // type=date is readonly content
+ { id: "l9", hint: "", desc: "type=date enterkeyhint=done" },
+ // type=time is readonly content
+ { id: "l10", hint: "", desc: "type=time enterkeyhint=done" },
+ // Since enterkeyhint is invalid, we infer action hint. So feel free to change this.
+ { id: "l11", hint: "go", desc: "enterkeyhint is invalid" },
+ ];
+
+ const todo_tests = [
+ { id: "f1", hint: "maybenext", desc: "next element is type=date" },
+ { id: "k1", hint: "", desc: "next is anchor link" },
+ ];
+
+
+ for (let test of tests) {
+ document.getElementById(test.id).focus();
+ is(SpecialPowers.DOMWindowUtils.focusedActionHint, test.hint, test.desc);
+ }
+
+ for (let test of todo_tests) {
+ document.getElementById(test.id).focus();
+ todo_is(SpecialPowers.DOMWindowUtils.focusedActionHint, test.hint, test.desc);
+ }
+});
+
+add_task(async function dynamicChange() {
+ let element = document.getElementById("l1");
+ element.focus();
+ is(SpecialPowers.DOMWindowUtils.focusedActionHint, "enter",
+ "Initial enterKeyHint");
+
+ element.setAttribute("enterkeyhint", "next");
+ is(SpecialPowers.DOMWindowUtils.focusedActionHint, "next",
+ "enterKeyHint in InputContext has to sync with enterkeyhint attribute");
+
+ element.enterKeyHint = "search";
+ is(SpecialPowers.DOMWindowUtils.focusedActionHint, "search",
+ "enterKeyHint in InputContext has to sync with enterKeyHint setter");
+
+ document.getElementById("l2").setAttribute("enterkeyhint", "send");
+ is(SpecialPowers.DOMWindowUtils.focusedActionHint, "search",
+ "enterKeyHint in InputContext keeps focused enterKeyHint value");
+
+ // Storing the original value may be safer.
+ element.enterkeyhint = "enter";
+ document.getElementById("l2").enterKeyHint = "done";
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/widget/tests/test_alwaysontop_focus.xhtml b/widget/tests/test_alwaysontop_focus.xhtml
new file mode 100644
index 0000000000..b9cc3ee33c
--- /dev/null
+++ b/widget/tests/test_alwaysontop_focus.xhtml
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta charset="utf-8" />
+ <title>Test that alwaysontop windows do not pull focus when opened.</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script><![CDATA[
+ add_task(async function testAlwaysOnTop() {
+ let topWin = window.docShell.rootTreeItem.domWindow;
+ await SimpleTest.promiseFocus(topWin);
+ is(Services.focus.activeWindow, topWin, "Top level window is focused");
+
+ let newWin = Services.ww.openWindow(
+ null,
+ "about:blank",
+ null,
+ "chrome,alwaysontop,width=300,height=300",
+ null
+ );
+ await new Promise(resolve => {
+ newWin.addEventListener("load", resolve, { once: true });
+ });
+
+ // Wait one tick of the event loop to give the window a chance to focus.
+ await new Promise(resolve => { SimpleTest.executeSoon(resolve); });
+
+ is(Services.focus.activeWindow, topWin, "Top level window is still focused");
+ newWin.close();
+ });
+ ]]></script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/widget/tests/test_assign_event_data.html b/widget/tests/test_assign_event_data.html
new file mode 100644
index 0000000000..1da9bb535f
--- /dev/null
+++ b/widget/tests/test_assign_event_data.html
@@ -0,0 +1,708 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Testing ns*Event::Assign*EventData()</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/NativeKeyCodes.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style>
+ #a {
+ background-color: transparent;
+ transition: background-color 0.1s linear;
+ }
+ #a:focus {
+ background-color: red;
+ }
+ .slidin {
+ border: green 1px solid;
+ width: 10px;
+ height: 10px;
+ animation-name: slidein;
+ animation-duration: 1s;
+ }
+ @keyframes slidein {
+ from {
+ margin-left: 100%;
+ }
+ to {
+ margin-left: 0;
+ }
+ }
+ #pointer-target {
+ border: 1px dashed red;
+ background: yellow;
+ margin: 0px 10px;
+ padding: 0px 10px;
+ }
+ #scrollable-div {
+ background: green;
+ overflow: auto;
+ width: 30px;
+ height: 30px;
+ }
+ #scrolled-div {
+ background: magenta;
+ width: 10px;
+ height: 10px;
+ }
+ #form {
+ background: silver;
+ padding: 0px 10px;
+ }
+ #animated-div {
+ background: cyan;
+ padding: 0px 10px;
+ }
+ </style>
+</head>
+<body>
+<div id="display">
+ <input id="input-text">
+ <button id="button">button</button>
+ <a id="a" href="about:blank">hyper link</a>
+ <span id="pointer-target">span</span>
+ <div id="scrollable-div"><div id="scrolled-div"></div></div>
+ <form id="form">form</form>
+ <div id="animated-div">&nbsp;</div>
+</div>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.expectAssertions(0, 34);
+
+const kIsMac = (navigator.platform.indexOf("Mac") == 0);
+const kIsWin = (navigator.platform.indexOf("Win") == 0);
+
+var gDescription = "";
+var gEvent = null;
+var gCopiedEvent = [];
+var gCallback = null;
+var gCallPreventDefault = false;
+
+function onEvent(aEvent) {
+ if (gCallPreventDefault) {
+ aEvent.preventDefault();
+ }
+ ok(gEvent === null, gDescription + `: We should receive only one event to check per test: already got: ${gEvent ? gEvent.type : "null"}, got: ${aEvent.type}`);
+ gEvent = aEvent;
+ for (var attr in aEvent) {
+ if (!attr.match(/^[A-Z0-9_]+$/) && // ignore const attributes
+ attr != "multipleActionsPrevented" && // multipleActionsPrevented isn't defined in any DOM event specs.
+ typeof(aEvent[attr]) != "function") {
+ gCopiedEvent.push({ name: attr, value: aEvent[attr]});
+ }
+ }
+ setTimeout(gCallback, 0);
+}
+
+function observeKeyUpOnContent(aKeyCode, aCallback) {
+ document.addEventListener("keyup", function keyUp(ev) {
+ if (ev.keyCode == aKeyCode) {
+ document.removeEventListener("keyup", keyUp);
+ SimpleTest.executeSoon(aCallback);
+ }
+ });
+}
+
+const kTests = [
+ { description: "InternalScrollPortEvent (overflow, vertical)",
+ targetID: "scrollable-div", eventType: "overflow",
+ dispatchEvent() {
+ document.getElementById("scrolled-div").style.height = "500px";
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalScrollPortEvent (overflow, horizontal)",
+ targetID: "scrollable-div", eventType: "overflow",
+ dispatchEvent() {
+ document.getElementById("scrolled-div").style.width = "500px";
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalScrollAreaEvent (MozScrolledAreaChanged, spreading)",
+ target() { return document; }, eventType: "MozScrolledAreaChanged",
+ dispatchEvent() {
+ document.getElementById("scrollable-div").style.width = "50000px";
+ document.getElementById("scrollable-div").style.height = "50000px";
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalScrollAreaEvent (MozScrolledAreaChanged, shrinking)",
+ target() { return document; }, eventType: "MozScrolledAreaChanged",
+ dispatchEvent() {
+ document.getElementById("scrollable-div").style.width = "30px";
+ document.getElementById("scrollable-div").style.height = "30px";
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetKeyboardEvent (keydown of 'a' key without modifiers)",
+ targetID: "input-text", eventType: "keydown",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_A : MAC_VK_ANSI_A,
+ {}, "a", "a");
+ observeKeyUpOnContent(KeyboardEvent.DOM_VK_A, runNextTest);
+ return true;
+ },
+ canRun() {
+ return (kIsMac || kIsWin);
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetKeyboardEvent (keyup of 'a' key without modifiers)",
+ targetID: "input-text", eventType: "keydown",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_A : MAC_VK_ANSI_A,
+ {}, "a", "a");
+ observeKeyUpOnContent(KeyboardEvent.DOM_VK_A, runNextTest);
+ return true;
+ },
+ canRun() {
+ return (kIsMac || kIsWin);
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetKeyboardEvent (keypress of 'b' key with Shift)",
+ targetID: "input-text", eventType: "keypress",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_B : MAC_VK_ANSI_B,
+ { shiftKey: true }, "B", "B");
+
+ // On Windows, synthesizeNativeKey will also fire keyup for shiftKey.
+ // We have to wait for it to prevent the key event break the next test case.
+ let waitKeyCode = _EU_isWin(window) ? KeyboardEvent.DOM_VK_SHIFT :
+ KeyboardEvent.DOM_VK_B;
+ observeKeyUpOnContent(waitKeyCode, runNextTest);
+ return true;
+ },
+ canRun() {
+ return (kIsMac || kIsWin);
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetKeyboardEvent (keyup during composition)",
+ targetID: "input-text", eventType: "keyup",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeCompositionChange({ "composition":
+ { "string": "\u306D",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ ],
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ synthesizeComposition({ type: "compositioncommitasis", key: {} });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetKeyboardEvent (keydown during composition)",
+ targetID: "input-text", eventType: "keydown",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeCompositionChange({ "composition":
+ { "string": "\u306D",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ ],
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": {},
+ });
+ synthesizeComposition({ type: "compositioncommitasis",
+ key: { key: "KEY_Enter" } });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetMouseEvent (mousedown of left button without modifier)",
+ targetID: "button", eventType: "mousedown",
+ dispatchEvent() {
+ synthesizeMouseAtCenter(document.getElementById(this.targetID),
+ { button: 0 });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetMouseEvent (click of middle button with Shift)",
+ targetID: "button", eventType: "auxclick",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeMouseAtCenter(document.getElementById(this.targetID),
+ { button: 1, shiftKey: true, pressure: 0.5 });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetMouseEvent (mouseup of right button with Alt)",
+ targetID: "button", eventType: "mouseup",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeMouseAtCenter(document.getElementById(this.targetID),
+ { button: 2, altKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetDragEvent",
+ targetID: "input-text", eventType: "dragstart",
+ dispatchEvent() {
+
+ },
+ canRun() {
+ todo(false, "WidgetDragEvent isn't tested");
+ return false;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetCompositionEvent (compositionupdate)",
+ targetID: "input-text", eventType: "compositionupdate",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeComposition({ type: "compositioncommit", data: "\u30E9\u30FC\u30E1\u30F3", key: { key: "KEY_Enter" } });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "InternalEditorInputEvent (input at key input)",
+ targetID: "input-text", eventType: "input",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_B : MAC_VK_ANSI_B,
+ { shiftKey: true }, "B", "B");
+ observeKeyUpOnContent(KeyboardEvent.DOM_VK_B, runNextTest);
+ return true;
+ },
+ canRun() {
+ return (kIsMac || kIsWin);
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalEditorInputEvent (input at composing)",
+ targetID: "input-text", eventType: "input",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeCompositionChange({ "composition":
+ { "string": "\u30E9\u30FC\u30E1\u30F3",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ ],
+ },
+ "caret": { "start": 4, "length": 0 },
+ "key": { key: "y" },
+ });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "InternalEditorInputEvent (input at committing)",
+ targetID: "input-text", eventType: "input",
+ dispatchEvent() {
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetMouseScrollEvent (DOMMouseScroll, vertical)",
+ targetID: "input-text", eventType: "DOMMouseScroll",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 3, 4,
+ { deltaY: 30, lineOrPageDeltaY: 2 });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetMouseScrollEvent (DOMMouseScroll, horizontal)",
+ targetID: "input-text", eventType: "DOMMouseScroll",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 4, 5,
+ { deltaX: 30, lineOrPageDeltaX: 2, shiftKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetMouseScrollEvent (MozMousePixelScroll, vertical)",
+ targetID: "input-text", eventType: "MozMousePixelScroll",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 3, 4,
+ { deltaY: 20, lineOrPageDeltaY: 1, altKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetMouseScrollEvent (MozMousePixelScroll, horizontal)",
+ targetID: "input-text", eventType: "MozMousePixelScroll",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 4, 5,
+ { deltaX: 20, lineOrPageDeltaX: 1, ctrlKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetWheelEvent (wheel, vertical)",
+ targetID: "input-text", eventType: "wheel",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 3, 4,
+ { deltaY: 20, lineOrPageDeltaY: 1, altKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetWheelEvent (wheel, horizontal)",
+ targetID: "input-text", eventType: "wheel",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 4, 5,
+ { deltaX: 20, lineOrPageDeltaX: 1, ctrlKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetWheelEvent (wheel, both)",
+ targetID: "input-text", eventType: "wheel",
+ dispatchEvent() {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 4, 5,
+ { deltaX: 20, deltaY: 10,
+ lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetTouchEvent (touchstart)",
+ target() { return document; }, eventType: "touchstart",
+ dispatchEvent() {
+ synthesizeTouchAtPoint(1, 2, { id: 10, rx: 4, ry: 3, angle: 0, force: 1, shiftKey: true});
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetTouchEvent (touchend)",
+ target() { return document; }, eventType: "touchend",
+ dispatchEvent() {
+ synthesizeTouchAtPoint(4, 6, { id: 5, rx: 1, ry: 2, angle: 0.5, force: 0.8, ctrlKey: true});
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "InternalFormEvent (reset)",
+ targetID: "form", eventType: "reset",
+ dispatchEvent() {
+ document.getElementById("form").reset();
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetCommandEvent",
+ targetID: "input-text", eventType: "",
+ dispatchEvent() {
+
+ },
+ canRun() {
+ todo(false, "WidgetCommandEvent isn't tested");
+ return false;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalClipboardEvent (copy)",
+ targetID: "input-text", eventType: "copy",
+ dispatchEvent() {
+ document.getElementById("input-text").value = "go to clipboard!";
+ document.getElementById("input-text").focus();
+ document.getElementById("input-text").select();
+ synthesizeKey("c", { accelKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "InternalUIEvent (DOMActivate)",
+ targetID: "button", eventType: "DOMActivate",
+ dispatchEvent() {
+ synthesizeMouseAtCenter(document.getElementById(this.targetID),
+ { button: 0, shiftKey: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalFocusEvent (focus)",
+ targetID: "input-text", eventType: "focus",
+ dispatchEvent() {
+ document.getElementById(this.targetID).focus();
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalFocusEvent (blur)",
+ targetID: "input-text", eventType: "blur",
+ dispatchEvent() {
+ document.getElementById(this.targetID).blur();
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetSimpleGestureEvent",
+ targetID: "", eventType: "",
+ dispatchEvent() {
+
+ },
+ canRun() {
+ // Simple gesture event may be handled before it comes content.
+ // So, we cannot test it in this test.
+ todo(false, "WidgetSimpleGestureEvent isn't tested");
+ return false;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalTransitionEvent (transitionend)",
+ targetID: "a", eventType: "transitionend",
+ dispatchEvent() {
+ document.getElementById(this.targetID).focus();
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalAnimationEvent (animationend)",
+ targetID: "animated-div", eventType: "animationend",
+ dispatchEvent() {
+ document.getElementById(this.targetID).className = "slidin";
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalMutationEvent (DOMAttrModified)",
+ targetID: "animated-div", eventType: "DOMAttrModified",
+ dispatchEvent() {
+ document.getElementById(this.targetID).setAttribute("x-data", "foo");
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalMutationEvent (DOMNodeInserted)",
+ targetID: "animated-div", eventType: "DOMNodeInserted",
+ dispatchEvent() {
+ var div = document.createElement("div");
+ div.id = "inserted-div";
+ document.getElementById("animated-div").appendChild(div);
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalMutationEvent (DOMNodeRemoved)",
+ targetID: "animated-div", eventType: "DOMNodeRemoved",
+ dispatchEvent() {
+ document.getElementById("animated-div").removeChild(document.getElementById("inserted-div"));
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "PointerEvent (pointerdown)",
+ targetID: "pointer-target", eventType: "mousedown",
+ dispatchEvent() {
+ var elem = document.getElementById(this.targetID);
+ var rect = elem.getBoundingClientRect();
+ synthesizeMouse(elem, rect.width / 2, rect.height / 2,
+ { type: this.eventType, button: 1, clickCount: 1, inputSource: 2, pressure: 0.25, isPrimary: true });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "PointerEvent (pointerup)",
+ targetID: "pointer-target", eventType: "mouseup",
+ dispatchEvent() {
+ var elem = document.getElementById(this.targetID);
+ var rect = elem.getBoundingClientRect();
+ synthesizeMouse(elem, rect.width / 2, rect.height / 2,
+ { type: this.eventType, button: -1, ctrlKey: true, shiftKey: true, altKey: true, isSynthesized: false });
+ },
+ canRun() {
+ return true;
+ },
+ todoMismatch: [],
+ },
+];
+
+function doTest(aTest) {
+ if (!aTest.canRun()) {
+ SimpleTest.executeSoon(runNextTest);
+ return;
+ }
+ gEvent = null;
+ gCopiedEvent = [];
+ gDescription = aTest.description + " (gCallPreventDefault=" + gCallPreventDefault + ")";
+ var target = aTest.target ? aTest.target() : document.getElementById(aTest.targetID);
+ target.addEventListener(aTest.eventType, onEvent, true);
+ gCallback = function() {
+ target.removeEventListener(aTest.eventType, onEvent, true);
+ ok(gEvent !== null, gDescription + ": failed to get duplicated event");
+ ok(!!gCopiedEvent.length, gDescription + ": count of attribute of the event must be larger than 0");
+ for (var i = 0; i < gCopiedEvent.length; ++i) {
+ var name = gCopiedEvent[i].name;
+ if (name == "rangeOffset") {
+ todo(false, gDescription + ": " + name + " attribute value is never reset (" + gEvent[name] + ")");
+ } else if (name == "eventPhase") {
+ is(gEvent[name], 0, gDescription + ": mismatch with fixed value (" + name + ")");
+ } else if (name == "rangeParent" || name == "currentTarget") {
+ is(gEvent[name], null, gDescription + ": mismatch with fixed value (" + name + ")");
+ } else if (aTest.todoMismatch.includes(name)) {
+ todo_is(gEvent[name], gCopiedEvent[i].value, gDescription + ": mismatch (" + name + ")");
+ } else if (name == "offsetX" || name == "offsetY") {
+ // do nothing; these are defined to return different values during event dispatch
+ // vs not during event dispatch
+ } else {
+ is(gEvent[name], gCopiedEvent[i].value, gDescription + ": mismatch (" + name + ")");
+ }
+ }
+ if (!testWillCallRunNextTest) {
+ runNextTest();
+ }
+ };
+ var testWillCallRunNextTest = aTest.dispatchEvent();
+}
+
+var gIndex = -1;
+function runNextTest() {
+ if (++gIndex == kTests.length) {
+ if (gCallPreventDefault) {
+ finish();
+ return;
+ }
+ // Test with a call of preventDefault() of the events.
+ gCallPreventDefault = true;
+ gIndex = -1;
+ // Restoring the initial state of all elements.
+ document.getElementById("scrollable-div").style.height = "30px";
+ document.getElementById("scrollable-div").style.width = "30px";
+ document.getElementById("scrolled-div").style.height = "10px";
+ document.getElementById("scrolled-div").style.width = "10px";
+ document.getElementById("input-text").value = "";
+ document.getElementById("animated-div").className = "";
+ document.getElementById("animated-div").removeAttribute("x-data");
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ window.requestAnimationFrame(function() {
+ setTimeout(runNextTest, 0);
+ });
+ return;
+ }
+ doTest(kTests[gIndex]);
+}
+
+function init() {
+ SpecialPowers.pushPrefEnv({"set": [["middlemouse.contentLoadURL", false],
+ ["middlemouse.paste", false],
+ ["general.autoScroll", false],
+ ["mousewheel.default.action", 0],
+ ["mousewheel.default.action.override_x", -1],
+ ["mousewheel.with_shift.action", 0],
+ ["mousewheel.with_shift.action.override_x", -1],
+ ["mousewheel.with_control.action", 0],
+ ["mousewheel.with_control.action.override_x", -1],
+ ["mousewheel.with_alt.action", 0],
+ ["mousewheel.with_alt.action.override_x", -1],
+ ["mousewheel.with_meta.action", 0],
+ ["mousewheel.with_meta.action.override_x", -1]]}, runNextTest);
+}
+
+function finish() {
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(init);
+
+</script>
+</body>
diff --git a/widget/tests/test_autocapitalize.html b/widget/tests/test_autocapitalize.html
new file mode 100644
index 0000000000..833dafe1af
--- /dev/null
+++ b/widget/tests/test_autocapitalize.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Tests for autocapitalize that is used by software keyboard</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/SpecialPowers.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+
+<div>
+<input type="text" id="a1"><br>
+<input type="text" id="a2" autocapitalize="characters"><br>
+<input type="text" id="a3" autocapitalize="sentences"><br>
+<input type="text" id="a4" autocapitalize="words"><br>
+<input type="text" id="a5" autocapitalize="off"><br>
+<input type="text" id="a6" autocapitalize="on"><br>
+<input type="url" id="a7" autocapitalize="on"><br>
+<input type="email" id="a8" autocapitalize="on"><br>
+<input type="password" id="a9" autocapitalize="on"><br>
+<textarea id="b1" autocapitalize="characters"></textarea><br>
+<div contenteditable id="c1" autocapitalize="sentences"></div><br>
+<form><input type="text" id="d1" autocapitalize="words"></form><br>
+<form autocapitalize="on"><input type="text" id="d2"></form><br>
+<form autocapitalize="off"><input type="text" id="d3" autocapitalize="on"></form><br>
+</div>
+
+<pre id="test">
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(async () => {
+ const tests = [
+ { id: "a1", autocapitalize: "", desc: "input without autocapitalize" },
+ { id: "a2", autocapitalize: "characters", desc: "input with autocapitalize=characters" },
+ { id: "a3", autocapitalize: "sentences", desc: "input with autocapitalize=sentences" },
+ { id: "a4", autocapitalize: "words", desc: "input with autocapitalize=words" },
+ { id: "a5", autocapitalize: "none", desc: "input with autocapitalize=off" },
+ { id: "a6", autocapitalize: "sentences", desc: "input with autocapitalize=on" },
+ { id: "a7", autocapitalize: "", desc: "input with type=url and autocapitalize=on" },
+ { id: "a8", autocapitalize: "", desc: "input with type=email and autocapitalize=on" },
+ { id: "a9", autocapitalize: "", desc: "input with type=password and autocapitalize=on" },
+ { id: "b1", autocapitalize: "characters", desc: "textarea with autocapitalize=characters" },
+ { id: "c1", autocapitalize: "sentences", desc: "contenteditable with autocapitalize=sentences" },
+ { id: "d1", autocapitalize: "words", desc: "input with autocapitalize=words in form" },
+ { id: "d2", autocapitalize: "sentences", desc: "input in form with autocapitalize=on" },
+ { id: "d3", autocapitalize: "sentences", desc: "input with autocapitalize=on in form" },
+ ];
+
+ await SpecialPowers.setBoolPref("dom.forms.autocapitalize", true);
+
+ for (let test of tests) {
+ document.getElementById(test.id).focus();
+ is(SpecialPowers.DOMWindowUtils.focusedAutocapitalize, test.autocapitalize, test.desc);
+ }
+
+ SpecialPowers.clearUserPref("dom.forms.autocapitalize");
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/widget/tests/test_bug1123480.xhtml b/widget/tests/test_bug1123480.xhtml
new file mode 100644
index 0000000000..c84a9c1d59
--- /dev/null
+++ b/widget/tests/test_bug1123480.xhtml
@@ -0,0 +1,153 @@
+<?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=1123480
+-->
+<window title="Mozilla Bug 1123480"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="RunTest();">
+ <title>nsTransferable PBM Overflow Selection Test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ // Create over 1 MB of sample garbage text. JavaScript strings are represented by
+ // UTF16 strings, so the size is twice as much as the actual string length.
+ // This value is chosen such that the size of the memory for the string exceeds
+ // the kLargeDatasetSize threshold in nsTransferable.h.
+ // It is also not a round number to reduce the odds of having an accidental
+ // collisions with another file (since the test below looks at the file size
+ // to identify the file).
+ var Ipsum = "0123456789".repeat(1234321);
+ var IpsumByteLength = Ipsum.length * 2;
+ var SHORT_STRING_NO_CACHE = "short string that will never be cached to the disk";
+
+ function isWindows() {
+ const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ return AppConstants.platform === 'win';
+ }
+
+ // Get a list of open file descriptors that refer to a file with the same size as
+ // the expected data (and assume that any mutations in file descriptor counts
+ // are caused by our test).
+ function getClipboardCacheFDCount() {
+ var dir;
+ if (isWindows()) {
+ // On Windows, nsAnonymousTemporaryFile does not immediately delete the file.
+ // Instead, the Windows-specific FILE_FLAG_DELETE_ON_CLOSE flag is used,
+ // which means that the file is deleted when the last handle is closed.
+ // Apparently, this flag is unreliable (e.g. when the application crashes),
+ // so nsAnonymousTemporaryFile stores the temporary files in a subdirectory,
+ // which is cleaned up some time after start-up.
+
+ // This is just a test, and during the test we deterministically close the
+ // handles, so if FILE_FLAG_DELETE_ON_CLOSE does the thing it promises, the
+ // file is actually removed when the handle is closed.
+
+ var {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
+ // Path from nsAnonymousTemporaryFile.cpp, GetTempDir.
+ dir = FileUtils.getFile("TmpD", ["mozilla-temp-files"]);
+ } else {
+ dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dir.initWithPath("/dev/fd");
+ }
+ var count = 0;
+ for (var de = dir.directoryEntries; de.hasMoreElements(); ) {
+ var fdFile = de.nextFile;
+ var fileSize;
+ try {
+ fileSize = fdFile.fileSize;
+ } catch (e) {
+ // This can happen on macOS.
+ continue;
+ }
+ if (fileSize === IpsumByteLength) {
+ // Assume that the file was created by us if the size matches.
+ ++count;
+ }
+ }
+ return count;
+ }
+
+ async function RunTest() {
+ SimpleTest.waitForExplicitFinish();
+ const gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
+
+ // Sanitize environment
+ gClipboardHelper.copyString(SHORT_STRING_NO_CACHE);
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ var initialFdCount = getClipboardCacheFDCount();
+
+ // Overflow a nsTransferable region by using the clipboard helper
+ gClipboardHelper.copyString(Ipsum);
+
+ // gClipboardHelper.copyString also puts the data on the selection
+ // clipboard if the platform supports it.
+ var expectedFdDelta = Services.clipboard.isClipboardTypeSupported(Services.clipboard.kSelectionClipboard) ? 2 : 1;
+ // Undefined private browsing mode should cache to disk
+ is(getClipboardCacheFDCount(), initialFdCount + expectedFdDelta, "should cache to disk when PBM is undefined");
+
+ // Sanitize environment again.
+ gClipboardHelper.copyString(SHORT_STRING_NO_CACHE);
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ is(getClipboardCacheFDCount(), initialFdCount, "should have cleared the clipboard data");
+
+ // Repeat procedure of plain text selection with private browsing
+ // disabled and enabled
+ const {PrivateBrowsingUtils} = ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+ for (let private of [false, true]) {
+ var win = window.browsingContext.topChromeWindow.open("about:blank", "_blank", "chrome, width=500, height=200" + (private ? ", private" : ""));
+ ok(win, private ? "should open private window" : "should open non-private window");
+ is(PrivateBrowsingUtils.isContentWindowPrivate(win), private, "used correct window context");
+
+ // Select plaintext in private/non-private channel
+ const nsTransferable = Components.Constructor("@mozilla.org/widget/transferable;1", "nsITransferable");
+ const nsSupportsString = Components.Constructor("@mozilla.org/supports-string;1", "nsISupportsString");
+ var Loadctx = PrivateBrowsingUtils.privacyContextFromWindow(win);
+ var Transfer = nsTransferable();
+ var Suppstr = nsSupportsString();
+ Suppstr.data = Ipsum;
+ Transfer.init(Loadctx);
+ Transfer.addDataFlavor("text/plain");
+ Transfer.setTransferData("text/plain", Suppstr);
+
+ // Enabled private browsing mode should not cache any selection to disk; disabled should
+ if (private) {
+ is(getClipboardCacheFDCount(), initialFdCount, "did not violate private browsing mode");
+ } else {
+ is(getClipboardCacheFDCount(), initialFdCount + 1, "should save memory by caching non-private clipboard data to disk");
+ }
+
+ // Share the transferable with the system.
+ Services.clipboard.setData(Transfer, null, Services.clipboard.kGlobalClipboard);
+ if (private) {
+ is(getClipboardCacheFDCount(), initialFdCount, "did not violate private browsing mode");
+ } else {
+ is(getClipboardCacheFDCount(), initialFdCount + 1, "should save memory by caching non-private clipboard data to disk");
+ }
+
+ // Sanitize the environment.
+ Suppstr = nsSupportsString();
+ Suppstr.data = SHORT_STRING_NO_CACHE;
+ Transfer.setTransferData("text/plain", Suppstr);
+ await new Promise(resolve => setTimeout(resolve, 0));
+ is(getClipboardCacheFDCount(), initialFdCount, "should drop the cache file, if any.");
+
+ Services.clipboard.setData(Transfer, null, Services.clipboard.kGlobalClipboard);
+ is(getClipboardCacheFDCount(), initialFdCount, "should postsanitize the environment");
+ }
+
+ SimpleTest.finish();
+ }
+ ]]>
+ </script>
+
+ <!-- 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=1123480"
+ target="_blank">Mozilla Bug 1123480</a>
+ </body>
+</window>
diff --git a/widget/tests/test_bug343416.xhtml b/widget/tests/test_bug343416.xhtml
new file mode 100644
index 0000000000..bf4dbbb9b4
--- /dev/null
+++ b/widget/tests/test_bug343416.xhtml
@@ -0,0 +1,191 @@
+<?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=343416
+-->
+<window title="Mozilla Bug 343416"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=343416">Mozilla Bug 343416</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 343416 */
+SimpleTest.waitForExplicitFinish();
+
+// Observer:
+var idleObserver =
+{
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe: function _observe(subject, topic, data)
+ {
+ if (topic != "idle")
+ return;
+
+// var diff = Math.abs(data - newIdleSeconds * 1000);
+// ok (diff < 5000, "The idle time should have increased by roughly 6 seconds, " +
+// "as that's when we told this listener to fire.");
+// if (diff >= 5000)
+// alert(data + " " + newIdleSeconds);
+
+ // Attempt to get to the nsIUserIdleService
+ var subjectOK = false;
+ try {
+ var idleService = subject.QueryInterface(nsIUserIdleService);
+ subjectOK = true;
+ }
+ catch (ex)
+ {}
+ ok(subjectOK, "The subject of the notification should be the " +
+ "nsIUserIdleService.");
+
+ // Attempt to remove ourselves.
+ var removedObserver = false;
+ try {
+ idleService.removeIdleObserver(this, newIdleSeconds);
+ removedObserver = true;
+ }
+ catch (ex)
+ {}
+ ok(removedObserver, "We should be able to remove our observer here.");
+ finishedListenerOK = true;
+ if (finishedTimeoutOK)
+ {
+ clearTimeout(testBailout);
+ finishThisTest();
+ }
+ }
+};
+
+
+const nsIUserIdleService = Ci.nsIUserIdleService;
+const nsIISCID = "@mozilla.org/widget/useridleservice;1";
+var idleService = null;
+try
+{
+ idleService = Cc[nsIISCID].getService(nsIUserIdleService);
+}
+catch (ex)
+{}
+
+ok(idleService, "nsIUserIdleService should exist and be implemented on all tier 1 platforms.");
+
+var idleTime = null;
+var gotIdleTime = false;
+try
+{
+ idleTime = idleService.idleTime;
+ gotIdleTime = true;
+}
+catch (ex)
+{}
+
+ok (gotIdleTime, "Getting the idle time should not fail " +
+ "in normal circumstances on any tier 1 platform.");
+
+// Now we set up a timeout to sanity-test the idleTime after 5 seconds
+setTimeout(testIdleTime, 5000);
+var startTimeStamp = Date.now();
+
+// Now we add the listener:
+var newIdleSeconds = Math.floor(idleTime / 1000) + 6;
+var addedObserver = false;
+try
+{
+ idleService.addIdleObserver(idleObserver, newIdleSeconds);
+ addedObserver = true;
+}
+catch (ex)
+{}
+
+ok(addedObserver, "The nsIUserIdleService should allow us to add an observer.");
+
+addedObserver = false;
+try
+{
+ idleService.addIdleObserver(idleObserver, newIdleSeconds);
+ addedObserver = true;
+}
+catch (ex)
+{}
+
+ok(addedObserver, "The nsIUserIdleService should allow us to add the same observer again.");
+
+var removedObserver = false;
+try
+{
+ idleService.removeIdleObserver(idleObserver, newIdleSeconds);
+ removedObserver = true;
+}
+catch (ex)
+{}
+
+ok(removedObserver, "The nsIUserIdleService should allow us to remove the observer just once.");
+
+function testIdleTime()
+{
+ /* eslint-disable-next-line no-shadow */
+ var gotIdleTime = false
+ try
+ {
+ var newIdleTime = idleService.idleTime;
+ gotIdleTime = true
+ }
+ catch (ex)
+ {}
+ ok(gotIdleTime, "Getting the idle time should not fail " +
+ "in normal circumstances on any tier 1 platform.");
+ // Get the time difference, remove the approx. 5 seconds that we've waited,
+ // should be very close to 0 left.
+ var timeDiff = Math.abs((newIdleTime - idleTime) -
+ (Date.now() - startTimeStamp));
+
+ // 1.5 second leniency.
+ ok(timeDiff < 1500, "The idle time should have increased by roughly the " +
+ "amount of time it took for the timeout to fire. " +
+ "You didn't touch the mouse or keyboard during the " +
+ "test did you?");
+ finishedTimeoutOK = true;
+}
+
+// make sure we still exit when the listener and/or setTimeout don't fire:
+var testBailout = setTimeout(finishThisTest, 12000);
+var finishedTimeoutOK = false, finishedListenerOK = false;
+function finishThisTest()
+{
+ ok(finishedTimeoutOK, "We set a timeout and it should have fired by now.");
+ ok(finishedListenerOK, "We added a listener and it should have been called by now.");
+ if (!finishedListenerOK)
+ {
+ var removedListener = false;
+ try
+ {
+ idleService.removeIdleObserver(idleObserver, newIdleSeconds);
+ removedListener = true;
+ }
+ catch (ex)
+ {}
+
+ ok(removedListener, "We added a listener and we should be able to remove it.");
+ }
+ // Done:
+ SimpleTest.finish();
+}
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug413277.html b/widget/tests/test_bug413277.html
new file mode 100644
index 0000000000..d9f6aaa807
--- /dev/null
+++ b/widget/tests/test_bug413277.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=413277
+-->
+<head>
+ <title>Test for Bug 413277</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" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=413277">Mozilla Bug 413277</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+ var atts = "width=100, height=100, top=100, screenY=100";
+ atts += ", left=100, screenX=100, toolbar=no";
+ atts += ", location=no, directories=no, status=no";
+ atts += ", menubar=no, scrollbars=no, resizable=no";
+ var newWindow = window.open("_blank", "win_name", atts);
+
+ newWindow.resizeBy(1000000, 1000000);
+ SimpleTest.is(newWindow.outerWidth, newWindow.screen.availWidth, true);
+ SimpleTest.is(newWindow.outerHeight, newWindow.screen.availHeight, true);
+ SimpleTest.is(newWindow.screenY, newWindow.screen.availTop, true);
+ SimpleTest.is(newWindow.screenX, newWindow.screen.availLeft, true);
+
+ newWindow.close();
+</script>
+</pre>
+</body>
diff --git a/widget/tests/test_bug428405.xhtml b/widget/tests/test_bug428405.xhtml
new file mode 100644
index 0000000000..ebfe9f127d
--- /dev/null
+++ b/widget/tests/test_bug428405.xhtml
@@ -0,0 +1,166 @@
+<?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 id="window1" title="Test Bug 428405"
+ onload="setGlobals(); loadFirstTab();"
+ 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 src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.js"/>
+
+ <tabbox id="tabbox" style="-moz-box-flex: 100">
+ <tabs>
+ <tab label="Tab 1"/>
+ <tab label="Tab 2"/>
+ </tabs>
+ <tabpanels style="-moz-box-flex: 100">
+ <browser onload="configureFirstTab();" id="tab1browser" style="-moz-box-flex: 100"/>
+ <browser onload="configureSecondTab();" id="tab2browser" style="-moz-box-flex: 100"/>
+ </tabpanels>
+ </tabbox>
+
+ <script type="application/javascript"><![CDATA[
+ const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+
+ SimpleTest.waitForExplicitFinish();
+
+ var gCmdOptYReceived = false;
+
+ // Look for a cmd-opt-y event.
+ function onKeyPress(aEvent) {
+ gCmdOptYReceived = false;
+ if (String.fromCharCode(aEvent.charCode) != 'y')
+ return;
+ if (aEvent.ctrlKey || aEvent.shiftKey || !aEvent.metaKey || !aEvent.altKey)
+ return;
+ gCmdOptYReceived = true;
+ }
+
+ function setGlobals() {
+ let chromeWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ // For some reason, a global <key> element's oncommand handler only gets
+ // invoked if the focus is outside both of the <browser> elements
+ // (tab1browser and tab2browser). So, to make sure we can see a
+ // cmd-opt-y event in window1 (if one is available), regardless of where
+ // the focus is in this window, we need to add a "keypress" event
+ // listener to gChromeWindow, and then check (in onKeyPress()) to see if
+ // it's a cmd-opt-y event.
+ chromeWindow.addEventListener("keypress", onKeyPress);
+ }
+
+ // 1) Start loading first tab.
+ // 6) Start reloading first tab.
+ function loadFirstTab() {
+ var browser = document.getElementById("tab1browser");
+ BrowserTestUtils.loadURIString(browser, "data:text/html;charset=utf-8,<body><h2>First Tab</h2><p><input type='submit' value='Button' id='button1'/></body>");
+ }
+
+ function configureFirstTab() {
+ try {
+ var button = document.getElementById("tab1browser").contentDocument.getElementById("button1");
+ button.addEventListener("click", onFirstTabButtonClicked);
+ button.focus();
+ if (document.getElementById("tabbox").selectedIndex == 0) {
+ // 2) When first tab has finished loading (while first tab is
+ // focused), hit Return to trigger the action of first tab's
+ // button.
+ synthesizeNativeReturnKey();
+ } else {
+ // 7) When first tab has finished reloading (while second tab is
+ // focused), start loading second tab.
+ loadSecondTab();
+ }
+ } catch(e) {
+ }
+ }
+
+ // 8) Start loading second tab.
+ function loadSecondTab() {
+ var browser = document.getElementById("tab2browser");
+ BrowserTestUtils.loadURIString(browser, "data:text/html;charset=utf-8,<body><h2>Second Tab</h2><p><input type='submit' value='Button' id='button1'/></body>");
+ }
+
+ function configureSecondTab() {
+ try {
+ var button = document.getElementById("tab2browser").contentDocument.getElementById("button1");
+ button.addEventListener("click", onSecondTabButtonClicked);
+ button.focus();
+ if (document.getElementById("tabbox").selectedIndex == 1) {
+ // 9) When second tab has finished loading (while second tab is
+ // focused), hit Return to trigger action of second tab's
+ // button.
+ synthesizeNativeReturnKey();
+ }
+ } catch(e) {
+ }
+ }
+
+ // 3) First tab's button clicked.
+ function onFirstTabButtonClicked() {
+ switchToSecondTabAndReloadFirst();
+ }
+
+ // 10) Second tab's button clicked.
+ function onSecondTabButtonClicked() {
+ switchToFirstTab();
+ }
+
+ function switchToSecondTabAndReloadFirst() {
+ // 4) Switch to second tab.
+ document.getElementById("tabbox").selectedIndex = 1;
+ // 5) Start reloading first tab (while second tab is focused).
+ loadFirstTab();
+ }
+
+ function switchToFirstTab() {
+ // 11) Switch back to first tab.
+ document.getElementById("tabbox").selectedIndex = 0;
+ doCmdY();
+ }
+
+ function doCmdY() {
+ // 12) Back in first tab, try cmd-y.
+ gCmdOptYReceived = false;
+ if (!synthesizeNativeCmdOptY(finishTest)) {
+ ok(false, "Failed to synthesize native key");
+ finishTest();
+ }
+ }
+
+ function finishTest() {
+ // 13) Check result.
+ is(gCmdOptYReceived, true);
+
+ SimpleTest.finish();
+ }
+
+ // synthesizeNativeReturnKey() and synthesizeNativeCmdOptY() are needed
+ // because their synthesizeKey() counterparts don't work properly -- the
+ // latter make this test succeed when it should fail.
+
+ // The 'aNativeKeyCode', 'aCharacters' and 'aUnmodifiedCharacters'
+ // parameters used below (in synthesizeNativeReturnKey() and
+ // synthesizeNativeCmdOptY()) were confirmed accurate using the
+ // DebugEventsPlugin v1.01 from bmo bug 441880.
+
+ function synthesizeNativeReturnKey() {
+ synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_Return, {}, "\u000a", "\u000a");
+ }
+
+ function synthesizeNativeCmdOptY(aCallback) {
+ return synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_Y, {metaKey:1, altKey:1}, "y", "y", aCallback);
+ }
+
+ ]]></script>
+
+ <!-- test results are displayed in the html:body -->
+ <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/widget/tests/test_bug429954.xhtml b/widget/tests/test_bug429954.xhtml
new file mode 100644
index 0000000000..40de88cd32
--- /dev/null
+++ b/widget/tests/test_bug429954.xhtml
@@ -0,0 +1,42 @@
+<?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=429954
+-->
+<window title="Mozilla Bug 429954"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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();
+
+SimpleTest.waitForFocus(function () {
+ var win = Services.wm.getMostRecentWindow("navigator:browser");
+ win.maximize();
+ var maxX = win.screenX, maxY = win.screenY;
+ var maxWidth = win.outerWidth, maxHeight = win.outerHeight;
+ win.restore();
+
+ window.openDialog("window_bug429954.xhtml", "_blank",
+ "chrome,noopener,resizable,width=" + maxWidth + ",height=" + maxHeight +
+ ",screenX=" + maxX + "screenY=" + maxY,
+ window);
+});
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug444800.xhtml b/widget/tests/test_bug444800.xhtml
new file mode 100644
index 0000000000..85a73b3288
--- /dev/null
+++ b/widget/tests/test_bug444800.xhtml
@@ -0,0 +1,97 @@
+<?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=444800
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Mozilla Bug 444800" onload="initAndRunTests()">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=444800"
+ target="_blank">Mozilla Bug 444800</a>
+ <p/>
+ <img id="bitmapImage" src="data:image/bmp;base64,Qk2KAwAAAAAAAIoAAAB8AAAADwAAABAAAAABABgAAAAAAAADAAASCwAAEgsAAAAAAAAAAAAAAAD%2FAAD%2FAAD%2FAAAAAAAA%2FwEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F0vf%2FAABc8tKY%2F%2F%2F%2FyNfq3Mi9%2F%2F%2F70vf%2FAABP8s2R%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fzff%2FAABB8s2R5f%2F%2FAAB5LgAA%2F%2B7Czff%2FAABB7s2R%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fzff%2FAABB99KRpdz%2FAAAAAAAA4Ktm0vv%2FAABB7s2R%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fzff%2FAABB7teYQZHNkS4AebfImAAA1%2FfyAABP7s2R%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fzff%2FAABByMiYAAB5159P0v%2F%2FAABBwtKrAABc7s2R%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fzff%2FAABPcIJwAAAA%2B%2BW3%2F%2F%2F%2FAHC3gnBBAABP7s2R%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fzff%2FAABcAAAAmE8A%2F%2F%2Fy%2F%2F%2F%2Fn9LyAAAAAAAA7s2Y%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FzfL%2FAABcAAAA4LFw%2F%2F%2F%2F%2F%2F%2F%2F4P%2F%2FAAB5AAAA7s2R%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F0vf%2FAABmXAAA%2F%2B7I%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FXJ%2FSAAAA8s2Y%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAAAA"/>
+ <p/>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+const knsIClipboard = Ci.nsIClipboard;
+
+function copyImageToClipboard()
+{
+ SpecialPowers.setCommandNode(window, document.getElementById("bitmapImage"));
+
+ const kCmd = "cmd_copyImageContents";
+ var controller = top.document.commandDispatcher
+ .getControllerForCommand(kCmd);
+ ok((controller && controller.isCommandEnabled(kCmd)), "have copy command");
+ controller.doCommand(kCmd);
+
+ SpecialPowers.setCommandNode(window, null);
+}
+
+function getLoadContext() {
+ return window.docShell.QueryInterface(Ci.nsILoadContext);
+}
+
+function runImageClipboardTests(aCBSvc, aImageType)
+{
+ // Verify that hasDataMatchingFlavors() is working correctly.
+ var typeArray = [ aImageType ];
+ var hasImage = aCBSvc.hasDataMatchingFlavors(typeArray,
+ knsIClipboard.kGlobalClipboard);
+ ok(hasImage, aImageType + " - hasDataMatchingFlavors()");
+
+ // Verify that getData() is working correctly.
+ var xfer = Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(Ci.nsITransferable);
+ xfer.init(getLoadContext());
+ xfer.addDataFlavor(aImageType);
+ aCBSvc.getData(xfer, knsIClipboard.kGlobalClipboard);
+
+ var typeObj = {}, dataObj = {};
+ xfer.getAnyTransferData(typeObj, dataObj);
+ var gotValue = (null != dataObj.value);
+ ok(gotValue, aImageType + " - getData() returned a value");
+ if (gotValue)
+ {
+ const knsIInputStream = Ci.nsIInputStream;
+ var imgStream = dataObj.value.QueryInterface(knsIInputStream);
+ ok((null != imgStream), aImageType + " - got an nsIInputStream");
+ var bytesAvailable = imgStream.available();
+ ok((bytesAvailable > 10), aImageType + " - got some data");
+ }
+}
+
+function initAndRunTests()
+{
+ SimpleTest.waitForExplicitFinish();
+
+ copyImageToClipboard();
+
+ var cbSvc = Cc["@mozilla.org/widget/clipboard;1"]
+ .getService(knsIClipboard);
+
+ // Work around a problem on Windows where clipboard is not ready after copy.
+ setTimeout(function() { runTests(cbSvc); }, 0);
+}
+
+function runTests(aCBSvc)
+{
+ runImageClipboardTests(aCBSvc, "image/png");
+ runImageClipboardTests(aCBSvc, "image/jpg");
+ runImageClipboardTests(aCBSvc, "image/jpeg");
+
+ SimpleTest.finish();
+}
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/test_bug466599.xhtml b/widget/tests/test_bug466599.xhtml
new file mode 100644
index 0000000000..6a81b16af2
--- /dev/null
+++ b/widget/tests/test_bug466599.xhtml
@@ -0,0 +1,103 @@
+<?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=466599
+-->
+<window title="Mozilla Bug 466599"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="initAndRunTests()">
+ <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">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ </body>
+
+ <!-- test code goes here -->
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 466599 */
+
+function getLoadContext() {
+ return window.docShell.QueryInterface(Ci.nsILoadContext);
+}
+
+function copyToClipboard(txt)
+{
+ var clipid = Ci.nsIClipboard;
+ var clip =
+ Cc['@mozilla.org/widget/clipboard;1'].createInstance(clipid);
+ if (!clip)
+ return false;
+ var trans =
+ Cc['@mozilla.org/widget/transferable;1'].createInstance(Ci.nsITransferable);
+ if (!trans)
+ return false;
+ trans.init(getLoadContext());
+ trans.addDataFlavor('text/html');
+ var str =
+ Cc['@mozilla.org/supports-string;1'].createInstance(Ci.nsISupportsString);
+ var copytext = txt;
+ str.data = copytext;
+ trans.setTransferData("text/html",str);
+ if (!clip)
+ return false;
+ clip.setData(trans,null,clipid.kGlobalClipboard);
+ return true;
+}
+
+function readFromClipboard()
+{
+ var clipid = Ci.nsIClipboard;
+ var clip =
+ Cc['@mozilla.org/widget/clipboard;1'].createInstance(clipid);
+ if (!clip)
+ return "";
+ var trans =
+ Cc['@mozilla.org/widget/transferable;1'].createInstance(Ci.nsITransferable);
+ if (!trans)
+ return "";
+ trans.init(getLoadContext());
+ trans.addDataFlavor('text/html');
+ clip.getData(trans,clipid.kGlobalClipboard);
+ var str = {};
+ trans.getTransferData("text/html",str);
+ if (str)
+ str = str.value.QueryInterface(Ci.nsISupportsString);
+ return str?.data;
+}
+
+function encodeHtmlEntities(s)
+{
+ var result = '';
+ for (var i = 0; i < s.length; i++) {
+ var c = s.charAt(i);
+ result += {'<':'&lt;', '>':'&gt;', '&':'&amp;', '"':'&quot;'}[c] || c;
+ }
+ return result;
+}
+
+function initAndRunTests()
+{
+ var source = '<p>Lorem ipsum</p>';
+ var expect = new RegExp('<html>.*charset=utf-8.*' + source + '.*</html>', 'im');
+
+ var result = copyToClipboard(source);
+ ok(result, "copied HTML data to system pasteboard");
+
+ result = readFromClipboard();
+ ok(expect.test(result), "data on system pasteboard is wrapped with charset metadata");
+
+ // eslint-disable-next-line no-unsanitized/property
+ $("display").innerHTML =
+ '<em>source:</em> <pre>' + encodeHtmlEntities(source) + '</pre><br/>' +
+ '<em>result:</em> <pre>' + encodeHtmlEntities(result) + '</pre>';
+}
+
+ ]]>
+ </script>
+</window>
diff --git a/widget/tests/test_bug478536.xhtml b/widget/tests/test_bug478536.xhtml
new file mode 100644
index 0000000000..383c0bb42f
--- /dev/null
+++ b/widget/tests/test_bug478536.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"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=478536
+-->
+<window title="Mozilla Bug 478536"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <title>Test for Bug 478536</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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();
+window.openDialog("window_bug478536.xhtml", "_blank",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug485118.xhtml b/widget/tests/test_bug485118.xhtml
new file mode 100644
index 0000000000..5c635f2982
--- /dev/null
+++ b/widget/tests/test_bug485118.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"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=485118
+-->
+<window title="Mozilla Bug 485118"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<hbox height="300">
+ <vbox width="300">
+ <scrollbar orient="horizontal"
+ maxpos="10000"
+ pageincrement="1"
+ id="horizontal"/>
+ <scrollbar orient="horizontal"
+ maxpos="10000"
+ pageincrement="1"
+ style="appearance: auto; -moz-default-appearance: scrollbar-small;"
+ id="horizontalSmall"/>
+ <hbox flex="1">
+ <scrollbar orient="vertical"
+ maxpos="10000"
+ pageincrement="1"
+ id="vertical"/>
+ <scrollbar orient="vertical"
+ maxpos="10000"
+ pageincrement="1"
+ style="appearance: auto; -moz-default-appearance: scrollbar-small;"
+ id="verticalSmall"/>
+ <spacer flex="1"/>
+ </hbox>
+ </vbox>
+</hbox>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ ["horizontal", "vertical"].forEach(function (orient) {
+ ["", "Small"].forEach(function (size) {
+ var elem = document.getElementById(orient + size);
+ var thumbRect = SpecialPowers.unwrap(
+ SpecialPowers.InspectorUtils.getChildrenForNode(elem, true, false)[0])
+ .childNodes[0].getBoundingClientRect();
+ var sizeToCheck = orient == "horizontal" ? "width" : "height";
+ // var expectedSize = size == "Small" ? 19 : 26;
+ var expectedSize = 26;
+ is(thumbRect[sizeToCheck], expectedSize, size + " scrollbar has wrong minimum " + sizeToCheck);
+ });
+ });
+ SimpleTest.finish();
+}
+window.addEventListener("load", runTest);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug517396.xhtml b/widget/tests/test_bug517396.xhtml
new file mode 100644
index 0000000000..c88baf49ab
--- /dev/null
+++ b/widget/tests/test_bug517396.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"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=517396
+-->
+<window title="Mozilla Bug 517396"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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();
+
+SimpleTest.waitForFocus(function () {
+ // this test fails on Linux, bug 526236
+ if (navigator.platform.includes("Lin")) {
+ ok(true, "disabled on Linux");
+ SimpleTest.finish();
+ return;
+ }
+
+ var win = Services.wm.getMostRecentWindow("navigator:browser");
+ var oldWidth = win.outerWidth, oldHeight = win.outerHeight;
+ win.maximize();
+ var newWidth = win.outerWidth, newHeight = win.outerHeight;
+ win.moveBy(10, 0);
+ var sizeShouldHaveChanged = !navigator.platform.match(/Mac/);
+ var compFunc = sizeShouldHaveChanged ? isnot : is;
+ var not = sizeShouldHaveChanged ? "" : "not ";
+ compFunc(win.outerWidth, newWidth, "moving a maximized window should " + not + "have changed its width");
+ compFunc(win.outerHeight, newHeight, "moving a maximized window should " + not + "have changed its height");
+ win.restore();
+ is(win.outerWidth, oldWidth, "restored window has wrong width");
+ is(win.outerHeight, oldHeight, "restored window has wrong height");
+ SimpleTest.finish();
+});
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug522217.xhtml b/widget/tests/test_bug522217.xhtml
new file mode 100644
index 0000000000..0fa55a65e8
--- /dev/null
+++ b/widget/tests/test_bug522217.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"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=522217
+-->
+<window title="Mozilla Bug 522217"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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();
+
+SimpleTest.waitForFocus(function () {
+ window.openDialog("window_bug522217.xhtml", "_blank",
+ "chrome,resizable,width=400,height=300,noopener", window);
+});
+
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug538242.xhtml b/widget/tests/test_bug538242.xhtml
new file mode 100644
index 0000000000..4608a74e35
--- /dev/null
+++ b/widget/tests/test_bug538242.xhtml
@@ -0,0 +1,55 @@
+<?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=538242
+-->
+<window title="Mozilla Bug 538242"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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[
+
+if (navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function () {
+ if (navigator.platform.includes("Lin")) {
+ ok(true, "This test is disabled on Linux because it expects moving windows to be synchronous which is not guaranteed on Linux.");
+ SimpleTest.finish();
+ return;
+ }
+
+ var win = window.browsingContext.topChromeWindow.open(
+ "window_bug538242.xhtml", "_blank",
+ "chrome,width=400,height=300,left=100,top=100");
+ SimpleTest.waitForFocus(function () {
+ is(win.screenX, 100, "window should open at 100, 100");
+ is(win.screenY, 100, "window should open at 100, 100");
+ var [oldX, oldY] = [win.screenX, win.screenY];
+ win.moveTo(0, 0);
+ isnot(win.screenX, oldX, "window should have moved to a point near 0, 0");
+ isnot(win.screenY, oldY, "window should have moved to a point near 0, 0");
+ win.close();
+ SimpleTest.finish();
+ }, win);
+});
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug565392.html b/widget/tests/test_bug565392.html
new file mode 100644
index 0000000000..a0efc0a7f9
--- /dev/null
+++ b/widget/tests/test_bug565392.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=565392
+-->
+<head>
+ <title>Test for Bug 565392</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"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=565392">Mozilla Bug 565392</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 565392 */
+
+var dir1 = Services.dirsvc.get("ProfD", Ci.nsIFile);
+var clipboard = Services.clipboard;
+
+ function getLoadContext() {
+ return window.docShell.QueryInterface(Ci.nsILoadContext);
+ }
+
+ function getTransferableFile(file) {
+ var transferable = Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(Ci.nsITransferable);
+ transferable.init(getLoadContext());
+ transferable.setTransferData("application/x-moz-file", file);
+ return transferable;
+ }
+
+ function setClipboardData(transferable) {
+ clipboard.setData(transferable, null, 1);
+ }
+
+ function getClipboardData(mime) {
+ var transferable = Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(Ci.nsITransferable);
+ transferable.init(getLoadContext());
+ transferable.addDataFlavor(mime);
+ clipboard.getData(transferable, 1);
+ var data = {};
+ transferable.getTransferData(mime, data);
+ return data;
+ }
+
+setClipboardData(getTransferableFile(dir1));
+is(clipboard.hasDataMatchingFlavors(["application/x-moz-file"], 1), true);
+var data = getClipboardData("application/x-moz-file");
+var file = data.value.QueryInterface(Ci.nsIFile);
+ok(file.isDirectory(), true);
+is(file.target, dir1.target, true);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/widget/tests/test_bug586713.xhtml b/widget/tests/test_bug586713.xhtml
new file mode 100644
index 0000000000..4733202264
--- /dev/null
+++ b/widget/tests/test_bug586713.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="Native menu system tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug586713_window.xhtml", "bug586713_window",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug593307.xhtml b/widget/tests/test_bug593307.xhtml
new file mode 100644
index 0000000000..770dd390cb
--- /dev/null
+++ b/widget/tests/test_bug593307.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=593307
+-->
+<window title="Mozilla Bug 593307"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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();
+
+function finish() {
+ offscreenWindow.close();
+ SimpleTest.finish();
+}
+
+var mainWindow = window.browsingContext.topChromeWindow;
+
+var offscreenWindow = mainWindow.openDialog("window_bug593307_offscreen.xhtml", "",
+ "dialog=no,chrome,width=200,height=200,screenX=-3000,screenY=-3000",
+ SimpleTest, finish);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug596600.xhtml b/widget/tests/test_bug596600.xhtml
new file mode 100644
index 0000000000..4acdab79bc
--- /dev/null
+++ b/widget/tests/test_bug596600.xhtml
@@ -0,0 +1,190 @@
+<?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="Native mouse event 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" />
+
+<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 class="testbody" type="application/javascript">
+<![CDATA[
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+var gLeftWindow, gRightWindow, gBrowserElement;
+
+function openWindows() {
+ gLeftWindow = window.browsingContext.topChromeWindow
+ .open('empty_window.xhtml', '_blank', 'chrome,screenX=50,screenY=50,width=200,height=200');
+ SimpleTest.waitForFocus(function () {
+ gRightWindow = window.browsingContext.topChromeWindow
+ .open('empty_window.xhtml', '', 'chrome,screenX=300,screenY=50,width=200,height=200');
+ SimpleTest.waitForFocus(attachBrowserToLeftWindow, gRightWindow);
+ }, gLeftWindow);
+}
+
+function attachBrowserToLeftWindow() {
+ gBrowserElement = gLeftWindow.document.createXULElement("browser");
+ gBrowserElement.setAttribute("type", "content");
+ gBrowserElement.setAttribute("src", "file_bug596600.html");
+ gBrowserElement.style.width = "100px";
+ gBrowserElement.style.height = "100px";
+ gBrowserElement.style.margin = "50px";
+ gLeftWindow.document.documentElement.appendChild(gBrowserElement);
+ gBrowserElement.addEventListener("load", async () => {
+ await test1();
+ await test2();
+ gRightWindow.close();
+ gLeftWindow.close();
+ SimpleTest.finish();
+ }, { capture: true, once: true });
+}
+
+async function test1() {
+ // gRightWindow is active, gLeftWindow is inactive.
+ info(`Synthesizing native "mousemove" event at top-left of the screen...`);
+ await promiseNativeMouseEvent({
+ type: "mousemove",
+ screenX: 0,
+ screenY: 0,
+ scale: "inScreenPixels",
+ });
+ await new Promise(resolve => SimpleTest.executeSoon(resolve));
+
+ // Move into the left window
+ info(`Synthesizing native "mousemove" event in the left window (but outside the content)...`);
+ await promiseNativeMouseEventAndWaitForEvent({
+ type: "mousemove",
+ target: gBrowserElement,
+ offsetX: -20,
+ offsetY: -20,
+ win: gLeftWindow,
+ eventTypeToWait: "mouseover",
+ eventTargetToListen: gLeftWindow,
+ });
+ ok(true, `"mouseover" event is fired on the left window when cursor is moved into it`);
+
+ // Move over the browser
+ info(`Synthesizing native "mousemove" event on the content in the left window...`);
+ await promiseNativeMouseEventAndWaitForEvent({
+ type: "mousemove",
+ target: gBrowserElement,
+ atCenter: true,
+ win: gLeftWindow,
+ eventTypeToWait: "mouseout",
+ eventTargetToListen: gLeftWindow,
+ });
+ ok(true, `"mouseout" event is fired on the left window when cursor is moved into its child browser`);
+}
+
+async function test2() {
+ // Make the browser cover the whole window.
+ gBrowserElement.style.margin = "0";
+ gBrowserElement.style.width = gBrowserElement.style.height = "200px";
+
+ // Add a box to the browser at the left edge.
+ var doc = gBrowserElement.contentDocument;
+ var box = doc.createElement("div");
+ box.setAttribute("id", "box");
+ box.style.position = "absolute";
+ box.style.left = "0";
+ box.style.top = "50px";
+ box.style.width = "100px";
+ box.style.height = "100px";
+ box.style.backgroundColor = "green";
+ doc.body.appendChild(box);
+
+ ok(!box.matches(":hover"), "Box shouldn't be hovered (since the mouse isn't over it and since it's in a non-clickthrough browser in a background window)");
+
+ // A function to waitForFocus and then wait for synthetic mouse
+ // events to happen. Note that those happen off the refresh driver,
+ // and happen after animation frame requests.
+ function changeFocusAndAwaitSyntheticMouse(winToFocus,
+ elementToWatchForMouseEventOn) {
+ return Promise.all([
+ new Promise(resolve => {
+ function mouseWatcher() {
+ elementToWatchForMouseEventOn.removeEventListener("mouseover",
+ mouseWatcher);
+ elementToWatchForMouseEventOn.removeEventListener("mouseout",
+ mouseWatcher);
+ SimpleTest.executeSoon(resolve);
+ }
+ elementToWatchForMouseEventOn.addEventListener("mouseover",
+ mouseWatcher);
+ elementToWatchForMouseEventOn.addEventListener("mouseout",
+ mouseWatcher);
+ }),
+ new Promise(resolve => SimpleTest.waitForFocus(resolve, winToFocus)),
+ ]);
+ }
+
+ // Move the mouse over the box.
+ info(`Synthesizing native "mousemove" event into the box...`);
+ await promiseNativeMouseEvent({
+ type: "mousemove",
+ target: box,
+ atCenter: true,
+ win: gLeftWindow,
+ });
+ await new Promise(resolve =>
+ requestAnimationFrame(() => SimpleTest.executeSoon(resolve))
+ );
+ // XXX We cannot guarantee that the native mousemouse have already handled here.
+ ok(!box.matches(":hover"), "Box shouldn't be hovered (since it's in a non-clickthrough browser in a background window)");
+
+ // Activate the left window.
+ info("Waiting the left window activated...");
+ await changeFocusAndAwaitSyntheticMouse(gLeftWindow, box);
+ ok(gBrowserElement.matches(":hover"), "browser should be hovered");
+ ok(box.matches(":hover"), "Box should be hovered");
+
+ // De-activate the window (by activating the right window).
+ info("Waiting the right window activated...");
+ await changeFocusAndAwaitSyntheticMouse(gRightWindow, box);
+ ok(!gBrowserElement.matches(":hover"), "browser shouldn't be hovered");
+ ok(!box.matches(":hover"), "Box shouldn't be hovered");
+
+ // Re-activate it.
+ info("Waiting the left window activated again...");
+ await changeFocusAndAwaitSyntheticMouse(gLeftWindow, box);
+ ok(gBrowserElement.matches(":hover"), "browser should be hovered");
+ ok(box.matches(":hover"), "Box should be hovered");
+
+ // Unhover the box and the left window.
+ info(`Synthesizing native "mousemove" event outside the box and the left window...`);
+ await promiseNativeMouseEventAndWaitForEvent({
+ type: "mousemove",
+ screenX: 0,
+ screenY: 0,
+ scale: "inScreenPixels",
+ win: gLeftWindow,
+ eventTargetToListen: box,
+ eventTypeToWait: "mouseout",
+ });
+ await new Promise(resolve =>
+ requestAnimationFrame(() => SimpleTest.executeSoon(resolve))
+ );
+
+ ok(!gBrowserElement.matches(":hover"), "browser shouldn't be hovered");
+ ok(!box.matches(":hover"), "box shouldn't be hovered");
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(openWindows);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug673301.xhtml b/widget/tests/test_bug673301.xhtml
new file mode 100644
index 0000000000..663f18397e
--- /dev/null
+++ b/widget/tests/test_bug673301.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 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display"></p>
+<div id="content" style="display: none"/>
+</body>
+
+<script type="application/javascript">
+function getLoadContext() {
+ return window.docShell.QueryInterface(Ci.nsILoadContext);
+}
+
+var transferable = Cc['@mozilla.org/widget/transferable;1']
+ .createInstance(Ci.nsITransferable);
+transferable.init(getLoadContext());
+
+transferable.addDataFlavor("text/plain");
+transferable.setTransferData("text/plain", document);
+
+Services.clipboard.setData(transferable, null, Ci.nsIClipboard.kGlobalClipboard);
+
+transferable.setTransferData("text/plain", null);
+
+SimpleTest.ok(true, "Didn't crash setting non-text data for text/plain type");
+</script>
+</window>
diff --git a/widget/tests/test_bug760802.xhtml b/widget/tests/test_bug760802.xhtml
new file mode 100644
index 0000000000..383a2be8b4
--- /dev/null
+++ b/widget/tests/test_bug760802.xhtml
@@ -0,0 +1,83 @@
+<?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=760802
+-->
+<window title="Mozilla Bug 760802"
+ 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=760802"
+ target="_blank">Mozilla Bug 760802</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"/>
+ <iframe id="iframe_not_editable" width="300" height="150"
+ src="data:text/html,&lt;html&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;"/><br/>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+function getBaseWindowInterface(win) {
+ return win.docShell
+ .treeOwner
+ .nsIBaseWindow;
+}
+
+function getBaseWindowInterfaceFromDocShell(win) {
+ return win.docShell.QueryInterface(Ci.nsIBaseWindow);
+}
+
+function shouldThrowException(fun, exception) {
+ try {
+ fun.call();
+ return false;
+ } catch (e) {
+ // eslint-disable-next-line no-unsanitized/property
+ $("display").innerHTML += "<br/>OK thrown: "+e.message;
+ return (e instanceof Components.Exception &&
+ e.result === exception)
+ }
+}
+function doesntThrowException(fun) {
+ return !shouldThrowException(fun);
+}
+
+var baseWindow = getBaseWindowInterface(this);
+var nativeHandle = baseWindow.nativeHandle;
+// eslint-disable-next-line no-unsanitized/property
+$("display").innerHTML = "found nativeHandle for this window: "+nativeHandle;
+
+var win = Services.wm.getMostRecentWindow("navigator:browser");
+let docShell = getBaseWindowInterfaceFromDocShell(win);
+
+ok(
+ shouldThrowException(function(){docShell.nativeHandle;},
+ Cr.NS_ERROR_NOT_IMPLEMENTED),
+ "nativeHandle should not be implemented for nsDocShell"
+);
+
+ok(typeof(nativeHandle) === "string", "nativeHandle should be a string");
+ok(nativeHandle.match(/^0x[0-9a-f]+$/), "nativeHandle should have a memory address format");
+
+var iWin = document.getElementById("iframe_not_editable").contentWindow;
+is(getBaseWindowInterface(iWin).nativeHandle, nativeHandle,
+ "the nativeHandle of an iframe should be its parent's nativeHandle");
+
+var dialog = win.openDialog("data:text/plain,this is an active window.", "_blank",
+ "chrome,dialog=yes,width=100,height=100");
+
+isnot(getBaseWindowInterface(dialog).nativeHandle, "",
+ "the nativeHandle of a dialog should not be empty");
+
+dialog.close();
+
+todo(false, "the nativeHandle of a window without a mainWidget should be empty"); // how to build a window without a mainWidget ?
+
+SimpleTest.finish();
+ ]]></script>
+</window>
diff --git a/widget/tests/test_clipboard.xhtml b/widget/tests/test_clipboard.xhtml
new file mode 100644
index 0000000000..9d1afe2432
--- /dev/null
+++ b/widget/tests/test_clipboard.xhtml
@@ -0,0 +1,111 @@
+<?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=948065
+-->
+<window title="Mozilla Bug 948065"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="clipboard_helper.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ </body>
+
+ <!-- test code goes here -->
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+
+ function getLoadContext() {
+ return window.docShell.QueryInterface(Ci.nsILoadContext);
+ }
+
+ // Get clipboard data to paste.
+ function paste(clipboard) {
+ let trans = Cc['@mozilla.org/widget/transferable;1']
+ .createInstance(Ci.nsITransferable);
+ trans.init(getLoadContext());
+ trans.addDataFlavor("text/plain");
+ clipboard.getData(trans, Ci.nsIClipboard.kGlobalClipboard);
+ let str = {};
+ try {
+ trans.getTransferData('text/plain', str);
+ } catch (e) {
+ str = '';
+ }
+ if (str) {
+ str = str.value.QueryInterface(Ci.nsISupportsString);
+ if (str) {
+ str = str.data;
+ }
+ }
+ return str;
+ }
+
+ add_setup(function init() {
+ cleanupAllClipboard();
+ });
+
+ /* Test for bug 948065 */
+ add_task(function test_copy() {
+ // Test copy.
+ const data = "random number: " + Math.random();
+ let helper = Cc['@mozilla.org/widget/clipboardhelper;1']
+ .getService(Ci.nsIClipboardHelper);
+ helper.copyString(data);
+ is(paste(Services.clipboard), data, 'Data was successfully copied.');
+
+ Services.clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard);
+ is(paste(Services.clipboard), '', 'Data was successfully cleared.');
+
+ cleanupAllClipboard();
+ });
+
+ /* Tests for bug 1834073 */
+ clipboardTypes.forEach(function(clipboardType) {
+ if (clipboard.isClipboardTypeSupported(clipboardType)) {
+ add_task(function test_clipboard_apis() {
+ info(`Test clipboard apis for type ${clipboardType}`);
+
+ // Set clipboard data
+ let str;
+ try {
+ str = writeRandomStringToClipboard('text/plain', clipboardType)
+ } catch(e) {
+ ok(false, `setData should not throw error for clipboard type ${clipboardType}`);
+ }
+
+ // Test hasDataMatchingFlavors
+ try {
+ // We only support the set operation on kSelectionCache type, see
+ // bug 1835059.
+ is(clipboard.hasDataMatchingFlavors(['text/plain'], clipboardType),
+ clipboardType != clipboard.kSelectionCache,
+ `Test hasDataMatchingFlavors for clipboard type ${clipboardType}`);
+ } catch(e) {
+ ok(false, `hasDataMatchingFlavors should not throw error for clipboard type ${clipboardType}`);
+ }
+
+ // Test getData
+ let isThrow = false;
+ try {
+ is(getClipboardData('text/plain', clipboardType), str,
+ `Test getData for clipboard type ${clipboardType}`);
+ } catch(e) {
+ isThrow = true;
+ }
+ // We only support the set operation on kSelectionCache type, see
+ // bug 1835059.
+ is(isThrow, clipboardType == clipboard.kSelectionCache,
+ `Test if getData throw an error for clipboard type ${clipboardType}`);
+ });
+ }
+ });
+
+ ]]>
+ </script>
+</window>
diff --git a/widget/tests/test_clipboard_asyncSetData.xhtml b/widget/tests/test_clipboard_asyncSetData.xhtml
new file mode 100644
index 0000000000..27f5fad58c
--- /dev/null
+++ b/widget/tests/test_clipboard_asyncSetData.xhtml
@@ -0,0 +1,173 @@
+<?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=1712122
+-->
+<window title="Mozilla Bug 1712122"
+ 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">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+
+<!-- test code goes here -->
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+const clipboard = SpecialPowers.Services.clipboard;
+const clipboardTypes = [
+ clipboard.kGlobalClipboard,
+ clipboard.kSelectionClipboard,
+ clipboard.kFindClipboard,
+];
+
+function generateRandomString() {
+ return "random number: " + Math.random();
+}
+
+function generateNewTransferable(aFlavor, aStr) {
+ let trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
+ trans.init(null);
+ trans.addDataFlavor(aFlavor);
+
+ let supportsStr = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+ supportsStr.data = aStr;
+ trans.setTransferData(aFlavor, supportsStr);
+
+ return trans;
+}
+
+function getClipboardData(aFlavor, aClipboardType) {
+ var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
+ trans.init(null);
+ trans.addDataFlavor(aFlavor);
+ clipboard.getData(trans, aClipboardType);
+
+ var data = {};
+ trans.getTransferData(aFlavor, data);
+ return data.value.QueryInterface(SpecialPowers.Ci.nsISupportsString).data;
+}
+
+function setClipboardData(aFlavor, aStr, aClipboardType, aAsync) {
+ let trans = generateNewTransferable(aFlavor, aStr);
+ if (aAsync) {
+ let request = clipboard.asyncSetData(aClipboardType);
+ request.setData(trans, null);
+ return;
+ }
+
+ clipboard.setData(trans, null, aClipboardType);
+}
+
+function cleanupAllClipboard() {
+ clipboardTypes.forEach(function(type) {
+ if (clipboard.isClipboardTypeSupported(type)) {
+ info(`Cleanup clipboard ${type}`);
+ clipboard.emptyClipboard(type);
+ }
+ });
+}
+
+clipboardTypes.forEach(function(type) {
+ if (clipboard.isClipboardTypeSupported(type)) {
+ clipboardTypes.forEach(function(otherType) {
+ if (clipboard.isClipboardTypeSupported(otherType)) {
+ [true, false].forEach(function(async) {
+ add_task(async function test_clipboard_pending_asyncSetData() {
+ info(`Test having a pending asyncSetData request on ${type} and then make a new ${async ? "asyncSetData" : "setData"} request on ${otherType}`);
+
+ // Create a pending asyncSetData request
+ let priorResult;
+ let priorRequest = clipboard.asyncSetData(type, {
+ QueryInterface: ChromeUtils.generateQI(["nsIAsyncSetClipboardDataCallback"]),
+ onComplete(rv) {
+ priorResult = rv;
+ },
+ });
+
+ // Create a new request
+ let str = generateRandomString();
+ setClipboardData("text/plain", str, otherType, async);
+
+ if (type === otherType) {
+ info("The new request should cancel the prior pending request");
+ is(priorResult, Cr.NS_ERROR_ABORT, "The pending asyncSetData request should be canceled");
+ try {
+ priorRequest.setData(generateNewTransferable("text/plain", generateRandomString()));
+ ok(false, "An error should be thrown if setData is called on a canceled clipboard request");
+ } catch (e) {
+ is(e.result, Cr.NS_ERROR_FAILURE, "An error should be thrown if setData is called on a canceled clipboard request");
+ }
+ } else {
+ info("The new request should be used to cancel the prior pending request for different clipboard types");
+ is(priorResult, undefined, "The pending asyncSetData request should not be canceled");
+ str = generateRandomString();
+ priorRequest.setData(generateNewTransferable("text/plain", str), null);
+ try {
+ priorRequest.setData(generateNewTransferable("text/plain", generateRandomString()));
+ ok(false, "Calling setData multiple times should throw an error");
+ } catch(e) {
+ is(e.result, Cr.NS_ERROR_FAILURE, "Calling setData multiple times should throw an error");
+ }
+ }
+
+ // Test clipboard data.
+ is(getClipboardData("text/plain", type), str, `Test clipboard data for type ${type}`);
+
+ // Clean clipboard data.
+ cleanupAllClipboard();
+ });
+ });
+ }
+ });
+
+ add_task(async function test_clipboard_asyncSetData_abort() {
+ info(`Test abort asyncSetData request on ${type}`);
+
+ // Create a pending asyncSetData request
+ let result;
+ let request = clipboard.asyncSetData(type, {
+ QueryInterface: ChromeUtils.generateQI(["nsIAsyncSetClipboardDataCallback"]),
+ onComplete(rv) {
+ result = rv;
+ },
+ });
+
+ // Abort with NS_OK.
+ try {
+ request.abort(Cr.NS_OK);
+ ok(false, "Throw an error when attempting to abort with NS_OK");
+ } catch(e) {
+ is(e.result, Cr.NS_ERROR_FAILURE, "Should throw an error when attempting to abort with NS_OK");
+ }
+ is(result, undefined, "The asyncSetData request should not be canceled");
+
+ // Abort with NS_ERROR_ABORT.
+ request.abort(Cr.NS_ERROR_ABORT);
+ is(result, Cr.NS_ERROR_ABORT, "The asyncSetData request should be canceled");
+ try {
+ request.abort(Cr.NS_ERROR_FAILURE);
+ ok(false, "Throw an error when attempting to abort again");
+ } catch(e) {
+ is(e.result, Cr.NS_ERROR_FAILURE, "Should throw an error when attempting to abort again");
+ }
+ is(result, Cr.NS_ERROR_ABORT, "The callback should not be notified again");
+
+ try {
+ request.setData(generateNewTransferable("text/plain", generateRandomString()));
+ ok(false, "An error should be thrown if setData is called on a canceled clipboard request");
+ } catch (e) {
+ is(e.result, Cr.NS_ERROR_FAILURE, "An error should be thrown if setData is called on a canceled clipboard request");
+ }
+ });
+ }
+});
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/test_clipboard_cache.xhtml b/widget/tests/test_clipboard_cache.xhtml
new file mode 100644
index 0000000000..39cf8113dd
--- /dev/null
+++ b/widget/tests/test_clipboard_cache.xhtml
@@ -0,0 +1,56 @@
+<?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=1812543
+-->
+<window title="Mozilla Bug 1812543"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript" src="clipboard_helper.js"/>
+
+<!-- test results are displayed in the html:body -->
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+
+<!-- test code goes here -->
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+let supportOtherClipboardTypes = false;
+clipboardTypes.forEach(function(type) {
+ if (clipboard.kGlobalClipboard != type && clipboard.isClipboardTypeSupported(type)) {
+ supportOtherClipboardTypes = true;
+ add_task(function test_clipboard_hasDataMatchingFlavors() {
+ info(`Test write data to clipboard type ${type}`);
+
+ // Write text/plain data to main clipboard.
+ writeRandomStringToClipboard("text/plain", clipboard.kGlobalClipboard);
+ ok(clipboard.hasDataMatchingFlavors(["text/plain"], clipboard.kGlobalClipboard),
+ "Should have text/plain flavor");
+ ok(!clipboard.hasDataMatchingFlavors(["text/html"], clipboard.kGlobalClipboard),
+ "Should not have text/html flavor");
+
+ // Write text/html data to other clipboard.
+ writeRandomStringToClipboard("text/html", type);
+ ok(clipboard.hasDataMatchingFlavors(["text/plain"], clipboard.kGlobalClipboard),
+ "Should have text/plain flavor");
+ ok(!clipboard.hasDataMatchingFlavors(["text/html"], clipboard.kGlobalClipboard),
+ "Should not have text/html flavor");
+
+ // Clean clipboard data.
+ cleanupAllClipboard();
+ });
+ }
+});
+
+if (!supportOtherClipboardTypes) {
+ ok(true, "Don't support other clipboard types, skip tests");
+}
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/test_clipboard_owner.xhtml b/widget/tests/test_clipboard_owner.xhtml
new file mode 100644
index 0000000000..4fd530d8ff
--- /dev/null
+++ b/widget/tests/test_clipboard_owner.xhtml
@@ -0,0 +1,80 @@
+<?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=1812078
+-->
+<window title="Mozilla Bug 1812078"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript" src="clipboard_helper.js"/>
+
+<!-- test results are displayed in the html:body -->
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+
+<!-- test code goes here -->
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+function testClipboardOwner(aClipboardType, aAsync) {
+ let losingOwnership = false;
+ const clipboardOwner = {
+ QueryInterface: ChromeUtils.generateQI(["nsIClipboardOwner"]),
+ // nsIClipboardOwner
+ LosingOwnership(aTransferable) {
+ losingOwnership = true;
+ },
+ };
+
+ add_task(function test_clipboard_owner() {
+ info(`Test clipboard owner for type ${aClipboardType} ${aAsync ? "async" : ""}`);
+
+ // Setup clipboard owner.
+ writeRandomStringToClipboard("text/plain", aClipboardType, clipboardOwner, aAsync);
+
+ // Test should not lose ownership.
+ clipboardTypes.forEach(function(otherType) {
+ losingOwnership = false;
+ if (aClipboardType != otherType && clipboard.isClipboardTypeSupported(otherType)) {
+ // Test setting clipboard data.
+ writeRandomStringToClipboard("text/plain", otherType);
+ ok(!losingOwnership, `Should not lose ownership while setting data to type ${otherType}`);
+
+ // Test async setting clipboard data.
+ writeRandomStringToClipboard("text/plain", otherType, null, true);
+ ok(!losingOwnership, `Should not lose ownership while async setting data to type ${otherType}`);
+ }
+ });
+
+ // Test whether should lose ownership.
+ losingOwnership = false;
+ writeRandomStringToClipboard("text/plain", aClipboardType, clipboardOwner);
+ ok(losingOwnership, `Should lose ownership while setting data to type ${aClipboardType}`);
+
+ losingOwnership = false;
+ writeRandomStringToClipboard("text/plain", aClipboardType, null, true);
+ ok(losingOwnership, `Should lose ownership while async setting data to type ${aClipboardType}`);
+
+ // Clean clipboard data.
+ cleanupAllClipboard();
+ });
+}
+
+/** Test for Bug 1812078 */
+clipboardTypes.forEach(function(testType) {
+ if (clipboard.isClipboardTypeSupported(testType)) {
+ // Test sync set clipboard data.
+ testClipboardOwner(testType, false);
+
+ // Test async set clipboard data.
+ testClipboardOwner(testType, true);
+ }
+});
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/test_composition_text_querycontent.xhtml b/widget/tests/test_composition_text_querycontent.xhtml
new file mode 100644
index 0000000000..48b7af8100
--- /dev/null
+++ b/widget/tests/test_composition_text_querycontent.xhtml
@@ -0,0 +1,34 @@
+<?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="Testing composition, text and query content events"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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[
+
+// 3 assertions are: If setting selection with eSetSelection event whose range
+// is larger than the actual range, hits "Can only call this on frames that have
+// been reflowed:
+// '!(GetStateBits() & NS_FRAME_FIRST_REFLOW) || (GetParent()->GetStateBits() &
+// NS_FRAME_TOO_DEEP_IN_FRAME_TREE)'" in nsTextFrame.cpp.
+// Strangely, this doesn't occur with RDP on Windows.
+SimpleTest.expectAssertions(0, 3);
+SimpleTest.waitForExplicitFinish();
+window.openDialog("window_composition_text_querycontent.xhtml", "_blank",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/test_ime_state_in_contenteditable_on_readonly_change_in_parent.html b/widget/tests/test_ime_state_in_contenteditable_on_readonly_change_in_parent.html
new file mode 100644
index 0000000000..8d8662a8d8
--- /dev/null
+++ b/widget/tests/test_ime_state_in_contenteditable_on_readonly_change_in_parent.html
@@ -0,0 +1,72 @@
+<html>
+<head>
+ <title>Test for IME state of contenteditable on readonly state change</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="file_ime_state_test_helper.js"></script>
+ <script src="file_test_ime_state_in_contenteditable_on_readonly_change.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+<script>
+"use strict";
+
+/* import-globals-from file_ime_state_test_helper.js */
+/* import-globals-from file_test_ime_state_in_contenteditable_on_readonly_change.js */
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(async () => {
+ const tipWrapper = new TIPWrapper(window);
+ const editingHost = document.querySelector("div[contenteditable]");
+ await (async function test_ime_state_in_contenteditable_on_readonly_change() {
+ const tester = new IMEStateInContentEditableOnReadonlyChangeTester();
+ tester.checkResultOfPreparation(await tester.prepareToRun(editingHost, editingHost), window, tipWrapper);
+ tester.checkResultOfMakingHTMLEditorReadonly(await tester.runToMakeHTMLEditorReadonly());
+ tester.checkResultOfMakingHTMLEditorEditable(await tester.runToMakeHTMLEditorEditable());
+ tester.checkResultOfRemovingContentEditableAttribute(await tester.runToRemoveContentEditableAttribute());
+ tester.clear();
+ })();
+
+ await (async function test_ime_state_in_button_in_contenteditable_on_readonly_change() {
+ const tester = new IMEStateInContentEditableOnReadonlyChangeTester();
+ const button = editingHost.querySelector("button");
+ tester.checkResultOfPreparation(await tester.prepareToRun(editingHost, button), window, tipWrapper);
+ tester.checkResultOfMakingHTMLEditorReadonly(await tester.runToMakeHTMLEditorReadonly());
+ tester.checkResultOfMakingHTMLEditorEditable(await tester.runToMakeHTMLEditorEditable());
+ tester.checkResultOfRemovingContentEditableAttribute(await tester.runToRemoveContentEditableAttribute());
+ tester.clear();
+ })();
+
+ await (async function test_ime_state_of_text_controls_in_contenteditable_on_readonly_change() {
+ const tester = new IMEStateOfTextControlInContentEditableOnReadonlyChangeTester();
+ for (let index = 0;
+ index < IMEStateOfTextControlInContentEditableOnReadonlyChangeTester.numberOfTextControlTypes;
+ index++) {
+ tester.checkResultOfPreparation(await tester.prepareToRun(index, editingHost), window, tipWrapper);
+ tester.checkResultOfMakingParentEditingHost(await tester.runToMakeParentEditingHost());
+ tester.checkResultOfMakingHTMLEditorReadonly(await tester.runToMakeHTMLEditorReadonly());
+ tester.checkResultOfMakingHTMLEditorEditable(await tester.runToMakeHTMLEditorEditable());
+ tester.checkResultOfMakingParentNonEditable(await tester.runToMakeParentNonEditingHost());
+ tester.clear();
+ }
+ editingHost.setAttribute("contenteditable", "");
+ })();
+
+ await (async function test_ime_state_outside_contenteditable_on_readonly_change() {
+ const tester = new IMEStateOutsideContentEditableOnReadonlyChangeTester();
+ for (let index = 0;
+ index < IMEStateOutsideContentEditableOnReadonlyChangeTester.numberOfFocusTargets;
+ index++) {
+ tester.checkResultOfPreparation(await tester.prepareToRun(index, editingHost), window, tipWrapper);
+ tester.checkResultOfMakingParentEditingHost(await tester.runToMakeParentEditingHost());
+ tester.checkResultOfMakingHTMLEditorReadonly(await tester.runToMakeHTMLEditorReadonly());
+ tester.checkResultOfMakingHTMLEditorEditable(await tester.runToMakeHTMLEditorEditable());
+ tester.checkResultOfMakingParentNonEditable(await tester.runToMakeParentNonEditingHost());
+ tester.clear();
+ }
+ editingHost.setAttribute("contenteditable", "");
+ })();
+
+ SimpleTest.finish();
+});
+</script>
+</head>
+<body><div contenteditable><br><button>button</button></div></body>
+</html>
diff --git a/widget/tests/test_ime_state_in_plugin_in_parent.html b/widget/tests/test_ime_state_in_plugin_in_parent.html
new file mode 100644
index 0000000000..9f3892ab88
--- /dev/null
+++ b/widget/tests/test_ime_state_in_plugin_in_parent.html
@@ -0,0 +1,92 @@
+<html>
+<head>
+ <title>Test for IME state on plugin</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="file_ime_state_test_helper.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<input>
+<object type="application/x-test"></object>
+<script>
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(() => {
+ const tipWrapper = new TIPWrapper(window);
+ const plugin = document.querySelector("object");
+
+ // Plugins are not supported and their elements should not accept focus;
+ // therefore, IME should not enable when we play with it.
+
+ document.activeElement?.blur();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "IME enabled state should be disabled when no element has focus"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "IME should not have focus when no element has focus"
+ );
+
+ plugin.focus();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "IME enabled state should be disabled when an <object> for plugin has focus"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "IME should not have focus when an <object> for plugin has focus"
+ );
+
+ plugin.blur();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "IME enabled state should be disabled when an <object> for plugin gets blurred"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "IME should not have focus when an <object> for plugin gets blurred"
+ );
+
+ plugin.focus();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "IME enabled state should be disabled when an <object> for plugin gets focused again"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "IME should not have focus when an <object> for plugin gets focused again"
+ );
+
+ plugin.remove();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "IME enabled state should be disabled when focused <object> for plugin is removed from the document"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "IME should not have focus when focused <object> for plugin is removed from the document"
+ );
+
+ document.querySelector("input").focus();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ "IME enabled state should be enabled after <input> gets focus"
+ );
+ ok(
+ tipWrapper.IMEHasFocus,
+ "IME should have focus after <input> gets focus"
+ );
+
+ SimpleTest.finish();
+});
+</script>
+</body>
+</html>
diff --git a/widget/tests/test_ime_state_in_text_control_on_reframe_in_parent.html b/widget/tests/test_ime_state_in_text_control_on_reframe_in_parent.html
new file mode 100644
index 0000000000..ab38806261
--- /dev/null
+++ b/widget/tests/test_ime_state_in_text_control_on_reframe_in_parent.html
@@ -0,0 +1,42 @@
+<html>
+<head>
+ <title>Test for IME state of contenteditable on readonly state change</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="file_ime_state_test_helper.js"></script>
+ <script src="file_test_ime_state_in_text_control_on_reframe.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<script>
+"use strict";
+
+/* import-globals-from file_ime_state_test_helper.js */
+/* import-globals-from file_test_ime_state_in_text_control_on_reframe.js */
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(async () => {
+ const tipWrapper = new TIPWrapper(window);
+ const tester = new IMEStateInTextControlOnReframeTester();
+ for (let index = 0;
+ index < IMEStateInTextControlOnReframeTester.numberOfTextControlTypes;
+ index++) {
+ tipWrapper.clearFocusBlurNotifications();
+ const expectedData1 = await tester.prepareToRun(index, document);
+ tipWrapper.typeA();
+ await new Promise(resolve => requestAnimationFrame(
+ () => requestAnimationFrame(resolve)
+ )); // Flush IME content observer notifications.
+ tester.checkResultAfterTypingA(expectedData1, window, tipWrapper);
+
+ const expectedData2 = await tester.prepareToRun2(index, document);
+ tipWrapper.typeA();
+ await new Promise(resolve => requestAnimationFrame(
+ () => requestAnimationFrame(resolve)
+ )); // Flush IME content observer notifications.
+ tester.checkResultAfterTypingA2(expectedData2);
+ }
+
+ SimpleTest.finish();
+});
+</script>
+<body></body>
+</html>
diff --git a/widget/tests/test_ime_state_on_editable_state_change_in_parent.html b/widget/tests/test_ime_state_on_editable_state_change_in_parent.html
new file mode 100644
index 0000000000..a1b307a51f
--- /dev/null
+++ b/widget/tests/test_ime_state_on_editable_state_change_in_parent.html
@@ -0,0 +1,263 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for IME state management at changing editable state</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="file_ime_state_test_helper.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<div></div>
+<script>
+"use strict";
+
+/* import-globals-from file_ime_state_test_helper.js */
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(async () => {
+ const tipWrapper = new TIPWrapper(window);
+
+ function waitForIMEContentObserverSendingNotifications() {
+ return new Promise(
+ resolve => requestAnimationFrame(
+ () => requestAnimationFrame(resolve)
+ )
+ );
+ }
+
+ function resetIMEStateWithFocusMove() {
+ const input = document.createElement("input");
+ document.body.appendChild(input);
+ input.focus();
+ input.remove();
+ return waitForIMEContentObserverSendingNotifications();
+ }
+
+ await (async function test_setting_contenteditable_of_focused_div() {
+ const div = document.querySelector("div");
+ div.setAttribute("tabindex", "0");
+ div.focus();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "test_setting_contenteditable_of_focused_div: IME should be disabled when non-editable <div> has focus"
+ );
+ div.setAttribute("contenteditable", "");
+ await waitForIMEContentObserverSendingNotifications();
+ // Sometimes, it's not enough waiting only 2 animation frames here to wait
+ // for IME focus, perhaps, it may be related to HTMLEditor initialization.
+ // Let's wait one more animation frame here.
+ if (!tipWrapper.IMEHasFocus) {
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ }
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ "test_setting_contenteditable_of_focused_div: IME should be enabled when contenteditable of focused <div> is set"
+ );
+ ok(
+ tipWrapper.IMEHasFocus,
+ "test_setting_contenteditable_of_focused_div: IME should have focus when contenteditable of focused <div> is set"
+ )
+ div.removeAttribute("contenteditable");
+ await waitForIMEContentObserverSendingNotifications();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "test_setting_contenteditable_of_focused_div: IME should be disabled when contenteditable of focused <div> is removed"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "test_setting_contenteditable_of_focused_div: IME should not have focus when contenteditable of focused <div> is removed"
+ );
+ div.removeAttribute("tabindex");
+ })();
+
+ await resetIMEStateWithFocusMove();
+
+ await (async function test_removing_contenteditable_of_non_last_editable_div() {
+ const div = document.querySelector("div");
+ div.setAttribute("tabindex", "0");
+ div.setAttribute("contenteditable", "");
+ const anotherEditableDiv = document.createElement("div");
+ anotherEditableDiv.setAttribute("contenteditable", "");
+ div.parentElement.appendChild(anotherEditableDiv);
+ div.focus();
+ await waitForIMEContentObserverSendingNotifications();
+ div.removeAttribute("contenteditable");
+ await waitForIMEContentObserverSendingNotifications();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ "test_removing_contenteditable_of_non_last_editable_div: IME should be disabled when contenteditable of focused <div> is removed"
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ "test_removing_contenteditable_of_non_last_editable_div: IME should not have focus when contenteditable of focused <div> is removed"
+ );
+ anotherEditableDiv.remove();
+ div.removeAttribute("tabindex");
+ })();
+
+ await resetIMEStateWithFocusMove();
+
+ await (async function test_setting_designMode() {
+ window.focus();
+ document.designMode = "on";
+ await waitForIMEContentObserverSendingNotifications();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ 'test_setting_designMode: IME should be enabled when designMode is set to "on"'
+ );
+ ok(
+ tipWrapper.IMEHasFocus,
+ 'test_setting_designMode: IME should have focus when designMode is set to "on"'
+ );
+ document.designMode = "off";
+ await waitForIMEContentObserverSendingNotifications();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ 'test_setting_designMode: IME should be disabled when designMode is set to "off"'
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ 'test_setting_designMode: IME should not have focus when designMode is set to "off"'
+ );
+ })();
+
+ await resetIMEStateWithFocusMove();
+
+ async function test_setting_content_editable_of_body_when_shadow_DOM_has_focus(aMode) {
+ const div = document.querySelector("div");
+ const shadow = div.attachShadow({mode: aMode});
+ const divInShadow = document.createElement("div");
+ divInShadow.setAttribute("tabindex", "0");
+ shadow.appendChild(divInShadow);
+ divInShadow.focus();
+ await waitForIMEContentObserverSendingNotifications();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `test_setting_content_editable_of_body_when_shadow_DOM_has_focus(${
+ aMode
+ }): IME should be disabled when non-editable <div> in a shadow DOM has focus`
+ );
+ document.body.setAttribute("contenteditable", "");
+ await waitForIMEContentObserverSendingNotifications();
+ // todo_is because of bug 1807597. Gecko does not update focus when focused
+ // element becomes an editable child. Therefore, cannot initialize
+ // HTMLEditor with the new editing host.
+ todo_is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ `test_setting_content_editable_of_body_when_shadow_DOM_has_focus(${
+ aMode
+ }): IME should be enabled when the <body> becomes editable`
+ );
+ todo(
+ tipWrapper.IMEHasFocus,
+ `test_setting_content_editable_of_body_when_shadow_DOM_has_focus(${
+ aMode
+ }): IME should have focus when the <body> becomes editable`
+ );
+ document.body.removeAttribute("contenteditable");
+ await waitForIMEContentObserverSendingNotifications();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `test_setting_content_editable_of_body_when_shadow_DOM_has_focus)${
+ aMode
+ }): IME should be disabled when the <body> becomes not editable`
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ `test_setting_content_editable_of_body_when_shadow_DOM_has_focus)${
+ aMode
+ }): IME should not have focus when the <body> becomes not editable`
+ );
+ div.remove();
+ document.body.appendChild(document.createElement("div"));
+ };
+
+ async function test_setting_designMode_when_shadow_DOM_has_focus(aMode) {
+ const div = document.querySelector("div");
+ const shadow = div.attachShadow({mode: aMode});
+ const divInShadow = document.createElement("div");
+ divInShadow.setAttribute("tabindex", "0");
+ shadow.appendChild(divInShadow);
+ divInShadow.focus();
+ await waitForIMEContentObserverSendingNotifications();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${
+ aMode
+ }): IME should be disabled when non-editable <div> in a shadow DOM has focus`
+ );
+ document.designMode = "on";
+ await waitForIMEContentObserverSendingNotifications();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${
+ aMode
+ }): IME should stay disabled when designMode is set`
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${
+ aMode
+ }): IME should not have focus when designMode is set`
+ );
+ divInShadow.setAttribute("contenteditable", "");
+ await waitForIMEContentObserverSendingNotifications();
+ // todo_is because of bug 1807597. Gecko does not update focus when focused
+ // document is into the design mode. Therefore, cannot initialize
+ // HTMLEditor with the document node properly.
+ todo_is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${
+ aMode
+ }): IME should be enabled when focused <div> in a shadow DOM becomes editable`
+ );
+ todo(
+ tipWrapper.IMEHasFocus,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${
+ aMode
+ }): IME should have focus when focused <div> in a shadow DOM becomes editable`
+ );
+ document.designMode = "off";
+ await waitForIMEContentObserverSendingNotifications();
+ is(
+ window.windowUtils.IMEStatus,
+ Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${
+ aMode
+ }): IME should be disabled when designMode is unset`
+ );
+ ok(
+ !tipWrapper.IMEHasFocus,
+ `test_setting_designMode_when_shadow_DOM_has_focus(${
+ aMode
+ }): IME should not have focus when designMode is unset`
+ );
+ div.remove();
+ document.body.appendChild(document.createElement("div"));
+ }
+
+ for (const mode of ["open", "closed"]) {
+ await test_setting_content_editable_of_body_when_shadow_DOM_has_focus(mode);
+ await resetIMEStateWithFocusMove();
+ await test_setting_designMode_when_shadow_DOM_has_focus(mode);
+ await resetIMEStateWithFocusMove();
+ }
+
+ SimpleTest.finish();
+});
+</script>
+</body>
+</html>
diff --git a/widget/tests/test_ime_state_on_focus_move_in_parent.html b/widget/tests/test_ime_state_on_focus_move_in_parent.html
new file mode 100644
index 0000000000..fd74d61c7e
--- /dev/null
+++ b/widget/tests/test_ime_state_on_focus_move_in_parent.html
@@ -0,0 +1,88 @@
+<!doctype html>
+<html style="ime-mode: disabled;">
+<head>
+ <meta charset="utf-8">
+ <title>Test for IME state management on focus move in parent process</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <script src="file_ime_state_test_helper.js"></script>
+ <script src="file_test_ime_state_on_focus_move.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body style="ime-mode: disabled;">
+<div style="ime-mode: disabled;"></div>
+<script>
+"use strict";
+
+/* import-globals-from file_ime_state_test_helper.js */
+/* import-globals-from file_test_ime_state_on_focus_move.js */
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(async () => {
+ const tipWrapper = new TIPWrapper(window);
+ ok(tipWrapper.isAvailable(), "TextInputProcessor should've been initialized");
+
+ const container = document.querySelector("div");
+ async function runIMEStateOnFocusMoveTests(aDescription) {
+ {
+ const runnerAndChecker = new IMEStateWhenNoActiveElementTester(aDescription);
+ const expectedData = await runnerAndChecker.run(document);
+ runnerAndChecker.check(expectedData);
+ }
+ for (let index = 0; index < IMEStateOnFocusMoveTester.numberOfTests; ++index) {
+ const runnerAndChecker = new IMEStateOnFocusMoveTester(aDescription, index);
+ const expectedData = await runnerAndChecker.prepareToRun(container);
+ runnerAndChecker.prepareToCheck(expectedData, tipWrapper);
+ await runnerAndChecker.run();
+ runnerAndChecker.check(expectedData);
+ if (runnerAndChecker.canTestOpenCloseState(expectedData)) {
+ for (const defaultOpenState of [false, true]) {
+ const expectedOpenStateData =
+ await runnerAndChecker.prepareToRunOpenCloseTest(container);
+ runnerAndChecker.prepareToCheckOpenCloseTest(
+ defaultOpenState,
+ expectedOpenStateData
+ );
+ await runnerAndChecker.runOpenCloseTest();
+ runnerAndChecker.checkOpenCloseTest(expectedOpenStateData);
+ }
+ }
+ runnerAndChecker.destroy();
+ }
+ }
+
+ // test for normal contents.
+ await runIMEStateOnFocusMoveTests("in non-editable container");
+
+ // test for contentEditable="true"
+ container.setAttribute("contenteditable", "true");
+ await runIMEStateOnFocusMoveTests("in div[contenteditable]");
+
+ // test for contentEditable="false"
+ container.setAttribute("contenteditable", "false");
+ await runIMEStateOnFocusMoveTests('in div[contenteditable="false"]');
+
+ // test for removing contentEditable
+ container.setAttribute("contenteditable", "true");
+ container.focus();
+ await new Promise(resolve =>
+ requestAnimationFrame(
+ () => requestAnimationFrame(resolve)
+ )
+ );
+ container.removeAttribute("contenteditable");
+ await runIMEStateOnFocusMoveTests("after removing contenteditable from the container");
+
+ // test designMode
+ document.designMode = "on";
+ await runIMEStateOnFocusMoveTests('in designMode="on"');
+ document.designMode = "off";
+ await runIMEStateOnFocusMoveTests('in designMode="off"');
+
+ tipWrapper.destroy();
+
+ SimpleTest.finish();
+});
+</script>
+</body>
+</html>
diff --git a/widget/tests/test_ime_state_on_input_type_change_in_parent.html b/widget/tests/test_ime_state_on_input_type_change_in_parent.html
new file mode 100644
index 0000000000..2644c31e3f
--- /dev/null
+++ b/widget/tests/test_ime_state_on_input_type_change_in_parent.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+ <title>Test for IME state on input type change</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="file_ime_state_test_helper.js"></script>
+ <script src="file_test_ime_state_on_input_type_change.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+<script>
+"use strict";
+
+/* import-globals-from file_ime_state_test_helper.js */
+/* import-globals-from file_test_ime_state_on_input_type_change.js */
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.expectAssertions(6); // Hit in IMEStateManager::UpdateIMEState
+SimpleTest.waitForFocus(async () => {
+ const tipWrapper = new TIPWrapper(window);
+ for (let srcIndex = 0; srcIndex < IMEStateOnInputTypeChangeTester.numberOfTests; srcIndex++) {
+ const tester = new IMEStateOnInputTypeChangeTester(srcIndex);
+ for (let destIndex = 0; destIndex < IMEStateOnInputTypeChangeTester.numberOfTests; destIndex++) {
+ const expectedResultBefore = await tester.prepareToRun(destIndex, window, document.body);
+ if (expectedResultBefore === false) {
+ continue;
+ }
+ tester.checkBeforeRun(expectedResultBefore, tipWrapper);
+ const expectedResult = await tester.run();
+ tester.checkResult(expectedResultBefore, expectedResult);
+ tipWrapper.clearFocusBlurNotifications();
+ tester.clear();
+ }
+ }
+
+ SimpleTest.finish();
+});
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/widget/tests/test_ime_state_on_readonly_change_in_parent.html b/widget/tests/test_ime_state_on_readonly_change_in_parent.html
new file mode 100644
index 0000000000..0557856542
--- /dev/null
+++ b/widget/tests/test_ime_state_on_readonly_change_in_parent.html
@@ -0,0 +1,31 @@
+<html>
+<head>
+ <title>Test for IME state on readonly state change</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="file_ime_state_test_helper.js"></script>
+ <script src="file_test_ime_state_on_readonly_change.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+<script>
+"use strict";
+
+/* import-globals-from file_ime_state_test_helper.js */
+/* import-globals-from file_test_ime_state_on_readonly_change.js */
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(async () => {
+ const tipWrapper = new TIPWrapper(window);
+ const tester = new IMEStateOnReadonlyChangeTester();
+ for (let i = 0; i < IMEStateOnReadonlyChangeTester.numberOfTextControlTypes; i++) {
+ tester.checkBeforeRun(await tester.prepareToRun(i, window, document.body), tipWrapper);
+ tester.checkResultOfMakingTextControlReadonly(await tester.runToMakeTextControlReadonly());
+ tester.checkResultOfMakingTextControlEditable(await tester.runToMakeTextControlEditable());
+ tipWrapper.clearFocusBlurNotifications();
+ tester.clear();
+ }
+ SimpleTest.finish();
+});
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/widget/tests/test_ime_state_others_in_parent.html b/widget/tests/test_ime_state_others_in_parent.html
new file mode 100644
index 0000000000..e6ae0ab272
--- /dev/null
+++ b/widget/tests/test_ime_state_others_in_parent.html
@@ -0,0 +1,153 @@
+<html>
+<head>
+ <title>Test for IME state controlling in some special cases</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <script src="file_ime_state_test_helper.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="display"></div>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+var gUtils = window.windowUtils;
+var gFM = Services.focus;
+
+function runEditorFlagChangeTests() {
+ var description = "runEditorFlagChangeTests: ";
+
+ var container = document.getElementById("display");
+
+ // Reset selection from previous tests.
+ window.getSelection().collapse(container, 0);
+
+ // the editor has focus directly.
+ container.setAttribute("contenteditable", "true");
+ container.focus();
+
+ is(gFM.focusedElement, container,
+ description + "The editor doesn't get focus");
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+ description + "IME isn't enabled on HTML editor");
+ const kIMEStateChangeFlags = Ci.nsIEditor.eEditorReadonlyMask;
+ const kFlagsNotAllowedWithHTMLEditor =
+ Ci.nsIEditor.eEditorPasswordMask |
+ Ci.nsIEditor.eEditorSingleLineMask;
+ var editor = window.docShell.editor;
+ var flags = editor.flags;
+
+ // input characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3078\u3093\u3057\u3093",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ ],
+ },
+ "caret": { "start": 4, "length": 0 },
+ });
+
+ editor.flags &= ~kIMEStateChangeFlags;
+ ok(editor.composing,
+ description + "#1 IME composition was committed unexpectedly");
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+ description + "#1 IME isn't enabled on HTML editor");
+
+ editor.flags |=
+ ~(kIMEStateChangeFlags | kFlagsNotAllowedWithHTMLEditor);
+ ok(editor.composing,
+ description + "#2 IME composition was committed unexpectedly");
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+ description + "#2 IME isn't enabled on HTML editor");
+
+ editor.flags = flags;
+ ok(editor.composing,
+ description + "#3 IME composition was committed unexpectedly");
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+ description + "#3 IME isn't enabled on HTML editor");
+
+ // cancel the composition
+ synthesizeComposition({ type: "compositioncommit", data: "" });
+
+ container.removeAttribute("contenteditable");
+}
+
+function runEditableSubframeTests() {
+ window.open("window_imestate_iframes.html", "_blank",
+ "width=600,height=600");
+}
+
+function runTestPasswordFieldOnDialog() {
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+
+ var dialog;
+
+ function WindowObserver() {
+ Services.obs.addObserver(this, "domwindowopened");
+ }
+
+ WindowObserver.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ observe(subject, topic, data) {
+ if (topic === "domwindowopened") {
+ ok(true, "dialog window is created");
+ dialog = subject;
+ dialog.addEventListener("load", onPasswordDialogLoad);
+ }
+ },
+ };
+
+ var observer = new WindowObserver();
+ var arg1 = {}, arg2 = {};
+ Services.prompt.promptPassword(window, "title", "text", arg1, "msg", arg2);
+
+ ok(true, "password dialog was closed");
+
+ Services.obs.removeObserver(observer, "domwindowopened");
+
+ var passwordField;
+
+ function onPasswordDialogLoad() {
+ ok(true, "onPasswordDialogLoad is called");
+ dialog.removeEventListener("load", onPasswordDialogLoad);
+ passwordField = dialog.document.getElementById("password1Textbox");
+ passwordField.addEventListener("focus", onPasswordFieldFocus);
+ }
+
+ function onPasswordFieldFocus() {
+ ok(true, "onPasswordFieldFocus is called");
+ passwordField.removeEventListener("focus", onPasswordFieldFocus);
+ var utils = dialog.windowUtils;
+ is(utils.IMEStatus, utils.IME_STATUS_PASSWORD,
+ "IME isn't disabled on a password field of password dialog");
+ synthesizeKey("VK_ESCAPE", { }, dialog);
+ }
+}
+
+SimpleTest.waitForFocus(async () => {
+ // test whether the IME state and composition are not changed unexpectedly
+ runEditorFlagChangeTests();
+
+ // test password field on dialog
+ // XXX temporary disable against failure
+ // runTestPasswordFieldOnDialog();
+
+ // This will call onFinish(), so, this test must be the last.
+ // TODO: Make this test run with remote content too.
+ runEditableSubframeTests();
+});
+
+function onFinish() {
+ SimpleTest.finish();
+}
+</script>
+</body>
+</html>
diff --git a/widget/tests/test_input_events_on_deactive_window.xhtml b/widget/tests/test_input_events_on_deactive_window.xhtml
new file mode 100644
index 0000000000..d54699f76c
--- /dev/null
+++ b/widget/tests/test_input_events_on_deactive_window.xhtml
@@ -0,0 +1,233 @@
+<?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="Testing composition, text and query content events"
+ 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>
+<p id="display">
+ <textarea id="textarea"></textarea>
+</p>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests, window);
+
+var textarea = document.getElementById("textarea");
+var otherWindow;
+var timer;
+
+function runTests()
+{
+ textarea.focus();
+ is(Services.focus.focusedElement, textarea, "we're deactive");
+ if (Services.focus.focusedElement != textarea) {
+ SimpleTest.finish();
+ return;
+ }
+
+ otherWindow =
+ window.browsingContext.topChromeWindow.open(
+ "./file_input_events_on_deactive_window.html", "_blank",
+ "chrome,width=100,height=100");
+ ok(otherWindow, "failed to open other window");
+ if (!otherWindow) {
+ SimpleTest.finish();
+ return;
+ }
+
+ SimpleTest.waitForFocus(startTests, otherWindow);
+ otherWindow.focus();
+}
+
+function startTests()
+{
+ clearTimeout(timer);
+ isnot(Services.focus.focusedWindow, window, "we're not deactive");
+ if (Services.focus.focusedWindow == window) {
+ otherWindow.close();
+ SimpleTest.finish();
+ return;
+ }
+
+ var keydownHandled, keypressHandled, keyupHandled, compositionstartHandled,
+ compositionendHandled, compositionupdateHandled, inputHandled;
+
+ function clear()
+ {
+ keydownHandled = false;
+ keypressHandled = false;
+ keyupHandled = false;
+ compositionstartHandled = false;
+ compositionendHandled = false;
+ compositionupdateHandled = false;
+ inputHandled = false;
+ }
+
+ function onEvent(aEvent)
+ {
+ if (aEvent.type == "keydown") {
+ keydownHandled = true;
+ } else if (aEvent.type == "keypress") {
+ keypressHandled = true;
+ } else if (aEvent.type == "keyup") {
+ keyupHandled = true;
+ } else if (aEvent.type == "compositionstart") {
+ compositionstartHandled = true;
+ } else if (aEvent.type == "compositionend") {
+ compositionendHandled = true;
+ } else if (aEvent.type == "compositionupdate") {
+ compositionupdateHandled = true;
+ } else if (aEvent.type == "input") {
+ inputHandled = true;
+ } else {
+ ok(false, "handled unknown event: " + aEvent.type);
+ }
+ }
+
+ textarea.addEventListener("keydown", onEvent);
+ textarea.addEventListener("keypress", onEvent);
+ textarea.addEventListener("keyup", onEvent);
+ textarea.addEventListener("compositionstart", onEvent);
+ textarea.addEventListener("compositionend", onEvent);
+ textarea.addEventListener("compositionupdate", onEvent);
+ textarea.addEventListener("input", onEvent);
+
+ startTestsInternal();
+
+ function startTestsInternal()
+ {
+ // key events
+ function checkKeyEvents(aKeydown, aKeypress, aKeyup, aInput, aDescription)
+ {
+ is(keydownHandled, aKeydown,
+ "keydown event is (not) handled: " + aDescription);
+ is(keypressHandled, aKeypress,
+ "keypress event is (not) handled: " + aDescription);
+ is(keyupHandled, aKeyup,
+ "keyup event is (not) handled: " + aDescription);
+ is(inputHandled, aInput,
+ "input event is (not) handled: " + aDescription);
+ }
+
+ function checkCompositionEvents(aStart, aEnd, aUpdate, aInput, aDescription)
+ {
+ is(compositionstartHandled, aStart,
+ "compositionstart event is (not) handled: " + aDescription);
+ is(compositionendHandled, aEnd,
+ "compositionend event is (not) handled: " + aDescription);
+ is(compositionupdateHandled, aUpdate,
+ "compositionupdate event is (not) handled: " + aDescription);
+ is(inputHandled, aInput,
+ "input event is (not) handled: " + aDescription);
+ }
+
+ clear();
+ synthesizeKey("a", {type: "keydown"});
+ checkKeyEvents(true, true, false, true, "a keydown and a keypress");
+ is(textarea.value, "a", "textarea value isn't 'a'");
+ clear();
+ synthesizeKey("a", {type: "keyup"});
+ checkKeyEvents(false, false, true, false, "a keyup");
+ clear();
+ synthesizeKey("KEY_Backspace");
+ checkKeyEvents(true, true, true, true, "KEY_Backspace key events");
+ is(textarea.value, "", "textarea value isn't empty");
+
+ // IME events
+ clear();
+ // input first character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ checkCompositionEvents(true, false, true, true, "starting to compose");
+ var queryText = synthesizeQueryTextContent(0, 100);
+ ok(queryText, "query text event result is null");
+ if (!queryText) {
+ return;
+ }
+ ok(queryText.succeeded, "query text event failed");
+ if (!queryText.succeeded) {
+ return;
+ }
+ is(queryText.text, "\u3089", "composing text is incorrect");
+ var querySelectedText = synthesizeQuerySelectedText();
+ ok(querySelectedText, "query selected text event result is null");
+ if (!querySelectedText) {
+ return;
+ }
+ ok(querySelectedText.succeeded, "query selected text event failed");
+ if (!querySelectedText.succeeded) {
+ return;
+ }
+ is(querySelectedText.offset, 1,
+ "query selected text event returns wrong offset");
+ is(querySelectedText.text, "",
+ "query selected text event returns wrong selected text");
+ clear();
+ // commit composition
+ synthesizeComposition({ type: "compositioncommitasis" });
+ checkCompositionEvents(false, true, false, true, "commit composition as is");
+ queryText = synthesizeQueryTextContent(0, 100);
+ ok(queryText, "query text event result is null after commit");
+ if (!queryText) {
+ return;
+ }
+ ok(queryText.succeeded, "query text event failed after commit");
+ if (!queryText.succeeded) {
+ return;
+ }
+ is(queryText.text, "\u3089", "composing text is incorrect after commit");
+ querySelectedText = synthesizeQuerySelectedText();
+ ok(querySelectedText,
+ "query selected text event result is null after commit");
+ if (!querySelectedText) {
+ return;
+ }
+ ok(querySelectedText.succeeded,
+ "query selected text event failed after commit");
+ if (!querySelectedText.succeeded) {
+ return;
+ }
+ is(querySelectedText.offset, 1,
+ "query selected text event returns wrong offset after commit");
+ is(querySelectedText.text, "",
+ "query selected text event returns wrong selected text after commit");
+ clear();
+ }
+
+ textarea.removeEventListener("keydown", onEvent);
+ textarea.removeEventListener("keypress", onEvent);
+ textarea.removeEventListener("keyup", onEvent);
+ textarea.removeEventListener("compositionstart", onEvent);
+ textarea.removeEventListener("compositionupdate", onEvent);
+ textarea.removeEventListener("compositionend", onEvent);
+ textarea.removeEventListener("input", onEvent);
+
+ otherWindow.close();
+
+ SimpleTest.finish();
+}
+
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/test_key_event_counts.xhtml b/widget/tests/test_key_event_counts.xhtml
new file mode 100644
index 0000000000..6eda6a52fb
--- /dev/null
+++ b/widget/tests/test_key_event_counts.xhtml
@@ -0,0 +1,90 @@
+<?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"?>
+
+<!-- We've had issues on Mac OS X where native key events either don't get processed
+ or they get processed twice. This test tests some of those scenarios. -->
+
+<window id="window1" title="Test Key Event Counts" onload="runTest()"
+ 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 src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <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 type="application/javascript"><![CDATA[
+ var gKeyPressEventCount = 0;
+ var gKeyDownEventCount = 0;
+
+ function onKeyDown(e)
+ {
+ gKeyDownEventCount++;
+ }
+
+ function onKeyPress(e)
+ {
+ gKeyPressEventCount++;
+ e.preventDefault();
+ }
+
+ function* testBody()
+ {
+ window.addEventListener("keydown", onKeyDown);
+ window.addEventListener("keypress", onKeyPress);
+
+ // Test ctrl-tab
+ gKeyDownEventCount = 0;
+ gKeyPressEventCount = 0;
+ yield synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_Tab, {ctrlKey:1}, "\t", "\t", continueTest);
+ is(gKeyDownEventCount, 1);
+ is(gKeyPressEventCount, 0, "ctrl-tab should be consumed by tabbox of tabbrowser at keydown");
+
+ // Test cmd+shift+a
+ gKeyDownEventCount = 0;
+ gKeyPressEventCount = 0;
+ yield synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {metaKey:1, shiftKey:1}, "a", "A", continueTest);
+ is(gKeyDownEventCount, 1);
+ is(gKeyPressEventCount, 1);
+
+ // Test cmd-;
+ gKeyDownEventCount = 0;
+ gKeyPressEventCount = 0;
+ yield synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_Semicolon, {metaKey:1}, ";", ";", continueTest);
+ is(gKeyDownEventCount, 1);
+ is(gKeyPressEventCount, 1);
+
+ window.removeEventListener("keydown", onKeyDown);
+ window.removeEventListener("keypress", onKeyPress);
+ }
+
+ var gTestContinuation = null;
+
+ function continueTest()
+ {
+ if (!gTestContinuation) {
+ gTestContinuation = testBody();
+ }
+ var ret = gTestContinuation.next();
+ if (ret.done) {
+ SimpleTest.finish();
+ } else {
+ is(ret.value, true, "Key synthesized successfully");
+ }
+ }
+
+ function runTest()
+ {
+ SimpleTest.waitForExplicitFinish();
+ continueTest();
+ }
+ ]]></script>
+
+</window>
diff --git a/widget/tests/test_keycodes.xhtml b/widget/tests/test_keycodes.xhtml
new file mode 100644
index 0000000000..7dd2250f7d
--- /dev/null
+++ b/widget/tests/test_keycodes.xhtml
@@ -0,0 +1,5626 @@
+<?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 event 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/NativeKeyCodes.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+<commandset>
+ <command id="expectedCommand" oncommand="this.activeCount++" disabled="true"/>
+ <command id="unexpectedCommand" oncommand="this.activeCount++" disabled="true"/>
+ <command id="expectedReservedCommand" oncommand="this.activeCount++" reserved="true" disabled="true"/>
+</commandset>
+<keyset>
+ <key id="unshiftedKey" key=";" modifiers="accel" command="unexpectedCommand"/>
+ <key id="shiftedKey" key=":" modifiers="accel" command="unexpectedCommand"/>
+ <key id="commandOptionF" key='f' modifiers="accel,alt" command="unexpectedCommand"/>
+ <key id="question" key='?' modifiers="accel" command="unexpectedCommand"/>
+ <key id="unshiftedX" key="x" modifiers="accel" command="unexpectedCommand"/>
+ <key id="shiftedX" key="X" modifiers="accel,shift" command="unexpectedCommand"/>
+ <key id="ctrlAltA" key="A" modifiers="accel,alt" command="unexpectedCommand"/>
+ <key id="unshiftedPlus" key="+" modifiers="accel" command="unexpectedCommand"/>
+ <key id="reservedUnshiftedKey" key="'" modifiers="accel" command="unexpectedCommand"/>
+ <key id="reservedShiftedKey" key='"' modifiers="accel" command="unexpectedCommand"/>
+</keyset>
+
+<browser id="browser" type="content" src="data:text/html;charset=utf-8,&lt;button id='content_button'&gt;button&lt;/button&gt;" width="200" height="32"/>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display">
+ <!-- for some reason, if we don't have 'accesskey' here, adding it dynamically later
+ doesn't work! -->
+ <button id="button" accesskey="z">Hello</button>
+ <input type="text" id="textbox" value=""/>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+const IS_MAC = navigator.platform.indexOf("Mac") == 0;
+const IS_WIN = navigator.platform.indexOf("Win") == 0;
+const OS_VERSION =
+ IS_WIN ? parseFloat(Services.sysinfo.getProperty("version")) : 0;
+const WIN7 = 6.1;
+const WIN8 = 6.2;
+
+function isModifierKeyEvent(aEvent)
+{
+ switch (aEvent.key) {
+ case "Alt":
+ case "AltGraph":
+ case "CapsLock":
+ case "Control":
+ case "Fn":
+ case "FnLock":
+ case "Hyper":
+ case "Meta":
+ case "NumLock":
+ case "ScrollLock":
+ case "Shift":
+ case "Super":
+ case "Symbol":
+ case "SymbolLock":
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * Firefox infobar UI can have access keys which conflict with this test. Really
+ * stupid workaround until we can move this test into its own chrome window.
+ */
+function clearInfobars()
+{
+ var browser = window.top.docShell.chromeEventHandler;
+ var chromeWin = browser.ownerGlobal;
+ var nb = chromeWin.gBrowser.getNotificationBox(browser);
+ for (let n of nb.allNotifications) {
+ nb.removeNotification(n, true);
+ }
+}
+
+function eventToString(aEvent)
+{
+ var name = aEvent.layout.name + " keyCode=" +
+ aEvent.keyCode + " (0x" + aEvent.keyCode.toString(16).toUpperCase() +
+ ") chars='" + aEvent.chars + "'";
+ if (typeof aEvent.unmodifiedChars === "string") {
+ name += " unmodifiedChars='" + aEvent.unmodifiedChars + "'";
+ }
+ if (aEvent.modifiers.capsLockKey) {
+ name += " [CapsLock]";
+ }
+ if (aEvent.modifiers.shiftKey) {
+ name += " [Shift]";
+ }
+ if (aEvent.modifiers.shiftRightKey) {
+ name += " [Right Shift]";
+ }
+ if (aEvent.modifiers.ctrlKey) {
+ name += " [Ctrl]";
+ }
+ if (aEvent.modifiers.ctrlRightKey) {
+ name += " [Right Ctrl]";
+ }
+ if (aEvent.modifiers.altKey) {
+ name += " [Alt]";
+ }
+ if (aEvent.modifiers.altGrKey) {
+ name += " [AltGr]";
+ }
+ if (aEvent.modifiers.altRightKey) {
+ name += " [Right Alt]";
+ }
+ if (aEvent.modifiers.metaKey) {
+ name += " [Command]";
+ }
+ if (aEvent.modifiers.metaRightKey) {
+ name += " [Right Command]";
+ }
+
+ return name;
+}
+
+function getPhase(aDOMEvent)
+{
+ switch (aDOMEvent.eventPhase) {
+ case aDOMEvent.None:
+ return "none";
+ case aDOMEvent.CAPTURING_PHASE:
+ return "capture";
+ case aDOMEvent.AT_TARGET:
+ return "target";
+ case aDOMEvent.BUBBLING_PHASE:
+ return "bubble";
+ default:
+ return "";
+ }
+}
+
+function eventTargetToString(aEventTarget)
+{
+ if (aEventTarget.navigator) {
+ return "window";
+ }
+ switch (aEventTarget.nodeType) {
+ case Node.ELEMENT_NODE:
+ return "element (" + aEventTarget.tagName + ")";
+ case Node.DOCUMENT_NODE:
+ return "document";
+ default:
+ return "";
+ }
+}
+
+function synthesizeKey(aEvent, aFocusElementId, aCallback)
+{
+ if (aFocusElementId.startsWith("content_")) {
+ var browser = document.getElementById("browser");
+ browser.contentDocument.getElementById(aFocusElementId).focus();
+ } else {
+ document.getElementById(aFocusElementId).focus();
+ }
+
+ return synthesizeNativeKey(aEvent.layout, aEvent.keyCode,
+ aEvent.modifiers,
+ aEvent.chars, aEvent.unmodifiedChars,
+ aCallback);
+}
+
+// Test the charcodes and modifiers being delivered to keypress handlers and
+// also keydown/keyup events too.
+function* runKeyEventTests()
+{
+ var currentTestName;
+ var eventList, keyDownFlags, keyUpFlags, testingEvent, expectedDOMKeyCode;
+ const kShiftFlag = 0x1;
+ const kCtrlFlag = 0x2;
+ const kAltFlag = 0x4;
+ const kMetaFlag = 0x8;
+ const kNumLockFlag = 0x10;
+ const kCapsLockFlag = 0x20;
+ const kAltGraphFlag = 0x40;
+
+ function onKeyEvent(e)
+ {
+ /* eslint-disable-next-line no-shadow */
+ function removeFlag(e, aFlag)
+ {
+ if (e.type == "keydown") {
+ let oldValue = keyDownFlags;
+ keyDownFlags &= ~aFlag;
+ return oldValue != keyDownFlags;
+ } else if (e.type == "keyup") {
+ let oldValue = keyUpFlags;
+ keyUpFlags &= ~aFlag;
+ return oldValue != keyUpFlags;
+ }
+ return false;
+ }
+
+ /* eslint-disable-next-line no-shadow, complexity */
+ function isStateChangingModifierKeyEvent(e)
+ {
+ var flags = 0;
+ if (e.type == "keydown") {
+ flags = keyDownFlags ^ keyUpFlags;
+ } else if (e.type == "keyup") {
+ flags = keyUpFlags;
+ }
+ switch (e.key) {
+ case "Shift":
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0,
+ currentTestName + ", Ctrl of Shift " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0,
+ currentTestName + ", Command of Shift " + e.type + " event mismatch");
+ is(e.altKey, (flags & kAltFlag) != 0,
+ currentTestName + ", Alt of Shift " + e.type + " event mismatch");
+ is(e.shiftKey, e.type == "keydown",
+ currentTestName + ", Shift of Shift " + e.type + " event mismatch");
+ // AltGr on Windows is always pressed after and released before Shift key operation.
+ is(e.getModifierState("AltGraph"), (IS_MAC && e.altKey),
+ currentTestName + ", AltGraph of Shift " + e.type + " event mismatch");
+ return (testingEvent.modifiers.shiftKey || testingEvent.modifiers.shiftRightKey) &&
+ removeFlag(e, kShiftFlag) && expectedDOMKeyCode != e.keyCode;
+ case "Control":
+ is(e.ctrlKey, e.type == "keydown",
+ currentTestName + ", Ctrl of Ctrl " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0,
+ currentTestName + ", Command of Ctrl " + e.type + " event mismatch");
+ // When AltGr key is released on Windows, ControlLeft keyup event
+ // is followed by AltRight keyup event. However, altKey should be
+ // false in such case.
+ is(e.altKey, (flags & kAltFlag) != 0 && !(IS_WIN && !!testingEvent.modifiers.altGrKey),
+ currentTestName + ", Alt of Ctrl " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0,
+ currentTestName + ", Shift of Ctrl " + e.type + " event mismatch");
+ is(e.getModifierState("AltGraph"),
+ (IS_WIN && !!testingEvent.modifiers.altGrKey && e.type == "keyup") || (IS_MAC && e.altKey),
+ currentTestName + ", AltGraph of Ctrl " + e.type + " event mismatch");
+ return (testingEvent.modifiers.ctrlKey || testingEvent.modifiers.ctrlRightKey ||
+ (IS_WIN && !!testingEvent.modifiers.altGrKey)) &&
+ removeFlag(e, kCtrlFlag) && expectedDOMKeyCode != e.keyCode;
+ case "Alt":
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0 && !(IS_WIN && !!testingEvent.modifiers.altGrKey),
+ currentTestName + ", Ctrl of Alt " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0,
+ currentTestName + ", Command of Alt " + e.type + " event mismatch");
+ is(e.altKey, e.type == "keydown" && !(IS_WIN && !!testingEvent.modifiers.altGrKey),
+ currentTestName + ", Alt of Alt " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0,
+ currentTestName + ", Shift of Alt " + e.type + " event mismatch");
+ is(e.getModifierState("AltGraph"),
+ e.type == "keydown" && ((IS_WIN && !!testingEvent.modifiers.altGrKey) || (IS_MAC && e.altKey)),
+ currentTestName + ", AltGraph of Alt " + e.type + " event mismatch");
+ return (testingEvent.modifiers.altKey || testingEvent.modifiers.altRightKey ||
+ (IS_WIN && !!testingEvent.modifiers.altGrKey)) &&
+ removeFlag(e, kAltFlag) && expectedDOMKeyCode != e.keyCode;
+ case "AltGraph":
+ // On Windows, AltGraph events are fired only when AltRight key is
+ // pressed when active keyboard layout maps AltGraph to AltRight.
+ // Note that AltGraph is represented with pressing both Control key
+ // and Alt key. Therefore, when AltGraph keyboard event is fired,
+ // both ctrlKey and altKey are always false on Windows.
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0 && !IS_WIN,
+ currentTestName + ", Ctrl of AltGraph " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0,
+ currentTestName + ", Command of AltGraph " + e.type + " event mismatch");
+ is(e.altKey, (flags & kAltFlag) != 0 && !IS_WIN,
+ currentTestName + ", Alt of AltGraph " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0,
+ currentTestName + ", Shift of Ctrl " + e.type + " event mismatch");
+ is(e.getModifierState("AltGraph"), e.type === "keydown",
+ currentTestName + ", AltGraph of AltGraph " + e.type + " event mismatch");
+ return IS_WIN && testingEvent.modifiers.altGrKey &&
+ removeFlag(e, kAltGraphFlag) && expectedDOMKeyCode != e.keyCode;
+ case "Meta":
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0,
+ currentTestName + ", Ctrl of Command " + e.type + " event mismatch");
+ is(e.metaKey, e.type == "keydown",
+ currentTestName + ", Command of Command " + e.type + " event mismatch");
+ is(e.altKey, (flags & kAltFlag) != 0,
+ currentTestName + ", Alt of Command " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0,
+ currentTestName + ", Shift of Command " + e.type + " event mismatch");
+ is(e.getModifierState("AltGraph"),
+ (IS_WIN && (flags & kAltGraphFlag) != 0) || (IS_MAC && e.altKey),
+ currentTestName + ", AltGraph of Meta " + e.type + " event mismatch");
+ return (testingEvent.modifiers.metaKey || testingEvent.modifiers.metaRightKey) &&
+ removeFlag(e, kMetaFlag) && expectedDOMKeyCode != e.keyCode;
+ case "NumLock":
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0,
+ currentTestName + ", Ctrl of NumLock " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0,
+ currentTestName + ", Command of NumLock " + e.type + " event mismatch");
+ is(e.altKey, (flags & kAltFlag) != 0,
+ currentTestName + ", Alt of NumLock " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0,
+ currentTestName + ", Shift of NumLock " + e.type + " event mismatch");
+ is(e.getModifierState("AltGraph"), false,
+ currentTestName + ", AltGraph of NumLock " + e.type + " event mismatch");
+ // AltGr on Windows is always pressed after and released before NumLock key operation.
+ return (testingEvent.modifiers.numLockKey || testingEvent.modifiers.numericKeyPadKey) &&
+ removeFlag(e, kNumLockFlag) && expectedDOMKeyCode != e.keyCode;
+ case "CapsLock":
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0,
+ currentTestName + ", Ctrl of CapsLock " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0,
+ currentTestName + ", Command of CapsLock " + e.type + " event mismatch");
+ is(e.altKey, (flags & kAltFlag) != 0,
+ currentTestName + ", Alt of CapsLock " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0,
+ currentTestName + ", Shift of CapsLock " + e.type + " event mismatch");
+ // AltGr on Windows is always pressed after and released before CapsLock key operation.
+ is(e.getModifierState("AltGraph"), false,
+ currentTestName + ", AltGraph of CapsLock " + e.type + " event mismatch");
+ return testingEvent.modifiers.capsLockKey &&
+ removeFlag(e, kCapsLockFlag) && expectedDOMKeyCode != e.keyCode;
+ }
+ return false;
+ }
+
+ // Ignore the state changing key events which is fired by the testing event.
+ if (!isStateChangingModifierKeyEvent(e))
+ eventList.push(e);
+ }
+
+ function consumer(aEvent)
+ {
+ aEvent.preventDefault();
+ }
+
+ const SHOULD_DELIVER_KEYDOWN = 0x1;
+ const SHOULD_DELIVER_KEYPRESS = 0x2;
+ const SHOULD_DELIVER_KEYUP = 0x4;
+ const SHOULD_DELIVER_ALL = SHOULD_DELIVER_KEYDOWN |
+ SHOULD_DELIVER_KEYPRESS |
+ SHOULD_DELIVER_KEYUP;
+ const SHOULD_DELIVER_KEYDOWN_KEYUP = SHOULD_DELIVER_KEYDOWN |
+ SHOULD_DELIVER_KEYUP;
+ const SHOULD_DELIVER_KEYDOWN_KEYPRESS = SHOULD_DELIVER_KEYDOWN |
+ SHOULD_DELIVER_KEYPRESS;
+
+ // The first parameter is the complete input event. The second parameter is
+ // what to test against. The third parameter is which key events should be
+ // delived for the event.
+ // @param aExpectedKeyValues Can be string or array of string.
+ // If all keyboard events have same key value,
+ // specify it as string. Otherwise, specify
+ // each key value in array.
+ function testKey(aEvent, aExpectedKeyValues, aExpectedCodeValue,
+ aExpectedGeckoKeyCode, aExpectGeckoChar,
+ aShouldDelivedEvent, aExpectLocation)
+ {
+ ok(aExpectedGeckoKeyCode != undefined, "keycode is undefined");
+ eventList = [];
+
+ // The modifier key events which are fired for state changing are har to
+ // test. We should ignore them for now.
+ keyDownFlags = keyUpFlags = 0;
+ if (!IS_MAC) {
+ // On Mac, nsChildView doesn't generate modifier keydown/keyup events for
+ // state changing for synthesizeNativeKeyEvent.
+ if (aEvent.modifiers.shiftKey || aEvent.modifiers.shiftRightKey) {
+ keyDownFlags |= kShiftFlag;
+ }
+ if (aEvent.modifiers.ctrlKey || aEvent.modifiers.ctrlRightKey ||
+ (IS_WIN && aEvent.modifiers.altGrKey)) {
+ keyDownFlags |= kCtrlFlag;
+ }
+ if (aEvent.modifiers.altKey || aEvent.modifiers.altRightKey) {
+ keyDownFlags |= kAltFlag;
+ }
+ if (aEvent.modifiers.altGrKey) {
+ keyDownFlags |= kAltGraphFlag;
+ }
+ if (aEvent.modifiers.metaKey || aEvent.modifiers.metaRightKey) {
+ keyDownFlags |= kMetaFlag;
+ }
+ if (aEvent.modifiers.numLockKey || aEvent.modifiers.numericKeyPadKey) {
+ keyDownFlags |= kNumLockFlag;
+ }
+ if (aEvent.modifiers.capsLockKey) {
+ keyDownFlags |= kCapsLockFlag;
+ }
+ keyUpFlags = keyDownFlags;
+ }
+
+ testingEvent = aEvent;
+ expectedDOMKeyCode = aExpectedGeckoKeyCode;
+
+ currentTestName = eventToString(aEvent);
+ ok(true, "Starting: " + currentTestName);
+
+ // eslint-disable-next-line complexity
+ return synthesizeKey(aEvent, "button", function() {
+
+ var expectEventTypeList = [];
+ if (aShouldDelivedEvent & SHOULD_DELIVER_KEYDOWN)
+ expectEventTypeList.push("keydown");
+ if (aShouldDelivedEvent & SHOULD_DELIVER_KEYPRESS) {
+ expectEventTypeList.push("keypress");
+ for (let i = 1; i < aExpectGeckoChar.length; i++) {
+ expectEventTypeList.push("keypress");
+ }
+ }
+ if (aShouldDelivedEvent & SHOULD_DELIVER_KEYUP)
+ expectEventTypeList.push("keyup");
+ is(eventList.length, expectEventTypeList.length,
+ currentTestName + ", wrong number of key events");
+
+ var longerLength = Math.max(eventList.length, expectEventTypeList.length);
+ var keypressCount = 0;
+ for (let i = 0; i < longerLength; i++) {
+ var firedEventType = i < eventList.length ? eventList[i].type : "";
+ var expectEventType = i < expectEventTypeList.length ? expectEventTypeList[i] : "";
+ if (firedEventType != "") {
+ is(firedEventType, expectEventType,
+ currentTestName + ", " + expectEventType + " should be fired");
+ } else {
+ is(firedEventType, expectEventType,
+ currentTestName + ", a needed event is not fired");
+ }
+
+ if (firedEventType != "") {
+ var expectedKeyValue =
+ // eslint-disable-next-line no-nested-ternary
+ typeof aExpectedKeyValues === "string" ? aExpectedKeyValues :
+ i < aExpectedKeyValues.length ? aExpectedKeyValues[i] :
+ undefined;
+
+ var e = eventList[i];
+ switch (e.key) {
+ case "Shift":
+ case "Control":
+ case "Alt":
+ case "AltGraph":
+ case "Meta":
+ case "CapsLock":
+ case "NumLock":
+ // XXX To check modifier state of modifiers, we need to check
+ // e.type since modifier key may change modifier state.
+ // However, doing it makes the following check more
+ // complicated. So, we ignore the modifier state of
+ // modifier keydown/keyup events for now.
+ break;
+ default:
+ is(e.shiftKey, !!(aEvent.modifiers.shiftKey || aEvent.modifiers.shiftRightKey),
+ currentTestName + ", Shift of " + e.type + " of " + e.code + " mismatch");
+ is(e.metaKey, !!(aEvent.modifiers.metaKey || aEvent.modifiers.metaRightKey),
+ currentTestName + ", Command of " + e.type + " of " + e.code + " mismatch");
+ var isControlPressed = !!(aEvent.modifiers.ctrlKey || aEvent.modifiers.ctrlRightKey);
+ var isAltPressed = !!(aEvent.modifiers.altKey || aEvent.modifiers.altRightKey);
+ var isAltGraphExpected =
+ !!aEvent.modifiers.altGrKey ||
+ (IS_WIN && aEvent.layout.hasAltGrOnWin &&
+ isControlPressed && isAltPressed &&
+ (aEvent.isInputtingCharacters || expectedKeyValue == "Dead")) ||
+ (IS_MAC && isAltPressed);
+ var isControlExpected = !(IS_WIN && isAltGraphExpected) && isControlPressed;
+ var isAltExpected = !(IS_WIN && isAltGraphExpected) && isAltPressed;
+ if (e.type == "keypress" && aEvent.isInputtingCharacters) {
+ isControlExpected = false;
+ isAltExpected = false;
+ }
+ is(e.ctrlKey, isControlExpected,
+ currentTestName + ", Ctrl of " + e.type + " of " + e.code + " mismatch");
+ is(e.altKey, isAltExpected,
+ currentTestName + ", Alt of " + e.type + " of " + e.code + " mismatch");
+ is(e.getModifierState("AltGraph"), isAltGraphExpected,
+ currentTestName + ", AltGraph of " + e.type + " of " + e.code + " mismatch");
+ break;
+ }
+
+ is(e.key, expectedKeyValue, currentTestName + ", wrong key value");
+ is(e.code, aExpectedCodeValue, currentTestName + ", wrong code value");
+
+ if (aExpectGeckoChar.length && e.type == "keypress") {
+ is(e.charCode, aExpectGeckoChar.charCodeAt(keypressCount++),
+ currentTestName + ", charcode");
+ if (aExpectedGeckoKeyCode >= 0) {
+ if (aExpectGeckoChar) {
+ is(e.keyCode, 0,
+ currentTestName + ", wrong keycode");
+ } else {
+ is(e.keyCode, aExpectedGeckoKeyCode,
+ currentTestName + ", wrong keycode");
+ }
+ }
+ } else {
+ is(e.charCode, 0,
+ currentTestName + ", no charcode");
+ if (aExpectedGeckoKeyCode >= 0) {
+ is(e.keyCode, aExpectedGeckoKeyCode,
+ currentTestName + ", wrong keycode");
+ }
+ }
+ is(e.location, aExpectLocation,
+ currentTestName + ", wrong location");
+ }
+ }
+
+ continueTest();
+ });
+ }
+
+ // These tests have to be per-plaform.
+ document.addEventListener("keydown", onKeyEvent);
+ document.addEventListener("keypress", onKeyEvent);
+ document.addEventListener("keyup", onKeyEvent);
+ // Prevent almost all shortcut key handlers.
+ SpecialPowers.addSystemEventListener(document, "keypress", consumer, true);
+
+ function cleanup()
+ {
+ document.removeEventListener("keydown", onKeyEvent);
+ document.removeEventListener("keypress", onKeyEvent);
+ document.removeEventListener("keyup", onKeyEvent);
+ SpecialPowers.removeSystemEventListener(document, "keypress", consumer, true);
+ }
+
+ function* testKeysOnMac()
+ {
+ // On Mac, you can produce event records for any desired keyboard input
+ // by running with NSPR_LOG_MODULES=TextInputHandlerWidgets:5 and typing
+ // into the browser. We will dump the key event fields to the console
+ // (Find TextInputHandler::HandleKeyDownEvent or
+ // TextInputHandler::HandleKeyUpEvent in the log). Use the International system
+ // preferences widget to enable other keyboard layouts and select them from the
+ // input menu to see what keyboard events they generate.
+ // Note that it's possible to send bogus key events here, e.g.
+ // {keyCode:0, chars:"z", unmodifiedChars:"P"} --- sendNativeKeyEvent
+ // makes no attempt to verify that the keyCode matches the characters. So only
+ // test key event records that you saw Cocoa send.
+
+ // Command keys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1}, chars:"a", unmodifiedChars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Shift-cmd gives us the shifted character
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"a", unmodifiedChars:"A"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Ctrl-cmd gives us the unshifted character
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, ctrlKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Alt-cmd gives us the shifted character
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, altKey:1}, chars:"\u00e5", unmodifiedChars:"a"},
+ "\u00e5", "KeyA", KeyboardEvent.DOM_VK_A, "\u00e5", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, altKey:1, shiftKey:1}, chars:"\u00c5", unmodifiedChars:"a"},
+ "\u00c5", "KeyA", KeyboardEvent.DOM_VK_A, "\u00c5", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Greek ctrl keys produce Latin charcodes
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1}, chars:"\u0001", unmodifiedChars:"\u03b1"},
+ "\u03b1", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0001", unmodifiedChars:"\u0391"},
+ "\u0391", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Greek command keys
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1}, chars:"a", unmodifiedChars:"\u03b1"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Shift-cmd gives us the shifted character
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"a", unmodifiedChars:"\u0391"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Ctrl-cmd gives us the unshifted character
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, ctrlKey:1}, chars:"\u0001", unmodifiedChars:"\u03b1"},
+ "\u03b1", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Alt-cmd gives us the shifted character
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, altKey:1}, chars:"\u00a8", unmodifiedChars:"\u03b1"},
+ "\u00a8", "KeyA", KeyboardEvent.DOM_VK_A, "\u00a8", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1, altKey:1, shiftKey:1}, chars:"\u00b9", unmodifiedChars:"\u0391"},
+ "\u00b9", "KeyA", KeyboardEvent.DOM_VK_A, "\u00b9", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // German
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_A,
+ modifiers: {}, chars:"a", unmodifiedChars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers: {}, chars:"\u00fc", unmodifiedChars:"\u00fc"},
+ "\u00fc", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "\u00fc", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_Minus,
+ modifiers: {}, chars:"\u00df", unmodifiedChars:"\u00df"},
+ "\u00df", "Minus", KeyboardEvent.DOM_VK_QUESTION_MARK, "\u00df", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{shiftKey:1}, chars:"?", unmodifiedChars:"?"},
+ "?", "Minus", KeyboardEvent.DOM_VK_QUESTION_MARK, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Note that Shift+SS is '?' but Cmd+Shift+SS is '/' on German layout.
+ // Therefore, when Cmd key is pressed, the SS key's keycode is changed.
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{metaKey:1}, chars:"\u00df", unmodifiedChars:"\u00df"},
+ "\u00df", "Minus", KeyboardEvent.DOM_VK_SLASH, "\u00df", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"/", unmodifiedChars:"?"},
+ "/", "Minus", KeyboardEvent.DOM_VK_SLASH, "?", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Caps Lock key event
+ // XXX keyup event of Caps Lock key is not fired.
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_CapsLock,
+ modifiers:{capsLockKey:1}, chars:"", unmodifiedChars:""},
+ "CapsLock", "CapsLock", KeyboardEvent.DOM_VK_CAPS_LOCK, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_CapsLock,
+ modifiers:{capsLockKey:0}, chars:"", unmodifiedChars:""},
+ "CapsLock", "CapsLock", KeyboardEvent.DOM_VK_CAPS_LOCK, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Shift/RightShift key event
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Shift,
+ modifiers:{shiftKey:1}, chars:"", unmodifiedChars:""},
+ "Shift", "ShiftLeft", KeyboardEvent.DOM_VK_SHIFT, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Shift,
+ modifiers:{shiftKey:0}, chars:"", unmodifiedChars:""},
+ "Shift", "ShiftLeft", KeyboardEvent.DOM_VK_SHIFT, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightShift,
+ modifiers:{shiftRightKey:1}, chars:"", unmodifiedChars:""},
+ "Shift", "ShiftRight", KeyboardEvent.DOM_VK_SHIFT, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightShift,
+ modifiers:{shiftRightKey:0}, chars:"", unmodifiedChars:""},
+ "Shift", "ShiftRight", KeyboardEvent.DOM_VK_SHIFT, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // Control/RightControl key event
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Control,
+ modifiers:{ctrlKey:1}, chars:"", unmodifiedChars:""},
+ "Control", "ControlLeft", KeyboardEvent.DOM_VK_CONTROL, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Control,
+ modifiers:{ctrlKey:0}, chars:"", unmodifiedChars:""},
+ "Control", "ControlLeft", KeyboardEvent.DOM_VK_CONTROL, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightControl,
+ modifiers:{ctrlRightKey:1}, chars:"", unmodifiedChars:""},
+ "Control", "ControlRight", KeyboardEvent.DOM_VK_CONTROL, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightControl,
+ modifiers:{ctrlRightKey:0}, chars:"", unmodifiedChars:""},
+ "Control", "ControlRight", KeyboardEvent.DOM_VK_CONTROL, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // Option/RightOption key event
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Option,
+ modifiers:{altKey:1}, chars:"", unmodifiedChars:""},
+ "Alt", "AltLeft", KeyboardEvent.DOM_VK_ALT, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Option,
+ modifiers:{altKey:0}, chars:"", unmodifiedChars:""},
+ "Alt", "AltLeft", KeyboardEvent.DOM_VK_ALT, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightOption,
+ modifiers:{altRightKey:1}, chars:"", unmodifiedChars:""},
+ "Alt", "AltRight", KeyboardEvent.DOM_VK_ALT, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightOption,
+ modifiers:{altRightKey:0}, chars:"", unmodifiedChars:""},
+ "Alt", "AltRight", KeyboardEvent.DOM_VK_ALT, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // Command/RightCommand key event
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Command,
+ modifiers:{metaKey:1}, chars:"", unmodifiedChars:""},
+ "Meta", "OSLeft", KeyboardEvent.DOM_VK_META, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Command,
+ modifiers:{metaKey:0}, chars:"", unmodifiedChars:""},
+ "Meta", "OSLeft", KeyboardEvent.DOM_VK_META, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightCommand,
+ modifiers:{metaRightKey:1}, chars:"", unmodifiedChars:""},
+ "Meta", "OSRight", KeyboardEvent.DOM_VK_META, "", SHOULD_DELIVER_KEYDOWN, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightCommand,
+ modifiers:{metaRightKey:0}, chars:"", unmodifiedChars:""},
+ "Meta", "OSRight", KeyboardEvent.DOM_VK_META, "", SHOULD_DELIVER_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // all keys on keyboard (keyCode test)
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Tab,
+ modifiers: {}, chars:"\t", unmodifiedChars:"\t"},
+ "Tab", "Tab", KeyboardEvent.DOM_VK_TAB, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadClear,
+ modifiers: {}, chars:"\uF739", unmodifiedChars:"\uF739"},
+ "Clear", "NumLock", KeyboardEvent.DOM_VK_CLEAR, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Return,
+ modifiers: {}, chars:"\u000D", unmodifiedChars:"\u000D"},
+ "Enter", "Enter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_PC_Pause,
+ modifiers: {}, chars:"\uF712", unmodifiedChars:"\uF712"},
+ "F15", "F15", KeyboardEvent.DOM_VK_PAUSE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Escape,
+ modifiers: {}, chars:"\u001B", unmodifiedChars:"\u001B"},
+ "Escape", "Escape", KeyboardEvent.DOM_VK_ESCAPE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Space,
+ modifiers: {}, chars:" ", unmodifiedChars:" "},
+ " ", "Space", KeyboardEvent.DOM_VK_SPACE, " ", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_PageUp,
+ modifiers: {}, chars:"\uF72C", unmodifiedChars:"\uF72C"},
+ "PageUp", "PageUp", KeyboardEvent.DOM_VK_PAGE_UP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_PageDown,
+ modifiers: {}, chars:"\uF72D", unmodifiedChars:"\uF72D"},
+ "PageDown", "PageDown", KeyboardEvent.DOM_VK_PAGE_DOWN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_End,
+ modifiers: {}, chars:"\uF72B", unmodifiedChars:"\uF72B"},
+ "End", "End", KeyboardEvent.DOM_VK_END, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_Home,
+ modifiers: {}, chars:"\uF729", unmodifiedChars:"\uF729"},
+ "Home", "Home", KeyboardEvent.DOM_VK_HOME, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_LeftArrow,
+ modifiers: {}, chars:"\uF702", unmodifiedChars:"\uF702"},
+ "ArrowLeft", "ArrowLeft", KeyboardEvent.DOM_VK_LEFT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_UpArrow,
+ modifiers: {}, chars:"\uF700", unmodifiedChars:"\uF700"},
+ "ArrowUp", "ArrowUp", KeyboardEvent.DOM_VK_UP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_RightArrow,
+ modifiers: {}, chars:"\uF703", unmodifiedChars:"\uF703"},
+ "ArrowRight", "ArrowRight", KeyboardEvent.DOM_VK_RIGHT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_DownArrow,
+ modifiers: {}, chars:"\uF701", unmodifiedChars:"\uF701"},
+ "ArrowDown", "ArrowDown", KeyboardEvent.DOM_VK_DOWN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_PC_PrintScreen,
+ modifiers: {}, chars:"\uF710", unmodifiedChars:"\uF710"},
+ "F13", "F13", KeyboardEvent.DOM_VK_PRINTSCREEN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_PC_Delete,
+ modifiers: {}, chars:"\uF728", unmodifiedChars:"\uF728"},
+ "Delete", "Delete", KeyboardEvent.DOM_VK_DELETE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_PC_ScrollLock,
+ modifiers: {}, chars:"\uF711", unmodifiedChars:"\uF711"},
+ "F14", "F14", KeyboardEvent.DOM_VK_SCROLL_LOCK, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_PC_ContextMenu,
+ modifiers: {}, chars:"\u0010", unmodifiedChars:"\u0010"},
+ "ContextMenu", "ContextMenu", KeyboardEvent.DOM_VK_CONTEXT_MENU, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F1,
+ modifiers:{fnKey:1}, chars:"\uF704", unmodifiedChars:"\uF704"},
+ "F1", "F1", KeyboardEvent.DOM_VK_F1, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F2,
+ modifiers:{fnKey:1}, chars:"\uF705", unmodifiedChars:"\uF705"},
+ "F2", "F2", KeyboardEvent.DOM_VK_F2, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F3,
+ modifiers:{fnKey:1}, chars:"\uF706", unmodifiedChars:"\uF706"},
+ "F3", "F3", KeyboardEvent.DOM_VK_F3, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F4,
+ modifiers:{fnKey:1}, chars:"\uF707", unmodifiedChars:"\uF707"},
+ "F4", "F4", KeyboardEvent.DOM_VK_F4, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F5,
+ modifiers:{fnKey:1}, chars:"\uF708", unmodifiedChars:"\uF708"},
+ "F5", "F5", KeyboardEvent.DOM_VK_F5, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F6,
+ modifiers:{fnKey:1}, chars:"\uF709", unmodifiedChars:"\uF709"},
+ "F6", "F6", KeyboardEvent.DOM_VK_F6, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F7,
+ modifiers:{fnKey:1}, chars:"\uF70A", unmodifiedChars:"\uF70A"},
+ "F7", "F7", KeyboardEvent.DOM_VK_F7, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F8,
+ modifiers:{fnKey:1}, chars:"\uF70B", unmodifiedChars:"\uF70B"},
+ "F8", "F8", KeyboardEvent.DOM_VK_F8, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F9,
+ modifiers:{fnKey:1}, chars:"\uF70C", unmodifiedChars:"\uF70C"},
+ "F9", "F9", KeyboardEvent.DOM_VK_F9, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F10,
+ modifiers:{fnKey:1}, chars:"\uF70D", unmodifiedChars:"\uF70D"},
+ "F10", "F10", KeyboardEvent.DOM_VK_F10, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F11,
+ modifiers:{fnKey:1}, chars:"\uF70E", unmodifiedChars:"\uF70E"},
+ "F11", "F11", KeyboardEvent.DOM_VK_F11, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F12,
+ modifiers:{fnKey:1}, chars:"\uF70F", unmodifiedChars:"\uF70F"},
+ "F12", "F12", KeyboardEvent.DOM_VK_F12, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F16,
+ modifiers:{fnKey:1}, chars:"\uF713", unmodifiedChars:"\uF713"},
+ "F16", "F16", KeyboardEvent.DOM_VK_F16, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F17,
+ modifiers:{fnKey:1}, chars:"\uF714", unmodifiedChars:"\uF714"},
+ "F17", "F17", KeyboardEvent.DOM_VK_F17, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F18,
+ modifiers:{fnKey:1}, chars:"\uF715", unmodifiedChars:"\uF715"},
+ "F18", "F18", KeyboardEvent.DOM_VK_F18, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_F19,
+ modifiers:{fnKey:1}, chars:"\uF716", unmodifiedChars:"\uF716"},
+ "F19", "F19", KeyboardEvent.DOM_VK_F19, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // US
+ // Alphabet
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers: {}, chars:"a", unmodifiedChars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{shiftKey:1}, chars:"A", unmodifiedChars:"A"},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0001", unmodifiedChars:"A"},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, capsLockKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, shiftKey:1, capsLockKey:1}, chars:"\u0001", unmodifiedChars:"A"},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{altKey:1}, chars:"\u00E5", unmodifiedChars:"a"},
+ "\u00E5", "KeyA", KeyboardEvent.DOM_VK_A, "\u00E5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C5", unmodifiedChars:"A"},
+ "\u00C5", "KeyA", KeyboardEvent.DOM_VK_A, "\u00C5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "\u00E5", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0001", unmodifiedChars:"A"},
+ "\u00C5", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1}, chars:"a", unmodifiedChars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{}, chars:"b", unmodifiedChars:"b"},
+ "b", "KeyB", KeyboardEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{shiftKey:1}, chars:"B", unmodifiedChars:"B"},
+ "B", "KeyB", KeyboardEvent.DOM_VK_B, "B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{ctrlKey:1}, chars:"\u0002", unmodifiedChars:"b"},
+ "b", "KeyB", KeyboardEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0002", unmodifiedChars:"B"},
+ "B", "KeyB", KeyboardEvent.DOM_VK_B, "B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{altKey:1}, chars:"\u222B", unmodifiedChars:"b"},
+ "\u222B", "KeyB", KeyboardEvent.DOM_VK_B, "\u222B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u0131", unmodifiedChars:"B"},
+ "\u0131", "KeyB", KeyboardEvent.DOM_VK_B, "\u0131", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0002", unmodifiedChars:"b"},
+ "\u222B", "KeyB", KeyboardEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0002", unmodifiedChars:"B"},
+ "\u0131", "KeyB", KeyboardEvent.DOM_VK_B, "B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_B,
+ modifiers:{metaKey:1}, chars:"b", unmodifiedChars:"b"},
+ "b", "KeyB", KeyboardEvent.DOM_VK_B, "b", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{}, chars:"c", unmodifiedChars:"c"},
+ "c", "KeyC", KeyboardEvent.DOM_VK_C, "c", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{shiftKey:1}, chars:"C", unmodifiedChars:"C"},
+ "C", "KeyC", KeyboardEvent.DOM_VK_C, "C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{ctrlKey:1}, chars:"\u0003", unmodifiedChars:"c"},
+ "c", "KeyC", KeyboardEvent.DOM_VK_C, "c", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0003", unmodifiedChars:"C"},
+ "C", "KeyC", KeyboardEvent.DOM_VK_C, "C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{altKey:1}, chars:"\u00E7", unmodifiedChars:"c"},
+ "\u00E7", "KeyC", KeyboardEvent.DOM_VK_C, "\u00E7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C7", unmodifiedChars:"C"},
+ "\u00C7", "KeyC", KeyboardEvent.DOM_VK_C, "\u00C7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0003", unmodifiedChars:"c"},
+ "\u00E7", "KeyC", KeyboardEvent.DOM_VK_C, "c", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0003", unmodifiedChars:"C"},
+ "\u00C7", "KeyC", KeyboardEvent.DOM_VK_C, "C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_C,
+ modifiers:{metaKey:1}, chars:"c", unmodifiedChars:"c"},
+ "c", "KeyC", KeyboardEvent.DOM_VK_C, "c", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{}, chars:"d", unmodifiedChars:"d"},
+ "d", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{shiftKey:1}, chars:"D", unmodifiedChars:"D"},
+ "D", "KeyD", KeyboardEvent.DOM_VK_D, "D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{ctrlKey:1}, chars:"\u0004", unmodifiedChars:"d"},
+ "d", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0004", unmodifiedChars:"D"},
+ "D", "KeyD", KeyboardEvent.DOM_VK_D, "D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{altKey:1}, chars:"\u2202", unmodifiedChars:"d"},
+ "\u2202", "KeyD", KeyboardEvent.DOM_VK_D, "\u2202", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00CE", unmodifiedChars:"D"},
+ "\u00CE", "KeyD", KeyboardEvent.DOM_VK_D, "\u00CE", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0004", unmodifiedChars:"d"},
+ "\u2202", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0004", unmodifiedChars:"D"},
+ "\u00CE", "KeyD", KeyboardEvent.DOM_VK_D, "D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_D,
+ modifiers:{metaKey:1}, chars:"d", unmodifiedChars:"d"},
+ "d", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{}, chars:"e", unmodifiedChars:"e"},
+ "e", "KeyE", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{shiftKey:1}, chars:"E", unmodifiedChars:"E"},
+ "E", "KeyE", KeyboardEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{ctrlKey:1}, chars:"\u0005", unmodifiedChars:"e"},
+ "e", "KeyE", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0005", unmodifiedChars:"E"},
+ "E", "KeyE", KeyboardEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{altKey:1}, chars:"", unmodifiedChars:"e"},
+ "Dead", "KeyE", KeyboardEvent.DOM_VK_E, "\u00B4", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // dead key
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00B4", unmodifiedChars:"E"},
+ "\u00B4", "KeyE", KeyboardEvent.DOM_VK_E, "\u00B4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0005", unmodifiedChars:"e"},
+ "Dead", "KeyE", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0005", unmodifiedChars:"E"},
+ "\u00B4", "KeyE", KeyboardEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_E,
+ modifiers:{metaKey:1}, chars:"e", unmodifiedChars:"e"},
+ "e", "KeyE", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{}, chars:"f", unmodifiedChars:"f"},
+ "f", "KeyF", KeyboardEvent.DOM_VK_F, "f", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{shiftKey:1}, chars:"F", unmodifiedChars:"F"},
+ "F", "KeyF", KeyboardEvent.DOM_VK_F, "F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{ctrlKey:1}, chars:"\u0006", unmodifiedChars:"f"},
+ "f", "KeyF", KeyboardEvent.DOM_VK_F, "f", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0006", unmodifiedChars:"F"},
+ "F", "KeyF", KeyboardEvent.DOM_VK_F, "F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{altKey:1}, chars:"\u0192", unmodifiedChars:"f"},
+ "\u0192", "KeyF", KeyboardEvent.DOM_VK_F, "\u0192", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00CF", unmodifiedChars:"F"},
+ "\u00CF", "KeyF", KeyboardEvent.DOM_VK_F, "\u00CF", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0006", unmodifiedChars:"f"},
+ "\u0192", "KeyF", KeyboardEvent.DOM_VK_F, "f", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0006", unmodifiedChars:"F"},
+ "\u00CF", "KeyF", KeyboardEvent.DOM_VK_F, "F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // XXX This test starts fullscreen mode.
+ // yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ // modifiers:{metaKey:1}, chars:"f", unmodifiedChars:"f"},
+ // "f", "KeyF", KeyboardEvent.DOM_VK_F, "f", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{}, chars:"g", unmodifiedChars:"g"},
+ "g", "KeyG", KeyboardEvent.DOM_VK_G, "g", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{shiftKey:1}, chars:"G", unmodifiedChars:"G"},
+ "G", "KeyG", KeyboardEvent.DOM_VK_G, "G", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{ctrlKey:1}, chars:"\u0007", unmodifiedChars:"g"},
+ "g", "KeyG", KeyboardEvent.DOM_VK_G, "g", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0007", unmodifiedChars:"G"},
+ "G", "KeyG", KeyboardEvent.DOM_VK_G, "G", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{altKey:1}, chars:"\u00A9", unmodifiedChars:"g"},
+ "\u00A9", "KeyG", KeyboardEvent.DOM_VK_G, "\u00A9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u02DD", unmodifiedChars:"G"},
+ "\u02DD", "KeyG", KeyboardEvent.DOM_VK_G, "\u02DD", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0007", unmodifiedChars:"g"},
+ "\u00A9", "KeyG", KeyboardEvent.DOM_VK_G, "g", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0007", unmodifiedChars:"G"},
+ "\u02DD", "KeyG", KeyboardEvent.DOM_VK_G, "G", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_G,
+ modifiers:{metaKey:1}, chars:"g", unmodifiedChars:"g"},
+ "g", "KeyG", KeyboardEvent.DOM_VK_G, "g", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{}, chars:"h", unmodifiedChars:"h"},
+ "h", "KeyH", KeyboardEvent.DOM_VK_H, "h", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{shiftKey:1}, chars:"H", unmodifiedChars:"H"},
+ "H", "KeyH", KeyboardEvent.DOM_VK_H, "H", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{ctrlKey:1}, chars:"\u0008", unmodifiedChars:"h"},
+ "h", "KeyH", KeyboardEvent.DOM_VK_H, "h", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0008", unmodifiedChars:"H"},
+ "H", "KeyH", KeyboardEvent.DOM_VK_H, "H", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{altKey:1}, chars:"\u02D9", unmodifiedChars:"h"},
+ "\u02D9", "KeyH", KeyboardEvent.DOM_VK_H, "\u02D9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00D3", unmodifiedChars:"H"},
+ "\u00D3", "KeyH", KeyboardEvent.DOM_VK_H, "\u00D3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0008", unmodifiedChars:"h"},
+ "\u02D9", "KeyH", KeyboardEvent.DOM_VK_H, "h", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0008", unmodifiedChars:"H"},
+ "\u00D3", "KeyH", KeyboardEvent.DOM_VK_H, "H", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_H,
+ modifiers:{metaKey:1}, chars:"h", unmodifiedChars:"h"},
+ "h", "KeyH", KeyboardEvent.DOM_VK_H, "h", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{}, chars:"i", unmodifiedChars:"i"},
+ "i", "KeyI", KeyboardEvent.DOM_VK_I, "i", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{shiftKey:1}, chars:"I", unmodifiedChars:"I"},
+ "I", "KeyI", KeyboardEvent.DOM_VK_I, "I", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{ctrlKey:1}, chars:"\u0009", unmodifiedChars:"i"},
+ "i", "KeyI", KeyboardEvent.DOM_VK_I, "i", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0009", unmodifiedChars:"I"},
+ "I", "KeyI", KeyboardEvent.DOM_VK_I, "I", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{altKey:1}, chars:"", unmodifiedChars:"i"},
+ "Dead", "KeyI", KeyboardEvent.DOM_VK_I, "\u02C6", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // dead key
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u02C6", unmodifiedChars:"I"},
+ "\u02C6", "KeyI", KeyboardEvent.DOM_VK_I, "\u02C6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0009", unmodifiedChars:"i"},
+ "Dead", "KeyI", KeyboardEvent.DOM_VK_I, "i", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0009", unmodifiedChars:"I"},
+ "\u02C6", "KeyI", KeyboardEvent.DOM_VK_I, "I", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // XXX This test causes memory leak.
+ // yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_I,
+ // modifiers:{metaKey:1}, chars:"i", unmodifiedChars:"i"},
+ // "i", "KeyI", KeyboardEvent.DOM_VK_I, "i", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{}, chars:"j", unmodifiedChars:"j"},
+ "j", "KeyJ", KeyboardEvent.DOM_VK_J, "j", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{shiftKey:1}, chars:"J", unmodifiedChars:"J"},
+ "J", "KeyJ", KeyboardEvent.DOM_VK_J, "J", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{ctrlKey:1}, chars:"\u000A", unmodifiedChars:"j"},
+ "j", "KeyJ", KeyboardEvent.DOM_VK_J, "j", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000A", unmodifiedChars:"J"},
+ "J", "KeyJ", KeyboardEvent.DOM_VK_J, "J", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{altKey:1}, chars:"\u2206", unmodifiedChars:"j"},
+ "\u2206", "KeyJ", KeyboardEvent.DOM_VK_J, "\u2206", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00D4", unmodifiedChars:"J"},
+ "\u00D4", "KeyJ", KeyboardEvent.DOM_VK_J, "\u00D4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u000A", unmodifiedChars:"j"},
+ "\u2206", "KeyJ", KeyboardEvent.DOM_VK_J, "j", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u000A", unmodifiedChars:"J"},
+ "\u00D4", "KeyJ", KeyboardEvent.DOM_VK_J, "J", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_J,
+ modifiers:{metaKey:1}, chars:"j", unmodifiedChars:"j"},
+ "j", "KeyJ", KeyboardEvent.DOM_VK_J, "j", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{}, chars:"k", unmodifiedChars:"k"},
+ "k", "KeyK", KeyboardEvent.DOM_VK_K, "k", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{shiftKey:1}, chars:"K", unmodifiedChars:"K"},
+ "K", "KeyK", KeyboardEvent.DOM_VK_K, "K", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{ctrlKey:1}, chars:"\u000B", unmodifiedChars:"k"},
+ "k", "KeyK", KeyboardEvent.DOM_VK_K, "k", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000B", unmodifiedChars:"K"},
+ "K", "KeyK", KeyboardEvent.DOM_VK_K, "K", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{altKey:1}, chars:"\u02DA", unmodifiedChars:"k"},
+ "\u02DA", "KeyK", KeyboardEvent.DOM_VK_K, "\u02DA", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\uF8FF", unmodifiedChars:"K"},
+ "\uF8FF", "KeyK", KeyboardEvent.DOM_VK_K, "\uF8FF", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u000B", unmodifiedChars:"k"},
+ "\u02DA", "KeyK", KeyboardEvent.DOM_VK_K, "k", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u000B", unmodifiedChars:"K"},
+ "\uF8FF", "KeyK", KeyboardEvent.DOM_VK_K, "K", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_K,
+ modifiers:{metaKey:1}, chars:"k", unmodifiedChars:"k"},
+ "k", "KeyK", KeyboardEvent.DOM_VK_K, "k", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{}, chars:"l", unmodifiedChars:"l"},
+ "l", "KeyL", KeyboardEvent.DOM_VK_L, "l", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{shiftKey:1}, chars:"L", unmodifiedChars:"L"},
+ "L", "KeyL", KeyboardEvent.DOM_VK_L, "L", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{ctrlKey:1}, chars:"\u000C", unmodifiedChars:"l"},
+ "l", "KeyL", KeyboardEvent.DOM_VK_L, "l", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000C", unmodifiedChars:"L"},
+ "L", "KeyL", KeyboardEvent.DOM_VK_L, "L", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{altKey:1}, chars:"\u00AC", unmodifiedChars:"l"},
+ "\u00AC", "KeyL", KeyboardEvent.DOM_VK_L, "\u00AC", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00D2", unmodifiedChars:"L"},
+ "\u00D2", "KeyL", KeyboardEvent.DOM_VK_L, "\u00D2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u000C", unmodifiedChars:"l"},
+ "\u00AC", "KeyL", KeyboardEvent.DOM_VK_L, "l", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u000C", unmodifiedChars:"L"},
+ "\u00D2", "KeyL", KeyboardEvent.DOM_VK_L, "L", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_L,
+ modifiers:{metaKey:1}, chars:"l", unmodifiedChars:"l"},
+ "l", "KeyL", KeyboardEvent.DOM_VK_L, "l", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{}, chars:"m", unmodifiedChars:"m"},
+ "m", "KeyM", KeyboardEvent.DOM_VK_M, "m", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{shiftKey:1}, chars:"M", unmodifiedChars:"M"},
+ "M", "KeyM", KeyboardEvent.DOM_VK_M, "M", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{ctrlKey:1}, chars:"\u000D", unmodifiedChars:"m"},
+ "m", "KeyM", KeyboardEvent.DOM_VK_M, "m", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000D", unmodifiedChars:"M"},
+ "M", "KeyM", KeyboardEvent.DOM_VK_M, "M", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{altKey:1}, chars:"\u00B5", unmodifiedChars:"m"},
+ "\u00B5", "KeyM", KeyboardEvent.DOM_VK_M, "\u00B5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C2", unmodifiedChars:"M"},
+ "\u00C2", "KeyM", KeyboardEvent.DOM_VK_M, "\u00C2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u000D", unmodifiedChars:"m"},
+ "\u00B5", "KeyM", KeyboardEvent.DOM_VK_M, "m", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u000D", unmodifiedChars:"M"},
+ "\u00C2", "KeyM", KeyboardEvent.DOM_VK_M, "M", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_M,
+ modifiers:{metaKey:1}, chars:"m", unmodifiedChars:"m"},
+ "m", "KeyM", KeyboardEvent.DOM_VK_M, "m", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{}, chars:"n", unmodifiedChars:"n"},
+ "n", "KeyN", KeyboardEvent.DOM_VK_N, "n", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{shiftKey:1}, chars:"N", unmodifiedChars:"N"},
+ "N", "KeyN", KeyboardEvent.DOM_VK_N, "N", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{ctrlKey:1}, chars:"\u000E", unmodifiedChars:"n"},
+ "n", "KeyN", KeyboardEvent.DOM_VK_N, "n", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000E", unmodifiedChars:"N"},
+ "N", "KeyN", KeyboardEvent.DOM_VK_N, "N", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{altKey:1}, chars:"", unmodifiedChars:"n"},
+ "Dead", "KeyN", KeyboardEvent.DOM_VK_N, "\u02DC", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // dead key
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u02DC", unmodifiedChars:"N"},
+ "\u02DC", "KeyN", KeyboardEvent.DOM_VK_N, "\u02DC", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u000E", unmodifiedChars:"n"},
+ "Dead", "KeyN", KeyboardEvent.DOM_VK_N, "n", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u000E", unmodifiedChars:"N"},
+ "\u02DC", "KeyN", KeyboardEvent.DOM_VK_N, "N", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_N,
+ modifiers:{metaKey:1}, chars:"n", unmodifiedChars:"n"},
+ "n", "KeyN", KeyboardEvent.DOM_VK_N, "n", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{}, chars:"o", unmodifiedChars:"o"},
+ "o", "KeyO", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{shiftKey:1}, chars:"O", unmodifiedChars:"O"},
+ "O", "KeyO", KeyboardEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{ctrlKey:1}, chars:"\u000F", unmodifiedChars:"o"},
+ "o", "KeyO", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000F", unmodifiedChars:"O"},
+ "O", "KeyO", KeyboardEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{altKey:1}, chars:"\u00F8", unmodifiedChars:"o"},
+ "\u00F8", "KeyO", KeyboardEvent.DOM_VK_O, "\u00F8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00D8", unmodifiedChars:"O"},
+ "\u00D8", "KeyO", KeyboardEvent.DOM_VK_O, "\u00D8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u000F", unmodifiedChars:"o"},
+ "\u00F8", "KeyO", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u000F", unmodifiedChars:"O"},
+ "\u00D8", "KeyO", KeyboardEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_O,
+ modifiers:{metaKey:1}, chars:"o", unmodifiedChars:"o"},
+ "o", "KeyO", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{}, chars:"p", unmodifiedChars:"p"},
+ "p", "KeyP", KeyboardEvent.DOM_VK_P, "p", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{shiftKey:1}, chars:"P", unmodifiedChars:"P"},
+ "P", "KeyP", KeyboardEvent.DOM_VK_P, "P", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{ctrlKey:1}, chars:"\u0010", unmodifiedChars:"p"},
+ "p", "KeyP", KeyboardEvent.DOM_VK_P, "p", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0010", unmodifiedChars:"P"},
+ "P", "KeyP", KeyboardEvent.DOM_VK_P, "P", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{altKey:1}, chars:"\u03C0", unmodifiedChars:"p"},
+ "\u03C0", "KeyP", KeyboardEvent.DOM_VK_P, "\u03C0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u220F", unmodifiedChars:"P"},
+ "\u220F", "KeyP", KeyboardEvent.DOM_VK_P, "\u220F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0010", unmodifiedChars:"p"},
+ "\u03C0", "KeyP", KeyboardEvent.DOM_VK_P, "p", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0010", unmodifiedChars:"P"},
+ "\u220F", "KeyP", KeyboardEvent.DOM_VK_P, "P", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // XXX This test starts private browsing mode (stopped at the confirmation dialog)
+ // yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_P,
+ // modifiers:{metaKey:1}, chars:"p", unmodifiedChars:"p"},
+ // "p", "KeyP", KeyboardEvent.DOM_VK_P, "p", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{}, chars:"q", unmodifiedChars:"q"},
+ "q", "KeyQ", KeyboardEvent.DOM_VK_Q, "q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{shiftKey:1}, chars:"Q", unmodifiedChars:"Q"},
+ "Q", "KeyQ", KeyboardEvent.DOM_VK_Q, "Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{ctrlKey:1}, chars:"\u0011", unmodifiedChars:"q"},
+ "q", "KeyQ", KeyboardEvent.DOM_VK_Q, "q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0011", unmodifiedChars:"Q"},
+ "Q", "KeyQ", KeyboardEvent.DOM_VK_Q, "Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{altKey:1}, chars:"\u0153", unmodifiedChars:"q"},
+ "\u0153", "KeyQ", KeyboardEvent.DOM_VK_Q, "\u0153", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u0152", unmodifiedChars:"Q"},
+ "\u0152", "KeyQ", KeyboardEvent.DOM_VK_Q, "\u0152", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0011", unmodifiedChars:"q"},
+ "\u0153", "KeyQ", KeyboardEvent.DOM_VK_Q, "q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0011", unmodifiedChars:"Q"},
+ "\u0152", "KeyQ", KeyboardEvent.DOM_VK_Q, "Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Q,
+ modifiers:{metaKey:1}, chars:"q", unmodifiedChars:"q"},
+ "q", "KeyQ", KeyboardEvent.DOM_VK_Q, "q", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{}, chars:"r", unmodifiedChars:"r"},
+ "r", "KeyR", KeyboardEvent.DOM_VK_R, "r", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{shiftKey:1}, chars:"R", unmodifiedChars:"R"},
+ "R", "KeyR", KeyboardEvent.DOM_VK_R, "R", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{ctrlKey:1}, chars:"\u0012", unmodifiedChars:"r"},
+ "r", "KeyR", KeyboardEvent.DOM_VK_R, "r", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0012", unmodifiedChars:"R"},
+ "R", "KeyR", KeyboardEvent.DOM_VK_R, "R", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{altKey:1}, chars:"\u00AE", unmodifiedChars:"r"},
+ "\u00AE", "KeyR", KeyboardEvent.DOM_VK_R, "\u00AE", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u2030", unmodifiedChars:"R"},
+ "\u2030", "KeyR", KeyboardEvent.DOM_VK_R, "\u2030", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0012", unmodifiedChars:"r"},
+ "\u00AE", "KeyR", KeyboardEvent.DOM_VK_R, "r", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0012", unmodifiedChars:"R"},
+ "\u2030", "KeyR", KeyboardEvent.DOM_VK_R, "R", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // XXX This test makes some tabs and dialogs.
+ // yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_R,
+ // modifiers:{metaKey:1}, chars:"r", unmodifiedChars:"r"},
+ // "r", "KeyR", KeyboardEvent.DOM_VK_R, "r", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{}, chars:"s", unmodifiedChars:"s"},
+ "s", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{shiftKey:1}, chars:"S", unmodifiedChars:"S"},
+ "S", "KeyS", KeyboardEvent.DOM_VK_S, "S", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{ctrlKey:1}, chars:"\u0013", unmodifiedChars:"s"},
+ "s", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0013", unmodifiedChars:"S"},
+ "S", "KeyS", KeyboardEvent.DOM_VK_S, "S", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{altKey:1}, chars:"\u00DF", unmodifiedChars:"s"},
+ "\u00DF", "KeyS", KeyboardEvent.DOM_VK_S, "\u00DF", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00CD", unmodifiedChars:"S"},
+ "\u00CD", "KeyS", KeyboardEvent.DOM_VK_S, "\u00CD", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0013", unmodifiedChars:"s"},
+ "\u00DF", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0013", unmodifiedChars:"S"},
+ "\u00CD", "KeyS", KeyboardEvent.DOM_VK_S, "S", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_S,
+ modifiers:{metaKey:1}, chars:"s", unmodifiedChars:"s"},
+ "s", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{}, chars:"t", unmodifiedChars:"t"},
+ "t", "KeyT", KeyboardEvent.DOM_VK_T, "t", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{shiftKey:1}, chars:"T", unmodifiedChars:"T"},
+ "T", "KeyT", KeyboardEvent.DOM_VK_T, "T", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{ctrlKey:1}, chars:"\u0014", unmodifiedChars:"t"},
+ "t", "KeyT", KeyboardEvent.DOM_VK_T, "t", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0014", unmodifiedChars:"T"},
+ "T", "KeyT", KeyboardEvent.DOM_VK_T, "T", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{altKey:1}, chars:"\u2020", unmodifiedChars:"t"},
+ "\u2020", "KeyT", KeyboardEvent.DOM_VK_T, "\u2020", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u02C7", unmodifiedChars:"T"},
+ "\u02C7", "KeyT", KeyboardEvent.DOM_VK_T, "\u02C7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0014", unmodifiedChars:"t"},
+ "\u2020", "KeyT", KeyboardEvent.DOM_VK_T, "t", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0014", unmodifiedChars:"T"},
+ "\u02C7", "KeyT", KeyboardEvent.DOM_VK_T, "T", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{metaKey:1}, chars:"t", unmodifiedChars:"t"},
+ "t", "KeyT", KeyboardEvent.DOM_VK_T, "t", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{}, chars:"u", unmodifiedChars:"u"},
+ "u", "KeyU", KeyboardEvent.DOM_VK_U, "u", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{shiftKey:1}, chars:"U", unmodifiedChars:"U"},
+ "U", "KeyU", KeyboardEvent.DOM_VK_U, "U", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{ctrlKey:1}, chars:"\u0015", unmodifiedChars:"u"},
+ "u", "KeyU", KeyboardEvent.DOM_VK_U, "u", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0015", unmodifiedChars:"U"},
+ "U", "KeyU", KeyboardEvent.DOM_VK_U, "U", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{altKey:1}, chars:"", unmodifiedChars:"u"},
+ "Dead", "KeyU", KeyboardEvent.DOM_VK_U, "\u00A8", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // dead key
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00A8", unmodifiedChars:"U"},
+ "\u00A8", "KeyU", KeyboardEvent.DOM_VK_U, "\u00A8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0015", unmodifiedChars:"u"},
+ "Dead", "KeyU", KeyboardEvent.DOM_VK_U, "u", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0015", unmodifiedChars:"U"},
+ "\u00A8", "KeyU", KeyboardEvent.DOM_VK_U, "U", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_U,
+ modifiers:{metaKey:1}, chars:"u", unmodifiedChars:"u"},
+ "u", "KeyU", KeyboardEvent.DOM_VK_U, "u", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{}, chars:"v", unmodifiedChars:"v"},
+ "v", "KeyV", KeyboardEvent.DOM_VK_V, "v", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{shiftKey:1}, chars:"V", unmodifiedChars:"V"},
+ "V", "KeyV", KeyboardEvent.DOM_VK_V, "V", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{ctrlKey:1}, chars:"\u0016", unmodifiedChars:"v"},
+ "v", "KeyV", KeyboardEvent.DOM_VK_V, "v", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0016", unmodifiedChars:"V"},
+ "V", "KeyV", KeyboardEvent.DOM_VK_V, "V", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{altKey:1}, chars:"\u221A", unmodifiedChars:"v"},
+ "\u221A", "KeyV", KeyboardEvent.DOM_VK_V, "\u221A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u25CA", unmodifiedChars:"V"},
+ "\u25CA", "KeyV", KeyboardEvent.DOM_VK_V, "\u25CA", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0016", unmodifiedChars:"v"},
+ "\u221A", "KeyV", KeyboardEvent.DOM_VK_V, "v", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0016", unmodifiedChars:"V"},
+ "\u25CA", "KeyV", KeyboardEvent.DOM_VK_V, "V", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_V,
+ modifiers:{metaKey:1}, chars:"v", unmodifiedChars:"v"},
+ "v", "KeyV", KeyboardEvent.DOM_VK_V, "v", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{}, chars:"w", unmodifiedChars:"w"},
+ "w", "KeyW", KeyboardEvent.DOM_VK_W, "w", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{shiftKey:1}, chars:"W", unmodifiedChars:"W"},
+ "W", "KeyW", KeyboardEvent.DOM_VK_W, "W", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{ctrlKey:1}, chars:"\u0017", unmodifiedChars:"w"},
+ "w", "KeyW", KeyboardEvent.DOM_VK_W, "w", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0017", unmodifiedChars:"W"},
+ "W", "KeyW", KeyboardEvent.DOM_VK_W, "W", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{altKey:1}, chars:"\u2211", unmodifiedChars:"w"},
+ "\u2211", "KeyW", KeyboardEvent.DOM_VK_W, "\u2211", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u201E", unmodifiedChars:"W"},
+ "\u201E", "KeyW", KeyboardEvent.DOM_VK_W, "\u201E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0017", unmodifiedChars:"w"},
+ "\u2211", "KeyW", KeyboardEvent.DOM_VK_W, "w", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0017", unmodifiedChars:"W"},
+ "\u201E", "KeyW", KeyboardEvent.DOM_VK_W, "W", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_W,
+ modifiers:{metaKey:1}, chars:"w", unmodifiedChars:"w"},
+ "w", "KeyW", KeyboardEvent.DOM_VK_W, "w", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{}, chars:"x", unmodifiedChars:"x"},
+ "x", "KeyX", KeyboardEvent.DOM_VK_X, "x", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{shiftKey:1}, chars:"X", unmodifiedChars:"X"},
+ "X", "KeyX", KeyboardEvent.DOM_VK_X, "X", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{ctrlKey:1}, chars:"\u0018", unmodifiedChars:"x"},
+ "x", "KeyX", KeyboardEvent.DOM_VK_X, "x", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0018", unmodifiedChars:"X"},
+ "X", "KeyX", KeyboardEvent.DOM_VK_X, "X", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{altKey:1}, chars:"\u2248", unmodifiedChars:"x"},
+ "\u2248", "KeyX", KeyboardEvent.DOM_VK_X, "\u2248", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u02DB", unmodifiedChars:"X"},
+ "\u02DB", "KeyX", KeyboardEvent.DOM_VK_X, "\u02DB", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0018", unmodifiedChars:"x"},
+ "\u2248", "KeyX", KeyboardEvent.DOM_VK_X, "x", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0018", unmodifiedChars:"X"},
+ "\u02DB", "KeyX", KeyboardEvent.DOM_VK_X, "X", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_X,
+ modifiers:{metaKey:1}, chars:"x", unmodifiedChars:"x"},
+ "x", "KeyX", KeyboardEvent.DOM_VK_X, "x", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{}, chars:"y", unmodifiedChars:"y"},
+ "y", "KeyY", KeyboardEvent.DOM_VK_Y, "y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{shiftKey:1}, chars:"Y", unmodifiedChars:"Y"},
+ "Y", "KeyY", KeyboardEvent.DOM_VK_Y, "Y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{ctrlKey:1}, chars:"\u0019", unmodifiedChars:"y"},
+ "y", "KeyY", KeyboardEvent.DOM_VK_Y, "y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0019", unmodifiedChars:"Y"},
+ "Y", "KeyY", KeyboardEvent.DOM_VK_Y, "Y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{altKey:1}, chars:"\u00A5", unmodifiedChars:"y"},
+ "\u00A5", "KeyY", KeyboardEvent.DOM_VK_Y, "\u00A5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C1", unmodifiedChars:"Y"},
+ "\u00C1", "KeyY", KeyboardEvent.DOM_VK_Y, "\u00C1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u0019", unmodifiedChars:"y"},
+ "\u00A5", "KeyY", KeyboardEvent.DOM_VK_Y, "y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0019", unmodifiedChars:"Y"},
+ "\u00C1", "KeyY", KeyboardEvent.DOM_VK_Y, "Y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Y,
+ modifiers:{metaKey:1}, chars:"y", unmodifiedChars:"y"},
+ "y", "KeyY", KeyboardEvent.DOM_VK_Y, "y", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{}, chars:"z", unmodifiedChars:"z"},
+ "z", "KeyZ", KeyboardEvent.DOM_VK_Z, "z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{shiftKey:1}, chars:"Z", unmodifiedChars:"Z"},
+ "Z", "KeyZ", KeyboardEvent.DOM_VK_Z, "Z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{ctrlKey:1}, chars:"\u001A", unmodifiedChars:"z"},
+ "z", "KeyZ", KeyboardEvent.DOM_VK_Z, "z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001A", unmodifiedChars:"Z"},
+ "Z", "KeyZ", KeyboardEvent.DOM_VK_Z, "Z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{altKey:1}, chars:"\u03A9", unmodifiedChars:"z"},
+ "\u03A9", "KeyZ", KeyboardEvent.DOM_VK_Z, "\u03A9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00B8", unmodifiedChars:"Z"},
+ "\u00B8", "KeyZ", KeyboardEvent.DOM_VK_Z, "\u00B8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u001A", unmodifiedChars:"z"},
+ "\u03A9", "KeyZ", KeyboardEvent.DOM_VK_Z, "z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001A", unmodifiedChars:"Z"},
+ "\u00B8", "KeyZ", KeyboardEvent.DOM_VK_Z, "Z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Z,
+ modifiers:{metaKey:1}, chars:"z", unmodifiedChars:"z"},
+ "z", "KeyZ", KeyboardEvent.DOM_VK_Z, "z", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // numeric
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{}, chars:"1", unmodifiedChars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{shiftKey:1}, chars:"!", unmodifiedChars:"!"},
+ "!", "Digit1", KeyboardEvent.DOM_VK_1, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{ctrlKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"!"},
+ "!", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1}, chars:"\u00A1", unmodifiedChars:"1"},
+ "\u00A1", "Digit1", KeyboardEvent.DOM_VK_1, "\u00A1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u2044", unmodifiedChars:"!"},
+ "\u2044", "Digit1", KeyboardEvent.DOM_VK_1, "\u2044", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"1", unmodifiedChars:"1"},
+ "\u00A1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"!"},
+ "\u2044", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_1,
+ modifiers:{metaKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{}, chars:"2", unmodifiedChars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{shiftKey:1}, chars:"@", unmodifiedChars:"@"},
+ "@", "Digit2", KeyboardEvent.DOM_VK_2, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{ctrlKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0000", unmodifiedChars:"@"},
+ "@", "Digit2", KeyboardEvent.DOM_VK_2, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1}, chars:"\u2122", unmodifiedChars:"2"},
+ "\u2122", "Digit2", KeyboardEvent.DOM_VK_2, "\u2122", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u20AC", unmodifiedChars:"@"},
+ "\u20AC", "Digit2", KeyboardEvent.DOM_VK_2, "\u20AC", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"2", unmodifiedChars:"2"},
+ "\u2122", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0000", unmodifiedChars:"@"},
+ "\u20AC", "Digit2", KeyboardEvent.DOM_VK_2, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_2,
+ modifiers:{metaKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{}, chars:"3", unmodifiedChars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{shiftKey:1}, chars:"#", unmodifiedChars:"#"},
+ "#", "Digit3", KeyboardEvent.DOM_VK_3, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{ctrlKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"#"},
+ "#", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1}, chars:"\u00A3", unmodifiedChars:"3"},
+ "\u00A3", "Digit3", KeyboardEvent.DOM_VK_3, "\u00A3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u2039", unmodifiedChars:"#"},
+ "\u2039", "Digit3", KeyboardEvent.DOM_VK_3, "\u2039", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"3", unmodifiedChars:"3"},
+ "\u00A3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"#"},
+ "\u2039", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_3,
+ modifiers:{metaKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{}, chars:"4", unmodifiedChars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{shiftKey:1}, chars:"$", unmodifiedChars:"$"},
+ "$", "Digit4", KeyboardEvent.DOM_VK_4, "$", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{ctrlKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"$"},
+ "$", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1}, chars:"\u00A2", unmodifiedChars:"4"},
+ "\u00A2", "Digit4", KeyboardEvent.DOM_VK_4, "\u00A2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u203A", unmodifiedChars:"$"},
+ "\u203A", "Digit4", KeyboardEvent.DOM_VK_4, "\u203A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"4", unmodifiedChars:"4"},
+ "\u00A2", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"$"},
+ "\u203A", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_4,
+ modifiers:{metaKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{}, chars:"5", unmodifiedChars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{shiftKey:1}, chars:"%", unmodifiedChars:"%"},
+ "%", "Digit5", KeyboardEvent.DOM_VK_5, "%", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{ctrlKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"%"},
+ "%", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1}, chars:"\u221E", unmodifiedChars:"5"},
+ "\u221E", "Digit5", KeyboardEvent.DOM_VK_5, "\u221E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\uFB01", unmodifiedChars:"%"},
+ "\uFB01", "Digit5", KeyboardEvent.DOM_VK_5, "\uFB01", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"5", unmodifiedChars:"5"},
+ "\u221E", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"%"},
+ "\uFB01", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_5,
+ modifiers:{metaKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{}, chars:"6", unmodifiedChars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{shiftKey:1}, chars:"^", unmodifiedChars:"^"},
+ "^", "Digit6", KeyboardEvent.DOM_VK_6, "^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{ctrlKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001E", unmodifiedChars:"^"},
+ "^", "Digit6", KeyboardEvent.DOM_VK_6, "\u0000", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1}, chars:"\u00A7", unmodifiedChars:"6"},
+ "\u00A7", "Digit6", KeyboardEvent.DOM_VK_6, "\u00A7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\uFB02", unmodifiedChars:"^"},
+ "\uFB02", "Digit6", KeyboardEvent.DOM_VK_6, "\uFB02", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"6", unmodifiedChars:"6"},
+ "\u00A7", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001E", unmodifiedChars:"^"},
+ "\uFB02", "Digit6", KeyboardEvent.DOM_VK_6, "\u0000", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_6,
+ modifiers:{metaKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{}, chars:"7", unmodifiedChars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{shiftKey:1}, chars:"&", unmodifiedChars:"&"},
+ "&", "Digit7", KeyboardEvent.DOM_VK_7, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{ctrlKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"&"},
+ "&", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1}, chars:"\u00B6", unmodifiedChars:"7"},
+ "\u00B6", "Digit7", KeyboardEvent.DOM_VK_7, "\u00B6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u2021", unmodifiedChars:"&"},
+ "\u2021", "Digit7", KeyboardEvent.DOM_VK_7, "\u2021", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"7", unmodifiedChars:"7"},
+ "\u00B6", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"&"},
+ "\u2021", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_7,
+ modifiers:{metaKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{}, chars:"8", unmodifiedChars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{shiftKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "Digit8", KeyboardEvent.DOM_VK_8, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{ctrlKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"*"},
+ "*", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1}, chars:"\u2022", unmodifiedChars:"8"},
+ "\u2022", "Digit8", KeyboardEvent.DOM_VK_8, "\u2022", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00B0", unmodifiedChars:"*"},
+ "\u00B0", "Digit8", KeyboardEvent.DOM_VK_8, "\u00B0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"8", unmodifiedChars:"8"},
+ "\u2022", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"*"},
+ "\u00B0", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_8,
+ modifiers:{metaKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{}, chars:"9", unmodifiedChars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{shiftKey:1}, chars:"(", unmodifiedChars:"("},
+ "(", "Digit9", KeyboardEvent.DOM_VK_9, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{ctrlKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"9", unmodifiedChars:"("},
+ "(", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1}, chars:"\u00AA", unmodifiedChars:"9"},
+ "\u00AA", "Digit9", KeyboardEvent.DOM_VK_9, "\u00AA", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00B7", unmodifiedChars:"("},
+ "\u00B7", "Digit9", KeyboardEvent.DOM_VK_9, "\u00B7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"9", unmodifiedChars:"9"},
+ "\u00AA", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"9", unmodifiedChars:"("},
+ "\u00B7", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_9,
+ modifiers:{metaKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{}, chars:"0", unmodifiedChars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{shiftKey:1}, chars:")", unmodifiedChars:")"},
+ ")", "Digit0", KeyboardEvent.DOM_VK_0, ")", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{ctrlKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"0", unmodifiedChars:")"},
+ ")", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1}, chars:"\u00BA", unmodifiedChars:"0"},
+ "\u00BA", "Digit0", KeyboardEvent.DOM_VK_0, "\u00BA", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u201A", unmodifiedChars:")"},
+ "\u201A", "Digit0", KeyboardEvent.DOM_VK_0, "\u201A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"0", unmodifiedChars:"0"},
+ "\u00BA", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"0", unmodifiedChars:")"},
+ "\u201A", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_0,
+ modifiers:{metaKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // other chracters
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{}, chars:"`", unmodifiedChars:"`"},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{shiftKey:1}, chars:"~", unmodifiedChars:"~"},
+ "~", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "~", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{ctrlKey:1}, chars:"`", unmodifiedChars:"`"},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"`", unmodifiedChars:"~"},
+ "~", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{altKey:1}, chars:"", unmodifiedChars:"`"},
+ "Dead", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // dead key
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{altKey:1, shiftKey:1}, chars:"`", unmodifiedChars:"~"},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"`", unmodifiedChars:"`"},
+ "Dead", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"`", unmodifiedChars:"~"},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Grave,
+ modifiers:{metaKey:1}, chars:"`", unmodifiedChars:"`"},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{}, chars:"-", unmodifiedChars:"-"},
+ "-", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{shiftKey:1}, chars:"_", unmodifiedChars:"_"},
+ "_", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{ctrlKey:1}, chars:"\u001F", unmodifiedChars:"-"},
+ "-", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001F", unmodifiedChars:"_"},
+ "_", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{altKey:1}, chars:"\u2013", unmodifiedChars:"-"},
+ "\u2013", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "\u2013", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u2014", unmodifiedChars:"_"},
+ "\u2014", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "\u2014", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u001F", unmodifiedChars:"-"},
+ "\u2013", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001F", unmodifiedChars:"_"},
+ "\u2014", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{metaKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "-", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{}, chars:"=", unmodifiedChars:"="},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{shiftKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "Equal", KeyboardEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{ctrlKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"=", unmodifiedChars:"+"},
+ "+", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{altKey:1}, chars:"\u2260", unmodifiedChars:"="},
+ "\u2260", "Equal", KeyboardEvent.DOM_VK_EQUALS, "\u2260", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00B1", unmodifiedChars:"+"},
+ "\u00B1", "Equal", KeyboardEvent.DOM_VK_EQUALS, "\u00B1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"=", unmodifiedChars:"="},
+ "\u2260", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"=", unmodifiedChars:"+"},
+ "\u00B1", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Equal,
+ modifiers:{metaKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{}, chars:"[", unmodifiedChars:"["},
+ "[", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "[", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{shiftKey:1}, chars:"{", unmodifiedChars:"{"},
+ "{", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{ctrlKey:1}, chars:"\u001B", unmodifiedChars:"["},
+ "[", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001B", unmodifiedChars:"{"},
+ "{", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{altKey:1}, chars:"\u201C", unmodifiedChars:"["},
+ "\u201C", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "\u201C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u201D", unmodifiedChars:"{"},
+ "\u201D", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "\u201D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u001B", unmodifiedChars:"["},
+ "\u201C", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001B", unmodifiedChars:"{"},
+ "\u201D", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{metaKey:1}, chars:"[", unmodifiedChars:"["},
+ "[", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "[", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{}, chars:"]", unmodifiedChars:"]"},
+ "]", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "]", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{shiftKey:1}, chars:"}", unmodifiedChars:"}"},
+ "}", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{ctrlKey:1}, chars:"\u001D", unmodifiedChars:"]"},
+ "]", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001D", unmodifiedChars:"}"},
+ "}", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{altKey:1}, chars:"\u2018", unmodifiedChars:"]"},
+ "\u2018", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "\u2018", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u2019", unmodifiedChars:"}"},
+ "\u2019", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "\u2019", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u001D", unmodifiedChars:"]"},
+ "\u2018", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001D", unmodifiedChars:"}"},
+ "\u2019", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ modifiers:{metaKey:1}, chars:"]", unmodifiedChars:"]"},
+ "]", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "]", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{}, chars:"\\", unmodifiedChars:"\\"},
+ "\\", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\\", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{shiftKey:1}, chars:"|", unmodifiedChars:"|"},
+ "|", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "|", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{ctrlKey:1}, chars:"\u001C", unmodifiedChars:"\\"},
+ "\\", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001C", unmodifiedChars:"|"},
+ "|", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{altKey:1}, chars:"\u00AB", unmodifiedChars:"\\"},
+ "\u00AB", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\u00AB", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00BB", unmodifiedChars:"|"},
+ "\u00BB", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\u00BB", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u001C", unmodifiedChars:"\\"},
+ "\u00AB", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001C", unmodifiedChars:"|"},
+ "\u00BB", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ modifiers:{metaKey:1}, chars:"\\", unmodifiedChars:"\\"},
+ "\\", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\\", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{}, chars:";", unmodifiedChars:";"},
+ ";", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{shiftKey:1}, chars:":", unmodifiedChars:":"},
+ ":", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ":", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{ctrlKey:1}, chars:";", unmodifiedChars:";"},
+ ";", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:";", unmodifiedChars:":"},
+ ":", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{altKey:1}, chars:"\u2026", unmodifiedChars:";"},
+ "\u2026", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, "\u2026", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00DA", unmodifiedChars:":"},
+ "\u00DA", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, "\u00DA", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{altKey:1, ctrlKey:1}, chars:";", unmodifiedChars:";"},
+ "\u2026", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:";", unmodifiedChars:":"},
+ "\u00DA", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{metaKey:1}, chars:";", unmodifiedChars:";"},
+ ";", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{}, chars:"'", unmodifiedChars:"'"},
+ "'", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{shiftKey:1}, chars:"\"", unmodifiedChars:"\""},
+ "\"", "Quote", KeyboardEvent.DOM_VK_QUOTE, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{ctrlKey:1}, chars:"'", unmodifiedChars:"'"},
+ "'", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"'", unmodifiedChars:"\""},
+ "\"", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{altKey:1}, chars:"\u00E6", unmodifiedChars:"'"},
+ "\u00E6", "Quote", KeyboardEvent.DOM_VK_QUOTE, "\u00E6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C6", unmodifiedChars:"\""},
+ "\u00C6", "Quote", KeyboardEvent.DOM_VK_QUOTE, "\u00C6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"'", unmodifiedChars:"'"},
+ "\u00E6", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"'", unmodifiedChars:"\""},
+ "\u00C6", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{metaKey:1}, chars:"'", unmodifiedChars:"'"},
+ "'", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{}, chars:",", unmodifiedChars:","},
+ ",", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{shiftKey:1}, chars:"<", unmodifiedChars:"<"},
+ "<", "Comma", KeyboardEvent.DOM_VK_COMMA, "<", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{ctrlKey:1}, chars:",", unmodifiedChars:","},
+ ",", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:",", unmodifiedChars:"<"},
+ "<", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{altKey:1}, chars:"\u2264", unmodifiedChars:","},
+ "\u2264", "Comma", KeyboardEvent.DOM_VK_COMMA, "\u2264", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00AF", unmodifiedChars:"<"},
+ "\u00AF", "Comma", KeyboardEvent.DOM_VK_COMMA, "\u00AF", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{altKey:1, ctrlKey:1}, chars:",", unmodifiedChars:","},
+ "\u2264", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:",", unmodifiedChars:"<"},
+ "\u00AF", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Comma,
+ modifiers:{metaKey:1}, chars:",", unmodifiedChars:","},
+ ",", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{}, chars:".", unmodifiedChars:"."},
+ ".", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{shiftKey:1}, chars:">", unmodifiedChars:">"},
+ ">", "Period", KeyboardEvent.DOM_VK_PERIOD, ">", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{ctrlKey:1}, chars:".", unmodifiedChars:"."},
+ ".", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:".", unmodifiedChars:">"},
+ ">", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{altKey:1}, chars:"\u2265", unmodifiedChars:"."},
+ "\u2265", "Period", KeyboardEvent.DOM_VK_PERIOD, "\u2265", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u02D8", unmodifiedChars:">"},
+ "\u02D8", "Period", KeyboardEvent.DOM_VK_PERIOD, "\u02D8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{altKey:1, ctrlKey:1}, chars:".", unmodifiedChars:"."},
+ "\u2265", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:".", unmodifiedChars:">"},
+ "\u02D8", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{metaKey:1}, chars:".", unmodifiedChars:"."},
+ ".", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{}, chars:"/", unmodifiedChars:"/"},
+ "/", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{shiftKey:1}, chars:"?", unmodifiedChars:"?"},
+ "?", "Slash", KeyboardEvent.DOM_VK_SLASH, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{ctrlKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"/", unmodifiedChars:"?"},
+ "?", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{altKey:1}, chars:"\u00F7", unmodifiedChars:"/"},
+ "\u00F7", "Slash", KeyboardEvent.DOM_VK_SLASH, "\u00F7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00BF", unmodifiedChars:"?"},
+ "\u00BF", "Slash", KeyboardEvent.DOM_VK_SLASH, "\u00BF", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"/", unmodifiedChars:"/"},
+ "\u00F7", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"/", unmodifiedChars:"?"},
+ "\u00BF", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Slash,
+ modifiers:{metaKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // numpad
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad1,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad2,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad3,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad4,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad5,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad6,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad7,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad8,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad9,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Keypad0,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEquals,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "NumpadEqual", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadDivide,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMultiply,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"*", unmodifiedChars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadMinus,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadPlus,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"+", unmodifiedChars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_KeypadEnter,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:"\u0003", unmodifiedChars:"\u0003"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, shiftKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, ctrlKey:1, shiftKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, altKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, altKey:1, shiftKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, altKey:1, ctrlKey:1, shiftKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_JIS_KeypadComma,
+ modifiers:{numericKeyPadKey:1, metaKey:1}, chars:",", unmodifiedChars:","},
+ ",", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ",", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+
+ // French, numeric
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{}, chars:"&", unmodifiedChars:"&"},
+ "&", "Digit1", KeyboardEvent.DOM_VK_1, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{ctrlKey:1}, chars:"1", unmodifiedChars:"&"},
+ "&", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1}, chars:"\uF8FF", unmodifiedChars:"&"},
+ "\uF8FF", "Digit1", KeyboardEvent.DOM_VK_1, "\uF8FF", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:"", unmodifiedChars:"1"},
+ "Dead", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"1", unmodifiedChars:"&"},
+ "\uF8FF", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "Dead", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{metaKey:1}, chars:"&", unmodifiedChars:"&"},
+ "&", "Digit1", KeyboardEvent.DOM_VK_1, "&", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{}, chars:"\u00E9", unmodifiedChars:"\u00E9"},
+ "\u00E9", "Digit2", KeyboardEvent.DOM_VK_2, "\u00E9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{ctrlKey:1}, chars:"2", unmodifiedChars:"\u00E9"},
+ "\u00E9", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1}, chars:"\u00EB", unmodifiedChars:"\u00E9"},
+ "\u00EB", "Digit2", KeyboardEvent.DOM_VK_2, "\u00EB", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u201E", unmodifiedChars:"2"},
+ "\u201E", "Digit2", KeyboardEvent.DOM_VK_2, "\u201E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"2", unmodifiedChars:"\u00E9"},
+ "\u00EB", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "\u201E", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{metaKey:1}, chars:"\u00E9", unmodifiedChars:"\u00E9"},
+ "\u00E9", "Digit2", KeyboardEvent.DOM_VK_2, "\u00E9", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_2,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"2", unmodifiedChars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{}, chars:"\"", unmodifiedChars:"\""},
+ "\"", "Digit3", KeyboardEvent.DOM_VK_3, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{shiftKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{ctrlKey:1}, chars:"3", unmodifiedChars:"\""},
+ "\"", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1}, chars:"\u201C", unmodifiedChars:"\""},
+ "\u201C", "Digit3", KeyboardEvent.DOM_VK_3, "\u201C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u201D", unmodifiedChars:"3"},
+ "\u201D", "Digit3", KeyboardEvent.DOM_VK_3, "\u201D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"3", unmodifiedChars:"\""},
+ "\u201C", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"3", unmodifiedChars:"3"},
+ "\u201D", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ modifiers:{metaKey:1}, chars:"\"", unmodifiedChars:"\""},
+ "\"", "Digit3", KeyboardEvent.DOM_VK_3, "\"", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Cmd+Shift+3 is a shortcut key of taking a snapshot
+ // yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_3,
+ // modifiers:{metaKey:1, shiftKey:1}, chars:"\"", unmodifiedChars:"\""},
+ // "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{}, chars:"'", unmodifiedChars:"'"},
+ "'", "Digit4", KeyboardEvent.DOM_VK_4, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{ctrlKey:1}, chars:"4", unmodifiedChars:"'"},
+ "'", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1}, chars:"\u2018", unmodifiedChars:"'"},
+ "\u2018", "Digit4", KeyboardEvent.DOM_VK_4, "\u2018", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u2019", unmodifiedChars:"4"},
+ "\u2019", "Digit4", KeyboardEvent.DOM_VK_4, "\u2019", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"4", unmodifiedChars:"'"},
+ "\u2018", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ "\u2019", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ modifiers:{metaKey:1}, chars:"'", unmodifiedChars:"'"},
+ "'", "Digit4", KeyboardEvent.DOM_VK_4, "'", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Cmd+Shift+4 is a shortcut key of taking a snapshot in specific range
+ // yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_4,
+ // modifiers:{metaKey:1, shiftKey:1}, chars:"4", unmodifiedChars:"4"},
+ // "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{}, chars:"(", unmodifiedChars:"("},
+ "(", "Digit5", KeyboardEvent.DOM_VK_5, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{ctrlKey:1}, chars:"5", unmodifiedChars:"("},
+ "(", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1}, chars:"{", unmodifiedChars:"("},
+ "{", "Digit5", KeyboardEvent.DOM_VK_5, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1, shiftKey:1}, chars:"[", unmodifiedChars:"5"},
+ "[", "Digit5", KeyboardEvent.DOM_VK_5, "[", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"5", unmodifiedChars:"("},
+ "{", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "[", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{metaKey:1}, chars:"(", unmodifiedChars:"("},
+ "(", "Digit5", KeyboardEvent.DOM_VK_5, "(", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_5,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{}, chars:"\u00A7", unmodifiedChars:"\u00A7"},
+ "\u00A7", "Digit6", KeyboardEvent.DOM_VK_6, "\u00A7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{shiftKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{ctrlKey:1}, chars:"\u001D", unmodifiedChars:"\u00A7"},
+ "\u00A7", "Digit6", KeyboardEvent.DOM_VK_6, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001D", unmodifiedChars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1}, chars:"\u00B6", unmodifiedChars:"\u00A7"},
+ "\u00B6", "Digit6", KeyboardEvent.DOM_VK_6, "\u00B6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00E5", unmodifiedChars:"6"},
+ "\u00E5", "Digit6", KeyboardEvent.DOM_VK_6, "\u00E5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u001D", unmodifiedChars:"\u00A7"},
+ "\u00B6", "Digit6", KeyboardEvent.DOM_VK_6, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001D", unmodifiedChars:"6"},
+ "\u00E5", "Digit6", KeyboardEvent.DOM_VK_6, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{metaKey:1}, chars:"\u00A7", unmodifiedChars:"\u00A7"},
+ "\u00A7", "Digit6", KeyboardEvent.DOM_VK_6, "\u00A7", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{}, chars:"\u00E8", unmodifiedChars:"\u00E8"},
+ "\u00E8", "Digit7", KeyboardEvent.DOM_VK_7, "\u00E8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{ctrlKey:1}, chars:"7", unmodifiedChars:"\u00E8"},
+ "\u00E8", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1}, chars:"\u00AB", unmodifiedChars:"\u00E8"},
+ "\u00AB", "Digit7", KeyboardEvent.DOM_VK_7, "\u00AB", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00BB", unmodifiedChars:"7"},
+ "\u00BB", "Digit7", KeyboardEvent.DOM_VK_7, "\u00BB", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"7", unmodifiedChars:"\u00E8"},
+ "\u00AB", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "\u00BB", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{metaKey:1}, chars:"\u00E8", unmodifiedChars:"\u00E8"},
+ "\u00E8", "Digit7", KeyboardEvent.DOM_VK_7, "\u00E8", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_7,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{}, chars:"!", unmodifiedChars:"!"},
+ "!", "Digit8", KeyboardEvent.DOM_VK_8, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{ctrlKey:1}, chars:"8", unmodifiedChars:"!"},
+ "!", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1}, chars:"\u00A1", unmodifiedChars:"!"},
+ "\u00A1", "Digit8", KeyboardEvent.DOM_VK_8, "\u00A1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00DB", unmodifiedChars:"8"},
+ "\u00DB", "Digit8", KeyboardEvent.DOM_VK_8, "\u00DB", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"8", unmodifiedChars:"!"},
+ "\u00A1", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "\u00DB", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{metaKey:1}, chars:"!", unmodifiedChars:"!"},
+ "!", "Digit8", KeyboardEvent.DOM_VK_8, "!", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_8,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{}, chars:"\u00E7", unmodifiedChars:"\u00E7"},
+ "\u00E7", "Digit9", KeyboardEvent.DOM_VK_9, "\u00E7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{shiftKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{ctrlKey:1}, chars:"\u001C", unmodifiedChars:"\u00E7"},
+ "\u00E7", "Digit9", KeyboardEvent.DOM_VK_9, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001C", unmodifiedChars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1}, chars:"\u00C7", unmodifiedChars:"\u00E7"},
+ "\u00C7", "Digit9", KeyboardEvent.DOM_VK_9, "\u00C7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C1", unmodifiedChars:"9"},
+ "\u00C1", "Digit9", KeyboardEvent.DOM_VK_9, "\u00C1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"\u001C", unmodifiedChars:"\u00E7"},
+ "\u00C7", "Digit9", KeyboardEvent.DOM_VK_9, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"\u001C", unmodifiedChars:"9"},
+ "\u00C1", "Digit9", KeyboardEvent.DOM_VK_9, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{metaKey:1}, chars:"\u00E7", unmodifiedChars:"\u00E7"},
+ "\u00E7", "Digit9", KeyboardEvent.DOM_VK_9, "\u00E7", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{}, chars:"\u00E0", unmodifiedChars:"\u00E0"},
+ "\u00E0", "Digit0", KeyboardEvent.DOM_VK_0, "\u00E0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{shiftKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{ctrlKey:1}, chars:"", unmodifiedChars:"\u00E0"},
+ "\u00E0", "Digit0", KeyboardEvent.DOM_VK_0, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"", unmodifiedChars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1}, chars:"\u00F8", unmodifiedChars:"\u00E0"},
+ "\u00F8", "Digit0", KeyboardEvent.DOM_VK_0, "\u00F8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00D8", unmodifiedChars:"0"},
+ "\u00D8", "Digit0", KeyboardEvent.DOM_VK_0, "\u00D8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1, ctrlKey:1}, chars:"", unmodifiedChars:"\u00E0"},
+ "\u00F8", "Digit0", KeyboardEvent.DOM_VK_0, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:"", unmodifiedChars:"0"},
+ "\u00D8", "Digit0", KeyboardEvent.DOM_VK_0, "\u0000" /* TODO */, SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{metaKey:1}, chars:"\u00E0", unmodifiedChars:"\u00E0"},
+ "\u00E0", "Digit0", KeyboardEvent.DOM_VK_0, "\u00E0", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Thai
+ // keycode should be DOM_VK_[A-Z] of the key on the latest ASCII capable keyboard layout is for alphabet
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_A,
+ modifiers:{}, chars:"\u0E1F", unmodifiedChars:"\u0E1F"},
+ "\u0E1F", "KeyA", KeyboardEvent.DOM_VK_A, "\u0E1F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // keycode should be shifted character if unshifted character isn't an ASCII character
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{}, chars:"\u0E07", unmodifiedChars:"\u0E07"},
+ "\u0E07", "Quote", KeyboardEvent.DOM_VK_PERIOD, "\u0E07", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // keycode should be same as ANSI keyboard layout's key which causes the native virtual keycode
+ // if the character of the key on the latest ASCII capable keyboard layout isn't for alphabet
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{}, chars:"\u0E43", unmodifiedChars:"\u0E43"},
+ "\u0E43", "Period", KeyboardEvent.DOM_VK_PERIOD, "\u0E43", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // keycode should be DOM_VK_[0-9] if the key on the latest ASCII capable keyboard layout is for numeric
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_1,
+ modifiers:{}, chars:"\u0E45", unmodifiedChars:"\u0E45"},
+ "\u0E45", "Digit1", KeyboardEvent.DOM_VK_1, "\u0E45", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_2,
+ modifiers:{}, chars:"/", unmodifiedChars:"/"},
+ "/", "Digit2", KeyboardEvent.DOM_VK_2, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_3,
+ modifiers:{}, chars:"_", unmodifiedChars:"_"},
+ "_", "Digit3", KeyboardEvent.DOM_VK_3, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_4,
+ modifiers:{}, chars:"\u0E20", unmodifiedChars:"\u0E20"},
+ "\u0E20", "Digit4", KeyboardEvent.DOM_VK_4, "\u0E20", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_5,
+ modifiers:{}, chars:"\u0E16", unmodifiedChars:"\u0E16"},
+ "\u0E16", "Digit5", KeyboardEvent.DOM_VK_5, "\u0E16", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_6,
+ modifiers:{}, chars:"\u0E38", unmodifiedChars:"\u0E38"},
+ "\u0E38", "Digit6", KeyboardEvent.DOM_VK_6, "\u0E38", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_7,
+ modifiers:{}, chars:"\u0E36", unmodifiedChars:"\u0E36"},
+ "\u0E36", "Digit7", KeyboardEvent.DOM_VK_7, "\u0E36", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_8,
+ modifiers:{}, chars:"\u0E04", unmodifiedChars:"\u0E04"},
+ "\u0E04", "Digit8", KeyboardEvent.DOM_VK_8, "\u0E04", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_9,
+ modifiers:{}, chars:"\u0E15", unmodifiedChars:"\u0E15"},
+ "\u0E15", "Digit9", KeyboardEvent.DOM_VK_9, "\u0E15", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_THAI, keyCode:MAC_VK_ANSI_0,
+ modifiers:{}, chars:"\u0E08", unmodifiedChars:"\u0E08"},
+ "\u0E08", "Digit0", KeyboardEvent.DOM_VK_0, "\u0E08", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Dvorak-Qwerty, layout should be changed when Command key is pressed.
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_S,
+ modifiers:{}, chars:"o", unmodifiedChars:"o"},
+ "o", "KeyS", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_S,
+ modifiers:{shiftKey:1}, chars:"O", unmodifiedChars:"O"},
+ "O", "KeyS", KeyboardEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_S,
+ modifiers:{ctrlKey:1}, chars:"\u000F", unmodifiedChars:"o"},
+ "o", "KeyS", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_S,
+ modifiers:{altKey:1}, chars:"\u00F8", unmodifiedChars:"o"},
+ "\u00F8", "KeyS", KeyboardEvent.DOM_VK_O, "\u00F8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_S,
+ modifiers:{metaKey:1}, chars:"s", unmodifiedChars:"o"},
+ "s", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_D,
+ modifiers:{}, chars:"e", unmodifiedChars:"e"},
+ "e", "KeyD", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_D,
+ modifiers:{shiftKey:1}, chars:"E", unmodifiedChars:"E"},
+ "E", "KeyD", KeyboardEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_D,
+ modifiers:{ctrlKey:1}, chars:"\u0005", unmodifiedChars:"e"},
+ "e", "KeyD", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_D,
+ modifiers:{altKey:1}, chars:"", unmodifiedChars:"e"},
+ "Dead", "KeyD", KeyboardEvent.DOM_VK_E, "\u00B4", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // dead key
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_D,
+ modifiers:{metaKey:1}, chars:"d", unmodifiedChars:"e"},
+ "d", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_I,
+ modifiers:{metaKey:1, altKey:1}, chars:"^", unmodifiedChars:"c"},
+ "^", "KeyI", KeyboardEvent.DOM_VK_I, "^", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_I,
+ modifiers:{metaKey:1, altKey:1, shiftKey:1}, chars:"\u02C6", unmodifiedChars:"C"},
+ "\u02C6", "KeyI", KeyboardEvent.DOM_VK_I, "\u02C6", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Arabic - PC keyboard layout inputs 2 or more characters with some key.
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_G,
+ modifiers:{shiftKey:1}, chars:"\u0644\u0623", unmodifiedChars:"\u0644\u0623"},
+ ["\u0644\u0623", "\u0644", "\u0623", "\u0644\u0623"], "KeyG", KeyboardEvent.DOM_VK_G, "\u0644\u0623", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_T,
+ modifiers:{shiftKey:1}, chars:"\u0644\u0625", unmodifiedChars:"\u0644\u0625"},
+ ["\u0644\u0625", "\u0644", "\u0625", "\u0644\u0625"], "KeyT", KeyboardEvent.DOM_VK_T, "\u0644\u0625", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_B,
+ modifiers:{shiftKey:1}, chars:"\u0644\u0622", unmodifiedChars:"\u0644\u0622"},
+ ["\u0644\u0622", "\u0644", "\u0622", "\u0644\u0622"], "KeyB", KeyboardEvent.DOM_VK_B, "\u0644\u0622", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_B,
+ modifiers:{}, chars:"\u0644\u0627", unmodifiedChars:"\u0644\u0627"},
+ ["\u0644\u0627", "\u0644", "\u0627", "\u0644\u0627"], "KeyB", KeyboardEvent.DOM_VK_B, "\u0644\u0627", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ cleanup();
+ }
+
+ function* testKeysOnWindows()
+ {
+ // On Windows, you can use Spy++ or Winspector (free) to watch window messages.
+ // The keyCode is given by the wParam of the last WM_KEYDOWN message. The
+ // chars string is given by the wParam of the WM_CHAR message. unmodifiedChars
+ // is not needed on Windows.
+
+ // Shift-ctrl-alt generates no WM_CHAR, but we still get a keypress
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:""},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Greek plain text
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u03b1"},
+ "\u03b1", "KeyA", KeyboardEvent.DOM_VK_A, "\u03b1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"\u0391"},
+ "\u0391", "KeyA", KeyboardEvent.DOM_VK_A, "\u0391", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Greek ctrl keys produce Latin charcodes
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1}, chars:"\u0001"},
+ "\u03b1", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0001"},
+ "\u0391", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Caps Lock key event
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_CAPITAL,
+ modifiers:{capsLockKey:1}, chars:""},
+ "CapsLock", "CapsLock", KeyboardEvent.DOM_VK_CAPS_LOCK, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_CAPITAL,
+ modifiers:{capsLockKey:0}, chars:""},
+ "CapsLock", "CapsLock", KeyboardEvent.DOM_VK_CAPS_LOCK, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Shift keys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_LSHIFT,
+ modifiers:{shiftKey:1}, chars:""},
+ "Shift", "ShiftLeft", KeyboardEvent.DOM_VK_SHIFT, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RSHIFT,
+ modifiers:{shiftRightKey:1}, chars:""},
+ "Shift", "ShiftRight", KeyboardEvent.DOM_VK_SHIFT, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // Ctrl keys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_LCONTROL,
+ modifiers:{ctrlKey:1}, chars:""},
+ "Control", "ControlLeft", KeyboardEvent.DOM_VK_CONTROL, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RCONTROL,
+ modifiers:{ctrlRightKey:1}, chars:""},
+ "Control", "ControlRight", KeyboardEvent.DOM_VK_CONTROL, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // Alt keys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_LMENU,
+ modifiers:{altKey:1}, chars:""},
+ "Alt", "AltLeft", KeyboardEvent.DOM_VK_ALT, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RMENU,
+ modifiers:{altRightKey:1}, chars:""},
+ "Alt", "AltRight", KeyboardEvent.DOM_VK_ALT, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // Win keys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_LWIN,
+ modifiers:{}, chars:""},
+ "OS" /* bug 1232918 */, "OSLeft", KeyboardEvent.DOM_VK_WIN, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_LEFT);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RWIN,
+ modifiers:{}, chars:""},
+ "OS" /* bug 1232918 */, "OSRight", KeyboardEvent.DOM_VK_WIN, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_RIGHT);
+
+ // all keys on keyboard (keyCode test)
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_BACK,
+ modifiers:{}, chars:"\u0008"},
+ "Backspace", "Backspace", KeyboardEvent.DOM_VK_BACK_SPACE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_TAB,
+ modifiers:{}, chars:"\t"},
+ "Tab", "Tab", KeyboardEvent.DOM_VK_TAB, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RETURN,
+ modifiers:{}, chars:"\r"},
+ "Enter", "Enter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_PAUSE,
+ modifiers:{}, chars:""},
+ "Pause", "Pause", KeyboardEvent.DOM_VK_PAUSE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_KANA,
+ modifiers:{}, chars:""},
+ "Unidentified", "", KeyboardEvent.DOM_VK_KANA, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_JUNJA,
+ modifiers:{}, chars:""},
+ "JunjaMode", "", KeyboardEvent.DOM_VK_JUNJA, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_FINAL,
+ modifiers:{}, chars:""},
+ "FinalMode", "", KeyboardEvent.DOM_VK_FINAL, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_KANJI,
+ modifiers:{}, chars:""},
+ "Unidentified", "", KeyboardEvent.DOM_VK_KANJI, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_ESCAPE,
+ modifiers:{}, chars:""},
+ "Escape", "Escape", KeyboardEvent.DOM_VK_ESCAPE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_CONVERT,
+ modifiers:{}, chars:""},
+ "Convert", "", KeyboardEvent.DOM_VK_CONVERT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NONCONVERT,
+ modifiers:{}, chars:""},
+ "NonConvert", "", KeyboardEvent.DOM_VK_NONCONVERT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_ACCEPT,
+ modifiers:{}, chars:""},
+ "Accept", "", KeyboardEvent.DOM_VK_ACCEPT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_MODECHANGE,
+ modifiers:{}, chars:""},
+ "ModeChange", "", KeyboardEvent.DOM_VK_MODECHANGE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SPACE,
+ modifiers:{}, chars:" "},
+ " ", "Space", KeyboardEvent.DOM_VK_SPACE, " ", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Ctrl+Space causes WM_CHAR with ' '. However, its keypress event's ctrlKey state shouldn't be false.
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SPACE,
+ modifiers:{ctrlKey:1}, chars:" "},
+ " ", "Space", KeyboardEvent.DOM_VK_SPACE, " ", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SELECT,
+ modifiers:{}, chars:""},
+ "Select", "", KeyboardEvent.DOM_VK_SELECT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_PRINT,
+ modifiers:{}, chars:""},
+ "Unidentified", "", KeyboardEvent.DOM_VK_PRINT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_EXECUTE,
+ modifiers:{}, chars:""},
+ "Execute", "", KeyboardEvent.DOM_VK_EXECUTE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SNAPSHOT,
+ modifiers:{}, chars:""},
+ "PrintScreen", "PrintScreen", KeyboardEvent.DOM_VK_PRINTSCREEN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_HELP,
+ modifiers:{}, chars:""},
+ "Help", "", KeyboardEvent.DOM_VK_HELP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SLEEP,
+ modifiers:{}, chars:""},
+ "Standby", "", KeyboardEvent.DOM_VK_SLEEP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_PRIOR,
+ modifiers:{}, chars:""},
+ "PageUp", "PageUp", KeyboardEvent.DOM_VK_PAGE_UP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NEXT,
+ modifiers:{}, chars:""},
+ "PageDown", "PageDown", KeyboardEvent.DOM_VK_PAGE_DOWN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_END,
+ modifiers:{}, chars:""},
+ "End", "End", KeyboardEvent.DOM_VK_END, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_HOME,
+ modifiers:{}, chars:""},
+ "Home", "Home", KeyboardEvent.DOM_VK_HOME, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_LEFT,
+ modifiers:{}, chars:""},
+ "ArrowLeft", "ArrowLeft", KeyboardEvent.DOM_VK_LEFT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_UP,
+ modifiers:{}, chars:""},
+ "ArrowUp", "ArrowUp", KeyboardEvent.DOM_VK_UP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RIGHT,
+ modifiers:{}, chars:""},
+ "ArrowRight", "ArrowRight", KeyboardEvent.DOM_VK_RIGHT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_DOWN,
+ modifiers:{}, chars:""},
+ "ArrowDown", "ArrowDown", KeyboardEvent.DOM_VK_DOWN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_INSERT,
+ modifiers:{}, chars:""},
+ "Insert", "Insert", KeyboardEvent.DOM_VK_INSERT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_DELETE,
+ modifiers:{}, chars:""},
+ "Delete", "Delete", KeyboardEvent.DOM_VK_DELETE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Backspace and Enter are handled with special path in mozilla::widget::NativeKey. So, let's test them with modifiers too.
+ // Note that when both Ctrl and Alt are pressed, they don't cause WM_(SYS)CHAR message.
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_BACK,
+ modifiers:{ctrlKey:1}, chars:"\u007F"},
+ "Backspace", "Backspace", KeyboardEvent.DOM_VK_BACK_SPACE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_BACK,
+ modifiers:{altKey:1}, chars:"\u0008"},
+ "Backspace", "Backspace", KeyboardEvent.DOM_VK_BACK_SPACE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_BACK,
+ modifiers:{ctrl:1, altKey:1}, chars:""},
+ "Backspace", "Backspace", KeyboardEvent.DOM_VK_BACK_SPACE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RETURN,
+ modifiers:{ctrlKey:1}, chars:"\n"},
+ "Enter", "Enter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RETURN,
+ modifiers:{altKey:1}, chars:"\r"},
+ "Enter", "Enter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RETURN,
+ modifiers:{ctrl:1, altKey:1}, chars:""},
+ "Enter", "Enter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Even non-printable key could be mapped as a printable key.
+ // Only "keyup" event cannot know if it *did* cause inputting text.
+ // Therefore, only "keydown" and "keypress" event's key value should be the character inputted by the key.
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F4,
+ modifiers:{}, chars:"a"},
+ ["a", "a", "F4"], "F4", KeyboardEvent.DOM_VK_F4, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Even if key message is processed by IME, when the key causes inputting text,
+ // keypress event(s) should be fired.
+ const WIN_VK_PROCESSKEY_WITH_SC_A = WIN_VK_PROCESSKEY | 0x001E0000;
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_PROCESSKEY_WITH_SC_A,
+ modifiers:{}, chars:"a"},
+ ["a", "a", "Process"], "KeyA", KeyboardEvent.DOM_VK_PROCESSKEY, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_PROCESSKEY_WITH_SC_A,
+ modifiers:{altKey:1}, chars:"a"},
+ ["a", "a", "Process"], "KeyA", KeyboardEvent.DOM_VK_PROCESSKEY, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // US
+ // Alphabet
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"A"},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1}, chars:"\u0001"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0001"},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{altKey:1}, chars:"a"},
+ "a", "KeyA", KeyboardEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{altKey:1, shiftKey:1}, chars:"A"},
+ "A", "KeyA", KeyboardEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+ modifiers:{}, chars:"b"},
+ "b", "KeyB", KeyboardEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+ modifiers:{shiftKey:1}, chars:"B"},
+ "B", "KeyB", KeyboardEvent.DOM_VK_B, "B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+ modifiers:{ctrlKey:1}, chars:"\u0002"},
+ "b", "KeyB", KeyboardEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0002"},
+ "B", "KeyB", KeyboardEvent.DOM_VK_B, "B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+ modifiers:{altKey:1}, chars:"b"},
+ "b", "KeyB", KeyboardEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+ modifiers:{altKey:1, shiftKey:1}, chars:"B"},
+ "B", "KeyB", KeyboardEvent.DOM_VK_B, "B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+ modifiers:{}, chars:"c"},
+ "c", "KeyC", KeyboardEvent.DOM_VK_C, "c", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+ modifiers:{shiftKey:1}, chars:"C"},
+ "C", "KeyC", KeyboardEvent.DOM_VK_C, "C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+ modifiers:{ctrlKey:1}, chars:"\u0003"},
+ "c", "KeyC", KeyboardEvent.DOM_VK_C, "c", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0003"},
+ "C", "KeyC", KeyboardEvent.DOM_VK_C, "C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+ modifiers:{altKey:1}, chars:"c"},
+ "c", "KeyC", KeyboardEvent.DOM_VK_C, "c", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+ modifiers:{altKey:1, shiftKey:1}, chars:"C"},
+ "C", "KeyC", KeyboardEvent.DOM_VK_C, "C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+ modifiers:{}, chars:"d"},
+ "d", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+ modifiers:{shiftKey:1}, chars:"D"},
+ "D", "KeyD", KeyboardEvent.DOM_VK_D, "D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+ modifiers:{ctrlKey:1}, chars:"\u0004"},
+ "d", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0004"},
+ "D", "KeyD", KeyboardEvent.DOM_VK_D, "D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+ modifiers:{altKey:1}, chars:"d"},
+ "d", "KeyD", KeyboardEvent.DOM_VK_D, "d", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+ modifiers:{altKey:1, shiftKey:1}, chars:"D"},
+ "D", "KeyD", KeyboardEvent.DOM_VK_D, "D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+ modifiers:{}, chars:"e"},
+ "e", "KeyE", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+ modifiers:{shiftKey:1}, chars:"E"},
+ "E", "KeyE", KeyboardEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+ modifiers:{ctrlKey:1}, chars:"\u0005"},
+ "e", "KeyE", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0005"},
+ "E", "KeyE", KeyboardEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+ modifiers:{altKey:1}, chars:"e"},
+ "e", "KeyE", KeyboardEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+ modifiers:{altKey:1, shiftKey:1}, chars:"E"},
+ "E", "KeyE", KeyboardEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+ modifiers:{}, chars:"f"},
+ "f", "KeyF", KeyboardEvent.DOM_VK_F, "f", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+ modifiers:{shiftKey:1}, chars:"F"},
+ "F", "KeyF", KeyboardEvent.DOM_VK_F, "F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+ modifiers:{ctrlKey:1}, chars:"\u0006"},
+ "f", "KeyF", KeyboardEvent.DOM_VK_F, "f", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0006"},
+ "F", "KeyF", KeyboardEvent.DOM_VK_F, "F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+ modifiers:{altKey:1}, chars:"f"},
+ "f", "KeyF", KeyboardEvent.DOM_VK_F, "f", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+ modifiers:{altKey:1, shiftKey:1}, chars:"F"},
+ "F", "KeyF", KeyboardEvent.DOM_VK_F, "F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+ modifiers:{}, chars:"g"},
+ "g", "KeyG", KeyboardEvent.DOM_VK_G, "g", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+ modifiers:{shiftKey:1}, chars:"G"},
+ "G", "KeyG", KeyboardEvent.DOM_VK_G, "G", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+ modifiers:{ctrlKey:1}, chars:"\u0007"},
+ "g", "KeyG", KeyboardEvent.DOM_VK_G, "g", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0007"},
+ "G", "KeyG", KeyboardEvent.DOM_VK_G, "G", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+ modifiers:{altKey:1}, chars:"g"},
+ "g", "KeyG", KeyboardEvent.DOM_VK_G, "g", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+ modifiers:{altKey:1, shiftKey:1}, chars:"G"},
+ "G", "KeyG", KeyboardEvent.DOM_VK_G, "G", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+ modifiers:{}, chars:"h"},
+ "h", "KeyH", KeyboardEvent.DOM_VK_H, "h", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+ modifiers:{shiftKey:1}, chars:"H"},
+ "H", "KeyH", KeyboardEvent.DOM_VK_H, "H", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+ modifiers:{ctrlKey:1}, chars:"\u0008"},
+ "h", "KeyH", KeyboardEvent.DOM_VK_H, "h", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0008"},
+ "H", "KeyH", KeyboardEvent.DOM_VK_H, "H", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+ modifiers:{altKey:1}, chars:"h"},
+ "h", "KeyH", KeyboardEvent.DOM_VK_H, "h", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+ modifiers:{altKey:1, shiftKey:1}, chars:"H"},
+ "H", "KeyH", KeyboardEvent.DOM_VK_H, "H", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+ modifiers:{}, chars:"i"},
+ "i", "KeyI", KeyboardEvent.DOM_VK_I, "i", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+ modifiers:{shiftKey:1}, chars:"I"},
+ "I", "KeyI", KeyboardEvent.DOM_VK_I, "I", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+ modifiers:{ctrlKey:1}, chars:"\u0009"},
+ "i", "KeyI", KeyboardEvent.DOM_VK_I, "i", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0009"},
+ "I", "KeyI", KeyboardEvent.DOM_VK_I, "I", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+ modifiers:{altKey:1}, chars:"i"},
+ "i", "KeyI", KeyboardEvent.DOM_VK_I, "i", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+ modifiers:{altKey:1, shiftKey:1}, chars:"I"},
+ "I", "KeyI", KeyboardEvent.DOM_VK_I, "I", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+ modifiers:{}, chars:"j"},
+ "j", "KeyJ", KeyboardEvent.DOM_VK_J, "j", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+ modifiers:{shiftKey:1}, chars:"J"},
+ "J", "KeyJ", KeyboardEvent.DOM_VK_J, "J", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+ modifiers:{ctrlKey:1}, chars:"\u000A"},
+ "j", "KeyJ", KeyboardEvent.DOM_VK_J, "j", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000A"},
+ "J", "KeyJ", KeyboardEvent.DOM_VK_J, "J", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+ modifiers:{altKey:1}, chars:"j"},
+ "j", "KeyJ", KeyboardEvent.DOM_VK_J, "j", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+ modifiers:{altKey:1, shiftKey:1}, chars:"J"},
+ "J", "KeyJ", KeyboardEvent.DOM_VK_J, "J", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+ modifiers:{}, chars:"k"},
+ "k", "KeyK", KeyboardEvent.DOM_VK_K, "k", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+ modifiers:{shiftKey:1}, chars:"K"},
+ "K", "KeyK", KeyboardEvent.DOM_VK_K, "K", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+ modifiers:{ctrlKey:1}, chars:"\u000B"},
+ "k", "KeyK", KeyboardEvent.DOM_VK_K, "k", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000B"},
+ "K", "KeyK", KeyboardEvent.DOM_VK_K, "K", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+ modifiers:{altKey:1}, chars:"k"},
+ "k", "KeyK", KeyboardEvent.DOM_VK_K, "k", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+ modifiers:{altKey:1, shiftKey:1}, chars:"K"},
+ "K", "KeyK", KeyboardEvent.DOM_VK_K, "K", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+ modifiers:{}, chars:"l"},
+ "l", "KeyL", KeyboardEvent.DOM_VK_L, "l", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+ modifiers:{shiftKey:1}, chars:"L"},
+ "L", "KeyL", KeyboardEvent.DOM_VK_L, "L", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+ modifiers:{ctrlKey:1}, chars:"\u000C"},
+ "l", "KeyL", KeyboardEvent.DOM_VK_L, "l", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000C"},
+ "L", "KeyL", KeyboardEvent.DOM_VK_L, "L", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+ modifiers:{altKey:1}, chars:"l"},
+ "l", "KeyL", KeyboardEvent.DOM_VK_L, "l", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+ modifiers:{altKey:1, shiftKey:1}, chars:"L"},
+ "L", "KeyL", KeyboardEvent.DOM_VK_L, "L", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+ modifiers:{}, chars:"m"},
+ "m", "KeyM", KeyboardEvent.DOM_VK_M, "m", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+ modifiers:{shiftKey:1}, chars:"M"},
+ "M", "KeyM", KeyboardEvent.DOM_VK_M, "M", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+ modifiers:{ctrlKey:1}, chars:"\u000D"},
+ "m", "KeyM", KeyboardEvent.DOM_VK_M, "m", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000D"},
+ "M", "KeyM", KeyboardEvent.DOM_VK_M, "M", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+ modifiers:{altKey:1}, chars:"m"},
+ "m", "KeyM", KeyboardEvent.DOM_VK_M, "m", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+ modifiers:{altKey:1, shiftKey:1}, chars:"M"},
+ "M", "KeyM", KeyboardEvent.DOM_VK_M, "M", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+ modifiers:{}, chars:"n"},
+ "n", "KeyN", KeyboardEvent.DOM_VK_N, "n", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+ modifiers:{shiftKey:1}, chars:"N"},
+ "N", "KeyN", KeyboardEvent.DOM_VK_N, "N", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+ modifiers:{ctrlKey:1}, chars:"\u000E"},
+ "n", "KeyN", KeyboardEvent.DOM_VK_N, "n", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000E"},
+ "N", "KeyN", KeyboardEvent.DOM_VK_N, "N", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+ modifiers:{altKey:1}, chars:"n"},
+ "n", "KeyN", KeyboardEvent.DOM_VK_N, "n", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+ modifiers:{altKey:1, shiftKey:1}, chars:"N"},
+ "N", "KeyN", KeyboardEvent.DOM_VK_N, "N", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+ modifiers:{}, chars:"o"},
+ "o", "KeyO", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+ modifiers:{shiftKey:1}, chars:"O"},
+ "O", "KeyO", KeyboardEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+ modifiers:{ctrlKey:1}, chars:"\u000F"},
+ "o", "KeyO", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000F"},
+ "O", "KeyO", KeyboardEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+ modifiers:{altKey:1}, chars:"o"},
+ "o", "KeyO", KeyboardEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+ modifiers:{altKey:1, shiftKey:1}, chars:"O"},
+ "O", "KeyO", KeyboardEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+ modifiers:{}, chars:"p"},
+ "p", "KeyP", KeyboardEvent.DOM_VK_P, "p", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+ modifiers:{shiftKey:1}, chars:"P"},
+ "P", "KeyP", KeyboardEvent.DOM_VK_P, "P", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+ modifiers:{ctrlKey:1}, chars:"\u0010"},
+ "p", "KeyP", KeyboardEvent.DOM_VK_P, "p", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0010"},
+ "P", "KeyP", KeyboardEvent.DOM_VK_P, "P", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+ modifiers:{altKey:1}, chars:"p"},
+ "p", "KeyP", KeyboardEvent.DOM_VK_P, "p", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+ modifiers:{altKey:1, shiftKey:1}, chars:"P"},
+ "P", "KeyP", KeyboardEvent.DOM_VK_P, "P", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+ modifiers:{}, chars:"q"},
+ "q", "KeyQ", KeyboardEvent.DOM_VK_Q, "q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+ modifiers:{shiftKey:1}, chars:"Q"},
+ "Q", "KeyQ", KeyboardEvent.DOM_VK_Q, "Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+ modifiers:{ctrlKey:1}, chars:"\u0011"},
+ "q", "KeyQ", KeyboardEvent.DOM_VK_Q, "q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0011"},
+ "Q", "KeyQ", KeyboardEvent.DOM_VK_Q, "Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+ modifiers:{altKey:1}, chars:"q"},
+ "q", "KeyQ", KeyboardEvent.DOM_VK_Q, "q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+ modifiers:{altKey:1, shiftKey:1}, chars:"Q"},
+ "Q", "KeyQ", KeyboardEvent.DOM_VK_Q, "Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+ modifiers:{}, chars:"r"},
+ "r", "KeyR", KeyboardEvent.DOM_VK_R, "r", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+ modifiers:{shiftKey:1}, chars:"R"},
+ "R", "KeyR", KeyboardEvent.DOM_VK_R, "R", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+ modifiers:{ctrlKey:1}, chars:"\u0012"},
+ "r", "KeyR", KeyboardEvent.DOM_VK_R, "r", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0012"},
+ "R", "KeyR", KeyboardEvent.DOM_VK_R, "R", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+ modifiers:{altKey:1}, chars:"r"},
+ "r", "KeyR", KeyboardEvent.DOM_VK_R, "r", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+ modifiers:{altKey:1, shiftKey:1}, chars:"R"},
+ "R", "KeyR", KeyboardEvent.DOM_VK_R, "R", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+ modifiers:{}, chars:"s"},
+ "s", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+ modifiers:{shiftKey:1}, chars:"S"},
+ "S", "KeyS", KeyboardEvent.DOM_VK_S, "S", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+ modifiers:{ctrlKey:1}, chars:"\u0013"},
+ "s", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0013"},
+ "S", "KeyS", KeyboardEvent.DOM_VK_S, "S", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+ modifiers:{altKey:1}, chars:"s"},
+ "s", "KeyS", KeyboardEvent.DOM_VK_S, "s", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+ modifiers:{altKey:1, shiftKey:1}, chars:"S"},
+ "S", "KeyS", KeyboardEvent.DOM_VK_S, "S", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+ modifiers:{}, chars:"t"},
+ "t", "KeyT", KeyboardEvent.DOM_VK_T, "t", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+ modifiers:{shiftKey:1}, chars:"T"},
+ "T", "KeyT", KeyboardEvent.DOM_VK_T, "T", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+ modifiers:{ctrlKey:1}, chars:"\u0014"},
+ "t", "KeyT", KeyboardEvent.DOM_VK_T, "t", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0014"},
+ "T", "KeyT", KeyboardEvent.DOM_VK_T, "T", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+ modifiers:{altKey:1}, chars:"t"},
+ "t", "KeyT", KeyboardEvent.DOM_VK_T, "t", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+ modifiers:{altKey:1, shiftKey:1}, chars:"T"},
+ "T", "KeyT", KeyboardEvent.DOM_VK_T, "T", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+ modifiers:{}, chars:"u"},
+ "u", "KeyU", KeyboardEvent.DOM_VK_U, "u", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+ modifiers:{shiftKey:1}, chars:"U"},
+ "U", "KeyU", KeyboardEvent.DOM_VK_U, "U", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+ modifiers:{ctrlKey:1}, chars:"\u0015"},
+ "u", "KeyU", KeyboardEvent.DOM_VK_U, "u", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0015"},
+ "U", "KeyU", KeyboardEvent.DOM_VK_U, "U", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+ modifiers:{altKey:1}, chars:"u"},
+ "u", "KeyU", KeyboardEvent.DOM_VK_U, "u", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+ modifiers:{altKey:1, shiftKey:1}, chars:"U"},
+ "U", "KeyU", KeyboardEvent.DOM_VK_U, "U", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+ modifiers:{}, chars:"v"},
+ "v", "KeyV", KeyboardEvent.DOM_VK_V, "v", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+ modifiers:{shiftKey:1}, chars:"V"},
+ "V", "KeyV", KeyboardEvent.DOM_VK_V, "V", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+ modifiers:{ctrlKey:1}, chars:"\u0016"},
+ "v", "KeyV", KeyboardEvent.DOM_VK_V, "v", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0016"},
+ "V", "KeyV", KeyboardEvent.DOM_VK_V, "V", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+ modifiers:{altKey:1}, chars:"v"},
+ "v", "KeyV", KeyboardEvent.DOM_VK_V, "v", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+ modifiers:{altKey:1, shiftKey:1}, chars:"V"},
+ "V", "KeyV", KeyboardEvent.DOM_VK_V, "V", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+ modifiers:{}, chars:"w"},
+ "w", "KeyW", KeyboardEvent.DOM_VK_W, "w", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+ modifiers:{shiftKey:1}, chars:"W"},
+ "W", "KeyW", KeyboardEvent.DOM_VK_W, "W", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+ modifiers:{ctrlKey:1}, chars:"\u0017"},
+ "w", "KeyW", KeyboardEvent.DOM_VK_W, "w", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0017"},
+ "W", "KeyW", KeyboardEvent.DOM_VK_W, "W", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+ modifiers:{altKey:1}, chars:"w"},
+ "w", "KeyW", KeyboardEvent.DOM_VK_W, "w", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+ modifiers:{altKey:1, shiftKey:1}, chars:"W"},
+ "W", "KeyW", KeyboardEvent.DOM_VK_W, "W", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{}, chars:"x"},
+ "x", "KeyX", KeyboardEvent.DOM_VK_X, "x", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{shiftKey:1}, chars:"X"},
+ "X", "KeyX", KeyboardEvent.DOM_VK_X, "X", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1}, chars:"\u0018"},
+ "x", "KeyX", KeyboardEvent.DOM_VK_X, "x", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0018"},
+ "X", "KeyX", KeyboardEvent.DOM_VK_X, "X", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{altKey:1}, chars:"x"},
+ "x", "KeyX", KeyboardEvent.DOM_VK_X, "x", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{altKey:1, shiftKey:1}, chars:"X"},
+ "X", "KeyX", KeyboardEvent.DOM_VK_X, "X", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+ modifiers:{}, chars:"y"},
+ "y", "KeyY", KeyboardEvent.DOM_VK_Y, "y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+ modifiers:{shiftKey:1}, chars:"Y"},
+ "Y", "KeyY", KeyboardEvent.DOM_VK_Y, "Y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+ modifiers:{ctrlKey:1}, chars:"\u0019"},
+ "y", "KeyY", KeyboardEvent.DOM_VK_Y, "y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0019"},
+ "Y", "KeyY", KeyboardEvent.DOM_VK_Y, "Y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+ modifiers:{altKey:1}, chars:"y"},
+ "y", "KeyY", KeyboardEvent.DOM_VK_Y, "y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+ modifiers:{altKey:1, shiftKey:1}, chars:"Y"},
+ "Y", "KeyY", KeyboardEvent.DOM_VK_Y, "Y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+ modifiers:{}, chars:"z"},
+ "z", "KeyZ", KeyboardEvent.DOM_VK_Z, "z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+ modifiers:{shiftKey:1}, chars:"Z"},
+ "Z", "KeyZ", KeyboardEvent.DOM_VK_Z, "Z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+ modifiers:{ctrlKey:1}, chars:"\u001A"},
+ "z", "KeyZ", KeyboardEvent.DOM_VK_Z, "z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001A"},
+ "Z", "KeyZ", KeyboardEvent.DOM_VK_Z, "Z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+ modifiers:{altKey:1}, chars:"z"},
+ "z", "KeyZ", KeyboardEvent.DOM_VK_Z, "z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+ modifiers:{altKey:1, shiftKey:1}, chars:"Z"},
+ "Z", "KeyZ", KeyboardEvent.DOM_VK_Z, "Z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Numeric
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+ modifiers:{}, chars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+ modifiers:{shiftKey:1}, chars:")"},
+ ")", "Digit0", KeyboardEvent.DOM_VK_0, ")", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+ modifiers:{ctrlKey:1}, chars:""},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ ")", "Digit0", KeyboardEvent.DOM_VK_0, ")", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+ modifiers:{altKey:1}, chars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+ modifiers:{altKey:1, shiftKey:1}, chars:")"},
+ ")", "Digit0", KeyboardEvent.DOM_VK_0, ")", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+ modifiers:{}, chars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+ modifiers:{shiftKey:1}, chars:"!"},
+ "!", "Digit1", KeyboardEvent.DOM_VK_1, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+ modifiers:{ctrlKey:1}, chars:""},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "!", "Digit1", KeyboardEvent.DOM_VK_1, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+ modifiers:{altKey:1}, chars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:"!"},
+ "!", "Digit1", KeyboardEvent.DOM_VK_1, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+ modifiers:{}, chars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+ modifiers:{shiftKey:1}, chars:"@"},
+ "@", "Digit2", KeyboardEvent.DOM_VK_2, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+ modifiers:{ctrlKey:1}, chars:""},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "@", "Digit2", KeyboardEvent.DOM_VK_2, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+ modifiers:{altKey:1}, chars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+ modifiers:{altKey:1, shiftKey:1}, chars:"@"},
+ "@", "Digit2", KeyboardEvent.DOM_VK_2, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+ modifiers:{}, chars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+ modifiers:{shiftKey:1}, chars:"#"},
+ "#", "Digit3", KeyboardEvent.DOM_VK_3, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+ modifiers:{ctrlKey:1}, chars:""},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "#", "Digit3", KeyboardEvent.DOM_VK_3, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+ modifiers:{altKey:1}, chars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+ modifiers:{altKey:1, shiftKey:1}, chars:"#"},
+ "#", "Digit3", KeyboardEvent.DOM_VK_3, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+ modifiers:{}, chars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+ modifiers:{shiftKey:1}, chars:"$"},
+ "$", "Digit4", KeyboardEvent.DOM_VK_4, "$", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+ modifiers:{ctrlKey:1}, chars:""},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "$", "Digit4", KeyboardEvent.DOM_VK_4, "$", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+ modifiers:{altKey:1}, chars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+ modifiers:{altKey:1, shiftKey:1}, chars:"$"},
+ "$", "Digit4", KeyboardEvent.DOM_VK_4, "$", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+ modifiers:{}, chars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+ modifiers:{shiftKey:1}, chars:"%"},
+ "%", "Digit5", KeyboardEvent.DOM_VK_5, "%", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+ modifiers:{ctrlKey:1}, chars:""},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "%", "Digit5", KeyboardEvent.DOM_VK_5, "%", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+ modifiers:{altKey:1}, chars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+ modifiers:{altKey:1, shiftKey:1}, chars:"%"},
+ "%", "Digit5", KeyboardEvent.DOM_VK_5, "%", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+ modifiers:{}, chars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+ modifiers:{shiftKey:1}, chars:"^"},
+ "^", "Digit6", KeyboardEvent.DOM_VK_6, "^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+ modifiers:{ctrlKey:1}, chars:""},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001E"},
+ "^", "Digit6", KeyboardEvent.DOM_VK_6, "^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+ modifiers:{altKey:1}, chars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+ modifiers:{altKey:1, shiftKey:1}, chars:"^"},
+ "^", "Digit6", KeyboardEvent.DOM_VK_6, "^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+ modifiers:{}, chars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+ modifiers:{shiftKey:1}, chars:"&"},
+ "&", "Digit7", KeyboardEvent.DOM_VK_7, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+ modifiers:{ctrlKey:1}, chars:""},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "&", "Digit7", KeyboardEvent.DOM_VK_7, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+ modifiers:{altKey:1}, chars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+ modifiers:{altKey:1, shiftKey:1}, chars:"&"},
+ "&", "Digit7", KeyboardEvent.DOM_VK_7, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+ modifiers:{}, chars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+ modifiers:{shiftKey:1}, chars:"*"},
+ "*", "Digit8", KeyboardEvent.DOM_VK_8, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+ modifiers:{ctrlKey:1, }, chars:""},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "*", "Digit8", KeyboardEvent.DOM_VK_8, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+ modifiers:{altKey:1}, chars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+ modifiers:{altKey:1, shiftKey:1}, chars:"*"},
+ "*", "Digit8", KeyboardEvent.DOM_VK_8, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+ modifiers:{}, chars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+ modifiers:{shiftKey:1}, chars:"("},
+ "(", "Digit9", KeyboardEvent.DOM_VK_9, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+ modifiers:{ctrlKey:1}, chars:""},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "(", "Digit9", KeyboardEvent.DOM_VK_9, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+ modifiers:{altKey:1}, chars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+ modifiers:{altKey:1, shiftKey:1}, chars:"("},
+ "(", "Digit9", KeyboardEvent.DOM_VK_9, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // OEM keys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+ modifiers:{}, chars:"-"},
+ "-", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+ modifiers:{shiftKey:1}, chars:"_"},
+ "_", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+ modifiers:{ctrlKey:1}, chars:""},
+ "-", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001F"},
+ "_", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+ modifiers:{altKey:1}, chars:"-"},
+ "-", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+ modifiers:{altKey:1, shiftKey:1}, chars:"_"},
+ "_", "Minus", KeyboardEvent.DOM_VK_HYPHEN_MINUS, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{}, chars:"="},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{shiftKey:1}, chars:"+"},
+ "+", "Equal", KeyboardEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{ctrlKey:1}, chars:""},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "+", "Equal", KeyboardEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{altKey:1}, chars:"="},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{altKey:1, shiftKey:1}, chars:"+"},
+ "+", "Equal", KeyboardEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+ modifiers:{}, chars:"["},
+ "[", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "[", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+ modifiers:{shiftKey:1}, chars:"{"},
+ "{", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+ modifiers:{ctrlKey:1}, chars:"\u001B"},
+ "[", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "[", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "{", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+ modifiers:{altKey:1}, chars:"["},
+ "[", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "[", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+ modifiers:{altKey:1, shiftKey:1}, chars:"{"},
+ "{", "BracketLeft", KeyboardEvent.DOM_VK_OPEN_BRACKET, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+ modifiers:{}, chars:"]"},
+ "]", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "]", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+ modifiers:{shiftKey:1}, chars:"}"},
+ "}", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+ modifiers:{ctrlKey:1}, chars:"\u001D"},
+ "]", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "]", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "}", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+ modifiers:{altKey:1}, chars:"]"},
+ "]", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "]", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+ modifiers:{altKey:1, shiftKey:1}, chars:"}"},
+ "}", "BracketRight", KeyboardEvent.DOM_VK_CLOSE_BRACKET, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{}, chars:";"},
+ ";", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{shiftKey:1}, chars:":"},
+ ":", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ":", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{ctrlKey:1}, chars:""},
+ ";", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ ":", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ":", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1}, chars:";"},
+ ";", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:":"},
+ ":", "Semicolon", KeyboardEvent.DOM_VK_SEMICOLON, ":", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{}, chars:"'"},
+ "'", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{shiftKey:1}, chars:"\""},
+ "\"", "Quote", KeyboardEvent.DOM_VK_QUOTE, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{ctrlKey:1}, chars:""},
+ "'", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "\"", "Quote", KeyboardEvent.DOM_VK_QUOTE, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{altKey:1}, chars:"'"},
+ "'", "Quote", KeyboardEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\""},
+ "\"", "Quote", KeyboardEvent.DOM_VK_QUOTE, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_5,
+ modifiers:{}, chars:"\\"},
+ "\\", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\\", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_5,
+ modifiers:{shiftKey:1}, chars:"|"},
+ "|", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "|", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_5,
+ modifiers:{ctrlKey:1}, chars:"\u001C"},
+ "\\", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\\", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_5,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "|", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "|", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_5,
+ modifiers:{altKey:1}, chars:"\\"},
+ "\\", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "\\", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_5,
+ modifiers:{altKey:1, shiftKey:1}, chars:"|"},
+ "|", "Backslash", KeyboardEvent.DOM_VK_BACK_SLASH, "|", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{}, chars:","},
+ ",", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{shiftKey:1}, chars:"<"},
+ "<", "Comma", KeyboardEvent.DOM_VK_COMMA, "<", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{ctrlKey:1}, chars:""},
+ ",", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "<", "Comma", KeyboardEvent.DOM_VK_COMMA, "<", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{altKey:1}, chars:","},
+ ",", "Comma", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{altKey:1, shiftKey:1}, chars:"<"},
+ "<", "Comma", KeyboardEvent.DOM_VK_COMMA, "<", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{}, chars:"."},
+ ".", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{shiftKey:1}, chars:">"},
+ ">", "Period", KeyboardEvent.DOM_VK_PERIOD, ">", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{ctrlKey:1}, chars:""},
+ ".", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ ">", "Period", KeyboardEvent.DOM_VK_PERIOD, ">", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{altKey:1}, chars:"."},
+ ".", "Period", KeyboardEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{altKey:1, shiftKey:1}, chars:">"},
+ ">", "Period", KeyboardEvent.DOM_VK_PERIOD, ">", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+ modifiers:{}, chars:"/"},
+ "/", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+ modifiers:{shiftKey:1}, chars:"?"},
+ "?", "Slash", KeyboardEvent.DOM_VK_SLASH, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+ modifiers:{ctrlKey:1}, chars:""},
+ "/", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "?", "Slash", KeyboardEvent.DOM_VK_SLASH, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+ modifiers:{altKey:1}, chars:"/"},
+ "/", "Slash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+ modifiers:{altKey:1, shiftKey:1}, chars:"?"},
+ "?", "Slash", KeyboardEvent.DOM_VK_SLASH, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+ modifiers:{}, chars:"`"},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+ modifiers:{shiftKey:1}, chars:"~"},
+ "~", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "~", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+ modifiers:{ctrlKey:1}, chars:""},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "~", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "~", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+ modifiers:{altKey:1}, chars:"`"},
+ "`", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+ modifiers:{altKey:1, shiftKey:1}, chars:"~"},
+ "~", "Backquote", KeyboardEvent.DOM_VK_BACK_QUOTE, "~", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Numpad
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD0,
+ modifiers:{numLockKey:1}, chars:"0"},
+ "0", "Numpad0", KeyboardEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD1,
+ modifiers:{numLockKey:1}, chars:"1"},
+ "1", "Numpad1", KeyboardEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD2,
+ modifiers:{numLockKey:1}, chars:"2"},
+ "2", "Numpad2", KeyboardEvent.DOM_VK_NUMPAD2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD3,
+ modifiers:{numLockKey:1}, chars:"3"},
+ "3", "Numpad3", KeyboardEvent.DOM_VK_NUMPAD3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD4,
+ modifiers:{numLockKey:1}, chars:"4"},
+ "4", "Numpad4", KeyboardEvent.DOM_VK_NUMPAD4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD5,
+ modifiers:{numLockKey:1}, chars:"5"},
+ "5", "Numpad5", KeyboardEvent.DOM_VK_NUMPAD5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD6,
+ modifiers:{numLockKey:1}, chars:"6"},
+ "6", "Numpad6", KeyboardEvent.DOM_VK_NUMPAD6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD7,
+ modifiers:{numLockKey:1}, chars:"7"},
+ "7", "Numpad7", KeyboardEvent.DOM_VK_NUMPAD7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD8,
+ modifiers:{numLockKey:1}, chars:"8"},
+ "8", "Numpad8", KeyboardEvent.DOM_VK_NUMPAD8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD9,
+ modifiers:{numLockKey:1}, chars:"9"},
+ "9", "Numpad9", KeyboardEvent.DOM_VK_NUMPAD9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_MULTIPLY,
+ modifiers:{numLockKey:1}, chars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_MULTIPLY,
+ modifiers:{numLockKey:1, shiftKey:1}, chars:"*"},
+ "*", "NumpadMultiply", KeyboardEvent.DOM_VK_MULTIPLY, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_ADD,
+ modifiers:{numLockKey:1}, chars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_ADD,
+ modifiers:{numLockKey:1, shiftKey:1}, chars:"+"},
+ "+", "NumpadAdd", KeyboardEvent.DOM_VK_ADD, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ // VK_SEPARATOR is keycode for NEC's PC-98 series whose keyboard layout was
+ // different from current PC's keyboard layout and it cannot connect to
+ // current PC. Note that even if we synthesize WM_KEYDOWN with
+ // VK_SEPARATOR, it doesn't work on Win7.
+ //yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SEPARATOR,
+ // modifiers:{numLockKey:1}, chars:""},
+ // "", "", KeyboardEvent.DOM_VK_SEPARATOR, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ //yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SEPARATOR,
+ // modifiers:{numLockKey:1, shiftKey:1}, chars:""},
+ // "", "", KeyboardEvent.DOM_VK_SEPARATOR, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SUBTRACT,
+ modifiers:{numLockKey:1}, chars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SUBTRACT,
+ modifiers:{numLockKey:1, shiftKey:1}, chars:"-"},
+ "-", "NumpadSubtract", KeyboardEvent.DOM_VK_SUBTRACT, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_DECIMAL,
+ modifiers:{numLockKey:1}, chars:"."},
+ ".", "NumpadDecimal", KeyboardEvent.DOM_VK_DECIMAL, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_DECIMAL,
+ modifiers:{numLockKey:1, shiftKey:1}, chars:"."},
+ ".", "NumpadDecimal", KeyboardEvent.DOM_VK_DECIMAL, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_DIVIDE,
+ modifiers:{numLockKey:1}, chars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_DIVIDE,
+ modifiers:{numLockKey:1, shiftKey:1}, chars:"/"},
+ "/", "NumpadDivide", KeyboardEvent.DOM_VK_DIVIDE, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_RETURN,
+ modifiers:{numLockKey:1}, chars:"\r"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_RETURN,
+ modifiers:{numLockKey:1, shiftKey:1}, chars:"\r"},
+ "Enter", "NumpadEnter", KeyboardEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+
+ // Numpad without NumLock
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_PRIOR,
+ modifiers:{}, chars:""},
+ "PageUp", "Numpad9", KeyboardEvent.DOM_VK_PAGE_UP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_NEXT,
+ modifiers:{}, chars:""},
+ "PageDown", "Numpad3", KeyboardEvent.DOM_VK_PAGE_DOWN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_END,
+ modifiers:{}, chars:""},
+ "End", "Numpad1", KeyboardEvent.DOM_VK_END, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_HOME,
+ modifiers:{}, chars:""},
+ "Home", "Numpad7", KeyboardEvent.DOM_VK_HOME, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_LEFT,
+ modifiers:{}, chars:""},
+ "ArrowLeft", "Numpad4", KeyboardEvent.DOM_VK_LEFT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_UP,
+ modifiers:{}, chars:""},
+ "ArrowUp", "Numpad8", KeyboardEvent.DOM_VK_UP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_RIGHT,
+ modifiers:{}, chars:""},
+ "ArrowRight", "Numpad6", KeyboardEvent.DOM_VK_RIGHT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_DOWN,
+ modifiers:{}, chars:""},
+ "ArrowDown", "Numpad2", KeyboardEvent.DOM_VK_DOWN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_INSERT,
+ modifiers:{}, chars:""},
+ "Insert", "Numpad0", KeyboardEvent.DOM_VK_INSERT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD_DELETE,
+ modifiers:{}, chars:""},
+ "Delete", "NumpadDecimal", KeyboardEvent.DOM_VK_DELETE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_CLEAR,
+ modifiers:{}, chars:""},
+ "Clear", "Numpad5", KeyboardEvent.DOM_VK_CLEAR, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+
+ // Even if widget receives unknown keycode, it should dispatch key events
+ // whose keycode is 0 rather than native keycode.
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:0x3A,
+ modifiers:{numLockKey:1}, chars:""},
+ "Unidentified", "", 0, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // French
+ // Numeric
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+ modifiers:{}, chars:"\u00E0"},
+ "\u00E0", "Digit0", KeyboardEvent.DOM_VK_0, "\u00E0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+ modifiers:{shiftKey:1}, chars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+ modifiers:{}, chars:"&"},
+ "&", "Digit1", KeyboardEvent.DOM_VK_1, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+ modifiers:{shiftKey:1}, chars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_2,
+ modifiers:{}, chars:"\u00E9"},
+ "\u00E9", "Digit2", KeyboardEvent.DOM_VK_2, "\u00E9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_2,
+ modifiers:{shiftKey:1}, chars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_3,
+ modifiers:{}, chars:"\""},
+ "\"", "Digit3", KeyboardEvent.DOM_VK_3, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_3,
+ modifiers:{shiftKey:1}, chars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_4,
+ modifiers:{}, chars:"'"},
+ "'", "Digit4", KeyboardEvent.DOM_VK_4, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_4,
+ modifiers:{shiftKey:1}, chars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_5,
+ modifiers:{}, chars:"("},
+ "(", "Digit5", KeyboardEvent.DOM_VK_5, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_5,
+ modifiers:{shiftKey:1}, chars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_6,
+ modifiers:{}, chars:"-"},
+ "-", "Digit6", KeyboardEvent.DOM_VK_6, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_6,
+ modifiers:{shiftKey:1}, chars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_7,
+ modifiers:{}, chars:"\u00E8"},
+ "\u00E8", "Digit7", KeyboardEvent.DOM_VK_7, "\u00E8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_7,
+ modifiers:{shiftKey:1}, chars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_8,
+ modifiers:{}, chars:"_"},
+ "_", "Digit8", KeyboardEvent.DOM_VK_8, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_8,
+ modifiers:{shiftKey:1}, chars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_9,
+ modifiers:{}, chars:"\u00E7"},
+ "\u00E7", "Digit9", KeyboardEvent.DOM_VK_9, "\u00E7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_9,
+ modifiers:{shiftKey:1}, chars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Numeric with ShiftLock
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+ modifiers:{capsLockKey:1}, chars:"0"},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"\u00E0"},
+ "\u00E0", "Digit0", KeyboardEvent.DOM_VK_0, "\u00E0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+ modifiers:{capsLockKey:1}, chars:"1"},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"&"},
+ "&", "Digit1", KeyboardEvent.DOM_VK_1, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_2,
+ modifiers:{capsLockKey:1}, chars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_2,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"\u00E9"},
+ "\u00E9", "Digit2", KeyboardEvent.DOM_VK_2, "\u00E9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_3,
+ modifiers:{capsLockKey:1}, chars:"3"},
+ "3", "Digit3", KeyboardEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_3,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"\""},
+ "\"", "Digit3", KeyboardEvent.DOM_VK_3, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_4,
+ modifiers:{capsLockKey:1}, chars:"4"},
+ "4", "Digit4", KeyboardEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_4,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"'"},
+ "'", "Digit4", KeyboardEvent.DOM_VK_4, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_5,
+ modifiers:{capsLockKey:1}, chars:"5"},
+ "5", "Digit5", KeyboardEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_5,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"("},
+ "(", "Digit5", KeyboardEvent.DOM_VK_5, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_6,
+ modifiers:{capsLockKey:1}, chars:"6"},
+ "6", "Digit6", KeyboardEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_6,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"-"},
+ "-", "Digit6", KeyboardEvent.DOM_VK_6, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_7,
+ modifiers:{capsLockKey:1}, chars:"7"},
+ "7", "Digit7", KeyboardEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_7,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"\u00E8"},
+ "\u00E8", "Digit7", KeyboardEvent.DOM_VK_7, "\u00E8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_8,
+ modifiers:{capsLockKey:1}, chars:"8"},
+ "8", "Digit8", KeyboardEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_8,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"_"},
+ "_", "Digit8", KeyboardEvent.DOM_VK_8, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_9,
+ modifiers:{capsLockKey:1}, chars:"9"},
+ "9", "Digit9", KeyboardEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_9,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"\u00E7"},
+ "\u00E7", "Digit9", KeyboardEvent.DOM_VK_9, "\u00E7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // OEM keys
+ // If the key doesn't cause ASCII character even with or without Shift key, keyCode value should be same as
+ // the key which causes the virtual keycode on ANSI keyboard layout.
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_7,
+ modifiers:{}, chars:"\u00B2"},
+ "\u00B2", "Backquote", KeyboardEvent.DOM_VK_QUOTE, "\u00B2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_7,
+ modifiers:{shiftKey:1}, chars:""},
+ "", "Backquote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_4,
+ modifiers:{}, chars:")"},
+ ")", "Minus", KeyboardEvent.DOM_VK_CLOSE_PAREN, ")", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_4,
+ modifiers:{shiftKey:1}, chars:"\u00B0"},
+ "\u00B0", "Minus", KeyboardEvent.DOM_VK_CLOSE_PAREN, "\u00B0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{}, chars:"="},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{shiftKey:1}, chars:"+"},
+ "+", "Equal", KeyboardEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ //yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ // modifiers:{}, chars:""},
+ // "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // Dead-key
+ //yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ // modifiers:{shiftKey:1}, chars:""},
+ // ["^^", "^", "^", "^"], "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // Dead-key
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_1,
+ modifiers:{}, chars:"$"},
+ "$", "BracketRight", KeyboardEvent.DOM_VK_DOLLAR, "$", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_1,
+ modifiers:{shiftKey:1}, chars:"\u00A3"},
+ "\u00A3", "BracketRight", KeyboardEvent.DOM_VK_DOLLAR, "\u00A3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_3,
+ modifiers:{}, chars:"\u00F9"},
+ "\u00F9", "Quote", KeyboardEvent.DOM_VK_PERCENT, "\u00F9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_3,
+ modifiers:{shiftKey:1}, chars:"%"},
+ "%", "Quote", KeyboardEvent.DOM_VK_PERCENT, "%", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_5,
+ modifiers:{}, chars:"*"},
+ "*", "Backslash", KeyboardEvent.DOM_VK_ASTERISK, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_5,
+ modifiers:{shiftKey:1}, chars:"\u00B5"},
+ "\u00B5", "Backslash", KeyboardEvent.DOM_VK_ASTERISK, "\u00B5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_102,
+ modifiers:{}, chars:"<"},
+ "<", "IntlBackslash", KeyboardEvent.DOM_VK_LESS_THAN, "<", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_102,
+ modifiers:{shiftKey:1}, chars:">"},
+ ">", "IntlBackslash", KeyboardEvent.DOM_VK_LESS_THAN, ">", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{}, chars:","},
+ ",", "KeyM", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{shiftKey:1}, chars:"?"},
+ "?", "KeyM", KeyboardEvent.DOM_VK_COMMA, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{}, chars:";"},
+ ";", "Comma", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{shiftKey:1}, chars:"."},
+ ".", "Comma", KeyboardEvent.DOM_VK_SEMICOLON, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_2,
+ modifiers:{}, chars:":"},
+ ":", "Period", KeyboardEvent.DOM_VK_COLON, ":", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_2,
+ modifiers:{shiftKey:1}, chars:"/"},
+ "/", "Period", KeyboardEvent.DOM_VK_COLON, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_8,
+ modifiers:{}, chars:"!"},
+ "!", "Slash", KeyboardEvent.DOM_VK_EXCLAMATION, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_8,
+ modifiers:{shiftKey:1}, chars:"\u00A7"},
+ "\u00A7", "Slash", KeyboardEvent.DOM_VK_EXCLAMATION, "\u00A7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // OEM keys with ShiftLock
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_7,
+ modifiers:{capsLockKey:1}, chars:"\u00B2"},
+ "\u00B2", "Backquote", KeyboardEvent.DOM_VK_QUOTE, "\u00B2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_7,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:""},
+ "", "Backquote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_4,
+ modifiers:{capsLockKey:1}, chars:"\u00B0"},
+ "\u00B0", "Minus", KeyboardEvent.DOM_VK_CLOSE_PAREN, "\u00B0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_4,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:")"},
+ ")", "Minus", KeyboardEvent.DOM_VK_CLOSE_PAREN, ")", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{capsLockKey:1}, chars:"+"},
+ "+", "Equal", KeyboardEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"="},
+ "=", "Equal", KeyboardEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ //yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ // modifiers:{capsLockKey:1}, chars:""},
+ // "Dead", "BracketLeft", KeyboardLayout.DOM_VK_CLOSE_BRACKET, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // Dead-key
+ //yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ // modifiers:{capsLockKey:1, shiftKey:1}, chars:""},
+ // ["\u00A8\u00A8", "\u00A8", "\u00A8", "\u00A8"], "BracketLeft", KeyboardLayout.DOM_VK_CLOSE_BRACKET, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // Dead-key
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_1,
+ modifiers:{capsLockKey:1}, chars:"\u00A3"},
+ "\u00A3", "BracketRight", KeyboardEvent.DOM_VK_DOLLAR, "\u00A3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_1,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"$"},
+ "$", "BracketRight", KeyboardEvent.DOM_VK_DOLLAR, "$", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_3,
+ modifiers:{capsLockKey:1}, chars:"%"},
+ "%", "Quote", KeyboardEvent.DOM_VK_PERCENT, "%", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_3,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"\u00F9"},
+ "\u00F9", "Quote", KeyboardEvent.DOM_VK_PERCENT, "\u00F9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_5,
+ modifiers:{capsLockKey:1}, chars:"\u00B5"},
+ "\u00B5", "Backslash", KeyboardEvent.DOM_VK_ASTERISK, "\u00B5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_5,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"*"},
+ "*", "Backslash", KeyboardEvent.DOM_VK_ASTERISK, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_102,
+ modifiers:{capsLockKey:1}, chars:"<"},
+ "<", "IntlBackslash", KeyboardEvent.DOM_VK_LESS_THAN, "<", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_102,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:">"},
+ ">", "IntlBackslash", KeyboardEvent.DOM_VK_LESS_THAN, ">", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{capsLockKey:1}, chars:"?"},
+ "?", "KeyM", KeyboardEvent.DOM_VK_COMMA, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_COMMA,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:","},
+ ",", "KeyM", KeyboardEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{capsLockKey:1}, chars:"."},
+ ".", "Comma", KeyboardEvent.DOM_VK_SEMICOLON, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:";"},
+ ";", "Comma", KeyboardEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_2,
+ modifiers:{capsLockKey:1}, chars:"/"},
+ "/", "Period", KeyboardEvent.DOM_VK_COLON, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_2,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:":"},
+ ":", "Period", KeyboardEvent.DOM_VK_COLON, ":", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_8,
+ modifiers:{capsLockKey:1}, chars:"\u00A7"},
+ "\u00A7", "Slash", KeyboardEvent.DOM_VK_EXCLAMATION, "\u00A7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_8,
+ modifiers:{capsLockKey:1, shiftKey:1}, chars:"!"},
+ "!", "Slash", KeyboardEvent.DOM_VK_EXCLAMATION, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // AltGr
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+ modifiers:{altGrKey:1}, chars:"@"},
+ "@", "Digit0", KeyboardEvent.DOM_VK_0, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // AltGr + Digit1 does not cause text input in French layout. In this case,
+ // AltGr shouldn't be used for a modifier of shortcut. Therefore, not
+ // receiving `keypress` event even in the system group is fine.
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+ modifiers:{altGrKey:1}, chars:""},
+ "&", "Digit1", KeyboardEvent.DOM_VK_1, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ //yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_2,
+ // modifiers:{altGrKey:1}, chars:""},
+ // "Dead", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // Dead-key
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_3,
+ modifiers:{altGrKey:1}, chars:"#"},
+ "#", "Digit3", KeyboardEvent.DOM_VK_3, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_4,
+ modifiers:{altGrKey:1}, chars:"{"},
+ "{", "Digit4", KeyboardEvent.DOM_VK_4, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_5,
+ modifiers:{altGrKey:1}, chars:"["},
+ "[", "Digit5", KeyboardEvent.DOM_VK_5, "[", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_6,
+ modifiers:{altGrKey:1}, chars:"|"},
+ "|", "Digit6", KeyboardEvent.DOM_VK_6, "|", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ //yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_7,
+ // modifiers:{altGrKey:1}, chars:""},
+ // "Dead", "Digit7", KeyboardEvent.DOM_VK_7, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // Dead-key
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_8,
+ modifiers:{altGrKey:1}, chars:"\\"},
+ "\\", "Digit8", KeyboardEvent.DOM_VK_8, "\\", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_9,
+ modifiers:{altGrKey:1}, chars:"^"},
+ "^", "Digit9", KeyboardEvent.DOM_VK_9, "^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_4,
+ modifiers:{altGrKey:1}, chars:"]"},
+ "]", "Minus", KeyboardEvent.DOM_VK_CLOSE_PAREN, "]", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{altGrKey:1}, chars:"}"},
+ "}", "Equal", KeyboardEvent.DOM_VK_EQUALS, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // AltGr emulated with Ctrl and Alt
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"@", isInputtingCharacters:true},
+ "@", "Digit0", KeyboardEvent.DOM_VK_0, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+ modifiers:{ctrlKey:1, altKey:1, shiftKey:1}, chars:"", isInputtingCharacters:false},
+ "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // Different from AltGr + Digit1 case, Ctrl + Alt + Digit1 should be
+ // available as a shortcut key. Therefore, `keypress` event needs to be
+ // fired.
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"", isInputtingCharacters:false},
+ "&", "Digit1", KeyboardEvent.DOM_VK_1, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+ modifiers:{ctrlKey:1, altKey:1, shiftKey:1}, chars:"", isInputtingCharacters:false},
+ "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // German
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_OEM_2,
+ modifiers:{}, chars:"#"},
+ "#", "Backslash", KeyboardEvent.DOM_VK_HASH, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_OEM_2,
+ modifiers:{shiftKey:1}, chars:"'"},
+ "'", "Backslash", KeyboardEvent.DOM_VK_HASH, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Khmer
+ if (OS_VERSION >= WIN7) {
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2,
+ modifiers:{}, chars:"\u17E2"},
+ "\u17E2", "Digit2", KeyboardEvent.DOM_VK_2, "\u17E2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2,
+ modifiers:{shiftKey:1}, chars:"\u17D7"},
+ "\u17D7", "Digit2", KeyboardEvent.DOM_VK_2, "\u17D7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2, // Ctrl+2 should cause inputting Euro sign.
+ modifiers:{ctrlKey:1}, chars:"\u20AC", isInputtingCharacters:true},
+ "\u20AC", "Digit2", KeyboardEvent.DOM_VK_2, "\u20AC", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2, // Ctrl+Shift+2 shouldn't cause any input.
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "\u17D7", "Digit2", KeyboardEvent.DOM_VK_2, "\u17D7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2,
+ modifiers:{altKey:1}, chars:"\u17E2"},
+ "\u17E2", "Digit2", KeyboardEvent.DOM_VK_2, "\u17E2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u17D7"},
+ "\u17D7", "Digit2", KeyboardEvent.DOM_VK_2, "\u17D7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2,
+ modifiers:{altGrKey:1}, chars:"2"},
+ "2", "Digit2", KeyboardEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_KHMER, keyCode:WIN_VK_2,
+ modifiers:{altGrKey:1, shiftKey:1}, chars:"\u19E2"},
+ "\u19E2", "Digit2", KeyboardEvent.DOM_VK_2, "\u19E2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ }
+
+ // Norwegian
+ yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_OEM_5,
+ modifiers:{}, chars:"|"},
+ "|", "Backquote", KeyboardEvent.DOM_VK_PIPE, "|", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_OEM_5,
+ modifiers:{shiftKey:1}, chars:"\u00A7"},
+ "\u00A7", "Backquote", KeyboardEvent.DOM_VK_PIPE, "\u00A7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Brazilian ABNT
+ yield testKey({layout:KEYBOARD_LAYOUT_BRAZILIAN_ABNT, keyCode:WIN_VK_ABNT_C1,
+ modifiers:{}, chars:"/"},
+ "/", "IntlBackslash", KeyboardEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_BRAZILIAN_ABNT, keyCode:WIN_VK_ABNT_C1,
+ modifiers:{shiftKey:1}, chars:"?"},
+ "?", "IntlBackslash", KeyboardEvent.DOM_VK_SLASH, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_BRAZILIAN_ABNT, keyCode:WIN_VK_ABNT_C2,
+ modifiers:{numLockKey:1}, chars:"."},
+ ".", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_BRAZILIAN_ABNT, keyCode:WIN_VK_DECIMAL,
+ modifiers:{numLockKey:1}, chars:","},
+ ",", "NumpadDecimal", KeyboardEvent.DOM_VK_DECIMAL, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+
+ // Mac JIS keyboard
+ // The separator key on JIS keyboard for Mac doesn't cause any character even with Japanese keyboard layout.
+ yield testKey({layout:KEYBOARD_LAYOUT_JAPANESE, keyCode:WIN_VK_ABNT_C2,
+ modifiers:{numLockKey:1}, chars:""},
+ "", "NumpadComma", KeyboardEvent.DOM_VK_SEPARATOR, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+ yield testKey({layout:KEYBOARD_LAYOUT_JAPANESE, keyCode:WIN_VK_DECIMAL,
+ modifiers:{numLockKey:1}, chars:"."},
+ ".", "NumpadDecimal", KeyboardEvent.DOM_VK_DECIMAL, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
+
+ // Dead keys on any layouts
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{}, chars:"^^"},
+ ["^^", "^", "^", "^"], "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "^^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E2"},
+ ["\u00E2", "\u00E2", "a"], "KeyQ", KeyboardEvent.DOM_VK_A, "\u00E2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"\u00C2"},
+ ["\u00C2", "\u00C2", "A"], "KeyQ", KeyboardEvent.DOM_VK_A, "\u00C2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_Q,
+ modifiers:{}, chars:"^q"},
+ ["^q", "^", "q", "q"], "KeyA", KeyboardEvent.DOM_VK_Q, "^q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{shiftKey:1}, chars:"\u00A8\u00A8"},
+ ["\u00A8\u00A8", "\u00A8", "\u00A8", "\u00A8"], "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "\u00A8\u00A8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"\u00C4"},
+ ["\u00C4", "\u00C4", "A"], "KeyQ", KeyboardEvent.DOM_VK_A, "\u00C4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E4"},
+ ["\u00E4", "\u00E4", "a"], "KeyQ", KeyboardEvent.DOM_VK_A, "\u00E4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_6,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_Q,
+ modifiers:{shiftKey:1}, chars:"\u00A8Q"},
+ ["\u00A8Q", "\u00A8", "Q", "Q"], "KeyA", KeyboardEvent.DOM_VK_Q, "\u00A8Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{}, chars:"``"},
+ ["``", "`", "`", "`"], "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "``", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E0"},
+ ["\u00E0", "\u00E0", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"\u00C0"},
+ ["\u00C0", "\u00C0", "A"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00C0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_Q,
+ modifiers:{}, chars:"`q"},
+ ["`q", "`", "q", "q"], "KeyQ", KeyboardEvent.DOM_VK_Q, "`q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{shiftKey:1}, chars:"^^"},
+ ["^^", "^", "^", "^"], "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "^^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"\u00C2"},
+ ["\u00C2", "\u00C2", "A"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00C2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E2"},
+ ["\u00E2", "\u00E2", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_Q,
+ modifiers:{shiftKey:1}, chars:"^Q"},
+ ["^Q", "^", "Q", "Q"], "KeyQ", KeyboardEvent.DOM_VK_Q, "^Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{}, chars:"\u00B4\u00B4"},
+ ["\u00B4\u00B4", "\u00B4", "\u00B4", "\u00B4"], "Quote", KeyboardEvent.DOM_VK_QUOTE, "\u00B4\u00B4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E1"},
+ ["\u00E1", "\u00E1", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"\u00C1"},
+ ["\u00C1", "\u00C1", "A"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00C1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_Q,
+ modifiers:{}, chars:"\u00B4q"},
+ ["\u00B4q", "\u00B4", "q", "q"], "KeyQ", KeyboardEvent.DOM_VK_Q, "\u00B4q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{shiftKey:1}, chars:"\u00A8\u00A8"},
+ ["\u00A8\u00A8", "\u00A8", "\u00A8", "\u00A8"], "Quote", KeyboardEvent.DOM_VK_QUOTE, "\u00A8\u00A8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"\u00C4"},
+ ["\u00C4", "\u00C4", "A"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00C4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E4"},
+ ["\u00E4", "\u00E4", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
+ modifiers:{shiftKey:1}, chars:""},
+ "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_Q,
+ modifiers:{shiftKey:1}, chars:"\u00A8Q"},
+ ["\u00A8Q", "\u00A8", "Q", "Q"], "KeyQ", KeyboardEvent.DOM_VK_Q, "\u00A8Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_OEM_1,
+ modifiers:{altGrKey:1}, chars:""},
+ "Dead", "BracketRight", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E3"},
+ ["\u00E3", "\u00E3", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_OEM_1,
+ modifiers:{ctrlKey:1, altKey:1}, chars:""},
+ "Dead", "BracketRight", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"\u00E3"},
+ ["\u00E3", "\u00E3", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ if (OS_VERSION >= WIN8) {
+ // On Russian Mnemonic layout, both 'KeyS' and 'KeyC' are dead key. However, the sequence 'KeyS' -> 'KeyC' causes a composite character.
+ yield testKey({layout:KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC, keyCode:WIN_VK_S,
+ modifiers:{}, chars:""},
+ "Dead", "KeyS", KeyboardEvent.DOM_VK_S, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC, keyCode:WIN_VK_S,
+ modifiers:{}, chars:"\u0441\u0441"},
+ ["\u0441\u0441", "\u0441", "\u0441", "\u0441"], "KeyS", KeyboardEvent.DOM_VK_S, "\u0441\u0441", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC, keyCode:WIN_VK_C,
+ modifiers:{}, chars:""},
+ "Dead", "KeyC", KeyboardEvent.DOM_VK_C, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC, keyCode:WIN_VK_C,
+ modifiers:{}, chars:"\u0446\u0446"},
+ ["\u0446\u0446", "\u0446", "\u0446", "\u0446"], "KeyC", KeyboardEvent.DOM_VK_C, "\u0446\u0446", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC, keyCode:WIN_VK_S,
+ modifiers:{}, chars:""},
+ "Dead", "KeyS", KeyboardEvent.DOM_VK_S, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC, keyCode:WIN_VK_C,
+ modifiers:{}, chars:"\u0449"},
+ ["\u0449", "\u0449", "\u0446"], "KeyC", KeyboardEvent.DOM_VK_C, "\u0449", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ }
+
+ // When Alt key is pressed, dead key sequence is generated with WM_SYSKEYDOWN, WM_SYSDEADCHAR, WM_SYSCHAR and WM_SYSKEYUP.
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1}, chars:"``"},
+ ["``", "`", "`", "`"], "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "``", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{altKey:1}, chars:"\u00E0"},
+ ["\u00E0", "\u00E0", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C0"},
+ ["\u00C0", "\u00C0", "A"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00C0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_Q,
+ modifiers:{altKey:1}, chars:"`q"},
+ ["`q", "`", "q", "q"], "KeyQ", KeyboardEvent.DOM_VK_Q, "`q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:"^^"},
+ ["^^", "^", "^", "^"], "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "^^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{altKey:1, shiftKey:1}, chars:"\u00C2"},
+ ["\u00C2", "\u00C2", "A"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00C2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_A,
+ modifiers:{altKey:1}, chars:"\u00E2"},
+ ["\u00E2", "\u00E2", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_1,
+ modifiers:{altKey:1, shiftKey:1}, chars:""},
+ "Dead", "BracketLeft", KeyboardEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_Q,
+ modifiers:{altKey:1, shiftKey:1}, chars:"^Q"},
+ ["^Q", "^", "Q", "Q"], "KeyQ", KeyboardEvent.DOM_VK_Q, "^Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ cleanup();
+ }
+
+
+ if (IS_WIN) {
+ yield* testKeysOnWindows();
+ } else if (IS_MAC) {
+ yield* testKeysOnMac();
+ } else {
+ cleanup();
+ }
+}
+
+// Test the activation (or not) of an HTML accesskey
+function* runAccessKeyTests()
+{
+ var button = document.getElementById("button");
+ var activationCount;
+
+ function onClick(e)
+ {
+ ++activationCount;
+ }
+
+ // The first parameter is the complete input event. The second and third parameters are
+ // what to test against.
+ function testKey(aEvent, aAccessKey, aShouldActivate)
+ {
+ activationCount = 0;
+ button.setAttribute("accesskey", aAccessKey);
+
+ return synthesizeKey(aEvent, "button", function() {
+
+ var currentTestName = eventToString(aEvent);
+
+ is(activationCount, aShouldActivate ? 1 : 0,
+ currentTestName + ", activating '" + aAccessKey + "'");
+
+ continueTest();
+ });
+ }
+
+ button.addEventListener("click", onClick);
+
+ // These tests have to be per-plaform.
+ if (IS_MAC) {
+ // Basic sanity checks
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{}, chars:"a", unmodifiedChars:"a"},
+ "a", false);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "a", false);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "A", false);
+
+ // Shift-ctrl does not activate accesskeys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0001", unmodifiedChars:"A"},
+ "a", false);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0001", unmodifiedChars:"A"},
+ "A", false);
+ // Alt-ctrl activate accesskeys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "a", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "A", true);
+
+ // Greek layout can activate a Latin accesskey
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u0001", unmodifiedChars:"\u03b1"},
+ "a", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u0001", unmodifiedChars:"\u03b1"},
+ "A", true);
+ // ... and a Greek accesskey!
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u0001", unmodifiedChars:"\u03b1"},
+ "\u03b1", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u0001", unmodifiedChars:"\u03b1"},
+ "\u0391", true);
+
+ // bug 359638
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Period,
+ modifiers:{ctrlKey:1, altKey:1}, chars:".", unmodifiedChars:"."},
+ ".", true);
+
+ // German (KCHR/KeyTranslate case)
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"a", unmodifiedChars:"a"},
+ "a", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"a", unmodifiedChars:"a"},
+ "A", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u00fc", unmodifiedChars:"\u00fc"},
+ "\u00fc", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:MAC_VK_ANSI_LeftBracket,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u00fc", unmodifiedChars:"\u00fc"},
+ "\u00dc", true);
+ }
+ else if (IS_WIN) {
+ // Basic sanity checks
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"a"},
+ "a", false);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1, altKey:1}, chars:"A"},
+ "a", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1, altKey:1}, chars:"A"},
+ "A", true);
+
+ // shift-alt-ctrl does not activate accesskeys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, shiftKey:1, altKey:1}, chars:""},
+ "a", false);
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, shiftKey:1, altKey:1}, chars:""},
+ "A", false);
+
+ // Greek layout can activate a Latin accesskey
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1, altKey:1}, chars:"\u0391"},
+ "a", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1, altKey:1}, chars:"\u0391"},
+ "A", true);
+ // ... and a Greek accesskey!
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1, altKey:1}, chars:"\u0391"},
+ "\u03b1", true);
+ yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1, altKey:1}, chars:"\u0391"},
+ "\u0391", true);
+
+ // bug 359638
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+ modifiers:{shiftKey:1, altKey:1}, chars:".", unmodifiedChars:"."},
+ ".", true);
+ }
+
+ button.removeEventListener("click", onClick);
+}
+
+function* runXULKeyTests()
+{
+ var commandElements = {
+ expectedCommand: document.getElementById("expectedCommand"),
+ unexpectedCommand: document.getElementById("unexpectedCommand"),
+ expectedReservedCommand: document.getElementById("expectedReservedCommand")
+ };
+ // Enable all command elements.
+ for (var id in commandElements) {
+ commandElements[id].removeAttribute("disabled");
+ }
+
+ var keyEvents = [];
+
+ function onKeyInDefaultEventGroup(aDOMEvent)
+ {
+ if (isModifierKeyEvent(aDOMEvent)) {
+ return;
+ }
+ keyEvents.push({ type: aDOMEvent.type, group: "default", phase: getPhase(aDOMEvent), currentTarget: eventTargetToString(aDOMEvent.currentTarget) });
+ }
+
+ function onKeyInSystemEventGroup(aDOMEvent)
+ {
+ if (isModifierKeyEvent(aDOMEvent)) {
+ return;
+ }
+ keyEvents.push({ type: aDOMEvent.type, group: "system", phase: getPhase(aDOMEvent), currentTarget: eventTargetToString(aDOMEvent.currentTarget) });
+ }
+
+ var buttonParent = document.getElementById("button").parentNode;
+ buttonParent.addEventListener("keydown", onKeyInDefaultEventGroup, true);
+ buttonParent.addEventListener("keypress", onKeyInDefaultEventGroup, true);
+ buttonParent.addEventListener("keydown", onKeyInDefaultEventGroup);
+ buttonParent.addEventListener("keypress", onKeyInDefaultEventGroup);
+ SpecialPowers.addSystemEventListener(buttonParent, "keydown", onKeyInSystemEventGroup, true);
+ SpecialPowers.addSystemEventListener(buttonParent, "keypress", onKeyInSystemEventGroup, true);
+ SpecialPowers.addSystemEventListener(buttonParent, "keydown", onKeyInSystemEventGroup, false);
+ SpecialPowers.addSystemEventListener(buttonParent, "keypress", onKeyInSystemEventGroup, false);
+
+ function finializeKeyElementTest()
+ {
+ buttonParent.removeEventListener("keydown", onKeyInDefaultEventGroup, true);
+ buttonParent.removeEventListener("keypress", onKeyInDefaultEventGroup, true);
+ buttonParent.removeEventListener("keydown", onKeyInDefaultEventGroup);
+ buttonParent.removeEventListener("keypress", onKeyInDefaultEventGroup);
+ SpecialPowers.removeSystemEventListener(buttonParent, "keydown", onKeyInSystemEventGroup, true);
+ SpecialPowers.removeSystemEventListener(buttonParent, "keypress", onKeyInSystemEventGroup, true);
+ SpecialPowers.removeSystemEventListener(buttonParent, "keydown", onKeyInSystemEventGroup, false);
+ SpecialPowers.removeSystemEventListener(buttonParent, "keypress", onKeyInSystemEventGroup, false);
+ }
+
+ // If aKeyElement is empty string, this function tests if the event kicks
+ // no key elements.
+ function testKeyElement(aEvent, aKeyElementId)
+ {
+ var testName = "testKeyElement (with non-reserved command element): " + eventToString(aEvent) + " ";
+ var keyElement = aKeyElementId == "" ? null : document.getElementById(aKeyElementId);
+ if (keyElement) {
+ keyElement.setAttribute("command", "expectedCommand");
+ }
+
+ /* eslint-disable-next-line no-shadow */
+ for (var id in commandElements) {
+ commandElements[id].activeCount = 0;
+ }
+
+ keyEvents = [];
+ return synthesizeKey(aEvent, "button", function() {
+ if (keyElement) {
+ is(commandElements.expectedCommand.activeCount, 1, testName + "the command element (id='expectedCommand') should be preformed");
+ } else {
+ is(commandElements.expectedCommand.activeCount, 0, testName + "the command element (id='expectedCommand') shouldn't be preformed");
+ }
+ is(commandElements.unexpectedCommand.activeCount, 0, testName + "the command element (id='unexpectedCommand') shouldn't be preformed");
+ is(commandElements.expectedReservedCommand.activeCount, 0, testName + "the command element (id='expectedReservedCommand') shouldn't be preformed");
+
+ function checkFiredEvents()
+ {
+ let expectKeyPressEvent = aKeyElementId != "" ||
+ ((aEvent.modifiers.ctrlKey || aEvent.modifiers.altKey || aEvent.modifiers.metaKey) &&
+ (!IS_WIN || !aEvent.modifiers.altGrKey));
+ is(keyEvents.length, expectKeyPressEvent ? 8 : 4, testName + "wrong number events fired");
+ is(JSON.stringify(keyEvents[0]), JSON.stringify({ type: "keydown", group: "default", phase: "capture", currentTarget: eventTargetToString(buttonParent) }), testName + "keydown event should be fired on chrome in the default event group #0");
+ is(JSON.stringify(keyEvents[1]), JSON.stringify({ type: "keydown", group: "default", phase: "bubble", currentTarget: eventTargetToString(buttonParent) }), testName + "keydown event should be fired on chrome in the default event group #1");
+
+ is(JSON.stringify(keyEvents[2]), JSON.stringify({ type: "keydown", group: "system", phase: "capture", currentTarget: eventTargetToString(buttonParent) }), testName + "keydown event should be fired on chrome in the system event group #2");
+ is(JSON.stringify(keyEvents[3]), JSON.stringify({ type: "keydown", group: "system", phase: "bubble", currentTarget: eventTargetToString(buttonParent) }), testName + "keydown event should be fired on chrome in the system event group #3");
+
+ if (expectKeyPressEvent) {
+ is(JSON.stringify(keyEvents[4]), JSON.stringify({ type: "keypress", group: "default", phase: "capture", currentTarget: eventTargetToString(buttonParent) }), testName + "keypress event should be fired on chrome in the default event group #4");
+ is(JSON.stringify(keyEvents[5]), JSON.stringify({ type: "keypress", group: "default", phase: "bubble", currentTarget: eventTargetToString(buttonParent) }), testName + "keypress event should be fired on chrome in the default event group #5");
+
+ is(JSON.stringify(keyEvents[6]), JSON.stringify({ type: "keypress", group: "system", phase: "capture", currentTarget: eventTargetToString(buttonParent) }), testName + "keypress event should be fired on chrome in the system event group #6");
+ is(JSON.stringify(keyEvents[7]), JSON.stringify({ type: "keypress", group: "system", phase: "bubble", currentTarget: eventTargetToString(buttonParent) }), testName + "keypress event should be fired on chrome in the system event group #7");
+ }
+ }
+
+ checkFiredEvents();
+
+ if (keyElement) {
+ testName = "testKeyElement (with reserved command element): " + eventToString(aEvent) + " ";
+ keyElement.setAttribute("command", "expectedReservedCommand");
+
+ for (id in commandElements) {
+ commandElements[id].activeCount = 0;
+ }
+ keyEvents = [];
+ synthesizeKey(aEvent, "button", function() {
+ is(commandElements.expectedCommand.activeCount, 0, testName + "the command element (id='expectedCommand') shouldn't be preformed");
+ is(commandElements.unexpectedCommand.activeCount, 0, testName + "the command element (id='unexpectedCommand') shouldn't be preformed");
+ is(commandElements.expectedReservedCommand.activeCount, 1, testName + "the command element (id='expectedReservedCommand') should be preformed");
+
+ checkFiredEvents();
+
+ if (keyElement) {
+ keyElement.setAttribute("command", "unexpectedCommand");
+ }
+ continueTest();
+ });
+ } else {
+ if (keyElement) {
+ keyElement.setAttribute("command", "unexpectedCommand");
+ }
+ continueTest();
+ }
+ });
+ }
+
+ if (IS_MAC) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{metaKey:1}, chars:";", unmodifiedChars:";"},
+ "unshiftedKey");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Semicolon,
+ modifiers:{metaKey:1, shiftKey:1}, chars:";", unmodifiedChars:":"},
+ "shiftedKey");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{metaKey:1}, chars:"'", unmodifiedChars:"'"},
+ "reservedUnshiftedKey");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Quote,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"\"", unmodifiedChars:"'"},
+ "reservedShiftedKey");
+ }
+ else if (IS_WIN) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{ctrlKey:1}, chars:""},
+ "unshiftedKey");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "shiftedKey");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{ctrlKey:1}, chars:""},
+ "reservedUnshiftedKey");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "reservedShiftedKey");
+ }
+
+ // 429160
+ if (IS_MAC) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_F,
+ modifiers:{metaKey:1, altKey:1}, chars:"\u0192", unmodifiedChars:"f"},
+ "commandOptionF");
+ }
+ else if (IS_WIN) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"\u0006"},
+ "commandOptionF");
+ }
+
+ // 432112
+ if (IS_MAC) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_SWEDISH, keyCode:MAC_VK_ANSI_Minus,
+ modifiers:{metaKey:1, shiftKey:1}, chars:"+", unmodifiedChars:"?"},
+ "question");
+ }
+ else if (IS_WIN) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_SWEDISH, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "question");
+ // For testing if Ctrl+? is kicked without Shift state, temporarily disable
+ // Ctrl-+ key element.
+ var unshiftedPlusKeyElement = document.getElementById("unshiftedPlus");
+ unshiftedPlusKeyElement.setAttribute("disabled", "true");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_SWEDISH, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{ctrlKey:1}, chars:""},
+ "");
+ unshiftedPlusKeyElement.removeAttribute("disabled");
+ }
+
+ // bug 433192
+ if (IS_WIN) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1}, chars:"\u0018"},
+ "unshiftedX");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0018"},
+ "shiftedX");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_ARABIC, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1}, chars:"\u0018"},
+ "unshiftedX");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_ARABIC, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0018"},
+ "shiftedX");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_HEBREW, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1}, chars:"\u0018"},
+ "unshiftedX");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_HEBREW, keyCode:WIN_VK_X,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0018"},
+ "shiftedX");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_JAPANESE, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "unshiftedPlus");
+ }
+
+ // bug 759346
+ if (IS_WIN) {
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_THAI, keyCode:WIN_VK_1,
+ modifiers:{ctrlKey:1}, chars:""},
+ "");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_THAI, keyCode:WIN_VK_1,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "unshiftedPlus");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_THAI, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{ctrlKey:1}, chars:""},
+ "unshiftedPlus");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_THAI, keyCode:WIN_VK_OEM_PLUS,
+ modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+ "");
+ }
+
+ // bug 1596916
+ if (IS_WIN) {
+ // Ctrl + Alt + foo should be performed only when AltGr key is not
+ // pressed, i.e., only when Ctrl key and left Alt key are pressed if
+ // active keyboard layout has AltGr key.
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_A,
+ modifiers:{altGrKey:1}, chars:""},
+ "");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, altGrKey:1}, chars:""},
+ "");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_A,
+ modifiers:{altKey:1, altGrKey:1}, chars:""},
+ "");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:""},
+ "ctrlAltA");
+ yield testKeyElement({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, altKey:1, altGrKey:1}, chars:""},
+ "");
+ }
+
+ for (id in commandElements) {
+ commandElements[id].setAttribute("disabled", "true");
+ }
+ finializeKeyElementTest();
+}
+
+function* runReservedKeyTests()
+{
+ var browser = document.getElementById("browser");
+ var contents = [
+ browser.contentWindow,
+ browser.contentDocument,
+ browser.contentDocument.documentElement,
+ browser.contentDocument.body,
+ browser.contentDocument.getElementById("content_button")
+ ];
+
+ for (var i = 0; i < contents.length; i++) {
+ contents[i].addEventListener("keydown", onKeyInDefaultEventGroup, true);
+ contents[i].addEventListener("keypress", onKeyInDefaultEventGroup, true);
+ contents[i].addEventListener("keydown", onKeyInDefaultEventGroup);
+ contents[i].addEventListener("keypress", onKeyInDefaultEventGroup);
+ SpecialPowers.addSystemEventListener(contents[i], "keydown", onKeyInSystemEventGroup, true);
+ SpecialPowers.addSystemEventListener(contents[i], "keypress", onKeyInSystemEventGroup, true);
+ SpecialPowers.addSystemEventListener(contents[i], "keydown", onKeyInSystemEventGroup, false);
+ SpecialPowers.addSystemEventListener(contents[i], "keypress", onKeyInSystemEventGroup, false);
+ }
+
+ var keyEvents = [];
+
+ function onKeyInDefaultEventGroup(aDOMEvent)
+ {
+ if (isModifierKeyEvent(aDOMEvent)) {
+ return;
+ }
+ keyEvents.push({ type: aDOMEvent.type, group: "default", phase: getPhase(aDOMEvent), currentTarget: eventTargetToString(aDOMEvent.currentTarget) });
+ }
+
+ function onKeyInSystemEventGroup(aDOMEvent)
+ {
+ if (isModifierKeyEvent(aDOMEvent)) {
+ return;
+ }
+ keyEvents.push({ type: aDOMEvent.type, group: "system", phase: getPhase(aDOMEvent), currentTarget: eventTargetToString(aDOMEvent.currentTarget) });
+ // prevents reserved default action
+ if (aDOMEvent.type == "keypress" &&
+ aDOMEvent.eventPhase == aDOMEvent.BUBBLING_PHASE &&
+ aDOMEvent.currentTarget == browser.contentWindow) {
+ aDOMEvent.preventDefault();
+ }
+ }
+
+ function finializeKeyElementTest()
+ {
+ /* eslint-disable-next-line no-shadow */
+ for (var i = 0; i < contents.length; i++) {
+ contents[i].removeEventListener("keydown", onKeyInDefaultEventGroup, true);
+ contents[i].removeEventListener("keypress", onKeyInDefaultEventGroup, true);
+ contents[i].removeEventListener("keydown", onKeyInDefaultEventGroup);
+ contents[i].removeEventListener("keypress", onKeyInDefaultEventGroup);
+ SpecialPowers.removeSystemEventListener(contents[i], "keydown", onKeyInSystemEventGroup, true);
+ SpecialPowers.removeSystemEventListener(contents[i], "keypress", onKeyInSystemEventGroup, true);
+ SpecialPowers.removeSystemEventListener(contents[i], "keydown", onKeyInSystemEventGroup, false);
+ SpecialPowers.removeSystemEventListener(contents[i], "keypress", onKeyInSystemEventGroup, false);
+ }
+ }
+
+ function testReservedKey(aEvent)
+ {
+ keyEvents = [];
+ return synthesizeKey(aEvent, "content_button", function() {
+ let testName = "testReservedKey: " + eventToString(aEvent) + " ";
+ is(keyEvents.length, 20, testName + "wrong number events fired");
+ is(JSON.stringify(keyEvents[0]), JSON.stringify({ type: "keydown", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[0]) }), testName + "keydown event should be fired on content only in the system event group #0");
+ is(JSON.stringify(keyEvents[1]), JSON.stringify({ type: "keydown", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[1]) }), testName + "keydown event should be fired on content only in the system event group #1");
+ is(JSON.stringify(keyEvents[2]), JSON.stringify({ type: "keydown", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[2]) }), testName + "keydown event should be fired on content only in the system event group #2");
+ is(JSON.stringify(keyEvents[3]), JSON.stringify({ type: "keydown", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[3]) }), testName + "keydown event should be fired on content only in the system event group #3");
+
+ is(JSON.stringify(keyEvents[4]), JSON.stringify({ type: "keydown", group: "system", phase: "target", currentTarget: eventTargetToString(contents[4]) }), testName + "keydown event should be fired on content only in the system event group #4");
+ is(JSON.stringify(keyEvents[5]), JSON.stringify({ type: "keydown", group: "system", phase: "target", currentTarget: eventTargetToString(contents[4]) }), testName + "keydown event should be fired on content only in the system event group #5");
+
+ is(JSON.stringify(keyEvents[6]), JSON.stringify({ type: "keydown", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[3]) }), testName + "keydown event should be fired on content only in the system event group #6");
+ is(JSON.stringify(keyEvents[7]), JSON.stringify({ type: "keydown", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[2]) }), testName + "keydown event should be fired on content only in the system event group #7");
+ is(JSON.stringify(keyEvents[8]), JSON.stringify({ type: "keydown", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[1]) }), testName + "keydown event should be fired on content only in the system event group #8");
+ is(JSON.stringify(keyEvents[9]), JSON.stringify({ type: "keydown", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[0]) }), testName + "keydown event should be fired on content only in the system event group #9");
+
+ is(JSON.stringify(keyEvents[10]), JSON.stringify({ type: "keypress", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[0]) }), testName + "keypress event should be fired on content only in the system event group #10");
+ is(JSON.stringify(keyEvents[11]), JSON.stringify({ type: "keypress", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[1]) }), testName + "keypress event should be fired on content only in the system event group #11");
+ is(JSON.stringify(keyEvents[12]), JSON.stringify({ type: "keypress", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[2]) }), testName + "keypress event should be fired on content only in the system event group #12");
+ is(JSON.stringify(keyEvents[13]), JSON.stringify({ type: "keypress", group: "system", phase: "capture", currentTarget: eventTargetToString(contents[3]) }), testName + "keypress event should be fired on content only in the system event group #13");
+
+ is(JSON.stringify(keyEvents[14]), JSON.stringify({ type: "keypress", group: "system", phase: "target", currentTarget: eventTargetToString(contents[4]) }), testName + "keypress event should be fired on content only in the system event group #14");
+ is(JSON.stringify(keyEvents[15]), JSON.stringify({ type: "keypress", group: "system", phase: "target", currentTarget: eventTargetToString(contents[4]) }), testName + "keypress event should be fired on content only in the system event group #15");
+
+ is(JSON.stringify(keyEvents[16]), JSON.stringify({ type: "keypress", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[3]) }), testName + "keypress event should be fired on content only in the system event group #16");
+ is(JSON.stringify(keyEvents[17]), JSON.stringify({ type: "keypress", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[2]) }), testName + "keypress event should be fired on content only in the system event group #17");
+ is(JSON.stringify(keyEvents[18]), JSON.stringify({ type: "keypress", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[1]) }), testName + "keypress event should be fired on content only in the system event group #18");
+ is(JSON.stringify(keyEvents[19]), JSON.stringify({ type: "keypress", group: "system", phase: "bubble", currentTarget: eventTargetToString(contents[0]) }), testName + "keypress event should be fired on content only in the system event group #19");
+
+ continueTest();
+ });
+ }
+
+ if (IS_MAC) {
+ // Cmd+T is reserved for opening new tab.
+ yield testReservedKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_T,
+ modifiers:{metaKey:1}, chars:"t", unmodifiedChars:"t"});
+ } else if (IS_WIN) {
+ // Ctrl+T is reserved for opening new tab.
+ yield testReservedKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+ modifiers:{ctrlKey:1}, chars:"\u0014"});
+ yield testReservedKey({layout:KEYBOARD_LAYOUT_ARABIC, keyCode:WIN_VK_T,
+ modifiers:{ctrlKey:1}, chars:"\u0014"});
+ }
+
+ finializeKeyElementTest();
+}
+
+function* runTextInputTests()
+{
+ var textbox = document.getElementById("textbox");
+
+ function testKey(aEvent, aExpectText) {
+ textbox.value = "";
+ textbox.focus();
+
+ /* eslint-disable-next-line no-shadow */
+ var currentTestName = eventToString(aEvent);
+
+ // Check if the text comes with keypress events rather than composition events.
+ var keypress = 0;
+ /* eslint-disable-next-line no-shadow */
+ function onKeypress(aEvent) {
+ keypress++;
+ if (keypress == 1 && aExpectText == "") {
+ if (!aEvent.ctrlKey && !aEvent.altKey) {
+ is(aEvent.charCode, 0,
+ currentTestName + ", the charCode value should be 0 when it shouldn't cause inputting text");
+ }
+ return;
+ }
+ if (keypress > aExpectText.length) {
+ ok(false, currentTestName + " causes too many keypress events");
+ return;
+ }
+ is(aEvent.key, aExpectText[keypress - 1],
+ currentTestName + ", " + keypress + "th keypress event's key value should be '" + aExpectText[keypress - 1] + "'");
+ is(aEvent.charCode, aExpectText.charCodeAt(keypress - 1),
+ currentTestName + ", " + keypress + "th keypress event's charCode value should be 0x" + parseInt(aExpectText.charCodeAt(keypress - 1), 16));
+ }
+ textbox.addEventListener("keypress", onKeypress, true);
+
+ return synthesizeKey(aEvent, "textbox", () => {
+ textbox.removeEventListener("keypress", onKeypress, true);
+ if (aExpectText == "") {
+ if (aEvent.modifiers.ctrlKey || aEvent.modifiers.altKey) {
+ is(keypress, 1,
+ currentTestName + " should cause one keypress event because it should be available for shortcut key");
+ } else {
+ is(keypress, 0,
+ currentTestName + " should cause no keypress event because simple key press only with Shift/AltGraph " +
+ "or without any modifiers shouldn't match with a shortcut key");
+ }
+ } else {
+ is(keypress, aExpectText.length,
+ currentTestName + " should cause " + aExpectText.length + " keypress events");
+ is(textbox.value, aExpectText,
+ currentTestName + " does not input correct text.");
+ }
+
+ continueTest();
+ });
+ }
+
+ if (IS_MAC) {
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_G,
+ modifiers:{shiftKey:1}, chars:"\u0644\u0623", unmodifiedChars:"\u0644\u0623"},
+ "\u0644\u0623");
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_T,
+ modifiers:{shiftKey:1}, chars:"\u0644\u0625", unmodifiedChars:"\u0644\u0625"},
+ "\u0644\u0625");
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_B,
+ modifiers:{shiftKey:1}, chars:"\u0644\u0622", unmodifiedChars:"\u0644\u0622"},
+ "\u0644\u0622");
+ yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_B,
+ modifiers:{}, chars:"\u0644\u0627", unmodifiedChars:"\u0644\u0627"},
+ "\u0644\u0627");
+ } else if (IS_WIN) {
+ // Basic sanity checks
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{}, chars:"a"},
+ "a");
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{shiftKey:1}, chars:"A"},
+ "A");
+ // When Ctrl+Alt are pressed, any text should not be inputted.
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:""},
+ "");
+ // AltGr should input only when it's mapped to a character
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_0,
+ modifiers:{altGrKey:1}, chars:"}"},
+ "}");
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_A,
+ modifiers:{altGrKey:1}, chars:""},
+ "");
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_A,
+ modifiers:{ctrlKey:1, altKey:1}, chars:""},
+ "");
+
+ // Lithuanian AltGr should be consumed at 9/0 keys pressed
+ yield testKey({layout:KEYBOARD_LAYOUT_LITHUANIAN, keyCode:WIN_VK_8,
+ modifiers:{}, chars:"\u016B"},
+ "\u016B");
+ yield testKey({layout:KEYBOARD_LAYOUT_LITHUANIAN, keyCode:WIN_VK_9,
+ modifiers:{}, chars:"9"},
+ "9");
+ yield testKey({layout:KEYBOARD_LAYOUT_LITHUANIAN, keyCode:WIN_VK_0,
+ modifiers:{}, chars:"0"},
+ "0");
+ yield testKey({layout:KEYBOARD_LAYOUT_LITHUANIAN, keyCode:WIN_VK_8,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"8"},
+ "8");
+ yield testKey({layout:KEYBOARD_LAYOUT_LITHUANIAN, keyCode:WIN_VK_9,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"9"},
+ "9");
+ yield testKey({layout:KEYBOARD_LAYOUT_LITHUANIAN, keyCode:WIN_VK_0,
+ modifiers:{ctrlKey:1, altKey:1}, chars:"0"},
+ "0");
+ }
+
+ // XXX We need to move focus for canceling to search the autocomplete
+ // result. If we don't do here, Fx will crash at end of this tests.
+ document.getElementById("button").focus();
+}
+
+function* runAltRightKeyOnWindows()
+{
+ if (!IS_WIN) {
+ return;
+ }
+
+ var button = document.getElementById("button");
+ button.focus();
+
+ const kKeyboardLayouts = [
+ { layout: KEYBOARD_LAYOUT_ARABIC },
+ { layout: KEYBOARD_LAYOUT_BRAZILIAN_ABNT },
+ { layout: KEYBOARD_LAYOUT_EN_US },
+ { layout: KEYBOARD_LAYOUT_FRENCH },
+ { layout: KEYBOARD_LAYOUT_GREEK },
+ { layout: KEYBOARD_LAYOUT_GERMAN },
+ { layout: KEYBOARD_LAYOUT_HEBREW },
+ { layout: KEYBOARD_LAYOUT_JAPANESE },
+ { layout: KEYBOARD_LAYOUT_KHMER },
+ { layout: KEYBOARD_LAYOUT_LITHUANIAN },
+ { layout: KEYBOARD_LAYOUT_NORWEGIAN },
+ { layout: KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC,
+ canTestIt () { return OS_VERSION >= WIN8; } },
+ { layout: KEYBOARD_LAYOUT_SPANISH },
+ { layout: KEYBOARD_LAYOUT_SWEDISH },
+ { layout: KEYBOARD_LAYOUT_THAI },
+ ];
+ var events = [];
+ function pushEvent(aEvent) {
+ events.push(aEvent);
+ if (aEvent.key === "Alt") {
+ // Prevent working the menubar.
+ aEvent.preventDefault();
+ }
+ }
+ button.addEventListener("keydown", pushEvent);
+ button.addEventListener("keyup", pushEvent);
+
+ function testKey(aKeyboardLayout) {
+ return synthesizeKey({layout: aKeyboardLayout.layout, keyCode: WIN_VK_RMENU,
+ modifiers: {}, chars: ""}, "button", function() {
+ const kDescription =
+ "runAltRightKeyOnWindows(" + aKeyboardLayout.layout.currentTestName + "): ";
+ if (aKeyboardLayout.layout.hasAltGrOnWin) {
+ is(events.length, 4,
+ kDescription + "AltRight should fire 2 pairs of keydown and keyup events");
+ is(events[0].type, "keydown",
+ kDescription + "First event should be keydown of ControlLeft");
+ is(events[0].key, "Control",
+ kDescription + "First event should be keydown of ControlLeft whose key should be Control");
+ is(events[0].code, "ControlLeft",
+ kDescription + "First event should be keydown of ControlLeft");
+ is(events[0].location, KeyboardEvent.DOM_KEY_LOCATION_LEFT,
+ kDescription + "First event should be keydown of ControlLeft whose location should be DOM_KEY_LOCATION_LEFT");
+ is(events[0].keyCode, KeyboardEvent.DOM_VK_CONTROL,
+ kDescription + "First event should be keydown of ControlLeft whose keyCode should be DOM_VK_CONTROL");
+ is(events[0].ctrlKey, true,
+ kDescription + "First event should be keydown of ControlLeft whose ctrlKey should be true");
+ is(events[0].altKey, false,
+ kDescription + "First event should be keydown of ControlLeft whose altKey should be false");
+ is(events[0].getModifierState("AltGraph"), false,
+ kDescription + "First event should be keydown of ControlLeft whose getModifierState(\"AltGraph\") should be false");
+ is(events[1].type, "keydown",
+ kDescription + "Second event should be keydown of AltRight");
+ is(events[1].key, "AltGraph",
+ kDescription + "Second event should be keydown of AltRight whose key should be AltGraph");
+ is(events[1].code, "AltRight",
+ kDescription + "Second event should be keydown of AltRight");
+ is(events[1].location, KeyboardEvent.DOM_KEY_LOCATION_RIGHT,
+ kDescription + "Second event should be keydown of AltRight whose location should be DOM_KEY_LOCATION_RIGHT");
+ is(events[1].keyCode, KeyboardEvent.DOM_VK_ALT,
+ kDescription + "Second event should be keydown of AltRight whose keyCode should be DOM_VK_ALT");
+ is(events[1].ctrlKey, false,
+ kDescription + "Second event should be keydown of AltRight whose ctrlKey should be false");
+ is(events[1].altKey, false,
+ kDescription + "Second event should be keydown of AltRight whose altKey should be false");
+ is(events[1].getModifierState("AltGraph"), true,
+ kDescription + "Second event should be keydown of AltRight whose getModifierState(\"AltGraph\") should be true");
+ is(events[2].type, "keyup",
+ kDescription + "Third event should be keyup of ControlLeft");
+ is(events[2].key, "Control",
+ kDescription + "Third event should be keyup of ControlLeft whose key should be Control");
+ is(events[2].code, "ControlLeft",
+ kDescription + "Third event should be keyup of ControlLeft");
+ is(events[2].location, KeyboardEvent.DOM_KEY_LOCATION_LEFT,
+ kDescription + "Third event should be keyup of ControlLeft whose location should be DOM_KEY_LOCATION_LEFT");
+ is(events[2].keyCode, KeyboardEvent.DOM_VK_CONTROL,
+ kDescription + "Third event should be keyup of ControlLeft whose keyCode should be DOM_VK_CONTROL");
+ is(events[2].ctrlKey, false,
+ kDescription + "Third event should be keyup of ControlLeft whose ctrlKey should be false");
+ is(events[2].altKey, false,
+ kDescription + "Third event should be keyup of ControlLeft whose altKey should be false");
+ is(events[2].getModifierState("AltGraph"), true,
+ kDescription + "Third event should be keyup of ControlLeft whose getModifierState(\"AltGraph\") should be true");
+ is(events[3].type, "keyup",
+ kDescription + "Forth event should be keyup of AltRight");
+ is(events[3].key, "AltGraph",
+ kDescription + "Forth event should be keyup of AltRight whose key should be AltGraph");
+ is(events[3].code, "AltRight",
+ kDescription + "Forth event should be keyup of AltRight");
+ is(events[3].location, KeyboardEvent.DOM_KEY_LOCATION_RIGHT,
+ kDescription + "Forth event should be keyup of AltRight whose location should be DOM_KEY_LOCATION_RIGHT");
+ is(events[3].keyCode, KeyboardEvent.DOM_VK_ALT,
+ kDescription + "Forth event should be keyup of AltRight whose keyCode should be DOM_VK_ALT");
+ is(events[3].ctrlKey, false,
+ kDescription + "Third event should be keyup of AltRight whose ctrlKey should be false");
+ is(events[3].altKey, false,
+ kDescription + "Third event should be keyup of AltRight whose altKey should be false");
+ is(events[3].getModifierState("AltGraph"), false,
+ kDescription + "Third event should be keyup of AltRight whose getModifierState(\"AltGraph\") should be false");
+ } else {
+ is(events.length, 2,
+ kDescription + "AltRight should fire a pair of keydown and keyup events");
+ is(events[0].type, "keydown",
+ kDescription + "First event should be keydown of AltRight");
+ is(events[0].key, "Alt",
+ kDescription + "First event should be keydown of AltRight whose key should be Alt");
+ is(events[0].code, "AltRight",
+ kDescription + "First event should be keydown of AltRight");
+ is(events[0].location, KeyboardEvent.DOM_KEY_LOCATION_RIGHT,
+ kDescription + "First event should be keydown of AltRight whose location should be DOM_KEY_LOCATION_RIGHT");
+ is(events[0].keyCode, KeyboardEvent.DOM_VK_ALT,
+ kDescription + "First event should be keydown of AltRight whose keyCode should be DOM_VK_ALT");
+ is(events[0].ctrlKey, false,
+ kDescription + "First event should be keydown of AltRight whose ctrlKey should be false");
+ is(events[0].altKey, true,
+ kDescription + "First event should be keydown of AltRight whose altKey should be true");
+ is(events[0].getModifierState("AltGraph"), false,
+ kDescription + "First event should be keydown of AltRight whose getModifierState(\"AltGraph\") should be false");
+ is(events[1].type, "keyup",
+ kDescription + "Second event should be keyup of AltRight");
+ is(events[1].key, "Alt",
+ kDescription + "Second event should be keyup of AltRight whose key should be Alt");
+ is(events[1].code, "AltRight",
+ kDescription + "Second event should be keyup of AltRight");
+ is(events[1].location, KeyboardEvent.DOM_KEY_LOCATION_RIGHT,
+ kDescription + "Second event should be keyup of AltRight whose location should be DOM_KEY_LOCATION_RIGHT");
+ is(events[1].keyCode, KeyboardEvent.DOM_VK_ALT,
+ kDescription + "Second event should be keyup of AltRight whose keyCode should be DOM_VK_ALT");
+ is(events[1].ctrlKey, false,
+ kDescription + "Second event should be keyup of AltRight whose ctrlKey should be false");
+ is(events[1].altKey, false,
+ kDescription + "Second event should be keyup of AltRight whose altKey should be false");
+ is(events[1].getModifierState("AltGraph"), false,
+ kDescription + "Second event should be keyup of AltRight whose getModifierState(\"AltGraph\") should be false");
+ }
+
+ continueTest();
+ });
+ }
+
+ for (const kKeyboardLayout of kKeyboardLayouts) {
+ if (typeof kKeyboardLayout.canTestIt === "function" &&
+ !kKeyboardLayout.canTestIt()) {
+ continue;
+ }
+ events = [];
+ yield testKey(kKeyboardLayout);
+ }
+
+ button.addEventListener("keydown", pushEvent);
+ button.addEventListener("keyup", pushEvent);
+}
+
+function* runAllTests() {
+ yield* runKeyEventTests();
+ yield* runAccessKeyTests();
+ yield* runXULKeyTests();
+ yield* runReservedKeyTests();
+ yield* runTextInputTests();
+ yield* runAltRightKeyOnWindows();
+}
+
+var gTestContinuation = null;
+
+function continueTest()
+{
+ if (!gTestContinuation) {
+ gTestContinuation = runAllTests();
+ }
+ var ret = gTestContinuation.next();
+ if (ret.done) {
+ SimpleTest.finish();
+ } else {
+ is(ret.value, true, "Key synthesized successfully");
+ }
+}
+
+function runTest()
+{
+ if (!IS_MAC && !IS_WIN) {
+ todo(false, "This test is supported on MacOSX and Windows only. (Bug 431503)");
+ return;
+ }
+
+ if (IS_WIN && OS_VERSION >= WIN8) {
+ // Switching keyboard layout to Russian - Mnemonic causes 2 assertions in
+ // KeyboardLayout::LoadLayout().
+ const kAssertionCountDueToRussainMnemonic = 2 * 2;
+ SimpleTest.expectAssertions(kAssertionCountDueToRussainMnemonic,
+ kAssertionCountDueToRussainMnemonic);
+ }
+ SimpleTest.waitForExplicitFinish();
+
+ clearInfobars();
+
+ continueTest();
+}
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_keypress_event_with_alt_on_mac.html b/widget/tests/test_keypress_event_with_alt_on_mac.html
new file mode 100644
index 0000000000..01d4100f97
--- /dev/null
+++ b/widget/tests/test_keypress_event_with_alt_on_mac.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Testing if keypress event is fired when alt key is pressed</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/NativeKeyCodes.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="display">
+ <input id="input">
+ <input id="password" type="password">
+ <input id="readonly-input" readonly>
+ <textarea id="textarea"></textarea>
+ <textarea id="readonly-textarea" readonly></textarea>
+ <button id="button">button</button>
+</div>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+async function testNativeKey(aKeyboardLayout, aNativeKeyCode, aModifiers,
+ aChars, aUnmodifiedChars) {
+ // XXX Need to listen keyup event here because synthesizeNativeKey() does not
+ // guarantee that its callback will be called after "keypress" and "keyup".
+ let waitForKeyUp = new Promise(resolve => {
+ document.addEventListener("keyup", resolve, {once: true});
+ });
+ let keypress;
+ document.addEventListener("keypress", (aKeyPressEvent) => {
+ keypress = aKeyPressEvent;
+ }, {once: true});
+ synthesizeNativeKey(aKeyboardLayout, aNativeKeyCode, aModifiers, aChars, aUnmodifiedChars);
+ await waitForKeyUp;
+ return keypress;
+}
+
+async function runTests() {
+ const kTests =
+ [ { target: "input", isEditable: true },
+ { target: "password", isEditable: true },
+ { target: "readonly-input", isEditable: false },
+ { target: "textarea", isEditable: true },
+ { target: "readonly-textarea", isEditable: false },
+ { target: "button", isEditable: false } ];
+ for (const kTest of kTests) {
+ let element = document.getElementById(kTest.target);
+ element.focus();
+
+ const kDescription = kTest.target + ": ";
+
+ let keypress = await testNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {}, "a", "a");
+ ok(keypress,
+ kDescription + "'a' key press should cause firing keypress event");
+
+ keypress = await testNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {shiftKey: true}, "A", "A");
+ ok(keypress,
+ kDescription + "'a' key press with shift key should cause firing keypress event");
+ ok(keypress.shiftKey,
+ kDescription + "shiftKey of 'a' key press with shift key should be true");
+
+ // When a key inputs a character with option key, we need to unset altKey for our editor.
+ // Otherwise, altKey should be true for compatibility with the other browsers.
+ keypress = await testNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {altKey: true}, "\u00E5", "a");
+ ok(keypress,
+ kDescription + "'a' key press with option key should cause firing keypress event");
+ is(keypress.altKey, !kTest.isEditable,
+ kDescription + "altKey of 'a' key press with option key should be " + !kTest.isEditable);
+
+ keypress = await testNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {altKey: true, shiftKey: true}, "\u00C5", "A");
+ ok(keypress,
+ kDescription + "'a' key press with option key and shift key should cause firing keypress event");
+ is(keypress.altKey, !kTest.isEditable,
+ kDescription + "altKey of 'a' key press with option key and shift key should be " + !kTest.isEditable);
+ ok(keypress.shiftKey,
+ kDescription + "shiftKey of 'a' key press with option key and shift key should be true");
+
+ keypress = await testNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {ctrlKey: true}, "\u0001", "a");
+ ok(!keypress,
+ kDescription + "'a' key press with control key should not cause firing keypress event");
+
+ keypress = await testNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {altKey: true, ctrlKey: true}, "\u0001", "a");
+ ok(!keypress,
+ kDescription + "'a' key press with option key and control key should not cause firing keypress event");
+
+ // XXX Cannot test with command key for now since keyup event won't be fired due to macOS's limitation.
+
+ // Some keys of Arabic - PC keyboard layout do not input any character with option key.
+ // In such case, we shouldn't dispatch keypress event.
+ keypress = await testNativeKey(KEYBOARD_LAYOUT_ARABIC_PC, MAC_VK_ANSI_7, {altKey: true}, "", "\u0667");
+ ok(!keypress,
+ kDescription + "'7' key press with option key should not cause firing keypress event");
+ }
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/widget/tests/test_mouse_event_with_control_on_mac.html b/widget/tests/test_mouse_event_with_control_on_mac.html
new file mode 100644
index 0000000000..52ce206d35
--- /dev/null
+++ b/widget/tests/test_mouse_event_with_control_on_mac.html
@@ -0,0 +1,116 @@
+<html>
+<head>
+ <title>Test control+click on Mac</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/SpecialPowers.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style>
+ #target {
+ width: 100px;
+ height: 100px;
+ background-color: lightgreen;
+ };
+ </style>
+</head>
+<body>
+<div id="target"></div>
+<script type="application/javascript">
+
+function waitAndCheckMouseEvents(aTarget, aExpectedEvents) {
+ return new Promise((aResolve, aReject) => {
+ let timer;
+ let cleanup = function() {
+ if (timer) {
+ clearTimeout(timer);
+ timer = null;
+ }
+ aTarget.removeEventListener("mousedown", listener);
+ aTarget.removeEventListener("mouseup", listener);
+ aTarget.removeEventListener("contextmenu", listener);
+ aTarget.removeEventListener("click", listener);
+ aTarget.removeEventListener("auxclick", listener);
+ };
+
+ let listener = function(aEvent) {
+ aEvent.preventDefault();
+ let expectedEvent = aExpectedEvents.shift();
+ if (!expectedEvent) {
+ cleanup();
+ ok(false, `receive unexpected ${aEvent.type} event`);
+ aReject(new Error(`receive unexpected ${aEvent.type} event`));
+ return;
+ }
+
+ isDeeply([aEvent.type, aEvent.button, aEvent.ctrlKey], expectedEvent,
+ `check received ${aEvent.type} event`);
+ if (!aExpectedEvents.length) {
+ // Wait a bit to see if there is any unexpected event.
+ timer = setTimeout(function() {
+ cleanup();
+ aResolve();
+ }, 0);
+ }
+ };
+
+ aTarget.addEventListener("mousedown", listener);
+ aTarget.addEventListener("mouseup", listener);
+ aTarget.addEventListener("contextmenu", listener);
+ aTarget.addEventListener("click", listener);
+ aTarget.addEventListener("auxclick", listener);
+ });
+}
+
+add_task(async function Init() {
+ await SimpleTest.promiseFocus();
+ await waitUntilApzStable();
+
+ let target = document.getElementById("target");
+ target.addEventListener("click", function() {
+ ok(false, `should not receive click event`);
+ });
+});
+
+add_task(async function TestMouseClickWithControl() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.event.treat_ctrl_click_as_right_click.disabled", true]],
+ });
+
+ let target = document.getElementById("target");
+ let promise = waitAndCheckMouseEvents(target, [["mousedown", 0, true],
+ ["contextmenu", 0, true],
+ ["mouseup", 0, true]]);
+ synthesizeNativeMouseEvent({
+ type: "click",
+ target,
+ offsetX: 10,
+ offsetY: 10,
+ modifiers: { ctrlKey: true },
+ });
+ await promise;
+});
+
+add_task(async function TestOldBehavior() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.event.treat_ctrl_click_as_right_click.disabled", false]],
+ });
+
+ let target = document.getElementById("target");
+ let promise = waitAndCheckMouseEvents(target, [["mousedown", 2, true],
+ ["contextmenu", 2, true],
+ ["mouseup", 2, true],
+ ["auxclick", 2, true]]);
+ synthesizeNativeMouseEvent({
+ type: "click",
+ target,
+ offsetX: 10,
+ offsetY: 10,
+ modifiers: { ctrlKey: true },
+ });
+ await promise;
+});
+</script>
+</body>
+</html>
diff --git a/widget/tests/test_mouse_scroll.xhtml b/widget/tests/test_mouse_scroll.xhtml
new file mode 100644
index 0000000000..82cb6a3ea3
--- /dev/null
+++ b/widget/tests/test_mouse_scroll.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="Testing composition, text and query content events"
+ onload="setTimeout(onLoad, 0);"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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();
+function onLoad()
+{
+ runTest();
+}
+
+function runTest()
+{
+ window.openDialog("window_mouse_scroll_win.html", "_blank",
+ "chrome,width=600,height=600,noopener", window);
+}
+]]>
+</script>
+</window>
diff --git a/widget/tests/test_native_key_bindings_mac.html b/widget/tests/test_native_key_bindings_mac.html
new file mode 100644
index 0000000000..8767a5a77d
--- /dev/null
+++ b/widget/tests/test_native_key_bindings_mac.html
@@ -0,0 +1,336 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset='utf-8'/>
+ <title>Native Key Bindings for Cocoa Test</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <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/NativeKeyCodes.js"></script>
+ </head>
+ <body>
+ <div id="editable" contenteditable>
+ <p>Stretching attack nullam stuck in a tree zzz, suspendisse cras nec
+ suspendisse lick suscipit. Nunc egestas amet litter box, nullam climb the
+ curtains biting I don't like that food tristique biting sleep on your
+ keyboard non. Lay down in your way cras nec tempus chase the red dot cras
+ nec, pharetra pharetra eat the grass leap run orci turpis attack.
+ Consectetur sleep in the sink eat I don't like that food, knock over the
+ lamp catnip in viverra tail flick zzz meow etiam enim. Ac ac hiss shed
+ everywhere kittens rhoncus, attack your ankles zzz iaculis kittens. Nullam
+ pellentesque rip the couch iaculis rhoncus nibh, give me fish orci turpis
+ purr sleep on your face quis nunc bibendum.</p>
+
+ <p>Neque jump on the table bat iaculis, adipiscing sleep on your keyboard
+ jump vel justo shed everywhere suspendisse lick. Zzz enim faucibus
+ hairball faucibus, pharetra sunbathe biting bat leap rip the couch attack.
+ Tortor nibh in viverra quis hairball nam, vulputate adipiscing sleep on
+ your keyboard purr knock over the lamp orci turpis. Vestibulum I don't
+ like that food et chase the red dot, adipiscing neque bibendum rutrum
+ accumsan quis rhoncus claw. Leap accumsan vehicula enim biting sleep on
+ your face, pharetra nam accumsan egestas kittens sunbathe. Pharetra chase
+ the red dot sniff non eat the grass, vulputate fluffy fur aliquam puking
+ judging you.</p>
+
+ <p>Claw purr sollicitudin sollicitudin lay down in your way consectetur,
+ pellentesque vehicula zzz orci turpis consectetur. I don't like that food
+ rhoncus pellentesque sniff attack, rhoncus tortor attack your ankles
+ iaculis scratched hiss vel. Tortor zzz tortor nullam rip the couch rutrum,
+ bat enim ut leap hairball iaculis. Bibendum sunbathe elit suspendisse
+ nibh, puking adipiscing sleep on your face sleep on your face zzz catnip.
+ Judging you rutrum bat sunbathe sleep on your face, jump on the table leap
+ tincidunt a faucibus sleep in the sink. Stuck in a tree tristique zzz hiss
+ in viverra nullam, quis tortor pharetra attack.</p>
+ </div>
+
+ <textarea id="textarea" cols="80">
+ Stretching attack nullam stuck in a tree zzz, suspendisse cras nec
+ suspendisse lick suscipit. Nunc egestas amet litter box, nullam climb the
+ curtains biting I don't like that food tristique biting sleep on your
+ keyboard non. Lay down in your way cras nec tempus chase the red dot cras
+ nec, pharetra pharetra eat the grass leap run orci turpis attack.
+ Consectetur sleep in the sink eat I don't like that food, knock over the
+ lamp catnip in viverra tail flick zzz meow etiam enim. Ac ac hiss shed
+ everywhere kittens rhoncus, attack your ankles zzz iaculis kittens. Nullam
+ pellentesque rip the couch iaculis rhoncus nibh, give me fish orci turpis
+ purr sleep on your face quis nunc bibendum.
+
+ Neque jump on the table bat iaculis, adipiscing sleep on your keyboard
+ jump vel justo shed everywhere suspendisse lick. Zzz enim faucibus
+ hairball faucibus, pharetra sunbathe biting bat leap rip the couch attack.
+ Tortor nibh in viverra quis hairball nam, vulputate adipiscing sleep on
+ your keyboard purr knock over the lamp orci turpis. Vestibulum I don't
+ like that food et chase the red dot, adipiscing neque bibendum rutrum
+ accumsan quis rhoncus claw. Leap accumsan vehicula enim biting sleep on
+ your face, pharetra nam accumsan egestas kittens sunbathe. Pharetra chase
+ the red dot sniff non eat the grass, vulputate fluffy fur aliquam puking
+ judging you.
+
+ Claw purr sollicitudin sollicitudin lay down in your way consectetur,
+ pellentesque vehicula zzz orci turpis consectetur. I don't like that food
+ rhoncus pellentesque sniff attack, rhoncus tortor attack your ankles
+ iaculis scratched hiss vel. Tortor zzz tortor nullam rip the couch rutrum,
+ bat enim ut leap hairball iaculis. Bibendum sunbathe elit suspendisse
+ nibh, puking adipiscing sleep on your face sleep on your face zzz catnip.
+ Judging you rutrum bat sunbathe sleep on your face, jump on the table leap
+ tincidunt a faucibus sleep in the sink. Stuck in a tree tristique zzz hiss
+ in viverra nullam, quis tortor pharetra attack.
+ </textarea>
+
+ <input id="input" type="text"
+ value="Stretching attack nullam stuck in a tree zzz, suspendisse cras nec
+ suspendisse lick suscipit. Nunc egestas amet litter box, nullam climb the
+ curtains biting I don't like that food tristique biting sleep on your
+ keyboard non. Lay down in your way cras nec tempus chase the red dot cras
+ nec, pharetra pharetra eat the grass leap run orci turpis attack.
+ Consectetur sleep in the sink eat I don't like that food, knock over the
+ lamp catnip in viverra tail flick zzz meow etiam enim. Ac ac hiss shed
+ everywhere kittens rhoncus, attack your ankles zzz iaculis kittens.
+ Nullam pellentesque rip the couch iaculis rhoncus nibh, give me fish orci
+ turpis purr sleep on your face quis nunc bibendum.">
+
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ let synthesizedKeys = [];
+ let expectations = [];
+
+ // Move to beginning of line
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_LeftArrow,
+ {ctrlKey: true}, "\uf702", "\uf702"]);
+ expectations.push({
+ editable: [0, 0],
+ textarea: [0, 0],
+ input: [0, 0],
+ });
+
+ // Move to end of line
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_RightArrow,
+ {ctrlKey: true}, "\uf703", "\uf703"]);
+ expectations.push({
+ editable: [73, 73],
+ textarea: [72, 72],
+ input: [732, 732],
+ });
+
+ // Move down
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_N,
+ {ctrlKey: true}, "\u000e", "n"]);
+ expectations.push({
+ editable: [140, 140],
+ textarea: [145, 145],
+ input: [732, 732],
+ });
+
+ // Move to beginning of line
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_LeftArrow,
+ {ctrlKey: true}, "\uf702", "\uf702"]);
+ expectations.push({
+ editable: [73, 73],
+ textarea: [73, 73],
+ input: [0, 0],
+ });
+
+ // Move word right and modify selection
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_RightArrow,
+ {altKey: true, shiftKey: true}, "\uf703", "\uf703"]);
+ expectations.push({
+ editable: [73, 84],
+ textarea: [73, 90],
+ input: [0, 10],
+ });
+
+ // Move word right
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_RightArrow,
+ {altKey: true}, "\uf703", "\uf703"]);
+ expectations.push({
+ editable: [84, 84],
+ textarea: [90, 90],
+ input: [10, 10],
+ });
+
+ // Move word right
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_RightArrow,
+ {altKey: true}, "\uf703", "\uf703"]);
+ expectations.push({
+ editable: [89, 89],
+ textarea: [95, 95],
+ input: [17, 17],
+ });
+
+ // Move down and modify selection
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_DownArrow,
+ {shiftKey: true}, "\uf701", "\uf701"]);
+ expectations.push({
+ editable: [89, 171],
+ textarea: [95, 175],
+ input: [17, 732],
+ });
+
+ // Move backward and modify selection
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_B,
+ {ctrlKey: true, shiftKey: true}, "\u0002", "B"]);
+ expectations.push({
+ editable: [89, 170],
+ textarea: [95, 174],
+ input: [17, 731],
+ });
+
+ // Delete forward
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_D,
+ {ctrlKey: true}, "\u0004", "d"]);
+ expectations.push({
+ editable: [89, 89],
+ textarea: [95, 95],
+ input: [17, 17],
+ });
+
+ // Delete backward
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_H,
+ {ctrlKey: true}, "\u0008", "h"]);
+ expectations.push({
+ editable: [88, 88],
+ textarea: [94, 94],
+ input: [16, 16],
+ });
+
+ // Move backward
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_B,
+ {ctrlKey: true}, "\u0002", "b"]);
+ expectations.push({
+ editable: [87, 87],
+ textarea: [93, 93],
+ input: [15, 15],
+ });
+
+ // Move to beginning of paragraph (line for now)
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A,
+ {ctrlKey: true}, "\u0001", "a"]);
+ expectations.push({
+ editable: [73, 73],
+ textarea: [73, 73],
+ input: [0, 0],
+ });
+
+ // Move forward
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_F,
+ {ctrlKey: true}, "\u0006", "f"]);
+ expectations.push({
+ editable: [74, 74],
+ textarea: [74, 74],
+ input: [1, 1],
+ });
+
+ // Move word right
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_RightArrow,
+ {altKey: true}, "\uf703", "\uf703"]);
+ expectations.push({
+ editable: [84, 84],
+ textarea: [90, 90],
+ input: [10, 10],
+ });
+
+ // Move word right
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_RightArrow,
+ {altKey: true}, "\uf703", "\uf703"]);
+ expectations.push({
+ editable: [88, 88],
+ textarea: [94, 94],
+ input: [17, 17],
+ });
+
+ // Delete to end of paragraph (line for now)
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_K,
+ {ctrlKey: true}, "\u000b", "k"]);
+ expectations.push({
+ editable: [88, 88],
+ textarea: [94, 94],
+ input: [17, 17],
+ });
+
+ // Move backward and modify selection
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_B,
+ {ctrlKey: true, shiftKey: true}, "\u0002", "B"]);
+ expectations.push({
+ editable: [88, 87],
+ textarea: [93, 94],
+ input: [16, 17],
+ });
+
+ // Move to end of paragraph (line for now)
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_E,
+ {ctrlKey: true}, "\u0005", "e"]);
+ expectations.push({
+ editable: [139, 139],
+ textarea: [94, 94],
+ input: [17, 17],
+ });
+
+ // Move up
+ synthesizedKeys.push([KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_P,
+ {ctrlKey: true}, "\u0010", "p"]);
+ expectations.push({
+ editable: [73, 73],
+ textarea: [21, 21],
+ input: [0, 0],
+ });
+
+ function checkWindowSelection(aElement, aSelection) {
+ let selection = window.getSelection();
+
+ is(selection.anchorOffset, aSelection[aElement.id][0],
+ aElement.id + ": Incorrect anchor offset");
+ is(selection.focusOffset, aSelection[aElement.id][1],
+ aElement.id + ": Incorrect focus offset");
+ }
+
+ function checkElementSelection(aElement, aSelection) {
+ is(aElement.selectionStart, aSelection[aElement.id][0],
+ aElement.id + ": Incorrect selection start");
+ is(aElement.selectionEnd, aSelection[aElement.id][1],
+ aElement.id + ": Incorrect selection end");
+ }
+
+ function* testRun(aElement, aSelectionCheck, aCallback) {
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+
+ aElement.focus();
+
+ for (let i = 0; i < synthesizedKeys.length; i++) {
+ synthesizedKeys[i].push(function() {
+ aSelectionCheck(aElement, expectations[i]);
+ continueTest();
+ });
+ var synthOk = synthesizeNativeKey.apply(null, synthesizedKeys[i]);
+ synthesizedKeys[i].pop();
+ yield synthOk;
+ }
+ }
+
+ function* doTest() {
+ yield* testRun(document.getElementById("editable"), checkWindowSelection);
+ yield* testRun(document.getElementById("textarea"), checkElementSelection);
+ yield* testRun(document.getElementById("input"), checkElementSelection);
+ }
+
+ let gTestContinuation = null;
+
+ function continueTest() {
+ if (!gTestContinuation) {
+ gTestContinuation = doTest();
+ }
+ var ret = gTestContinuation.next();
+ if (ret.done) {
+ SimpleTest.finish();
+ } else {
+ is(ret.value, true, "Successfully synthesized key");
+ }
+ }
+
+ SimpleTest.waitForFocus(continueTest);
+ </script>
+ </body>
+</html>
diff --git a/widget/tests/test_native_menus.xhtml b/widget/tests/test_native_menus.xhtml
new file mode 100644
index 0000000000..d62c57f21c
--- /dev/null
+++ b/widget/tests/test_native_menus.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="Native menu system tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("native_menus_window.xhtml", "NativeMenuWindow",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_panel_mouse_coords.xhtml b/widget/tests/test_panel_mouse_coords.xhtml
new file mode 100644
index 0000000000..43c4e10249
--- /dev/null
+++ b/widget/tests/test_panel_mouse_coords.xhtml
@@ -0,0 +1,78 @@
+<?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=835044
+-->
+<window title="Mozilla Bug 835044"
+ onload="startTest()"
+ 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"/>
+
+<panel id="thepanel" level="parent"
+ onpopupshown="sendMouseEvent();"
+ onmousemove="checkCoords(event);"
+ style="width: 80px; height: 80px">
+</panel>
+
+ <!-- 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=835044"
+ id="anchor"
+ target="_blank">Mozilla Bug 835044</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+let panel = document.getElementById('thepanel');
+let rect = null;
+
+
+function startTest() {
+ // This first event is to ensure that the next event will have different
+ // coordinates to the previous mouse position, and so actually generates
+ // mouse events. The mouse is not moved off the window, as that might
+ // move focus to another application.
+ synthesizeNativeMouseEvent({
+ type: "mousemove",
+ screenX: window.mozInnerScreenX,
+ screenY: window.mozInnerScreenY,
+ elementOnWidget: window.documentElement,
+ });
+
+ panel.openPopup(document.getElementById("anchor"), "after_start");
+}
+
+function sendMouseEvent() {
+ rect = panel.getBoundingClientRect();
+ synthesizeNativeMouseEvent({
+ type: "mousemove",
+ target: panel,
+ offsetX: 10,
+ offsetY: 20,
+ });
+}
+
+function checkCoords(event) {
+ if (!rect) {
+ return;
+ }
+ isfuzzy(event.clientX, rect.left + 10, window.devicePixelRatio, "Motion x coordinate");
+ isfuzzy(event.clientY, rect.top + 20, window.devicePixelRatio, "Motion y coordinate");
+ info(`Event client: ${event.clientX}, ${event.clientY}, panel client: ${rect.left}, ${rect.top}`);
+ info(`Event screen: ${event.screenX}, ${event.screenY}, panel screen: ${panel.screenX}, ${panel.screenY}`);
+ info(`offset client: ${event.clientX - rect.left}, ${event.clientY - rect.top}`);
+ info(`offset screen: ${event.screenX - panel.screenX}, ${event.screenY - panel.screenY}`);
+ done();
+}
+
+function done() {
+ SimpleTest.finish();
+}
+ ]]>
+ </script>
+</window>
diff --git a/widget/tests/test_picker_no_crash.html b/widget/tests/test_picker_no_crash.html
new file mode 100644
index 0000000000..dbb75627b5
--- /dev/null
+++ b/widget/tests/test_picker_no_crash.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<title>Test for crashes when the parent window of a file picker is closed via script</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script>
+SimpleTest.requestFlakyTimeout("testing we don't crash");
+
+async function testPicker(id) {
+ let childWindow = window.open("window_picker_no_crash_child.html", "childWindow", "width=500,height=500");
+ await SimpleTest.promiseFocus(childWindow);
+ ok(!childWindow.clicked, "Shouldn't have clicked");
+ synthesizeMouseAtCenter(childWindow.document.getElementById(id), {}, childWindow);
+ ok(childWindow.clicked, "Should have clicked");
+ childWindow.close();
+}
+
+add_task(async function test_simple() {
+ await testPicker("uploadbox");
+});
+
+add_task(async function test_multiple() {
+ await testPicker("multiple");
+});
+
+add_task(async function wait() {
+ await new Promise(r => setTimeout(r, 1000));
+ ok(true, "browser didn't crash");
+});
+</script>
diff --git a/widget/tests/test_platform_colors.xhtml b/widget/tests/test_platform_colors.xhtml
new file mode 100644
index 0000000000..c82e1f486e
--- /dev/null
+++ b/widget/tests/test_platform_colors.xhtml
@@ -0,0 +1,106 @@
+<?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="Mac platform colors"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=518395">Mozilla Bug 518395</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<box id="colorbox"></box>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+var colors = {
+ "activeborder": ["rgb(0, 0, 0)"],
+ "activecaption": ["rgb(204, 204, 204)"],
+ "appworkspace": ["rgb(255, 255, 255)"],
+ "background": ["rgb(99, 99, 206)"],
+ "buttonface": ["rgb(240, 240, 240)"],
+ "buttonhighlight": ["rgb(255, 255, 255)"],
+ "buttonshadow": ["rgb(220, 220, 220)"],
+ "buttontext": ["rgb(0, 0, 0)"],
+ "captiontext": ["rgb(0, 0, 0)"],
+ "graytext": ["rgb(127, 127, 127)"],
+ "highlight": ["rgb(115, 132, 153)", "rgb(127, 127, 127)", "rgb(56, 117, 215)", "rgb(255, 193, 31)", "rgb(243, 70, 72)", "rgb(255, 138, 34)", "rgb(102, 197, 71)", "rgb(140, 78, 184)"],
+ "highlighttext": ["rgb(255, 255, 255)", "rgb(255, 254, 254)", "rgb(254, 255, 254)"],
+ "inactiveborder": ["rgb(255, 255, 255)"],
+ "inactivecaption": ["rgb(255, 255, 255)"],
+ "inactivecaptiontext": ["rgb(69, 69, 69)"],
+ "infobackground": ["rgb(255, 255, 199)"],
+ "infotext": ["rgb(0, 0, 0)"],
+ "menu": ["rgb(255, 255, 255)", "rgb(254, 255, 254)", "rgb(255, 254, 254)"],
+ "menutext": ["rgb(0, 0, 0)"],
+ "scrollbar": ["rgb(170, 170, 170)"],
+ "threeddarkshadow": ["rgb(220, 220, 220)"],
+ "threedface": ["rgb(240, 240, 240)"],
+ "threedhighlight": ["rgb(255, 255, 255)"],
+ "threedlightshadow": ["rgb(218, 218, 218)"],
+ "threedshadow": ["rgb(224, 224, 224)"],
+ "window": ["rgb(255, 255, 255)"],
+ "windowframe": ["rgb(204, 204, 204)"],
+ "windowtext": ["rgb(0, 0, 0)"],
+ "-moz-activehyperlinktext": ["rgb(238, 0, 0)"],
+ "-moz-buttondefault": ["rgb(220, 220, 220)"],
+ "-moz-buttonhoverface": ["rgb(240, 240, 240)"],
+ "-moz-buttonhovertext": ["rgb(0, 0, 0)"],
+ "-moz-cellhighlight": ["rgb(212, 212, 212)", "rgb(220, 220, 220)"],
+ "-moz-cellhighlighttext": ["rgb(0, 0, 0)"],
+ "-moz-eventreerow": ["rgb(255, 255, 255)"],
+ "-moz-field": ["rgb(255, 255, 255)"],
+ "-moz-fieldtext": ["rgb(0, 0, 0)"],
+ "-moz-dialog": ["rgb(232, 232, 232)"],
+ "-moz-dialogtext": ["rgb(0, 0, 0)"],
+ "-moz-dragtargetzone": ["rgb(199, 208, 218)", "rgb(198, 198, 198)", "rgb(180, 213, 255)", "rgb(250, 236, 115)", "rgb(255, 176, 139)", "rgb(255, 209, 129)", "rgb(194, 249, 144)", "rgb(232, 184, 255)"],
+ "-moz-hyperlinktext": ["rgb(0, 0, 238)"],
+ "-moz-html-cellhighlight": ["rgb(212, 212, 212)"],
+ "-moz-html-cellhighlighttext": ["rgb(0, 0, 0)"],
+ "-moz-buttonactivetext": ["rgb(0, 0, 0)", "rgb(255, 255, 255)"],
+ "-moz-mac-chrome-active": ["rgb(150, 150, 150)", "rgb(167, 167, 167)", "rgb(178, 178, 178)"],
+ "-moz-mac-chrome-inactive": ["rgb(202, 202, 202)", "rgb(216, 216, 216)", "rgb(225, 225, 225)"],
+ "-moz-mac-defaultbuttontext": ["rgb(0, 0, 0)", "rgb(255, 255, 255)"],
+ //"-moz-mac-focusring": ["rgb(83, 144, 210)", "rgb(95, 112, 130)", "rgb(63, 152, 221)", "rgb(108, 126, 141)"],
+ "-moz-mac-menuselect": ["rgb(115, 132, 153)", "rgb(127, 127, 127)", "rgb(56, 117, 215)", "rgb(255, 193, 31)", "rgb(243, 70, 72)", "rgb(255, 138, 34)", "rgb(102, 197, 71)", "rgb(140, 78, 184)"],
+ "-moz-mac-menushadow": ["rgb(163, 163, 163)"],
+ "-moz-mac-menutextdisable": ["rgb(152, 152, 152)"],
+ "-moz-mac-menutextselect": ["rgb(255, 255, 255)"],
+ "-moz-mac-disabledtoolbartext": ["rgb(127, 127, 127)"],
+ "-moz-mac-secondaryhighlight": ["rgb(212, 212, 212)"],
+ "-moz-menuhover": ["rgb(115, 132, 153)", "rgb(127, 127, 127)", "rgb(56, 117, 215)", "rgb(255, 193, 31)", "rgb(243, 70, 72)", "rgb(255, 138, 34)", "rgb(102, 197, 71)", "rgb(140, 78, 184)"],
+ "-moz-menuhovertext": ["rgb(255, 255, 255)", "rgb(255, 254, 254)", "rgb(254, 255, 254)"],
+ "-moz-menubartext": ["rgb(0, 0, 0)"],
+ //"-moz-menubarhovertext": ["rgb(255, 255, 255)"],
+ "-moz-oddtreerow": ["rgb(236, 242, 254)", "rgb(240, 240, 240)", "rgb(243, 245, 250)", "rgb(243, 246, 250)", "rgb(245, 245, 245)"],
+ "-moz-visitedhyperlinktext": ["rgb(85, 26, 139)"],
+ "currentcolor": ["rgb(0, 0, 0)"],
+ //"-moz-win-mediatext": ["rgb(255, 255, 255)"],
+ //"-moz-win-communicationstext": ["rgb(255, 255, 255)"],
+ "-moz-nativehyperlinktext": ["rgb(20, 79, 174)"],
+ "-moz-comboboxtext": ["rgb(0, 0, 0)"],
+ "-moz-combobox": ["rgb(255, 255, 255)"]
+};
+
+var colorbox = document.getElementById('colorbox');
+
+for (var c in colors) {
+ dump("testing color " + c + "\n");
+ colorbox.style.backgroundColor = c;
+ var rgb = document.defaultView.getComputedStyle(colorbox).getPropertyValue('background-color');
+ ok(colors[c].includes(rgb) || colors[c].length == 8, 'platform color ' + c + ' is wrong: ' + rgb);
+}
+
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_position_on_resize.xhtml b/widget/tests/test_position_on_resize.xhtml
new file mode 100644
index 0000000000..a7c5551018
--- /dev/null
+++ b/widget/tests/test_position_on_resize.xhtml
@@ -0,0 +1,90 @@
+<?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 Position On Resize Test"
+ onload="startTest()"
+ 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, x, y;
+
+ function startTest() {
+ win = window.browsingContext.topChromeWindow.openDialog(
+ "about:blank",
+ null,
+ "chrome,dialog=no,outerHeight=170,outerWidth=200");
+ waitForSuccess(function() { return SpecialPowers.DOMWindowUtils.paintCount },
+ "No paint received", checkInitialSize);
+ }
+
+ function checkInitialSize() {
+ is(win.outerHeight,170, "initial outerHeight");
+ is(win.outerWidth, 200, "initial outerWidth");
+ x = win.screenX;
+ y = win.screenY;
+ shrink();
+ }
+ function shrink() {
+ win.resizeTo(180, 160);
+ waitForSuccess(function() { return win.outerHeight == 160 },
+ "outerHeight did not change to 160", checkShrink);
+ }
+ function checkShrink() {
+ is(win.outerWidth, 180, "resized outerWidth");
+ is(win.screenY, y, "resized window top should not change");
+ y = win.screenY;
+ restore();
+ }
+ function restore() {
+ win.resizeBy(20, 10);
+ waitForSuccess(function() { return win.outerHeight == 170 },
+ "outerHeight did not change to 170", checkRestore);
+ }
+ function checkRestore() {
+ is(win.outerWidth, 200, "restored outerWidth");
+ is(win.screenX, x, "restored window left should not change");
+ is(win.screenY, y, "restored window top should not change");
+ done();
+ }
+ function done() {
+ win.close();
+ SimpleTest.finish();
+ }
+
+ function waitForSuccess(testForSuccess, failureMsg, nextFunc) {
+ var waitCount = 0;
+
+ function repeatWait() {
+ ++waitCount;
+
+ if (testForSuccess()) {
+ nextFunc();
+ }
+ else if (waitCount > 50) {
+ ok(false, failureMsg);
+ nextFunc();
+ } else {
+ setTimeout(repeatWait, 100);
+ }
+ }
+
+ repeatWait();
+ }
+]]></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/widget/tests/test_secure_input.html b/widget/tests/test_secure_input.html
new file mode 100644
index 0000000000..846465b4c2
--- /dev/null
+++ b/widget/tests/test_secure_input.html
@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for secure input mode</title>
+ <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/NativeKeyCodes.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<p>
+ <input id="input_text" type="text"><br>
+ <input id="input_password" type="password"><br>
+ <input id="input_text_readonly" type="text" readonly><br>
+ <input id="input_text_ime_mode_disabled" type="text" style="ime-mode: disabled;"><br>
+ <input id="input_change" type="text"><br>
+ <textarea id="textarea"></textarea><br>
+</p>
+<div id="contenteditable" contenteditable style="min-height: 3em;"></div>
+
+<script class="testbody" type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ function sendAKeyEvent() {
+ synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_A, {}, "a", "a");
+ }
+
+ function isFocused(aElement) {
+ return (SpecialPowers.focusManager.focusedElement == aElement);
+ }
+
+ function runTest() {
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on the document");
+ $("input_text").focus();
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <input type=\"text\">");
+ $("input_password").focus();
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <input type=\"password\">");
+ $("input_password").blur();
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on the document after blur() of <input type=\"password\">");
+ $("input_password").focus();
+ $("input_text_readonly").focus();
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <input type=\"text\" readonly>");
+ $("input_password").focus();
+ $("input_text_ime_mode_disabled").focus();
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <input type=\"text\" style=\"ime-mode: disabled;\">");
+ $("input_password").focus();
+ $("textarea").focus();
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <textarea>");
+ $("input_password").focus();
+ $("contenteditable").focus();
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <div contenteditable>");
+
+ $("input_change").focus();
+ $("input_change").type = "password";
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <input type=\"password\"> changed from type=\"text\"");
+ $("input_change").type = "text";
+ sendAKeyEvent();
+ ok(true, "Not crashed: input on <input type=\"text\"> changed from type=\"password\"");
+
+ var otherWindow =
+ window.browsingContext.topChromeWindow.open("file_secure_input.html",
+ "_blank", "chrome,width=100,height=100");
+ ok(otherWindow, "failed to open other window");
+ if (!otherWindow) {
+ SimpleTest.finish();
+ return;
+ }
+
+ $("input_text").focus();
+ otherWindow.focus();
+
+ SimpleTest.waitForFocus(function() {
+ window.focus();
+ sendAKeyEvent();
+ ok(isFocused($("input_text")), "focused element isn't <input type=\"text\">");
+ ok(true, "Not crashed: input on <input type=\"text\"> after the other document has focus");
+
+ $("input_password").focus();
+ otherWindow.focus();
+ window.focus();
+ sendAKeyEvent();
+ ok(isFocused($("input_password")), "focused element isn't <input type=\"password\">");
+ ok(true, "Not crashed: input on <input type=\"password\"> after the other document has focus");
+
+ $("input_text").focus();
+ otherWindow.focus();
+ otherWindow.document.getElementById("text").focus();
+ window.focus();
+ sendAKeyEvent();
+ ok(isFocused($("input_text")), "focused element isn't <input type=\"text\">");
+ ok(true, "Not crashed: input on <input type=\"text\"> after the other document's <input type=\"text\"> has focus");
+
+ $("input_password").focus();
+ otherWindow.focus();
+ otherWindow.document.getElementById("text").focus();
+ window.focus();
+ sendAKeyEvent();
+ ok(isFocused($("input_password")), "focused element isn't <input type=\"password\">");
+ ok(true, "Not crashed: input on <input type=\"password\"> after the other document's <input type=\"text\"> has focus");
+
+ $("input_text").focus();
+ otherWindow.focus();
+ otherWindow.document.getElementById("password").focus();
+ window.focus();
+ sendAKeyEvent();
+ ok(isFocused($("input_text")), "focused element isn't <input type=\"text\">");
+ ok(true, "Not crashed: input on <input type=\"text\"> after the other document's <input type=\"password\"> has focus");
+
+ $("input_password").focus();
+ otherWindow.focus();
+ otherWindow.document.getElementById("password").focus();
+ window.focus();
+ sendAKeyEvent();
+ ok(isFocused($("input_password")), "focused element isn't <input type=\"password\">");
+ ok(true, "Not crashed: input on <input type=\"password\"> after the other document's <input type=\"password\"> has focus");
+
+ SimpleTest.finish();
+ }, otherWindow);
+ }
+
+ SimpleTest.waitForFocus(runTest);
+</script>
+</body>
+</html>
diff --git a/widget/tests/test_sizemode_events.xhtml b/widget/tests/test_sizemode_events.xhtml
new file mode 100644
index 0000000000..bd1e3a38d1
--- /dev/null
+++ b/widget/tests/test_sizemode_events.xhtml
@@ -0,0 +1,148 @@
+<?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 for bug 715867"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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>
+
+<script class="testbody">
+<![CDATA[
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+let gWindow = null;
+
+const kIsLinux = navigator.platform.includes("Lin");
+const kIsMacOS = navigator.platform.includes("Mac");
+// On Linux and macOS sizemode changes might be async.
+const kAsyncChanges = kIsLinux || kIsMacOS;
+
+let gSizeModeDidChange = false;
+let gSizeModeDidChangeTo = 0;
+
+function sizemodeChanged(e) {
+ gSizeModeDidChange = true;
+ gSizeModeDidChangeTo = gWindow.windowState;
+}
+
+async function expectSizeModeChange(newMode, duringActionCallback) {
+ gSizeModeDidChange = false;
+
+ let promise = null;
+ if (kAsyncChanges) {
+ if (newMode) {
+ promise = new Promise(resolve => {
+ gWindow.addEventListener("sizemodechange", function() {
+ SimpleTest.executeSoon(resolve);
+ }, { once: true })
+ });
+ } else {
+ promise = new Promise(SimpleTest.executeSoon);
+ }
+ }
+
+ duringActionCallback();
+
+ if (promise) {
+ await promise;
+ }
+
+ if (newMode == 0) {
+ // No change should have taken place, no event should have fired.
+ ok(!gSizeModeDidChange, "No sizemodechange event should have fired.");
+ } else {
+ // Size mode change event was expected to fire.
+ ok(gSizeModeDidChange, "A sizemodechanged event should have fired.");
+ is(gSizeModeDidChangeTo, newMode, "The new sizemode should have the expected value.");
+ const expectedHidden = newMode == gWindow.STATE_MINIMIZED || gWindow.isFullyOccluded;
+ if (gWindow.document.hidden != expectedHidden) {
+ await new Promise(resolve => {
+ gWindow.addEventListener("visibilitychange", resolve, { once: true });
+ });
+ }
+ is(gWindow.document.hidden, expectedHidden, "Should be inactive if minimized or occluded.");
+ }
+}
+
+function startTest() {
+ openWindow();
+}
+
+function openWindow() {
+ gWindow = window.browsingContext.topChromeWindow
+ .open('empty_window.xhtml', '_blank', 'chrome,screenX=50,screenY=50,width=200,height=200,resizable');
+ SimpleTest.waitForFocus(runTest, gWindow);
+}
+
+async function runTest() {
+ // Install event handler.
+ gWindow.addEventListener("sizemodechange", sizemodeChanged);
+
+ // Run tests.
+ info("Testing minimize()");
+ await expectSizeModeChange(gWindow.STATE_MINIMIZED, function () {
+ gWindow.minimize();
+ });
+
+ info("Testing restore() after minimize()");
+ await expectSizeModeChange(gWindow.STATE_NORMAL, function () {
+ gWindow.restore();
+ });
+
+ info("Testing maximize()");
+ await expectSizeModeChange(gWindow.STATE_MAXIMIZED, function () {
+ gWindow.maximize();
+ });
+
+ info("Testing restore() after maximize()");
+ await expectSizeModeChange(gWindow.STATE_NORMAL, function () {
+ gWindow.restore();
+ });
+
+ // Normal window resizing shouldn't fire a sizemodechanged event, bug 715867.
+ info("Testing resizeTo() horizontal");
+ await expectSizeModeChange(0, function () {
+ gWindow.resizeTo(gWindow.outerWidth + 10, gWindow.outerHeight);
+ });
+
+ info("Testing resizeTo() vertical");
+ await expectSizeModeChange(0, function () {
+ gWindow.resizeTo(gWindow.outerWidth, gWindow.outerHeight + 10);
+ });
+
+ // Resizing a maximized window should change to normal sizemode.
+ info("maximize() in preparation for resize");
+ await expectSizeModeChange(gWindow.STATE_MAXIMIZED, function () {
+ gWindow.maximize();
+ });
+
+ info("Testing resizeTo() from maximized");
+ await expectSizeModeChange(gWindow.STATE_NORMAL, function () {
+ // MacOS treats windows close to the available screen size as maximized.
+ // Shrinking the window by only 10px isn't enough to change the sizemode.
+ gWindow.resizeTo(gWindow.outerWidth / 2, gWindow.outerHeight / 2);
+ });
+
+ gWindow.removeEventListener("sizemodechange", sizemodeChanged);
+ gWindow.close();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(startTest);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_standalone_native_menu.xhtml b/widget/tests/test_standalone_native_menu.xhtml
new file mode 100644
index 0000000000..96e41036c3
--- /dev/null
+++ b/widget/tests/test_standalone_native_menu.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="Standalone Native Menu tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("standalone_native_menu_window.xhtml", "StandaloneNativeMenuWindow",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_system_font_changes.xhtml b/widget/tests/test_system_font_changes.xhtml
new file mode 100644
index 0000000000..036c775463
--- /dev/null
+++ b/widget/tests/test_system_font_changes.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="Native menu system tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("system_font_changes.xhtml", "system_font_changes_window",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_system_status_bar.xhtml b/widget/tests/test_system_status_bar.xhtml
new file mode 100644
index 0000000000..f2348fa6f5
--- /dev/null
+++ b/widget/tests/test_system_status_bar.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"?>
+<window title="Testing composition, text and query content events"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<menupopup id="menuContainer">
+ <menu id="menu1" image="data:image/svg+xml,&lt;svg%20xmlns=&quot;http://www.w3.org/2000/svg&quot;%20width=&quot;32&quot;%20height=&quot;32&quot;>&lt;circle%20cx=&quot;16&quot;%20cy=&quot;16&quot;%20r=&quot;16&quot;/>&lt;/svg>">
+ <menupopup>
+ <menuitem label="Item 1 in menu 1"/>
+ <menuitem label="Item 2 in menu 1"/>
+ </menupopup>
+ </menu>
+ <menu id="menu2" image="data:image/svg+xml,&lt;svg%20xmlns=&quot;http://www.w3.org/2000/svg&quot;%20width=&quot;32&quot;%20height=&quot;32&quot;>&lt;path%20d=&quot;M0 16 L 16 0 L 32 16 L 16 32 Z&quot;/>&lt;/svg>">
+ <menupopup>
+ <menuitem label="Item 1 in menu 2"/>
+ <menuitem label="Item 2 in menu 2"/>
+ </menupopup>
+ </menu>
+</menupopup>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+ let systemStatusBar = Cc["@mozilla.org/widget/systemstatusbar;1"].getService(Ci.nsISystemStatusBar);
+ ok(systemStatusBar, "should have got an nsISystemStatusBar instance");
+
+ let menu1 = document.getElementById("menu1");
+ let menu2 = document.getElementById("menu2");
+
+ // Add and remove the item, just to get basic leak testing coverage.
+ systemStatusBar.addItem(menu1);
+ systemStatusBar.removeItem(menu1);
+
+ // Make sure that calling addItem twice with the same element doesn't leak.
+ systemStatusBar.addItem(menu2);
+ systemStatusBar.addItem(menu2);
+ systemStatusBar.removeItem(menu2);
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/test_taskbar_progress.xhtml b/widget/tests/test_taskbar_progress.xhtml
new file mode 100644
index 0000000000..f2494a27bb
--- /dev/null
+++ b/widget/tests/test_taskbar_progress.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"?>
+<window title="Taskbar Previews Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="loaded();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+ let TP = Ci.nsITaskbarProgress;
+
+ function IsWin7OrHigher() {
+ try {
+ var ver = parseFloat(Services.sysinfo.getProperty("version"));
+ if (ver >= 6.1)
+ return true;
+ } catch (ex) { }
+ return false;
+ }
+
+ function winProgress() {
+ let taskbar = Cc["@mozilla.org/windows-taskbar;1"];
+ if (!taskbar) {
+ ok(false, "Taskbar service is always available");
+ return null;
+ }
+ taskbar = taskbar.getService(Ci.nsIWinTaskbar);
+
+ is(taskbar.available, IsWin7OrHigher(), "Expected availability of taskbar");
+ if (!taskbar.available)
+ return null;
+
+ // HACK from mconnor:
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let docShell = win.docShell;
+
+ let progress = taskbar.getTaskbarProgress(docShell);
+ isnot(progress, null, "Progress is not null");
+
+ try {
+ taskbar.getTaskbarProgress(null);
+ ok(false, "Got progress for null docshell");
+ } catch (e) {
+ ok(true, "Cannot get progress for null docshell");
+ }
+
+ return progress;
+ }
+
+ function macProgress() {
+ let progress = Cc["@mozilla.org/widget/macdocksupport;1"];
+ if (!progress) {
+ ok(false, "Should have gotten Mac progress service.");
+ return null;
+ }
+ return progress.getService(TP);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ function loaded()
+ {
+ let isWin = /Win/.test(navigator.platform);
+ let progress = isWin ? winProgress() : macProgress();
+ if (!TP || !progress) {
+ SimpleTest.finish();
+ return;
+ }
+
+ function shouldThrow(s,c,m) {
+ try {
+ progress.setProgressState(s,c,m);
+ return false;
+ } catch (e) {
+ return true;
+ }
+ }
+
+ function doesntThrow(s,c,m) {
+ return !shouldThrow(s,c,m);
+ }
+
+ ok(doesntThrow(TP.STATE_NO_PROGRESS, 0, 0), "No progress state can be set");
+ ok(doesntThrow(TP.STATE_INDETERMINATE, 0, 0), "Indeterminate state can be set");
+ ok(doesntThrow(TP.STATE_NORMAL, 0, 0), "Normal state can be set");
+ ok(doesntThrow(TP.STATE_ERROR, 0, 0), "Error state can be set");
+ ok(doesntThrow(TP.STATE_PAUSED, 0, 0), "Paused state can be set");
+
+ ok(shouldThrow(TP.STATE_NO_PROGRESS, 1, 1), "Cannot set no progress with values");
+ ok(shouldThrow(TP.STATE_INDETERMINATE, 1, 1), "Cannot set indeterminate with values");
+
+ // Technically passes since unsigned(-1) > 10
+ ok(shouldThrow(TP.STATE_NORMAL, -1, 10), "Cannot set negative progress");
+ todo(shouldThrow(TP.STATE_NORMAL, 1, -1), "Cannot set negative progress");
+ todo(shouldThrow(TP.STATE_NORMAL, -1, -1), "Cannot set negative progress");
+ todo(shouldThrow(TP.STATE_NORMAL, -2, -1), "Cannot set negative progress");
+
+ ok(shouldThrow(TP.STATE_NORMAL, 5, 3), "Cannot set progress greater than max");
+
+ ok(doesntThrow(TP.STATE_NORMAL, 1, 5), "Normal state can be set with values");
+ ok(doesntThrow(TP.STATE_ERROR, 3, 6), "Error state can be set with values");
+ ok(doesntThrow(TP.STATE_PAUSED, 2, 9), "Paused state can be set with values");
+
+ 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/widget/tests/test_textScaleFactor_system_font.html b/widget/tests/test_textScaleFactor_system_font.html
new file mode 100644
index 0000000000..bd2b55fbb6
--- /dev/null
+++ b/widget/tests/test_textScaleFactor_system_font.html
@@ -0,0 +1,139 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test that system font sizing is independent from ui.textScaleFactor</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <style>
+ p { width: max-content }
+ #menu { font: menu }
+ </style>
+</head>
+<body>
+ <p id="menu">"menu" text.</p>
+ <p id="default">Default text.</p>
+</body>
+<script>
+"use strict";
+
+const { AppConstants } = SpecialPowers.ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+// Returns a Number for the font size in CSS pixels.
+function elementFontSize(element) {
+ return parseFloat(getComputedStyle(element).getPropertyValue("font-size"));
+}
+
+// "look-and-feel-changed" may be dispatched twice: once for the pref
+// change and once after receiving the new values from
+// ContentChild::RecvThemeChanged().
+// pushPrefEnv() resolves after the former. This resolves after the latter.
+function promiseNewFontSizeOnThemeChange(element) {
+ return new Promise(resolve => {
+ const lastSize = elementFontSize(element);
+
+ function ThemeChanged() {
+ const size = elementFontSize(element);
+ if (size != lastSize) {
+ resolve(size);
+ }
+ }
+ // "look-and-feel-changed" is dispatched before the style system is flushed,
+ // https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/layout/base/nsPresContext.cpp#1684,1703-1705
+ // so use an async observer to get a notification after style changes.
+ SpecialPowers.addAsyncObserver(ThemeChanged, "look-and-feel-changed");
+ SimpleTest.registerCleanupFunction(function() {
+ SpecialPowers.removeAsyncObserver(ThemeChanged, "look-and-feel-changed");
+ });
+ });
+}
+
+function fuzzyCompareLength(actual, expected, message, tolerance, expectFn) {
+ expectFn(Math.abs(actual-expected) <= tolerance,
+ `${message} - got ${actual}, expected ${expected} +/- ${tolerance}`);
+}
+
+add_task(async () => {
+ // MOZ_HEADLESS is set in content processes with GTK regardless of the
+ // headless state of the parent process. Check the parent state.
+ const headless = await SpecialPowers.spawnChrome([], function get_headless() {
+ return Services.env.get("MOZ_HEADLESS");
+ });
+ // LookAndFeel::TextScaleFactor::FloatID is implemented only for WINNT and
+ // GTK. ui.textScaleFactor happens to scale CSS pixels on other platforms
+ // but system font integration has not been implemented.
+ const expectSuccess = AppConstants.MOZ_WIDGET_TOOLKIT == "windows" ||
+ (AppConstants.MOZ_WIDGET_TOOLKIT == "gtk" &&
+ // Headless GTK doesn't get system font sizes from the system, but
+ // uses sizes fixed in CSS pixels.
+ !headless);
+
+ async function setScaleAndPromiseFontSize(scale, element) {
+ const prefPromise = SpecialPowers.pushPrefEnv({
+ set: [["ui.textScaleFactor", scale]],
+ });
+ if (!expectSuccess) {
+ // The size is not expected to change but get it afresh to check our
+ // assumption.
+ await prefPromise;
+ return elementFontSize(element);
+ }
+ const [size] = await Promise.all([
+ promiseNewFontSizeOnThemeChange(element),
+ prefPromise,
+ ]);
+ return size;
+ }
+
+ const menu = document.getElementById("menu");
+ const def = document.getElementById("default");
+ // Choose a scaleFactor value different enough from possible default values
+ // that app unit rounding does not prevent a change in devicePixelRatio.
+ // A scaleFactor of 120 also has no rounding of app units per dev pixel.
+ const referenceScale = 120;
+ const menuSize1 = await setScaleAndPromiseFontSize(referenceScale, menu);
+ const menuRect1 = menu.getBoundingClientRect();
+ const defSize1 = elementFontSize(def);
+ const defRect1 = def.getBoundingClientRect();
+
+ const expectFn = expectSuccess ? ok : todo;
+ const menuSize2 = await setScaleAndPromiseFontSize(2*referenceScale, menu);
+ {
+ const singlePrecisionULP = Math.pow(2, -23);
+ // Precision seems to be lost in the conversion to decimal string for the
+ // property value.
+ const reltolerance = 30 * singlePrecisionULP;
+ fuzzyCompareLength(menuSize2, menuSize1/2, "size of menu font",
+ reltolerance*menuSize1/2, expectFn);
+ }
+ {
+ const menuRect2 = menu.getBoundingClientRect();
+ // The menu font text renders exactly the same and app-unit rects are
+ // equal, but the DOMRect conversion is rounded to 1/65536 CSS pixels.
+ // https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/dom/base/DOMRect.cpp#151-159
+ // See also https://bugzilla.mozilla.org/show_bug.cgi?id=1640441#c28
+ const absTolerance = 1/65536
+ fuzzyCompareLength(menuRect2.width, menuRect1.width/2,
+ "width of menu font <p> in px", absTolerance, expectFn);
+ fuzzyCompareLength(menuRect2.height, menuRect1.height/2,
+ "height of menu font <p> in px", absTolerance, expectFn);
+ }
+
+ const defSize2 = elementFontSize(def);
+ is(defSize2, defSize1, "size of default font");
+ {
+ const defRect2 = def.getBoundingClientRect();
+ // Wider tolerance for hinting and snapping
+ const relTolerance = 1/12;
+ fuzzyCompareLength(defRect2.width, defRect1.width,
+ "width of default font <p> in px",
+ relTolerance*defRect1.width, ok);
+ fuzzyCompareLength(defRect2.height, defRect1.height,
+ "height of default font <p> in px",
+ relTolerance*defRect1.height, ok);
+ }
+});
+</script>
+</html>
diff --git a/widget/tests/test_transferable_overflow.xhtml b/widget/tests/test_transferable_overflow.xhtml
new file mode 100644
index 0000000000..5c6ff443c4
--- /dev/null
+++ b/widget/tests/test_transferable_overflow.xhtml
@@ -0,0 +1,151 @@
+<?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"?>
+<window title="nsTransferable with large string"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="RunTest();">
+ <title>nsTransferable with large string</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ // This value is chosen such that the size of the memory for the string exceeds
+ // the kLargeDatasetSize threshold in nsTransferable.h (one million).
+ // Each character of a JS string is internally represented by two bytes,
+ // so the following string of length 500 001 uses 1 000 002 bytes.
+ const BIG_STRING = "x" + "BIGGY".repeat(100000);
+
+ // Some value with a length that is exactly kLargeDatasetSize (1 000 000).
+ const SMALL_STRING = "small".repeat(100000);
+
+ const nsTransferable = Components.Constructor("@mozilla.org/widget/transferable;1", "nsITransferable");
+ const nsSupportsString = Components.Constructor("@mozilla.org/supports-string;1", "nsISupportsString");
+
+ function assignTextToTransferable(transferable, string) {
+ var Suppstr = nsSupportsString();
+ Suppstr.data = string;
+ transferable.setTransferData("text/plain", Suppstr);
+ }
+
+ function checkTransferableText(transferable, expectedString, description) {
+ var data = {};
+ transferable.getTransferData("text/plain", data);
+ var actualValue = data.value.QueryInterface(Ci.nsISupportsString).data;
+ // Use ok + shortenString instead of is(...) to avoid dumping millions of characters in the output.
+ ok(actualValue === expectedString, description + ": text should match. " +
+ "Expected " + shortenString(expectedString) + ", got " + shortenString(actualValue));
+
+ function shortenString(str) {
+ return str && str.length > 30 ? str.slice(0, 10) + "..." + str.slice(-10) : String(str);
+ }
+ }
+
+ function isFDCountingSupported() {
+ // On on-Windows we can count the number of file handles for the current process,
+ // while on Windows we need to count the number of files in ${TempD}\mozilla-temp-files\,
+ // which can be unreliable, especially because nsAnonymousTemporaryFile has documented
+ // that the deletion might not be immediate.
+ //
+ // To avoid intermittents, we only check the file descriptor counts on non-Windows.
+ // test_bug1123480.xhtml will do some basic testing for Windows.
+ const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ return AppConstants.platform !== 'win';
+ }
+
+ function getClipboardCacheFDCount() {
+ var dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dir.initWithPath("/dev/fd");
+ var count = 0;
+ for (var de = dir.directoryEntries; de.hasMoreElements(); ) {
+ var fdFile = de.nextFile;
+ var fileSize;
+ try {
+ fileSize = fdFile.fileSize;
+ } catch (e) {
+ // This can happen on macOS.
+ continue;
+ }
+ if (fileSize === BIG_STRING.length * 2 ||
+ // We are not expecting files of this small size,
+ // but include them in the count anyway
+ // in case the files are unexpectedly created.
+ fileSize === SMALL_STRING.length * 2) {
+ // Assume that the file was created by us if the size matches.
+ ++count;
+ }
+ }
+ return count;
+ }
+
+ function RunTest() {
+ const {PrivateBrowsingUtils} = ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+ var win = window.browsingContext.topChromeWindow.open("about:blank", "_blank", "chrome, width=500, height=200");
+ ok(win, "should open window");
+ is(PrivateBrowsingUtils.isContentWindowPrivate(win), false, "used correct window context");
+
+ // ### Part 1 - Writing to the clipboard.
+
+ var Loadctx = PrivateBrowsingUtils.privacyContextFromWindow(win);
+ var Transfer = nsTransferable();
+ Transfer.init(Loadctx);
+ Transfer.addDataFlavor("text/plain");
+ var initialFdCount = isFDCountingSupported() ? getClipboardCacheFDCount() : -1;
+
+ assignTextToTransferable(Transfer, BIG_STRING);
+ checkTransferableText(Transfer, BIG_STRING, "transferable after assigning BIG_STRING");
+ if (isFDCountingSupported()) {
+ is(getClipboardCacheFDCount(), initialFdCount + 1, "should use a file for BIG_STRING");
+ }
+
+ // Share the transferable with the system.
+ Services.clipboard.setData(Transfer, null, Services.clipboard.kGlobalClipboard);
+
+ // Sanity check: Copying to the clipboard should not have altered the transferable.
+ checkTransferableText(Transfer, BIG_STRING, "transferable after copying to clipboard");
+ if (isFDCountingSupported()) {
+ // We are only counting file descriptors for the current process,
+ // so even if the test were to be multi-process and the parent process creates another
+ // nsTransferable, then the count should still be the same.
+ is(getClipboardCacheFDCount(), initialFdCount + 1, "should still be using files for previously stored BIG_STRING");
+
+ // Re-establish baseline for the second part of the test below.
+ initialFdCount = getClipboardCacheFDCount();
+ }
+
+ // ### Part 2 - Reading from the clipboard.
+
+ var Transfer2 = nsTransferable();
+ Transfer2.init(Loadctx);
+ Transfer2.addDataFlavor("text/plain");
+
+ // Iniitalize with a small string, so we can see that mData -> mCacheFD works.
+ assignTextToTransferable(Transfer2, SMALL_STRING);
+ checkTransferableText(Transfer2, SMALL_STRING, "transferable after assigning SMALL_STRING");
+ if (isFDCountingSupported()) {
+ is(getClipboardCacheFDCount(), initialFdCount, "should not use file to store SMALL_STRING.");
+ }
+
+ // Check whether the clipboard data can be read, and simulatenously trigger mData -> mCacheFD.
+ Services.clipboard.getData(Transfer2, Services.clipboard.kGlobalClipboard);
+ checkTransferableText(Transfer2, BIG_STRING, "transferable after retrieving from clipboard");
+ if (isFDCountingSupported()) {
+ is(getClipboardCacheFDCount(), initialFdCount + 1, "should use a file for BIG_STRING (read from clipboard).");
+ }
+
+ // Store a small string, to exercise the code path from mCacheFD -> mData.
+ assignTextToTransferable(Transfer2, SMALL_STRING);
+ checkTransferableText(Transfer2, SMALL_STRING, "transferable after assigning SMALL_STRING");
+ if (isFDCountingSupported()) {
+ is(getClipboardCacheFDCount(), initialFdCount, "should release the file after clearing the transferable.");
+ }
+ }
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ This test checks whether a big string can be copied to the clipboard, and then retrieved in the same form.
+ On non-Windows, the test also checks whether the data of the transferable is really stored in a file.
+ </body>
+</window>
diff --git a/widget/tests/test_wheeltransaction.xhtml b/widget/tests/test_wheeltransaction.xhtml
new file mode 100644
index 0000000000..23e855c39b
--- /dev/null
+++ b/widget/tests/test_wheeltransaction.xhtml
@@ -0,0 +1,27 @@
+<?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="Wheel scroll transaction tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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();
+window.openDialog("window_wheeltransaction.xhtml", "_blank",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/unit/test_macsharingservice.js b/widget/tests/unit/test_macsharingservice.js
new file mode 100644
index 0000000000..f6b0a8e3fc
--- /dev/null
+++ b/widget/tests/unit/test_macsharingservice.js
@@ -0,0 +1,61 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+// Basic tests to verify that MacSharingService returns expected data
+
+function test_getSharingProviders() {
+ let sharingService = Cc["@mozilla.org/widget/macsharingservice;1"].getService(
+ Ci.nsIMacSharingService
+ );
+
+ // Ensure these URL's are accepted without error by the getSharingProviders()
+ // method. This does not test if the URL's are interpreted correctly by
+ // the platform implementation and does not test that the URL will be
+ // successfully shared to the target application if the shareURL method is
+ // used. It does indicate the Mac API's used to get the sharing providers
+ // successfully created a URL object for the URL provided and returned at
+ // least one provider.
+ let urls = [
+ "http://example.org",
+ "http://example.org/#",
+ "http://example.org/dkl??",
+ "http://example.org/dkl?a=b;c=d#thisisaref",
+ "http://example.org/dkl?a=b;c=d#thisisaref#double",
+ "http://example.org/#/",
+ "http://example.org/#/#",
+ "http://example.org/#/#/",
+ // This test fails due to the '|' in the path which needs additional
+ // encoding before conversion to NSURL. See bug 1740565.
+ // "http://example.org/foo/bar/x|page.html#this_is_a_fragment",
+ "http://example.org/page.html#this_is_a_fragment",
+ "http://example.org/page.html#this_is_a_fragment#and_another",
+ "http://example.org/foo/bar;#foo",
+ "http://example.org/a file with spaces.html",
+ "https://chat.mozilla.org/#/room/#macdev:mozilla.org",
+ "https://chat.mozilla.org/#/room/%23macdev:mozilla.org",
+ ];
+
+ urls.forEach(url => testGetSharingProvidersForUrl(sharingService, url));
+}
+
+function testGetSharingProvidersForUrl(sharingService, url) {
+ let providers = sharingService.getSharingProviders(url);
+ Assert.greater(providers.length, 1, "There are providers returned");
+ providers.forEach(provider => {
+ Assert.ok("name" in provider, "Provider has name");
+ Assert.ok("menuItemTitle" in provider, "Provider has menuItemTitle");
+ Assert.ok("image" in provider, "Provider has image");
+
+ Assert.notEqual(
+ provider.title,
+ "Mail",
+ "Known filtered provider not returned"
+ );
+ });
+}
+
+function run_test() {
+ test_getSharingProviders();
+}
diff --git a/widget/tests/unit/test_macwebapputils.js b/widget/tests/unit/test_macwebapputils.js
new file mode 100644
index 0000000000..8967f8a593
--- /dev/null
+++ b/widget/tests/unit/test_macwebapputils.js
@@ -0,0 +1,34 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+// Basic tests to verify that MacWebAppUtils works
+
+function test_find_app() {
+ var mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"].createInstance(
+ Ci.nsIMacWebAppUtils
+ );
+ let sig = "com.apple.TextEdit";
+
+ let path;
+ path = mwaUtils.pathForAppWithIdentifier(sig);
+ info("TextEdit path: " + path + "\n");
+ Assert.notEqual(path, "");
+}
+
+function test_dont_find_fake_app() {
+ var mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"].createInstance(
+ Ci.nsIMacWebAppUtils
+ );
+ let sig = "calliope.penitentiary.dramamine";
+
+ let path;
+ path = mwaUtils.pathForAppWithIdentifier(sig);
+ Assert.equal(path, "");
+}
+
+function run_test() {
+ test_find_app();
+ test_dont_find_fake_app();
+}
diff --git a/widget/tests/unit/test_taskbar_jumplistitems.js b/widget/tests/unit/test_taskbar_jumplistitems.js
new file mode 100644
index 0000000000..f839160bde
--- /dev/null
+++ b/widget/tests/unit/test_taskbar_jumplistitems.js
@@ -0,0 +1,282 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+// This tests taskbar jump list functionality available on win7 and up.
+
+function test_basics() {
+ var item = Cc["@mozilla.org/windows-jumplistitem;1"].createInstance(
+ Ci.nsIJumpListItem
+ );
+
+ var sep = Cc["@mozilla.org/windows-jumplistseparator;1"].createInstance(
+ Ci.nsIJumpListSeparator
+ );
+
+ var shortcut = Cc["@mozilla.org/windows-jumplistshortcut;1"].createInstance(
+ Ci.nsIJumpListShortcut
+ );
+
+ var link = Cc["@mozilla.org/windows-jumplistlink;1"].createInstance(
+ Ci.nsIJumpListLink
+ );
+
+ Assert.ok(!item.equals(sep));
+ Assert.ok(!item.equals(shortcut));
+ Assert.ok(!item.equals(link));
+
+ Assert.ok(!sep.equals(item));
+ Assert.ok(!sep.equals(shortcut));
+ Assert.ok(!sep.equals(link));
+
+ Assert.ok(!shortcut.equals(item));
+ Assert.ok(!shortcut.equals(sep));
+ Assert.ok(!shortcut.equals(link));
+
+ Assert.ok(!link.equals(item));
+ Assert.ok(!link.equals(sep));
+ Assert.ok(!link.equals(shortcut));
+
+ Assert.ok(item.equals(item));
+ Assert.ok(sep.equals(sep));
+ Assert.ok(link.equals(link));
+ Assert.ok(shortcut.equals(shortcut));
+}
+
+function test_separator() {
+ // separators:
+
+ var item = Cc["@mozilla.org/windows-jumplistseparator;1"].createInstance(
+ Ci.nsIJumpListSeparator
+ );
+
+ Assert.ok(item.type == Ci.nsIJumpListItem.JUMPLIST_ITEM_SEPARATOR);
+}
+
+function test_hashes() {
+ var link = Cc["@mozilla.org/windows-jumplistlink;1"].createInstance(
+ Ci.nsIJumpListLink
+ );
+ var uri1 = Cc["@mozilla.org/network/simple-uri-mutator;1"]
+ .createInstance(Ci.nsIURIMutator)
+ .setSpec("http://www.123.com/")
+ .finalize();
+ var uri2 = Cc["@mozilla.org/network/simple-uri-mutator;1"]
+ .createInstance(Ci.nsIURIMutator)
+ .setSpec("http://www.123.com/")
+ .finalize();
+
+ link.uri = uri1;
+
+ Assert.ok(link.compareHash(uri2));
+ uri2 = uri2.mutate().setSpec("http://www.456.com/").finalize();
+ Assert.ok(!link.compareHash(uri2));
+ uri2 = uri2.mutate().setSpec("http://www.123.com/").finalize();
+ Assert.ok(link.compareHash(uri2));
+ uri2 = uri2.mutate().setSpec("https://www.123.com/").finalize();
+ Assert.ok(!link.compareHash(uri2));
+ uri2 = uri2.mutate().setSpec("http://www.123.com/test/").finalize();
+ Assert.ok(!link.compareHash(uri2));
+ uri1 = uri1.mutate().setSpec("http://www.123.com/test/").finalize();
+ link.uri = uri1;
+ uri2 = uri2.mutate().setSpec("http://www.123.com/test/").finalize();
+ Assert.ok(link.compareHash(uri2));
+ uri1 = uri1.mutate().setSpec("https://www.123.com/test/").finalize();
+ link.uri = uri1;
+ uri2 = uri2.mutate().setSpec("https://www.123.com/test/").finalize();
+ Assert.ok(link.compareHash(uri2));
+ uri2 = uri2.mutate().setSpec("ftp://www.123.com/test/").finalize();
+ Assert.ok(!link.compareHash(uri2));
+ uri2 = uri2.mutate().setSpec("http://123.com/test/").finalize();
+ Assert.ok(!link.compareHash(uri2));
+ uri1 = uri1.mutate().setSpec("https://www.123.com/test/").finalize();
+ link.uri = uri1;
+ uri2 = uri2.mutate().setSpec("https://www.123.com/Test/").finalize();
+ Assert.ok(!link.compareHash(uri2));
+
+ uri1 = uri1.mutate().setSpec("http://www.123.com/").finalize();
+ link.uri = uri1;
+ Assert.equal(link.uriHash, "QGLmWuwuTozr3tOfXSf5mg==");
+ uri1 = uri1.mutate().setSpec("http://www.123.com/test/").finalize();
+ link.uri = uri1;
+ Assert.equal(link.uriHash, "AG87Ls+GmaUYSUJFETRr3Q==");
+ uri1 = uri1.mutate().setSpec("https://www.123.com/").finalize();
+ link.uri = uri1;
+ Assert.equal(link.uriHash, "iSx6UH1a9enVPzUA9JZ42g==");
+}
+
+function test_links() {
+ // links:
+ var link1 = Cc["@mozilla.org/windows-jumplistlink;1"].createInstance(
+ Ci.nsIJumpListLink
+ );
+ var link2 = Cc["@mozilla.org/windows-jumplistlink;1"].createInstance(
+ Ci.nsIJumpListLink
+ );
+
+ var uri1 = Cc["@mozilla.org/network/simple-uri-mutator;1"]
+ .createInstance(Ci.nsIURIMutator)
+ .setSpec("http://www.test.com/")
+ .finalize();
+ var uri2 = Cc["@mozilla.org/network/simple-uri-mutator;1"]
+ .createInstance(Ci.nsIURIMutator)
+ .setSpec("http://www.test.com/")
+ .finalize();
+
+ link1.uri = uri1;
+ link1.uriTitle = "Test";
+ link2.uri = uri2;
+ link2.uriTitle = "Test";
+
+ Assert.ok(link1.equals(link2));
+
+ link2.uriTitle = "Testing";
+
+ Assert.ok(!link1.equals(link2));
+
+ link2.uriTitle = "Test";
+ uri2 = uri2.mutate().setSpec("http://www.testing.com/").finalize();
+ link2.uri = uri2;
+
+ Assert.ok(!link1.equals(link2));
+}
+
+function test_shortcuts() {
+ // shortcuts:
+ var sc = Cc["@mozilla.org/windows-jumplistshortcut;1"].createInstance(
+ Ci.nsIJumpListShortcut
+ );
+
+ var handlerApp = Cc[
+ "@mozilla.org/uriloader/local-handler-app;1"
+ ].createInstance(Ci.nsILocalHandlerApp);
+
+ handlerApp.name = "TestApp";
+ handlerApp.detailedDescription = "TestApp detailed description.";
+ handlerApp.appendParameter("-test");
+
+ sc.iconIndex = 1;
+ Assert.equal(sc.iconIndex, 1);
+
+ var faviconPageUri = Cc["@mozilla.org/network/simple-uri-mutator;1"]
+ .createInstance(Ci.nsIURIMutator)
+ .setSpec("http://www.123.com/")
+ .finalize();
+ sc.faviconPageUri = faviconPageUri;
+ Assert.equal(sc.faviconPageUri, faviconPageUri);
+
+ var notepad = Services.dirsvc.get("WinD", Ci.nsIFile);
+ notepad.append("notepad.exe");
+ if (notepad.exists()) {
+ handlerApp.executable = notepad;
+ sc.app = handlerApp;
+ Assert.equal(sc.app.detailedDescription, "TestApp detailed description.");
+ Assert.equal(sc.app.name, "TestApp");
+ Assert.ok(sc.app.parameterExists("-test"));
+ Assert.ok(!sc.app.parameterExists("-notset"));
+ }
+}
+
+async function test_jumplist() {
+ // Jump lists can't register links unless the application is the default
+ // protocol handler for the protocol of the link, so we skip off testing
+ // those in these tests. We'll init the jump list for the xpc shell harness,
+ // add a task item, and commit it.
+
+ // not compiled in
+ if (Ci.nsIWinTaskbar == null) {
+ return;
+ }
+
+ var taskbar = Cc["@mozilla.org/windows-taskbar;1"].getService(
+ Ci.nsIWinTaskbar
+ );
+
+ // Since we're only testing the general functionality of the JumpListBuilder
+ // et. al, we can just test the non-private browsing version.
+ // (The only difference between the two at this level is the App User Model ID.)
+ var builder = taskbar.createJumpListBuilder(false);
+
+ Assert.notEqual(builder, null);
+
+ // Win7 and up only
+ try {
+ var ver = parseFloat(Services.sysinfo.getProperty("version"));
+ if (ver < 6.1) {
+ Assert.ok(!builder.available);
+ return;
+ }
+ } catch (ex) {}
+
+ Assert.ok(taskbar.available);
+
+ builder.deleteActiveList();
+
+ var items = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+
+ var sc = Cc["@mozilla.org/windows-jumplistshortcut;1"].createInstance(
+ Ci.nsIJumpListShortcut
+ );
+
+ var handlerApp = Cc[
+ "@mozilla.org/uriloader/local-handler-app;1"
+ ].createInstance(Ci.nsILocalHandlerApp);
+
+ handlerApp.name = "Notepad";
+ handlerApp.detailedDescription = "Testing detailed description.";
+
+ var notepad = Services.dirsvc.get("WinD", Ci.nsIFile);
+ notepad.append("notepad.exe");
+ if (notepad.exists()) {
+ // To ensure "profile-before-change" will fire before
+ // "xpcom-shutdown-threads"
+ do_get_profile();
+
+ handlerApp.executable = notepad;
+ sc.app = handlerApp;
+ items.appendElement(sc);
+
+ var removed = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ Assert.ok(builder.initListBuild(removed));
+ Assert.ok(builder.addListToBuild(builder.JUMPLIST_CATEGORY_TASKS, items));
+ Assert.ok(builder.addListToBuild(builder.JUMPLIST_CATEGORY_RECENT));
+ Assert.ok(builder.addListToBuild(builder.JUMPLIST_CATEGORY_FREQUENT));
+ let rv = new Promise(resolve => {
+ builder.commitListBuild(resolve);
+ });
+ Assert.ok(await rv);
+
+ builder.deleteActiveList();
+
+ Assert.ok(builder.initListBuild(removed));
+ Assert.ok(
+ builder.addListToBuild(
+ builder.JUMPLIST_CATEGORY_CUSTOMLIST,
+ items,
+ "Custom List"
+ )
+ );
+ rv = new Promise(resolve => {
+ builder.commitListBuild(resolve);
+ });
+ Assert.ok(await rv);
+
+ builder.deleteActiveList();
+ }
+}
+
+function run_test() {
+ if (mozinfo.os != "win") {
+ return;
+ }
+ test_basics();
+ test_separator();
+ test_hashes();
+ test_links();
+ test_shortcuts();
+
+ run_next_test();
+}
+
+add_task(test_jumplist);
diff --git a/widget/tests/unit/xpcshell.ini b/widget/tests/unit/xpcshell.ini
new file mode 100644
index 0000000000..e6fafa44f0
--- /dev/null
+++ b/widget/tests/unit/xpcshell.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+head =
+
+[test_taskbar_jumplistitems.js]
+skip-if =
+ os == "win" && os_version == "10.0" # Bug 1457329
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[test_macsharingservice.js]
+skip-if = os != "mac"
+[test_macwebapputils.js]
+skip-if = os != "mac"
diff --git a/widget/tests/window_bug429954.xhtml b/widget/tests/window_bug429954.xhtml
new file mode 100644
index 0000000000..ca26d52621
--- /dev/null
+++ b/widget/tests/window_bug429954.xhtml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 478536"
+ onload="start();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml" id="body">
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+function ok(aCondition, aMessage)
+{
+ window.browsingContext.topChromeWindow.opener.wrappedJSObject.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 start() {
+ var oldWidth = window.outerWidth, oldHeight = window.outerHeight;
+ window.maximize();
+ window.restore();
+ is(window.outerWidth, oldWidth, "wrong window width after maximize+restore");
+ is(window.outerHeight, oldHeight, "wrong window height after maximize+restore");
+ window.arguments[0].SimpleTest.finish();
+ window.close();
+}
+
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_bug478536.xhtml b/widget/tests/window_bug478536.xhtml
new file mode 100644
index 0000000000..7318eb0bff
--- /dev/null
+++ b/widget/tests/window_bug478536.xhtml
@@ -0,0 +1,211 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 478536"
+ width="600" height="600"
+ onload="onload();"
+ onunload="onunload();"
+ 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" />
+
+<body xmlns="http://www.w3.org/1999/xhtml" id="body">
+<style type="text/css">
+ #view {
+ overflow: auto;
+ width: 100px;
+ height: 100px;
+ border: 1px solid;
+ margin: 0;
+ }
+</style>
+<pre id="view" onscroll="onScrollView(event);">
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+</pre>
+</body>
+
+<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);
+}
+
+var gBody = document.getElementById("body");
+var gView = document.getElementById("view");
+
+/**
+ * Description:
+ *
+ * First, lock the wheel scrolling target to "view" at first step.
+ * Next, scroll back to top most of the "view" at second step.
+ * Finally, scroll back again at third step. This fails to scroll the "view",
+ * then, |onMouseScrollFailed| event should be fired. And at that time, we
+ * can remove the "view". So, in post processing of the event firere, the
+ * "view" should not be referred.
+ *
+ * For suppressing random test failure, all tests will be retried if we handle
+ * unexpected timeout event.
+ */
+
+var gTests = [
+ { scrollToForward: true, shouldScroll: true },
+ { scrollToForward: false, shouldScroll: true },
+ { scrollToForward: false, shouldScroll: false }
+];
+var gCurrentTestIndex = -1;
+var gIgnoreScrollEvent = true;
+
+var gPrefSvc = SpecialPowers.Services.prefs;
+const kPrefSmoothScroll = "general.smoothScroll";
+const kPrefNameTimeout = "mousewheel.transaction.timeout";
+const kDefaultTimeout = gPrefSvc.getIntPref(kPrefNameTimeout);
+
+gPrefSvc.setBoolPref(kPrefSmoothScroll, false);
+
+var gTimeout = kDefaultTimeout;
+
+gBody.addEventListener("MozMouseScrollFailed", onMouseScrollFailed);
+gBody.addEventListener("MozMouseScrollTransactionTimeout",
+ onTransactionTimeout);
+
+function setTimeoutPrefs(aTimeout)
+{
+ gPrefSvc.setIntPref(kPrefNameTimeout, aTimeout);
+ gTimeout = aTimeout;
+}
+
+function resetTimeoutPrefs()
+{
+ if (gTimeout == kDefaultTimeout)
+ return;
+ setTimeoutPrefs(kDefaultTimeout);
+}
+
+function growUpTimeoutPrefs()
+{
+ if (gTimeout != kDefaultTimeout)
+ return;
+ setTimeoutPrefs(5000);
+}
+
+function onload()
+{
+ disableNonTestMouseEvents(true);
+ setTimeout(runNextTest, 0);
+}
+
+function onunload()
+{
+ resetTimeoutPrefs();
+ disableNonTestMouseEvents(false);
+ gPrefSvc.clearUserPref(kPrefSmoothScroll);
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+ window.arguments[0].SimpleTest.finish();
+}
+
+function finish()
+{
+ window.close();
+}
+
+// testing code
+
+var gTimer;
+function clearTimer()
+{
+ clearTimeout(gTimer);
+ gTimer = 0;
+}
+
+function runNextTest()
+{
+ clearTimer();
+ if (++gCurrentTestIndex >= gTests.length) {
+ ok(true, "didn't crash, succeeded");
+ finish();
+ return;
+ }
+ fireWheelScrollEvent(gTests[gCurrentTestIndex].scrollToForward);
+}
+
+var gRetryCount = 5;
+function retryAllTests()
+{
+ clearTimer();
+ if (--gRetryCount >= 0) {
+ gView.scrollTop = 0;
+ gView.scrollLeft = 0;
+ gCurrentTestIndex = -1;
+ growUpTimeoutPrefs();
+ ok(true, "WARNING: retry current test-list...");
+ gTimer = setTimeout(runNextTest, 0);
+ } else {
+ ok(false, "Failed by unexpected timeout");
+ finish();
+ }
+}
+
+function fireWheelScrollEvent(aForward)
+{
+ gIgnoreScrollEvent = false;
+ var event = { deltaY: aForward ? 4.0 : -4.0,
+ deltaMode: WheelEvent.DOM_DELTA_LINE };
+ sendWheelAndPaint(gView, 5, 5, event, function() {
+ // No callback - we're just forcing the refresh driver to tick.
+ }, window);
+}
+
+function onScrollView(aEvent)
+{
+ if (gIgnoreScrollEvent)
+ return;
+ gIgnoreScrollEvent = true;
+ clearTimer();
+ ok(gTests[gCurrentTestIndex].shouldScroll, "The view is scrolled");
+ gTimer = setTimeout(runNextTest, 0);
+}
+
+function onMouseScrollFailed(aEvent)
+{
+ clearTimer();
+ gIgnoreScrollEvent = true;
+ ok(!gTests[gCurrentTestIndex].shouldScroll, "The view is not scrolled");
+ if (!gTests[gCurrentTestIndex].shouldScroll)
+ gBody.removeChild(gView);
+ runNextTest();
+}
+
+function onTransactionTimeout(aEvent)
+{
+ if (!gTimer)
+ return;
+ gIgnoreScrollEvent = true;
+ retryAllTests();
+}
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_bug522217.xhtml b/widget/tests/window_bug522217.xhtml
new file mode 100644
index 0000000000..8d03879521
--- /dev/null
+++ b/widget/tests/window_bug522217.xhtml
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 522217"
+ onload="start();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml" id="body">
+</body>
+
+<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 executeSoon() {
+ return new Promise(resolve => {
+ window.arguments[0].SimpleTest.executeSoon(resolve);
+ });
+}
+
+function waitForEvent(obj, name) {
+ return new Promise(resolve => {
+ obj.addEventListener(name, resolve, { once: true });
+ });
+}
+
+async function start() {
+ await waitForEvent(window, "focus");
+ var oldOuterWidth = window.outerWidth, oldOuterHeight = window.outerHeight;
+ var oldInnerWidth = window.innerWidth, oldInnerHeight = window.innerHeight;
+ document.documentElement.setAttribute("drawintitlebar", "true");
+
+ await executeSoon();
+ is(window.outerWidth, oldOuterWidth, "drawintitlebar shouldn't change the window's outerWidth");
+ is(window.outerHeight, oldOuterHeight, "drawintitlebar shouldn't change the window's outerHeight");
+ is(window.innerWidth, oldOuterWidth, "if drawintitlebar is set, innerWidth and outerWidth should be the same");
+ is(window.innerHeight, oldOuterHeight, "if drawintitlebar is set, innerHeight and outerHeight should be the same");
+
+ // Wait for going full screen and back.
+ let sizemodeChange = waitForEvent(window, "sizemodechange");
+ window.fullScreen = true;
+ await sizemodeChange;
+ sizemodeChange = waitForEvent(window, "sizemodechange");
+ window.fullScreen = false;
+ await sizemodeChange;
+ is(window.outerWidth, oldOuterWidth, "wrong outerWidth after fullscreen mode");
+ is(window.outerHeight, oldOuterHeight, "wrong outerHeight after fullscreen mode");
+ is(window.innerWidth, oldOuterWidth, "wrong innerWidth after fullscreen mode");
+ is(window.innerHeight, oldOuterHeight, "wrong innerHeight after fullscreen mode");
+ document.documentElement.removeAttribute("drawintitlebar");
+
+ await executeSoon();
+ is(window.outerWidth, oldOuterWidth, "wrong outerWidth after removing drawintitlebar");
+ is(window.outerHeight, oldOuterHeight, "wrong outerHeight after removing drawintitlebar");
+ is(window.innerWidth, oldInnerWidth, "wrong innerWidth after removing drawintitlebar");
+ is(window.innerHeight, oldInnerHeight, "wrong innerHeight after removing drawintitlebar");
+ window.arguments[0].SimpleTest.finish();
+ window.close();
+}
+
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_bug538242.xhtml b/widget/tests/window_bug538242.xhtml
new file mode 100644
index 0000000000..fb878b1383
--- /dev/null
+++ b/widget/tests/window_bug538242.xhtml
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<window title="Window for Test for Mozilla Bug 538242"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
diff --git a/widget/tests/window_bug593307_centerscreen.xhtml b/widget/tests/window_bug593307_centerscreen.xhtml
new file mode 100644
index 0000000000..816e314262
--- /dev/null
+++ b/widget/tests/window_bug593307_centerscreen.xhtml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 593307"
+ width="100" height="100"
+ onload="onload();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml" id="body">
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+function onload()
+{
+ var SimpleTest = window.opener.SimpleTest;
+ SimpleTest.ok(window.screenX >= 0, "centerscreen window should not start offscreen (X coordinate)");
+ SimpleTest.ok(window.screenY >= 0, "centerscreen window should not start offscreen (Y coordinate)");
+ window.opener.finished();
+}
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_bug593307_offscreen.xhtml b/widget/tests/window_bug593307_offscreen.xhtml
new file mode 100644
index 0000000000..422c406812
--- /dev/null
+++ b/widget/tests/window_bug593307_offscreen.xhtml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 593307"
+ width="100" height="100"
+ onload="onLoad();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml" id="body">
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+var centerscreen = null;
+var SimpleTest = window.arguments[0];
+var finish = window.arguments[1];
+
+function onLoad()
+{
+ centerscreen = window.openDialog('window_bug593307_centerscreen.xhtml','', 'chrome,centerscreen,dependent,dialog=no');
+}
+
+function finished() {
+ centerscreen.close();
+ finish();
+}
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_composition_text_querycontent.xhtml b/widget/tests/window_composition_text_querycontent.xhtml
new file mode 100644
index 0000000000..7cdbf28568
--- /dev/null
+++ b/widget/tests/window_composition_text_querycontent.xhtml
@@ -0,0 +1,10977 @@
+<?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="Testing composition, text and query content events"
+ 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 src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js" />
+
+ <panel id="panel" hidden="true" orient="vertical">
+ <vbox id="vbox">
+ <html:textarea id="textbox" cols="20" rows="4" style="font-size: 36px;"/>
+ </vbox>
+ </panel>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="display">
+<div id="div" style="margin: 0; padding: 0; font-size: 36px;">Here is a text frame.</div>
+<textarea style="margin: 0; font-family: -moz-fixed;" id="textarea" cols="20" rows="4"></textarea><br/>
+<iframe id="iframe" width="300" height="150"
+ src="data:text/html,&lt;textarea id='textarea' cols='20' rows='4'&gt;&lt;/textarea&gt;"></iframe><br/>
+<iframe id="iframe2" width="300" height="150"
+ src="data:text/html,&lt;body onload='document.designMode=%22on%22'&gt;body content&lt;/body&gt;"></iframe><br/>
+<iframe id="iframe3" width="300" height="150"
+ src="data:text/html,&lt;body onload='document.designMode=%22on%22'&gt;body content&lt;/body&gt;"></iframe><br/>
+<iframe id="iframe4" width="300" height="150"
+ src="data:text/html,&lt;div contenteditable id='contenteditable'&gt;&lt;/div&gt;"></iframe><br/>
+<!--
+ NOTE: the width for the next two iframes is chosen to be small enough to make
+ the Show Password button (for type=password) be outside the viewport so that
+ it doesn't affect the rendering compared to the type=text control.
+ But still large enough to comfortably fit the input values we test.
+-->
+<iframe id="iframe5" style="width:10ch" height="50" src="data:text/html,&lt;input id='input'&gt;"></iframe>
+<iframe id="iframe6" style="width:10ch" height="50" src="data:text/html,&lt;input id='password' type='password'&gt;"></iframe><br/>
+<iframe id="iframe7" width="300" height="150"
+ src="data:text/html,&lt;span contenteditable id='contenteditable'&gt;&lt;/span&gt;"></iframe><br/>
+<input id="input" type="text"/><br/>
+<input id="password" type="password"/><br/>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<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 isfuzzy(aLeft, aRight, aEpsilon, aMessage) {
+ window.arguments[0].SimpleTest.isfuzzy(aLeft, aRight, aEpsilon, aMessage);
+}
+
+function todo(aCondition, aMessage)
+{
+ window.arguments[0].SimpleTest.todo(aCondition, aMessage);
+}
+
+function todo_is(aLeft, aRight, aMessage)
+{
+ window.arguments[0].SimpleTest.todo_is(aLeft, aRight, aMessage);
+}
+
+function todo_isnot(aLeft, aRight, aMessage)
+{
+ window.arguments[0].SimpleTest.todo_isnot(aLeft, aRight, aMessage);
+}
+
+function isSimilarTo(aLeft, aRight, aAllowedDifference, aMessage)
+{
+ if (Math.abs(aLeft - aRight) <= aAllowedDifference) {
+ ok(true, aMessage);
+ } else {
+ ok(false, aMessage + ", got=" + aLeft + ", expected=" + (aRight - aAllowedDifference) + "~" + (aRight + aAllowedDifference));
+ }
+}
+
+function isGreaterThan(aLeft, aRight, aMessage)
+{
+ ok(aLeft > aRight, aMessage + ", got=" + aLeft + ", expected minimum value=" + aRight);
+}
+
+/**
+ * synthesizeSimpleCompositionChange synthesizes a composition which has only
+ * one clause and put caret end of it.
+ *
+ * @param aComposition string or object. If string, it's treated as
+ * composition string whose attribute is
+ * COMPOSITION_ATTR_RAW_CLAUSE.
+ * If object, it must have .string whose type is "string".
+ * Additionally, .attr can be specified if you'd like to
+ * use the other attribute instead of
+ * COMPOSITION_ATTR_RAW_CLAUSE.
+ */
+function synthesizeSimpleCompositionChange(aComposition, aWindow, aCallback) {
+ const comp = (() => {
+ if (typeof aComposition == "string") {
+ return { string: aComposition, attr: COMPOSITION_ATTR_RAW_CLAUSE };
+ }
+ return {
+ string: aComposition.string,
+ attr: aComposition.attr === undefined
+ ? COMPOSITION_ATTR_RAW_CLAUSE
+ : aComposition.attr
+ };
+ })();
+ synthesizeCompositionChange(
+ {
+ composition: {
+ string: comp.string,
+ clauses: [
+ { length: comp.string.length, attr: comp.attr },
+ ],
+ },
+ caret: { start: comp.string.length, length: 0 },
+ },
+ aWindow,
+ aCallback
+ );
+}
+
+
+var div = document.getElementById("div");
+var textarea = document.getElementById("textarea");
+var panel = document.getElementById("panel");
+var textbox = document.getElementById("textbox");
+var iframe = document.getElementById("iframe");
+var iframe2 = document.getElementById("iframe2");
+var iframe3 = document.getElementById("iframe3");
+var contenteditable;
+var windowOfContenteditable;
+var contenteditableBySpan;
+var windowOfContenteditableBySpan;
+var input = document.getElementById("input");
+var password = document.getElementById("password");
+var textareaInFrame;
+
+const nsITextInputProcessorCallback = Ci.nsITextInputProcessorCallback;
+const nsIInterfaceRequestor = Ci.nsIInterfaceRequestor;
+const nsIWebNavigation = Ci.nsIWebNavigation;
+const nsIDocShell = Ci.nsIDocShell;
+
+function waitForTick() {
+ return new Promise(resolve => { SimpleTest.executeSoon(resolve); });
+}
+
+async function waitForEventLoops(aTimes)
+{
+ for (let i = 1; i < aTimes; i++) {
+ await waitForTick();
+ }
+ await new Promise(resolve => { setTimeout(resolve, 20); });
+}
+
+function getEditor(aNode)
+{
+ return aNode.editor;
+}
+
+function getHTMLEditorIMESupport(aWindow)
+{
+ return aWindow.docShell.editor;
+}
+
+const kIsWin = (navigator.platform.indexOf("Win") == 0);
+const kIsMac = (navigator.platform.indexOf("Mac") == 0);
+
+const kLFLen = kIsWin ? 2 : 1;
+const kLF = kIsWin ? "\r\n" : "\n";
+
+function checkQueryContentResult(aResult, aMessage)
+{
+ ok(aResult, aMessage + ": the result is null");
+ if (!aResult) {
+ return false;
+ }
+ ok(aResult.succeeded, aMessage + ": the query content failed");
+ return aResult.succeeded;
+}
+
+function checkContent(aExpectedText, aMessage, aID)
+{
+ if (!aID) {
+ aID = "";
+ }
+ let textContent = synthesizeQueryTextContent(0, 100);
+ if (!checkQueryContentResult(textContent, aMessage +
+ ": synthesizeQueryTextContent " + aID)) {
+ return false;
+ }
+ is(textContent.text, aExpectedText,
+ aMessage + ": composition string is wrong " + aID);
+ return textContent.text == aExpectedText;
+}
+
+function checkContentRelativeToSelection(aRelativeOffset, aLength, aExpectedOffset, aExpectedText, aMessage, aID)
+{
+ if (!aID) {
+ aID = "";
+ }
+ aMessage += " (aRelativeOffset=" + aRelativeOffset + "): "
+ let textContent = synthesizeQueryTextContent(aRelativeOffset, aLength, true);
+ if (!checkQueryContentResult(textContent, aMessage +
+ "synthesizeQueryTextContent " + aID)) {
+ return false;
+ }
+ is(textContent.offset, aExpectedOffset,
+ aMessage + "offset is wrong " + aID);
+ is(textContent.text, aExpectedText,
+ aMessage + "text is wrong " + aID);
+ return textContent.offset == aExpectedOffset &&
+ textContent.text == aExpectedText;
+}
+
+function checkSelection(aExpectedOffset, aExpectedText, aMessage, aID)
+{
+ if (!aID) {
+ aID = "";
+ }
+ let selectedText = synthesizeQuerySelectedText();
+ if (!checkQueryContentResult(selectedText, aMessage +
+ ": synthesizeQuerySelectedText " + aID)) {
+ return false;
+ }
+ if (aExpectedOffset === null) {
+ is(
+ selectedText.notFound,
+ true,
+ `${aMessage}: selection should not be found ${aID}`
+ );
+ return selectedText.notFound;
+ }
+
+ is(
+ selectedText.notFound,
+ false,
+ `${aMessage}: selection should be found ${aID}`
+ );
+ if (selectedText.notFound) {
+ return false;
+ }
+ is(
+ selectedText.offset,
+ aExpectedOffset,
+ `${aMessage}: selection offset should be ${aExpectedOffset} ${aID}`
+ );
+ is(
+ selectedText.text,
+ aExpectedText,
+ `${aMessage}: selected text should be "${aExpectedText}" ${aID}`
+ );
+ return selectedText.offset == aExpectedOffset &&
+ selectedText.text == aExpectedText;
+}
+
+function checkIMESelection(
+ aSelectionType,
+ aExpectedFound,
+ aExpectedOffset,
+ aExpectedText,
+ aMessage,
+ aID,
+ aToDo = {}
+) {
+ if (!aID) {
+ aID = "";
+ }
+ aMessage += " (" + aSelectionType + ")";
+ let {
+ notFound = is,
+ offset = is,
+ text = is,
+ } = aToDo;
+ let selectionType = 0;
+ switch (aSelectionType) {
+ case "RawClause":
+ selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_RAWINPUT;
+ break;
+ case "SelectedRawClause":
+ selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDRAWTEXT;
+ break;
+ case "ConvertedClause":
+ selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_CONVERTEDTEXT;
+ break;
+ case "SelectedClause":
+ selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDCONVERTEDTEXT;
+ break;
+ default:
+ ok(false, aMessage + ": invalid selection type, " + aSelectionType);
+ }
+ isnot(selectionType, 0, aMessage + ": wrong value");
+ let selectedText = synthesizeQuerySelectedText(selectionType);
+ if (!checkQueryContentResult(selectedText, aMessage +
+ ": synthesizeQuerySelectedText " + aID)) {
+ return false;
+ }
+ notFound(
+ selectedText.notFound,
+ !aExpectedFound,
+ `${aMessage}: selection should ${
+ aExpectedFound ? "" : "not"
+ } be found ${aID}`);
+ if (selectedText.notFound) {
+ return selectedText.notFound == !aExpectedFound;
+ }
+
+ offset(
+ selectedText.offset,
+ aExpectedOffset,
+ `${aMessage}: selection offset is wrong ${aID}`
+ );
+ text(
+ selectedText.text,
+ aExpectedText,
+ `${aMessage}: selected text is wrong ${aID}`
+ );
+ return selectedText.offset == aExpectedOffset &&
+ selectedText.text == aExpectedText;
+}
+
+function checkRect(aRect, aExpectedRect, aMessage)
+{
+ is(aRect.left, aExpectedRect.left, aMessage + ": left is wrong");
+ is(aRect.top, aExpectedRect.top, aMessage + " top is wrong");
+ is(aRect.width, aExpectedRect.width, aMessage + ": width is wrong");
+ is(aRect.height, aExpectedRect.height, aMessage + ": height is wrong");
+ return aRect.left == aExpectedRect.left &&
+ aRect.top == aExpectedRect.top &&
+ aRect.width == aExpectedRect.width &&
+ aRect.height == aExpectedRect.height;
+}
+
+function checkRectFuzzy(aRect, aExpectedRect, aEpsilon, aMessage) {
+ isfuzzy(aRect.left, aExpectedRect.left, aEpsilon.left, aMessage + ": left is wrong");
+ isfuzzy(aRect.top, aExpectedRect.top, aEpsilon.top, aMessage + " top is wrong");
+ isfuzzy(aRect.width, aExpectedRect.width, aEpsilon.width, aMessage + ": width is wrong");
+ isfuzzy(aRect.height, aExpectedRect.height, aEpsilon.height, aMessage + ": height is wrong");
+ return (aRect.left >= aExpectedRect.left - aEpsilon.left &&
+ aRect.left <= aExpectedRect.left + aEpsilon.left) &&
+ (aRect.top >= aExpectedRect.top - aEpsilon.top &&
+ aRect.top <= aExpectedRect.top + aEpsilon.top) &&
+ (aRect.width >= aExpectedRect.width - aEpsilon.width &&
+ aRect.width <= aExpectedRect.width + aEpsilon.width) &&
+ (aRect.height >= aExpectedRect.height - aEpsilon.height &&
+ aRect.height <= aExpectedRect.height + aEpsilon.height);
+}
+
+function getRectArray(aQueryTextRectArrayResult) {
+ let rects = [];
+ for (let i = 0; ; i++) {
+ let rect = { left: {}, top: {}, width: {}, height: {} };
+ try {
+ aQueryTextRectArrayResult.getCharacterRect(i, rect.left, rect.top, rect.width, rect.height);
+ } catch (e) {
+ break;
+ }
+ rects.push({
+ left: rect.left.value,
+ top: rect.top.value,
+ width: rect.width.value,
+ height: rect.height.value,
+ });
+ }
+ return rects;
+}
+
+function checkRectArray(aQueryTextRectArrayResult, aExpectedTextRectArray, aMessage)
+{
+ for (let i = 1; i < aExpectedTextRectArray.length; ++i) {
+ let rect = { left: {}, top: {}, width: {}, height: {} };
+ try {
+ aQueryTextRectArrayResult.getCharacterRect(i, rect.left, rect.top, rect.width, rect.height);
+ } catch (e) {
+ ok(false, aMessage + ": failed to retrieve " + i + "th rect (" + e + ")");
+ return false;
+ }
+ function toRect(aRect)
+ {
+ return { left: aRect.left.value, top: aRect.top.value, width: aRect.width.value, height: aRect.height.value };
+ }
+ if (!checkRect(toRect(rect), aExpectedTextRectArray[i], aMessage + " " + i + "th rect")) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function checkRectContainsRect(aRect, aContainer, aMessage)
+{
+ let container = { left: Math.ceil(aContainer.left),
+ top: Math.ceil(aContainer.top),
+ width: Math.floor(aContainer.width),
+ height: Math.floor(aContainer.height) };
+
+ let ret = container.left <= aRect.left &&
+ container.top <= aRect.top &&
+ container.left + container.width >= aRect.left + aRect.width &&
+ container.top + container.height >= aRect.top + aRect.height;
+ ret = ret && aMessage;
+ ok(ret, aMessage + " container={ left=" + container.left + ", top=" +
+ container.top + ", width=" + container.width + ", height=" +
+ container.height + " } rect={ left=" + aRect.left + ", top=" + aRect.top +
+ ", width=" + aRect.width + ", height=" + aRect.height + " }");
+ return ret;
+}
+
+// eslint-disable-next-line complexity
+function runUndoRedoTest()
+{
+ textarea.value = "";
+ textarea.focus();
+
+ // input raw characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306D",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "," },
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306D\u3053",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: "b" },
+ });
+
+ // convert
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u732B",
+ "clauses":
+ [
+ { "length": 1,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: " " },
+ });
+
+ // commit
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ // input raw characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u307E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "j" },
+ });
+
+ // cancel the composition
+ synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Escape" } });
+
+ // input raw characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3080",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "]" },
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3080\u3059",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: "r" },
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3080\u3059\u3081",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 },
+ "key": { key: "/" },
+ });
+
+ // convert
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u5A18",
+ "clauses":
+ [
+ { "length": 1,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: " " },
+ });
+
+ // commit
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ sendString(" meant");
+ synthesizeKey("KEY_Backspace");
+ synthesizeKey("s \"cat-girl\". She is a ");
+
+ // input raw characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3088",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "9" },
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3088\u3046",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: "4" },
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3088\u3046\u304b",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 },
+ "key": { key: "t" },
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3088\u3046\u304b\u3044",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 },
+ "key": { key: "e" },
+ });
+
+ // convert
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u5996\u602a",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: " " },
+ });
+
+ // commit
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "Enter" } });
+
+ synthesizeKey("KEY_Backspace", {repeat: 12});
+
+ let i = 0;
+ if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a \u5996\u602A",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(32, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a ",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(30, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("\u732B\u5A18 mean",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(7, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("\u732B\u5A18 meant",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(8, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("\u732B\u5A18",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(2, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("\u732B",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ // XXX this is unexpected behavior, see bug 258291
+ if (!checkContent("\u732B",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(0, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true});
+
+ if (!checkContent("",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(0, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ // XXX this is unexpected behavior, see bug 258291
+ if (!checkContent("\u732B",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B\u5A18",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(2, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B\u5A18 meant",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(8, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B\u5A18 mean",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(7, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a ",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(30, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a \u5996\u602A",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(32, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
+ return;
+ }
+
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
+ "runUndoRedoTest", "#" + ++i) ||
+ !checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
+ // eslint-disable-next-line no-useless-return
+ return;
+ }
+}
+
+function checkInputEvent(aEvent, aIsComposing, aInputType, aData, aTargetRanges, aDescription) {
+ if (aEvent.type !== "input" && aEvent.type !== "beforeinput") {
+ throw new Error(`${aDescription}: "${aEvent.type}" is not InputEvent`);
+ }
+ ok(InputEvent.isInstance(aEvent), `"${aEvent.type}" event should be dispatched with InputEvent interface: ${aDescription}`);
+ let cancelable = aEvent.type === "beforeinput" &&
+ aInputType !== "insertCompositionText" &&
+ aInputType !== "deleteCompositionText";
+ is(aEvent.cancelable, cancelable, `"${aEvent.type}" event should ${cancelable ? "be" : "be never"} cancelable: ${aDescription}`);
+ is(aEvent.bubbles, true, `"${aEvent.type}" event should always bubble: ${aDescription}`);
+ is(aEvent.isComposing, aIsComposing, `isComposing of "${aEvent.type}" event should be ${aIsComposing}: ${aDescription}`);
+ is(aEvent.inputType, aInputType, `inputType of "${aEvent.type}" event should be "${aInputType}": ${aDescription}`);
+ is(aEvent.data, aData, `data of "${aEvent.type}" event should be ${aData}: ${aDescription}`);
+ is(aEvent.dataTransfer, null, `dataTransfer of "${aEvent.type}" event should be null: ${aDescription}`);
+ let targetRanges = aEvent.getTargetRanges();
+ if (aTargetRanges.length === 0) {
+ is(targetRanges.length, 0,
+ `getTargetRange() of "${aEvent.type}" event should return empty array: ${aDescription}`);
+ } else {
+ is(targetRanges.length, aTargetRanges.length,
+ `getTargetRange() of "${aEvent.type}" event should return static range array: ${aDescription}`);
+ if (targetRanges.length == aTargetRanges.length) {
+ for (let i = 0; i < targetRanges.length; i++) {
+ is(targetRanges[i].startContainer, aTargetRanges[i].startContainer,
+ `startContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
+ is(targetRanges[i].startOffset, aTargetRanges[i].startOffset,
+ `startOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
+ is(targetRanges[i].endContainer, aTargetRanges[i].endContainer,
+ `endContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
+ is(targetRanges[i].endOffset, aTargetRanges[i].endOffset,
+ `endOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
+ }
+ }
+ }
+}
+
+function runCompositionCommitAsIsTest()
+{
+ textarea.focus();
+
+ let result = [];
+ function clearResult()
+ {
+ result = [];
+ }
+
+ function handler(aEvent)
+ {
+ result.push(aEvent);
+ }
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", handler, true);
+ textarea.addEventListener("beforeinput", handler, true);
+ textarea.addEventListener("input", handler, true);
+ textarea.addEventListener("text", handler, true);
+
+ // compositioncommitasis with composing string.
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #1");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "Enter" } });
+
+ is(result.length, 4,
+ "runCompositionCommitAsIsTest: 4 events should be fired after dispatching compositioncommitasis #1");
+ is(result[0].type, "text",
+ "runCompositionCommitAsIsTest: text should be fired after dispatching compositioncommitasis because it's dispatched when there is composing string #1");
+ is(result[1].type, "beforeinput",
+ "runCompositionCommitAsIsTest: beforeinput should be fired after dispatching compositioncommitasis because it's dispatched when there is composing string #1");
+ checkInputEvent(result[1], true, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #1");
+ is(result[2].type, "compositionend",
+ "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #1");
+ is(result[3].type, "input",
+ "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #1");
+ checkInputEvent(result[3], false, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #1");
+ is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #1");
+
+ // compositioncommitasis with committed string.
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #2");
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "KEY_Enter", type: "keydown" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #2");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter", type: "keyup" } });
+
+ is(result.length, 2,
+ "runCompositionCommitAsIsTest: 2 events should be fired after dispatching compositioncommitasis #2");
+ // XXX Do we need a "beforeinput" event here? Not sure.
+ is(result[0].type, "compositionend",
+ "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #2");
+ is(result[1].type, "input",
+ "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #2");
+ checkInputEvent(result[1], false, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #2");
+ is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #2");
+
+ // compositioncommitasis with committed string.
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #3");
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "KEY_Escape", type: "keydown" },
+ });
+ is(textarea.value, "", "runCompositionCommitAsIsTest: textarea has non-empty composition string #3");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape", type: "keyup" } });
+
+ is(result.length, 2,
+ "runCompositionCommitAsIsTest: 2 events should be fired after dispatching compositioncommitasis #3");
+ // XXX Do we need a "beforeinput" event here? Not sure.
+ is(result[0].type, "compositionend",
+ "runCompositionCommitAsIsTest: compositionend shouldn't be fired after dispatching compositioncommitasis #3");
+ is(result[1].type, "input",
+ "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #3");
+ checkInputEvent(result[1], false, "insertCompositionText", "", [],
+ "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #3");
+ is(textarea.value, "", "runCompositionCommitAsIsTest: textarea doesn't have committed string #3");
+
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+ textarea.removeEventListener("beforeinput", handler, true);
+ textarea.removeEventListener("input", handler, true);
+ textarea.removeEventListener("text", handler, true);
+}
+
+function runCompositionCommitTest()
+{
+ textarea.focus();
+
+ let result = [];
+ function clearResult()
+ {
+ result = [];
+ }
+
+ function handler(aEvent)
+ {
+ result.push(aEvent);
+ }
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", handler, true);
+ textarea.addEventListener("beforeinput", handler, true);
+ textarea.addEventListener("input", handler, true);
+ textarea.addEventListener("text", handler, true);
+
+ // compositioncommit with different composing string.
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a", type: "keydown" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #1");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "a", type: "keyup" } });
+
+ is(result.length, 5,
+ "runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #1");
+ is(result[0].type, "compositionupdate",
+ "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit because it's dispatched when there is composing string #1");
+ is(result[1].type, "text",
+ "runCompositionCommitTest: text should be fired after dispatching compositioncommit #1");
+ is(result[2].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit because it's dispatched when there is composing string #1");
+ checkInputEvent(result[2], true, "insertCompositionText", "\u3043", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #1");
+ is(result[3].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #1");
+ is(result[4].type, "input",
+ "runCompositionCommitTest: input should be fired after dispatching compositioncommit #1");
+ checkInputEvent(result[4], false, "insertCompositionText", "\u3043", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #1");
+ is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #1");
+
+ // compositioncommit with different committed string when there is already committed string
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #2");
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "KEY_Enter", type: "keydown" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have committed string #2");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "KEY_Enter", type: "keyup" } });
+
+ is(result.length, 5,
+ "runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #2");
+ is(result[0].type, "compositionupdate",
+ "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #2");
+ is(result[1].type, "text",
+ "runCompositionCommitTest: text should be fired after dispatching compositioncommit #2");
+ is(result[2].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #2");
+ checkInputEvent(result[2], true, "insertCompositionText", "\u3043", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #2");
+ is(result[3].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #2");
+ is(result[4].type, "input",
+ "runCompositionCommitTest: input should be fired after dispatching compositioncommit #2");
+ checkInputEvent(result[4], false, "insertCompositionText", "\u3043", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #2");
+ is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #2");
+
+ // compositioncommit with empty composition string.
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #3");
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "KEY_Enter", type: "keydown" },
+ });
+ is(textarea.value, "", "runCompositionCommitTest: textarea has non-empty composition string #3");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "KEY_Enter", type: "keyup" } });
+
+ is(result.length, 5,
+ "runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #3");
+ is(result[0].type, "compositionupdate",
+ "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #3");
+ is(result[1].type, "text",
+ "runCompositionCommitTest: text should be fired after dispatching compositioncommit #3");
+ is(result[2].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #3");
+ checkInputEvent(result[2], true, "insertCompositionText", "\u3043", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #3");
+ is(result[3].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #3");
+ is(result[4].type, "input",
+ "runCompositionCommitTest: input should be fired after dispatching compositioncommit #3");
+ checkInputEvent(result[4], false, "insertCompositionText", "\u3043", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #3");
+ is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #3");
+
+ // inserting empty string with simple composition.
+ textarea.value = "abc";
+ textarea.setSelectionRange(3, 3);
+ synthesizeComposition({ type: "compositionstart" });
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "" });
+
+ is(result.length, 4,
+ "runCompositionCommitTest: 4 events should be fired when inserting empty string with composition");
+ is(result[0].type, "text",
+ "runCompositionCommitTest: text should be fired when inserting empty string with composition");
+ is(result[1].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired when inserting empty string with composition");
+ checkInputEvent(result[1], true, "insertCompositionText", "", [],
+ "runCompositionCommitTest: when inserting empty string with composition");
+ is(result[2].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired when inserting empty string with composition");
+ is(result[3].type, "input",
+ "runCompositionCommitTest: input should be fired when inserting empty string with composition");
+ checkInputEvent(result[3], false, "insertCompositionText", "", [],
+ "runCompositionCommitTest: when inserting empty string with composition");
+ is(textarea.value, "abc",
+ "runCompositionCommitTest: textarea should keep original value when inserting empty string with composition");
+
+ // replacing selection with empty string with simple composition.
+ textarea.value = "abc";
+ textarea.setSelectionRange(0, 3);
+ synthesizeComposition({ type: "compositionstart" });
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "" });
+
+ is(result.length, 4,
+ "runCompositionCommitTest: 4 events should be fired when replacing with empty string with composition");
+ is(result[0].type, "text",
+ "runCompositionCommitTest: text should be fired when replacing with empty string with composition");
+ is(result[1].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired when replacing with empty string with composition");
+ checkInputEvent(result[1], true, "insertCompositionText", "", [],
+ "runCompositionCommitTest: when replacing with empty string with composition");
+ is(result[2].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired when replacing with empty string with composition");
+ is(result[3].type, "input",
+ "runCompositionCommitTest: input should be fired when replacing with empty string with composition");
+ checkInputEvent(result[3], false, "insertCompositionText", "", [],
+ "runCompositionCommitTest: when replacing with empty string with composition");
+ is(textarea.value, "",
+ "runCompositionCommitTest: textarea should become empty when replacing selection with empty string with composition");
+
+ // replacing selection with same string with simple composition.
+ textarea.value = "abc";
+ textarea.setSelectionRange(0, 3);
+ synthesizeComposition({ type: "compositionstart" });
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "abc" });
+
+ is(result.length, 5,
+ "runCompositionCommitTest: 5 events should be fired when replacing selection with same string with composition");
+ is(result[0].type, "compositionupdate",
+ "runCompositionCommitTest: compositionupdate should be fired when replacing selection with same string with composition");
+ is(result[1].type, "text",
+ "runCompositionCommitTest: text should be fired when replacing selection with same string with composition");
+ is(result[2].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired when replacing selection with same string with composition");
+ checkInputEvent(result[2], true, "insertCompositionText", "abc", [],
+ "runCompositionCommitTest: when replacing selection with same string with composition");
+ is(result[3].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired when replacing selection with same string with composition");
+ is(result[4].type, "input",
+ "runCompositionCommitTest: input should be fired when replacing selection with same string with composition");
+ checkInputEvent(result[4], false, "insertCompositionText", "abc", [],
+ "runCompositionCommitTest: when replacing selection with same string with composition");
+ is(textarea.value, "abc",
+ "runCompositionCommitTest: textarea should keep same value when replacing selection with same string with composition");
+
+ // compositioncommit with non-empty composition string.
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #4");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Enter" } });
+
+ is(result.length, 5,
+ "runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #4");
+ is(result[0].type, "compositionupdate",
+ "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #4");
+ is(result[1].type, "text",
+ "runCompositionCommitTest: text should be fired after dispatching compositioncommit #4");
+ is(result[2].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #4");
+ checkInputEvent(result[2], true, "insertCompositionText", "", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #4");
+ is(result[3].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #4");
+ is(result[4].type, "input",
+ "runCompositionCommitTest: input should be fired after dispatching compositioncommit #4");
+ checkInputEvent(result[4], false, "insertCompositionText", "", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #4");
+ is(textarea.value, "", "runCompositionCommitTest: textarea should be empty #4");
+
+ // compositioncommit immediately without compositionstart
+ textarea.value = "";
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "a" } });
+
+ is(result.length, 5,
+ "runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #5");
+ is(result[0].type, "compositionupdate",
+ "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #5");
+ is(result[1].type, "text",
+ "runCompositionCommitTest: text should be fired after dispatching compositioncommit #5");
+ is(result[2].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #5");
+ checkInputEvent(result[2], true, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #5");
+ is(result[3].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #5");
+ is(result[4].type, "input",
+ "runCompositionCommitTest: input should be fired after dispatching compositioncommit #5");
+ checkInputEvent(result[4], false, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #5");
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should be empty #5");
+
+ // compositioncommit with same composition string.
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #5");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "KEY_Enter" } });
+
+ is(result.length, 4,
+ "runCompositionCommitTest: 4 events should be fired after dispatching compositioncommit #6");
+ is(result[0].type, "text",
+ "runCompositionCommitTest: text should be fired after dispatching compositioncommit #6");
+ is(result[1].type, "beforeinput",
+ "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #6");
+ checkInputEvent(result[1], true, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #6");
+ is(result[2].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #6");
+ is(result[3].type, "input",
+ "runCompositionCommitTest: input should be fired after dispatching compositioncommit #6");
+ checkInputEvent(result[3], false, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #6");
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #6");
+
+ // compositioncommit with same composition string when there is committed string
+ textarea.value = "";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #6");
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "KEY_Enter", type: "keydown" },
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #6");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "KEY_Enter", type: "keyup" } });
+
+ is(result.length, 2,
+ "runCompositionCommitTest: 2 events should be fired after dispatching compositioncommit #7");
+ // XXX Do we need a "beforeinput" event here? Not sure.
+ is(result[0].type, "compositionend",
+ "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #7");
+ is(result[1].type, "input",
+ "runCompositionCommitTest: input should be fired after dispatching compositioncommit #7");
+ checkInputEvent(result[1], false, "insertCompositionText", "\u3042", [],
+ "runCompositionCommitTest: after dispatching compositioncommit #7");
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #6");
+
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+ textarea.removeEventListener("beforeinput", handler, true);
+ textarea.removeEventListener("input", handler, true);
+ textarea.removeEventListener("text", handler, true);
+}
+
+// eslint-disable-next-line complexity
+async function runCompositionTest()
+{
+ textarea.value = "";
+ textarea.focus();
+ let caretRects = [];
+
+ let caretRect = synthesizeQueryCaretRect(0);
+ if (!checkQueryContentResult(caretRect,
+ "runCompositionTest: synthesizeQueryCaretRect #0")) {
+ return;
+ }
+ caretRects[0] = caretRect;
+
+ // input first character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "o" },
+ });
+
+ if (!checkContent("\u3089", "runCompositionTest", "#1-1") ||
+ !checkSelection(1, "", "runCompositionTest", "#1-1")) {
+ return;
+ }
+
+ caretRect = synthesizeQueryCaretRect(1);
+ if (!checkQueryContentResult(caretRect,
+ "runCompositionTest: synthesizeQueryCaretRect #1-1")) {
+ return;
+ }
+ caretRects[1] = caretRect;
+
+ // input second character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: "\\", code: "IntlYen", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
+ });
+
+ if (!checkContent("\u3089\u30FC", "runCompositionTest", "#1-2") ||
+ !checkSelection(2, "", "runCompositionTest", "#1-2")) {
+ return;
+ }
+
+ caretRect = synthesizeQueryCaretRect(2);
+ if (!checkQueryContentResult(caretRect,
+ "runCompositionTest: synthesizeQueryCaretRect #1-2")) {
+ return;
+ }
+ caretRects[2] = caretRect;
+
+ isnot(caretRects[2].left, caretRects[1].left,
+ "runCompositionTest: caret isn't moved (#1-2)");
+ is(caretRects[2].top, caretRects[1].top,
+ "runCompositionTest: caret is moved to another line (#1-2)");
+ is(caretRects[2].width, caretRects[1].width,
+ "runCompositionTest: caret width is wrong (#1-2)");
+ is(caretRects[2].height, caretRects[1].height,
+ "runCompositionTest: caret width is wrong (#1-2)");
+
+ // input third character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 },
+ "key": { key: "/" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3") ||
+ !checkSelection(3, "", "runCompositionTest", "#1-3")) {
+ return;
+ }
+
+ caretRect = synthesizeQueryCaretRect(3);
+ if (!checkQueryContentResult(caretRect,
+ "runCompositionTest: synthesizeQueryCaretRect #1-3")) {
+ return;
+ }
+ caretRects[3] = caretRect;
+
+ isnot(caretRects[3].left, caretRects[2].left,
+ "runCompositionTest: caret isn't moved (#1-3)");
+ is(caretRects[3].top, caretRects[2].top,
+ "runCompositionTest: caret is moved to another line (#1-3)");
+ is(caretRects[3].width, caretRects[2].width,
+ "runCompositionTest: caret width is wrong (#1-3)");
+ is(caretRects[3].height, caretRects[2].height,
+ "runCompositionTest: caret height is wrong (#1-3)");
+
+ // moves the caret left
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: "KEY_ArrowLeft" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3-1") ||
+ !checkSelection(2, "", "runCompositionTest", "#1-3-1")) {
+ return;
+ }
+
+
+ caretRect = synthesizeQueryCaretRect(2);
+ if (!checkQueryContentResult(caretRect,
+ "runCompositionTest: synthesizeQueryCaretRect #1-3-1")) {
+ return;
+ }
+
+ is(caretRect.left, caretRects[2].left,
+ "runCompositionTest: caret rects are different (#1-3-1, left)");
+ is(caretRect.top, caretRects[2].top,
+ "runCompositionTest: caret rects are different (#1-3-1, top)");
+ // by bug 335359, the caret width depends on the right side's character.
+ is(caretRect.width, caretRects[2].width + Math.round(window.devicePixelRatio),
+ "runCompositionTest: caret rects are different (#1-3-1, width)");
+ is(caretRect.height, caretRects[2].height,
+ "runCompositionTest: caret rects are different (#1-3-1, height)");
+
+ // moves the caret left
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "KEY_ArrowLeft" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3-2") ||
+ !checkSelection(1, "", "runCompositionTest", "#1-3-2")) {
+ return;
+ }
+
+
+ caretRect = synthesizeQueryCaretRect(1);
+ if (!checkQueryContentResult(caretRect,
+ "runCompositionTest: synthesizeQueryCaretRect #1-3-2")) {
+ return;
+ }
+
+ is(caretRect.left, caretRects[1].left,
+ "runCompositionTest: caret rects are different (#1-3-2, left)");
+ is(caretRect.top, caretRects[1].top,
+ "runCompositionTest: caret rects are different (#1-3-2, top)");
+ // by bug 335359, the caret width depends on the right side's character.
+ is(caretRect.width, caretRects[1].width + Math.round(window.devicePixelRatio),
+ "runCompositionTest: caret rects are different (#1-3-2, width)");
+ is(caretRect.height, caretRects[1].height,
+ "runCompositionTest: caret rects are different (#1-3-2, height)");
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 },
+ "key": { key: "y" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093", "runCompositionTest", "#1-4") ||
+ !checkSelection(4, "", "runCompositionTest", "#1-4")) {
+ return;
+ }
+
+
+ // backspace
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 },
+ "key": { key: "KEY_Backspace" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-5") ||
+ !checkSelection(3, "", "runCompositionTest", "#1-5")) {
+ return;
+ }
+
+ // re-input
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 },
+ "key": { key: "y" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093", "runCompositionTest", "#1-6") ||
+ !checkSelection(4, "", "runCompositionTest", "#1-6")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055",
+ "clauses":
+ [
+ { "length": 5, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 5, "length": 0 },
+ "key": { key: "x" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055", "runCompositionTest", "#1-7") ||
+ !checkSelection(5, "", "runCompositionTest", "#1-7")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044",
+ "clauses":
+ [
+ { "length": 6, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 6, "length": 0 },
+ "key": { key: "e" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044", "runCompositionTest", "#1-8") ||
+ !checkSelection(6, "", "runCompositionTest", "#1-8")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053",
+ "clauses":
+ [
+ { "length": 7, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 7, "length": 0 },
+ "key": { key: "b" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053", "runCompositionTest", "#1-8") ||
+ !checkSelection(7, "", "runCompositionTest", "#1-8")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
+ "clauses":
+ [
+ { "length": 8, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 8, "length": 0 },
+ "key": { key: "4" },
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
+ "runCompositionTest", "#1-9") ||
+ !checkSelection(8, "", "runCompositionTest", "#1-9")) {
+ return;
+ }
+
+ // convert
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
+ "clauses":
+ [
+ { "length": 4,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 2,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 },
+ "key": { key: " " },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
+ "runCompositionTest", "#1-10") ||
+ !checkSelection(4, "", "runCompositionTest", "#1-10")) {
+ return;
+ }
+
+ // change the selected clause
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
+ "clauses":
+ [
+ { "length": 4,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": 2,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 6, "length": 0 },
+ "key": { key: "KEY_ArrowLeft", shiftKey: true },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
+ "runCompositionTest", "#1-11") ||
+ !checkSelection(6, "", "runCompositionTest", "#1-11")) {
+ return;
+ }
+
+ // reset clauses
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
+ "clauses":
+ [
+ { "length": 5,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 3,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 5, "length": 0 },
+ "key": { key: "KEY_ArrowRight" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
+ "runCompositionTest", "#1-12") ||
+ !checkSelection(5, "", "runCompositionTest", "#1-12")) {
+ return;
+ }
+
+
+ let textRect1 = synthesizeQueryTextRect(0, 1);
+ let textRect2 = synthesizeQueryTextRect(1, 1);
+ if (!checkQueryContentResult(textRect1,
+ "runCompositionTest: synthesizeQueryTextRect #1-12-1") ||
+ !checkQueryContentResult(textRect2,
+ "runCompositionTest: synthesizeQueryTextRect #1-12-2")) {
+ return;
+ }
+
+ // commit the composition string
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
+ "runCompositionTest", "#1-13") ||
+ !checkSelection(8, "", "runCompositionTest", "#1-13")) {
+ return;
+ }
+
+ let textRect3 = synthesizeQueryTextRect(0, 1);
+ let textRect4 = synthesizeQueryTextRect(1, 1);
+
+ if (!checkQueryContentResult(textRect3,
+ "runCompositionTest: synthesizeQueryTextRect #1-13-1") ||
+ !checkQueryContentResult(textRect4,
+ "runCompositionTest: synthesizeQueryTextRect #1-13-2")) {
+ return;
+ }
+
+ checkRect(textRect3, textRect1, "runCompositionTest: textRect #1-13-1");
+ checkRect(textRect4, textRect2, "runCompositionTest: textRect #1-13-2");
+
+ // restart composition and input characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3057",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "d" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3057",
+ "runCompositionTest", "#2-1") ||
+ !checkSelection(8 + 1, "", "runCompositionTest", "#2-1")) {
+ return;
+ }
+
+ let textRect3QueriedWithRelativeOffset = synthesizeQueryTextRect(-8, 1, true);
+ let textRect4QueriedWithRelativeOffset = synthesizeQueryTextRect(-8 + 1, 1, true);
+ checkRect(textRect3QueriedWithRelativeOffset, textRect3, "runCompositionTest: textRect #2-1-2");
+ checkRect(textRect4QueriedWithRelativeOffset, textRect4, "runCompositionTest: textRect #2-1-3");
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3058",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "r" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058",
+ "runCompositionTest", "#2-2") ||
+ !checkSelection(8 + 1, "", "runCompositionTest", "#2-2")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3058\u3087",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: ")", code: "Digit9", keyCode: KeyboardEvent.DOM_VK_9, shiftKey: true },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087",
+ "runCompositionTest", "#2-3") ||
+ !checkSelection(8 + 2, "", "runCompositionTest", "#2-3")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3058\u3087\u3046",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 },
+ "key": { key: "4" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087\u3046",
+ "runCompositionTest", "#2-4") ||
+ !checkSelection(8 + 3, "", "runCompositionTest", "#2-4")) {
+ return;
+ }
+
+ // commit the composition string
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087\u3046",
+ "runCompositionTest", "#2-4") ||
+ !checkSelection(8 + 3, "", "runCompositionTest", "#2-4")) {
+ return;
+ }
+
+ // set selection
+ const selectionSetTest = await synthesizeSelectionSet(4, 7, false);
+ ok(selectionSetTest, "runCompositionTest: selectionSetTest failed");
+
+ if (!checkSelection(4, "\u3055\u884C\u3053\u3046\u3058\u3087\u3046", "runCompositionTest", "#3-1")) {
+ return;
+ }
+
+ // start composition with selection
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u304A",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "6" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u304A",
+ "runCompositionTest", "#3-2") ||
+ !checkSelection(4 + 1, "", "runCompositionTest", "#3-2")) {
+ return;
+ }
+
+ // remove the composition string
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "KEY_Backspace" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#3-3") ||
+ !checkSelection(4, "", "runCompositionTest", "#3-3")) {
+ return;
+ }
+
+ // re-input the composition string
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3046",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "4" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3046",
+ "runCompositionTest", "#3-4") ||
+ !checkSelection(4 + 1, "", "runCompositionTest", "#3-4")) {
+ return;
+ }
+
+ // cancel the composition
+ synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Escape" } });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#3-5") ||
+ !checkSelection(4, "", "runCompositionTest", "#3-5")) {
+ return;
+ }
+
+ // bug 271815, some Chinese IMEs for Linux make empty composition string
+ // and compty clause information when it lists up Chinese characters on
+ // its candidate window.
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "a" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#4-1") ||
+ !checkSelection(4, "", "runCompositionTest", "#4-1")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "b" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#4-2") ||
+ !checkSelection(4, "", "runCompositionTest", "#4-2")) {
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommit", data: "\u6700", key: { key: "KEY_Enter" } });
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
+ "runCompositionTest", "#4-3") ||
+ !checkSelection(5, "", "runCompositionTest", "#4-3")) {
+ return;
+ }
+
+ // testing the canceling case
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "a" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
+ "runCompositionTest", "#4-5") ||
+ !checkSelection(5, "", "runCompositionTest", "#4-5")) {
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape" } });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
+ "runCompositionTest", "#4-6") ||
+ !checkSelection(5, "", "runCompositionTest", "#4-6")) {
+ return;
+ }
+
+ // testing whether the empty composition string deletes selected string.
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "a" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#4-8") ||
+ !checkSelection(4, "", "runCompositionTest", "#4-8")) {
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommit", data: "\u9AD8", key: { key: "KEY_Enter" } });
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u9AD8",
+ "runCompositionTest", "#4-9") ||
+ !checkSelection(5, "", "runCompositionTest", "#4-9")) {
+ return;
+ }
+
+ synthesizeKey("KEY_Backspace");
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#4-11") ||
+ !checkSelection(4, "", "runCompositionTest", "#4-11")) {
+ return;
+ }
+
+ // bug 23558, ancient Japanese IMEs on Window may send empty text event
+ // twice at canceling composition.
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u6700",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
+ "runCompositionTest", "#5-1") ||
+ !checkSelection(4 + 1, "", "runCompositionTest", "#5-1")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 },
+ "key": { key: "KEY_Backspace", type: "keydown" },
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#5-2") ||
+ !checkSelection(4, "", "runCompositionTest", "#5-2")) {
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Backspace", type: "keyup" } });
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#5-3") ||
+ !checkSelection(4, "", "runCompositionTest", "#5-3")) {
+ return;
+ }
+
+ // Undo tests for the testcases for bug 23558 and bug 271815
+ synthesizeKey("z", { accelKey: true });
+
+ // XXX this is unexpected behavior, see bug 258291
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#6-1") ||
+ !checkSelection(4, "", "runCompositionTest", "#6-1")) {
+ return;
+ }
+
+ synthesizeKey("z", { accelKey: true });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u9AD8",
+ "runCompositionTest", "#6-2") ||
+ !checkSelection(5, "", "runCompositionTest", "#6-2")) {
+ return;
+ }
+
+ synthesizeKey("z", { accelKey: true });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
+ "runCompositionTest", "#6-3") ||
+ !checkSelection(4, "", "runCompositionTest", "#6-3")) {
+ return;
+ }
+
+ synthesizeKey("z", { accelKey: true });
+
+ // XXX this is unexpected behavior, see bug 258291
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
+ "runCompositionTest", "#6-4") ||
+ !checkSelection(5, "", "runCompositionTest", "#6-4")) {
+ return;
+ }
+
+ synthesizeKey("z", { accelKey: true });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#6-5") ||
+ !checkSelection(4, "", "runCompositionTest", "#6-5")) {
+ // eslint-disable-next-line no-useless-return
+ return;
+ }
+}
+
+function runCompositionEventTest()
+{
+ const kDescription = "runCompositionEventTest: ";
+ const kEvents = ["compositionstart", "compositionupdate", "compositionend",
+ "input"];
+
+ input.value = "";
+ input.focus();
+
+ let windowEventCounts = [], windowEventData = [], windowEventLocale = [];
+ let inputEventCounts = [], inputEventData = [], inputEventLocale = [];
+ let preventDefault = false;
+ let stopPropagation = false;
+
+ function initResults()
+ {
+ for (let i = 0; i < kEvents.length; i++) {
+ windowEventCounts[kEvents[i]] = 0;
+ windowEventData[kEvents[i]] = "";
+ windowEventLocale[kEvents[i]] = "";
+ inputEventCounts[kEvents[i]] = 0;
+ inputEventData[kEvents[i]] = "";
+ inputEventLocale[kEvents[i]] = "";
+ }
+ }
+
+ function compositionEventHandlerForWindow(aEvent)
+ {
+ windowEventCounts[aEvent.type]++;
+ windowEventData[aEvent.type] = aEvent.data;
+ windowEventLocale[aEvent.type] = aEvent.locale;
+ if (preventDefault) {
+ aEvent.preventDefault();
+ }
+ if (stopPropagation) {
+ aEvent.stopPropagation();
+ }
+ }
+
+ function formEventHandlerForWindow(aEvent)
+ {
+ ok(aEvent.isTrusted, "input events must be trusted events");
+ windowEventCounts[aEvent.type]++;
+ windowEventData[aEvent.type] = input.value;
+ }
+
+ function compositionEventHandlerForInput(aEvent)
+ {
+ inputEventCounts[aEvent.type]++;
+ inputEventData[aEvent.type] = aEvent.data;
+ inputEventLocale[aEvent.type] = aEvent.locale;
+ if (preventDefault) {
+ aEvent.preventDefault();
+ }
+ if (stopPropagation) {
+ aEvent.stopPropagation();
+ }
+ }
+
+ function formEventHandlerForInput(aEvent)
+ {
+ inputEventCounts[aEvent.type]++;
+ inputEventData[aEvent.type] = input.value;
+ }
+
+ window.addEventListener("compositionstart", compositionEventHandlerForWindow,
+ true, true);
+ window.addEventListener("compositionend", compositionEventHandlerForWindow,
+ true, true);
+ window.addEventListener("compositionupdate", compositionEventHandlerForWindow,
+ true, true);
+ window.addEventListener("input", formEventHandlerForWindow,
+ true, true);
+
+ input.addEventListener("compositionstart", compositionEventHandlerForInput,
+ true, true);
+ input.addEventListener("compositionend", compositionEventHandlerForInput,
+ true, true);
+ input.addEventListener("compositionupdate", compositionEventHandlerForInput,
+ true, true);
+ input.addEventListener("input", formEventHandlerForInput,
+ true, true);
+
+ // test for normal case
+ initResults();
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "o" },
+ });
+
+ is(windowEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by window #1");
+ is(windowEventData.compositionstart, "",
+ kDescription + "data of compositionstart isn't empty (window) #1");
+ is(windowEventLocale.compositionstart, "",
+ kDescription + "locale of compositionstart isn't empty (window) #1");
+ is(inputEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by input #1");
+ is(inputEventData.compositionstart, "",
+ kDescription + "data of compositionstart isn't empty (input) #1");
+ is(inputEventLocale.compositionstart, "",
+ kDescription + "locale of compositionstart isn't empty (input) #1");
+
+ is(windowEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by window #1");
+ is(windowEventData.compositionupdate, "\u3089",
+ kDescription + "data of compositionupdate doesn't match (window) #1");
+ is(windowEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (window) #1");
+ is(inputEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by input #1");
+ is(inputEventData.compositionupdate, "\u3089",
+ kDescription + "data of compositionupdate doesn't match (input) #1");
+ is(inputEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (input) #1");
+
+ is(windowEventCounts.compositionend, 0,
+ kDescription + "compositionend has been handled by window #1");
+ is(inputEventCounts.compositionend, 0,
+ kDescription + "compositionend has been handled by input #1");
+
+ is(windowEventCounts.input, 1,
+ kDescription + "input hasn't been handled by window #1");
+ is(windowEventData.input, "\u3089",
+ kDescription + "value of input element wasn't modified (window) #1");
+ is(inputEventCounts.input, 1,
+ kDescription + "input hasn't been handled by input #1");
+ is(inputEventData.input, "\u3089",
+ kDescription + "value of input element wasn't modified (input) #1");
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 },
+ "key": { key: "\\", code: "IntlYen", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
+ });
+
+ is(windowEventCounts.compositionstart, 1,
+ kDescription + "compositionstart has been handled more than once by window #2");
+ is(inputEventCounts.compositionstart, 1,
+ kDescription + "compositionstart has been handled more than once by input #2");
+
+ is(windowEventCounts.compositionupdate, 2,
+ kDescription + "compositionupdate hasn't been handled by window #2");
+ is(windowEventData.compositionupdate, "\u3089\u30FC",
+ kDescription + "data of compositionupdate doesn't match (window) #2");
+ is(windowEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (window) #2");
+ is(inputEventCounts.compositionupdate, 2,
+ kDescription + "compositionupdate hasn't been handled by input #2");
+ is(inputEventData.compositionupdate, "\u3089\u30FC",
+ kDescription + "data of compositionupdate doesn't match (input) #2");
+ is(inputEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (input) #2");
+
+ is(windowEventCounts.compositionend, 0,
+ kDescription + "compositionend has been handled during composition by window #2");
+ is(inputEventCounts.compositionend, 0,
+ kDescription + "compositionend has been handled during composition by input #2");
+
+ is(windowEventCounts.input, 2,
+ kDescription + "input hasn't been handled by window #2");
+ is(windowEventData.input, "\u3089\u30FC",
+ kDescription + "value of input element wasn't modified (window) #2");
+ is(inputEventCounts.input, 2,
+ kDescription + "input hasn't been handled by input #2");
+ is(inputEventData.input, "\u3089\u30FC",
+ kDescription + "value of input element wasn't modified (input) #2");
+
+ // text event shouldn't cause composition update, e.g., at committing.
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ is(windowEventCounts.compositionstart, 1,
+ kDescription + "compositionstart has been handled more than once by window #3");
+ is(inputEventCounts.compositionstart, 1,
+ kDescription + "compositionstart has been handled more than once by input #3");
+
+ is(windowEventCounts.compositionupdate, 2,
+ kDescription + "compositionupdate has been fired unexpectedly on window #3");
+ is(inputEventCounts.compositionupdate, 2,
+ kDescription + "compositionupdate has been fired unexpectedly on input #3");
+
+ is(windowEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by window #3");
+ is(windowEventData.compositionend, "\u3089\u30FC",
+ kDescription + "data of compositionend doesn't match (window) #3");
+ is(windowEventLocale.compositionend, "",
+ kDescription + "locale of compositionend isn't empty (window) #3");
+ is(inputEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by input #3");
+ is(inputEventData.compositionend, "\u3089\u30FC",
+ kDescription + "data of compositionend doesn't match (input) #3");
+ is(inputEventLocale.compositionend, "",
+ kDescription + "locale of compositionend isn't empty (input) #3");
+
+ is(windowEventCounts.input, 3,
+ kDescription + "input hasn't been handled by window #3");
+ is(windowEventData.input, "\u3089\u30FC",
+ kDescription + "value of input element wasn't modified (window) #3");
+ is(inputEventCounts.input, 3,
+ kDescription + "input hasn't been handled by input #3");
+ is(inputEventData.input, "\u3089\u30FC",
+ kDescription + "value of input element wasn't modified (input) #3");
+
+ // select the second character, then, data of composition start should be
+ // the selected character.
+ initResults();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "o" },
+ });
+
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ is(windowEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by window #4");
+ is(windowEventData.compositionstart, "\u30FC",
+ kDescription + "data of compositionstart is empty (window) #4");
+ is(windowEventLocale.compositionstart, "",
+ kDescription + "locale of compositionstart isn't empty (window) #4");
+ is(inputEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by input #4");
+ is(inputEventData.compositionstart, "\u30FC",
+ kDescription + "data of compositionstart is empty (input) #4");
+ is(inputEventLocale.compositionstart, "",
+ kDescription + "locale of compositionstart isn't empty (input) #4");
+
+ is(windowEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by window #4");
+ is(windowEventData.compositionupdate, "\u3089",
+ kDescription + "data of compositionupdate doesn't match (window) #4");
+ is(windowEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (window) #4");
+ is(inputEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by input #4");
+ is(inputEventData.compositionupdate, "\u3089",
+ kDescription + "data of compositionupdate doesn't match (input) #4");
+ is(inputEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (input) #4");
+
+ is(windowEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by window #4");
+ is(windowEventData.compositionend, "\u3089",
+ kDescription + "data of compositionend doesn't match (window) #4");
+ is(windowEventLocale.compositionend, "",
+ kDescription + "locale of compositionend isn't empty (window) #4");
+ is(inputEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by input #4");
+ is(inputEventData.compositionend, "\u3089",
+ kDescription + "data of compositionend doesn't match (input) #4");
+ is(inputEventLocale.compositionend, "",
+ kDescription + "locale of compositionend isn't empty (input) #4");
+
+ is(windowEventCounts.input, 2,
+ kDescription + "input hasn't been handled by window #4");
+ is(windowEventData.input, "\u3089\u3089",
+ kDescription + "value of input element wasn't modified (window) #4");
+ is(inputEventCounts.input, 2,
+ kDescription + "input hasn't been handled by input #4");
+ is(inputEventData.input, "\u3089\u3089",
+ kDescription + "value of input element wasn't modified (input) #4");
+
+ // preventDefault() should effect nothing.
+ preventDefault = true;
+
+ initResults();
+ synthesizeKey("a", { accelKey: true }); // Select All
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306D",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "," },
+ });
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ is(windowEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by window #5");
+ is(windowEventData.compositionstart, "\u3089\u3089",
+ kDescription + "data of compositionstart is empty (window) #5");
+ is(windowEventLocale.compositionstart, "",
+ kDescription + "locale of compositionstart isn't empty (window) #5");
+ is(inputEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by input #5");
+ is(inputEventData.compositionstart, "\u3089\u3089",
+ kDescription + "data of compositionstart is empty (input) #5");
+ is(inputEventLocale.compositionstart, "",
+ kDescription + "locale of compositionstart isn't empty (input) #5");
+
+ is(windowEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by window #5");
+ is(windowEventData.compositionupdate, "\u306D",
+ kDescription + "data of compositionupdate doesn't match (window) #5");
+ is(windowEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (window) #5");
+ is(inputEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by input #5");
+ is(inputEventData.compositionupdate, "\u306D",
+ kDescription + "data of compositionupdate doesn't match (input) #5");
+ is(inputEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty (input) #5");
+
+ is(windowEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by window #5");
+ is(windowEventData.compositionend, "\u306D",
+ kDescription + "data of compositionend doesn't match (window) #5");
+ is(windowEventLocale.compositionend, "",
+ kDescription + "locale of compositionend isn't empty (window) #5");
+ is(inputEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by input #5");
+ is(inputEventData.compositionend, "\u306D",
+ kDescription + "data of compositionend doesn't match (input) #5");
+ is(inputEventLocale.compositionend, "",
+ kDescription + "locale of compositionend isn't empty (input) #5");
+
+ is(windowEventCounts.input, 2,
+ kDescription + "input hasn't been handled by window #5");
+ is(windowEventData.input, "\u306D",
+ kDescription + "value of input element wasn't modified (window) #5");
+ is(inputEventCounts.input, 2,
+ kDescription + "input hasn't been handled by input #5");
+ is(inputEventData.input, "\u306D",
+ kDescription + "value of input element wasn't modified (input) #5");
+
+ preventDefault = false;
+
+ // stopPropagation() should effect nothing (except event count)
+ stopPropagation = true;
+
+ initResults();
+ synthesizeKey("a", { accelKey: true }); // Select All
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "\\", code: "IntlRo", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
+ });
+
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ is(windowEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by window #6");
+ is(windowEventData.compositionstart, "\u306D",
+ kDescription + "data of compositionstart is empty #6");
+ is(windowEventLocale.compositionstart, "",
+ kDescription + "locale of compositionstart isn't empty #6");
+ is(inputEventCounts.compositionstart, 0,
+ kDescription + "compositionstart has been handled by input #6");
+
+ is(windowEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by window #6");
+ is(windowEventData.compositionupdate, "\u306E",
+ kDescription + "data of compositionupdate doesn't match #6");
+ is(windowEventLocale.compositionupdate, "",
+ kDescription + "locale of compositionupdate isn't empty #6");
+ is(inputEventCounts.compositionupdate, 0,
+ kDescription + "compositionupdate has been handled by input #6");
+
+ is(windowEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by window #6");
+ is(windowEventData.compositionend, "\u306E",
+ kDescription + "data of compositionend doesn't match #6");
+ is(windowEventLocale.compositionend, "",
+ kDescription + "locale of compositionend isn't empty #6");
+ is(inputEventCounts.compositionend, 0,
+ kDescription + "compositionend has been handled by input #6");
+
+ is(windowEventCounts.input, 2,
+ kDescription + "input hasn't been handled by window #6");
+ is(windowEventData.input, "\u306E",
+ kDescription + "value of input element wasn't modified (window) #6");
+ is(inputEventCounts.input, 2,
+ kDescription + "input hasn't been handled by input #6");
+ is(inputEventData.input, "\u306E",
+ kDescription + "value of input element wasn't modified (input) #6");
+
+ stopPropagation = false;
+
+ // create event and dispatch it.
+ initResults();
+
+ input.value = "value of input";
+ synthesizeKey("a", { accelKey: true }); // Select All
+
+ let compositionstart = document.createEvent("CompositionEvent");
+ compositionstart.initCompositionEvent("compositionstart",
+ true, true, document.defaultView,
+ "start data", "start locale");
+ is(compositionstart.type, "compositionstart",
+ kDescription + "type doesn't match #7");
+ is(compositionstart.data, "start data",
+ kDescription + "data doesn't match #7");
+ is(compositionstart.locale, "start locale",
+ kDescription + "locale doesn't match #7");
+ is(compositionstart.detail, 0,
+ kDescription + "detail isn't 0 #7");
+
+ input.dispatchEvent(compositionstart);
+
+ is(windowEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by window #7");
+ is(windowEventData.compositionstart, "start data",
+ kDescription + "data of compositionstart was changed (window) #7");
+ is(windowEventLocale.compositionstart, "start locale",
+ kDescription + "locale of compositionstart was changed (window) #7");
+ is(inputEventCounts.compositionstart, 1,
+ kDescription + "compositionstart hasn't been handled by input #7");
+ is(inputEventData.compositionstart, "start data",
+ kDescription + "data of compositionstart was changed (input) #7");
+ is(inputEventLocale.compositionstart, "start locale",
+ kDescription + "locale of compositionstart was changed (input) #7");
+
+ is(input.value, "value of input",
+ kDescription + "input value was changed #7");
+
+ let compositionupdate1 = document.createEvent("compositionevent");
+ compositionupdate1.initCompositionEvent("compositionupdate",
+ true, false, document.defaultView,
+ "composing string", "composing locale");
+ is(compositionupdate1.type, "compositionupdate",
+ kDescription + "type doesn't match #8");
+ is(compositionupdate1.data, "composing string",
+ kDescription + "data doesn't match #8");
+ is(compositionupdate1.locale, "composing locale",
+ kDescription + "locale doesn't match #8");
+ is(compositionupdate1.detail, 0,
+ kDescription + "detail isn't 0 #8");
+
+ input.dispatchEvent(compositionupdate1);
+
+ is(windowEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by window #8");
+ is(windowEventData.compositionupdate, "composing string",
+ kDescription + "data of compositionupdate was changed (window) #8");
+ is(windowEventLocale.compositionupdate, "composing locale",
+ kDescription + "locale of compositionupdate was changed (window) #8");
+ is(inputEventCounts.compositionupdate, 1,
+ kDescription + "compositionupdate hasn't been handled by input #8");
+ is(inputEventData.compositionupdate, "composing string",
+ kDescription + "data of compositionupdate was changed (input) #8");
+ is(inputEventLocale.compositionupdate, "composing locale",
+ kDescription + "locale of compositionupdate was changed (input) #8");
+
+ is(input.value, "value of input",
+ kDescription + "input value was changed #8");
+
+ let compositionupdate2 = document.createEvent("compositionEvent");
+ compositionupdate2.initCompositionEvent("compositionupdate",
+ true, false, document.defaultView,
+ "commit string", "commit locale");
+ is(compositionupdate2.type, "compositionupdate",
+ kDescription + "type doesn't match #9");
+ is(compositionupdate2.data, "commit string",
+ kDescription + "data doesn't match #9");
+ is(compositionupdate2.locale, "commit locale",
+ kDescription + "locale doesn't match #9");
+ is(compositionupdate2.detail, 0,
+ kDescription + "detail isn't 0 #9");
+
+ input.dispatchEvent(compositionupdate2);
+
+ is(windowEventCounts.compositionupdate, 2,
+ kDescription + "compositionupdate hasn't been handled by window #9");
+ is(windowEventData.compositionupdate, "commit string",
+ kDescription + "data of compositionupdate was changed (window) #9");
+ is(windowEventLocale.compositionupdate, "commit locale",
+ kDescription + "locale of compositionupdate was changed (window) #9");
+ is(inputEventCounts.compositionupdate, 2,
+ kDescription + "compositionupdate hasn't been handled by input #9");
+ is(inputEventData.compositionupdate, "commit string",
+ kDescription + "data of compositionupdate was changed (input) #9");
+ is(inputEventLocale.compositionupdate, "commit locale",
+ kDescription + "locale of compositionupdate was changed (input) #9");
+
+ is(input.value, "value of input",
+ kDescription + "input value was changed #9");
+
+ let compositionend = document.createEvent("Compositionevent");
+ compositionend.initCompositionEvent("compositionend",
+ true, false, document.defaultView,
+ "end data", "end locale");
+ is(compositionend.type, "compositionend",
+ kDescription + "type doesn't match #10");
+ is(compositionend.data, "end data",
+ kDescription + "data doesn't match #10");
+ is(compositionend.locale, "end locale",
+ kDescription + "locale doesn't match #10");
+ is(compositionend.detail, 0,
+ kDescription + "detail isn't 0 #10");
+
+ input.dispatchEvent(compositionend);
+
+ is(windowEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by window #10");
+ is(windowEventData.compositionend, "end data",
+ kDescription + "data of compositionend was changed (window) #10");
+ is(windowEventLocale.compositionend, "end locale",
+ kDescription + "locale of compositionend was changed (window) #10");
+ is(inputEventCounts.compositionend, 1,
+ kDescription + "compositionend hasn't been handled by input #10");
+ is(inputEventData.compositionend, "end data",
+ kDescription + "data of compositionend was changed (input) #10");
+ is(inputEventLocale.compositionend, "end locale",
+ kDescription + "locale of compositionend was changed (input) #10");
+
+ is(input.value, "value of input",
+ kDescription + "input value was changed #10");
+
+ window.removeEventListener("compositionstart",
+ compositionEventHandlerForWindow, true);
+ window.removeEventListener("compositionend",
+ compositionEventHandlerForWindow, true);
+ window.removeEventListener("compositionupdate",
+ compositionEventHandlerForWindow, true);
+ window.removeEventListener("input",
+ formEventHandlerForWindow, true);
+
+ input.removeEventListener("compositionstart",
+ compositionEventHandlerForInput, true);
+ input.removeEventListener("compositionend",
+ compositionEventHandlerForInput, true);
+ input.removeEventListener("compositionupdate",
+ compositionEventHandlerForInput, true);
+ input.removeEventListener("input",
+ formEventHandlerForInput, true);
+}
+
+function runCompositionTestWhoseTextNodeModified() {
+ const selection = windowOfContenteditable.getSelection();
+
+ (function testInsertTextBeforeComposition() {
+ const description =
+ "runCompositionTestWhoseTextNodeModified: testInsertTextBeforeComposition:";
+ contenteditable.focus();
+ contenteditable.innerHTML = "<p>def</p>";
+ const textNode = contenteditable.firstChild.firstChild;
+ selection.collapse(textNode, "def".length);
+ // Insert composition to the end of a text node
+ synthesizeSimpleCompositionChange("g");
+ is(
+ textNode.data,
+ "defg",
+ `${description} Composition should be inserted to end of the text node`
+ );
+
+ // Insert a character before the composition string
+ textNode.insertData(0, "c");
+ is(
+ textNode.data,
+ "cdefg",
+ `${
+ description
+ } Composition should be shifted when a character is inserted before it`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}cdef`.length,
+ "g",
+ `${
+ description
+ } IME selection should be shifted when a character is inserted before it`
+ );
+
+ // Update composition string (appending a character)
+ synthesizeSimpleCompositionChange("gh");
+ is(
+ textNode.data,
+ "cdefgh",
+ `${
+ description
+ } Composition should be updated correctly after inserted a character before it`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}cdef`.length,
+ "gh",
+ `${
+ description
+ } IME selection should be extended correctly at updating composition after inserted a character before it`
+ );
+
+ // Insert another character before the composition
+ textNode.insertData(0, "b");
+ is(
+ textNode.data,
+ "bcdefgh",
+ `${
+ description
+ } Composition should be shifted when a character is inserted again before it`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}bcdef`.length,
+ "gh",
+ `${
+ description
+ } IME selection should be shifted when a character is inserted again before it`
+ );
+
+ // Update the composition string again (appending another character)
+ synthesizeSimpleCompositionChange("ghi");
+ is(
+ textNode.data,
+ "bcdefghi",
+ `${
+ description
+ } Composition should be updated correctly after inserted 2 characters before it`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}bcdef`.length,
+ "ghi",
+ `${
+ description
+ } IME selection should be extended correctly at updating composition after inserted 2 characters before it`
+ );
+
+ // Insert a new character before the composition string
+ textNode.insertData(0, "a");
+ is(
+ textNode.data,
+ "abcdefghi",
+ `${
+ description
+ } Composition should be shifted when a character is inserted again and again before it`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}abcdef`.length,
+ "ghi",
+ `${
+ description
+ } IME selection should be shifted when a character is inserted again and again before it`
+ );
+
+ // Commit the composition string
+ synthesizeComposition({ type: "compositioncommitasis" });
+ is(
+ textNode.data,
+ "abcdefghi",
+ `${
+ description
+ } Composition should be committed as is`
+ );
+ is(
+ selection.focusOffset,
+ "abcdefghi".length,
+ `${description} Selection should be collapsed at end of the commit string`
+ );
+
+ // Undo the commit
+ synthesizeKey("z", { accelKey: true });
+ is(
+ textNode.data,
+ "abcdef",
+ `${
+ description
+ } Composition should be undone correctly`
+ );
+ is(
+ selection.focusOffset,
+ "abcdef".length,
+ `${
+ description
+ } Selection should be collapsed at where the composition was after undoing`
+ );
+
+ // Redo the commit
+ synthesizeKey("z", { accelKey: true, shiftKey: true });
+ is(
+ textNode.data,
+ "abcdefghi",
+ `${
+ description
+ } Composition should be redone correctly`
+ );
+ is(
+ selection.focusOffset,
+ "abcdefghi".length,
+ `${
+ description
+ } focus offset of Selection should be at end of the commit string after redoing`
+ );
+ })();
+
+ (function testInsertTextImmediatelyBeforeComposition() {
+ const description =
+ "runCompositionTestWhoseTextNodeModified: testInsertTextImmediatelyBeforeComposition:";
+ contenteditable.focus();
+ contenteditable.innerHTML = "<p>d</p>";
+ const textNode = contenteditable.firstChild.firstChild;
+ selection.collapse(textNode, 0);
+ // Insert composition at start of the text node
+ synthesizeSimpleCompositionChange("b");
+ is(
+ textNode.data,
+ "bd",
+ `${description} Composition should be inserted to start of the text node`
+ );
+
+ // Insert a character before the composition string
+ textNode.insertData(0, "a");
+ is(
+ textNode.data,
+ "abd",
+ `${
+ description
+ } Composition should be shifted when a character is inserted immediately before it`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}a`.length,
+ "b",
+ `${
+ description
+ } IME selection should be shifted when a character is inserted immediately before it`,
+ "",
+ { offset: todo_is, text: todo_is }
+ );
+
+ // Update the composition string after inserting character immediately before it
+ synthesizeSimpleCompositionChange("bc");
+ is(
+ textNode.data,
+ "abcd",
+ `${description} Composition should be updated after the inserted character`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}a`.length,
+ "bc",
+ `${
+ description
+ } IME selection should be set at the composition string after the inserted character`
+ );
+
+ // Commit it
+ synthesizeComposition({ type: "compositioncommitasis" });
+ is(
+ textNode.data,
+ "abcd",
+ `${
+ description
+ } Composition should be committed after the inserted character`
+ );
+ is(
+ selection.focusOffset,
+ "abc".length,
+ `${description} Selection should be collapsed at end of the commit string`
+ );
+ })();
+
+ (function testInsertTextImmediatelyAfterComposition() {
+ const description =
+ "runCompositionTestWhoseTextNodeModified: testInsertTextImmediatelyAfterComposition:";
+ contenteditable.focus();
+ contenteditable.innerHTML = "<p>a</p>";
+ const textNode = contenteditable.firstChild.firstChild;
+ selection.collapse(textNode, "a".length);
+ // Insert composition at end of the text node
+ synthesizeSimpleCompositionChange("b");
+ is(
+ textNode.data,
+ "ab",
+ `${description} Composition should be inserted to start of the text node`
+ );
+
+ // Insert a character after the composition string
+ textNode.insertData("ab".length, "d");
+ is(
+ textNode.data,
+ "abd",
+ `${
+ description
+ } Composition should stay when a character is inserted immediately after it`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}a`.length,
+ "b",
+ `${
+ description
+ } IME selection should stay when a character is inserted immediately after it`
+ );
+
+ // Update the composition string after inserting character immediately after it
+ synthesizeSimpleCompositionChange("bc");
+ is(
+ textNode.data,
+ "abcd",
+ `${description} Composition should be updated before the inserted character`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}a`.length,
+ "bc",
+ `${
+ description
+ } IME selection should be set at the composition string before the inserted character`
+ );
+
+ // Commit it
+ synthesizeComposition({ type: "compositioncommitasis" });
+ is(
+ textNode.data,
+ "abcd",
+ `${
+ description
+ } Composition should be committed before the inserted character`
+ );
+ is(
+ selection.focusOffset,
+ "abc".length,
+ `${description} Selection should be collapsed at end of the commit string`
+ );
+ })();
+
+ // Inserting/replacing text before the last character of composition string
+ // should be contained by the composition, i.e., updated by next composition
+ // update. This is Chrome compatible.
+ (function testInsertTextMiddleOfComposition() {
+ const description =
+ "runCompositionTestWhoseTextNodeModified: testInsertTextMiddleOfComposition:";
+ contenteditable.focus();
+ contenteditable.innerHTML = "<p>a</p>";
+ const textNode = contenteditable.firstChild.firstChild;
+ selection.collapse(textNode, "a".length);
+ // Insert composition at middle of the text node
+ synthesizeSimpleCompositionChange("bd");
+ is(
+ textNode.data,
+ "abd",
+ `${description} Composition should be inserted to end of the text node`
+ );
+
+ // Insert a character before the composition string
+ textNode.insertData("ab".length, "c");
+ is(
+ textNode.data,
+ "abcd",
+ `${
+ description
+ } Inserted string should inserted into the middle of composition string`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}a`.length,
+ "bcd",
+ `${
+ description
+ } IME selection should be extended when a character is inserted into middle of it`
+ );
+
+ // Update the composition string after inserting character into it
+ synthesizeSimpleCompositionChange("BD");
+ is(
+ textNode.data,
+ "aBD",
+ `${
+ description
+ } Composition should be replace the range containing the inserted character`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}a`.length,
+ "BD",
+ `${
+ description
+ } IME selection should be set at the updated composition string`
+ );
+
+ // Commit it
+ synthesizeComposition({ type: "compositioncommitasis" });
+ is(
+ textNode.data,
+ "aBD",
+ `${
+ description
+ } Composition should be committed without the inserted character`
+ );
+ is(
+ selection.focusOffset,
+ "aBD".length,
+ `${description} Selection should be collapsed at end of the commit string`
+ );
+ })();
+
+ (function testReplaceFirstCharOfCompositionString() {
+ const description =
+ "runCompositionTestWhoseTextNodeModified: testReplaceFirstCharOfCompositionString:";
+ contenteditable.focus();
+ contenteditable.innerHTML = "<p>abfg</p>";
+ const textNode = contenteditable.firstChild.firstChild;
+ selection.collapse(textNode, "ab".length);
+ // Insert composition at middle of the text node
+ synthesizeSimpleCompositionChange("cde");
+ is(
+ textNode.data,
+ "abcdefg",
+ `${description} Composition should be inserted`
+ );
+
+ // Replace the composition string
+ textNode.replaceData("ab".length, "c".length, "XYZ");
+ is(
+ textNode.data,
+ "abXYZdefg",
+ `${description} First character of the composition should be replaced`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}ab`.length,
+ "XYZde",
+ `${description} IME selection should contain the replace string`
+ );
+
+ // Update the composition string after replaced
+ synthesizeSimpleCompositionChange("CDE");
+ is(
+ textNode.data,
+ "abCDEfg",
+ `${description} Composition should update the replace string too`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}ab`.length,
+ "CDE",
+ `${description} IME selection should update the replace string too`
+ );
+
+ // Commit it
+ synthesizeComposition({ type: "compositioncommitasis" });
+ is(
+ textNode.data,
+ "abCDEfg",
+ `${description} Composition should be committed`
+ );
+ is(
+ selection.focusOffset,
+ "abCDE".length,
+ `${description} Selection should be collapsed at end of the commit string`
+ );
+ })();
+
+ // Although Chrome commits composition if all composition string is removed,
+ // let's keep composition for making TSF stable...
+ (function testReplaceAllCompositionString() {
+ const description =
+ "runCompositionTestWhoseTextNodeModified: testReplaceAllCompositionString:";
+ contenteditable.focus();
+ contenteditable.innerHTML = "<p>abfg</p>";
+ const textNode = contenteditable.firstChild.firstChild;
+ selection.collapse(textNode, "ab".length);
+ // Insert composition at middle of the text node
+ synthesizeSimpleCompositionChange("cde");
+ is(
+ textNode.data,
+ "abcdefg",
+ `${description} Composition should be inserted to the text node`
+ );
+
+ // Replace the composition string
+ textNode.replaceData("ab".length, "cde".length, "XYZ");
+ is(
+ textNode.data,
+ "abXYZfg",
+ `${description} Composition should be replaced`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}ab`.length,
+ "",
+ `${
+ description
+ } IME selection should be collapsed before the replace string`
+ );
+
+ // Update the composition string after replaced
+ synthesizeSimpleCompositionChange("CDE");
+ is(
+ textNode.data,
+ "abCDEXYZfg",
+ `${description} Composition should be inserted again`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}ab`.length,
+ "CDE",
+ `${description} IME selection should not contain the replace string`
+ );
+
+ // Commit it
+ synthesizeComposition({ type: "compositioncommitasis" });
+ is(
+ textNode.data,
+ "abCDEXYZfg",
+ `${description} Composition should be committed`
+ );
+ is(
+ selection.focusOffset,
+ "abCDE".length,
+ `${description} Selection should be collapsed at end of the commit string`
+ );
+ })();
+
+ (function testReplaceCompositionStringAndSurroundedCharacters() {
+ const description =
+ "runCompositionTestWhoseTextNodeModified: testReplaceCompositionStringAndSurroundedCharacters:";
+ contenteditable.focus();
+ contenteditable.innerHTML = "<p>abfg</p>";
+ const textNode = contenteditable.firstChild.firstChild;
+ selection.collapse(textNode, "ab".length);
+ // Insert composition at middle of the text node
+ synthesizeSimpleCompositionChange("cde");
+ is(
+ textNode.data,
+ "abcdefg",
+ `${description} Composition should be inserted to the text node`
+ );
+
+ // Replace the composition string
+ textNode.replaceData("a".length, "bcdef".length, "XYZ");
+ is(
+ textNode.data,
+ "aXYZg",
+ `${description} Composition should be replaced`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}a`.length,
+ "",
+ `${
+ description
+ } IME selection should be collapsed before the replace string`
+ );
+
+ // Update the composition string after replaced
+ synthesizeSimpleCompositionChange("CDE");
+ is(
+ textNode.data,
+ "aCDEXYZg",
+ `${description} Composition should be inserted again`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}a`.length,
+ "CDE",
+ `${description} IME selection should not contain the replace string`
+ );
+
+ // Commit it
+ synthesizeComposition({ type: "compositioncommitasis" });
+ is(
+ textNode.data,
+ "aCDEXYZg",
+ `${description} Composition should be committed`
+ );
+ is(
+ selection.focusOffset,
+ "aCDE".length,
+ `${description} Selection should be collapsed at end of the commit string`
+ );
+ })();
+
+ // If start boundary characters are replaced, the replace string should be
+ // contained into the composition range. This is Chrome compatible.
+ (function testReplaceStartBoundaryOfCompositionString() {
+ const description =
+ "runCompositionTestWhoseTextNodeModified: testReplaceStartBoundaryOfCompositionString:";
+ contenteditable.focus();
+ contenteditable.innerHTML = "<p>abfg</p>";
+ const textNode = contenteditable.firstChild.firstChild;
+ selection.collapse(textNode, "ab".length);
+ // Insert composition at middle of the text node
+ synthesizeSimpleCompositionChange("cde");
+ is(
+ textNode.data,
+ "abcdefg",
+ `${description} Composition should be inserted to the text node`
+ );
+
+ // Replace some text
+ textNode.replaceData("a".length, "bc".length, "XYZ");
+ is(
+ textNode.data,
+ "aXYZdefg",
+ `${
+ description
+ } Start of the composition should be replaced`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}a`.length,
+ "XYZde",
+ `${description} IME selection should contain the replace string`
+ );
+
+ // Update the replace string and remaining composition.
+ synthesizeSimpleCompositionChange("CDE");
+ is(
+ textNode.data,
+ "aCDEfg",
+ `${description} Composition should update the replace string too`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}a`.length,
+ "CDE",
+ `${description} IME selection should contain the replace string`
+ );
+
+ // Commit it
+ synthesizeComposition({ type: "compositioncommitasis" });
+ is(
+ textNode.data,
+ "aCDEfg",
+ `${
+ description
+ } Composition should be committed`
+ );
+ is(
+ selection.focusOffset,
+ "aCDE".length,
+ `${description} Selection should be collapsed at end of the commit string`
+ );
+ })();
+
+ // If start boundary characters are replaced, the replace string should NOT
+ // be contained in the composition range. This is Chrome compatible.
+ (function testReplaceEndBoundaryOfCompositionString() {
+ const description =
+ "runCompositionTestWhoseTextNodeModified: testReplaceEndBoundaryOfCompositionString:";
+ contenteditable.focus();
+ contenteditable.innerHTML = "<p>abfg</p>";
+ const textNode = contenteditable.firstChild.firstChild;
+ selection.collapse(textNode, "ab".length);
+ // Insert composition at middle of the text node
+ synthesizeSimpleCompositionChange("cde");
+ is(
+ textNode.data,
+ "abcdefg",
+ `${description} Composition should be inserted to the text node`
+ );
+
+ // Replace the composition string
+ textNode.replaceData("abcd".length, "ef".length, "XYZ");
+ is(
+ textNode.data,
+ "abcdXYZg",
+ `${
+ description
+ } End half of the composition should be replaced`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}ab`.length,
+ "cd",
+ `${
+ description
+ } IME selection should be shrunken to the non-replaced part`
+ );
+
+ // Update the composition string after replaced
+ synthesizeSimpleCompositionChange("CDE");
+ is(
+ textNode.data,
+ "abCDEXYZg",
+ `${description} Only the remaining composition string should be updated`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}ab`.length,
+ "CDE",
+ `${description} IME selection should NOT include the replace string`
+ );
+
+ // Commit it
+ synthesizeComposition({ type: "compositioncommitasis" });
+ is(
+ textNode.data,
+ "abCDEXYZg",
+ `${description} Composition should be committed`
+ );
+ is(
+ selection.focusOffset,
+ "abCDE".length,
+ `${description} Selection should be collapsed at end of the commit string`
+ );
+ })();
+
+ // If the last character of composition is replaced, i.e., it should NOT be
+ // treated as a part of composition string. This is Chrome compatible.
+ (function testReplaceLastCharOfCompositionString() {
+ const description =
+ "runCompositionTestWhoseTextNodeModified: testReplaceLastCharOfCompositionString:";
+ contenteditable.focus();
+ contenteditable.innerHTML = "<p>abfg</p>";
+ const textNode = contenteditable.firstChild.firstChild;
+ selection.collapse(textNode, "ab".length);
+ // Insert composition at middle of the text node
+ synthesizeSimpleCompositionChange("cde");
+ is(
+ textNode.data,
+ "abcdefg",
+ `${description} Composition should be inserted`
+ );
+
+ // Replace the composition string
+ textNode.replaceData("abcd".length, "e".length, "XYZ");
+ is(
+ textNode.data,
+ "abcdXYZfg",
+ `${description} Last character of the composition should be replaced`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}ab`.length,
+ "cd",
+ `${description} IME selection should be shrunken`
+ );
+
+ // Update the composition string after replaced
+ synthesizeSimpleCompositionChange("CDE");
+ is(
+ textNode.data,
+ "abCDEXYZfg",
+ `${description} Composition should NOT update the replace string`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}ab`.length,
+ "CDE",
+ `${description} IME selection should not contain the replace string`
+ );
+
+ // Commit it
+ synthesizeComposition({ type: "compositioncommitasis" });
+ is(
+ textNode.data,
+ "abCDEXYZfg",
+ `${description} Composition should be committed`
+ );
+ is(
+ selection.focusOffset,
+ "abCDE".length,
+ `${description} Selection should be collapsed at end of the commit string`
+ );
+ })();
+
+ (function testReplaceMiddleCharOfCompositionString() {
+ const description =
+ "runCompositionTestWhoseTextNodeModified: testReplaceMiddleCharOfCompositionString:";
+ contenteditable.focus();
+ contenteditable.innerHTML = "<p>abfg</p>";
+ const textNode = contenteditable.firstChild.firstChild;
+ selection.collapse(textNode, "ab".length);
+ // Insert composition at middle of the text node
+ synthesizeSimpleCompositionChange("cde");
+ is(
+ textNode.data,
+ "abcdefg",
+ `${description} Composition should be inserted`
+ );
+
+ // Replace the composition string
+ textNode.replaceData("abc".length, "d".length, "XYZ");
+ is(
+ textNode.data,
+ "abcXYZefg",
+ `${
+ description
+ } Middle character of the composition should be replaced`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}ab`.length,
+ "cXYZe",
+ `${description} IME selection should be extended by the replace string`
+ );
+
+ // Update the composition string after replaced
+ synthesizeSimpleCompositionChange("CDE");
+ is(
+ textNode.data,
+ "abCDEfg",
+ `${description} Composition should update the replace string`
+ );
+ checkIMESelection(
+ "RawClause",
+ true,
+ `${kLF}ab`.length,
+ "CDE",
+ `${description} IME selection should be shrunken after update`
+ );
+
+ // Commit it
+ synthesizeComposition({ type: "compositioncommitasis" });
+ is(
+ textNode.data,
+ "abCDEfg",
+ `${description} Composition should be committed`
+ );
+ is(
+ selection.focusOffset,
+ "abCDE".length,
+ `${description} Selection should be collapsed at end of the commit string`
+ );
+ })();
+}
+
+// eslint-disable-next-line complexity
+function runQueryTextRectInContentEditableTest()
+{
+ contenteditable.focus();
+
+ contenteditable.innerHTML = "<p>abc</p><p>def</p>";
+ // \n 0 123 4 567
+ // \r\n 01 234 56 789
+
+ let description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
+
+ // "a"
+ let a = synthesizeQueryTextRect(kLFLen, 1);
+ if (!checkQueryContentResult(a, description + "rect for 'a'")) {
+ return;
+ }
+
+ // "b"
+ let b = synthesizeQueryTextRect(kLFLen + 1, 1);
+ if (!checkQueryContentResult(b, description + "rect for 'b'")) {
+ return;
+ }
+
+ is(b.top, a.top, description + "'a' and 'b' should be at same top");
+ isSimilarTo(b.left, a.left + a.width, 2, description + "left of 'b' should be at similar to right of 'a'");
+ is(b.height, a.height, description + "'a' and 'b' should be same height");
+
+ // "c"
+ let c = synthesizeQueryTextRect(kLFLen + 2, 1);
+ if (!checkQueryContentResult(c, description + "rect for 'c'")) {
+ return;
+ }
+
+ is(c.top, b.top, description + "'b' and 'c' should be at same top");
+ isSimilarTo(c.left, b.left + b.width, 2, description + "left of 'c' should be at similar to right of 'b'");
+ is(c.height, b.height, description + "'b' and 'c' should be same height");
+
+ // "abc" as array
+ let abcAsArray = synthesizeQueryTextRectArray(kLFLen, 3);
+ if (!checkQueryContentResult(abcAsArray, description + "rect array for 'abc'") ||
+ !checkRectArray(abcAsArray, [a, b, c], description + "query text rect array result of 'abc' should match with each query text rect result")) {
+ return;
+ }
+
+ // 2nd <p> (can be computed with the rect of 'c')
+ let p2 = synthesizeQueryTextRect(kLFLen + 3, 1);
+ if (!checkQueryContentResult(p2, description + "rect for 2nd <p>")) {
+ return;
+ }
+
+ is(p2.top, c.top, description + "'c' and a line breaker caused by 2nd <p> should be at same top");
+ isSimilarTo(p2.left, c.left + c.width, 2, description + "left of a line breaker caused by 2nd <p> should be at similar to right of 'c'");
+ is(p2.height, c.height, description + "'c' and a line breaker caused by 2nd <p> should be same height");
+
+ // 2nd <p> as array
+ let p2AsArray = synthesizeQueryTextRectArray(kLFLen + 3, 1);
+ if (!checkQueryContentResult(p2AsArray, description + "2nd <p>'s line breaker as array") ||
+ !checkRectArray(p2AsArray, [p2], description + "query text rect array result of 2nd <p> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let p2_2 = synthesizeQueryTextRect(kLFLen + 4, 1);
+ if (!checkQueryContentResult(p2_2, description + "rect for \\n of \\r\\n caused by 2nd <p>")) {
+ return;
+ }
+
+ is(p2_2.top, p2.top, description + "'\\r' and '\\n' should be at same top");
+ is(p2_2.left, p2.left, description + "'\\r' and '\\n' should be at same top");
+ is(p2_2.height, p2.height, description + "'\\r' and '\\n' should be same height");
+ is(p2_2.width, p2.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let p2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 4, 1);
+ if (!checkQueryContentResult(p2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <p>") ||
+ !checkRectArray(p2_2AsArray, [p2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd <p> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // "d"
+ let d = synthesizeQueryTextRect(kLFLen * 2 + 3, 1);
+ if (!checkQueryContentResult(d, description + "rect for 'd'")) {
+ return;
+ }
+
+ isGreaterThan(d.top, a.top + a.height, description + "top of 'd' should be greater than bottom of 'a'");
+ is(d.left, a.left, description + "'a' and 'd' should be same at same left");
+ is(d.height, a.height, description + "'a' and 'd' should be same height");
+
+ // "e"
+ let e = synthesizeQueryTextRect(kLFLen * 2 + 4, 1);
+ if (!checkQueryContentResult(e, description + "rect for 'e'")) {
+ return;
+ }
+
+ is(e.top, d.top, description + "'d' and 'd' should be at same top");
+ isSimilarTo(e.left, d.left + d.width, 2, description + "left of 'e' should be at similar to right of 'd'");
+ is(e.height, d.height, description + "'d' and 'e' should be same height");
+
+ // "f"
+ let f = synthesizeQueryTextRect(kLFLen * 2 + 5, 1);
+ if (!checkQueryContentResult(f, description + "rect for 'f'")) {
+ return;
+ }
+
+ is(f.top, e.top, description + "'e' and 'f' should be at same top");
+ isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
+ is(f.height, e.height, description + "'e' and 'f' should be same height");
+
+ // "def" as array
+ let defAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 3, 3);
+ if (!checkQueryContentResult(defAsArray, description + "rect array for 'def'") ||
+ !checkRectArray(defAsArray, [d, e, f], description + "query text rect array result of 'def' should match with each query text rect result")) {
+ return;
+ }
+
+ // next of "f" (can be computed with rect of 'f')
+ let next_f = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(next_f, description + "rect for next of 'f'")) {
+ return;
+ }
+
+ is(next_f.top, d.top, 2, description + "'f' and next of 'f' should be at same top");
+ isSimilarTo(next_f.left, f.left + f.width, 2, description + "left of next of 'f' should be at similar to right of 'f'");
+ is(next_f.height, d.height, description + "'f' and next of 'f' should be same height");
+
+ // next of "f" as array
+ let next_fAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(next_fAsArray, description + "rect array for next of 'f'") ||
+ !checkRectArray(next_fAsArray, [next_f], description + "query text rect array result of next of 'f' should match with each query text rect result")) {
+ return;
+ }
+
+ // too big offset for the editor
+ let tooBigOffset = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
+ return;
+ }
+
+ is(tooBigOffset.top, next_f.top, description + "too big offset and next of 'f' should be at same top");
+ is(tooBigOffset.left, next_f.left, description + "too big offset and next of 'f' should be at same left");
+ is(tooBigOffset.height, next_f.height, description + "too big offset and next of 'f' should be same height");
+ is(tooBigOffset.width, next_f.width, description + "too big offset and next of 'f' should be same width");
+
+ // too big offset for the editors as array
+ let tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
+ !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
+ return;
+ }
+
+ contenteditable.innerHTML = "<p>abc</p><p>def</p><p><br></p>";
+ // \n 0 123 4 567 8 9
+ // \r\n 01 234 56 789 01 23
+
+ description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
+
+ // "f"
+ f = synthesizeQueryTextRect(kLFLen * 2 + 5, 1);
+ if (!checkQueryContentResult(f, description + "rect for 'f'")) {
+ return;
+ }
+
+ is(f.top, e.top, description + "'e' and 'f' should be at same top");
+ is(f.height, e.height, description + "'e' and 'f' should be same height");
+ isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
+
+ // 3rd <p> (can be computed with rect of 'f')
+ let p3 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(p3, description + "rect for 3rd <p>")) {
+ return;
+ }
+
+ is(p3.top, f.top, description + "'f' and a line breaker caused by 3rd <p> should be at same top");
+ is(p3.height, f.height, description + "'f' and a line breaker caused by 3rd <p> should be same height");
+ isSimilarTo(p3.left, f.left + f.width, 2, description + "left of a line breaker caused by 3rd <p> should be similar to right of 'f'");
+
+ // 3rd <p> as array
+ let p3AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(p3AsArray, description + "3rd <p>'s line breaker as array") ||
+ !checkRectArray(p3AsArray, [p3], description + "query text rect array result of 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let p3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(p3_2, description + "rect for \\n of \\r\\n caused by 3rd <p>")) {
+ return;
+ }
+
+ is(p3_2.top, p3.top, description + "'\\r' and '\\n' should be at same top");
+ is(p3_2.left, p3.left, description + "'\\r' and '\\n' should be at same top");
+ is(p3_2.height, p3.height, description + "'\\r' and '\\n' should be same height");
+ is(p3_2.width, p3.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let p3_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(p3_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <p>") ||
+ !checkRectArray(p3_2AsArray, [p3_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // <br> in 3rd <p>
+ let br = synthesizeQueryTextRect(kLFLen * 3 + 6, 1);
+ if (!checkQueryContentResult(br, description + "rect for <br> in 3rd <p>")) {
+ return;
+ }
+
+ isGreaterThan(br.top, d.top + d.height, description + "a line breaker caused by <br> in 3rd <p> should be greater than bottom of 'd'");
+ isSimilarTo(br.height, d.height, 2, description + "'d' and a line breaker caused by <br> in 3rd <p> should be similar height");
+ is(br.left, d.left, description + "left of a line breaker caused by <br> in 3rd <p> should be same left of 'd'");
+
+ // <br> in 3rd <p> as array
+ let brAsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
+ if (!checkQueryContentResult(brAsArray, description + "<br> in 3rd <p> as array") ||
+ !checkRectArray(brAsArray, [br], description + "query text rect array result of <br> in 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let br_2 = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
+ if (!checkQueryContentResult(br_2, description + "rect for \\n of \\r\\n caused by <br> in 3rd <p>")) {
+ return;
+ }
+
+ is(br_2.top, br.top, description + "'\\r' and '\\n' should be at same top");
+ is(br_2.left, br.left, description + "'\\r' and '\\n' should be at same top");
+ is(br_2.height, br.height, description + "'\\r' and '\\n' should be same height");
+ is(br_2.width, br.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let br_2AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 7, 1);
+ if (!checkQueryContentResult(br_2AsArray, description + "rect array for \\n of \\r\\n caused by <br> in 3rd <p>") ||
+ !checkRectArray(br_2AsArray, [br_2], description + "query text rect array result of \\n of \\r\\n caused by <br> in 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // next of <br> in 3rd <p>
+ let next_br = synthesizeQueryTextRect(kLFLen * 4 + 6, 1);
+ if (!checkQueryContentResult(next_br, description + "rect for next of <br> in 3rd <p>")) {
+ return;
+ }
+
+ is(next_br.top, br.top, description + "next of <br> and <br> should be at same top");
+ is(next_br.left, br.left, description + "next of <br> and <br> should be at same left");
+ is(next_br.height, br.height, description + "next of <br> and <br> should be same height");
+ is(next_br.width, br.width, description + "next of <br> and <br> should be same width");
+
+ // next of <br> in 3rd <p> as array
+ let next_brAsArray = synthesizeQueryTextRectArray(kLFLen * 4 + 6, 1);
+ if (!checkQueryContentResult(next_brAsArray, description + "rect array for next of <br> in 3rd <p>") ||
+ !checkRectArray(next_brAsArray, [next_br], description + "query text rect array result of next of <br> in 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+
+ // too big offset for the editor
+ tooBigOffset = synthesizeQueryTextRect(kLFLen * 4 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
+ return;
+ }
+
+ is(tooBigOffset.top, next_br.top, description + "too big offset and next of 3rd <p> should be at same top");
+ is(tooBigOffset.left, next_br.left, description + "too big offset and next of 3rd <p> should be at same left");
+ is(tooBigOffset.height, next_br.height, description + "too big offset and next of 3rd <p> should be same height");
+ is(tooBigOffset.width, next_br.width, description + "too big offset and next of 3rd <p> should be same width");
+
+ // too big offset for the editors as array
+ tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 4 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
+ !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
+ return;
+ }
+
+ contenteditable.innerHTML = "<p>abc</p><p>def</p><p></p>";
+ // \n 0 123 4 567 8
+ // \r\n 01 234 56 789 0
+
+ description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
+
+ // "f"
+ f = synthesizeQueryTextRect(kLFLen * 2 + 5, 1);
+ if (!checkQueryContentResult(f, description + "rect for 'f'")) {
+ return;
+ }
+
+ is(f.top, e.top, description + "'e' and 'f' should be at same top");
+ isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
+ is(f.height, e.height, description + "'e' and 'f' should be same height");
+
+ // 3rd <p> (can be computed with rect of 'f')
+ p3 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(p3, description + "rect for 3rd <p>")) {
+ return;
+ }
+
+ is(p3.top, f.top, description + "'f' and a line breaker caused by 3rd <p> should be at same top");
+ is(p3.height, f.height, description + "'f' and a line breaker caused by 3rd <p> should be same height");
+ isSimilarTo(p3.left, f.left + f.width, 2, description + "left of a line breaker caused by 3rd <p> should be similar to right of 'f'");
+
+ // 3rd <p> as array
+ p3AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(p3AsArray, description + "3rd <p>'s line breaker as array") ||
+ !checkRectArray(p3AsArray, [p3], description + "query text rect array result of 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let p3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(p3_2, description + "rect for \\n of \\r\\n caused by 3rd <p>")) {
+ return;
+ }
+
+ is(p3_2.top, p3.top, description + "'\\r' and '\\n' should be at same top");
+ is(p3_2.left, p3.left, description + "'\\r' and '\\n' should be at same top");
+ is(p3_2.height, p3.height, description + "'\\r' and '\\n' should be same height");
+ is(p3_2.width, p3.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let p3_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(p3_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <p>") ||
+ !checkRectArray(p3_2AsArray, [p3_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // next of 3rd <p>
+ let next_p3 = synthesizeQueryTextRect(kLFLen * 3 + 6, 1);
+ if (!checkQueryContentResult(next_p3, description + "rect for next of 3rd <p>")) {
+ return;
+ }
+
+ isGreaterThan(next_p3.top, d.top + d.height, description + "top of next of 3rd <p> should equal to or be bigger than bottom of 'd'");
+ isSimilarTo(next_p3.left, d.left, 2, description + "left of next of 3rd <p> should be at similar to left of 'd'");
+ isSimilarTo(next_p3.height, d.height, 2, description + "next of 3rd <p> and 'd' should be similar height");
+
+ // next of 3rd <p> as array
+ let next_p3AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
+ if (!checkQueryContentResult(next_p3AsArray, description + "next of 3rd <p> as array") ||
+ !checkRectArray(next_p3AsArray, [next_p3], description + "query text rect array result of next of 3rd <p> should match with each query text rect result")) {
+ return;
+ }
+
+ // too big offset for the editor
+ tooBigOffset = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
+ return;
+ }
+
+ is(tooBigOffset.top, next_p3.top, description + "too big offset and next of 3rd <p> should be at same top");
+ is(tooBigOffset.left, next_p3.left, description + "too big offset and next of 3rd <p> should be at same left");
+ is(tooBigOffset.height, next_p3.height, description + "too big offset and next of 3rd <p> should be same height");
+ is(tooBigOffset.width, next_p3.width, description + "too big offset and next of 3rd <p> should be same width");
+
+ // too big offset for the editors as array
+ tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
+ !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
+ return;
+ }
+
+ contenteditable.innerHTML = "abc<br>def";
+ // \n 0123 456
+ // \r\n 01234 567
+
+ description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
+
+ // "a"
+ a = synthesizeQueryTextRect(0, 1);
+ if (!checkQueryContentResult(a, description + "rect for 'a'")) {
+ return;
+ }
+
+ // "b"
+ b = synthesizeQueryTextRect(1, 1);
+ if (!checkQueryContentResult(b, description + "rect for 'b'")) {
+ return;
+ }
+
+ is(b.top, a.top, description + "'a' and 'b' should be at same top");
+ isSimilarTo(b.left, a.left + a.width, 2, description + "left of 'b' should be at similar to right of 'a'");
+ is(b.height, a.height, description + "'a' and 'b' should be same height");
+
+ // "c"
+ c = synthesizeQueryTextRect(2, 1);
+ if (!checkQueryContentResult(c, description + "rect for 'c'")) {
+ return;
+ }
+
+ is(c.top, b.top, description + "'b' and 'c' should be at same top");
+ isSimilarTo(c.left, b.left + b.width, 2, description + "left of 'c' should be at similar to right of 'b'");
+ is(c.height, b.height, description + "'b' and 'c' should be same height");
+
+ // "abc" as array
+ abcAsArray = synthesizeQueryTextRectArray(0, 3);
+ if (!checkQueryContentResult(abcAsArray, description + "rect array for 'abc'") ||
+ !checkRectArray(abcAsArray, [a, b, c], description + "query text rect array result of 'abc' should match with each query text rect result")) {
+ return;
+ }
+
+ // <br> (can be computed with the rect of 'c')
+ br = synthesizeQueryTextRect(3, 1);
+ if (!checkQueryContentResult(br, description + "rect for <br>")) {
+ return;
+ }
+
+ is(br.top, c.top, description + "'c' and a line breaker caused by <br> should be at same top");
+ isSimilarTo(br.left, c.left + c.width, 2, description + "left of a line breaker caused by <br> should be at similar to right of 'c'");
+ is(br.height, c.height, description + "'c' and a line breaker caused by <br> should be same height");
+
+ // <br> as array
+ brAsArray = synthesizeQueryTextRectArray(3, 1);
+ if (!checkQueryContentResult(brAsArray, description + "<br>'s line breaker as array") ||
+ !checkRectArray(brAsArray, [br], description + "query text rect array result of <br> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let br_2 = synthesizeQueryTextRect(4, 1);
+ if (!checkQueryContentResult(br_2, description + "rect for \n of \r\n caused by <br>")) {
+ return;
+ }
+
+ is(br_2.top, br.top, description + "'\\r' and '\\n' should be at same top");
+ is(br_2.left, br.left, description + "'\\r' and '\\n' should be at same top");
+ is(br_2.height, br.height, description + "'\\r' and '\\n' should be same height");
+ is(br_2.width, br.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let br_2AsArray = synthesizeQueryTextRectArray(4, 1);
+ if (!checkQueryContentResult(br_2AsArray, description + "rect array for \\n of \\r\\n caused by <br>") ||
+ !checkRectArray(br_2AsArray, [br_2], description + "query text rect array result of \\n of \\r\\n caused by <br> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // "d"
+ d = synthesizeQueryTextRect(kLFLen + 3, 1);
+ if (!checkQueryContentResult(d, description + "rect for 'd'")) {
+ return;
+ }
+
+ isSimilarTo(d.top, a.top + a.height, 2, description + "top of 'd' should be at similar to bottom of 'a'");
+ is(d.left, a.left, description + "'a' and 'd' should be same at same left");
+ is(d.height, a.height, description + "'a' and 'd' should be same height");
+
+ // "e"
+ e = synthesizeQueryTextRect(kLFLen + 4, 1);
+ if (!checkQueryContentResult(e, description + "rect for 'e'")) {
+ return;
+ }
+
+ is(e.top, d.top, description + "'d' and 'd' should be at same top");
+ isSimilarTo(e.left, d.left + d.width, 2, description + "left of 'e' should be at similar to right of 'd'");
+ is(e.height, d.height, description + "'d' and 'e' should be same height");
+
+ // "f"
+ f = synthesizeQueryTextRect(kLFLen + 5, 1);
+ if (!checkQueryContentResult(f, description + "rect for 'f'")) {
+ return;
+ }
+
+ is(f.top, e.top, description + "'e' and 'f' should be at same top");
+ isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
+ is(f.height, e.height, description + "'e' and 'f' should be same height");
+
+ // "def" as array
+ defAsArray = synthesizeQueryTextRectArray(kLFLen + 3, 3);
+ if (!checkQueryContentResult(defAsArray, description + "rect array for 'def'") ||
+ !checkRectArray(defAsArray, [d, e, f], description + "query text rect array result of 'def' should match with each query text rect result")) {
+ return;
+ }
+
+ // next of "f" (can be computed with rect of 'f')
+ next_f = synthesizeQueryTextRect(kLFLen + 6, 1);
+ if (!checkQueryContentResult(next_f, description + "rect for next of 'f'")) {
+ return;
+ }
+
+ is(next_f.top, d.top, 2, description + "'f' and next of 'f' should be at same top");
+ isSimilarTo(next_f.left, f.left + f.width, 2, description + "left of next of 'f' should be at similar to right of 'f'");
+ is(next_f.height, d.height, description + "'f' and next of 'f' should be same height");
+
+ // next of "f" as array
+ next_fAsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1);
+ if (!checkQueryContentResult(next_fAsArray, description + "rect array for next of 'f'") ||
+ !checkRectArray(next_fAsArray, [next_f], description + "query text rect array result of next of 'f' should match with each query text rect result")) {
+ return;
+ }
+
+ // too big offset for the editor
+ tooBigOffset = synthesizeQueryTextRect(kLFLen + 7, 1);
+ if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
+ return;
+ }
+
+ is(tooBigOffset.top, next_f.top, description + "too big offset and next of 'f' should be at same top");
+ is(tooBigOffset.left, next_f.left, description + "too big offset and next of 'f' should be at same left");
+ is(tooBigOffset.height, next_f.height, description + "too big offset and next of 'f' should be same height");
+ is(tooBigOffset.width, next_f.width, description + "too big offset and next of 'f' should be same width");
+
+ // too big offset for the editors as array
+ tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen + 7, 1);
+ if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
+ !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
+ return;
+ }
+
+ // Note that this case does not have an empty line at the end.
+ contenteditable.innerHTML = "abc<br>def<br>";
+ // \n 0123 4567
+ // \r\n 01234 56789
+
+ description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
+
+ // "f"
+ f = synthesizeQueryTextRect(kLFLen + 5, 1);
+ if (!checkQueryContentResult(f, description + "rect for 'f'")) {
+ return;
+ }
+
+ is(f.top, e.top, description + "'e' and 'f' should be at same top");
+ is(f.height, e.height, description + "'e' and 'f' should be same height");
+ isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
+
+ // 2nd <br> (can be computed with rect of 'f')
+ let br2 = synthesizeQueryTextRect(kLFLen + 6, 1);
+ if (!checkQueryContentResult(br2, description + "rect for 2nd <br>")) {
+ return;
+ }
+
+ is(br2.top, f.top, description + "'f' and a line breaker caused by 2nd <br> should be at same top");
+ is(br2.height, f.height, description + "'f' and a line breaker caused by 2nd <br> should be same height");
+ isSimilarTo(br2.left, f.left + f.width, 2, description + "left of a line breaker caused by 2nd <br> should be similar to right of 'f'");
+
+ // 2nd <br> as array
+ let br2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(br2AsArray, description + "2nd <br>'s line breaker as array") ||
+ !checkRectArray(br2AsArray, [br2], description + "query text rect array result of 2nd <br> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let br2_2 = synthesizeQueryTextRect(kLFLen + 7, 1);
+ if (!checkQueryContentResult(br2_2, description + "rect for \\n of \\r\\n caused by 2nd <br>")) {
+ return;
+ }
+
+ is(br2_2.top, br2.top, description + "'\\r' and '\\n' should be at same top");
+ is(br2_2.left, br2.left, description + "'\\r' and '\\n' should be at same top");
+ is(br2_2.height, br2.height, description + "'\\r' and '\\n' should be same height");
+ is(br2_2.width, br2.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let br2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 7, 1);
+ if (!checkQueryContentResult(br2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <br>") ||
+ !checkRectArray(br2_2AsArray, [br2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd <br> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // next of 2nd <br>
+ let next_br2 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(next_br2, description + "rect for next of 2nd <br>")) {
+ return;
+ }
+
+ is(next_br2.top, br2.top, description + "2nd <br> and next of 2nd <br> should be at same top");
+ is(next_br2.left, br2.left, description + "2nd <br> and next of 2nd <br> should be at same top");
+ is(next_br2.height, br2.height, description + "2nd <br> and next of 2nd <br> should be same height");
+ is(next_br2.width, br2.width, description + "2nd <br> and next of 2nd <br> should be same width");
+
+ // next of 2nd <br> as array
+ let next_br2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
+ if (!checkQueryContentResult(next_br2AsArray, description + "rect array for next of 2nd <br>") ||
+ !checkRectArray(next_br2AsArray, [next_br2], description + "query text rect array result of next of 2nd <br> should match with each query text rect result")) {
+ return;
+ }
+
+ // too big offset for the editor
+ tooBigOffset = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
+ return;
+ }
+
+ is(tooBigOffset.top, next_br2.top, description + "too big offset and next of 2nd <br> should be at same top");
+ is(tooBigOffset.left, next_br2.left, description + "too big offset and next of 2nd <br> should be at same left");
+ is(tooBigOffset.height, next_br2.height, description + "too big offset and next of 2nd <br> should be same height");
+ is(tooBigOffset.width, next_br2.width, description + "too big offset and next of 2nd <br> should be same width");
+
+ // too big offset for the editors as array
+ tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
+ !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
+ return;
+ }
+
+ contenteditable.innerHTML = "abc<br>def<br><br>";
+ // \n 0123 4567 8
+ // \r\n 01234 56789 01
+
+ description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
+
+ // "f"
+ f = synthesizeQueryTextRect(kLFLen + 5, 1);
+ if (!checkQueryContentResult(f, description + "rect for 'f'")) {
+ return;
+ }
+
+ is(f.top, e.top, description + "'e' and 'f' should be at same top");
+ isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
+ is(f.height, e.height, description + "'e' and 'f' should be same height");
+
+ // 2nd <br>
+ br2 = synthesizeQueryTextRect(kLFLen + 6, 1);
+ if (!checkQueryContentResult(br2, description + "rect for 2nd <br>")) {
+ return;
+ }
+
+ is(br2.top, f.top, description + "'f' and a line breaker caused by 2nd <br> should be at same top");
+ is(br2.height, f.height, description + "'f' and a line breaker caused by 2nd <br> should be same height");
+ ok(f.left < br2.left, description + "left of a line breaker caused by 2nd <br> should be bigger than left of 'f', f.left=" + f.left + ", br2.left=" + br2.left);
+
+ // 2nd <br> as array
+ br2AsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1);
+ if (!checkQueryContentResult(br2AsArray, description + "2nd <br>'s line breaker as array") ||
+ !checkRectArray(br2AsArray, [br2], description + "query text rect array result of 2nd <br> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let br2_2 = synthesizeQueryTextRect(kLFLen + 7, 1);
+ if (!checkQueryContentResult(br2_2, description + "rect for \\n of \\r\\n caused by 2nd <br>")) {
+ return;
+ }
+
+ is(br2_2.top, br2.top, description + "'\\r' and '\\n' should be at same top");
+ is(br2_2.left, br2.left, description + "'\\r' and '\\n' should be at same top");
+ is(br2_2.height, br2.height, description + "'\\r' and '\\n' should be same height");
+ is(br2_2.width, br2.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let br2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 7, 1);
+ if (!checkQueryContentResult(br2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <br>") ||
+ !checkRectArray(br2_2AsArray, [br2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd <br> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // 3rd <br>
+ let br3 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(br3, description + "rect for next of 3rd <br>")) {
+ return;
+ }
+
+ isSimilarTo(br3.top, d.top + d.height, 3, description + "top of next of 3rd <br> should at similar to bottom of 'd'");
+ isSimilarTo(br3.left, d.left, 2, description + "left of next of 3rd <br> should be at similar to left of 'd'");
+ isSimilarTo(br3.height, d.height, 2, description + "next of 3rd <br> and 'd' should be similar height");
+
+ // 3rd <br> as array
+ let br3AsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1);
+ if (!checkQueryContentResult(br3AsArray, description + "3rd <br>'s line breaker as array") ||
+ !checkRectArray(br3AsArray, [br3], description + "query text rect array result of 3rd <br> should match with each query text rect result")) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ // \n of \r\n
+ let br3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(br3_2, description + "rect for \\n of \\r\\n caused by 3rd <br>")) {
+ return;
+ }
+
+ is(br3_2.top, br3.top, description + "'\\r' and '\\n' should be at same top");
+ is(br3_2.left, br3.left, description + "'\\r' and '\\n' should be at same left");
+ is(br3_2.height, br3.height, description + "'\\r' and '\\n' should be same height");
+ is(br3_2.width, br3.width, description + "'\\r' and '\\n' should be same width");
+
+ // \n of \r\n as array
+ let br3_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(br3_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <br>") ||
+ !checkRectArray(br3_2AsArray, [br3_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd <br> should match with each query text rect result")) {
+ return;
+ }
+ }
+
+ // next of 3rd <br>
+ let next_br3 = synthesizeQueryTextRect(kLFLen * 3 + 6, 1);
+ if (!checkQueryContentResult(next_br3, description + "rect for next of 3rd <br>")) {
+ return;
+ }
+
+ is(next_br3.top, br3.top, description + "3rd <br> and next of 3rd <br> should be at same top");
+ is(next_br3.left, br3.left, description + "3rd <br> and next of 3rd <br> should be at same left");
+ is(next_br3.height, br3.height, description + "3rd <br> and next of 3rd <br> should be same height");
+ is(next_br3.width, br3.width, description + "3rd <br> and next of 3rd <br> should be same width");
+
+ // next of 3rd <br> as array
+ let next_br3AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
+ if (!checkQueryContentResult(next_br3AsArray, description + "rect array for next of 3rd <br>") ||
+ !checkRectArray(next_br3AsArray, [next_br3], description + "query text rect array result of next of 3rd <br> should match with each query text rect result")) {
+ return;
+ }
+
+ // too big offset for the editor
+ tooBigOffset = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
+ if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
+ return;
+ }
+
+ is(tooBigOffset.top, next_br3.top, description + "too big offset and next of 3rd <br> should be at same top");
+ is(tooBigOffset.left, next_br3.left, description + "too big offset and next of 3rd <br> should be at same left");
+ is(tooBigOffset.height, next_br3.height, description + "too big offset and next of 3rd <br> should be same height");
+ is(tooBigOffset.width, next_br3.width, description + "too big offset and next of 3rd <br> should be same width");
+
+ // too big offset for the editors as array
+ tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
+ if (checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset")) {
+ checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result");
+ }
+
+ if (!(function test_query_text_rects_across_invisible_text() {
+ contenteditable.innerHTML = "<div>\n<div>abc</div></div>";
+ // \n 0 1 2 345
+ // \r\n 01 2345 678
+ description = `runQueryTextRectInContentEditableTest: test_query_text_rects_across_invisible_text: "${
+ contenteditable.innerHTML.replace(/\n/g, "\\n")
+ }",`;
+ // rect of "a"
+ const rectA = synthesizeQueryTextRect(kLFLen * 3, 1);
+ if (!checkQueryContentResult(rectA, `${description} rect of "a"`)) {
+ return false;
+ }
+ const rectArrayFromStartToA = synthesizeQueryTextRectArray(0, kLFLen * 3 + 1);
+ if (!checkQueryContentResult(rectArrayFromStartToA, `${description} rect array from invisible text to "a"`)) {
+ return false;
+ }
+ const fromStartToARects = getRectArray(rectArrayFromStartToA);
+ if (!checkRect(
+ fromStartToARects[kLFLen * 3],
+ rectA,
+ `${description} rect for "a" in array should be same as the result of query only it`
+ )) {
+ return false;
+ }
+ return checkRect(
+ fromStartToARects[kLFLen * 2],
+ fromStartToARects[0],
+ `${description} rect for the linebreak in invisible text node should be same as first linebreak`
+ );
+ })()) {
+ return;
+ }
+
+ function test_query_text_rects_starting_from_invisible_text() {
+ contenteditable.innerHTML = "<div>\n<div>abc</div></div>";
+ // \n 0 1 2 345
+ // \r\n 01 2345 678
+ description = `runQueryTextRectInContentEditableTest: test_query_text_rects_starting_from_invisible_text: "${
+ contenteditable.innerHTML.replace(/\n/g, "\\n")
+ }",`;
+ // rect of "a"
+ const rectA = synthesizeQueryTextRect(kLFLen * 3, 1);
+ if (!checkQueryContentResult(rectA, `${description} rect of "a"`)) {
+ return false;
+ }
+ const rectArrayFromInvisibleToA = synthesizeQueryTextRectArray(kLFLen, kLFLen * 2 + 1);
+ if (!checkQueryContentResult(rectArrayFromInvisibleToA, `${description} rect array from invisible text to "a"`)) {
+ return false;
+ }
+ const fromInvisibleToARects = getRectArray(rectArrayFromInvisibleToA);
+ if (!checkRect(
+ fromInvisibleToARects[kLFLen * 2],
+ rectA,
+ `${description} rect for "a" in array should be same as the result of query only it`
+ )) {
+ return false;
+ }
+ // For now the rect of characters in invisible text node should be caret rect
+ // before the following line break. This is inconsistent from the result of
+ // the query text rect array event starting from the previous visible line
+ // break or character, but users anyway cannot insert text into the invisible
+ // text node only with user's operation. Therefore, this won't be problem
+ // in usual web apps.
+ const caretRectBeforeLineBreakBeforeA = fromInvisibleToARects[kLFLen];
+ return checkRect(
+ fromInvisibleToARects[0],
+ caretRectBeforeLineBreakBeforeA,
+ `${description} rect for the linebreak in invisible text node should be same as caret rect before the following visible linebreak before "a"`
+ );
+ }
+ if (!test_query_text_rects_starting_from_invisible_text()) {
+ return;
+ }
+
+ if (kLFLen > 1) {
+ function test_query_text_rects_starting_from_middle_of_invisible_linebreak() {
+ contenteditable.innerHTML = "<div>\n<div>abc</div></div>";
+ // \n 0 1 2 345
+ // \r\n 01 2345 678
+ description = `runQueryTextRectInContentEditableTest: test_query_text_rects_starting_from_middle_of_invisible_linebreak: "${
+ contenteditable.innerHTML.replace(/\n/g, "\\n")
+ }",`;
+ // rect of "a"
+ const rectA = synthesizeQueryTextRect(kLFLen * 3, 1);
+ if (!checkQueryContentResult(rectA, `${description} rect of "a"`)) {
+ return false;
+ }
+ const rectArrayFromInvisibleToA = synthesizeQueryTextRectArray(kLFLen + 1, 1 + kLFLen + 1);
+ if (!checkQueryContentResult(rectArrayFromInvisibleToA, `${description} rect array from invisible text to "a"`)) {
+ return false;
+ }
+ const fromInvisibleToARects = getRectArray(rectArrayFromInvisibleToA);
+ if (!checkRect(
+ fromInvisibleToARects[1 + kLFLen],
+ rectA,
+ `${description} rect for "a" in array should be same as the result of query only it`
+ )) {
+ return false;
+ }
+ // For now the rect of characters in invisible text node should be caret rect
+ // before the following line break. This is inconsistent from the result of
+ // the query text rect array event starting from the previous visible line
+ // break or character, but users anyway cannot insert text into the invisible
+ // text node only with user's operation. Therefore, this won't be problem
+ // in usual web apps.
+ const caretRectBeforeLineBreakBeforeA = fromInvisibleToARects[1];
+ return checkRect(
+ fromInvisibleToARects[0],
+ caretRectBeforeLineBreakBeforeA,
+ `${description} rect for the linebreak in invisible text node should be same as caret rect before the following visible linebreak before "a"`
+ );
+ }
+ if (!test_query_text_rects_starting_from_middle_of_invisible_linebreak()) {
+ return;
+ }
+ }
+
+ function test_query_text_rects_ending_with_invisible_text() {
+ contenteditable.innerHTML = "<div><div>abc</div>\n</div>";
+ // \n 0 1 234 5
+ // \r\n 01 23 456 78
+ description = `runQueryTextRectInContentEditableTest: test_query_text_rects_ending_with_invisible_text: "${
+ contenteditable.innerHTML.replace(/\n/g, "\\n")
+ }",`;
+ // rect of "c"
+ const rectC = synthesizeQueryTextRect(kLFLen * 2 + 2, 1);
+ if (!checkQueryContentResult(rectC, `${description} rect of "c"`)) {
+ return false;
+ }
+ const rectArrayFromCToInvisible = synthesizeQueryTextRectArray(kLFLen * 2 + 2, 1 + kLFLen);
+ if (!checkQueryContentResult(rectArrayFromCToInvisible, `${description} rect array from "c" to invisible linebreak`)) {
+ return false;
+ }
+ const fromCToInvisibleRects = getRectArray(rectArrayFromCToInvisible);
+ if (!checkRect(
+ fromCToInvisibleRects[0],
+ rectC,
+ `${description} rect for "c" in array should be same as the result of query only it`
+ )) {
+ return false;
+ }
+ const caretRectAfterC = {
+ left: rectC.left + rectC.width,
+ top: rectC.top,
+ width: 1,
+ height: rectC.height,
+ };
+ return checkRectFuzzy(
+ fromCToInvisibleRects[1],
+ caretRectAfterC,
+ {
+ left: 1,
+ top: 0,
+ width: 0,
+ height: 0,
+ },
+ `${description} rect for the linebreak in invisible text node should be same as caret rect after "c"`
+ );
+ }
+ if (!test_query_text_rects_ending_with_invisible_text()) {
+ // eslint-disable-next-line no-useless-return
+ return;
+ }
+}
+
+function runCharAtPointTest(aFocusedEditor, aTargetName)
+{
+ aFocusedEditor.value = "This is a test of the\nContent Events";
+ // 012345678901234567890 12345678901234
+ // 0 1 2 3
+
+ aFocusedEditor.focus();
+
+ const kNone = -1;
+ const kTestingOffset = [ 0, 10, 20, 21 + kLFLen, 34 + kLFLen];
+ const kLeftSideOffset = [ kNone, 9, 19, kNone, 33 + kLFLen];
+ const kRightSideOffset = [ 1, 11, kNone, 22 + kLFLen, kNone];
+ const kLeftTentativeCaretOffset = [ 0, 10, 20, 21 + kLFLen, 34 + kLFLen];
+ const kRightTentativeCaretOffset = [ 1, 11, 21, 22 + kLFLen, 35 + kLFLen];
+
+ let editorRect = synthesizeQueryEditorRect();
+ if (!checkQueryContentResult(editorRect,
+ "runCharAtPointTest (" + aTargetName + "): editorRect")) {
+ return;
+ }
+
+ for (let i = 0; i < kTestingOffset.length; i++) {
+ let textRect = synthesizeQueryTextRect(kTestingOffset[i], 1);
+ if (!checkQueryContentResult(textRect,
+ "runCharAtPointTest (" + aTargetName + "): textRect: i=" + i)) {
+ continue;
+ }
+
+ checkRectContainsRect(textRect, editorRect,
+ "runCharAtPointTest (" + aTargetName +
+ "): the text rect isn't in the editor");
+
+ // Test #1, getting same character rect by the point near the top-left.
+ let charAtPt1 = synthesizeCharAtPoint(textRect.left + 1,
+ textRect.top + 1);
+ if (checkQueryContentResult(charAtPt1,
+ "runCharAtPointTest (" + aTargetName + "): charAtPt1: i=" + i)) {
+ ok(!charAtPt1.notFound,
+ "runCharAtPointTest (" + aTargetName + "): charAtPt1 isn't found: i=" + i);
+ if (!charAtPt1.notFound) {
+ is(charAtPt1.offset, kTestingOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): charAtPt1 offset is wrong: i=" + i);
+ checkRect(charAtPt1, textRect, "runCharAtPointTest (" + aTargetName +
+ "): charAtPt1 left is wrong: i=" + i);
+ }
+ ok(!charAtPt1.tentativeCaretOffsetNotFound,
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt1 isn't found: i=" + i);
+ if (!charAtPt1.tentativeCaretOffsetNotFound) {
+ is(charAtPt1.tentativeCaretOffset, kLeftTentativeCaretOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt1 is wrong: i=" + i);
+ }
+ }
+
+ // Test #2, getting same character rect by the point near the bottom-right.
+ let charAtPt2 = synthesizeCharAtPoint(textRect.left + textRect.width - 2,
+ textRect.top + textRect.height - 2);
+ if (checkQueryContentResult(charAtPt2,
+ "runCharAtPointTest (" + aTargetName + "): charAtPt2: i=" + i)) {
+ ok(!charAtPt2.notFound,
+ "runCharAtPointTest (" + aTargetName + "): charAtPt2 isn't found: i=" + i);
+ if (!charAtPt2.notFound) {
+ is(charAtPt2.offset, kTestingOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): charAtPt2 offset is wrong: i=" + i);
+ checkRect(charAtPt2, textRect, "runCharAtPointTest (" + aTargetName +
+ "): charAtPt1 left is wrong: i=" + i);
+ }
+ ok(!charAtPt2.tentativeCaretOffsetNotFound,
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt2 isn't found: i=" + i);
+ if (!charAtPt2.tentativeCaretOffsetNotFound) {
+ is(charAtPt2.tentativeCaretOffset, kRightTentativeCaretOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt2 is wrong: i=" + i);
+ }
+ }
+
+ // Test #3, getting left character offset.
+ let charAtPt3 = synthesizeCharAtPoint(textRect.left - 2,
+ textRect.top + 1);
+ if (checkQueryContentResult(charAtPt3,
+ "runCharAtPointTest (" + aTargetName + "): charAtPt3: i=" + i)) {
+ is(charAtPt3.notFound, kLeftSideOffset[i] == kNone,
+ kLeftSideOffset[i] == kNone ?
+ "runCharAtPointTest (" + aTargetName + "): charAtPt3 is found: i=" + i :
+ "runCharAtPointTest (" + aTargetName + "): charAtPt3 isn't found: i=" + i);
+ if (!charAtPt3.notFound) {
+ is(charAtPt3.offset, kLeftSideOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): charAtPt3 offset is wrong: i=" + i);
+ }
+ if (kLeftSideOffset[i] == kNone) {
+ // There may be no enough padding-left (depends on platform)
+ todo(false,
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 isn't tested: i=" + i);
+ } else {
+ ok(!charAtPt3.tentativeCaretOffsetNotFound,
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 isn't found: i=" + i);
+ if (!charAtPt3.tentativeCaretOffsetNotFound) {
+ is(charAtPt3.tentativeCaretOffset, kLeftTentativeCaretOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 is wrong: i=" + i);
+ }
+ }
+ }
+
+ // Test #4, getting right character offset.
+ let charAtPt4 = synthesizeCharAtPoint(textRect.left + textRect.width + 1,
+ textRect.top + textRect.height - 2);
+ if (checkQueryContentResult(charAtPt4,
+ "runCharAtPointTest (" + aTargetName + "): charAtPt4: i=" + i)) {
+ is(charAtPt4.notFound, kRightSideOffset[i] == kNone,
+ kRightSideOffset[i] == kNone ?
+ "runCharAtPointTest (" + aTargetName + "): charAtPt4 is found: i=" + i :
+ "runCharAtPointTest (" + aTargetName + "): charAtPt4 isn't found: i=" + i);
+ if (!charAtPt4.notFound) {
+ is(charAtPt4.offset, kRightSideOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): charAtPt4 offset is wrong: i=" + i);
+ }
+ ok(!charAtPt4.tentativeCaretOffsetNotFound,
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt4 isn't found: i=" + i);
+ if (!charAtPt4.tentativeCaretOffsetNotFound) {
+ is(charAtPt4.tentativeCaretOffset, kRightTentativeCaretOffset[i],
+ "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt4 is wrong: i=" + i);
+ }
+ }
+ }
+}
+
+function runCharAtPointAtOutsideTest()
+{
+ textarea.focus();
+ textarea.value = "some text";
+ let editorRect = synthesizeQueryEditorRect();
+ if (!checkQueryContentResult(editorRect,
+ "runCharAtPointAtOutsideTest: editorRect")) {
+ return;
+ }
+ // Check on a text node which is at the outside of editor.
+ let charAtPt = synthesizeCharAtPoint(editorRect.left + 20,
+ editorRect.top - 10);
+ if (checkQueryContentResult(charAtPt,
+ "runCharAtPointAtOutsideTest: charAtPt")) {
+ ok(charAtPt.notFound,
+ "runCharAtPointAtOutsideTest: charAtPt is found on outside of editor");
+ ok(charAtPt.tentativeCaretOffsetNotFound,
+ "runCharAtPointAtOutsideTest: tentative caret offset for charAtPt is found on outside of editor");
+ }
+}
+
+async function runSetSelectionEventTest()
+{
+ contenteditable.focus();
+
+ const selection = windowOfContenteditable.getSelection();
+
+ // #1
+ contenteditable.innerHTML = "abc<br>def";
+
+ await synthesizeSelectionSet(0, 100);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node of the editor");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of children");
+ checkSelection(0, "abc" + kLF + "def", "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(2, 2 + kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, 2,
+ "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 2");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(2, "c" + kLF + "d", "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(1, 2);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node");
+ is(selection.focusOffset, contenteditable.firstChild.wholeText.length,
+ "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node");
+ checkSelection(1, "bc", "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(3, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, contenteditable.firstChild.wholeText.length,
+ "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the first text node");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 2,
+ "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the index of the last text node");
+ checkSelection(3, kLF, "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(6+kLFLen, 0);
+ is(selection.anchorNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, contenteditable.lastChild.wholeText.length,
+ "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.anchorOffset, contenteditable.lastChild.wholeText.length,
+ "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(6 + kLFLen, "", "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(100, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node of the editor");
+ is(selection.anchorOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of children");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node of the editor");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of children");
+ checkSelection(6 + kLFLen, "", "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #2
+ contenteditable.innerHTML = "<p>a<b>b</b>c</p><p>def</p>";
+
+ await synthesizeSelectionSet(kLFLen, 4+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <p> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first <p> node");
+ is(selection.focusNode, contenteditable.lastChild.firstChild,
+ "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the second <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(kLFLen, "abc" + kLF + "d", "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen, 2);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <p> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first <p> node");
+ is(selection.focusNode, contenteditable.firstChild.childNodes.item(1).firstChild,
+ "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <b> node");
+ is(selection.focusOffset, contenteditable.firstChild.childNodes.item(1).firstChild.wholeText.length,
+ "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node in the <b> node");
+ checkSelection(kLFLen, "ab", "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(1+kLFLen, 2);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node in the first <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node in the first <p> node");
+ checkSelection(1+kLFLen, "bc", "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(2+kLFLen, 2+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.childNodes.item(1).firstChild,
+ "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <b> node");
+ is(selection.anchorOffset, contenteditable.firstChild.childNodes.item(1).firstChild.wholeText.length,
+ "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node in the <b> node");
+ is(selection.focusNode, contenteditable.lastChild.firstChild,
+ "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the last <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(2+kLFLen, "c" + kLF + "d", "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(3+kLFLen*2, 1);
+ is(selection.anchorNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the second <p> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the second <p> node");
+ is(selection.focusNode, contenteditable.lastChild.firstChild,
+ "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the second <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(3+kLFLen*2, "d", "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first <p> node");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the first <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, kLF, "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(2+kLFLen, 1+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.childNodes.item(1).firstChild,
+ "runSetSelectionEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node of the <b> node");
+ is(selection.anchorOffset, contenteditable.firstChild.childNodes.item(1).firstChild.wholeText.length,
+ "runSetSelectionEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node of the first <b> node");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(2+kLFLen, "c" + kLF, "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(3+kLFLen, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node of the first <p> node");
+ is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node of the first <p> node");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(3+kLFLen, kLF, "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(3+kLFLen, 1+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node of the first <p> node");
+ is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node of the first <p> node");
+ is(selection.focusNode, contenteditable.lastChild.firstChild,
+ "runSetSelectionEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node of the second <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(3+kLFLen, kLF + "d", "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ // #3
+ contenteditable.innerHTML = "<div>abc<p>def</p></div>";
+
+ await synthesizeSelectionSet(1+kLFLen, 2);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node");
+ is(selection.focusOffset, contenteditable.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the first text node");
+ checkSelection(1+kLFLen, "bc", "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(1+kLFLen, 3+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(1+kLFLen, "bc" + kLF + "d", "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(3+kLFLen, 0);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, contenteditable.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the first text node");
+ is(selection.focusNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node");
+ is(selection.focusOffset, contenteditable.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the first text node");
+ checkSelection(3+kLFLen, "", "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, 6+kLFLen*2);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(0, kLF + "abc" + kLF + "def", "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, 100);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(0, kLF + "abc" + kLF + "def", "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(4+kLFLen*2, 2);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(4+kLFLen*2, 100);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(6+kLFLen*2, 0);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
+ is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(6+kLFLen*2, 1);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first text node");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <div> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, kLF, "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, 1+kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
+ is(selection.focusNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node of the <div> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(0, kLF + "a", "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(2+kLFLen, 1+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node of the <div> node");
+ is(selection.anchorOffset, 2,
+ "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 2");
+ is(selection.focusNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(2+kLFLen, "c" + kLF, "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(3+kLFLen, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node of the <div> node");
+ is(selection.anchorOffset, contenteditable.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node of the <div> node");
+ is(selection.focusNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(3+kLFLen, kLF, "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(3+kLFLen, 1+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node of the <div> node");
+ is(selection.anchorOffset, contenteditable.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node of the <div> node");
+ is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
+ "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node of the <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(3+kLFLen, kLF + "d", "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ // #4
+ contenteditable.innerHTML = "<div><p>abc</p>def</div>";
+
+ await synthesizeSelectionSet(1+kLFLen*2, 2);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <p> node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node in the <p> node");
+ checkSelection(1+kLFLen*2, "bc", "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(1+kLFLen*2, 3);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <p> node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(1+kLFLen*2, "bcd", "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(3+kLFLen*2, 0);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <p> node");
+ is(selection.anchorOffset, contenteditable.firstChild.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node in the <p> node");
+ is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.firstChild.firstChild.wholeText.length,
+ "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node in the <p> node");
+ checkSelection(3+kLFLen*2, "", "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, 6+kLFLen*2);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(0, kLF + kLF + "abcdef", "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, 100);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(0, kLF + kLF + "abcdef", "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(4+kLFLen*2, 2);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(4+kLFLen*2, 100);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(6+kLFLen*2, 0);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
+ is(selection.focusNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(6+kLFLen*2, 1);
+ is(selection.anchorNode, contenteditable.firstChild.lastChild,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
+ is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <div> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, kLF, "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, kLFLen*2);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
+ is(selection.focusNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, kLF + kLF, "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, 1+kLFLen*2);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
+ is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(0, kLF + kLF + "a", "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen, 0);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <div> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <div> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(kLFLen, "", "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <div> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node");
+ is(selection.focusNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(kLFLen, kLF, "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen, 1+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <div> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node");
+ is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
+ "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(kLFLen, kLF +"a", "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ // #5
+ contenteditable.innerHTML = "<br>";
+
+ await synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(0, kLF, "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(kLFLen, "", "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen, 1);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(kLFLen, "", "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\"");
+
+ // #6
+ contenteditable.innerHTML = "<p><br></p>";
+
+ await synthesizeSelectionSet(kLFLen, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
+ checkSelection(kLFLen, kLF, "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen*2, 0);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the <p>'s children");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
+ checkSelection(kLFLen*2, "", "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen*2, 1);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(kLFLen*2, "", "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, kLF, "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, kLFLen*2);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
+ checkSelection(0, kLF + kLF, "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen, 0);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(kLFLen, "", "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #7
+ contenteditable.innerHTML = "<br><br>";
+
+ await synthesizeSelectionSet(0, kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(0, kLF, "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, kLFLen * 2);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(0, kLF + kLF, "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(kLFLen, "", "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen, kLFLen);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #7 (kLFLen, kLFLen) selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(kLFLen, kLF, "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen * 2, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(kLFLen * 2, "", "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #8
+ contenteditable.innerHTML = "<p><br><br></p>";
+
+ await synthesizeSelectionSet(kLFLen, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(kLFLen, kLF, "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen, kLFLen * 2);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
+ checkSelection(kLFLen, kLF + kLF, "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen*2, 0);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
+ checkSelection(kLFLen*2, "", "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen*2, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen*2, kLFLen) selection focus node should be the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
+ checkSelection(kLFLen*2, kLF, "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen*3, 0);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
+ is(selection.anchorOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the <p>'s children");
+ is(selection.focusNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
+ "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
+ checkSelection(kLFLen*3, "", "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #9 (ContentEventHandler cannot distinguish if <p> can have children, so, the result is same as case #5, "<br>")
+ contenteditable.innerHTML = "<p></p>";
+
+ await synthesizeSelectionSet(kLFLen, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node + 1");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
+ is(selection.focusOffset, 1,
+ "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the index of the <p> node + 1");
+ checkSelection(kLFLen, "", "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(kLFLen, 1);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node + 1");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #9 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #9 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(kLFLen, "", "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #10
+ contenteditable.innerHTML = "";
+
+ await synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, 1);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\"");
+
+ // #11
+ contenteditable.innerHTML = "<span></span><i><u></u></i>";
+
+ await synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(0, 1);
+ is(selection.anchorNode, contenteditable,
+ "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable,
+ "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
+ is(selection.focusOffset, contenteditable.childNodes.length,
+ "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
+ checkSelection(0, "", "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\"");
+
+ // #12
+ contenteditable.innerHTML = "<span>abc</span><i><u></u></i>";
+ selection.selectAllChildren(contenteditable);
+
+ await synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.firstChild.firstChild,
+ "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #13
+ contenteditable.innerHTML = "<span></span><i>abc<u></u></i>";
+ selection.selectAllChildren(contenteditable);
+
+ await synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable.childNodes.item(1).firstChild,
+ "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.childNodes.item(1).firstChild,
+ "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #14
+ contenteditable.innerHTML = "<span></span><i><u>abc</u></i>";
+ selection.selectAllChildren(contenteditable);
+
+ await synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable.childNodes.item(1).firstChild.firstChild,
+ "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.childNodes.item(1).firstChild.firstChild,
+ "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #15
+ contenteditable.innerHTML = "<span></span><i><u></u>abc</i>";
+ selection.selectAllChildren(contenteditable);
+
+ await synthesizeSelectionSet(0, 0);
+ is(selection.anchorNode, contenteditable.childNodes.item(1).lastChild,
+ "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.childNodes.item(1).lastChild,
+ "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(0, "", "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #16
+ contenteditable.innerHTML = "a<blink>b</blink>c";
+ await synthesizeSelectionSet(0, 3);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
+ is(selection.focusOffset, contenteditable.lastChild.wholeText.length,
+ "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
+ checkSelection(0, "abc", "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\"");
+
+ // #17 (bug 1319660 - incorrect adjustment of content iterator last node)
+ contenteditable.innerHTML = "<div>a</div><div><br></div>";
+
+ await synthesizeSelectionSet(kLFLen, 1+kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <div> element");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <div> element");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(kLFLen, "a" + kLF, "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ await synthesizeSelectionSet(1+2*kLFLen, 0);
+ is(selection.anchorNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the second <div> element");
+ is(selection.anchorOffset, 0,
+ "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <div> element");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(1+2*kLFLen, "", "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #18 (bug 1319660 - content iterator start node regression)
+ contenteditable.innerHTML = "<div><br></div><div><br></div>";
+
+ await synthesizeSelectionSet(2*kLFLen, kLFLen);
+ is(selection.anchorNode, contenteditable.firstChild,
+ "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <div> element");
+ is(selection.anchorOffset, 1,
+ "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
+ is(selection.focusNode, contenteditable.lastChild,
+ "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <div> element");
+ is(selection.focusOffset, 0,
+ "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
+ checkSelection(2*kLFLen, kLF, "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+}
+
+function runQueryTextContentEventTest()
+{
+ contenteditable.focus();
+
+ let result;
+
+ // #1
+ contenteditable.innerHTML = "abc<br>def";
+
+ result = synthesizeQueryTextContent(0, 6 + kLFLen);
+ is(result.text, "abc" + kLF + "def", "runQueryTextContentEventTest #1 (0, 6+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, 100);
+ is(result.text, "abc" + kLF + "def", "runQueryTextContentEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(2, 2 + kLFLen);
+ is(result.text, "c" + kLF + "d", "runQueryTextContentEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(1, 2);
+ is(result.text, "bc", "runQueryTextContentEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(6 + kLFLen, 1);
+ is(result.text, "", "runQueryTextContentEventTest #1 (6 + kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
+
+ // #2
+ contenteditable.innerHTML = "<p>a<b>b</b>c</p><p>def</p>";
+
+ result = synthesizeQueryTextContent(kLFLen, 4+kLFLen);
+ is(result.text, "abc" + kLF + "d", "runQueryTextContentEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen, 2);
+ is(result.text, "ab", "runQueryTextContentEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(1+kLFLen, 2);
+ is(result.text, "bc", "runQueryTextContentEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(2+kLFLen, 2+kLFLen);
+ is(result.text, "c" + kLF + "d", "runQueryTextContentEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3+kLFLen*2, 1);
+ is(result.text, "d", "runQueryTextContentEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(2+kLFLen, 1+kLFLen);
+ is(result.text, "c" + kLF, "runQueryTextContentEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3+kLFLen, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3+kLFLen, 1+kLFLen);
+ is(result.text, kLF + "d", "runQueryTextContentEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ // #3
+ contenteditable.innerHTML = "<div>abc<p>def</p></div>";
+
+ result = synthesizeQueryTextContent(1+kLFLen, 2);
+ is(result.text, "bc", "runQueryTextContentEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(1+kLFLen, 3+kLFLen);
+ is(result.text, "bc" + kLF + "d", "runQueryTextContentEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3+kLFLen*2, 1);
+ is(result.text, "d", "runQueryTextContentEventTest #3 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, 6+kLFLen*2);
+ is(result.text, kLF + "abc" + kLF + "def", "runQueryTextContentEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, 100);
+ is(result.text, kLF + "abc" + kLF + "def", "runQueryTextContentEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(4+kLFLen*2, 2);
+ is(result.text, "ef", "runQueryTextContentEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(4+kLFLen*2, 100);
+ is(result.text, "ef", "runQueryTextContentEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(6+kLFLen*2, 1);
+ is(result.text, "", "runQueryTextContentEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, 1+kLFLen);
+ is(result.text, kLF + "a", "runQueryTextContentEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(2+kLFLen, 1+kLFLen);
+ is(result.text, "c" + kLF, "runQueryTextContentEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3+kLFLen, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3+kLFLen, 1+kLFLen);
+ is(result.text, kLF + "d", "runQueryTextContentEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ // #4
+ contenteditable.innerHTML = "<div><p>abc</p>def</div>";
+
+ result = synthesizeQueryTextContent(1+kLFLen*2, 2);
+ is(result.text, "bc", "runQueryTextContentEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(1+kLFLen*2, 3);
+ is(result.text, "bcd", "runQueryTextContentEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(3+kLFLen*2, 1);
+ is(result.text, "d", "runQueryTextContentEventTest #4 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, 6+kLFLen*2);
+ is(result.text, kLF + kLF + "abcdef", "runQueryTextContentEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, 100);
+ is(result.text, kLF + kLF + "abcdef", "runQueryTextContentEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(4+kLFLen*2, 2);
+ is(result.text, "ef", "runQueryTextContentEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(4+kLFLen*2, 100);
+ is(result.text, "ef", "runQueryTextContentEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(6+kLFLen*2, 1);
+ is(result.text, "", "runQueryTextContentEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, kLFLen*2);
+ is(result.text, kLF + kLF, "runQueryTextContentEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, 1+kLFLen*2);
+ is(result.text, kLF + kLF + "a", "runQueryTextContentEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen, 1+kLFLen);
+ is(result.text, kLF + "a", "runQueryTextContentEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ // #5
+ contenteditable.innerHTML = "<br>";
+
+ result = synthesizeQueryTextContent(0, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen, 1);
+ is(result.text, "", "runQueryTextContentEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\"");
+
+ // #6
+ contenteditable.innerHTML = "<p><br></p>";
+
+ result = synthesizeQueryTextContent(kLFLen, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen*2, 1);
+ is(result.text, "", "runQueryTextContentEventTest #5 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, kLFLen*2);
+ is(result.text, kLF + kLF, "runQueryTextContentEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ // #7
+ contenteditable.innerHTML = "<br><br>";
+
+ result = synthesizeQueryTextContent(0, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(0, kLFLen * 2);
+ is(result.text, kLF + kLF, "runQueryTextContentEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen * 2, 1);
+ is(result.text, "", "runQueryTextContentEventTest #7 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
+
+ // #8
+ contenteditable.innerHTML = "<p><br><br></p>";
+
+ result = synthesizeQueryTextContent(kLFLen, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen, kLFLen * 2);
+ is(result.text, kLF + kLF, "runQueryTextContentEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen*2, kLFLen);
+ is(result.text, kLF, "runQueryTextContentEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\"");
+
+ result = synthesizeQueryTextContent(kLFLen*3, 1);
+ is(result.text, "", "runQueryTextContentEventTest #8 (kLFLen*3, 1), \"" + contenteditable.innerHTML + "\"");
+
+ // #16
+ contenteditable.innerHTML = "a<blink>b</blink>c";
+
+ result = synthesizeQueryTextContent(0, 3);
+ is(result.text, "abc", "runQueryTextContentEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\"");
+}
+
+function runQuerySelectionEventTest()
+{
+ contenteditable.focus();
+
+ let selection = windowOfContenteditable.getSelection();
+
+ // #1
+ contenteditable.innerHTML = "<br/>a";
+ selection.setBaseAndExtent(
+ contenteditable.firstChild,
+ 0,
+ contenteditable.lastChild,
+ 1
+ );
+ checkSelection(
+ 0,
+ `${kLF}a`,
+ `runQuerySelectionEventTest #1, "${contenteditable.innerHTML}"`
+ );
+
+ // #2
+ contenteditable.innerHTML = "<p></p><p>abc</p>";
+ selection.setBaseAndExtent(
+ contenteditable.firstChild,
+ 0,
+ contenteditable.lastChild.firstChild,
+ 1
+ );
+ checkSelection(
+ kLFLen,
+ `${kLF}a`,
+ `runQuerySelectionEventTest #2, "${contenteditable.innerHTML}"`
+ );
+
+ // #3
+ contenteditable.innerHTML = "<p>abc</p><p>def</p>";
+ selection.setBaseAndExtent(
+ contenteditable.firstChild,
+ 0,
+ contenteditable.lastChild.firstChild,
+ 1
+ );
+ checkSelection(
+ kLFLen,
+ `abc${kLF}d`,
+ `runQuerySelectionEventTest #3, "${contenteditable.innerHTML}"`
+ );
+
+ // #4
+ contenteditable.innerHTML = "<p>abc</p>";
+ selection.removeAllRanges();
+ checkSelection(
+ null,
+ null,
+ `runQuerySelectionEventTest #4, "${contenteditable.innerHTML}"`
+ );
+}
+
+function runQueryIMESelectionTest()
+{
+ textarea.focus();
+ textarea.value = "before after";
+ let startoffset = textarea.selectionStart = textarea.selectionEnd = "before ".length;
+
+ if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: before starting composition") ||
+ !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: before starting composition") ||
+ !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: before starting composition") ||
+ !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: before starting composition")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "a",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ if (!checkIMESelection("RawClause", true, startoffset, "a", "runQueryIMESelectionTest: inputting raw text") ||
+ !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: inputting raw text") ||
+ !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: inputting raw text") ||
+ !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: inputting raw text")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "abcdefgh",
+ "clauses":
+ [
+ { "length": 8, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 8, "length": 0 }
+ });
+
+ if (!checkIMESelection("RawClause", true, startoffset, "abcdefgh", "runQueryIMESelectionTest: updating raw text") ||
+ !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: updating raw text") ||
+ !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: updating raw text") ||
+ !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: updating raw text")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABCDEFGH",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 3, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": 3, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: starting to convert") ||
+ !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: starting to convert") ||
+ !checkIMESelection("ConvertedClause", true, startoffset + 2, "CDE", "runQueryIMESelectionTest: starting to convert") ||
+ !checkIMESelection("SelectedClause", true, startoffset, "AB", "runQueryIMESelectionTest: starting to convert")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABCDEFGH",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": 3, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 3, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ ]
+ },
+ "caret": { "start": 5, "length": 0 }
+ });
+
+ if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: changing selected clause") ||
+ !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: changing selected clause") ||
+ !checkIMESelection("ConvertedClause", true, startoffset, "AB", "runQueryIMESelectionTest: changing selected clause") ||
+ !checkIMESelection("SelectedClause", true, startoffset + 2, "CDE", "runQueryIMESelectionTest: changing selected clause")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: after committing composition") ||
+ !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: after committing composition") ||
+ !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: after committing composition") ||
+ !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: after committing composition")) {
+ return;
+ }
+
+ startoffset = textarea.selectionStart;
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "abcdefgh",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ ]
+ },
+ "caret": { "start": 8, "length": 0 }
+ });
+
+ if (!checkIMESelection("RawClause", true, startoffset, "a", "runQueryIMESelectionTest: unrealistic testcase") ||
+ !checkIMESelection("SelectedRawClause", true, startoffset + 1, "b", "runQueryIMESelectionTest: unrealistic testcase") ||
+ !checkIMESelection("ConvertedClause", true, startoffset + 2, "c", "runQueryIMESelectionTest: unrealistic testcase") ||
+ !checkIMESelection("SelectedClause", true, startoffset + 3, "d", "runQueryIMESelectionTest: unrealistic testcase")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+}
+
+function runQueryPasswordTest() {
+ function checkRange(aOffset, aLength, aExpectedResult, aDescription) {
+ password.focus();
+ let result = synthesizeQueryTextContent(aOffset, aLength);
+ is(result.text, aExpectedResult,
+ `${aDescription}: synthesizeQueryTextContent(${aOffset}, ${aLength})`);
+ password.setSelectionRange(aOffset, aOffset + aLength);
+ result = synthesizeQuerySelectedText();
+ is(result.text, aExpectedResult,
+ `${aDescription}: synthesizeQuerySelectedText(${aOffset}, ${aLength})`);
+ }
+
+ let editor = password.editor;
+ const kMask = editor.passwordMask;
+ password.value = "abcdef";
+
+ editor.mask();
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range is not specified #1");
+ checkRange(0, 3, `${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range is not specified #2");
+ checkRange(3, 3, `${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range is not specified #3");
+ checkRange(2, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range is not specified #4");
+
+ editor.unmask(0, 6);
+ checkRange(0, 6, "abcdef",
+ "runQueryPasswordTest: unmasked range 0-6 #1");
+ checkRange(0, 3, "abc",
+ "runQueryPasswordTest: unmasked range 0-6 #2");
+ checkRange(3, 3, "def",
+ "runQueryPasswordTest: unmasked range 0-6 #3");
+ checkRange(2, 2, "cd",
+ "runQueryPasswordTest: unmasked range 0-6 #4");
+
+ editor.unmask(0, 3);
+ checkRange(0, 6, `abc${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range 0-3 #1");
+ checkRange(0, 3, "abc",
+ "runQueryPasswordTest: unmasked range 0-3 #2");
+ checkRange(3, 3, `${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range 0-3 #3");
+ checkRange(2, 2, `c${kMask}`,
+ "runQueryPasswordTest: unmasked range 0-3 #4");
+
+ editor.unmask(3, 6);
+ checkRange(0, 6, `${kMask}${kMask}${kMask}def`,
+ "runQueryPasswordTest: unmasked range 3-6 #1");
+ checkRange(0, 3, `${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range 3-6 #2");
+ checkRange(3, 3, `def`,
+ "runQueryPasswordTest: unmasked range 3-6 #3");
+ checkRange(2, 2, `${kMask}d`,
+ "runQueryPasswordTest: unmasked range 3-6 #4");
+
+ editor.unmask(2, 4);
+ checkRange(0, 6, `${kMask}${kMask}cd${kMask}${kMask}`,
+ "runQueryPasswordTest: unmasked range 3-4 #1");
+ checkRange(1, 2, `${kMask}c`,
+ "runQueryPasswordTest: unmasked range 3-4 #2");
+ checkRange(1, 3, `${kMask}cd`,
+ "runQueryPasswordTest: unmasked range 3-4 #3");
+ checkRange(1, 4, `${kMask}cd${kMask}`,
+ "runQueryPasswordTest: unmasked range 3-4 #4");
+ checkRange(2, 2, "cd",
+ "runQueryPasswordTest: unmasked range 3-4 #5");
+ checkRange(2, 3, `cd${kMask}`,
+ "runQueryPasswordTest: unmasked range 3-4 #6");
+
+
+ const kEmoji = String.fromCodePoint(0x1f914);
+ password.value = `${kEmoji}${kEmoji}${kEmoji}`
+
+ editor.mask();
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range is not specified");
+
+ editor.unmask(0, 2);
+ checkRange(0, 6, `${kEmoji}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 0-2 #1");
+ checkRange(0, 2, `${kEmoji}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 0-2 #2");
+ checkRange(2, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 0-2 #3");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 0-2 #4");
+
+ editor.unmask(2, 4);
+ checkRange(0, 6, `${kMask}${kMask}${kEmoji}${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 2-4 #1");
+ checkRange(0, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 2-4 #2");
+ checkRange(2, 2, `${kEmoji}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 2-4 #3");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 2-4 #4");
+
+ editor.unmask(4, 6);
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kEmoji}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 4-6 #1");
+ checkRange(0, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 4-6 #2");
+ checkRange(2, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 4-6 #3");
+ checkRange(4, 2, `${kEmoji}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 4-6 #4");
+
+ editor.unmask(0, 1);
+ checkRange(0, 6, `${kEmoji}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 0-1");
+
+ editor.unmask(1, 2);
+ checkRange(0, 6, `${kEmoji}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 1-2");
+
+ editor.unmask(2, 3);
+ checkRange(0, 6, `${kMask}${kMask}${kEmoji}${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 2-3");
+
+ editor.unmask(3, 4);
+ checkRange(0, 6, `${kMask}${kMask}${kEmoji}${kMask}${kMask}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 3-4");
+
+ editor.unmask(4, 5);
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kEmoji}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 4-5");
+
+ editor.unmask(5, 6);
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kEmoji}`,
+ "runQueryPasswordTest: Emojis in password, unmasked range 5-6");
+
+
+ const kEmojiSuperhero = String.fromCodePoint(0x1f9b8);
+ const kEmojiMediumSkinTone = String.fromCodePoint(0x1f3fd);
+ const kZeroWidthJoiner = "\u200d";
+ const kFemaleSign = "\u2640";
+ const kVariationSelector16 = "\ufe0f";
+ const kComplicatedEmoji = `${kEmojiSuperhero}${kEmojiMediumSkinTone}${kZeroWidthJoiner}${kFemaleSign}${kVariationSelector16}`;
+ password.value = `${kComplicatedEmoji}${kComplicatedEmoji}${kComplicatedEmoji}`
+ editor.mask();
+ checkRange(0, 21, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range is not specified");
+
+ editor.unmask(0, 7);
+ checkRange(0, 21, `${kComplicatedEmoji}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 0-7 #1");
+ checkRange(0, 7, `${kComplicatedEmoji}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 0-7 #2");
+ checkRange(7, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 0-7 #3");
+ checkRange(14, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 0-7 #4");
+
+ editor.unmask(7, 14);
+ checkRange(0, 21, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kComplicatedEmoji}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 7-14 #1");
+ checkRange(0, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 7-14 #2");
+ checkRange(7, 7, `${kComplicatedEmoji}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 7-14 #3");
+ checkRange(14, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 7-14 #4");
+
+ editor.unmask(14, 21);
+ checkRange(0, 21, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kComplicatedEmoji}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 14-21 #1");
+ checkRange(0, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 14-21 #2");
+ checkRange(7, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 14-21 #3");
+ checkRange(14, 7, `${kComplicatedEmoji}`,
+ "runQueryPasswordTest: Complicated emojis in password, unmasked range 14-21 #4");
+
+ password.value = `${kComplicatedEmoji}`
+ editor.unmask(0, 1);
+ checkRange(0, 7, `${kEmojiSuperhero}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emoji in password, unmasked range 0-1");
+
+ editor.unmask(1, 2);
+ checkRange(0, 7, `${kEmojiSuperhero}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emoji in password, unmasked range 1-2");
+
+ editor.unmask(2, 3);
+ checkRange(0, 7, `${kMask}${kMask}${kEmojiMediumSkinTone}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emoji in password, unmasked range 2-3");
+
+ editor.unmask(3, 4);
+ checkRange(0, 7, `${kMask}${kMask}${kEmojiMediumSkinTone}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emoji in password, unmasked range 3-4");
+
+ editor.unmask(4, 5);
+ checkRange(0, 7, `${kMask}${kMask}${kMask}${kMask}${kZeroWidthJoiner}${kMask}${kMask}`,
+ "runQueryPasswordTest: Complicated emoji in password, unmasked range 4-5");
+
+ editor.unmask(5, 6);
+ checkRange(0, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kFemaleSign}${kMask}`,
+ "runQueryPasswordTest: Complicated emoji in password, unmasked range 5-6");
+
+ editor.unmask(6, 7);
+ checkRange(0, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kVariationSelector16}`,
+ "runQueryPasswordTest: Complicated emoji in password, unmasked range 6-7");
+
+
+ const kKanji = "\u8fba";
+ const kIVS = String.fromCodePoint(0xe0101);
+ const kKanjiWithIVS = `${kKanji}${kIVS}`;
+ password.value = `${kKanjiWithIVS}${kKanjiWithIVS}${kKanjiWithIVS}`
+
+ editor.mask();
+ checkRange(0, 9, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range is not specified");
+
+ editor.unmask(0, 3);
+ checkRange(0, 9, `${kKanjiWithIVS}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #1");
+ checkRange(0, 3, `${kKanjiWithIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #2");
+ checkRange(1, 3, `${kIVS}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #3");
+ checkRange(0, 1, `${kKanji}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #4");
+ checkRange(1, 2, `${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #5");
+ checkRange(3, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #6");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #7");
+ checkRange(6, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #8");
+ checkRange(7, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #9");
+
+ editor.unmask(0, 1);
+ checkRange(0, 9, `${kKanji}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #1");
+ checkRange(0, 1, `${kKanji}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #2");
+ checkRange(1, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #3");
+ checkRange(3, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #4");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #5");
+ checkRange(6, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #6");
+ checkRange(7, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #7");
+
+ editor.unmask(1, 3);
+ checkRange(0, 9, `${kMask}${kIVS}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #1");
+ checkRange(0, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #2");
+ checkRange(1, 2, `${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #3");
+ checkRange(3, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #4");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #5");
+ checkRange(6, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #6");
+ checkRange(7, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #7");
+
+ editor.unmask(3, 6);
+ checkRange(0, 9, `${kMask}${kMask}${kMask}${kKanjiWithIVS}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #1");
+ checkRange(3, 3, `${kKanjiWithIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #2");
+ checkRange(4, 3, `${kIVS}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #3");
+ checkRange(0, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #4");
+ checkRange(1, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #5");
+ checkRange(3, 1, `${kKanji}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #6");
+ checkRange(4, 2, `${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #7");
+ checkRange(6, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #8");
+ checkRange(7, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #9");
+
+ editor.unmask(3, 4);
+ checkRange(0, 9, `${kMask}${kMask}${kMask}${kKanji}${kMask}${kMask}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #1");
+ checkRange(0, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #2");
+ checkRange(1, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #3");
+ checkRange(3, 1, `${kKanji}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #4");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #5");
+ checkRange(6, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #6");
+ checkRange(7, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #7");
+
+ editor.unmask(4, 6);
+ checkRange(0, 9, `${kMask}${kMask}${kMask}${kMask}${kIVS}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #1");
+ checkRange(0, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #2");
+ checkRange(1, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #3");
+ checkRange(3, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #4");
+ checkRange(4, 2, `${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #5");
+ checkRange(6, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #6");
+ checkRange(7, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #7");
+
+ editor.unmask(6, 9);
+ checkRange(0, 9, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kKanjiWithIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #1");
+ checkRange(6, 3, `${kKanjiWithIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #2");
+ checkRange(4, 3, `${kMask}${kMask}${kKanji}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #3");
+ checkRange(0, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #4");
+ checkRange(1, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #5");
+ checkRange(3, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #6");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #7");
+ checkRange(6, 1, `${kKanji}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #8");
+ checkRange(7, 2, `${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #9");
+
+ editor.unmask(6, 7);
+ checkRange(0, 9, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kKanji}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #1");
+ checkRange(0, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #2");
+ checkRange(1, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #3");
+ checkRange(3, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #4");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #5");
+ checkRange(6, 1, `${kKanji}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #6");
+ checkRange(7, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #7");
+
+ editor.unmask(7, 9);
+ checkRange(0, 9, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #1");
+ checkRange(0, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #2");
+ checkRange(1, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #3");
+ checkRange(3, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #4");
+ checkRange(4, 2, `${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #5");
+ checkRange(6, 1, `${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #6");
+ checkRange(7, 2, `${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #7");
+
+ password.value = `${kKanjiWithIVS}${kKanjiWithIVS}`;
+ editor.unmask(0, 2);
+ checkRange(0, 6, `${kKanjiWithIVS}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-2");
+
+ editor.unmask(1, 2);
+ checkRange(0, 6, `${kMask}${kIVS}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-2");
+
+ editor.unmask(2, 3);
+ checkRange(0, 6, `${kMask}${kIVS}${kMask}${kMask}${kMask}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 2-3");
+
+ editor.unmask(3, 5);
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kKanjiWithIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-5");
+
+ editor.unmask(4, 5);
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-5");
+
+ editor.unmask(5, 6);
+ checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kIVS}`,
+ "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 5-6");
+
+ editor.mask();
+}
+
+function runQueryContentEventRelativeToInsertionPoint()
+{
+ textarea.focus();
+ textarea.value = "0123456789";
+
+ // "[]0123456789"
+ let startOffset = textarea.selectionStart = textarea.selectionEnd = 0;
+ if (!checkContentRelativeToSelection(0, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#1") ||
+ !checkContentRelativeToSelection(-1, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#2") ||
+ !checkContentRelativeToSelection(1, 1, 1, "1", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#3") ||
+ !checkContentRelativeToSelection(5, 10, 5, "56789", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#4") ||
+ !checkContentRelativeToSelection(10, 1, 10, "", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#5")) {
+ return;
+ }
+
+ // "[01234]56789"
+ textarea.selectionEnd = 5;
+ if (!checkContentRelativeToSelection(0, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#1") ||
+ !checkContentRelativeToSelection(-1, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#2") ||
+ !checkContentRelativeToSelection(1, 1, 1, "1", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#3") ||
+ !checkContentRelativeToSelection(5, 10, 5, "56789", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#4") ||
+ !checkContentRelativeToSelection(10, 1, 10, "", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#5")) {
+ return;
+ }
+
+ // "0123[]456789"
+ startOffset = textarea.selectionStart = textarea.selectionEnd = 4;
+ if (!checkContentRelativeToSelection(0, 1, startOffset + 0, "4", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#1") ||
+ !checkContentRelativeToSelection(-1, 1, startOffset - 1, "3", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#2") ||
+ !checkContentRelativeToSelection(1, 1, startOffset + 1, "5", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#3") ||
+ !checkContentRelativeToSelection(5, 10, startOffset + 5, "9", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#4") ||
+ !checkContentRelativeToSelection(10, 1, 10, "", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#5")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "a",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ // "0123[a]456789"
+ if (!checkContentRelativeToSelection(0, 1, startOffset + 0, "a", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#1") ||
+ !checkContentRelativeToSelection(-1, 1, startOffset - 1, "3", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#2") ||
+ !checkContentRelativeToSelection(1, 1, startOffset + 1, "4", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#3") ||
+ !checkContentRelativeToSelection(5, 10, startOffset + 5, "89", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#4") ||
+ !checkContentRelativeToSelection(11, 1, 11, "", "runQueryContentEventRelativeToInsertionPoint[composition at 4]")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ // Move start of composition at first compositionupdate event.
+ function onCompositionUpdate(aEvent)
+ {
+ startOffset = textarea.selectionStart = textarea.selectionEnd = textarea.selectionStart - 1;
+ textarea.removeEventListener("compositionupdate", onCompositionUpdate);
+ }
+ textarea.addEventListener("compositionupdate", onCompositionUpdate);
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "b",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ // "0123[b]a456789"
+ if (!checkContentRelativeToSelection(0, 1, startOffset + 0, "b", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#1") ||
+ !checkContentRelativeToSelection(-1, 1, startOffset - 1, "3", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#2") ||
+ !checkContentRelativeToSelection(1, 1, startOffset + 1, "a", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#3") ||
+ !checkContentRelativeToSelection(5, 10, startOffset + 5, "789", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#4") ||
+ !checkContentRelativeToSelection(12, 1, 12, "", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#5")) {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+}
+
+function runBug1375825Test()
+{
+ contenteditable.focus();
+
+ // #1
+ contenteditable.innerHTML = "abc<span contenteditable=\"false\">defgh</span>";
+
+ let ret = synthesizeQueryTextRect(2, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #1 (2, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "c", "runBug1375825Test #1 (2, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'c'");
+
+ ret = synthesizeQueryTextRect(3, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #1 (3, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "d", "runBug1375825Test #1 (3, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'd'");
+
+ ret = synthesizeQueryTextRect(4, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #1 (4, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "e", "runBug1375825Test #1 (4, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'e'");
+
+ ret = synthesizeQueryTextRect(5, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #1 (5, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "f", "runBug1375825Test #1 (5, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'f'");
+
+ ret = synthesizeQueryTextRect(6, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #1 (6, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "g", "runBug1375825Test #1 (6, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'g'");
+
+ ret = synthesizeQueryTextRect(7, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #1 (7, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "h", "runBug1375825Test #1 (7, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'h'");
+
+ // #2
+ contenteditable.innerHTML = "abc<span style=\"user-select: all;\">defgh</span>";
+
+ ret = synthesizeQueryTextRect(2, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #2 (2, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "c", "runBug1375825Test #2 (2, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'c'");
+
+ ret = synthesizeQueryTextRect(3, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #2 (3, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "d", "runBug1375825Test #2 (3, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'd'");
+
+ ret = synthesizeQueryTextRect(4, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #2 (4, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "e", "runBug1375825Test #2 (4, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'e'");
+
+ ret = synthesizeQueryTextRect(5, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #2 (5, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "f", "runBug1375825Test #2 (5, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'f'");
+
+ ret = synthesizeQueryTextRect(6, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #2 (6, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "g", "runBug1375825Test #2 (6, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'g'");
+
+ ret = synthesizeQueryTextRect(7, 1);
+ if (!checkQueryContentResult(ret, "runBug1375825Test #2 (7, 1), \"" + contenteditable.innerHTML + "\"")) {
+ return;
+ }
+ is(ret.text, "h", "runBug1375825Test #2 (7, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'h'");
+}
+
+function runBug1530649Test()
+{
+ // Vietnamese IME on macOS commits composition with typing space key.
+ // Then, typing new word shouldn't trim the trailing whitespace.
+ contenteditable.focus();
+ contenteditable.innerHTML = "";
+ synthesizeCompositionChange(
+ {composition: {string: "abc", clauses: [{length: 3, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ caret: {start: 3, length: 0}});
+ synthesizeComposition({type: "compositioncommit", data: "abc ", key: " "});
+
+ is(contenteditable.innerHTML, "abc <br>",
+ "runBug1530649Test: The trailing space shouldn't be removed");
+
+ synthesizeCompositionChange(
+ {composition: {string: "d", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ caret: {start: 1, length: 0}});
+
+ is(contenteditable.innerHTML, "abc d<br>",
+ "runBug1530649Test: The new composition string shouldn't remove the last space");
+
+ synthesizeComposition({type: "compositioncommitasis", key: "KEY_Enter"});
+
+ is(contenteditable.innerHTML, "abc d<br>",
+ "runBug1530649Test: Committing the new composition string shouldn't remove the last space");
+}
+
+function runBug1571375Test()
+{
+ let selection = windowOfContenteditableBySpan.getSelection();
+ let doc = document.getElementById("iframe7").contentDocument;
+
+ contenteditableBySpan.focus();
+
+ contenteditableBySpan.innerHTML = "hello world";
+ let range = doc.createRange();
+ range.setStart(contenteditableBySpan.firstChild, 6);
+ range.setEnd(contenteditableBySpan.firstChild, 11);
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ synthesizeCompositionChange({
+ composition: {string: "world", clauses: [{length: 5, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ caret: { start: 5, length: 0 },
+ });
+ synthesizeComposition({type: "compositioncommit", data: "world", key: " "});
+ is(contenteditableBySpan.innerHTML, "hello world",
+ "runBug1571375Test: space must not be removed by commit");
+
+ contenteditableBySpan.innerHTML = "hello world";
+ range = doc.createRange();
+ range.setStart(contenteditableBySpan.firstChild, 0);
+ range.setEnd(contenteditableBySpan.firstChild, 5);
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ synthesizeCompositionChange({
+ composition: {string: "hello", clauses: [{length: 5, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ caret: { start: 5, length: 0 },
+ });
+ synthesizeComposition({type: "compositioncommit", data: "hello", key: " "});
+ is(contenteditableBySpan.innerHTML, "hello world",
+ "runBug1571375Test: space must not be removed by commit");
+
+ contenteditableBySpan.innerHTML = "hello world<div>.</div>";
+ range = doc.createRange();
+ range.setStart(contenteditableBySpan.firstChild, 6);
+ range.setEnd(contenteditableBySpan.firstChild, 11);
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ synthesizeCompositionChange({
+ composition: {string: "world", clauses: [{length: 5, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ caret: {start: 0, length: 0}}
+ );
+ synthesizeComposition({type: "compositioncommit", data: "world", key: " "});
+ is(contenteditableBySpan.innerHTML, "hello world<div>.</div>",
+ "runBug1571375Test: space must not be removed by commit");
+}
+
+async function runBug1584901Test()
+{
+ contenteditableBySpan.focus();
+ contenteditableBySpan.innerHTML = "";
+
+ // XXX synthesizeCompositionChange won't work without wait.
+ await waitForTick();
+
+ synthesizeCompositionChange({
+ composition: {string: "a ", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ });
+ synthesizeComposition({type: "compositioncommitasis", key: " "});
+
+ is(contenteditableBySpan.innerHTML, "a&nbsp;",
+ "runBug1584901Test: space must not be removed by composition change");
+
+ synthesizeCompositionChange({
+ composition: {string: "b ", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ });
+ synthesizeComposition({type: "compositioncommitasis", key: " "});
+
+ is(contenteditableBySpan.innerHTML, "a b&nbsp;",
+ "runBug1584901Test: space must not be removed by composition change");
+}
+
+function runBug1675313Test()
+{
+ input.value = "";
+ input.focus();
+ let count = 0;
+
+ function handler() {
+ input.focus();
+ count++;
+ }
+
+ input.addEventListener("keydown", handler);
+ input.addEventListener("keyup", handler);
+
+ synthesizeCompositionChange({
+ composition: {
+ string: "a",
+ clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
+ key: { key: "a", type: "keyup" },
+ },
+ });
+ synthesizeCompositionChange({
+ composition: {
+ string: "b",
+ clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
+ key: { key: "b", type: "keyup" },
+ },
+ });
+ synthesizeComposition({type: "compositioncommitasis"});
+
+ is(count, 6, "runBug1675313Test: keydown event and keyup event are fired correctly");
+ is(input.value, "b",
+ "runBug1675313Test: re-focus element doesn't commit composition if re-focus isn't click by user");
+
+ input.removeEventListener("keyup", handler);
+}
+
+function runCommitCompositionWithSpaceKey()
+{
+ contenteditable.focus();
+ contenteditable.innerHTML = "";
+
+ // Last white space might be &nbsp; if last child is no <br>
+ // Actually, our implementation will insert <br> element at last child, so
+ // white space will be ASCII space.
+
+ synthesizeCompositionChange({
+ composition: {string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ });
+ synthesizeComposition({type: "compositioncommit", data: "a"});
+ synthesizeKey(" ");
+
+ is(contenteditable.innerHTML, "a <br>",
+ "runCommitCompositionWithSpaceKey: last single space should be kept");
+
+ synthesizeCompositionChange({
+ composition: {string: "b", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ });
+ synthesizeComposition({type: "compositioncommit", data: "b"});
+ synthesizeKey(" ");
+
+ is(contenteditable.innerHTML, "a b <br>",
+ "runCommitCompositionWithSpaceKey: inserting composition shouldn't remove last single space.");
+
+ synthesizeCompositionChange({
+ composition: {string: "c", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ });
+ synthesizeComposition({type: "compositioncommit", data: "c"});
+ synthesizeKey(" ");
+
+ is(contenteditable.innerHTML, "a b c <br>",
+ "runCommitCompositionWithSpaceKey: inserting composition shouldn't remove last single space.");
+
+ contenteditable.innerHTML = "a";
+ windowOfContenteditable.getSelection().collapse(contenteditable.firstChild, contenteditable.firstChild.length);
+ is(contenteditable.innerHTML, "a",
+ "runCommitCompositionWithSpaceKey: contenteditable should be initialized with text ending with a space and without following <br> element");
+
+ synthesizeCompositionChange({
+ composition: {string: "b", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ });
+ synthesizeComposition({type: "compositioncommit", data: "b ", key: { key: " ", code: "Space" }});
+
+ is(contenteditable.innerHTML, "ab <br>",
+ "runCommitCompositionWithSpaceKey: contenteditable should end with a padding <br> element after inserting commit string ending with a space");
+}
+
+function runCSSTransformTest()
+{
+ textarea.focus();
+ textarea.value = "some text";
+ textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
+ let editorRect = synthesizeQueryEditorRect();
+ if (!checkQueryContentResult(editorRect,
+ "runCSSTransformTest: editorRect")) {
+ return;
+ }
+ let firstCharRect = synthesizeQueryTextRect(0, 1);
+ if (!checkQueryContentResult(firstCharRect,
+ "runCSSTransformTest: firstCharRect")) {
+ return;
+ }
+ let lastCharRect = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRect,
+ "runCSSTransformTest: lastCharRect")) {
+ return;
+ }
+ let caretRect = synthesizeQueryCaretRect(textarea.selectionStart);
+ if (!checkQueryContentResult(caretRect,
+ "runCSSTransformTest: caretRect")) {
+ return;
+ }
+ let caretRectBeforeFirstChar = synthesizeQueryCaretRect(0);
+ if (!checkQueryContentResult(caretRectBeforeFirstChar,
+ "runCSSTransformTest: caretRectBeforeFirstChar")) {
+ return;
+ }
+
+ try {
+ textarea.style.transform = "translate(10px, 15px)";
+ function movedRect(aRect, aCSS_CX, aCSS_CY)
+ {
+ return {
+ left: aRect.left + Math.round(aCSS_CX * window.devicePixelRatio),
+ top: aRect.top + Math.round(aCSS_CY * window.devicePixelRatio),
+ width: aRect.width,
+ height: aRect.height
+ };
+ }
+
+ let editorRectTranslated = synthesizeQueryEditorRect();
+ if (!checkQueryContentResult(editorRectTranslated,
+ "runCSSTransformTest: editorRectTranslated, " + textarea.style.transform) ||
+ !checkRectFuzzy(editorRectTranslated, movedRect(editorRect, 10, 15), {left: 1, top: 1, width: 1, height: 1},
+ "runCSSTransformTest: editorRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ let firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
+ if (!checkQueryContentResult(firstCharRectTranslated,
+ "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform) ||
+ !checkRectFuzzy(firstCharRectTranslated, movedRect(firstCharRect, 10, 15), {left: 1, top: 1, width: 1, height: 1},
+ "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ let lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRectTranslated,
+ "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform) ||
+ !checkRectFuzzy(lastCharRectTranslated, movedRect(lastCharRect, 10, 15), {left: 1, top: 1, width: 1, height: 1},
+ "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ let caretRectTranslated = synthesizeQueryCaretRect(textarea.selectionStart);
+ if (!checkQueryContentResult(caretRectTranslated,
+ "runCSSTransformTest: caretRectTranslated, " + textarea.style.transform) ||
+ !checkRectFuzzy(caretRectTranslated, movedRect(caretRect, 10, 15), {left: 1, top: 1, width: 1, height: 1},
+ "runCSSTransformTest: caretRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ let caretRectBeforeFirstCharTranslated = synthesizeQueryCaretRect(0);
+ if (!checkQueryContentResult(caretRectBeforeFirstCharTranslated,
+ "runCSSTransformTest: caretRectBeforeFirstCharTranslated, " + textarea.style.transform) ||
+ !checkRectFuzzy(caretRectBeforeFirstCharTranslated, movedRect(caretRectBeforeFirstChar, 10, 15), {left: 1, top: 1, width: 1, height: 1},
+ "runCSSTransformTest: caretRectBeforeFirstCharTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ let firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
+ if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
+ !checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
+ return;
+ }
+ let lastCharRectTranslatedAsArray = synthesizeQueryTextRectArray(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRectTranslatedAsArray, "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform) ||
+ !checkRectArray(lastCharRectTranslatedAsArray, [lastCharRectTranslated], "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform)) {
+ return;
+ }
+
+ // XXX It's too difficult to check the result with scale and rotate...
+ // For now, let's check if query text rect and query text rect array returns same rect.
+ textarea.style.transform = "scale(1.5)";
+ firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
+ if (!checkQueryContentResult(firstCharRectTranslated,
+ "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRectTranslated,
+ "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
+ if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
+ !checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
+ return;
+ }
+ lastCharRectTranslatedAsArray = synthesizeQueryTextRectArray(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRectTranslatedAsArray, "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform) ||
+ !checkRectArray(lastCharRectTranslatedAsArray, [lastCharRectTranslated], "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform)) {
+ return;
+ }
+
+ textarea.style.transform = "rotate(30deg)";
+ firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
+ if (!checkQueryContentResult(firstCharRectTranslated,
+ "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRectTranslated,
+ "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
+ if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
+ !checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
+ return;
+ }
+ lastCharRectTranslatedAsArray = synthesizeQueryTextRectArray(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRectTranslatedAsArray, "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform) ||
+ !checkRectArray(lastCharRectTranslatedAsArray, [lastCharRectTranslated], "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform)) {
+ return;
+ }
+ } finally {
+ textarea.style.transform = "";
+ }
+}
+
+function runBug722639Test()
+{
+ textarea.focus();
+ textarea.value = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
+ textarea.value += textarea.value;
+ textarea.value += textarea.value; // 80 characters
+
+ let firstLine = synthesizeQueryTextRect(0, 1);
+ if (!checkQueryContentResult(firstLine,
+ "runBug722639Test: firstLine")) {
+ return;
+ }
+ ok(true, "runBug722639Test: 1st line, top=" + firstLine.top + ", left=" + firstLine.left);
+ let firstLineAsArray = synthesizeQueryTextRectArray(0, 1);
+ if (!checkQueryContentResult(firstLineAsArray, "runBug722639Test: 1st line as array") ||
+ !checkRectArray(firstLineAsArray, [firstLine], "runBug722639Test: 1st line as array should match with text rect result")) {
+ return;
+ }
+ if (kLFLen > 1) {
+ let firstLineLF = synthesizeQueryTextRect(1, 1);
+ if (!checkQueryContentResult(firstLineLF,
+ "runBug722639Test: firstLineLF")) {
+ return;
+ }
+ is(firstLineLF.top, firstLine.top, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
+ is(firstLineLF.left, firstLine.left, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
+ isfuzzy(firstLineLF.height, firstLine.height, 1,
+ "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
+ is(firstLineLF.width, firstLine.width, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
+ let firstLineLFAsArray = synthesizeQueryTextRectArray(1, 1);
+ if (!checkQueryContentResult(firstLineLFAsArray, "runBug722639Test: 1st line's \\n rect as array") ||
+ !checkRectArray(firstLineLFAsArray, [firstLineLF], "runBug722639Test: 1st line's rect as array should match with text rect result")) {
+ return;
+ }
+ }
+ let secondLine = synthesizeQueryTextRect(kLFLen, 1);
+ if (!checkQueryContentResult(secondLine,
+ "runBug722639Test: secondLine")) {
+ return;
+ }
+ ok(true, "runBug722639Test: 2nd line, top=" + secondLine.top + ", left=" + secondLine.left);
+ let secondLineAsArray = synthesizeQueryTextRectArray(kLFLen, 1);
+ if (!checkQueryContentResult(secondLineAsArray, "runBug722639Test: 2nd line as array") ||
+ !checkRectArray(secondLineAsArray, [secondLine], "runBug722639Test: 2nd line as array should match with text rect result")) {
+ return;
+ }
+ if (kLFLen > 1) {
+ let secondLineLF = synthesizeQueryTextRect(kLFLen + 1, 1);
+ if (!checkQueryContentResult(secondLineLF,
+ "runBug722639Test: secondLineLF")) {
+ return;
+ }
+ is(secondLineLF.top, secondLine.top, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
+ is(secondLineLF.left, secondLine.left, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
+ isfuzzy(secondLineLF.height, secondLine.height, 1,
+ "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
+ is(secondLineLF.width, secondLine.width, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
+ let secondLineLFAsArray = synthesizeQueryTextRectArray(kLFLen + 1, 1);
+ if (!checkQueryContentResult(secondLineLFAsArray, "runBug722639Test: 2nd line's \\n rect as array") ||
+ !checkRectArray(secondLineLFAsArray, [secondLineLF], "runBug722639Test: 2nd line's rect as array should match with text rect result")) {
+ return;
+ }
+ }
+ let lineHeight = secondLine.top - firstLine.top;
+ ok(lineHeight > 0,
+ "runBug722639Test: lineHeight must be positive");
+ is(secondLine.left, firstLine.left,
+ "runBug722639Test: the left value must be always same value");
+ isfuzzy(secondLine.height, firstLine.height, 1,
+ "runBug722639Test: the height must be always same value");
+ let previousTop = secondLine.top;
+ for (let i = 3; i <= textarea.value.length + 1; i++) {
+ let currentLine = synthesizeQueryTextRect(kLFLen * (i - 1), 1);
+ if (!checkQueryContentResult(currentLine,
+ "runBug722639Test: " + i + "th currentLine")) {
+ return;
+ }
+ ok(true, "runBug722639Test: " + i + "th line, top=" + currentLine.top + ", left=" + currentLine.left);
+ let currentLineAsArray = synthesizeQueryTextRectArray(kLFLen * (i - 1), 1);
+ if (!checkQueryContentResult(currentLineAsArray, "runBug722639Test: " + i + "th line as array") ||
+ !checkRectArray(currentLineAsArray, [currentLine], "runBug722639Test: " + i + "th line as array should match with text rect result")) {
+ return;
+ }
+ // NOTE: the top position may be 1px larger or smaller than other lines
+ // due to sub pixel positioning.
+ if (Math.abs(currentLine.top - (previousTop + lineHeight)) <= 1) {
+ ok(true, "runBug722639Test: " + i + "th line's top is expected");
+ } else {
+ is(currentLine.top, previousTop + lineHeight,
+ "runBug722639Test: " + i + "th line's top is unexpected");
+ }
+ is(currentLine.left, firstLine.left,
+ "runBug722639Test: " + i + "th line's left is unexpected");
+ isfuzzy(currentLine.height, firstLine.height, 1,
+ `runBug722639Test: ${i}th line's height is unexpected`);
+ if (kLFLen > 1) {
+ let currentLineLF = synthesizeQueryTextRect(kLFLen * (i - 1) + 1, 1);
+ if (!checkQueryContentResult(currentLineLF,
+ "runBug722639Test: " + i + "th currentLineLF")) {
+ return;
+ }
+ is(currentLineLF.top, currentLine.top, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
+ is(currentLineLF.left, currentLine.left, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
+ isfuzzy(currentLineLF.height, currentLine.height, 1,
+ `runBug722639Test: ${i}th line's \\n rect should be same as same line's \\r rect`);
+ is(currentLineLF.width, currentLine.width, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
+ let currentLineLFAsArray = synthesizeQueryTextRectArray(kLFLen * (i - 1) + 1, 1);
+ if (!checkQueryContentResult(currentLineLFAsArray, "runBug722639Test: " + i + "th line's \\n rect as array") ||
+ !checkRectArray(currentLineLFAsArray, [currentLineLF], "runBug722639Test: " + i + "th line's rect as array should match with text rect result")) {
+ return;
+ }
+ }
+ previousTop = currentLine.top;
+ }
+}
+
+function runCompositionWithSelectionChange() {
+ function doTest(aEditor, aDescription) {
+ aEditor.focus();
+ const isHTMLEditor =
+ aEditor.nodeName.toLowerCase() != "input" && aEditor.nodeName.toLowerCase() != "textarea";
+ const win = isHTMLEditor ? windowOfContenteditable : window;
+ function getValue() {
+ return isHTMLEditor ? aEditor.innerHTML : aEditor.value;
+ }
+ function setSelection(aStart, aLength) {
+ if (isHTMLEditor) {
+ win.getSelection().setBaseAndExtent(aEditor.firstChild, aStart, aEditor.firstChild, aStart + aLength);
+ } else {
+ aEditor.setSelectionRange(aStart, aStart + aLength);
+ }
+ }
+
+ if (isHTMLEditor) {
+ aEditor.innerHTML = "abcxyz";
+ } else {
+ aEditor.value = "abcxyz";
+ }
+ setSelection("abc".length, 0);
+
+ synthesizeCompositionChange({
+ composition: {
+ string: "1",
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
+ caret: { start: 1, length: 0 },
+ }
+ });
+
+ is(getValue(), "abc1xyz",
+ `${aDescription}: First composing character should be inserted middle of the text`);
+
+ aEditor.addEventListener("compositionupdate", () => {
+ setSelection("abc".length, "1".length);
+ }, {once: true});
+
+ synthesizeCompositionChange({
+ composition: {
+ string: "12",
+ clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
+ caret: { start: 2, length: 0 },
+ }
+ });
+
+ is(getValue(), "abc12xyz",
+ `${aDescription}: Only composition string should be updated even if selection range is updated by "compositionupdate" event listener`);
+
+ aEditor.addEventListener("compositionupdate", () => {
+ setSelection("abc1".length, "2d".length);
+ }, {once: true});
+
+ synthesizeCompositionChange({
+ composition: {
+ string: "123",
+ clauses: [{ length: 3, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
+ caret: { start: 3, length: 0 },
+ }
+ });
+
+ is(getValue(), "abc123xyz",
+ `${aDescription}: Only composition string should be updated even if selection range wider than composition string is updated by "compositionupdate" event listener`);
+
+ aEditor.addEventListener("compositionupdate", () => {
+ setSelection("ab".length, "c123d".length);
+ }, {once: true});
+
+ synthesizeCompositionChange({
+ composition: {
+ string: "456",
+ clauses: [{ length: 3, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
+ caret: { start: 3, length: 0 },
+ }
+ });
+
+ is(getValue(), "abc456xyz",
+ `${aDescription}: Only composition string should be updated even if selection range which covers all over the composition string is updated by "compositionupdate" event listener`);
+
+ aEditor.addEventListener("beforeinput", () => {
+ setSelection("abc456d".length, 0);
+ }, {once: true});
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ is(getValue(), "abc456xyz",
+ `${aDescription}: Only composition string should be updated when committing composition but selection is updated by "beforeinput" event listener`);
+ if (isHTMLEditor) {
+ is(win.getSelection().focusNode, aEditor.firstChild,
+ `${aDescription}: The focus node after composition should be the text node`);
+ is(win.getSelection().focusOffset, "abc456".length,
+ `${aDescription}: The focus offset after composition should be end of the composition string`);
+ is(win.getSelection().anchorNode, aEditor.firstChild,
+ `${aDescription}: The anchor node after composition should be the text node`);
+ is(win.getSelection().anchorOffset, "abc456".length,
+ `${aDescription}: The anchor offset after composition should be end of the composition string`);
+ } else {
+ is(aEditor.selectionStart, "abc456".length,
+ `${aDescription}: The selectionStart after composition should be end of the composition string`);
+ is(aEditor.selectionEnd, "abc456".length,
+ `${aDescription}: The selectionEnd after composition should be end of the composition string`);
+ }
+ }
+ doTest(textarea, "runCompositionWithSelectionChange(textarea)");
+ doTest(input, "runCompositionWithSelectionChange(input)");
+ doTest(contenteditable, "runCompositionWithSelectionChange(contenteditable)");
+}
+
+function runForceCommitTest()
+{
+ let events;
+ function eventHandler(aEvent)
+ {
+ events.push(aEvent);
+ }
+ window.addEventListener("compositionstart", eventHandler, true);
+ window.addEventListener("compositionupdate", eventHandler, true);
+ window.addEventListener("compositionend", eventHandler, true);
+ window.addEventListener("beforeinput", eventHandler, true);
+ window.addEventListener("input", eventHandler, true);
+ window.addEventListener("text", eventHandler, true);
+
+ // Make the composition in textarea commit by click in the textarea
+ textarea.focus();
+ textarea.value = "";
+
+ events = [];
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ is(events.length, 5,
+ "runForceCommitTest: wrong event count #1");
+ is(events[0].type, "compositionstart",
+ "runForceCommitTest: the 1st event must be compositionstart #1");
+ is(events[1].type, "compositionupdate",
+ "runForceCommitTest: the 2nd event must be compositionupdate #1");
+ is(events[2].type, "text",
+ "runForceCommitTest: the 3rd event must be text #1");
+ is(events[3].type, "beforeinput",
+ "runForceCommitTest: the 4th event must be beforeinput #1");
+ checkInputEvent(events[3], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #1");
+ is(events[4].type, "input",
+ "runForceCommitTest: the 5th event must be input #1");
+ checkInputEvent(events[4], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #1");
+
+ events = [];
+ synthesizeMouseAtCenter(textarea, {});
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #2");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #2");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #2`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #2");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #2`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #2");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #2");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #2`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #2");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #2");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #2`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #2");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #2");
+ is(textarea.value, "\u306E",
+ "runForceCommitTest: the textarea doesn't have the committed text #2");
+
+ // Make the composition in textarea commit by click in another editor (input)
+ textarea.focus();
+ textarea.value = "";
+ input.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ synthesizeMouseAtCenter(input, {});
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #3");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #3");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #3`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #3");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #3`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #3");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #3");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #3`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #3");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #3");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #3`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #3");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #3");
+ ok(!getEditor(input).isComposing,
+ "runForceCommitTest: the input has composition #3");
+ is(textarea.value, "\u306E",
+ "runForceCommitTest: the textarea doesn't have the committed text #3");
+ is(input.value, "",
+ "runForceCommitTest: the input has the committed text? #3");
+
+ // Make the composition in textarea commit by blur()
+ textarea.focus();
+ textarea.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ textarea.blur();
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #4");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #4");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #4`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #4");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #4`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #4");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #4");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #4`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #4");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #4");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #4`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #4");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #4");
+ is(textarea.value, "\u306E",
+ "runForceCommitTest: the textarea doesn't have the committed text #4");
+
+ // Make the composition in textarea commit by input.focus()
+ textarea.focus();
+ textarea.value = "";
+ input.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ input.focus();
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #5");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #5");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #5`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #5");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #5`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #5");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #5");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #5`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #5");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #5");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #5`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #5");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #5");
+ ok(!getEditor(input).isComposing,
+ "runForceCommitTest: the input has composition #5");
+ is(textarea.value, "\u306E",
+ "runForceCommitTest: the textarea doesn't have the committed text #5");
+ is(input.value, "",
+ "runForceCommitTest: the input has the committed text? #5");
+
+ // Make the composition in textarea commit by click in another document's editor
+ textarea.focus();
+ textarea.value = "";
+ textareaInFrame.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ synthesizeMouseAtCenter(textareaInFrame, {}, iframe.contentWindow);
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #6");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #6");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #6`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #6");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #6`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #6");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #6");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #6`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #6");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #6");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #6`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #6");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #6");
+ ok(!getEditor(textareaInFrame).isComposing,
+ "runForceCommitTest: the textarea in frame has composition #6");
+ is(textarea.value, "\u306E",
+ "runForceCommitTest: the textarea doesn't have the committed text #6");
+ is(textareaInFrame.value, "",
+ "runForceCommitTest: the textarea in frame has the committed text? #6");
+
+ // Make the composition in textarea commit by another document's editor's focus()
+ textarea.focus();
+ textarea.value = "";
+ textareaInFrame.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ textareaInFrame.focus();
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #7");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #7");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #7`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #7");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #7`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #7");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #7");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #7`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #7");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #7");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #7`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #7");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #7");
+ ok(!getEditor(textareaInFrame).isComposing,
+ "runForceCommitTest: the textarea in frame has composition #7");
+ is(textarea.value, "\u306E",
+ "runForceCommitTest: the textarea doesn't have the committed text #7");
+ is(textareaInFrame.value, "",
+ "runForceCommitTest: the textarea in frame has the committed text? #7");
+
+ // Make the composition in a textarea commit by click in another editable document
+ textarea.focus();
+ textarea.value = "";
+ iframe2.contentDocument.body.innerHTML = "Text in the Body";
+ let iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ synthesizeMouseAtCenter(iframe2.contentDocument.body, {}, iframe2.contentWindow);
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #8");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #8");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The ${events[0].type} event was fired on wrong event target #8`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #8");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The ${events[1].type} event was fired on wrong event target #8`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #8");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #8");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The ${events[2].type} event was fired on wrong event target #8`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #8");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #8");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The ${events[3].type} event was fired on wrong event target #8`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #8");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #8");
+ ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
+ "runForceCommitTest: the editable document has composition #8");
+ is(textarea.value, "\u306E",
+ "runForceCommitTest: the textarea doesn't have the committed text #8");
+ is(iframe2.contentDocument.body.innerHTML, iframe2BodyInnerHTML,
+ "runForceCommitTest: the editable document has the committed text? #8");
+
+ // Make the composition in an editable document commit by click in it
+ iframe2.contentWindow.focus();
+ iframe2.contentDocument.body.innerHTML = "Text in the Body";
+ iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ }, iframe2.contentWindow);
+
+ events = [];
+ synthesizeMouseAtCenter(iframe2.contentDocument.body, {}, iframe2.contentWindow);
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #9");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #9");
+ is(events[0].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #9`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #9");
+ is(events[1].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #9`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E",
+ [{startContainer: iframe2.contentDocument.body.firstChild,
+ startOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E"),
+ endContainer: iframe2.contentDocument.body.firstChild,
+ endOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E") + 1}],
+ "runForceCommitTest #9");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #9");
+ is(events[2].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #9`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #9");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #9");
+ is(events[3].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #9`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #9");
+ ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
+ "runForceCommitTest: the editable document still has composition #9");
+ ok(iframe2.contentDocument.body.innerHTML != iframe2BodyInnerHTML &&
+ iframe2.contentDocument.body.innerHTML.includes("\u306E"),
+ "runForceCommitTest: the editable document doesn't have the committed text #9");
+
+ // Make the composition in an editable document commit by click in another document's editor
+ textarea.value = "";
+ iframe2.contentWindow.focus();
+ iframe2.contentDocument.body.innerHTML = "Text in the Body";
+ iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ }, iframe2.contentWindow);
+
+ events = [];
+ synthesizeMouseAtCenter(textarea, {});
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #10");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #10");
+ is(events[0].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The ${events[0].type} event was fired on wrong event target #10`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #10");
+ is(events[1].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The ${events[1].type} event was fired on wrong event target #10`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E",
+ [{startContainer: iframe2.contentDocument.body.firstChild,
+ startOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E"),
+ endContainer: iframe2.contentDocument.body.firstChild,
+ endOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E") + 1}],
+ "runForceCommitTest #10");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #10");
+ is(events[2].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The ${events[2].type} event was fired on wrong event target #10`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #10");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #10");
+ is(events[3].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The ${events[3].type} event was fired on wrong event target #10`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #10");
+ ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
+ "runForceCommitTest: the editable document still has composition #10");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea has composition #10");
+ ok(iframe2.contentDocument.body.innerHTML != iframe2BodyInnerHTML &&
+ iframe2.contentDocument.body.innerHTML.includes("\u306E"),
+ "runForceCommitTest: the editable document doesn't have the committed text #10");
+ is(textarea.value, "",
+ "runForceCommitTest: the textarea has the committed text? #10");
+
+ // Make the composition in an editable document commit by click in the another editable document
+ iframe2.contentWindow.focus();
+ iframe2.contentDocument.body.innerHTML = "Text in the Body";
+ iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
+ iframe3.contentDocument.body.innerHTML = "Text in the Body";
+ let iframe3BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ }, iframe2.contentWindow);
+
+ events = [];
+ synthesizeMouseAtCenter(iframe3.contentDocument.body, {}, iframe3.contentWindow);
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #11");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #11");
+ is(events[0].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #11`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #11");
+ is(events[1].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #11`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E",
+ [{startContainer: iframe2.contentDocument.body.firstChild,
+ startOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E"),
+ endContainer: iframe2.contentDocument.body.firstChild,
+ endOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E") + 1}],
+ "runForceCommitTest #11");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #11");
+ is(events[2].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #11`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #11");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #11");
+ is(events[3].target, iframe2.contentDocument.body,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #11`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #11");
+ ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
+ "runForceCommitTest: the editable document still has composition #11");
+ ok(!getHTMLEditorIMESupport(iframe3.contentWindow).isComposing,
+ "runForceCommitTest: the other editable document has composition #11");
+ ok(iframe2.contentDocument.body.innerHTML != iframe2BodyInnerHTML &&
+ iframe2.contentDocument.body.innerHTML.includes("\u306E"),
+ "runForceCommitTest: the editable document doesn't have the committed text #11");
+ is(iframe3.contentDocument.body.innerHTML, iframe3BodyInnerHTML,
+ "runForceCommitTest: the other editable document has the committed text? #11");
+
+ input.focus();
+ input.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ input.value = "set value";
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #12");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #12");
+ is(events[0].target, input,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #12`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #12");
+ is(events[1].target, input,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #12`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #12");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #12");
+ is(events[2].target, input,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #12`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #12");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #12");
+ is(events[3].target, input,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #12`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #12");
+ ok(!getEditor(input).isComposing,
+ "runForceCommitTest: the input still has composition #12");
+ is(input.value, "set value",
+ "runForceCommitTest: the input doesn't have the set text #12");
+
+ textarea.focus();
+ textarea.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ textarea.value = "set value";
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #13");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #13");
+ is(events[0].target, textarea,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #13`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #13");
+ is(events[1].target, textarea,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #13`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #13");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #13");
+ is(events[2].target, textarea,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #13`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #13");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #13");
+ is(events[3].target, textarea,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #13`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #13");
+ ok(!getEditor(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #13");
+ is(textarea.value, "set value",
+ "runForceCommitTest: the textarea doesn't have the set text #13");
+
+ input.focus();
+ input.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ input.value += " appended value";
+
+ is(events.length, 4,
+ "runForceCommitTest: wrong event count #14");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #14");
+ is(events[0].target, input,
+ `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #14`);
+ is(events[1].type, "beforeinput",
+ "runForceCommitTest: the 2nd event must be beforeinput #14");
+ is(events[1].target, input,
+ `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #14`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #14");
+ is(events[2].type, "compositionend",
+ "runForceCommitTest: the 3rd event must be compositionend #14");
+ is(events[2].target, input,
+ `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #14`);
+ is(events[2].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #14");
+ is(events[3].type, "input",
+ "runForceCommitTest: the 4th event must be input #14");
+ is(events[3].target, input,
+ `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #14`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runForceCommitTest #14");
+ ok(!getEditor(input).isComposing,
+ "runForceCommitTest: the input still has composition #14");
+ is(input.value, "\u306E appended value",
+ "runForceCommitTest: the input should have both composed text and appended text #14");
+
+ input.focus();
+ input.value = "abcd";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ input.value = "abcd\u306E";
+
+ is(events.length, 0,
+ "runForceCommitTest: setting same value to input with composition shouldn't cause any events #15");
+ is(input.value, "abcd\u306E",
+ "runForceCommitTest: the input has unexpected value #15");
+
+ input.blur(); // commit composition
+
+ textarea.focus();
+ textarea.value = "abcd";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ textarea.value = "abcd\u306E";
+
+ is(events.length, 0,
+ "runForceCommitTest: setting same value to textarea with composition shouldn't cause any events #16");
+ is(textarea.value, "abcd\u306E",
+ "runForceCommitTest: the input has unexpected value #16");
+
+ textarea.blur(); // commit composition
+
+ window.removeEventListener("compositionstart", eventHandler, true);
+ window.removeEventListener("compositionupdate", eventHandler, true);
+ window.removeEventListener("compositionend", eventHandler, true);
+ window.removeEventListener("beforeinput", eventHandler, true);
+ window.removeEventListener("input", eventHandler, true);
+ window.removeEventListener("text", eventHandler, true);
+}
+
+function runNestedSettingValue()
+{
+ let isTesting = false;
+ let events = [];
+ function eventHandler(aEvent)
+ {
+ events.push(aEvent);
+ if (isTesting) {
+ aEvent.target.value += aEvent.type + ", ";
+ }
+ }
+ window.addEventListener("compositionstart", eventHandler, true);
+ window.addEventListener("compositionupdate", eventHandler, true);
+ window.addEventListener("compositionend", eventHandler, true);
+ window.addEventListener("beforeinput", eventHandler, true);
+ window.addEventListener("input", eventHandler, true);
+ window.addEventListener("text", eventHandler, true);
+
+ textarea.focus();
+ textarea.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ isTesting = true;
+ textarea.value = "first setting value, ";
+ isTesting = false;
+
+ is(events.length, 4,
+ "runNestedSettingValue: wrong event count #1");
+ is(events[0].type, "text",
+ "runNestedSettingValue: the 1st event must be text #1");
+ is(events[0].target, textarea,
+ `runNestedSettingValue: The "${events[0].type}" event was fired on wrong event target #1`);
+ is(events[1].type, "beforeinput",
+ "runNestedSettingValue: the 2nd event must be beforeinput #1");
+ is(events[1].target, textarea,
+ `runNestedSettingValue: The "${events[1].type}" event was fired on wrong event target #1`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #1");
+ is(events[2].type, "compositionend",
+ "runNestedSettingValue: the 3rd event must be compositionend #1");
+ is(events[2].target, textarea,
+ `runNestedSettingValue: The "${events[2].type}" event was fired on wrong event target #1`);
+ is(events[2].data, "\u306E",
+ "runNestedSettingValue: compositionend has wrong data #1");
+ is(events[3].type, "input",
+ "runNestedSettingValue: the 4th event must be input #1");
+ is(events[3].target, textarea,
+ `runNestedSettingValue: The "${events[3].type}" event was fired on wrong event target #1`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #1");
+ ok(!getEditor(textarea).isComposing,
+ "runNestedSettingValue: the textarea still has composition #1");
+ is(textarea.value, "first setting value, text, beforeinput, compositionend, input, ",
+ "runNestedSettingValue: the textarea should have all string set to value attribute");
+
+ input.focus();
+ input.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ isTesting = true;
+ input.value = "first setting value, ";
+ isTesting = false;
+
+ is(events.length, 4,
+ "runNestedSettingValue: wrong event count #2");
+ is(events[0].type, "text",
+ "runNestedSettingValue: the 1st event must be text #2");
+ is(events[0].target, input,
+ `runNestedSettingValue: The "${events[0].type}" event was fired on wrong event target #2`);
+ is(events[1].type, "beforeinput",
+ "runNestedSettingValue: the 2nd event must be beforeinput #2");
+ is(events[1].target, input,
+ `runNestedSettingValue: The "${events[1].type}" event was fired on wrong event target #2`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #2");
+ is(events[2].type, "compositionend",
+ "runNestedSettingValue: the 3rd event must be compositionend #2");
+ is(events[2].target, input,
+ `runNestedSettingValue: The "${events[2].type}" event was fired on wrong event target #2`);
+ is(events[2].data, "\u306E",
+ "runNestedSettingValue: compositionend has wrong data #2");
+ is(events[3].type, "input",
+ "runNestedSettingValue: the 4th event must be input #2");
+ is(events[3].target, input,
+ `runNestedSettingValue: The "${events[3].type}" event was fired on wrong event target #2`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #2");
+ ok(!getEditor(input).isComposing,
+ "runNestedSettingValue: the input still has composition #2");
+ is(textarea.value, "first setting value, text, beforeinput, compositionend, input, ",
+ "runNestedSettingValue: the input should have all string set to value attribute #2");
+
+ textarea.focus();
+ textarea.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ isTesting = true;
+ textarea.setRangeText("first setting value, ");
+ isTesting = false;
+
+ is(events.length, 4,
+ "runNestedSettingValue: wrong event count #3");
+ is(events[0].type, "text",
+ "runNestedSettingValue: the 1st event must be text #3");
+ is(events[0].target, textarea,
+ `runNestedSettingValue: The ${events[0].type} event was fired on wrong event target #3`);
+ is(events[1].type, "beforeinput",
+ "runNestedSettingValue: the 2nd event must be beforeinput #3");
+ is(events[1].target, textarea,
+ `runNestedSettingValue: The ${events[1].type} event was fired on wrong event target #3`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #3");
+ is(events[2].type, "compositionend",
+ "runNestedSettingValue: the 3rd event must be compositionend #3");
+ is(events[2].target, textarea,
+ `runNestedSettingValue: The ${events[2].type} event was fired on wrong event target #3`);
+ is(events[2].data, "\u306E",
+ "runNestedSettingValue: compositionend has wrong data #3");
+ is(events[3].type, "input",
+ "runNestedSettingValue: the 4th event must be input #3");
+ is(events[3].target, textarea,
+ `runNestedSettingValue: The ${events[3].type} event was fired on wrong event target #3`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #3");
+ ok(!getEditor(textarea).isComposing,
+ "runNestedSettingValue: the textarea still has composition #3");
+ is(textarea.value, "\u306Efirst setting value, text, beforeinput, compositionend, input, ",
+ "runNestedSettingValue: the textarea should have appended by setRangeText() and all string set to value attribute #3");
+
+ input.focus();
+ input.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ events = [];
+ isTesting = true;
+ input.setRangeText("first setting value, ");
+ isTesting = false;
+
+ is(events.length, 4,
+ "runNestedSettingValue: wrong event count #4");
+ is(events[0].type, "text",
+ "runNestedSettingValue: the 1st event must be text #4");
+ is(events[0].target, input,
+ `runNestedSettingValue: The "${events[0].type}" event was fired on wrong event target #4`);
+ is(events[1].type, "beforeinput",
+ "runNestedSettingValue: the 2nd event must be beforeinput #4");
+ is(events[1].target, input,
+ `runNestedSettingValue: The "${events[1].type}" event was fired on wrong event target #4`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #4");
+ is(events[2].type, "compositionend",
+ "runNestedSettingValue: the 3rd event must be compositionend #4");
+ is(events[2].target, input,
+ `runNestedSettingValue: The "${events[2].type}" event was fired on wrong event target #4`);
+ is(events[2].data, "\u306E",
+ "runNestedSettingValue: compositionend has wrong data #4");
+ is(events[3].type, "input",
+ "runNestedSettingValue: the 4th event must be input #4");
+ is(events[3].target, input,
+ `runNestedSettingValue: The "${events[3].type}" event was fired on wrong event target #4`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runNestedSettingValue #4");
+ ok(!getEditor(input).isComposing,
+ "runNestedSettingValue: the input still has composition #4");
+ is(textarea.value, "\u306Efirst setting value, text, beforeinput, compositionend, input, ",
+ "runNestedSettingValue: the input should have all string appended by setRangeText() and set to value attribute #4");
+
+ window.removeEventListener("compositionstart", eventHandler, true);
+ window.removeEventListener("compositionupdate", eventHandler, true);
+ window.removeEventListener("compositionend", eventHandler, true);
+ window.removeEventListener("beforeinput", eventHandler, true);
+ window.removeEventListener("input", eventHandler, true);
+ window.removeEventListener("text", eventHandler, true);
+
+}
+
+async function runAsyncForceCommitTest()
+{
+ let events;
+ function eventHandler(aEvent)
+ {
+ events.push(aEvent);
+ };
+
+ // If IME commits composition for a request, TextComposition commits
+ // composition automatically because most web apps must expect that active
+ // composition should be committed synchronously. Therefore, in this case,
+ // a click during composition should cause committing composition
+ // synchronously and delayed commit shouldn't cause composition events.
+ let commitRequested = false;
+ let onFinishTest = null;
+ function callback(aTIP, aNotification)
+ {
+ ok(true, aNotification.type);
+ if (aNotification.type != "request-to-commit") {
+ return true;
+ }
+ commitRequested = true;
+ if (onFinishTest) {
+ let resolve = onFinishTest;
+ onFinishTest = null;
+
+ SimpleTest.executeSoon(() => {
+ events = [];
+ aTIP.commitComposition();
+
+ is(events.length, 0,
+ "runAsyncForceCommitTest: composition events shouldn't been fired by asynchronous call of nsITextInputProcessor.commitComposition()");
+
+ SimpleTest.executeSoon(resolve);
+ });
+ }
+ return true;
+ };
+
+ function promiseCleanUp() {
+ return new Promise(resolve => { onFinishTest = resolve; });
+ }
+
+ window.addEventListener("compositionstart", eventHandler, true);
+ window.addEventListener("compositionupdate", eventHandler, true);
+ window.addEventListener("compositionend", eventHandler, true);
+ window.addEventListener("beforeinput", eventHandler, true);
+ window.addEventListener("input", eventHandler, true);
+ window.addEventListener("text", eventHandler, true);
+
+ // Make the composition in textarea commit by click in the textarea
+ textarea.focus();
+ textarea.value = "";
+
+ events = [];
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ }, window, callback);
+
+ is(events.length, 5,
+ "runAsyncForceCommitTest: wrong event count #1");
+ is(events[0].type, "compositionstart",
+ "runAsyncForceCommitTest: the 1st event must be compositionstart #1");
+ is(events[1].type, "compositionupdate",
+ "runAsyncForceCommitTest: the 2nd event must be compositionupdate #1");
+ is(events[2].type, "text",
+ "runAsyncForceCommitTest: the 3rd event must be text #1");
+ is(events[3].type, "beforeinput",
+ "runAsyncForceCommitTest: the 4th event must be beforeinput #1");
+ checkInputEvent(events[3], true, "insertCompositionText", "\u306E", [],
+ "runAsyncForceCommitTest #1");
+ is(events[4].type, "input",
+ "runAsyncForceCommitTest: the 5th event must be input #1");
+ checkInputEvent(events[4], true, "insertCompositionText", "\u306E", [],
+ "runAsyncForceCommitTest #1");
+
+ events = [];
+ let waitCleanState = promiseCleanUp();
+
+ synthesizeMouseAtCenter(textarea, {});
+
+ ok(commitRequested,
+ "runAsyncForceCommitTest: \"request-to-commit\" should've been notified");
+ is(events.length, 4,
+ "runAsyncForceCommitTest: wrong event count #2");
+ is(events[0].type, "text",
+ "runAsyncForceCommitTest: the 1st event must be text #2");
+ is(events[0].target, textarea,
+ `runAsyncForceCommitTest: The "${events[0].type}" event was fired on wrong event target #2`);
+ is(events[1].type, "beforeinput",
+ "runAsyncForceCommitTest: the 2nd event must be beforeinput #2");
+ is(events[1].target, textarea,
+ `runAsyncForceCommitTest: The "${events[1].type}" event was fired on wrong event target #2`);
+ checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
+ "runAsyncForceCommitTest #2");
+ is(events[2].type, "compositionend",
+ "runAsyncForceCommitTest: the 3rd event must be compositionend #2");
+ is(events[2].target, textarea,
+ `runAsyncForceCommitTest: The "${events[2].type}" event was fired on wrong event target #2`);
+ is(events[2].data, "\u306E",
+ "runAsyncForceCommitTest: compositionend has wrong data #2");
+ is(events[3].type, "input",
+ "runAsyncForceCommitTest: the 4th event must be input #2");
+ is(events[3].target, textarea,
+ `runAsyncForceCommitTest: The "${events[3].type}" event was fired on wrong event target #2`);
+ checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
+ "runAsyncForceCommitTest #2");
+ ok(!getEditor(textarea).isComposing,
+ "runAsyncForceCommitTest: the textarea still has composition #2");
+ is(textarea.value, "\u306E",
+ "runAsyncForceCommitTest: the textarea doesn't have the committed text #2");
+
+ await waitCleanState;
+
+ window.removeEventListener("compositionstart", eventHandler, true);
+ window.removeEventListener("compositionupdate", eventHandler, true);
+ window.removeEventListener("compositionend", eventHandler, true);
+ window.removeEventListener("beforeinput", eventHandler, true);
+ window.removeEventListener("input", eventHandler, true);
+ window.removeEventListener("text", eventHandler, true);
+}
+
+function runBug811755Test()
+{
+ iframe2.contentDocument.body.innerHTML = "<div>content<br/></div>";
+ iframe2.contentWindow.focus();
+ // Query everything
+ let textContent = synthesizeQueryTextContent(0, 10);
+ if (!checkQueryContentResult(textContent, "runBug811755Test: synthesizeQueryTextContent #1")) {
+ return;
+ }
+ // Query everything but specify exact end offset, which should be immediately after the <br> node
+ // If PreContentIterator is used, the next node after <br> is the node after </div>.
+ // If ContentIterator is used, the next node is the <div> node itself. In this case, the end
+ // node ends up being before the start node, and an empty string is returned.
+ let queryContent = synthesizeQueryTextContent(0, textContent.text.length);
+ if (!checkQueryContentResult(queryContent, "runBug811755Test: synthesizeQueryTextContent #2")) {
+ return;
+ }
+ is(queryContent.text, textContent.text, "runBug811755Test: two queried texts don't match");
+}
+
+function runIsComposingTest()
+{
+ let expectedIsComposing = false;
+ let description = "";
+
+ function eventHandler(aEvent)
+ {
+ if (aEvent.type == "keydown" || aEvent.type == "keyup") {
+ is(aEvent.isComposing, expectedIsComposing,
+ "runIsComposingTest: " + description + " (type=" + aEvent.type + ", key=" + aEvent.key + ")");
+ } else if (aEvent.type == "keypress") {
+ // keypress event shouldn't be fired during composition so that isComposing should be always false.
+ is(aEvent.isComposing, false,
+ "runIsComposingTest: " + description + " (type=" + aEvent.type + ")");
+ } else {
+ checkInputEvent(aEvent, expectedIsComposing, "insertCompositionText", "\u3042", [],
+ `runIsComposingTest: ${description}`);
+ }
+ }
+
+ function onComposition(aEvent)
+ {
+ if (aEvent.type == "compositionstart") {
+ expectedIsComposing = true;
+ } else if (aEvent.type == "compositionend") {
+ expectedIsComposing = false;
+ }
+ }
+
+ textarea.addEventListener("keydown", eventHandler, true);
+ textarea.addEventListener("keypress", eventHandler, true);
+ textarea.addEventListener("keyup", eventHandler, true);
+ textarea.addEventListener("beforeinput", eventHandler, true);
+ textarea.addEventListener("input", eventHandler, true);
+ textarea.addEventListener("compositionstart", onComposition, true);
+ textarea.addEventListener("compositionend", onComposition, true);
+
+ textarea.focus();
+ textarea.value = "";
+
+ // XXX These cases shouldn't occur in actual native key events because we
+ // don't dispatch key events while composition (bug 354358).
+ description = "events before dispatching compositionstart";
+ synthesizeKey("KEY_ArrowLeft");
+
+ description = "events after dispatching compositionchange";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 },
+ "key": { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A },
+ });
+
+ // Although, firing keypress event during composition is a bug.
+ synthesizeKey("KEY_Insert");
+
+ description = "events for committing composition string";
+
+ synthesizeComposition({ type: "compositioncommitasis",
+ key: { key: "KEY_Enter", code: "Enter", type: "keydown" } });
+
+ // input event will be fired by synthesizing compositionend event.
+ // Then, its isComposing should be false.
+ description = "events after dispatching compositioncommitasis";
+ synthesizeKey("KEY_Enter", {type: "keyup"});
+
+ textarea.removeEventListener("keydown", eventHandler, true);
+ textarea.removeEventListener("keypress", eventHandler, true);
+ textarea.removeEventListener("keyup", eventHandler, true);
+ textarea.removeEventListener("beforeinput", eventHandler, true);
+ textarea.removeEventListener("input", eventHandler, true);
+ textarea.removeEventListener("compositionstart", onComposition, true);
+ textarea.removeEventListener("compositionend", onComposition, true);
+
+ textarea.value = "";
+}
+
+function runRedundantChangeTest()
+{
+ textarea.focus();
+
+ let result = [];
+ function clearResult()
+ {
+ result = [];
+ }
+
+ function handler(aEvent)
+ {
+ result.push(aEvent);
+ }
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", handler, true);
+ textarea.addEventListener("beforeinput", handler, true);
+ textarea.addEventListener("input", handler, true);
+ textarea.addEventListener("text", handler, true);
+
+ textarea.value = "";
+
+ // synthesize change event
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ is(result.length, 4,
+ "runRedundantChangeTest: 4 events should be fired after synthesizing composition change #1");
+ is(result[0].type, "compositionupdate",
+ "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #1");
+ is(result[1].type, "text",
+ "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #1");
+ is(result[2].type, "beforeinput",
+ "runRedundantChangeTest: beforeinput should be fired after synthesizing composition change #1");
+ checkInputEvent(result[2], true, "insertCompositionText", "\u3042", [],
+ "runRedundantChangeTest: after synthesizing composition change #1");
+ is(result[3].type, "input",
+ "runRedundantChangeTest: input should be fired after synthesizing composition change #1");
+ checkInputEvent(result[3], true, "insertCompositionText", "\u3042", [],
+ "runRedundantChangeTest: after synthesizing composition change #1");
+ is(textarea.value, "\u3042", "runRedundantChangeTest: textarea has uncommitted string #1");
+
+ // synthesize another change event
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042\u3044",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ is(result.length, 4,
+ "runRedundantChangeTest: 4 events should be fired after synthesizing composition change #2");
+ is(result[0].type, "compositionupdate",
+ "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #2");
+ is(result[1].type, "text",
+ "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #2");
+ is(result[2].type, "beforeinput",
+ "runRedundantChangeTest: beforeinput should be fired after synthesizing composition change #2");
+ checkInputEvent(result[2], true, "insertCompositionText", "\u3042\u3044", [],
+ "runRedundantChangeTest: after synthesizing composition change #2");
+ is(result[3].type, "input",
+ "runRedundantChangeTest: input should be fired after synthesizing composition change #2");
+ checkInputEvent(result[3], true, "insertCompositionText", "\u3042\u3044", [],
+ "runRedundantChangeTest: after synthesizing composition change #2");
+ is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has uncommitted string #2");
+
+ // synthesize same change event again
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042\u3044",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ is(result.length, 0, "runRedundantChangeTest: no events should be fired after synthesizing composition change again");
+ is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has uncommitted string #3");
+
+ // synthesize commit-as-is
+ clearResult();
+ synthesizeComposition({ type: "compositioncommitasis" });
+ is(result.length, 4,
+ "runRedundantChangeTest: 4 events should be fired after synthesizing composition commit-as-is");
+ is(result[0].type, "text",
+ "runRedundantChangeTest: text should be fired after synthesizing composition commit-as-is for removing the ranges");
+ is(result[1].type, "beforeinput",
+ "runRedundantChangeTest: beforeinput should be fired after synthesizing composition commit-as-is for removing the ranges");
+ checkInputEvent(result[1], true, "insertCompositionText", "\u3042\u3044", [],
+ "runRedundantChangeTest: at synthesizing commit-as-is");
+ is(result[2].type, "compositionend",
+ "runRedundantChangeTest: compositionend should be fired after synthesizing composition commit-as-is");
+ is(result[3].type, "input",
+ "runRedundantChangeTest: input should be fired before compositionend at synthesizing commit-as-is");
+ checkInputEvent(result[3], false, "insertCompositionText", "\u3042\u3044", [],
+ "runRedundantChangeTest: at synthesizing commit-as-is");
+ is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has the commit string");
+
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+ textarea.removeEventListener("beforeinput", handler, true);
+ textarea.removeEventListener("input", handler, true);
+ textarea.removeEventListener("text", handler, true);
+}
+
+function runNotRedundantChangeTest()
+{
+ textarea.focus();
+
+ let result = [];
+ function clearResult()
+ {
+ result = [];
+ }
+
+ function handler(aEvent)
+ {
+ result.push(aEvent);
+ }
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", handler, true);
+ textarea.addEventListener("beforeinput", handler, true);
+ textarea.addEventListener("input", handler, true);
+ textarea.addEventListener("text", handler, true);
+
+ textarea.value = "abcde";
+
+ // synthesize change event with non-null ranges
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABCDE",
+ "clauses":
+ [
+ { "length": 5, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 5, "length": 0 }
+ });
+
+ is(result.length, 4,
+ "runNotRedundantChangeTest: 4 events should be fired after synthesizing composition change with non-null ranges");
+ is(result[0].type, "compositionupdate",
+ "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with non-null ranges");
+ is(result[1].type, "text",
+ "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges");
+ is(result[2].type, "beforeinput",
+ "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition change with non-null ranges");
+ checkInputEvent(result[2], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges");
+ is(result[3].type, "input",
+ "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges");
+ checkInputEvent(result[3], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges");
+ is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #1");
+
+ // synthesize change event with null ranges
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABCDE",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ });
+ is(result.length, 3,
+ "runNotRedundantChangeTest: 3 events should be fired after synthesizing composition change with null ranges after non-null ranges");
+ is(result[0].type, "text",
+ "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with null ranges after non-null ranges");
+ is(result[1].type, "beforeinput",
+ "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition change because it's dispatched when there is composing string with null ranges after non-null ranges");
+ checkInputEvent(result[1], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with null ranges after non-null ranges");
+ is(result[2].type, "input",
+ "runNotRedundantChangeTest: input should be fired after synthesizing composition change with null ranges after non-null ranges");
+ checkInputEvent(result[2], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with null ranges after non-null ranges");
+ is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #2");
+
+ // synthesize change event with non-null ranges
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABCDE",
+ "clauses":
+ [
+ { "length": 5, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 5, "length": 0 }
+ });
+
+ is(result.length, 3,
+ "runNotRedundantChangeTest: 3 events should be fired after synthesizing composition change with null ranges after non-null ranges");
+ is(result[0].type, "text",
+ "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges after null ranges");
+ is(result[1].type, "beforeinput",
+ "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges after null ranges");
+ checkInputEvent(result[1], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after null ranges");
+ is(result[2].type, "input",
+ "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges after null ranges");
+ checkInputEvent(result[2], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after null ranges");
+ is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #3");
+
+ // synthesize change event with empty data and null ranges
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ });
+ is(result.length, 4,
+ "runNotRedundantChangeTest: 4 events should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
+ is(result[0].type, "compositionupdate",
+ "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
+ is(result[1].type, "text",
+ "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with empty data and null ranges after non-null ranges");
+ is(result[2].type, "beforeinput",
+ "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
+ checkInputEvent(result[2], true, "insertCompositionText", "", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with empty data and null ranges after non-null ranges");
+ is(result[3].type, "input",
+ "runNotRedundantChangeTest: input should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
+ checkInputEvent(result[3], true, "insertCompositionText", "", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with empty data and null ranges after non-null ranges");
+ is(textarea.value, "abcde", "runNotRedundantChangeTest: textarea doesn't have uncommitted string #1");
+
+ // synthesize change event with non-null ranges
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABCDE",
+ "clauses":
+ [
+ { "length": 5, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 5, "length": 0 }
+ });
+
+ is(result.length, 4,
+ "runNotRedundantChangeTest: 4 events should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
+ is(result[0].type, "compositionupdate",
+ "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
+ is(result[1].type, "text",
+ "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges after empty data and null ranges");
+ is(result[2].type, "beforeinput",
+ "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
+ checkInputEvent(result[2], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after empty data and null ranges");
+ is(result[3].type, "input",
+ "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
+ checkInputEvent(result[3], true, "insertCompositionText", "ABCDE", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after empty data and null ranges");
+ is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #4");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "" });
+
+ is(result.length, 5,
+ "runNotRedundantChangeTest: 5 events should be fired after synthesizing composition commit with empty data after non-empty data");
+ is(result[0].type, "compositionupdate",
+ "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition commit with empty data after non-empty data");
+ is(result[1].type, "text",
+ "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with empty data after non-empty data");
+ is(result[2].type, "beforeinput",
+ "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition commit with empty data after non-empty data");
+ checkInputEvent(result[2], true, "insertCompositionText", "", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with empty data after non-empty data");
+ is(result[3].type, "compositionend",
+ "runNotRedundantChangeTest: compositionend should be fired after synthesizing composition commit with empty data after non-empty data");
+ is(result[4].type, "input",
+ "runNotRedundantChangeTest: input should be fired after compositionend after synthesizing composition change with empty data after non-empty data");
+ checkInputEvent(result[4], false, "insertCompositionText", "", [],
+ "runNotRedundantChangeTest: after synthesizing composition change with empty data after non-empty data");
+ is(textarea.value, "abcde", "runNotRedundantChangeTest: textarea doesn't have uncommitted string #2");
+
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+ textarea.removeEventListener("beforeinput", handler, true);
+ textarea.removeEventListener("input", handler, true);
+ textarea.removeEventListener("text", handler, true);
+}
+
+function runNativeLineBreakerTest()
+{
+ textarea.focus();
+
+ let result = {};
+ function clearResult()
+ {
+ result = { compositionupdate: null, compositionend: null };
+ }
+
+ function handler(aEvent)
+ {
+ result[aEvent.type] = aEvent.data;
+ }
+
+ Services.prefs.setBoolPref("dom.compositionevent.allow_control_characters", false);
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", handler, true);
+
+ // '\n' in composition string shouldn't be changed.
+ clearResult();
+ textarea.value = "";
+ let clauses = [ "abc\n", "def\n\ng", "hi\n", "\njkl" ];
+ let caret = clauses[0] + clauses[1] + clauses[2];
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": clauses.join(''),
+ "clauses":
+ [
+ { "length": clauses[0].length,
+ "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ { "length": clauses[1].length,
+ "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
+ { "length": clauses[2].length,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": clauses[3].length,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ ]
+ },
+ "caret": { "start": caret.length, "length": 0 }
+ });
+
+ checkSelection(caret.replace(/\n/g, "\n").length + (kLFLen - 1) * 4, "", "runNativeLineBreakerTest", "#1");
+ checkIMESelection("RawClause", true, 0, clauses[0].replace(/\n/g, kLF), "runNativeLineBreakerTest: \\n shouldn't be replaced with any character #1");
+ checkIMESelection("SelectedRawClause", true, clauses[0].replace(/\n/g, kLF).length, clauses[1].replace(/\n/g, kLF), "runNativeLineBreakerTest: \\n shouldn't be replaced with any character #1");
+ checkIMESelection("ConvertedClause", true, (clauses[0] + clauses[1]).replace(/\n/g, kLF).length, clauses[2].replace(/\n/g, kLF), "runNativeLineBreakerTest: \\n shouldn't be replaced with any character #1");
+ checkIMESelection("SelectedClause", true, (clauses[0] + clauses[1] + clauses[2]).replace(/\n/g, kLF).length, clauses[3].replace(/\n/g, kLF), "runNativeLineBreakerTest: \\n shouldn't be replaced with any character #1");
+ is(result.compositionupdate, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in compositionupdate.data shouldn't be removed nor replaced with other characters #1");
+ is(textarea.value, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in textarea.value shouldn't be removed nor replaced with other characters #1");
+
+ synthesizeComposition({ type: "compositioncommit", data: clauses.join('') });
+ checkSelection(clauses.join('').replace(/\n/g, "\n").length + (kLFLen - 1) * 5, "", "runNativeLineBreakerTest", "#2");
+ is(result.compositionend, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in compositionend.data shouldn't be removed nor replaced with other characters #2");
+ is(textarea.value, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in textarea.value shouldn't be removed nor replaced with other characters #2");
+
+ // \r\n in composition string should be replaced with \n.
+ clearResult();
+ textarea.value = "";
+ clauses = [ "abc\r\n", "def\r\n\r\ng", "hi\r\n", "\r\njkl" ];
+ caret = clauses[0] + clauses[1] + clauses[2];
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": clauses.join(''),
+ "clauses":
+ [
+ { "length": clauses[0].length,
+ "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ { "length": clauses[1].length,
+ "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
+ { "length": clauses[2].length,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": clauses[3].length,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ ]
+ },
+ "caret": { "start": caret.length, "length": 0 }
+ });
+
+ checkSelection(caret.replace(/\r\n/g, "\n").length + (kLFLen - 1) * 4, "", "runNativeLineBreakerTest", "#3");
+ checkIMESelection("RawClause", true, 0, clauses[0].replace(/\r\n/g, kLF), "runNativeLineBreakerTest: \\r\\n should be replaced with \\n #3");
+ checkIMESelection("SelectedRawClause", true, clauses[0].replace(/\r\n/g, kLF).length, clauses[1].replace(/\r\n/g, kLF), "runNativeLineBreakerTest: \\r\\n should be replaced with \\n #3");
+ checkIMESelection("ConvertedClause", true, (clauses[0] + clauses[1]).replace(/\r\n/g, kLF).length, clauses[2].replace(/\r\n/g, kLF), "runNativeLineBreakerTest: \\r\\n should be replaced with \\n #3");
+ checkIMESelection("SelectedClause", true, (clauses[0] + clauses[1] + clauses[2]).replace(/\r\n/g, kLF).length, clauses[3].replace(/\r\n/g, kLF), "runNativeLineBreakerTest: \\r\\n should be replaced with \\n #3");
+ is(result.compositionupdate, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in compositionudpate.data should be replaced with \\n #3");
+ is(textarea.value, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in textarea.value should be replaced with \\n #3");
+
+ synthesizeComposition({ type: "compositioncommit", data: clauses.join('') });
+ checkSelection(clauses.join('').replace(/\r\n/g, "\n").length + (kLFLen - 1) * 5, "", "runNativeLineBreakerTest", "#4");
+ is(result.compositionend, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in compositionend.data should be replaced with \\n #4");
+ is(textarea.value, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in textarea.value should be replaced with \\n #4");
+
+ // \r (not followed by \n) in composition string should be replaced with \n.
+ clearResult();
+ textarea.value = "";
+ clauses = [ "abc\r", "def\r\rg", "hi\r", "\rjkl" ];
+ caret = clauses[0] + clauses[1] + clauses[2];
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": clauses.join(''),
+ "clauses":
+ [
+ { "length": clauses[0].length,
+ "attr": COMPOSITION_ATTR_RAW_CLAUSE },
+ { "length": clauses[1].length,
+ "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
+ { "length": clauses[2].length,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": clauses[3].length,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ ]
+ },
+ "caret": { "start": caret.length, "length": 0 }
+ });
+
+ checkSelection(caret.replace(/\r/g, "\n").length + (kLFLen - 1) * 4, "", "runNativeLineBreakerTest", "#5");
+ checkIMESelection("RawClause", true, 0, clauses[0].replace(/\r/g, kLF), "runNativeLineBreakerTest: \\r should be replaced with \\n #5");
+ checkIMESelection("SelectedRawClause", true, clauses[0].replace(/\r/g, kLF).length, clauses[1].replace(/\r/g, kLF), "runNativeLineBreakerTest: \\r should be replaced with \\n #5");
+ checkIMESelection("ConvertedClause", true, (clauses[0] + clauses[1]).replace(/\r/g, kLF).length, clauses[2].replace(/\r/g, kLF), "runNativeLineBreakerTest: \\r should be replaced with \\n #5");
+ checkIMESelection("SelectedClause", true, (clauses[0] + clauses[1] + clauses[2]).replace(/\r/g, kLF).length, clauses[3].replace(/\r/g, kLF), "runNativeLineBreakerTest: \\r should be replaced with \\n #5");
+ is(result.compositionupdate, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in compositionupdate.data should be replaced with \\n #5");
+ is(textarea.value, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in textarea.value should be replaced with \\n #5");
+
+ synthesizeComposition({ type: "compositioncommit", data: clauses.join('') });
+ checkSelection(clauses.join('').replace(/\r/g, "\n").length + (kLFLen - 1) * 5, "", "runNativeLineBreakerTest", "#6");
+ is(result.compositionend, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in compositionend.data should be replaced with \\n #6");
+ is(textarea.value, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in textarea.value should be replaced with \\n #6");
+
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+
+ Services.prefs.clearUserPref("dom.compositionevent.allow_control_characters");
+}
+
+function runControlCharTest()
+{
+ textarea.focus();
+
+ let result = {};
+ function clearResult()
+ {
+ result = { compositionupdate: null, compositionend: null };
+ }
+
+ function handler(aEvent)
+ {
+ result[aEvent.type] = aEvent.data;
+ }
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", handler, true);
+
+ textarea.value = "";
+
+ let controlChars = String.fromCharCode.apply(null, Object.keys(Array.from({length:0x20}))) + "\x7F";
+ let allowedChars = "\t\n\n";
+
+ let data = "AB" + controlChars + "CD" + controlChars + "EF";
+ let removedData = "AB" + allowedChars + "CD" + allowedChars + "EF";
+
+ let DIndex = data.indexOf("D");
+ let removedDIndex = removedData.indexOf("D");
+
+ // input string contains control characters
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": data,
+ "clauses":
+ [
+ { "length": DIndex,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": data.length - DIndex,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": DIndex, "length": 0 }
+ });
+
+ checkSelection(removedDIndex + (kLFLen - 1) * 2, "", "runControlCharTest", "#1")
+
+ is(result.compositionupdate, removedData, "runControlCharTest: control characters in event.data should be removed in compositionupdate event #1");
+ is(textarea.value, removedData, "runControlCharTest: control characters should not appear in textarea #1");
+
+ synthesizeComposition({ type: "compositioncommit", data });
+
+ is(result.compositionend, removedData, "runControlCharTest: control characters in event.data should be removed in compositionend event #2");
+ is(textarea.value, removedData, "runControlCharTest: control characters should not appear in textarea #2");
+
+ textarea.value = "";
+
+ clearResult();
+
+ Services.prefs.setBoolPref("dom.compositionevent.allow_control_characters", true);
+
+ // input string contains control characters, allowing control characters
+ clearResult();
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": data,
+ "clauses":
+ [
+ { "length": DIndex,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": data.length - DIndex,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": DIndex, "length": 0 }
+ });
+
+ checkSelection(DIndex + (kLFLen - 1) * 2, "", "runControlCharTest", "#3")
+
+ is(result.compositionupdate, data.replace(/\r/g, "\n"), "runControlCharTest: control characters in event.data should not be removed in compositionupdate event #3");
+ is(textarea.value, data.replace(/\r/g, "\n"), "runControlCharTest: control characters should appear in textarea #3");
+
+ synthesizeComposition({ type: "compositioncommit", data });
+
+ is(result.compositionend, data.replace(/\r/g, "\n"), "runControlCharTest: control characters in event.data should not be removed in compositionend event #4");
+ is(textarea.value, data.replace(/\r/g, "\n"), "runControlCharTest: control characters should appear in textarea #4");
+
+ Services.prefs.clearUserPref("dom.compositionevent.allow_control_characters");
+
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+}
+
+async function runRemoveContentTest()
+{
+ let events = [];
+ function eventHandler(aEvent)
+ {
+ events.push(aEvent);
+ }
+ textarea.addEventListener("compositionstart", eventHandler, true);
+ textarea.addEventListener("compositionupdate", eventHandler, true);
+ textarea.addEventListener("compositionend", eventHandler, true);
+ textarea.addEventListener("beforeinput", eventHandler, true);
+ textarea.addEventListener("input", eventHandler, true);
+ textarea.addEventListener("text", eventHandler, true);
+
+ textarea.focus();
+ textarea.value = "";
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ let nextSibling = textarea.nextSibling;
+ let parent = textarea.parentElement;
+
+ events = [];
+ parent.removeChild(textarea);
+
+ await waitForEventLoops(50);
+
+ // XXX Currently, "input" event and "beforeinput" event are not fired on removed content.
+ is(events.length, 3,
+ "runRemoveContentTest: wrong event count #1");
+ is(events[0].type, "compositionupdate",
+ "runRemoveContentTest: the 1st event must be compositionupdate #1");
+ is(events[0].target, textarea,
+ `runRemoveContentTest: The "${events[0].type}" event was fired on wrong event target #1`);
+ is(events[0].data, "",
+ "runRemoveContentTest: compositionupdate has wrong data #1");
+ is(events[1].type, "text",
+ "runRemoveContentTest: the 2nd event must be text #1");
+ is(events[1].target, textarea,
+ `runRemoveContentTest: The "${events[1].type}" event was fired on wrong event target #1`);
+ todo_is(events[2].type, "beforeinput",
+ "runRemoveContentTest: the 3rd event must be beforeinput #1");
+ // is(events[2].target, textarea,
+ // `runRemoveContentTest: The "${events[2].type}" event was fired on wrong event target #1`);
+ // checkInputEvent(events[2], true, "insertCompositionText", "", [],
+ // "runRemoveContentTest: #1");
+ is(events[2].type, "compositionend",
+ "runRemoveContentTest: the 4th event must be compositionend #1");
+ is(events[2].target, textarea,
+ `runRemoveContentTest: The "${events[2].type}" event was fired on wrong event target #1`);
+ is(events[2].data, "",
+ "runRemoveContentTest: compositionend has wrong data #1");
+ ok(!getEditor(textarea).isComposing,
+ "runRemoveContentTest: the textarea still has composition #1");
+ todo_is(textarea.value, "",
+ "runRemoveContentTest: the textarea has the committed text? #1");
+
+ parent.insertBefore(textarea, nextSibling);
+
+ textarea.focus();
+ textarea.value = "";
+
+ synthesizeComposition({ type: "compositionstart" });
+
+ events = [];
+ parent.removeChild(textarea);
+
+ await waitForEventLoops(50);
+
+ // XXX Currently, "input" event and "beforeinput" event are not fired on removed content.
+ is(events.length, 2,
+ "runRemoveContentTest: wrong event count #2");
+ is(events[0].type, "text",
+ "runRemoveContentTest: the 1st event must be text #2");
+ is(events[0].target, textarea,
+ `runRemoveContentTest: The ${events[0].type} event was fired on wrong event target #2`);
+ todo_is(events[1].type, "beforeinput",
+ "runRemoveContentTest: the 2nd event must be beforeinput #2");
+ // is(events[1].target, textarea,
+ // `runRemoveContentTest: The ${events[1].type} event was fired on wrong event target #2`);
+ // checkInputEvent(events[1], true, "insertCompositionText", "", [],
+ // "runRemoveContentTest: #2");
+ is(events[1].type, "compositionend",
+ "runRemoveContentTest: the 3rd event must be compositionend #2");
+ is(events[1].target, textarea,
+ `runRemoveContentTest: The ${events[1].type} event was fired on wrong event target #2`);
+ is(events[1].data, "",
+ "runRemoveContentTest: compositionupdate has wrong data #2");
+ ok(!getEditor(textarea).isComposing,
+ "runRemoveContentTest: the textarea still has composition #2");
+ is(textarea.value, "",
+ "runRemoveContentTest: the textarea has the committed text? #2");
+
+ parent.insertBefore(textarea, nextSibling);
+
+ textarea.removeEventListener("compositionstart", eventHandler, true);
+ textarea.removeEventListener("compositionupdate", eventHandler, true);
+ textarea.removeEventListener("compositionend", eventHandler, true);
+ textarea.removeEventListener("beforeinput", eventHandler, true);
+ textarea.removeEventListener("input", eventHandler, true);
+ textarea.removeEventListener("text", eventHandler, true);
+
+ await waitForTick();
+}
+
+function runTestOnAnotherContext(aPanelOrFrame, aFocusedEditor, aTestName)
+{
+ aFocusedEditor.value = "";
+
+ // The frames and panel are cross-origin, and we no longer
+ // propagate flushes to parent cross-origin iframes explicitly,
+ // so flush our own layout here so the positions are correct.
+ document.documentElement.getBoundingClientRect();
+
+ let editorRect = synthesizeQueryEditorRect();
+ if (!checkQueryContentResult(editorRect, aTestName + ": editorRect")) {
+ return;
+ }
+
+ let r = aPanelOrFrame.getBoundingClientRect();
+ let parentRect = {
+ left: r.left * window.devicePixelRatio,
+ top: r.top * window.devicePixelRatio,
+ width: (r.right - r.left) * window.devicePixelRatio,
+ height: (r.bottom - r.top) * window.devicePixelRatio,
+ };
+ checkRectContainsRect(editorRect, parentRect, aTestName +
+ ": the editor rect coordinates are wrong");
+
+ // input characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3078\u3093\u3057\u3093",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 }
+ });
+
+ if (!checkContent("\u3078\u3093\u3057\u3093", aTestName, "#1-1") ||
+ !checkSelection(4, "", aTestName, "#1-1")) {
+ return;
+ }
+
+ // convert them #1
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u8FD4\u4FE1",
+ "clauses":
+ [
+ { "length": 2,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ if (!checkContent("\u8FD4\u4FE1", aTestName, "#1-2") ||
+ !checkSelection(2, "", aTestName, "#1-2")) {
+ return;
+ }
+
+ // convert them #2
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u5909\u8EAB",
+ "clauses":
+ [
+ { "length": 2,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ if (!checkContent("\u5909\u8EAB", aTestName, "#1-3") ||
+ !checkSelection(2, "", aTestName, "#1-3")) {
+ return;
+ }
+
+ // commit them
+ synthesizeComposition({ type: "compositioncommitasis" });
+ if (!checkContent("\u5909\u8EAB", aTestName, "#1-4") ||
+ !checkSelection(2, "", aTestName, "#1-4")) {
+ return;
+ }
+
+ is(aFocusedEditor.value, "\u5909\u8EAB",
+ aTestName + ": composition isn't in the focused editor");
+ if (aFocusedEditor.value != "\u5909\u8EAB") {
+ return;
+ }
+
+ let textRect = synthesizeQueryTextRect(0, 1);
+ let caretRect = synthesizeQueryCaretRect(2);
+ if (!checkQueryContentResult(textRect,
+ aTestName + ": synthesizeQueryTextRect") ||
+ !checkQueryContentResult(caretRect,
+ aTestName + ": synthesizeQueryCaretRect")) {
+ return;
+ }
+ checkRectContainsRect(textRect, editorRect, aTestName + ":testRect");
+ checkRectContainsRect(caretRect, editorRect, aTestName + ":caretRect");
+}
+
+function runFrameTest()
+{
+ textareaInFrame.focus();
+ runTestOnAnotherContext(iframe, textareaInFrame, "runFrameTest");
+ runCharAtPointTest(textareaInFrame, "textarea in the iframe");
+}
+
+async function runPanelTest()
+{
+ panel.hidden = false;
+ let waitOpenPopup = new Promise(resolve => {
+ panel.addEventListener("popupshown", resolve, {once: true});
+ });
+ let waitFocusTextBox = new Promise(resolve => {
+ textbox.addEventListener("focus", resolve, {once: true});
+ });
+ panel.openPopupAtScreen(window.screenX + window.outerWidth, 0, false);
+ await waitOpenPopup;
+ textbox.focus();
+ await waitFocusTextBox;
+ is(panel.state, "open", "The panel should be open (after textbox.focus())");
+ await waitForTick();
+ is(panel.state, "open", "The panel should be open (after waitForTick())");
+ runTestOnAnotherContext(panel, textbox, "runPanelTest");
+ is(panel.state, "open", "The panel should be open (after runTestOnAnotherContext())");
+ runCharAtPointTest(textbox, "textbox in the panel");
+ is(panel.state, "open", "The panel should be open (after runCharAtPointTest())");
+ let waitClosePopup = new Promise(resolve => {
+ panel.addEventListener("popuphidden", resolve, {once: true});
+ });
+ panel.hidePopup();
+ await waitClosePopup;
+ await waitForTick();
+}
+
+// eslint-disable-next-line complexity
+function runMaxLengthTest()
+{
+ input.maxLength = 1;
+ input.value = "";
+ input.focus();
+
+ let kDesc ="runMaxLengthTest";
+
+ // input first character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ if (!checkContent("\u3089", kDesc, "#1-1") ||
+ !checkSelection(1, "", kDesc, "#1-1")) {
+ return;
+ }
+
+ // input second character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC", kDesc, "#1-2") ||
+ !checkSelection(2, "", kDesc, "#1-2")) {
+ return;
+ }
+
+ // input third character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081", kDesc, "#1-3") ||
+ !checkSelection(3, "", kDesc, "#1-3")) {
+ return;
+ }
+
+ // input fourth character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093", kDesc, "#1-4") ||
+ !checkSelection(4, "", kDesc, "#1-4")) {
+ return;
+ }
+
+
+ // backspace
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081", kDesc, "#1-5") ||
+ !checkSelection(3, "", kDesc, "#1-5")) {
+ return;
+ }
+
+ // re-input
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093", kDesc, "#1-6") ||
+ !checkSelection(4, "", kDesc, "#1-6")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055",
+ "clauses":
+ [
+ { "length": 5, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 5, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055", kDesc, "#1-7") ||
+ !checkSelection(5, "", kDesc, "#1-7")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044",
+ "clauses":
+ [
+ { "length": 6, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 6, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044", kDesc, "#1-8") ||
+ !checkSelection(6, "", kDesc, "#1-8")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053",
+ "clauses":
+ [
+ { "length": 7, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 7, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053",
+ kDesc, "#1-8") ||
+ !checkSelection(7, "", kDesc, "#1-8")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
+ "clauses":
+ [
+ { "length": 8, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 8, "length": 0 }
+ });
+
+ if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
+ kDesc, "#1-9") ||
+ !checkSelection(8, "", kDesc, "#1-9")) {
+ return;
+ }
+
+ // convert
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
+ "clauses":
+ [
+ { "length": 4,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 2,
+ "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 }
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8", kDesc, "#1-10") ||
+ !checkSelection(4, "", kDesc, "#1-10")) {
+ return;
+ }
+
+ // commit the composition string
+ synthesizeComposition({ type: "compositioncommitasis" });
+ if (!checkContent("\u30E9", kDesc, "#1-11") ||
+ !checkSelection(1, "", kDesc, "#1-11")) {
+ return;
+ }
+
+ // input characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3057",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ if (!checkContent("\u30E9\u3057", kDesc, "#2-1") ||
+ !checkSelection(1 + 1, "", kDesc, "#2-1")) {
+ return;
+ }
+
+ // commit the composition string
+ synthesizeComposition({ type: "compositioncommit", data: "\u3058" });
+ if (!checkContent("\u30E9", kDesc, "#2-2") ||
+ !checkSelection(1 + 0, "", kDesc, "#2-2")) {
+ return;
+ }
+
+ // Undo
+ synthesizeKey("Z", {accelKey: true});
+
+ // XXX this is unexpected behavior, see bug 258291
+ if (!checkContent("\u30E9", kDesc, "#3-1") ||
+ !checkSelection(1 + 0, "", kDesc, "#3-1")) {
+ return;
+ }
+
+ // Undo
+ synthesizeKey("Z", {accelKey: true});
+ if (!checkContent("", kDesc, "#3-2") ||
+ !checkSelection(0, "", kDesc, "#3-2")) {
+ return;
+ }
+
+ // Redo
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+ if (!checkContent("\u30E9", kDesc, "#3-3") ||
+ !checkSelection(1, "", kDesc, "#3-3")) {
+ return;
+ }
+
+ // Redo
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+ if (!checkContent("\u30E9", kDesc, "#3-4") ||
+ !checkSelection(1 + 0, "", kDesc, "#3-4")) {
+ return;
+ }
+
+ // The input element whose content length is already maxlength and
+ // the carest is at start of the content.
+ input.value = "X";
+ input.selectionStart = input.selectionEnd = 0;
+
+ // input characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u9B54",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ if (!checkContent("\u9B54X", kDesc, "#4-1") ||
+ !checkSelection(1, "", kDesc, "#4-1")) {
+ return;
+ }
+
+ // commit the composition string
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ // The input text must be discarded. Then, the caret position shouldn't be
+ // updated from its position at compositionstart.
+ if (!checkContent("X", kDesc, "#4-2") ||
+ !checkSelection(0, "", kDesc, "#4-2")) {
+ return;
+ }
+
+ // input characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u9B54\u6CD5",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ if (!checkContent("\u9B54\u6CD5X", kDesc, "#5-1") ||
+ !checkSelection(2, "", kDesc, "#5-1")) {
+ return;
+ }
+
+ // commit the composition string
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ if (checkContent("X", kDesc, "#5-2")) {
+ checkSelection(0, "", kDesc, "#5-2");
+ }
+}
+
+async function runEditorReframeTests()
+{
+ async function runEditorReframeTest(aEditor, aWindow, aEventType)
+ {
+ function getValue()
+ {
+ return aEditor == contenteditable ?
+ aEditor.innerHTML.replace("<br>", "") : aEditor.value;
+ }
+
+ let description = "runEditorReframeTest(" + aEditor.id + ", \"" + aEventType + "\"): ";
+
+ let tests = [
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "a",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "a", description + "Typing 'a'");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ab",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ab", description + "Typing 'b' next to 'a'");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "abc",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "abc", description + "Typing 'c' next to 'ab'");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "abc",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "abc", description + "Starting to convert 'ab][c'");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABc",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABc", description + "Starting to convert 'AB][c'");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ABC",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
+ { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABC", description + "Starting to convert 'AB][C'");
+ },
+ },
+ { test () {
+ // Commit composition
+ synthesizeComposition({ type: "compositioncommitasis" });
+ },
+ check () {
+ is(getValue(aEditor), "ABC", description + "Committed as 'ABC'");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "d",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABCd", description + "Typing 'd' next to ABC");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "de",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABCde", description + "Typing 'e' next to ABCd");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "def",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABCdef", description + "Typing 'f' next to ABCde");
+ },
+ },
+ { test () {
+ // Commit composition
+ synthesizeComposition({ type: "compositioncommitasis" });
+ },
+ check () {
+ is(getValue(aEditor), "ABCdef", description + "Commit 'def' without convert");
+ },
+ },
+ { test () {
+ // Select "Cd"
+ synthesizeKey("KEY_ArrowLeft");
+ synthesizeKey("KEY_ArrowLeft");
+ synthesizeKey("KEY_Shift", {type: "keydown", shiftKey: true});
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
+ synthesizeKey("KEY_Shift", {type: "keyup"});
+ },
+ check () {
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "g",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABgef", description + "Typing 'g' next to AB");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "gh",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABghef", description + "Typing 'h' next to ABg");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ghi",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABghief", description + "Typing 'i' next to ABgh");
+ },
+ },
+ { test () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "GHI",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+ },
+ check () {
+ is(getValue(aEditor), "ABGHIef", description + "Convert 'ghi' to 'GHI'");
+ },
+ },
+ { test () {
+ // Commit composition
+ synthesizeComposition({ type: "compositioncommitasis" });
+ },
+ check () {
+ is(getValue(aEditor), "ABGHIef", description + "Commit 'GHI'");
+ },
+ },
+ ];
+
+ function doReframe(aEvent)
+ {
+ aEvent.target.style.overflow =
+ aEvent.target.style.overflow != "hidden" ? "hidden" : "auto";
+ }
+ aEditor.focus();
+ aEditor.addEventListener(aEventType, doReframe);
+
+ for (const currentTest of tests) {
+ currentTest.test();
+ await waitForEventLoops(20);
+ currentTest.check();
+ await waitForTick();
+ }
+
+ await new Promise(resolve => {
+ aEditor.style.overflow = "auto";
+ aEditor.removeEventListener(aEventType, doReframe);
+ requestAnimationFrame(() => { SimpleTest.executeSoon(resolve); });
+ });
+ }
+
+ // TODO: Add "beforeinput" case.
+ input.value = "";
+ await runEditorReframeTest(input, window, "input");
+ input.value = "";
+ await runEditorReframeTest(input, window, "compositionupdate");
+ textarea.value = "";
+ await runEditorReframeTest(textarea, window, "input");
+ textarea.value = "";
+ await runEditorReframeTest(textarea, window, "compositionupdate");
+ contenteditable.innerHTML = "";
+ await runEditorReframeTest(contenteditable, windowOfContenteditable, "input");
+ contenteditable.innerHTML = "";
+ await runEditorReframeTest(contenteditable, windowOfContenteditable, "compositionupdate");
+}
+
+async function runIMEContentObserverTest()
+{
+ let notifications = [];
+ let onReceiveNotifications = null;
+ function callback(aTIP, aNotification)
+ {
+ if (aNotification.type != "notify-end-input-transaction") {
+ notifications.push(aNotification);
+ }
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ }
+ if (onReceiveNotifications) {
+ let resolve = onReceiveNotifications;
+ onReceiveNotifications = null;
+ SimpleTest.executeSoon(() => {
+ resolve();
+ });
+ }
+ return true;
+ }
+
+ function dumpUnexpectedNotifications(aDescription, aExpectedCount)
+ {
+ if (notifications.length <= aExpectedCount) {
+ return;
+ }
+ for (let i = aExpectedCount; i < notifications.length; i++) {
+ ok(false,
+ aDescription + " caused unexpected notification: " + notifications[i].type);
+ }
+ }
+
+ function promiseReceiveNotifications()
+ {
+ notifications = [];
+ return new Promise(resolve => {
+ onReceiveNotifications = resolve;
+ });
+ }
+
+ function flushNotifications()
+ {
+ return new Promise(resolve => {
+ // FYI: Dispatching non-op keyboard events causes forcibly flushing pending
+ // notifications.
+ synthesizeKey("KEY_Unidentified", { code: "" });
+ SimpleTest.executeSoon(()=>{
+ notifications = [];
+ resolve();
+ });
+ });
+ }
+
+ function ensureToRemovePrecedingPositionChangeNotification(aDescription)
+ {
+ if (!notifications.length) {
+ return;
+ }
+ if (notifications[0].type != "notify-position-change") {
+ return;
+ }
+ // Sometimes, notify-position-change is notified first separately if
+ // the operation causes scroll or something. Tests can ignore this.
+ ok(true, "notify-position-change", aDescription + "Unnecessary notify-position-change occurred, ignoring it");
+ notifications.shift();
+ }
+
+ // Bug 1374057 - On ubuntu 16.04 there are notify-position-change events that are
+ // recorded after all the other events so we remove them through this function.
+ function ensureToRemovePostPositionChangeNotification(aDescription, expectedCount)
+ {
+ if (!notifications.length) {
+ return;
+ }
+ if (notifications.length <= expectedCount) {
+ return;
+ }
+ if (notifications[notifications.length-1].type != "notify-position-change") {
+ return;
+ }
+ ok(true, "notify-position-change", aDescription + "Unnecessary notify-position-change occurred, ignoring it");
+ notifications.pop();
+ }
+
+ function getNativeText(aXPText)
+ {
+ if (kLF == "\n") {
+ return aXPText;
+ }
+ return aXPText.replace(/\n/g, kLF);
+ }
+
+ function checkPositionChangeNotification(aNotification, aDescription)
+ {
+ is(!aNotification || aNotification.type, "notify-position-change",
+ aDescription + " should cause position change notification");
+ }
+
+ function checkSelectionChangeNotification(aNotification, aDescription, aExpected)
+ {
+ is(!aNotification || aNotification.type, "notify-selection-change",
+ aDescription + " should cause selection change notification");
+ if (!aNotification || (aNotification.type != "notify-selection-change")) {
+ return;
+ }
+ is(aNotification.offset, aExpected.offset,
+ aDescription + " should cause selection change notification whose offset is " + aExpected.offset);
+ is(aNotification.text, aExpected.text,
+ aDescription + " should cause selection change notification whose text is '" + aExpected.text + "'");
+ is(aNotification.collapsed, !aExpected.text.length,
+ aDescription + " should cause selection change notification whose collapsed is " + (!aExpected.text.length));
+ is(aNotification.length, aExpected.text.length,
+ aDescription + " should cause selection change notification whose length is " + aExpected.text.length);
+ is(aNotification.reversed, aExpected.reversed || false,
+ aDescription + " should cause selection change notification whose reversed is " + (aExpected.reversed || false));
+ is(aNotification.writingMode, aExpected.writingMode || "horizontal-tb",
+ aDescription + " should cause selection change notification whose writingMode is '" + (aExpected.writingMode || "horizontal-tb"));
+ }
+
+ function checkTextChangeNotification(aNotification, aDescription, aExpected)
+ {
+ is(!aNotification || aNotification.type, "notify-text-change",
+ aDescription + " should cause text change notification");
+ if (!aNotification || aNotification.type != "notify-text-change") {
+ return;
+ }
+ is(aNotification.offset, aExpected.offset,
+ aDescription + " should cause text change notification whose offset is " + aExpected.offset);
+ is(aNotification.removedLength, aExpected.removedLength,
+ aDescription + " should cause text change notification whose removedLength is " + aExpected.removedLength);
+ is(aNotification.addedLength, aExpected.addedLength,
+ aDescription + " should cause text change notification whose addedLength is " + aExpected.addedLength);
+ }
+
+ async function testWithPlaintextEditor(aDescription, aElement, aTestLineBreaker)
+ {
+ aElement.value = "";
+ aElement.blur();
+ let doc = aElement.ownerDocument;
+ let win = doc.defaultView;
+ aElement.focus();
+ await flushNotifications();
+
+ // "a[]"
+ let description = aDescription + "typing 'a'";
+ let waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("a", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 0, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 1, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "ab[]"
+ description = aDescription + "typing 'b'";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("b", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 0, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "abc[]"
+ description = aDescription + "typing 'c'";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("c", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: 0, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 3, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "ab[c]"
+ description = aDescription + "selecting 'c' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 2, text: "c", reversed: true });
+ ensureToRemovePostPositionChangeNotification(description, 1);
+ dumpUnexpectedNotifications(description, 1);
+
+ // "a[bc]"
+ description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 1, text: "bc", reversed: true });
+ ensureToRemovePostPositionChangeNotification(description, 1);
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[abc]"
+ description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "abc", reversed: true });
+ ensureToRemovePostPositionChangeNotification(description, 1);
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[]abc"
+ description = aDescription + "collapsing selection to the left-most with pressing ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "" });
+ ensureToRemovePostPositionChangeNotification(description, 1);
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[a]bc"
+ description = aDescription + "selecting 'a' with pressing Shift+ArrowRight";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "a" });
+ ensureToRemovePostPositionChangeNotification(description, 1);
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[ab]c"
+ description = aDescription + "selecting 'ab' with pressing Shift+ArrowRight";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "ab" });
+ ensureToRemovePostPositionChangeNotification(description, 1);
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[]c"
+ description = aDescription + "deleting 'ab' with pressing Delete";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 2, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "[]"
+ description = aDescription + "deleting following 'c' with pressing Delete";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 1, addedLength: 0 });
+ checkPositionChangeNotification(notifications[1], description);
+ ensureToRemovePostPositionChangeNotification(description, 2);
+ dumpUnexpectedNotifications(description, 2);
+
+ // "abc[]"
+ synthesizeKey("a", {}, win, callback);
+ synthesizeKey("b", {}, win, callback);
+ synthesizeKey("c", {}, win, callback);
+ await flushNotifications();
+
+ // "ab[]"
+ description = aDescription + "deleting 'c' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: 1, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "[ab]"
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "[]"
+ description = aDescription + "deleting 'ab' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 2, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "abcd[]"
+ synthesizeKey("a", {}, win, callback);
+ synthesizeKey("b", {}, win, callback);
+ synthesizeKey("c", {}, win, callback);
+ synthesizeKey("d", {}, win, callback);
+ await flushNotifications();
+
+ // "a[bc]d"
+ synthesizeKey("KEY_ArrowLeft", {}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "a[]d"
+ description = aDescription + "deleting 'bc' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 2, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 1, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "a[bc]d"
+ synthesizeKey("b", {}, win, callback);
+ synthesizeKey("c", {}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "aB[]d"
+ description = aDescription + "replacing 'bc' with 'B' with pressing Shift+B";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("B", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 2, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ if (!aTestLineBreaker) {
+ return;
+ }
+
+ // "aB\n[]d"
+ description = aDescription + "inserting a line break after 'B' with pressing Enter";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Enter", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: 0, addedLength: kLFLen });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("aB\n").length, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "aB[]d"
+ description = aDescription + "removing a line break after 'B' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: kLFLen, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "a[B]d"
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "a\n[]d"
+ description = aDescription + "replacing 'B' with a line break with pressing Enter";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Enter", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 1, addedLength: kLFLen });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("a\n").length, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "a[\n]d"
+ description = aDescription + "selecting '\n' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 1, text: kLF, reversed: true });
+ ensureToRemovePostPositionChangeNotification(description, 1);
+ dumpUnexpectedNotifications(description, 1);
+
+ // "a[]d"
+ description = aDescription + "removing selected '\n' with pressing Delete";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: kLFLen, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 1, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // ab\ncd\nef\ngh\n[]
+ description = aDescription + "setting the value property to 'ab\ncd\nef\ngh\n'";
+ waitNotifications = promiseReceiveNotifications();
+ aElement.value = "ab\ncd\nef\ngh\n";
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 2, addedLength: getNativeText("ab\ncd\nef\ngh\n").length });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("ab\ncd\nef\ngh\n").length, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+
+ // []
+ description = aDescription + "setting the value property to ''";
+ waitNotifications = promiseReceiveNotifications();
+ aElement.value = "";
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: getNativeText("ab\ncd\nef\ngh\n").length, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ ensureToRemovePostPositionChangeNotification(description, 3);
+ dumpUnexpectedNotifications(description, 3);
+ }
+
+ async function testWithHTMLEditor(aDescription, aElement, aDefaultParagraphSeparator)
+ {
+ const isTraditionalSplitDirection =!SpecialPowers.getBoolPref(
+ "editor.join_split_direction.compatible_with_the_other_browsers"
+ );
+
+ let doc = aElement.ownerDocument;
+ let win = doc.defaultView;
+ let sel = doc.getSelection();
+ let inDesignMode = doc.designMode == "on";
+ let offsetAtStart = 0;
+ let offsetAtContainer = 0;
+ let isDefaultParagraphSeparatorBlock = aDefaultParagraphSeparator != "br";
+ doc.execCommand("defaultParagraphSeparator", false, aDefaultParagraphSeparator);
+
+ // "[]", "<p>[]</p>" or "<div>[]</div>"
+ switch (aDefaultParagraphSeparator) {
+ case "br":
+ aElement.innerHTML = "";
+ break;
+ case "p":
+ case "div":
+ // eslint-disable-next-line no-unsanitized/property
+ aElement.innerHTML = "<" + aDefaultParagraphSeparator + "></" + aDefaultParagraphSeparator + ">";
+ sel.collapse(aElement.firstChild, 0);
+ offsetAtContainer = offsetAtStart + kLFLen;
+ break;
+ default:
+ ok(false, aDescription + "aDefaultParagraphSeparator is illegal value");
+ await flushNotifications();
+ return;
+ }
+
+ if (inDesignMode) {
+ win.focus();
+ } else {
+ aElement.focus();
+ }
+ await flushNotifications();
+
+ // "a[]"
+ let description = aDescription + "typing 'a'";
+ let waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("a", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 0, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 1 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "ab[]"
+ description = aDescription + "typing 'b'";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("b", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: 0, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "abc[]"
+ description = aDescription + "typing 'c'";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("c", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 2 + offsetAtContainer, removedLength: 0, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 3 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "ab[c]"
+ description = aDescription + "selecting 'c' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 2 + offsetAtContainer, text: "c", reversed: true });
+ dumpUnexpectedNotifications(description, 1);
+
+ // "a[bc]"
+ description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, text: "bc", reversed: true });
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[abc]"
+ description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "abc", reversed: true });
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[]abc"
+ description = aDescription + "collapsing selection to the left-most with pressing ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "" });
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[a]bc"
+ description = aDescription + "selecting 'a' with pressing Shift+ArrowRight";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "a" });
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[ab]c"
+ description = aDescription + "selecting 'ab' with pressing Shift+ArrowRight";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "ab" });
+ dumpUnexpectedNotifications(description, 1);
+
+ // "[]c"
+ description = aDescription + "deleting 'ab' with pressing Delete";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 2, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 0 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "[]"
+ description = aDescription + "deleting following 'c' with pressing Delete";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 1, addedLength: kLFLen });
+ checkPositionChangeNotification(notifications[1], description);
+ dumpUnexpectedNotifications(description, 2);
+
+ // "abc[]"
+ synthesizeKey("a", {}, win, callback);
+ synthesizeKey("b", {}, win, callback);
+ synthesizeKey("c", {}, win, callback);
+ await flushNotifications();
+
+ // "ab[]"
+ description = aDescription + "deleting 'c' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 2 + offsetAtContainer, removedLength: 1, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "[ab]"
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "[]"
+ description = aDescription + "deleting 'ab' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 2, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 0 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "abcd[]"
+ synthesizeKey("a", {}, win, callback);
+ synthesizeKey("b", {}, win, callback);
+ synthesizeKey("c", {}, win, callback);
+ synthesizeKey("d", {}, win, callback);
+ await flushNotifications();
+
+ // "a[bc]d"
+ synthesizeKey("KEY_ArrowLeft", {}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "a[]d"
+ description = aDescription + "deleting 'bc' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: 2, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 1 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "a[bc]d"
+ synthesizeKey("b", {}, win, callback);
+ synthesizeKey("c", {}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "aB[]d"
+ description = aDescription + "replacing 'bc' with 'B' with pressing Shift+B";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("B", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: 2, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "aB<br>[]d" or "<block>aB</block><block>[]d</block>"
+ description = aDescription + "inserting a line break after 'B' with pressing Enter";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Enter", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ if (isDefaultParagraphSeparatorBlock) {
+ if (isTraditionalSplitDirection) {
+ // Splitting current block causes removing "<block>aB" and inserting "<block>aB</block><block>".
+ checkTextChangeNotification(notifications[0], description, {
+ offset: offsetAtContainer - kLFLen,
+ removedLength: getNativeText("\naB").length,
+ addedLength: getNativeText("\naB\n").length,
+ });
+ } else {
+ // Splitting current block causes removing "d</block>" and inserting "</block><block>d</block>".
+ checkTextChangeNotification(notifications[0], description, {
+ offset: offsetAtContainer + "aB".length,
+ removedLength: getNativeText("d\n").length,
+ addedLength: getNativeText("\nd\n").length,
+ });
+ }
+ } else {
+ // eslint-disable-next-line no-lonely-if
+ if (isTraditionalSplitDirection) {
+ // Oddly, inserting <br> causes removing "aB" and inserting "ab<br>".
+ checkTextChangeNotification(notifications[0], description, {
+ offset: offsetAtContainer,
+ removedLength: 2,
+ addedLength: getNativeText("aB\n").length,
+ });
+ } else {
+ // Inserting <br> causes removing "d" and inserting "<br>d"
+ checkTextChangeNotification(notifications[0], description, {
+ offset: offsetAtContainer + "aB".length,
+ removedLength: "d".length,
+ addedLength: getNativeText("\nd").length,
+ });
+ }
+ }
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("aB\n").length + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "aB[]d"
+ description = aDescription + "removing a line break after 'B' with pressing Backspace";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Backspace", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ if (isDefaultParagraphSeparatorBlock) {
+ if (isTraditionalSplitDirection) {
+ // Joining two blocks causes removing both block elements and inserting new block element.
+ checkTextChangeNotification(notifications[0], description, {
+ offset: offsetAtContainer - kLFLen,
+ removedLength: getNativeText("\naB\nd").length,
+ addedLength: getNativeText("\naBd").length,
+ });
+ } else {
+ // Joining two blocks causes removing "aB</block><block>d</block>" and inserting "aBd</block>"
+ checkTextChangeNotification(notifications[0], description, {
+ offset: offsetAtContainer,
+ removedLength: getNativeText("aB\nd\n").length,
+ addedLength: getNativeText("aBd\n").length,
+ });
+ }
+ checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+ } else {
+ checkTextChangeNotification(notifications[0], description, {
+ offset: offsetAtContainer + "aB".length,
+ removedLength: kLFLen,
+ addedLength: 0,
+ });
+ is(notifications.length, 3, description + " should cause 3 notifications");
+ is(notifications[1] && notifications[1].type, "notify-selection-change", description + " should cause selection change notification");
+ is(notifications[2] && notifications[2].type, "notify-position-change", description + " should cause position change notification");
+ }
+
+ // "a[B]d"
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await flushNotifications();
+
+ // "a<br>[]d" or "<block>a</block><block>[]d</block>"
+ description = aDescription + "replacing 'B' with a line break with pressing Enter";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Enter", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ if (isDefaultParagraphSeparatorBlock) {
+ if (isTraditionalSplitDirection) {
+ // Splitting current block causes removing "<block>aB" and inserting "<block>aB</block><block>".
+ checkTextChangeNotification(notifications[0], description, {
+ offset: offsetAtContainer - kLFLen,
+ removedLength: getNativeText("\naB").length,
+ addedLength: getNativeText("\na\n").length,
+ });
+ } else {
+ // Splitting current block causes removing "Bd</block>" and inserting "</block><block>d</block>".
+ checkTextChangeNotification(notifications[0], description, {
+ offset: offsetAtContainer + "a".length,
+ removedLength: getNativeText("Bd\n").length,
+ addedLength: getNativeText("\nd\n").length,
+ });
+ }
+ } else {
+ checkTextChangeNotification(notifications[0], description, {
+ offset: offsetAtContainer + "a".length,
+ removedLength: "B".length,
+ addedLength: kLFLen,
+ });
+ }
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("a\n").length + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "a[<br>]d" or "<block>a[</block><block>]d</block>"
+ description = aDescription + "selecting '\\n' with pressing Shift+ArrowLeft";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkSelectionChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, text: kLF, reversed: true });
+ dumpUnexpectedNotifications(description, 1);
+
+ // "a[]d"
+ description = aDescription + "removing selected '\\n' with pressing Delete";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ if (isDefaultParagraphSeparatorBlock) {
+ if (isTraditionalSplitDirection) {
+ // Joining the blocks causes removing "<block>a</block><block>d</block>" and inserting "<block>ad</block>".
+ checkTextChangeNotification(notifications[0], description, {
+ offset: offsetAtContainer - kLFLen,
+ removedLength: getNativeText("\na\nd").length,
+ addedLength: getNativeText("\nad").length,
+ });
+ } else {
+ // Joining the blocks causes removing "a</block><block>d</block>" and inserting "<block>ad</block>".
+ checkTextChangeNotification(notifications[0], description, {
+ offset: offsetAtContainer,
+ removedLength: getNativeText("a\nd\n").length,
+ addedLength: getNativeText("ad\n").length,
+ });
+ }
+ } else {
+ checkTextChangeNotification(notifications[0], description, {
+ offset: offsetAtContainer + "a".length,
+ removedLength: kLFLen,
+ addedLength: 0,
+ });
+ }
+ checkSelectionChangeNotification(notifications[1], description, { offset: 1 + offsetAtContainer, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>"
+ description = aDescription + "inserting HTML which has nested block elements";
+ waitNotifications = promiseReceiveNotifications();
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ // There is <br> after the end of the line. Therefore, removed length includes a line breaker length.
+ if (isDefaultParagraphSeparatorBlock) {
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer - kLFLen, removedLength: getNativeText("\nad\n").length, addedLength: getNativeText("\n1\n2\n345").length });
+ } else {
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 2 + kLFLen, addedLength: getNativeText("\n1\n2\n345").length });
+ }
+ checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1[<div>2<div>3</div>4</div>]5</div>" and removing selection
+ sel.setBaseAndExtent(aElement.firstChild.firstChild, 1, aElement.firstChild.childNodes.item(2), 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>15</div>", description + " should remove '<div>2<div>3</div>4</div>'");
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1").length + offsetAtStart, removedLength: getNativeText("\n2\n34").length, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1[<div>2<div>3</div>]4</div>5</div>" and removing selection
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
+ sel.setBaseAndExtent(aElement.firstChild.firstChild, 1, aElement.firstChild.childNodes.item(1).childNodes.item(2), 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (partially #1) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>145</div>", description + " should remove '<div>2<div>3</div></div>'");
+ // It causes removing '<div>2<div>3</div>4</div>' and inserting '4'.
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1").length + offsetAtStart, removedLength: getNativeText("\n2\n34").length, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1[<div>2<div>]3</div>4</div>5</div>" and removing selection
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
+ sel.setBaseAndExtent(aElement.firstChild.firstChild, 1, aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (partially #2) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>13<div>4</div>5</div>", description + " should remove '<div>2</div>'");
+ // It causes removing '1<div>2<div>3</div></div>' and inserting '13<div>'.
+ checkTextChangeNotification(notifications[0], description, { offset: kLFLen + offsetAtStart, removedLength: getNativeText("1\n2\n3").length, addedLength: getNativeText("13\n").length });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1<div>2<div>3[</div>4</div>]5</div>" and removing selection
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
+ sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(2), 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (partially #3) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>1<div>2<div>35</div></div></div>", description + " should remove '4'");
+ // It causes removing '45' and inserting '5'.
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, removedLength: 2, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>"
+ description = aDescription + "inserting HTML which has a pair of nested block elements";
+ waitNotifications = promiseReceiveNotifications();
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtStart, removedLength: getNativeText("\n1\n2\n35").length, addedLength: getNativeText("\n1\n2\n345\n6\n789").length });
+ checkSelectionChangeNotification(notifications[1], description, { offset: 0 + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1<div>2<div>3[</div>4</div>5<div>6<div>]7</div>8</div>9</div>" and removing selection
+ sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(3).childNodes.item(1).firstChild, 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (between same level descendants) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>1<div>2<div>37</div></div><div>8</div>9</div>", description + " should remove '456<div>7'");
+ // It causes removing '<div>3</div>4</div>5<div>6<div>7</div>' and inserting '<div>37</div><div>'.
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, removedLength: getNativeText("\n345\n6\n7").length, addedLength: getNativeText("\n37\n").length });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1<div>2[<div>3</div>4</div>5<div>6<div>]7</div>8</div>9</div>" and removing selection
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
+ sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(3).childNodes.item(1).firstChild, 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (between different level descendants #1) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>1<div>27</div><div>8</div>9</div>", description + " should remove '<div>2<div>3</div>4</div>5<div>6<div>7</div>'");
+ // It causes removing '<div>2<div>3</div>4</div>5<div>6<div>7</div>' and inserting '<div>27</div>'.
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1").length + offsetAtStart, removedLength: getNativeText("\n2\n345\n6\n7").length, addedLength: getNativeText("\n27\n").length });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1<div>2[<div>3</div>4</div>5<div>6<div>7</div>8</div>]9</div>" and removing selection
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
+ sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(4), 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (between different level descendants #2) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>1<div>29</div></div>", description + " should remove '<div>3</div>4</div>5<div>6<div>7</div>8</div>'");
+ // It causes removing '<div>3</div>4</div>5</div>6<div>7</div>8</div>9' and inserting '9</div>'.
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, removedLength: getNativeText("\n345\n6\n789").length, addedLength: 1 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1<div>2<div>3[</div>4</div>5<div>]6<div>7</div>8</div>9</div>" and removing selection
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
+ sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(3).firstChild, 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (between different level descendants #3) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>1<div>2<div>36<div>7</div>8</div></div>9</div>", description + " should remove '<div>36<div>7</div>8</div>'");
+ // It causes removing '<div>3</div>4</div>5<div>6<div>7</div>8</div>' and inserting '<div>36<div>7</div>8</div>'.
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, removedLength: getNativeText("\n345\n6\n78").length, addedLength: getNativeText("\n36\n78").length });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<div>1<div>2<div>3[</div>4</div>5<div>6<div>7</div>8</div>]9</div>" and removing selection
+ aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
+ sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(4), 0);
+ await flushNotifications();
+ description = aDescription + "deleting child nodes (between different level descendants #4) with pressing Delete key";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Delete", {}, win, callback);
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<div>1<div>2<div>39</div></div></div>", description + " should remove '</div>4</div>5<div>6<div>7</div>8</div>'");
+ // It causes removing '</div>4</div>5<div>6<div>7</div>8</div>' and inserting '<div>36<div>7</div>8</div>'.
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, removedLength: getNativeText("45\n6\n789").length, addedLength: getNativeText("9").length });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<p>abc</p><p><br></p><p>{<br>}</p>" and removing second paragraph with DOM API
+ aElement.innerHTML = "<p>abc</p><p><br></p><p><br></p>";
+ sel.collapse(aElement.firstChild.nextSibling.nextSibling, 0);
+ await flushNotifications();
+ description = aDescription + "deleting previous paragraph with DOM API";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Unidentified", { code: "" }, win, callback); // For setting the callback to recode notifications
+ aElement.firstChild.nextSibling.remove();
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<p>abc</p><p><br></p>", description + " the second paragraph should've been removed");
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\nabc").length + offsetAtStart, removedLength: getNativeText("\n\n").length, addedLength: 0 });
+ checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\nabc\n").length + offsetAtStart, text: "" });
+ checkPositionChangeNotification(notifications[2], description);
+ dumpUnexpectedNotifications(description, 3);
+
+ // "<p>abc</p><p>{<br>}</p><p><br></p>" and removing last paragraph with DOM API
+ aElement.innerHTML = "<p>abc</p><p><br></p><p><br></p>";
+ sel.collapse(aElement.firstChild.nextSibling, 0);
+ await flushNotifications();
+ description = aDescription + "deleting next paragraph with DOM API";
+ waitNotifications = promiseReceiveNotifications();
+ synthesizeKey("KEY_Unidentified", { code: "" }, win, callback); // For setting the callback to recode notifications
+ aElement.firstChild.nextSibling.nextSibling.remove();
+ await waitNotifications;
+ ensureToRemovePrecedingPositionChangeNotification();
+ is(aElement.innerHTML, "<p>abc</p><p><br></p>", description + " the last paragraph should've been removed");
+ checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\nabc\n\n").length + offsetAtStart, removedLength: getNativeText("\n\n").length, addedLength: 0 });
+ checkPositionChangeNotification(notifications[1], description);
+ dumpUnexpectedNotifications(description, 2);
+ }
+
+ await testWithPlaintextEditor("runIMEContentObserverTest with input element: ", input, false);
+ await testWithPlaintextEditor("runIMEContentObserverTest with textarea element: ", textarea, true);
+ await testWithHTMLEditor("runIMEContentObserverTest with contenteditable (defaultParagraphSeparator is br): ", contenteditable, "br");
+ await testWithHTMLEditor("runIMEContentObserverTest with contenteditable (defaultParagraphSeparator is p): ", contenteditable, "p");
+ await testWithHTMLEditor("runIMEContentObserverTest with contenteditable (defaultParagraphSeparator is div): ", contenteditable, "div");
+ // XXX Due to the difference of HTML editor behavior between designMode and contenteditable,
+ // testWithHTMLEditor() gets some unexpected behavior. However, IMEContentObserver is
+ // not depend on editor's detail. So, we should investigate this issue later. It's not
+ // so important for now.
+ // await testWithHTMLEditor("runIMEContentObserverTest in designMode (defaultParagraphSeparator is br): ", iframe2.contentDocument.body, "br");
+ // await testWithHTMLEditor("runIMEContentObserverTest in designMode (defaultParagraphSeparator is p): ", iframe2.contentDocument.body, "p");
+ // await testWithHTMLEditor("runIMEContentObserverTest in designMode (defaultParagraphSeparator is div): ", iframe2.contentDocument.body, "div");
+}
+
+async function runPasswordMaskDelayTest() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["editor.password.mask_delay", 600],
+ ["editor.password.testing.mask_delay", true],
+ ],
+ });
+
+ let iframe5 = document.getElementById("iframe5");
+ let iframe6 = document.getElementById("iframe6");
+ let inputWindow = iframe5.contentWindow;
+ let passwordWindow = iframe6.contentWindow;
+
+ let inputElement = iframe5.contentDocument.getElementById("input");
+ let passwordElement = iframe6.contentDocument.getElementById("password");
+
+ const kMask = passwordElement.editor.passwordMask;
+
+ function promiseAllPasswordMasked() {
+ return new Promise(resolve => {
+ passwordElement.addEventListener("MozLastInputMasked", resolve, {once: true});
+ });
+ }
+
+ function checkSnapshots(aResult, aReference, aMatch, aDescription) {
+ let [correct, data1, data2] = compareSnapshots(aResult, aReference, true);
+ is(correct, aMatch, `${aDescription}\nREFTEST IMAGE 1 (TEST): ${data1}\nREFTEST IMAGE 2 (REFERENCE): ${data2}`);
+ }
+
+ // First character input
+ passwordElement.value = "";
+ passwordElement.focus();
+ let waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeKey("a");
+ let unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ let maskedResult = await snapshotWindow(passwordWindow, true);
+
+ inputElement.value = "a";
+ inputElement.focus();
+ inputElement.setSelectionRange(1, 1);
+ let unmaskedReference = await snapshotWindow(inputWindow, true);
+ inputElement.value = kMask;
+ inputElement.setSelectionRange(1, 1);
+ let maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): first inputted character should be unmasked for a while");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): first inputted character should be masked after a while");
+
+ // Second character input
+ passwordElement.value = "a";
+ passwordElement.focus();
+ passwordElement.setSelectionRange(1, 1);
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeKey("b");
+ unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ maskedResult = await snapshotWindow(passwordWindow, true);
+
+ inputElement.value = `${kMask}b`;
+ inputElement.focus();
+ inputElement.setSelectionRange(2, 2);
+ unmaskedReference = await snapshotWindow(inputWindow, true);
+ inputElement.value = `${kMask}${kMask}`;
+ inputElement.setSelectionRange(2, 2);
+ maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): second inputted character should be unmasked for a while");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): second inputted character should be masked after a while");
+
+ // Typing new character should mask the previous unmasked characters
+ passwordElement.value = "ab";
+ passwordElement.focus();
+ passwordElement.setSelectionRange(2, 2);
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeKey("c");
+ synthesizeKey("d");
+ unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ maskedResult = await snapshotWindow(passwordWindow, true);
+
+ inputElement.value = `${kMask}${kMask}${kMask}d`;
+ inputElement.focus();
+ inputElement.setSelectionRange(4, 4);
+ unmaskedReference = await snapshotWindow(inputWindow, true);
+ inputElement.value = `${kMask}${kMask}${kMask}${kMask}`;
+ inputElement.setSelectionRange(4, 4);
+ maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): forth character input should mask the third character");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): forth inputted character should be masked after a while");
+
+ // Typing middle of password should unmask the last input character
+ passwordElement.value = "abcd";
+ passwordElement.focus();
+ passwordElement.setSelectionRange(2, 2);
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeKey("e");
+ unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ maskedResult = await snapshotWindow(passwordWindow, true);
+
+ inputElement.value = `${kMask}${kMask}e${kMask}${kMask}`;
+ inputElement.focus();
+ inputElement.setSelectionRange(3, 3);
+ unmaskedReference = await snapshotWindow(inputWindow, true);
+ inputElement.value = `${kMask}${kMask}${kMask}${kMask}${kMask}`;
+ inputElement.setSelectionRange(3, 3);
+ maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): inserted character should be unmasked for a while");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): inserted character should be masked after a while");
+
+ // Composition string should be unmasked for a while, and shouldn't be committed at masking
+ passwordElement.value = "ab";
+ passwordElement.focus();
+ passwordElement.setSelectionRange(1, 1);
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeCompositionChange(
+ { composition:
+ { string: "c",
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ maskedResult = await snapshotWindow(passwordWindow, true);
+ is(getEditor(passwordElement).composing, true,
+ "runPasswordMaskDelayTest(): composition shouldn't be commited at masking the composing string #1");
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ inputElement.value = `${kMask}${kMask}`;
+ inputElement.focus();
+ inputElement.setSelectionRange(1, 1);
+ synthesizeCompositionChange(
+ { composition:
+ { string: "c",
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ unmaskedReference = await snapshotWindow(inputWindow, true);
+ synthesizeCompositionChange(
+ { composition:
+ { string: kMask,
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): composing character should be unmasked for a while");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): composing character should be masked after a while");
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ // Updating composition string should unmask the composition string for a while
+ passwordElement.value = "ab";
+ passwordElement.focus();
+ passwordElement.setSelectionRange(1, 1);
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeCompositionChange(
+ { composition:
+ { string: "c",
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ await waitForMaskingLastInput;
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeCompositionChange(
+ { composition:
+ { string: "d",
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ maskedResult = await snapshotWindow(passwordWindow, true);
+ is(getEditor(passwordElement).composing, true,
+ "runPasswordMaskDelayTest(): composition shouldn't be commited at masking the composing string #2");
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ inputElement.value = `${kMask}${kMask}`;
+ inputElement.focus();
+ inputElement.setSelectionRange(1, 1);
+ synthesizeCompositionChange(
+ { composition:
+ { string: "d",
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ unmaskedReference = await snapshotWindow(inputWindow, true);
+ synthesizeCompositionChange(
+ { composition:
+ { string: kMask,
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): updated composing character should be unmasked for a while");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): updated composing character should be masked after a while");
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ // Composing multi-characters should be unmasked for a while.
+ passwordElement.value = "ab";
+ passwordElement.focus();
+ passwordElement.setSelectionRange(1, 1);
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeCompositionChange(
+ { composition:
+ { string: "c",
+ clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 1, length: 0 },
+ });
+ await waitForMaskingLastInput;
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeCompositionChange(
+ { composition:
+ { string: "cd",
+ clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 2, length: 0 },
+ });
+ unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ maskedResult = await snapshotWindow(passwordWindow, true);
+ is(getEditor(passwordElement).composing, true,
+ "runPasswordMaskDelayTest(): composition shouldn't be commited at masking the composing string #3");
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ inputElement.value = `${kMask}${kMask}`;
+ inputElement.focus();
+ inputElement.setSelectionRange(1, 1);
+ synthesizeCompositionChange(
+ { composition:
+ { string: "cd",
+ clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 2, length: 0 },
+ });
+ unmaskedReference = await snapshotWindow(inputWindow, true);
+ synthesizeCompositionChange(
+ { composition:
+ { string: `${kMask}${kMask}`,
+ clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 2, length: 0 },
+ });
+ maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): all of composing string should be unmasked for a while");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): all of composing string should be masked after a while");
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+
+ // Committing composition should make the commit string unmasked.
+ passwordElement.value = "ab";
+ passwordElement.focus();
+ passwordElement.setSelectionRange(1, 1);
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeCompositionChange(
+ { composition:
+ { string: "cd",
+ clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
+ },
+ caret: { start: 2, length: 0 },
+ });
+ await waitForMaskingLastInput;
+ waitForMaskingLastInput = promiseAllPasswordMasked();
+ synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
+ unmaskedResult = await snapshotWindow(passwordWindow, true);
+ await waitForMaskingLastInput;
+ maskedResult = await snapshotWindow(passwordWindow, true);
+
+ inputElement.value = `${kMask}cd${kMask}`;
+ inputElement.focus();
+ inputElement.setSelectionRange(3, 3);
+ unmaskedReference = await snapshotWindow(inputWindow, true);
+ inputElement.value = `${kMask}${kMask}${kMask}${kMask}`;
+ inputElement.setSelectionRange(3, 3);
+ maskedReference = await snapshotWindow(inputWindow, true);
+ checkSnapshots(unmaskedResult, unmaskedReference, true,
+ "runPasswordMaskDelayTest(): committed string should be unmasked for a while");
+ checkSnapshots(maskedResult, maskedReference, true,
+ "runPasswordMaskDelayTest(): committed string should be masked after a while");
+}
+
+async function runInputModeTest()
+{
+ await SpecialPowers.setBoolPref("dom.forms.inputmode", true);
+
+ let result = [];
+
+ function handler(aEvent)
+ {
+ result.push(aEvent);
+ }
+
+ textarea.inputMode = "text";
+ textarea.value = "";
+ textarea.focus();
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", handler, true);
+
+ synthesizeCompositionChange({
+ composition: {string: "a ", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+ });
+
+ is(result[0].type, "compositionupdate", "Set initial composition for inputmode test");
+ result = [];
+
+ textarea.inputMode = "tel";
+ is(result.length, 0, "No compositonend event even if inputmode is updated");
+
+ // Clean up
+ synthesizeComposition({ type: "compositioncommitasis" });
+ textarea.inputMode = "";
+ textarea.value = "";
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+
+ await SpecialPowers.clearUserPref("dom.forms.inputmode");
+}
+
+
+async function runTest()
+{
+ window.addEventListener("unload", window.arguments[0].SimpleTest.finish, {once: true, capture: true});
+
+ contenteditable = document.getElementById("iframe4").contentDocument.getElementById("contenteditable");
+ windowOfContenteditable = document.getElementById("iframe4").contentWindow;
+ textareaInFrame = iframe.contentDocument.getElementById("textarea");
+
+ contenteditableBySpan = document.getElementById("iframe7").contentDocument.getElementById("contenteditable");
+ windowOfContenteditableBySpan = document.getElementById("iframe7").contentWindow;
+
+ await runIMEContentObserverTest();
+ await runEditorReframeTests();
+ await runAsyncForceCommitTest();
+ await runRemoveContentTest();
+ await runPanelTest();
+ await runPasswordMaskDelayTest();
+ await runBug1584901Test();
+ await runInputModeTest();
+ await runCompositionTest();
+ await runCompositionCommitTest();
+ await runSetSelectionEventTest();
+
+ runUndoRedoTest();
+ runCompositionCommitAsIsTest();
+ runCompositionEventTest();
+ runCompositionTestWhoseTextNodeModified();
+ runQueryTextRectInContentEditableTest();
+ runCharAtPointTest(textarea, "textarea in the document");
+ runCharAtPointAtOutsideTest();
+ runQueryTextContentEventTest();
+ runQuerySelectionEventTest();
+ runQueryIMESelectionTest();
+ runQueryContentEventRelativeToInsertionPoint();
+ runQueryPasswordTest();
+ runCSSTransformTest();
+ runBug722639Test();
+ runBug1375825Test();
+ runBug1530649Test();
+ runBug1571375Test();
+ runBug1675313Test();
+ runCommitCompositionWithSpaceKey();
+ runCompositionWithSelectionChange();
+ runForceCommitTest();
+ runNestedSettingValue();
+ runBug811755Test();
+ runIsComposingTest();
+ runRedundantChangeTest();
+ runNotRedundantChangeTest();
+ runNativeLineBreakerTest();
+ runControlCharTest();
+ runFrameTest();
+ runMaxLengthTest();
+
+ window.close();
+}
+
+window.arguments[0].SimpleTest.waitForFocus(runTest, window);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_imestate_iframes.html b/widget/tests/window_imestate_iframes.html
new file mode 100644
index 0000000000..c8b182977f
--- /dev/null
+++ b/widget/tests/window_imestate_iframes.html
@@ -0,0 +1,358 @@
+<html>
+<head>
+ <title>Test for IME state controling and focus moving for iframes</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ iframe {
+ border: none;
+ height: 100px;
+ }
+ </style>
+</head>
+<body onunload="onUnload();">
+<p id="display">
+ <!-- Use input[readonly] because it isn't affected by the partial focus
+ movement on Mac -->
+ <input id="prev" readonly><br>
+ <iframe id="iframe_not_editable"
+ src="data:text/html,&lt;html&gt;&lt;body&gt;&lt;input id='editor'&gt;&lt;/body&gt;&lt;/html&gt;"></iframe><br>
+
+ <!-- Testing IME state and focus movement, the anchor elements cannot get focus -->
+ <iframe id="iframe_html"
+ src="data:text/html,&lt;html id='editor' contenteditable='true'&gt;&lt;body&gt;&lt;a href='about:blank'&gt;about:blank;&lt;/a&gt;&lt;/body&gt;&lt;/html&gt;"></iframe><br>
+ <iframe id="iframe_designMode"
+ src="data:text/html,&lt;body id='editor' onload='document.designMode=&quot;on&quot;;'&gt;&lt;a href='about:blank'&gt;about:blank;&lt;/a&gt;&lt;/body&gt;"></iframe><br>
+ <iframe id="iframe_body"
+ src="data:text/html,&lt;body id='editor' contenteditable='true'&gt;&lt;a href='about:blank'&gt;about:blank;&lt;/a&gt;&lt;/body&gt;"></iframe><br>
+ <iframe id="iframe_p"
+ src="data:text/html,&lt;body&gt;&lt;p id='editor' contenteditable='true'&gt;&lt;a href='about:blank'&gt;about:blank;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;"></iframe><br>
+
+ <input id="next" readonly><br>
+</p>
+<script class="testbody" type="application/javascript">
+
+window.opener.SimpleTest.waitForFocus(runTests, window);
+
+function ok(aCondition, aMessage) {
+ window.opener.SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage) {
+ window.opener.SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function onUnload() {
+ window.opener.onFinish();
+}
+
+var gFocusObservingElement = null;
+var gBlurObservingElement = null;
+
+function onFocus(aEvent) {
+ if (aEvent.target != gFocusObservingElement) {
+ return;
+ }
+ ok(gFocusObservingElement.willFocus,
+ "focus event is fired on unexpected element");
+ gFocusObservingElement.willFocus = false;
+}
+
+function onBlur(aEvent) {
+ if (aEvent.target != gBlurObservingElement) {
+ return;
+ }
+ ok(gBlurObservingElement.willBlur,
+ "blur event is fired on unexpected element");
+ gBlurObservingElement.willBlur = false;
+}
+
+function observeFocusBlur(aNextFocusedNode, aWillFireFocusEvent,
+ aNextBlurredNode, aWillFireBlurEvent) {
+ if (gFocusObservingElement) {
+ if (gFocusObservingElement.willFocus) {
+ ok(false, "focus event was never fired on " + gFocusObservingElement);
+ }
+ gFocusObservingElement.removeEventListener("focus", onFocus, true);
+ gFocusObservingElement.willFocus = NaN;
+ gFocusObservingElement = null;
+ }
+ if (gBlurObservingElement) {
+ if (gBlurObservingElement.willBlur) {
+ ok(false, "blur event was never fired on " + gBlurObservingElement);
+ }
+ gBlurObservingElement.removeEventListener("blur", onBlur, true);
+ gBlurObservingElement.willBlur = NaN;
+ gBlurObservingElement = null;
+ }
+ if (aNextFocusedNode) {
+ gFocusObservingElement = aNextFocusedNode;
+ gFocusObservingElement.willFocus = aWillFireFocusEvent;
+ gFocusObservingElement.addEventListener("focus", onFocus, true);
+ }
+ if (aNextBlurredNode) {
+ gBlurObservingElement = aNextBlurredNode;
+ gBlurObservingElement.willBlur = aWillFireBlurEvent;
+ gBlurObservingElement.addEventListener("blur", onBlur, true);
+ }
+}
+
+function runTests() {
+ var utils = window.windowUtils;
+ var fm = Services.focus;
+
+ var iframe, editor, root;
+ var prev = document.getElementById("prev");
+ var next = document.getElementById("next");
+ var html = document.documentElement;
+
+ function resetFocusToInput(aDescription) {
+ observeFocusBlur(null, false, null, false);
+ prev.focus();
+ is(fm.focusedElement, prev,
+ "input#prev[readonly] element didn't get focus: " + aDescription);
+ is(utils.IMEStatus, utils.IME_STATUS_DISABLED,
+ "IME enabled on input#prev[readonly]: " + aDescription);
+ }
+
+ function resetFocusToParentHTML(aDescription) {
+ observeFocusBlur(null, false, null, false);
+ html.focus();
+ is(fm.focusedElement, html,
+ "Parent html element didn't get focus: " + aDescription);
+ is(utils.IMEStatus, utils.IME_STATUS_DISABLED,
+ "IME enabled on parent html element: " + aDescription);
+ }
+
+ function testTabKey(aForward,
+ aNextFocusedNode, aWillFireFocusEvent,
+ aNextBlurredNode, aWillFireBlurEvent,
+ aIMEShouldBeEnabled, aTestingCaseDescription) {
+ observeFocusBlur(aNextFocusedNode, aWillFireFocusEvent,
+ aNextBlurredNode, aWillFireBlurEvent);
+ synthesizeKey("VK_TAB", { shiftKey: !aForward });
+ var description = "Tab key test: ";
+ if (!aForward) {
+ description = "Shift-" + description;
+ }
+ description += aTestingCaseDescription + ": ";
+ is(fm.focusedElement, aNextFocusedNode,
+ description + "didn't move focus as expected");
+ is(utils.IMEStatus,
+ aIMEShouldBeEnabled ?
+ utils.IME_STATUS_ENABLED : utils.IME_STATUS_DISABLED,
+ description + "didn't set IME state as expected");
+ }
+
+ function testMouseClick(aNextFocusedNode, aWillFireFocusEvent,
+ aWillAllNodeLostFocus,
+ aNextBlurredNode, aWillFireBlurEvent,
+ aIMEShouldBeEnabled, aTestingCaseDescription) {
+ observeFocusBlur(aNextFocusedNode, aWillFireFocusEvent,
+ aNextBlurredNode, aWillFireBlurEvent);
+ // We're relying on layout inside the iframe being up to date, so make it so
+ iframe.contentDocument.documentElement.getBoundingClientRect();
+ synthesizeMouse(iframe, 10, 80, { });
+ var description = "Click test: " + aTestingCaseDescription + ": ";
+ is(fm.focusedElement, !aWillAllNodeLostFocus ? aNextFocusedNode : null,
+ description + "didn't move focus as expected");
+ is(utils.IMEStatus,
+ aIMEShouldBeEnabled ?
+ utils.IME_STATUS_ENABLED : utils.IME_STATUS_DISABLED,
+ description + "didn't set IME state as expected");
+ }
+
+ function testOnEditorFlagChange(aDescription, aIsInDesignMode) {
+ const kReadonly = Ci.nsIEditor.eEditorReadonlyMask;
+ var description = "testOnEditorFlagChange: " + aDescription;
+ resetFocusToParentHTML(description);
+ var htmlEditor = iframe.contentWindow.docShell.editor;
+ var e = aIsInDesignMode ? root : editor;
+ e.focus();
+ is(fm.focusedElement, e,
+ description + ": focus() of editor didn't move focus as expected");
+ is(utils.IMEStatus, utils.IME_STATUS_ENABLED,
+ description + ": IME isn't enabled when the editor gets focus");
+ var flags = htmlEditor.flags;
+ htmlEditor.flags |= kReadonly;
+ is(fm.focusedElement, e,
+ description + ": when editor becomes readonly, focus moved unexpectedly");
+ is(utils.IMEStatus, utils.IME_STATUS_DISABLED,
+ description + ": when editor becomes readonly, IME is still enabled");
+ htmlEditor.flags = flags;
+ is(fm.focusedElement, e,
+ description + ": when editor becomes read-write, focus moved unexpectedly");
+ is(utils.IMEStatus, utils.IME_STATUS_ENABLED,
+ description + ": when editor becomes read-write, IME is still disabled");
+ }
+
+ // hide all iframes
+ document.getElementById("iframe_not_editable").style.display = "none";
+ document.getElementById("iframe_html").style.display = "none";
+ document.getElementById("iframe_designMode").style.display = "none";
+ document.getElementById("iframe_body").style.display = "none";
+ document.getElementById("iframe_p").style.display = "none";
+
+ // non editable HTML element and input element can get focus.
+ iframe = document.getElementById("iframe_not_editable");
+ iframe.style.display = "inline";
+ editor = iframe.contentDocument.getElementById("editor");
+ root = iframe.contentDocument.documentElement;
+ resetFocusToInput("initializing for iframe_not_editable");
+
+ testTabKey(true, root, false, prev, true,
+ false, "input#prev[readonly] -> html");
+ testTabKey(true, editor, true, root, false,
+ true, "html -> input in the subdoc");
+ testTabKey(true, next, true, editor, true,
+ false, "input in the subdoc -> input#next[readonly]");
+ testTabKey(false, editor, true, next, true,
+ true, "input#next[readonly] -> input in the subdoc");
+ testTabKey(false, root, false, editor, true,
+ false, "input in the subdoc -> html");
+ testTabKey(false, prev, true, root, false,
+ false, "html -> input#next[readonly]");
+
+ iframe.style.display = "none";
+
+ // HTML element (of course, it's root) must enables IME.
+ iframe = document.getElementById("iframe_html");
+ iframe.style.display = "inline";
+ editor = iframe.contentDocument.getElementById("editor");
+ root = iframe.contentDocument.documentElement;
+ resetFocusToInput("initializing for iframe_html");
+
+ testTabKey(true, editor, true, prev, true,
+ true, "input#prev[readonly] -> html[contentediable=true]");
+ testTabKey(true, next, true, editor, true,
+ false, "html[contentediable=true] -> input#next[readonly]");
+ testTabKey(false, editor, true, next, true,
+ true, "input#next[readonly] -> html[contentediable=true]");
+ testTabKey(false, prev, true, editor, true,
+ false, "html[contenteditable=true] -> input[readonly]");
+
+ prev.style.display = "none";
+ resetFocusToParentHTML("testing iframe_html");
+ testTabKey(true, editor, true, html, false,
+ true, "html of parent -> html[contentediable=true]");
+ testTabKey(false, html, false, editor, true,
+ false, "html[contenteditable=true] -> html of parent");
+ prev.style.display = "inline";
+ resetFocusToInput("after parent html <-> html[contenteditable=true]");
+
+ testMouseClick(editor, true, false, prev, true, true, "iframe_html");
+
+ testOnEditorFlagChange("html[contentediable=true]", false);
+
+ iframe.style.display = "none";
+
+ // designMode should behave like <html contenteditable="true"></html>
+ // but focus/blur events shouldn't be fired on its root element because
+ // any elements shouldn't be focused state in designMode.
+ iframe = document.getElementById("iframe_designMode");
+ iframe.style.display = "inline";
+ iframe.contentDocument.designMode = "on";
+ editor = iframe.contentDocument.getElementById("editor");
+ root = iframe.contentDocument.documentElement;
+ resetFocusToInput("initializing for iframe_designMode");
+
+ testTabKey(true, root, false, prev, true,
+ true, "input#prev[readonly] -> html in designMode");
+ testTabKey(true, next, true, root, false,
+ false, "html in designMode -> input#next[readonly]");
+ testTabKey(false, root, false, next, true,
+ true, "input#next[readonly] -> html in designMode");
+ testTabKey(false, prev, true, root, false,
+ false, "html in designMode -> input#prev[readonly]");
+
+ prev.style.display = "none";
+ resetFocusToParentHTML("testing iframe_designMode");
+ testTabKey(true, root, false, html, false,
+ true, "html of parent -> html in designMode");
+ testTabKey(false, html, false, root, false,
+ false, "html in designMode -> html of parent");
+ prev.style.display = "inline";
+ resetFocusToInput("after parent html <-> html in designMode");
+
+ testMouseClick(editor, false, true, prev, true, true, "iframe_designMode");
+
+ testOnEditorFlagChange("html in designMode", true);
+
+ iframe.style.display = "none";
+
+ // When there is no HTML element but the BODY element is editable,
+ // the body element should get focus and enables IME.
+ iframe = document.getElementById("iframe_body");
+ iframe.style.display = "inline";
+ editor = iframe.contentDocument.getElementById("editor");
+ root = iframe.contentDocument.documentElement;
+ resetFocusToInput("initializing for iframe_body");
+
+ testTabKey(true, editor, true, prev, true,
+ true, "input#prev[readonly] -> body[contentediable=true]");
+ testTabKey(true, next, true, editor, true,
+ false, "body[contentediable=true] -> input#next[readonly]");
+ testTabKey(false, editor, true, next, true,
+ true, "input#next[readonly] -> body[contentediable=true]");
+ testTabKey(false, prev, true, editor, true,
+ false, "body[contenteditable=true] -> input#prev[readonly]");
+
+ prev.style.display = "none";
+ resetFocusToParentHTML("testing iframe_body");
+ testTabKey(true, editor, true, html, false,
+ true, "html of parent -> body[contentediable=true]");
+ testTabKey(false, html, false, editor, true,
+ false, "body[contenteditable=true] -> html of parent");
+ prev.style.display = "inline";
+ resetFocusToInput("after parent html <-> body[contenteditable=true]");
+
+ testMouseClick(editor, true, false, prev, true, true, "iframe_body");
+
+ testOnEditorFlagChange("body[contentediable=true]", false);
+
+ iframe.style.display = "none";
+
+ // When HTML/BODY elements are not editable, focus shouldn't be moved to
+ // the editable content directly.
+ iframe = document.getElementById("iframe_p");
+ iframe.style.display = "inline";
+ editor = iframe.contentDocument.getElementById("editor");
+ root = iframe.contentDocument.documentElement;
+ resetFocusToInput("initializing for iframe_p");
+
+ testTabKey(true, root, false, prev, true,
+ false, "input#prev[readonly] -> html (has p[contenteditable=true])");
+ testTabKey(true, editor, true, root, false,
+ true, "html (has p[contenteditable=true]) -> p[contentediable=true]");
+ testTabKey(true, next, true, editor, true,
+ false, "p[contentediable=true] -> input#next[readonly]");
+ testTabKey(false, editor, true, next, true,
+ true, "input#next[readonly] -> p[contentediable=true]");
+ testTabKey(false, root, false, editor, true,
+ false, "p[contenteditable=true] -> html (has p[contenteditable=true])");
+ testTabKey(false, prev, true, root, false,
+ false, "html (has p[contenteditable=true]) -> input#prev[readonly]");
+ prev.style.display = "none";
+
+ resetFocusToParentHTML("testing iframe_p");
+ testTabKey(true, root, false, html, false,
+ false, "html of parent -> html (has p[contentediable=true])");
+ testTabKey(false, html, false, root, false,
+ false, "html (has p[contentediable=true]) -> html of parent");
+ prev.style.display = "inline";
+ resetFocusToInput("after parent html <-> html (has p[contentediable=true])");
+
+ testMouseClick(root, false, true, prev, true, false, "iframe_p");
+
+ testOnEditorFlagChange("p[contenteditable=true]", false);
+
+ iframe.style.display = "none";
+
+ window.close();
+}
+
+</script>
+</body>
+
+</html>
diff --git a/widget/tests/window_mouse_scroll_win.html b/widget/tests/window_mouse_scroll_win.html
new file mode 100644
index 0000000000..69eca9ebb0
--- /dev/null
+++ b/widget/tests/window_mouse_scroll_win.html
@@ -0,0 +1,1516 @@
+<html lang="en-US"
+ style="font-family: Arial; font-size: 10px; line-height: 16px;">
+<head>
+ <title>Test for mouse scroll handling on Windows</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://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body onunload="onUnload();">
+<div id="display" style="width: 5000px; height: 5000px;">
+<p id="p1" style="font-size: 16px; width: 100px; height: 100px;">1st &lt;p&gt;.</p>
+<p id="p2" style="font-size: 32px; width: 100px; height: 100px;">2nd &lt;p&gt;.</p>
+</div>
+<script class="testbody" type="application/javascript">
+
+window.arguments[0].SimpleTest.waitForFocus(prepareTests, window);
+
+const nsIDOMWindowUtils = Ci.nsIDOMWindowUtils;
+
+const WHEEL_PAGESCROLL = 4294967295;
+
+const WM_VSCROLL = 0x0115;
+const WM_HSCROLL = 0x0114;
+const WM_MOUSEWHEEL = 0x020A;
+const WM_MOUSEHWHEEL = 0x020E;
+
+const SB_LINEUP = 0;
+const SB_LINELEFT = 0;
+const SB_LINEDOWN = 1;
+const SB_LINERIGHT = 1;
+const SB_PAGEUP = 2;
+const SB_PAGELEFT = 2;
+const SB_PAGEDOWN = 3;
+const SB_PAGERIGHT = 3;
+
+const SHIFT_L = 0x0100;
+const SHIFT_R = 0x0200;
+const CTRL_L = 0x0400;
+const CTRL_R = 0x0800;
+const ALT_L = 0x1000;
+const ALT_R = 0x2000;
+
+const DOM_PAGE_SCROLL_DELTA = 32768;
+
+const kSystemScrollSpeedOverridePref = "mousewheel.system_scroll_override.enabled";
+
+const kAltKeyActionPref = "mousewheel.with_alt.action";
+const kCtrlKeyActionPref = "mousewheel.with_control.action";
+const kShiftKeyActionPref = "mousewheel.with_shift.action";
+const kWinKeyActionPref = "mousewheel.with_win.action";
+
+const kAltKeyDeltaMultiplierXPref = "mousewheel.with_alt.delta_multiplier_x";
+const kAltKeyDeltaMultiplierYPref = "mousewheel.with_alt.delta_multiplier_y";
+const kCtrlKeyDeltaMultiplierXPref = "mousewheel.with_control.delta_multiplier_x";
+const kCtrlKeyDeltaMultiplierYPref = "mousewheel.with_control.delta_multiplier_y";
+const kShiftKeyDeltaMultiplierXPref = "mousewheel.with_shift.delta_multiplier_x";
+const kShiftKeyDeltaMultiplierYPref = "mousewheel.with_shift.delta_multiplier_y";
+const kWinKeyDeltaMultiplierXPref = "mousewheel.with_win.delta_multiplier_x";
+const kWinKeyDeltaMultiplierYPref = "mousewheel.with_win.delta_multiplier_y";
+
+const kEmulateWheelByWMSCROLLPref = "mousewheel.emulate_at_wm_scroll";
+const kVAmountPref = "mousewheel.windows.vertical_amount_override";
+const kHAmountPref = "mousewheel.windows.horizontal_amount_override";
+const kTimeoutPref = "mousewheel.windows.transaction.timeout";
+
+const kMouseLineScrollEvent = "DOMMouseScroll";
+const kMousePixelScrollEvent = "MozMousePixelScroll";
+
+const kVAxis = MouseScrollEvent.VERTICAL_AXIS;
+const kHAxis = MouseScrollEvent.HORIZONTAL_AXIS;
+
+var gLineHeight = 0;
+var gCharWidth = 0;
+var gPageHeight = 0;
+var gPageWidth = 0;
+
+var gP1 = document.getElementById("p1");
+var gP2 = document.getElementById("p2");
+
+var gOtherWindow;
+
+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 todo_is(aLeft, aRight, aMessage) {
+ window.arguments[0].SimpleTest.todo_is(aLeft, aRight, aMessage);
+}
+
+function onUnload() {
+ SpecialPowers.clearUserPref(kAltKeyActionPref);
+ SpecialPowers.clearUserPref(kCtrlKeyActionPref);
+ SpecialPowers.clearUserPref(kShiftKeyActionPref);
+ SpecialPowers.clearUserPref(kWinKeyActionPref);
+
+ SpecialPowers.clearUserPref(kAltKeyDeltaMultiplierXPref);
+ SpecialPowers.clearUserPref(kAltKeyDeltaMultiplierYPref);
+ SpecialPowers.clearUserPref(kCtrlKeyDeltaMultiplierXPref);
+ SpecialPowers.clearUserPref(kCtrlKeyDeltaMultiplierYPref);
+ SpecialPowers.clearUserPref(kShiftKeyDeltaMultiplierXPref);
+ SpecialPowers.clearUserPref(kShiftKeyDeltaMultiplierYPref);
+ SpecialPowers.clearUserPref(kWinKeyDeltaMultiplierXPref);
+ SpecialPowers.clearUserPref(kWinKeyDeltaMultiplierYPref);
+
+ SpecialPowers.clearUserPref(kSystemScrollSpeedOverridePref);
+ SpecialPowers.clearUserPref(kEmulateWheelByWMSCROLLPref);
+ SpecialPowers.clearUserPref(kVAmountPref);
+ SpecialPowers.clearUserPref(kHAmountPref);
+ SpecialPowers.clearUserPref(kTimeoutPref);
+ window.arguments[0].SimpleTest.finish();
+}
+
+function getWindowUtils(aWindow) {
+ if (!aWindow) {
+ aWindow = window;
+ }
+ return aWindow.windowUtils;
+}
+
+function getPointInScreen(aElement, aWindow) {
+ if (!aWindow) {
+ aWindow = window;
+ }
+ var bounds = aElement.getBoundingClientRect();
+ return { x: bounds.left + aWindow.mozInnerScreenX,
+ y: bounds.top + aWindow.mozInnerScreenY };
+}
+
+function cut(aNum) {
+ return (aNum >= 0) ? Math.floor(aNum) : Math.ceil(aNum);
+}
+
+/**
+ * Make each steps for the tests in following arrays in global scope. Each item
+ * of the arrays will be executed after previous test is finished.
+ *
+ * description:
+ * Set the description of the test. This will be used for the message of is()
+ * or the others.
+ *
+ * message:
+ * aNativeMessage of nsIDOMWindowUtils.sendNativeMouseScrollEvent().
+ * Must be WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_VSCROLL or WM_HSCROLL.
+ *
+ * delta:
+ * The native delta value for WM_MOUSEWHEEL or WM_MOUSEHWHEEL.
+ * Or one of the SB_* const value for WM_VSCROLL or WM_HSCROLL.
+ *
+ * target:
+ * The target element, under the mouse cursor.
+ *
+ * window:
+ * The window which is used for getting nsIDOMWindowUtils.
+ *
+ * modifiers:
+ * Pressed modifier keys, 0 means no modifier key is pressed.
+ * Otherwise, one or more values of SHIFT_L, SHIFT_R, CTRL_L, CTRL_R,
+ * ALT_L or ALT_R.
+ *
+ * additionalFlags:
+ * aAdditionalFlags of nsIDOMWindowUtils.sendNativeMouseScrollEvent().
+ * See the document of nsIDOMWindowUtils for the detail of the values.
+ *
+ * onLineScrollEvent:
+ * Must be a function or null.
+ * If the value is a function, it will be called when DOMMouseScroll event
+ * is received by the synthesized event.
+ * If return true, the common checks are canceled.
+ *
+ * onPixelScrollEvent:
+ * Must be a function or null.
+ * If the value is a function, it will be called when MozMousePixelScroll
+ * event is received by the synthesized event.
+ * If return true, the common checks are canceled.
+ *
+ * expected:
+ * Must not be null and this must have:
+ * axis:
+ * kVAxis if the synthesized event causes vertical scroll. Otherwise,
+ * it causes horizontal scroll, kHAxis.
+ * lines:
+ * Integer value which is expected detail attribute value of
+ * DOMMouseScroll. If the event shouldn't be fired, must be 0.
+ * pixels:
+ * Integer value or a function which returns double value. The value is
+ * expected detail attribute value of MozMousePixelScroll.
+ * If the event shouldn't be fired, must be 0.
+ *
+ * Note that if both lines and pixels are 0, the test framework waits
+ * a few seconds. After that, go to next test.
+ *
+ * init:
+ * Must be a function or null. If this value is a function, it's called
+ * before synthesizing the native event.
+ *
+ * finish:
+ * Must be a function or null. If this value is a function, it's called
+ * after received all expected events or timeout if no events are expected.
+ */
+
+// First, get the computed line height, char width, page height and page width.
+var gPreparingSteps = [
+ { description: "Preparing gLineHeight",
+ message: WM_MOUSEWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ onLineScrollEvent(aEvent) {
+ return true;
+ },
+ onPixelScrollEvent(aEvent) {
+ gLineHeight = aEvent.detail;
+ return true;
+ },
+ expected: {
+ axis: kVAxis, lines: 1, pixels: 1,
+ },
+ init() {
+ SpecialPowers.setIntPref(kVAmountPref, 1);
+ SpecialPowers.setIntPref(kHAmountPref, 1);
+ },
+ },
+ { description: "Preparing gCharWidth",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ onLineScrollEvent(aEvent) {
+ return true;
+ },
+ onPixelScrollEvent(aEvent) {
+ gCharWidth = aEvent.detail;
+ return true;
+ },
+ expected: {
+ axis: kVAxis, lines: 1, pixels: 1,
+ },
+ },
+ { description: "Preparing gPageHeight",
+ message: WM_MOUSEWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ onLineScrollEvent(aEvent) {
+ return true;
+ },
+ onPixelScrollEvent(aEvent) {
+ gPageHeight = aEvent.detail;
+ return true;
+ },
+ expected: {
+ axis: kHAxis, lines: 1, pixels: 1,
+ },
+ init() {
+ SpecialPowers.setIntPref(kVAmountPref, 0xFFFF);
+ SpecialPowers.setIntPref(kHAmountPref, 0xFFFF);
+ },
+ },
+ { description: "Preparing gPageWidth",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ onLineScrollEvent(aEvent) {
+ return true;
+ },
+ onPixelScrollEvent(aEvent) {
+ gPageWidth = aEvent.detail;
+ return true;
+ },
+ expected: {
+ axis: kHAxis, lines: 1, pixels: 1,
+ },
+ finish() {
+ ok(gLineHeight > 0, "gLineHeight isn't positive got " + gLineHeight);
+ ok(gCharWidth > 0, "gCharWidth isn't positive got " + gCharWidth);
+ ok(gPageHeight > 0, "gPageHeight isn't positive got " + gPageHeight);
+ ok(gPageWidth > 0, "gPageWidth isn't positive got " + gPageWidth);
+
+ ok(gPageHeight > gLineHeight,
+ "gPageHeight must be larger than gLineHeight");
+ ok(gPageWidth > gCharWidth,
+ "gPageWidth must be larger than gCharWidth");
+ runNextTest(gBasicTests, 0);
+ },
+ },
+];
+
+var gBasicTests = [
+ // Widget shouldn't dispatch a pixel event if the delta can be devided by
+ // lines to be scrolled. However, pixel events should be fired by ESM.
+ { description: "WM_MOUSEWHEEL, -120, 3 lines",
+ message: WM_MOUSEWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 3, pixels() { return gLineHeight * 3; },
+ },
+ init() {
+ SpecialPowers.setIntPref(kVAmountPref, 3);
+ SpecialPowers.setIntPref(kHAmountPref, 3);
+ },
+ },
+
+ { description: "WM_MOUSEWHEEL, 120, -3 lines",
+ message: WM_MOUSEWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -3, pixels() { return gLineHeight * -3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 120, 3 chars",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 3, pixels() { return gCharWidth * 3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, -120, -3 chars",
+ message: WM_MOUSEHWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -3, pixels() { return gCharWidth * -3; },
+ },
+ },
+
+ // Pixel scroll event should be fired always but line scroll event should be
+ // fired only when accumulated delta value is over a line.
+ { description: "WM_MOUSEWHEEL, -20, 0.5 lines",
+ message: WM_MOUSEWHEEL, delta: -20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gLineHeight / 2; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -20, 0.5 lines (pending: 0.5 lines)",
+ message: WM_MOUSEWHEEL, delta: -20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight / 2; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -20, 0.5 lines",
+ message: WM_MOUSEWHEEL, delta: -20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gLineHeight / 2; },
+ },
+ },
+
+ { description: "WM_MOUSEWHEEL, 20, -0.5 lines (pending: 0.5 lines)",
+ message: WM_MOUSEWHEEL, delta: 20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gLineHeight / -2; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, 20, -0.5 lines (pending: -0.5 lines)",
+ message: WM_MOUSEWHEEL, delta: 20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -1, pixels() { return -gLineHeight / 2; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, 20, -0.5 lines",
+ message: WM_MOUSEWHEEL, delta: 20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gLineHeight / -2; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 20, 0.5 chars",
+ message: WM_MOUSEHWHEEL, delta: 20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gCharWidth / 2; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 20, 0.5 chars (pending: 0.5 chars)",
+ message: WM_MOUSEHWHEEL, delta: 20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth / 2; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 20, 0.5 chars",
+ message: WM_MOUSEHWHEEL, delta: 20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gCharWidth / 2; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, -20, -0.5 chars (pending: 0.5 chars)",
+ message: WM_MOUSEHWHEEL, delta: -20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gCharWidth / -2; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, -20, -0.5 chars (pending: -0.5 chars)",
+ message: WM_MOUSEHWHEEL, delta: -20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -1, pixels() { return -gCharWidth / 2; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, -20, -0.5 chars",
+ message: WM_MOUSEHWHEEL, delta: -20,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gCharWidth / -2; },
+ },
+ },
+
+ // Even if the mouse cursor is an element whose font-size is different than
+ // the scrollable element, the pixel scroll amount shouldn't be changed.
+ // Widget shouldn't dispatch a pixel event if the delta can be devided by
+ // lines to be scrolled. However, pixel events should be fired by ESM.
+ { description: "WM_MOUSEWHEEL, -120, 3 lines, on the other div whose font-size is larger",
+ message: WM_MOUSEWHEEL, delta: -120,
+ target: gP2, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 3, pixels() { return gLineHeight * 3; },
+ },
+ },
+
+ { description: "WM_MOUSEWHEEL, 120, -3 lines, on the other div whose font-size is larger",
+ message: WM_MOUSEWHEEL, delta: 120,
+ target: gP2, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -3, pixels() { return gLineHeight * -3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 120, 3 chars, on the other div whose font-size is larger",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP2, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 3, pixels() { return gCharWidth * 3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, -120, -3 chars, on the other div whose font-size is larger",
+ message: WM_MOUSEHWHEEL, delta: -120,
+ target: gP2, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -3, pixels() { return gCharWidth * -3; },
+ },
+ },
+
+ // Modifier key tests
+ { description: "WM_MOUSEWHEEL, -40, 1 line with left Shift",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -40, 1 line with right Shift",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -40, 1 line with left Ctrl",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -40, 1 line with right Ctrl",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -40, 1 line with left Alt",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -40, 1 line with right Alt",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with left Shift",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with right Shift",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with left Ctrl",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with right Ctrl",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with left Alt",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with right Alt",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+
+ finish() {
+ runNextTest(gScrollMessageTests, 0);
+ },
+ },
+];
+
+var gPageScrllTests = [
+ // Pixel scroll event should be fired always but line scroll event should be
+ // fired only when accumulated delta value is over a line.
+ { description: "WM_MOUSEWHEEL, -60, 0.5 pages",
+ message: WM_MOUSEWHEEL, delta: -60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gPageHeight / 2; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -60, 0.5 pages (pending: 0.5 pages)",
+ message: WM_MOUSEWHEEL, delta: -60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels() { return ((gPageHeight / 2) + (gPageHeight % 2)); },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -60, 0.5 pages",
+ message: WM_MOUSEWHEEL, delta: -60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gPageHeight / 2; },
+ },
+ },
+
+ { description: "WM_MOUSEWHEEL, 60, -0.5 pages (pending: 0.5 pages)",
+ message: WM_MOUSEWHEEL, delta: 60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gPageHeight / -2; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, 60, -0.5 pages (pending: -0.5 pages)",
+ message: WM_MOUSEWHEEL, delta: 60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels() { return -((gPageHeight / 2) + (gPageHeight % 2)); },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, 60, -0.5 pages",
+ message: WM_MOUSEWHEEL, delta: 60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels() { return gPageHeight / -2; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 60, 0.5 pages",
+ message: WM_MOUSEHWHEEL, delta: 60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gPageWidth / 2; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 60, 0.5 pages (pending: 0.5 pages)",
+ message: WM_MOUSEHWHEEL, delta: 60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels() { return ((gPageWidth / 2) + (gPageWidth % 2)); },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 60, 0.5 pages",
+ message: WM_MOUSEHWHEEL, delta: 60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gPageWidth / 2; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, -60, -0.5 pages (pending: 0.5 pages)",
+ message: WM_MOUSEHWHEEL, delta: -60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gCharWidth / -2; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, -60, -0.5 pages (pending: -0.5 pages)",
+ message: WM_MOUSEHWHEEL, delta: -60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels() { return -((gCharWidth / 2) + (gCharWidth % 2)); },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, -60, -0.5 pages",
+ message: WM_MOUSEHWHEEL, delta: -60,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels() { return gCharWidth / -2; },
+ },
+ },
+];
+
+var gScrollMessageTests = [
+ // Widget should dispatch neither line scroll event nor pixel scroll event if
+ // the WM_*SCROLL's lParam is NULL and mouse wheel emulation is disabled.
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is NULL, emulation disabled",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels: 0,
+ },
+ init() {
+ SpecialPowers.setIntPref(kVAmountPref, 3);
+ SpecialPowers.setIntPref(kHAmountPref, 3);
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, false);
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_LINEUP, lParam is NULL, emulation disabled",
+ message: WM_VSCROLL, delta: SB_LINEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels: 0,
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is NULL, emulation disabled",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels: 0,
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINELEFT, lParam is NULL, emulation disabled",
+ message: WM_HSCROLL, delta: SB_LINELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels: 0,
+ },
+ },
+
+ // Widget should emulate mouse wheel behavior for WM_*SCROLL even if the
+ // kEmulateWheelByWMSCROLLPref is disabled but the message's lParam is not
+ // NULL. Then, widget doesn't dispatch a pixel event for WM_*SCROLL messages,
+ // but ESM dispatches it instead.
+ { description: "WM_VSCROLL, SB_LINEUP, lParam is not NULL, emulation disabled",
+ message: WM_VSCROLL, delta: SB_LINEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: -1, pixels() { return -gLineHeight; },
+ },
+ init() {
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, false);
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINELEFT, lParam is not NULL, emulation disabled",
+ message: WM_HSCROLL, delta: SB_LINELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: -1, pixels() { return -gCharWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_PAGEUP, lParam is not NULL, emulation disabled",
+ message: WM_VSCROLL, delta: SB_PAGEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels() { return -gPageHeight; },
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_PAGEDOWN, lParam is not NULL, emulation disabled",
+ message: WM_VSCROLL, delta: SB_PAGEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels() { return gPageHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_PAGELEFT, lParam is not NULL, emulation disabled",
+ message: WM_HSCROLL, delta: SB_PAGELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels() { return -gPageWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_PAGERIGHT, lParam is not NULL, emulation disabled",
+ message: WM_HSCROLL, delta: SB_PAGERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels() { return gPageWidth; },
+ },
+ },
+
+ // Widget should emulate mouse wheel behavior for WM_*SCROLL when the
+ // kEmulateWheelByWMSCROLLPref is enabled even if the message's lParam is
+ // NULL. Then, widget doesn't dispatch a pixel event for WM_*SCROLL messages,
+ // but ESM dispatches it instead.
+ { description: "WM_VSCROLL, SB_LINEUP, lParam is NULL, emulation enabled",
+ message: WM_VSCROLL, delta: SB_LINEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -1, pixels() { return -gLineHeight; },
+ },
+ init() {
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, true);
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is NULL, emulation enabled",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINELEFT, lParam is NULL, emulation enabled",
+ message: WM_HSCROLL, delta: SB_LINELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -1, pixels() { return -gCharWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is NULL, emulation enabled",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_PAGEUP, lParam is NULL, emulation enabled",
+ message: WM_VSCROLL, delta: SB_PAGEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels() { return -gPageHeight; },
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_PAGEDOWN, lParam is NULL, emulation enabled",
+ message: WM_VSCROLL, delta: SB_PAGEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels() { return gPageHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_PAGELEFT, lParam is NULL, emulation enabled",
+ message: WM_HSCROLL, delta: SB_PAGELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels() { return -gPageWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_PAGERIGHT, lParam is NULL, emulation enabled",
+ message: WM_HSCROLL, delta: SB_PAGERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels() { return gPageWidth; },
+ },
+ },
+
+ // Modifier key tests for WM_*SCROLL
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, with left Shift",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ init() {
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, false);
+ },
+ },
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, with right Shift",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_R,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, with left Ctrl",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, with right Ctrl",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, with left Alt",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, with right Alt",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_R,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, with left Shift",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, with right Shift",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: SHIFT_R,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, with left Ctrl",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, with right Ctrl",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: CTRL_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, with left Alt",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, with right Alt",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: ALT_R,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+
+ finish() {
+ runDeactiveWindowTests();
+ },
+ },
+];
+
+var gDeactiveWindowTests = [
+ // Typically, mouse drivers send wheel messages to focused window.
+ // However, we prefer to scroll a scrollable element under the mouse cursor.
+ { description: "WM_MOUSEWHEEL, -120, 3 lines, window is deactive",
+ message: WM_MOUSEWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 3, pixels() { return gLineHeight * 3; },
+ },
+ init() {
+ SpecialPowers.setIntPref(kVAmountPref, 3);
+ SpecialPowers.setIntPref(kHAmountPref, 3);
+ },
+ onLineScrollEvent(aEvent) {
+ var fm = Services.focus;
+ is(fm.activeWindow, gOtherWindow, "The other window isn't activated");
+ },
+ },
+
+ { description: "WM_MOUSEWHEEL, 120, -3 lines, window is deactive",
+ message: WM_MOUSEWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -3, pixels() { return gLineHeight * -3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 120, 3 chars, window is deactive",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 3, pixels() { return gCharWidth * 3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, -120, -3 chars, window is deactive",
+ message: WM_MOUSEHWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -3, pixels() { return gCharWidth * -3; },
+ },
+ },
+
+ // Of course, even if some drivers prefer the cursor position, we don't need
+ // to change anything.
+ { description: "WM_MOUSEWHEEL, -120, 3 lines, window is deactive (receive the message directly)",
+ message: WM_MOUSEWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: 3, pixels() { return gLineHeight * 3; },
+ },
+ },
+
+ { description: "WM_MOUSEWHEEL, 120, -3 lines, window is deactive (receive the message directly)",
+ message: WM_MOUSEWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: -3, pixels() { return gLineHeight * -3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 120, 3 chars, window is deactive (receive the message directly)",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: 3, pixels() { return gCharWidth * 3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, -120, -3 chars, window is deactive (receive the message directly)",
+ message: WM_MOUSEHWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: -3, pixels() { return gCharWidth * -3; },
+ },
+ },
+
+ // Same for WM_*SCROLL if lParam is not NULL
+ { description: "WM_VSCROLL, SB_LINEUP, lParam is not NULL, emulation disabled, window is deactive",
+ message: WM_VSCROLL, delta: SB_LINEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: -1, pixels() { return -gLineHeight; },
+ },
+ init() {
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, false);
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, window is deactive",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINELEFT, lParam is not NULL, emulation disabled, window is deactive",
+ message: WM_HSCROLL, delta: SB_LINELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: -1, pixels() { return -gCharWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, window is deactive",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+
+ // Same for WM_*SCROLL if lParam is NULL but emulation is enabled
+ { description: "WM_VSCROLL, SB_LINEUP, lParam is NULL, emulation enabled, window is deactive",
+ message: WM_VSCROLL, delta: SB_LINEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -1, pixels() { return -gLineHeight; },
+ },
+ init() {
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, true);
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is NULL, emulation enabled, window is deactive",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINELEFT, lParam is NULL, emulation enabled, window is deactive",
+ message: WM_HSCROLL, delta: SB_LINELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -1, pixels() { return -gCharWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is NULL, emulation enabled, window is deactive",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+
+ // Same for WM_*SCROLL if lParam is not NULL and message sent to the deactive window directly
+ { description: "WM_VSCROLL, SB_LINEUP, lParam is not NULL, emulation disabled, window is deactive (receive the message directly)",
+ message: WM_VSCROLL, delta: SB_LINEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL |
+ nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: -1, pixels() { return -gLineHeight; },
+ },
+ init() {
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, false);
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is not NULL, emulation disabled, window is deactive (receive the message directly)",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL |
+ nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINELEFT, lParam is not NULL, emulation disabled, window is deactive (receive the message directly)",
+ message: WM_HSCROLL, delta: SB_LINELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL |
+ nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: -1, pixels() { return -gCharWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is not NULL, emulation disabled, window is deactive (receive the message directly)",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL |
+ nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+ },
+
+ // Same for WM_*SCROLL if lParam is NULL but emulation is enabled, and message sent to the deactive window directly
+ { description: "WM_VSCROLL, SB_LINEUP, lParam is NULL, emulation enabled, window is deactive (receive the message directly)",
+ message: WM_VSCROLL, delta: SB_LINEUP,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: -1, pixels() { return -gLineHeight; },
+ },
+ init() {
+ SpecialPowers.setBoolPref(kEmulateWheelByWMSCROLLPref, true);
+ },
+ },
+
+ { description: "WM_VSCROLL, SB_LINEDOWN, lParam is NULL, emulation enabled, window is deactive (receive the message directly)",
+ message: WM_VSCROLL, delta: SB_LINEDOWN,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: 1, pixels() { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINELEFT, lParam is NULL, emulation enabled, window is deactive (receive the message directly)",
+ message: WM_HSCROLL, delta: SB_LINELEFT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: -1, pixels() { return -gCharWidth; },
+ },
+ },
+
+ { description: "WM_HSCROLL, SB_LINERIGHT, lParam is NULL, emulation enabled, window is deactive (receive the message directly)",
+ message: WM_HSCROLL, delta: SB_LINERIGHT,
+ target: gP1, x: 10, y: 10, window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: 1, pixels() { return gCharWidth; },
+ },
+
+ finish() {
+ gOtherWindow.close();
+ gOtherWindow = null;
+ window.close();
+ },
+ },
+];
+
+function runDeactiveWindowTests() {
+ gOtherWindow = window.open("window_mouse_scroll_win_2.html", "_blank",
+ "chrome,width=100,height=100,top=700,left=700");
+
+ window.arguments[0].SimpleTest.waitForFocus(function() {
+ runNextTest(gDeactiveWindowTests, 0);
+ }, gOtherWindow);
+}
+
+function runNextTest(aTests, aIndex) {
+ if (aIndex > 0 && aTests[aIndex - 1] && aTests[aIndex - 1].finish) {
+ aTests[aIndex - 1].finish();
+ }
+
+ if (aTests.length == aIndex) {
+ return;
+ }
+
+ var test = aTests[aIndex++];
+ if (test.init) {
+ test.init();
+ }
+ test.handled = { lines: false, pixels: false };
+
+ switch (test.message) {
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL:
+ case WM_VSCROLL:
+ case WM_HSCROLL:
+ var expectedLines = test.expected.lines;
+ var expectedPixels =
+ cut((typeof test.expected.pixels == "function") ?
+ test.expected.pixels() : test.expected.pixels);
+ var handler = function(aEvent) {
+ var doCommonTests = true;
+
+ if (!aEvent) {
+ ok(!test.handled.lines,
+ test.description + ", line scroll event has been handled");
+ ok(!test.handled.pixels,
+ test.description + ", pixel scroll event has been handled");
+ doCommonTests = false;
+ } else if (aEvent.type == kMouseLineScrollEvent) {
+ ok(!test.handled.lines,
+ test.description + ":(" + aEvent.type + "), same event has already been handled");
+ test.handled.lines = true;
+ isnot(expectedLines, 0,
+ test.description + ":(" + aEvent.type + "), event shouldn't be fired");
+ if (test.onLineScrollEvent && test.onLineScrollEvent(aEvent)) {
+ doCommonTests = false;
+ }
+ } else if (aEvent.type == kMousePixelScrollEvent) {
+ ok(!test.handled.pixels,
+ test.description + ":(" + aEvent.type + "), same event has already been handled");
+ test.handled.pixels = true;
+ isnot(expectedPixels, 0,
+ test.description + ":(" + aEvent.type + "), event shouldn't be fired");
+ if (test.onPixelScrollEvent && test.onPixelScrollEvent(aEvent)) {
+ doCommonTests = false;
+ }
+ }
+
+ if (doCommonTests) {
+ var expectedDelta =
+ (aEvent.type == kMouseLineScrollEvent) ?
+ expectedLines : expectedPixels;
+ is(aEvent.target.id, test.target.id,
+ test.description + ":(" + aEvent.type + "), ID mismatch");
+ is(aEvent.axis, test.expected.axis,
+ test.description + ":(" + aEvent.type + "), axis mismatch");
+ ok(aEvent.detail != 0,
+ test.description + ":(" + aEvent.type + "), delta must not be 0");
+ is(aEvent.detail, expectedDelta,
+ test.description + ":(" + aEvent.type + "), delta mismatch");
+ is(aEvent.shiftKey, (test.modifiers & (SHIFT_L | SHIFT_R)) != 0,
+ test.description + ":(" + aEvent.type + "), shiftKey mismatch");
+ is(aEvent.ctrlKey, (test.modifiers & (CTRL_L | CTRL_R)) != 0,
+ test.description + ":(" + aEvent.type + "), ctrlKey mismatch");
+ is(aEvent.altKey, (test.modifiers & (ALT_L | ALT_R)) != 0,
+ test.description + ":(" + aEvent.type + "), altKey mismatch");
+ }
+
+ if (!aEvent || (test.handled.lines || expectedLines == 0) &&
+ (test.handled.pixels || expectedPixels == 0)) {
+ // Don't scroll actually.
+ if (aEvent) {
+ aEvent.preventDefault();
+ }
+ test.target.removeEventListener(kMouseLineScrollEvent, handler, true);
+ test.target.removeEventListener(kMousePixelScrollEvent, handler, true);
+ setTimeout(runNextTest, 0, aTests, aIndex);
+ }
+ };
+
+ test.target.addEventListener(kMouseLineScrollEvent, handler, true);
+ test.target.addEventListener(kMousePixelScrollEvent, handler, true);
+
+ if (expectedLines == 0 && expectedPixels == 0) {
+ // The timeout might not be enough if system is slow by other process,
+ // so, the test might be passed unexpectedly. However, it must be able
+ // to be detected by random orange.
+ setTimeout(handler, 500);
+ }
+
+ var utils = getWindowUtils(test.window);
+ var ptInScreen = getPointInScreen(test.target, test.window);
+ var isVertical =
+ ((test.message == WM_MOUSEWHEEL) || (test.message == WM_VSCROLL));
+ var deltaX = !isVertical ? test.delta : 0;
+ var deltaY = isVertical ? test.delta : 0;
+ utils.sendNativeMouseScrollEvent(ptInScreen.x + test.x,
+ ptInScreen.y + test.y,
+ test.message, deltaX, deltaY, 0,
+ test.modifiers,
+ test.additionalFlags,
+ test.target);
+ break;
+ default:
+ ok(false, test.description + ": invalid message");
+ // Let's timeout.
+ }
+}
+
+function prepareTests() {
+ // Disable special action with modifier key
+ SpecialPowers.setIntPref(kAltKeyActionPref, 1);
+ SpecialPowers.setIntPref(kCtrlKeyActionPref, 1);
+ SpecialPowers.setIntPref(kShiftKeyActionPref, 1);
+ SpecialPowers.setIntPref(kWinKeyActionPref, 1);
+
+ SpecialPowers.setIntPref(kAltKeyDeltaMultiplierXPref, 100);
+ SpecialPowers.setIntPref(kAltKeyDeltaMultiplierYPref, 100);
+ SpecialPowers.setIntPref(kCtrlKeyDeltaMultiplierXPref, 100);
+ SpecialPowers.setIntPref(kCtrlKeyDeltaMultiplierYPref, 100);
+ SpecialPowers.setIntPref(kShiftKeyDeltaMultiplierXPref, 100);
+ SpecialPowers.setIntPref(kShiftKeyDeltaMultiplierYPref, 100);
+ SpecialPowers.setIntPref(kWinKeyDeltaMultiplierXPref, 100);
+ SpecialPowers.setIntPref(kWinKeyDeltaMultiplierYPref, 100);
+
+ SpecialPowers.setBoolPref(kSystemScrollSpeedOverridePref, false);
+ SpecialPowers.setIntPref(kTimeoutPref, -1);
+
+ runNextTest(gPreparingSteps, 0);
+}
+
+</script>
+</body>
+
+</html>
diff --git a/widget/tests/window_mouse_scroll_win_2.html b/widget/tests/window_mouse_scroll_win_2.html
new file mode 100644
index 0000000000..c8d3762405
--- /dev/null
+++ b/widget/tests/window_mouse_scroll_win_2.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ Helper file for window_mouse_scroll_win.html
+</body>
+</html>
diff --git a/widget/tests/window_picker_no_crash_child.html b/widget/tests/window_picker_no_crash_child.html
new file mode 100644
index 0000000000..c980f979be
--- /dev/null
+++ b/widget/tests/window_picker_no_crash_child.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<title>Picker window</title>
+<form name="form1">
+ <input type="file" name="uploadbox" id="uploadbox" onclick="window.clicked = true;">
+ <input type="file" name="multiple" id="multiple" multiple onclick="window.clicked = true">
+</form>
diff --git a/widget/tests/window_state_windows.xhtml b/widget/tests/window_state_windows.xhtml
new file mode 100644
index 0000000000..60989db144
--- /dev/null
+++ b/widget/tests/window_state_windows.xhtml
@@ -0,0 +1,80 @@
+<?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="NativeWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="300"
+ height="300"
+ onload="onLoad();"
+ title="Window State Tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript">
+ <![CDATA[
+
+ SimpleTest.waitForExplicitFinish();
+
+ function onLoad() {
+ var win = Services.wm.getMostRecentWindow("navigator:browser");
+
+ /*
+ switch(win.windowState) {
+ case win.STATE_FULLSCREEN:
+ dump("STATE_FULLSCREEN \n");
+ break;
+ case win.STATE_MAXIMIZED:
+ dump("STATE_MAXIMIZED \n");
+ break;
+ case win.STATE_MINIMIZED:
+ dump("STATE_MINIMIZED \n");
+ break;
+ case win.STATE_NORMAL:
+ dump("STATE_NORMAL \n");
+ break;
+ }
+ */
+
+ // Make sure size mode changes are reflected in the widget.
+ win.restore();
+ ok(win.windowState == win.STATE_NORMAL, "window state is restored.");
+ win.minimize();
+ ok(win.windowState == win.STATE_MINIMIZED, "window state is minimized.");
+
+ // Windows resizes children to 0x0. Code in nsWindow filters these changes out. Without
+ // this all sorts of screwy things can happen in child widgets.
+ ok(document.documentElement.clientHeight > 0, "document height should not be zero for a minimized window!");
+ ok(document.documentElement.clientWidth > 0, "document width should not be zero for a minimized window!");
+
+ // Make sure size mode changes are reflected in the widget.
+ win.restore();
+ ok(win.windowState == win.STATE_NORMAL, "window state is restored.");
+ win.maximize();
+ ok(win.windowState == win.STATE_MAXIMIZED, "window state is maximized.");
+ win.restore();
+ ok(win.windowState == win.STATE_NORMAL, "window state is restored.");
+
+ /*
+ dump(win.screenX + "\n");
+ win.minimize();
+ dump(win.screenX + "\n");
+ win.restore();
+ dump(win.screenX + "\n");
+ */
+
+ 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/widget/tests/window_wheeltransaction.xhtml b/widget/tests/window_wheeltransaction.xhtml
new file mode 100644
index 0000000000..f3c081b105
--- /dev/null
+++ b/widget/tests/window_wheeltransaction.xhtml
@@ -0,0 +1,1569 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Wheel scroll tests"
+ width="600" height="600"
+ onload="onload();"
+ onunload="onunload();"
+ 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" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<style type="text/css">
+ #rootview {
+ overflow: auto;
+ width: 400px;
+ height: 400px;
+ border: 1px solid;
+ }
+ #container {
+ overflow: auto;
+ width: 600px;
+ height: 600px;
+ }
+ #rootview pre {
+ margin: 20px 0 20px 20px;
+ padding: 0;
+ overflow: auto;
+ display: block;
+ width: 100px;
+ height: 100.5px;
+ font-size: 16px;
+ }
+</style>
+<div id="rootview" onscroll="onScrollView(event);">
+ <div id="container">
+ <pre id="subview1" onscroll="onScrollView(event);">
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+ </pre>
+ <pre id="subview2" onscroll="onScrollView(event);">
+Text.
+Text.
+Text.
+Text.
+Text.
+Text.
+Text.
+Text.
+Text.
+Text.
+ </pre>
+ <pre id="subview3" onscroll="onScrollView(event);">
+Text. Text. Text. Text. Text. Text. Text. Text. Text. Text. Text.
+ </pre>
+ </div>
+</div>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<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);
+}
+
+var gCurrentTestListStatus = { nextListIndex: 0 };
+var gCurrentTest;
+
+const kListenEvent_None = 0;
+const kListenEvent_OnScroll = 1;
+const kListenEvent_OnScrollFailed = 2;
+const kListenEvent_OnTransactionTimeout = 4;
+const kListenEvent_All = kListenEvent_OnScroll |
+ kListenEvent_OnScrollFailed |
+ kListenEvent_OnTransactionTimeout;
+var gLitesnEvents = kListenEvent_None;
+
+/**
+ * At unexpected transaction timeout, we need to stop *all* timers. But it is
+ * difficult and it can be create more complex testing code. So, we should use
+ * only one timer at one time. For that, we must store the timer id to this
+ * variable. And the functions which may be called via a timer must clear the
+ * current timer by |_clearTimer| function.
+ */
+var gTimer;
+
+var gPrefSvc = SpecialPowers.Services.prefs;
+const kPrefSmoothScroll = "general.smoothScroll";
+const kPrefNameTimeout = "mousewheel.transaction.timeout";
+const kPrefNameIgnoreMoveDelay = "mousewheel.transaction.ignoremovedelay";
+const kPrefTestEventsAsyncEnabled = "test.events.async.enabled";
+
+const kDefaultTimeout = gPrefSvc.getIntPref(kPrefNameTimeout);
+const kDefaultIgnoreMoveDelay = gPrefSvc.getIntPref(kPrefNameIgnoreMoveDelay);
+
+gPrefSvc.setBoolPref(kPrefSmoothScroll, false);
+gPrefSvc.setBoolPref(kPrefTestEventsAsyncEnabled, true);
+
+var gTimeout, gIgnoreMoveDelay;
+var gEnoughForTimeout, gEnoughForIgnoreMoveDelay;
+
+function setTimeoutPrefs(aTimeout, aIgnoreMoveDelay)
+{
+ gPrefSvc.setIntPref(kPrefNameTimeout, aTimeout);
+ gPrefSvc.setIntPref(kPrefNameIgnoreMoveDelay, aIgnoreMoveDelay);
+ gTimeout = aTimeout;
+ gIgnoreMoveDelay = aIgnoreMoveDelay;
+ gEnoughForTimeout = gTimeout * 2;
+ gEnoughForIgnoreMoveDelay = gIgnoreMoveDelay * 1.2;
+}
+
+function resetTimeoutPrefs()
+{
+ if (gTimeout == kDefaultTimeout)
+ return;
+ setTimeoutPrefs(kDefaultTimeout, kDefaultIgnoreMoveDelay);
+ initTestList();
+}
+
+function growUpTimeoutPrefs()
+{
+ if (gTimeout != kDefaultTimeout)
+ return;
+ setTimeoutPrefs(5000, 1000);
+ initTestList();
+}
+
+// setting enough time for testing.
+gPrefSvc.setIntPref(kPrefNameTimeout, gTimeout);
+gPrefSvc.setIntPref(kPrefNameIgnoreMoveDelay, gIgnoreMoveDelay);
+
+var gRootView = document.getElementById("rootview");
+var gSubView1 = document.getElementById("subview1");
+var gSubView2 = document.getElementById("subview2");
+var gSubView3 = document.getElementById("subview3");
+
+gRootView.addEventListener("MozMouseScrollFailed", onMouseScrollFailed);
+gRootView.addEventListener("MozMouseScrollTransactionTimeout",
+ onTransactionTimeout);
+
+function finish()
+{
+ window.close();
+}
+
+async function onload()
+{
+ // Before actually running tests, we disable auto-dir scrolling, becasue the
+ // tests in this file are meant to test scrolling transactions, not 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]]});
+
+ runNextTestList();
+}
+
+function onunload()
+{
+ resetTimeoutPrefs();
+ gPrefSvc.clearUserPref(kPrefSmoothScroll);
+ gPrefSvc.clearUserPref(kPrefTestEventsAsyncEnabled);
+ disableNonTestMouseEvents(false);
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+ window.arguments[0].SimpleTest.finish();
+}
+
+function offsetForRootView()
+{
+ let rootViewRect = gRootView.getBoundingClientRect();
+ let subView1Rect = gSubView1.getBoundingClientRect();
+ return {
+ x: (subView1Rect.left - rootViewRect.left) / 2,
+ y: (subView1Rect.top - rootViewRect.top) / 2,
+ }
+}
+
+function _offsetFor(aSubView)
+{
+ let rootViewRect = gRootView.getBoundingClientRect();
+ let subViewRect = aSubView.getBoundingClientRect();
+ return {
+ x: subViewRect.left - rootViewRect.left + subViewRect.width / 2,
+ y: subViewRect.top - rootViewRect.top + subViewRect.height / 2,
+ }
+}
+
+function offsetForSubView1()
+{
+ return _offsetFor(gSubView1);
+}
+
+function offsetForSubView2()
+{
+ return _offsetFor(gSubView2);
+}
+
+function offsetForSubView3()
+{
+ return _offsetFor(gSubView3);
+}
+
+/**
+ * Define the tests here:
+ * Scrolls are processed async always. Therefore, we need to call all tests
+ * by timer. gTestLists is array of testing lists. In other words, an item
+ * of gTestList is a group of one or more testing. Each items has following
+ * properties:
+ *
+ * - retryWhenTransactionTimeout
+ * The testing of wheel transaction might be fialed randomly by
+ * timeout. Then, automatically the failed test list will be retested
+ * automatically only this number of times.
+ *
+ * - steps
+ * This property is array of testing. Each steps must have following
+ * properties at least.
+ *
+ * - func
+ * This property means function which will be called via
+ * |setTimeout|. The function cannot have params. If you need
+ * some additional parameters, you can specify some original
+ * properties for the test function. If you do so, you should
+ * document it in the testing function.
+ * - delay
+ * This property means delay time until the function to be called.
+ * I.e., the value used for the second param of |setTimeout|.
+ *
+ * And also you need one more property when you call a testing function.
+ *
+ * - description
+ * This property is description of the test. This is used for
+ * logging.
+ *
+ * At testing, you can access to current step via |gCurrentTest|.
+ */
+
+var gTestLists;
+function initTestList()
+{
+ gTestLists = [
+ /**************************************************************************
+ * Continuous scrolling test for |gRootView|
+ * |gRootView| has both scrollbars and it has three children which are
+ * |gSubView1|, |gSubView2| and |gSubView3|. They have scrollbars. If
+ * the current transaction targets |gRootView|, other children should not
+ * be scrolled even if the wheel events are fired on them.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Vertical wheel events should scroll |gRootView| even if the position
+ // of wheel events in a child view which has scrollbar.
+ { func: testContinuousScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Continuous scrolling test for root view (vertical/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ description: "Continuous scrolling test for root view (vertical/backward)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Horizontal wheel events should scroll |gRootView| even if the
+ // position of wheel events in a child view which has scrollbar.
+ { func: testContinuousScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Continuous scrolling test for root view (horizontal/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ description: "Continuous scrolling test for root view (horizontal/backward)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Continuous scrolling test for |gSubView1|
+ * |gSubView1| has both scrollbars.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Vertical wheel events should scroll |gSubView1|.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ description: "Continuous scrolling test for sub view 1 (vertical/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: true, expectedView: gSubView1,
+ description: "Continuous scrolling test for sub view 1 (vertical/backward)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Horitontal wheel events should scroll |gSubView1|.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ description: "Continuous scrolling test for sub view 1 (horizontal/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: false, expectedView: gSubView1,
+ description: "Continuous scrolling test for sub view 1 (horizontal/backward)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Continuous scrolling test for |gSubView2|
+ * |gSubView2| has only vertical scrollbar.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Vertical wheel events should scroll |gSubView2|.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView2,
+ isForward: true, isVertical: true, expectedView: gSubView2,
+ description: "Continuous scrolling test for sub view 2 (vertical/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView2,
+ isForward: false, isVertical: true, expectedView: gSubView2,
+ description: "Continuous scrolling test for sub view 2 (vertical/backward)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Horizontal wheel events should scroll its nearest scrollable ancestor
+ // view, i.e., it is |gRootView|.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView2,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Continuous scrolling test for sub view 2 (horizontal/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView2,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ description: "Continuous scrolling test for sub view 2 (horizontal/backward)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Continuous scrolling test for |gSubView3|
+ * |gSubView3| has only horizontal scrollbar.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Vertical wheel events should scroll its nearest scrollable ancestor
+ // view, i.e., it is |gRootView|.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView3,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Continuous scrolling test for sub view 3 (vertical/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView3,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ description: "Continuous scrolling test for sub view 3 (vertical/backward)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Horitontal wheel events should scroll |gSubView3|.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView3,
+ isForward: true, isVertical: false, expectedView: gSubView3,
+ description: "Continuous scrolling test for sub view 3 (horizontal/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView3,
+ isForward: false, isVertical: false, expectedView: gSubView3,
+ description: "Continuous scrolling test for sub view 3 (horizontal/backward)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Don't reset transaction by a different direction wheel event
+ * Even if a wheel event doesn't same direction as last wheel event, the
+ * current transaction should not be reset.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical -> Horizontal
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView| by a vertical wheel
+ // event.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Don't reset transaction by a different direction wheel event (1-1)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ description: "Don't reset transaction by a different direction wheel event (1-2)" },
+ // Send a horizontal wheel event over |gSubView1| but |gRootView| should
+ // be scrolled.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Don't reset transaction by a different direction wheel event (1-3)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal -> Vertical
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView| by a horizontal wheel
+ // event.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Don't reset transaction by a different direction wheel event (2-1)" },
+ // Scroll back to left-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ description: "Don't reset transaction by a different direction wheel event (2-2)" },
+ // Send a vertical wheel event over |gSubView1| but |gRootView| should
+ // be scrolled.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Don't reset transaction by a different direction wheel event (2-3)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Don't reset transaction even if a wheel event cannot scroll
+ * Even if a wheel event cannot scroll to specified direction in the
+ * current target view, the transaction should not be reset. E.g., there
+ * are some devices which can scroll obliquely. If so, probably, users
+ * cannot input only intended direction.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // A view only has vertical scrollbar case.
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gSubView2|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView2,
+ isForward: true, isVertical: true, expectedView: gSubView2,
+ description: "Don't reset transaction even if a wheel event cannot scroll (1-1)" },
+ // |gSubView2| doesn't have horizontal scrollbar but should not scroll
+ // any views.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView2,
+ isForward: true, isVertical: false, expectedView: null,
+ description: "Don't reset transaction even if a wheel event cannot scroll (1-2)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // A view only has horizontal scrollbar case.
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gSubView3|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView3,
+ isForward: true, isVertical: false, expectedView: gSubView3,
+ description: "Don't reset transaction even if a wheel event cannot scroll (2-1)" },
+ // |gSubView3| doesn't have vertical scrollbar but should not scroll any
+ // views.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView3,
+ isForward: true, isVertical: true, expectedView: null,
+ description: "Don't reset transaction even if a wheel event cannot scroll (2-2)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Reset transaction by mouse down/mouse up events
+ * Mouse down and mouse up events should cause resetting the current
+ * transaction.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by mouse down/mouse up events (v-1)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by mouse down/mouse up events (v-2)" },
+ // Send mouse button events which should reset the current transaction.
+ // So, the next wheel event should scroll |gSubView1|.
+ { func: sendMouseButtonEvents, delay: 0,
+ description: "sendMouseButtonEvents" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ description: "Reset transaction by mouse down/mouse up events (v-3)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Reset transaction by mouse down/mouse up events (h-1)" },
+ // Scroll back to left-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ description: "Reset transaction by mouse down/mouse up events (h-2)" },
+ // Send mouse button events which should reset the current transaction.
+ // So, the next wheel event should scroll |gSubView1|.
+ { func: sendMouseButtonEvents, delay: 0,
+ description: "sendMouseButtonEvents" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ description: "Reset transaction by mouse down/mouse up events (h-3)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Reset transaction by a key event
+ * A key event should cause resetting the current transaction.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by a key event (v-1)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by a key event (v-2)" },
+ // Send a key event which should reset the current transaction. So, the
+ // next wheel event should scroll |gSubView1|.
+ { func: sendKeyEvents, delay: 0, key: "a",
+ description: "sendKeyEvents" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ description: "Reset transaction by a key event (v-3)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Reset transaction by a key event (h-1)" },
+ // Scroll back to left-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ description: "Reset transaction by a key event (h-2)" },
+ // Send a key event which should reset the current transaction. So, the
+ // next wheel event should scroll |gSubView1|.
+ { func: sendKeyEvents, delay: 0, key: "a",
+ description: "sendKeyEvents" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ description: "Reset transaction by a key event (h-3)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Reset transaction by a mouse move event
+ * A mouse move event can cause reseting the current transaction even if
+ * mouse cursor is inside the target view of current transaction. Only
+ * when a wheel event is fired after |gIgnoreMoveDelay| milliseconds since
+ * the first mouse move event from last wheel event, the transaction
+ * should be reset.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by a mouse move event (v-1)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by a mouse move event (v-2)" },
+ // Send a mouse move event immediately after last wheel event, then,
+ // current transaction should be kept.
+ { func: sendMouseMoveEvent, delay: 0, offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (v-3)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (v-4)" },
+ // Send a mouse move event after |gIgnoreMoveDelay| milliseconds since
+ // last wheel event, then, current transaction should be kept.
+ { func: sendMouseMoveEvent, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (v-5)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (v-6)" },
+ // Send a wheel event after |gIgnoreMoveDelay| milliseconds since last
+ // mouse move event but it is fired immediately after the last wheel
+ // event, then, current transaction should be kept.
+ { func: sendMouseMoveEvent, delay: 0, offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (v-7)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (v-8)" },
+ // Send a wheel event after |gIgnoreMoveDelay| milliseconds have passed
+ // since last mouse move event which is fired after |gIgnoreMoveDelay|
+ // milliseconds since last wheel event, then, current transaction should
+ // be reset.
+ { func: sendMouseMoveEvent, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ canFailRandomly: { possibleView: gRootView },
+ description: "Reset transaction by a mouse move event (v-9)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-1)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-2)" },
+ // Send a mouse move event immediately after last wheel event, then,
+ // current transaction should be kept.
+ { func: sendMouseMoveEvent, delay: 0, offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-3)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-4)" },
+ // Send a mouse move event after |gIgnoreMoveDelay| milliseconds since
+ // last wheel event, then, current transaction should be kept.
+ { func: sendMouseMoveEvent, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-5)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-6)" },
+ // Send a wheel event after |gIgnoreMoveDelay| milliseconds since last
+ // mouse move event but it is fired immediately after the last wheel
+ // event, then, current transaction should be kept.
+ { func: sendMouseMoveEvent, delay: 0, offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-7)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Reset transaction by a mouse move event (h-8)" },
+ // Send a wheel event after |gIgnoreMoveDelay| milliseconds have passed
+ // since last mouse move event which is fired after |gIgnoreMoveDelay|
+ // milliseconds since last wheel event, then, current transaction should
+ // be reset.
+ { func: sendMouseMoveEvent, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: gEnoughForIgnoreMoveDelay,
+ offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ canFailRandomly: { possibleView: gRootView },
+ description: "Reset transaction by a mouse move event (h-9)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Reset transaction by a mouse move event on outside of view
+ * When mouse cursor is moved to outside of the current target view, the
+ * transaction should be reset immediately.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gSubView1|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ description: "Reset transaction by a mouse move event on outside of view (v-1)" },
+ // Send mouse move event over |gRootView|.
+ { func: sendMouseMoveEvent, delay: 0, offset: offsetForRootView,
+ description: "sendMouseMoveEvent" },
+ // Send Wheel event over |gRootView| which should be scrolled.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by a mouse move event on outside of view (v-2)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Create a transaction which targets |gSubView1|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ description: "Reset transaction by a mouse move event on outside of view (h-1)" },
+ // Send mouse move event over |gRootView|.
+ { func: sendMouseMoveEvent, delay: 0, offset: offsetForRootView,
+ description: "sendMouseMoveEvent" },
+ // Send Wheel event over |gRootView| which should be scrolled.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Reset transaction by a mouse move event on outside of view (h-2)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Timeout test
+ * A view should not be scrolled during another to be transaction for
+ * another view scrolling. However, a wheel event which is sent after
+ * timeout, a view which is under the mouse cursor should be scrolled.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // First, create a transaction which should target the |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Timeout test (v-1)" },
+ // Scroll back to top-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ description: "Timeout test (v-2)" },
+ // A wheel event over |gSubView1| should not scroll it during current
+ // transaction.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Timeout test (v-3)" },
+ // Scroll back to top-most again.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: true, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Timeout test (v-4)" },
+ // A wheel event over |gSubView1| after timeout should scroll
+ // |gSubView1|.
+ { func: testOneTimeScroll, delay: gEnoughForTimeout,
+ offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ isTimeoutTesting: true,
+ description: "Timeout test (v-5)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // First, create a transaction which should target the |gRootView|.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Timeout test (h-1)" },
+ // Scroll back to left-most for easy cursor position specifying.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForRootView,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ description: "Timeout test (h-2)" },
+ // A wheel event over |gSubView1| should not scroll it during current
+ // transaction.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Timeout test (h-3)" },
+ // Scroll back to left-most again.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: false, isVertical: false, expectedView: gRootView,
+ canFailRandomly: { possibleView: gSubView1 },
+ description: "Timeout test (h-4)" },
+ // A wheel event over |gSubView1| after timeout should scroll
+ // |gSubView1|.
+ { func: testOneTimeScroll, delay: gEnoughForTimeout,
+ offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ isTimeoutTesting: true,
+ description: "Timeout test (h-5)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Timeout test even with many wheel events
+ * This tests whether timeout is occurred event if wheel events are sent.
+ * The transaction should not be updated by non-scrollable wheel events.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Vertical case
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Scroll |gSubView1| to bottom-most.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ description: "Timeout test even with many wheel events (v-1)" },
+ // Don't scroll any views before timeout.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: null,
+ canFailRandomly: { possibleView: gRootView },
+ description: "Timeout test even with many wheel events (v-2)" },
+ // Recreate a transaction which is scrolling |gRootView| after time out.
+ { func: testRestartScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Timeout test even with many wheel events (v-3)" }
+ ]
+ },
+
+
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ // Horizontal case
+ { func: initElements, delay: 0, forVertical: false,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ // Scroll |gSubView1| to right-most.
+ { func: testContinuousScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ description: "Timeout test even with many wheel events (h-1)" },
+ // Don't scroll any views before timeout.
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: null,
+ canFailRandomly: { possibleView: gRootView },
+ description: "Timeout test even with many wheel events (h-2)" },
+ // Recreate a transaction which is scrolling |gRootView| after time out.
+ { func: testRestartScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Timeout test even with many wheel events (h-3)" }
+ ]
+ },
+
+
+ /**************************************************************************
+ * Very large scrolling wheel event
+ * If the delta value is larger than the scrolling page size, it should be
+ * scrolled only one page instead of the delta value.
+ **************************************************************************/
+ { retryWhenTransactionTimeout: 5,
+ steps: [
+ { func: initElements, delay: 0, forVertical: true,
+ description: "initElements" },
+ { func: clearWheelTransaction, delay: 0,
+ description: "clearWheelTransaction" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ delta: 5000,
+ description: "Very large delta scrolling (v-1)" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ delta: 5000,
+ description: "Very large delta scrolling (v-2)" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ delta: 5000,
+ description: "Very large delta scrolling (h-1)" },
+ { func: testOneTimeScroll, delay: 0, offset: offsetForSubView1,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ delta: 5000,
+ description: "Very large delta scrolling (h-2)" }
+ ]
+ }
+ ];
+}
+
+/******************************************************************************
+ * Actions for preparing tests
+ ******************************************************************************/
+
+function initElements()
+{
+ _clearTimer();
+
+ function resetScrollPosition(aElement)
+ {
+ aElement.scrollTop = 0;
+ aElement.scrollLeft = 0;
+ }
+
+ const kDisplay = gCurrentTest.forVertical ? "block" : "inline-block";
+ gSubView1.style.display = kDisplay;
+ gSubView2.style.display = kDisplay;
+ gSubView3.style.display = kDisplay;
+
+ resetScrollPosition(gRootView);
+ resetScrollPosition(gSubView1);
+ resetScrollPosition(gSubView2);
+ resetScrollPosition(gSubView3);
+ _getDOMWindowUtils(window).advanceTimeAndRefresh(0);
+
+ runNextTestStep();
+}
+
+function clearWheelTransaction()
+{
+ _clearTimer();
+ _clearTransaction();
+ runNextTestStep();
+}
+
+function sendKeyEvents()
+{
+ _clearTimer();
+ synthesizeKey(gCurrentTest.key, {}, window);
+ runNextTestStep();
+}
+
+function sendMouseButtonEvents()
+{
+ _clearTimer();
+ synthesizeMouse(gRootView, -1, -1, { type:"mousedown" }, window);
+ synthesizeMouse(gRootView, -1, -1, { type:"mouseup" }, window);
+ runNextTestStep();
+}
+
+function sendMouseMoveEvent()
+{
+ _clearTimer();
+ _fireMouseMoveEvent(gCurrentTest.offset());
+ runNextTestStep();
+}
+
+/******************************************************************************
+ * Utilities for testing functions
+ ******************************************************************************/
+
+function _clearTransaction()
+{
+ synthesizeMouse(gRootView, -1, -1, { type:"mousedown" }, window);
+ synthesizeMouse(gRootView, -1, -1, { type:"mouseup" }, window);
+}
+
+function _saveScrollPositions()
+{
+ function save(aElement)
+ {
+ aElement.prevTop = aElement.scrollTop;
+ aElement.prevLeft = aElement.scrollLeft;
+ }
+ save(gRootView);
+ save(gSubView1);
+ save(gSubView2);
+ save(gSubView3);
+}
+
+function _fireMouseMoveEvent(aOffset)
+{
+ synthesizeMouse(gRootView, aOffset.x, aOffset.y, { type:"mousemove" }, window);
+}
+
+function _fireWheelScrollEvent(aOffset, aIsVertical, aForward, aDelta)
+{
+ var event = { deltaMode: WheelEvent.DOM_DELTA_LINE };
+ if (aIsVertical) {
+ event.deltaY = aForward ? aDelta : -aDelta;
+ } else {
+ event.deltaX = aForward ? aDelta : -aDelta;
+ }
+ sendWheelAndPaint(gRootView, aOffset.x, aOffset.y, event, null, window);
+}
+
+function _canScroll(aElement, aIsVertical, aForward)
+{
+ if (aIsVertical) {
+ if (!aForward)
+ return aElement.scrollTop > 0;
+ return aElement.scrollHeight > aElement.scrollTop + aElement.clientHeight;
+ }
+ if (!aForward)
+ return aElement.scrollLeft > 0;
+ return aElement.scrollWidth > aElement.scrollLeft + aElement.clientWidth;
+}
+
+const kNotScrolled = 0;
+const kScrolledToTop = 1;
+const kScrolledToBottom = 2;
+const kScrolledToLeft = 4;
+const kScrolledToRight = 8;
+
+const kScrolledVertical = kScrolledToTop | kScrolledToBottom;
+const kScrolledHorizontal = kScrolledToLeft | kScrolledToRight;
+
+function _getScrolledState(aElement)
+{
+ var ret = kNotScrolled;
+ if (aElement.scrollTop != aElement.prevTop) {
+ ret |= aElement.scrollTop < aElement.prevTop ? kScrolledToTop :
+ kScrolledToBottom;
+ }
+ if (aElement.scrollLeft != aElement.prevLeft) {
+ ret |= aElement.scrollLeft < aElement.prevLeft ? kScrolledToLeft :
+ kScrolledToRight;
+ }
+ return ret;
+}
+
+function _getExpectedScrolledState()
+{
+ // eslint-disable-next-line no-nested-ternary
+ return gCurrentTest.isVertical ?
+ gCurrentTest.isForward ? kScrolledToBottom : kScrolledToTop :
+ gCurrentTest.isForward ? kScrolledToRight : kScrolledToLeft;
+}
+
+function _getScrolledStateText(aScrolledState)
+{
+ if (aScrolledState == kNotScrolled)
+ return "Not scrolled";
+
+ var s = "scrolled to ";
+ if (aScrolledState & kScrolledVertical) {
+ s += aScrolledState & kScrolledToTop ? "backward" : "forward";
+ s += " (vertical)"
+ if (aScrolledState & kScrolledHorizontal)
+ s += " and to ";
+ }
+ if (aScrolledState & kScrolledHorizontal) {
+ s += aScrolledState & kScrolledToLeft ? "backward" : "forward";
+ s += " (horizontal)"
+ }
+ return s;
+}
+
+function _getCurrentTestList()
+{
+ return gTestLists[gCurrentTestListStatus.nextListIndex - 1];
+}
+
+function _clearTimer()
+{
+ clearTimeout(gTimer);
+ gTimer = 0;
+}
+
+/******************************************************************************
+ * Testing functions
+ ******************************************************************************/
+
+/**
+ * Note that testing functions must set following variables:
+ *
+ * gCurrentTest.repeatTest: See comment in |continueTest|.
+ * gCurrentTest.autoRepeatDelay: See comment in |continueTest|.
+ * gListenScrollEvent: When this is not true, the event handlers ignores the
+ * events.
+ */
+
+function testContinuousScroll()
+{
+ /**
+ * Testing continuous scrolling. This function synthesizes a wheel event. If
+ * the test was success, this function will be recalled automatically.
+ * And when a generating wheel event cannot scroll the expected view, this
+ * function fires the wheel event only one time.
+ *
+ * @param gCurrentTest.offset
+ * A function to compute the cursor position of firing wheel event.
+ * The values are offset from |gRootView|.
+ * @param gCurrentTest.isVertical
+ * Whether the wheel event is for virtical scrolling or horizontal.
+ * @param gCurrentTest.isForward
+ * Whether the wheel event is to forward or to backward.
+ * @param gCurrentTest.expectedView
+ * The expected view which will be scrolled by wheel event. This
+ * value must not be null.
+ */
+
+ _clearTimer();
+ _saveScrollPositions();
+ if (!gCurrentTest.expectedView) {
+ runNextTestStep();
+ return;
+ }
+
+ gLitesnEvents = kListenEvent_All;
+ gCurrentTest.repeatTest = true;
+ gCurrentTest.autoRepeatDelay = 0;
+
+ if (!_canScroll(gCurrentTest.expectedView,
+ gCurrentTest.isVertical, gCurrentTest.isForward)) {
+ gCurrentTest.expectedView = null;
+ }
+ var delta = gCurrentTest.delta ? gCurrentTest.delta : 4;
+ _fireWheelScrollEvent(gCurrentTest.offset(),
+ gCurrentTest.isVertical, gCurrentTest.isForward, delta);
+}
+
+function testOneTimeScroll()
+{
+ /**
+ * Testing one wheel event. |runNextTestStep| will be called immediately
+ * after this function by |onScrollView| or |onTimeout|.
+ *
+ * @param gCurrentTest.offset
+ * A function to compute the cursor position of firing wheel event.
+ * The values are offset from |gRootView|.
+ * @param gCurrentTest.isVertical
+ * Whether the wheel event is for virtical scrolling or horizontal.
+ * @param gCurrentTest.isForward
+ * Whether the wheel event is to forward or to backward.
+ * @param gCurrentTest.expectedView
+ * The expected view which will be scrolled by wheel event. This
+ * value can be null. It means any views should not be scrolled.
+ */
+
+ _clearTimer();
+ _saveScrollPositions();
+
+ gLitesnEvents = kListenEvent_All;
+ gCurrentTest.repeatTest = false;
+ gCurrentTest.autoRepeatDelay = 0;
+
+ var delta = gCurrentTest.delta ? gCurrentTest.delta : 4;
+ _fireWheelScrollEvent(gCurrentTest.offset(),
+ gCurrentTest.isVertical, gCurrentTest.isForward, delta);
+}
+
+function testRestartScroll()
+{
+ /**
+ * Testing restart to scroll in expected view after timeout from the current
+ * transaction. This function recall this itself until to success this test
+ * or timeout from this test.
+ *
+ * @param gCurrentTest.offset
+ * A function to compute the cursor position of firing wheel event.
+ * The values are offset from |gRootView|.
+ * @param gCurrentTest.isVertical
+ * Whether the wheel event is for virtical scrolling or horizontal.
+ * @param gCurrentTest.isForward
+ * Whether the wheel event is to forward or to backward.
+ * @param gCurrentTest.expectedView
+ * The expected view which will be scrolled by wheel event. This
+ * value must not be null.
+ */
+
+ _clearTimer();
+ _saveScrollPositions();
+
+ if (!gCurrentTest.wasTransactionTimeout) {
+ gCurrentTest.repeatTest = true;
+ gCurrentTest.autoRepeatDelay = gTimeout / 3;
+ gLitesnEvents = kListenEvent_All;
+ gCurrentTest.isTimeoutTesting = true;
+ if (gCurrentTest.expectedView) {
+ gCurrentTest.expectedViewAfterTimeout = gCurrentTest.expectedView;
+ gCurrentTest.expectedView = null;
+ }
+ } else {
+ gCurrentTest.repeatTest = false;
+ gCurrentTest.autoRepeatDelay = 0;
+ gLitesnEvents = kListenEvent_All;
+ gCurrentTest.isTimeoutTesting = false;
+ gCurrentTest.expectedView = gCurrentTest.expectedViewAfterTimeout;
+ }
+
+ var delta = gCurrentTest.delta ? gCurrentTest.delta : 4;
+ _fireWheelScrollEvent(gCurrentTest.offset(),
+ gCurrentTest.isVertical, gCurrentTest.isForward, delta);
+}
+
+/******************************************************************************
+ * Event handlers
+ ******************************************************************************/
+
+function onScrollView(aEvent)
+{
+ /**
+ * Scroll event handler of |gRootView|, |gSubView1|, |gSubView2| and
+ * |gSubView3|. If testing is failed, this function cancels all left tests.
+ * For checking the event is expected, the event firer must call
+ * |_saveScrollPositions|.
+ *
+ * @param gCurrentTest.expectedView
+ * The expected view which should be scrolled by the wheel event.
+ * This value can be null. It means any views should not be
+ * scrolled.
+ * @param gCurrentTest.isVertical
+ * The expected view should be scrolled vertical or horizontal.
+ * @param gCurrentTest.isForward
+ * The expected view should be scrolled to forward or backward.
+ * @param gCurrentTest.canFailRandomly
+ * If this is not undefined, this test can fail by unexpected view
+ * scrolling which is caused by unexpected timeout. If this is
+ * defined, |gCurrentTest.possibleView| must be set. If the view is
+ * same as the event target, the failure can be random. At this
+ * time, we should retry the current test list.
+ */
+
+ if (!(gLitesnEvents & kListenEvent_OnScroll))
+ return;
+
+ // Now testing a timeout, but a view is scrolled before timeout.
+ if (gCurrentTest.isTimeoutTesting && !gCurrentTest.wasTransactionTimeout) {
+ is(aEvent.target.id, "",
+ "The view scrolled before timeout (the expected view after timeout is " +
+ gCurrentTest.expectedView ? gCurrentTest.expectedView.id : "null" +
+ "): " + gCurrentTest.description);
+ runNextTestList();
+ return;
+ }
+
+ // Check whether the scrolled event should be fired or not.
+ if (!gCurrentTest.expectedView) {
+ is(aEvent.target.id, "",
+ "no views should be scrolled (" +
+ _getScrolledStateText(_getScrolledState(aEvent.target)) + "): " +
+ gCurrentTest.description);
+ runNextTestList();
+ return;
+ }
+
+ // Check whether the scrolled view is expected or not.
+ if (aEvent.target != gCurrentTest.expectedView) {
+ // If current test can fail randomly and the possible view is same as the
+ // event target, this failure may be caused by unexpected timeout.
+ // At this time, we should retry the current tests with slower settings.
+ if (gCurrentTest.canFailRandomly &&
+ gCurrentTest.canFailRandomly.possibleView == aEvent.target &&
+ gCurrentTestListStatus.retryWhenTransactionTimeout > 0) {
+ gCurrentTestListStatus.retryWhenTransactionTimeout--;
+ retryCurrentTestList();
+ return;
+ }
+ is(aEvent.target.id, gCurrentTest.expectedView.id,
+ "wrong view was scrolled: " + gCurrentTest.description);
+ runNextTestList();
+ return;
+ }
+
+ // Check whether the scrolling direction is expected or not.
+ var expectedState = _getExpectedScrolledState();
+ var currentState = _getScrolledState(aEvent.target);
+ if (expectedState != currentState) {
+ is(_getScrolledStateText(currentState),
+ _getScrolledStateText(expectedState),
+ "scrolled to wrong direction: " + gCurrentTest.description);
+ runNextTestList();
+ return;
+ }
+
+ ok(true, "passed: " + gCurrentTest.description);
+ continueTest();
+}
+
+function onMouseScrollFailed()
+{
+ /**
+ * Scroll failed event handler. If testing is failed, this function cancels
+ * all remains of current test-list, and go to next test-list.
+ *
+ * NOTE: This event is fired immediately after |_fireWheelScrollEvent|.
+ *
+ * @param gCurrentTest.expectedView
+ * The expected view which should be scrolled by the wheel event.
+ * This value can be null. It means any views should not be
+ * scrolled. When this is not null, this event means the test may
+ * be failed.
+ */
+
+ if (!(gLitesnEvents & kListenEvent_OnScrollFailed))
+ return;
+
+ ok(!gCurrentTest.expectedView,
+ "failed to scroll on current target: " + gCurrentTest.description);
+ if (gCurrentTest.expectedView) {
+ runNextTestList();
+ return;
+ }
+
+ continueTest();
+}
+
+function onTransactionTimeout()
+{
+ /**
+ * Scroll transaction timeout event handler. If the timeout is unexpected,
+ * i.e., |gCurrentTest.isTimeoutTesting| is not true, this function retry
+ * the current test-list. However, if the current test-list failed by timeout
+ * |gCurrentTestListStatus.retryWhenTransactionTimeout| times already, marking
+ * to failed the current test-list, and go to next test-list.
+ *
+ * @param gCurrentTest.expectedView
+ * The expected view which should be scrolled by the wheel event.
+ * This value can be null. It means any views should not be
+ * scrolled. When this is not null, this event means the testing may
+ * be failed.
+ * @param gCurrentTest.isTimeoutTesting
+ * If this value is true, the current testing have waited this
+ * event. Otherwise, the testing may be failed.
+ * @param gCurrentTestListStatus.retryWhenTransactionTimeout
+ * If |gCurrentTest.isTimeoutTesting| is not true but this event is
+ * fired, the failure may be randomly. Then, this event handler
+ * retry to test the current test-list until this cound will be zero.
+ */
+
+ if (!gCurrentTest.isTimeoutTesting &&
+ gCurrentTestListStatus.retryWhenTransactionTimeout > 0) {
+ gCurrentTestListStatus.retryWhenTransactionTimeout--;
+ // retry current test list
+ retryCurrentTestList();
+ return;
+ }
+
+ gCurrentTest.wasTransactionTimeout = true;
+
+ if (!(gLitesnEvents & kListenEvent_OnTransactionTimeout))
+ return;
+
+ ok(gCurrentTest.isTimeoutTesting,
+ "transaction timeout: " + gCurrentTest.description);
+ if (!gCurrentTest.isTimeoutTesting) {
+ runNextTestList();
+ return;
+ }
+
+ continueTest();
+}
+
+/******************************************************************************
+ * Main function for this tests
+ ******************************************************************************/
+
+function runNextTestStep()
+{
+ // When this is first time or the current test list is finised, load next
+ // test-list.
+ _clearTimer();
+ if (!gCurrentTest)
+ runNextTestList();
+ else
+ runTestStepAt(gCurrentTestListStatus.nextStepIndex);
+}
+
+function runNextTestList()
+{
+ _clearTimer();
+
+ gLitesnEvents = kListenEvent_None;
+ _clearTransaction();
+ resetTimeoutPrefs();
+ if (gCurrentTestListStatus.nextListIndex >= gTestLists.length) {
+ finish();
+ return;
+ }
+
+ gCurrentTestListStatus.nextListIndex++;
+ gCurrentTestListStatus.retryWhenTransactionTimeout =
+ _getCurrentTestList().retryWhenTransactionTimeout;
+ runTestStepAt(0);
+}
+
+function runTestStepAt(aStepIndex)
+{
+ _clearTimer();
+
+ disableNonTestMouseEvents(true);
+
+ // load a step of testing.
+ gCurrentTestListStatus.nextStepIndex = aStepIndex;
+ gCurrentTest =
+ _getCurrentTestList().steps[gCurrentTestListStatus.nextStepIndex++];
+ if (gCurrentTest) {
+ gCurrentTest.wasTransactionTimeout = false;
+ gTimer = setTimeout(gCurrentTest.func, gCurrentTest.delay);
+ } else {
+ // If current test-list doesn't have more testing, go to next test-list
+ // after cleaning up the current transaction.
+ _clearTransaction();
+ runNextTestList();
+ }
+}
+
+function retryCurrentTestList()
+{
+ _clearTimer();
+
+ gLitesnEvents = kListenEvent_None;
+ _clearTransaction();
+ ok(true, "WARNING: retry current test-list...");
+ growUpTimeoutPrefs(); // retry the test with longer timeout settings.
+ runTestStepAt(0);
+}
+
+function continueTest()
+{
+ /**
+ * This function is called from an event handler when a test succeeded.
+ *
+ * @param gCurrentTest.repeatTest
+ * When this is true, onScrollView calls |gCurrentTest.func|. So,
+ * same test can repeat. Otherwise, this calls |runNextTestStep|.
+ * @param gCurrentTest.autoRepeatDelay
+ * The delay value in milliseconds, this is used to call
+ * |gCurrentTest.func| via |setTimeout|.
+ */
+
+ _clearTimer();
+ gLitesnEvents = kListenEvent_OnTransactionTimeout;
+
+ // We should call each functions via setTimeout. Because sometimes this test
+ // is broken by stack overflow.
+ if (gCurrentTest.repeatTest) {
+ gTimer = setTimeout(gCurrentTest.func, gCurrentTest.autoRepeatDelay);
+ } else {
+ gTimer = setTimeout(runNextTestStep, 0);
+ }
+}
+
+]]>
+</script>
+
+</window>