// -*- 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/. */ "use strict"; const { classes: Cc, interfaces: Ci, utils: Cu } = Components; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Messaging.jsm"); Cu.import('resource://gre/modules/Geometry.jsm'); const SELECTION_CARETS_PREF = "selectioncaret.enabled"; const TOUCH_CARET_PREF = "touchcaret.enabled"; const TEST_URL = "http://mochi.test:8888/tests/robocop/testSelectionCarets.html"; // After longpress, Gecko notifys ActionBarHandler to init then update state. // When it does, we'll peek over its shoulder and test status. const LONGPRESS_EVENT = "testSelectionCarets:Longpress"; const STATUS_UPDATE_EVENT = "ActionBar:UpdateState"; // Ensures Tabs are completely loaded, viewport and zoom constraints updated, etc. const TAB_CHANGE_EVENT = "testSelectionCarets:TabChange"; const TAB_STOP_EVENT = "STOP"; const gChromeWin = Services.wm.getMostRecentWindow("navigator:browser"); /** * Wait for and return, when an expected tab change event occurs. * * @param tabId, The id of the target tab we're observing. * @param eventType, The event type we expect. * @return {Promise} * @resolves The tab change object, including the matched tab id and event. */ function do_promiseTabChangeEvent(tabId, eventType) { return new Promise(resolve => { let observer = (subject, topic, data) => { let message = JSON.parse(data); if (message.event === eventType && message.tabId === tabId) { Services.obs.removeObserver(observer, TAB_CHANGE_EVENT); resolve(data); } } Services.obs.addObserver(observer, TAB_CHANGE_EVENT, false); }); } /** * Selection methods vary if we have an input / textarea element, * or if we have basic content. */ function isInputOrTextarea(element) { return ((element instanceof Ci.nsIDOMHTMLInputElement) || (element instanceof Ci.nsIDOMHTMLTextAreaElement)); } /** * Return the selection controller based on element. */ function elementSelection(element) { return (isInputOrTextarea(element)) ? element.editor.selection : element.ownerDocument.defaultView.getSelection(); } /** * Select the first character of a target element, w/o affecting focus. */ function selectElementFirstChar(doc, element) { if (isInputOrTextarea(element)) { element.setSelectionRange(0, 1); return; } // Simple test cases designed firstChild == #text node. let range = doc.createRange(); range.setStart(element.firstChild, 0); range.setEnd(element.firstChild, 1); let selection = elementSelection(element); selection.removeAllRanges(); selection.addRange(range); } /** * Get longpress point. Determine the midpoint in the first character of * the content in the element. X will be midpoint from left to right. * Y will be 1/3 of the height up from the bottom to account for both * LTR and smaller RTL characters. ie: |X| vs. |א| */ function getFirstCharPressPoint(doc, element, expected) { // Select the first char in the element. selectElementFirstChar(doc, element); // Reality check selected char to expected. let selection = elementSelection(element); is(selection.toString(), expected, "Selected char should match expected char."); // Return a point where long press should select entire word. let rect = selection.getRangeAt(0).getBoundingClientRect(); let r = new Point(rect.left + (rect.width / 2), rect.bottom - (rect.height / 3)); return r; } /** * Long press an element (RTL/LTR) at its calculated first character * position, and return the result. * * @param midPoint, The screen coord for the longpress. * @return {Promise} * @resolves The ActionBar status, including its target focused element, and * the selected text that it sees. */ function do_promiseLongPressResult(midPoint) { return new Promise(resolve => { let observer = (subject, topic, data) => { let ActionBarHandler = gChromeWin.ActionBarHandler; if (topic === STATUS_UPDATE_EVENT) { let text = ActionBarHandler._getSelectedText(); if (text !== "") { // Remove notification observer, and resolve. Services.obs.removeObserver(observer, STATUS_UPDATE_EVENT); resolve({ focusedElement: ActionBarHandler._targetElement, text: text, }); } } }; // Add notification observer, trigger the longpress and wait. Services.obs.addObserver(observer, STATUS_UPDATE_EVENT, false); Messaging.sendRequestForResult({ type: LONGPRESS_EVENT, x: midPoint.x, y: midPoint.y, }); }); } /** * Main test method. */ add_task(function* testSelectionCarets() { // Wait to start loading our test page until after the initial browser tab is // completely loaded. This allows each tab to complete its layer initialization, // importantly, its viewport and zoomContraints info. let BrowserApp = gChromeWin.BrowserApp; yield do_promiseTabChangeEvent(BrowserApp.selectedTab.id, TAB_STOP_EVENT); // Ensure Gecko Selection and Touch carets are enabled. Services.prefs.setBoolPref(SELECTION_CARETS_PREF, true); Services.prefs.setBoolPref(TOUCH_CARET_PREF, true); // Load test page, wait for load completion, register cleanup. let browser = BrowserApp.addTab(TEST_URL).browser; let tab = BrowserApp.getTabForBrowser(browser); yield do_promiseTabChangeEvent(tab.id, TAB_STOP_EVENT); do_register_cleanup(function cleanup() { Services.prefs.clearUserPref(SELECTION_CARETS_PREF); Services.prefs.clearUserPref(TOUCH_CARET_PREF); BrowserApp.closeTab(tab); }); // References to test document elements. let doc = browser.contentDocument; let ce_LTR_elem = doc.getElementById("LTRcontenteditable"); let tc_LTR_elem = doc.getElementById("LTRtextContent"); let i_LTR_elem = doc.getElementById("LTRinput"); let ta_LTR_elem = doc.getElementById("LTRtextarea"); let ce_RTL_elem = doc.getElementById("RTLcontenteditable"); let tc_RTL_elem = doc.getElementById("RTLtextContent"); let i_RTL_elem = doc.getElementById("RTLinput"); let ta_RTL_elem = doc.getElementById("RTLtextarea"); // Locate longpress midpoints for test elements, ensure expactations. let ce_LTR_midPoint = getFirstCharPressPoint(doc, ce_LTR_elem, "F"); let tc_LTR_midPoint = getFirstCharPressPoint(doc, tc_LTR_elem, "O"); let i_LTR_midPoint = getFirstCharPressPoint(doc, i_LTR_elem, "T"); let ta_LTR_midPoint = getFirstCharPressPoint(doc, ta_LTR_elem, "W"); let ce_RTL_midPoint = getFirstCharPressPoint(doc, ce_RTL_elem, "א"); let tc_RTL_midPoint = getFirstCharPressPoint(doc, tc_RTL_elem, "ת"); let i_RTL_midPoint = getFirstCharPressPoint(doc, i_RTL_elem, "ל"); let ta_RTL_midPoint = getFirstCharPressPoint(doc, ta_RTL_elem, "ה"); // Longpress various LTR content elements. Test focused element against // expected, and selected text against expected. let result = yield do_promiseLongPressResult(ce_LTR_midPoint); is(result.focusedElement, ce_LTR_elem, "Focused element should match expected."); is(result.text, "Find", "Selected text should match expected text."); result = yield do_promiseLongPressResult(tc_LTR_midPoint); is(result.focusedElement, null, "No focused element is expected."); is(result.text, "Open", "Selected text should match expected text."); result = yield do_promiseLongPressResult(i_LTR_midPoint); is(result.focusedElement, i_LTR_elem, "Focused element should match expected."); is(result.text, "Type", "Selected text should match expected text."); result = yield do_promiseLongPressResult(ta_LTR_midPoint); is(result.focusedElement, ta_LTR_elem, "Focused element should match expected."); is(result.text, "Words", "Selected text should match expected text."); // Longpress various RTL content elements. Test focused element against // expected, and selected text against expected. result = yield do_promiseLongPressResult(ce_RTL_midPoint); is(result.focusedElement, ce_RTL_elem, "Focused element should match expected."); is(result.text, "איפה", "Selected text should match expected text."); result = yield do_promiseLongPressResult(tc_RTL_midPoint); is(result.focusedElement, null, "No focused element is expected."); is(result.text, "תן", "Selected text should match expected text."); result = yield do_promiseLongPressResult(i_RTL_midPoint); is(result.focusedElement, i_RTL_elem, "Focused element should match expected."); is(result.text, "לרוץ", "Selected text should match expected text."); result = yield do_promiseLongPressResult(ta_RTL_midPoint); is(result.focusedElement, ta_RTL_elem, "Focused element should match expected."); is(result.text, "הספר", "Selected text should match expected text."); ok(true, "Finished all tests."); }); run_next_test();