author | Mark Capella <markcapella@twcny.rr.com> |
Thu, 14 May 2015 22:06:13 -0400 (2015-05-15) | |
changeset 243982 | 101feffdaed800475440429baace98a1be17a27a |
parent 243981 | 1be077af56e34552a462e30ca7ce1b56ab21f786 |
child 243983 | b0ae961951f59c840a57561894ff77d35a94d0ee |
push id | 28761 |
push user | cbook@mozilla.com |
push date | Fri, 15 May 2015 14:50:10 +0000 (2015-05-15) |
treeherder | mozilla-central@c0e709a5baca [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | margaret |
bugs | 1157637 |
milestone | 41.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/mobile/android/base/Tab.java +++ b/mobile/android/base/Tab.java @@ -18,16 +18,17 @@ import org.json.JSONObject; import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.db.URLMetadata; import org.mozilla.gecko.favicons.Favicons; import org.mozilla.gecko.favicons.LoadFaviconTask; import org.mozilla.gecko.favicons.OnFaviconLoadedListener; import org.mozilla.gecko.favicons.RemoteFavicon; import org.mozilla.gecko.gfx.BitmapUtils; import org.mozilla.gecko.gfx.Layer; +import org.mozilla.gecko.mozglue.RobocopTarget; import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState; import org.mozilla.gecko.util.ThreadUtils; import android.content.ContentResolver; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; @@ -141,16 +142,17 @@ public class Tab { private ContentResolver getContentResolver() { return mAppContext.getContentResolver(); } public void onDestroy() { Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.CLOSED); } + @RobocopTarget public int getId() { return mId; } public synchronized void onChange() { mLastUsed = System.currentTimeMillis(); }
--- a/mobile/android/tests/browser/robocop/robocop.ini +++ b/mobile/android/tests/browser/robocop/robocop.ini @@ -163,16 +163,17 @@ skip-if = android_version == "10" || and [testJavascriptBridge] [testNativeCrypto] [testReaderModeTitle] [testSessionHistory] # disabled on Android 4.3, bug 1144879 skip-if = android_version == "18" [testStateWhileLoading] +[testSelectionCarets] # testSelectionHandler disabled on Android 2.3 by trailing skip-if, due to bug 980074 # also disabled on Android 4.3, bug 1144882 [testSelectionHandler] skip-if = android_version == "10" || android_version == "18" # testInputSelections disabled on Android 2.3 by trailing skip-if, due to bug 980074 [testInputSelections] skip-if = android_version == "10"
new file mode 100644 --- /dev/null +++ b/mobile/android/tests/browser/robocop/testSelectionCarets.html @@ -0,0 +1,35 @@ +<html> + <head> + <title>ActionBar Handler and SelectionCarets tests</title> + <meta name="viewport" + content="initial-scale=1, allowZoom=no, maximum-scale=1, + user-scalable=no, width=device-width"> + </head> + + <body> + <div id="LTRcontenteditable" + style="direction: ltr; width: 10em; height: 2em; word-wrap: break-word; + overflow: auto; -moz-user-select:text" + contenteditable="true">Find my book</div> + <div id="RTLcontenteditable" + style="direction: rtl; width: 10em; height: 2em; word-wrap: break-word; + overflow: auto; -moz-user-select:text" + contenteditable="true">איפה האוטו שלי</div> + + <div id="LTRtextContent" + style="direction: ltr; width: 10em; height: 2em; word-wrap: break-word; + overflow: auto; -moz-user-select:text">Open the door</div> + <div id="RTLtextContent" + style="direction: rtl; width: 10em; height: 2em; word-wrap: break-word; + overflow: auto; -moz-user-select:text">תן לי מים</div> + + <input id="LTRinput" style="direction: ltr;" value="Type something"> + <input id="RTLinput" style="direction: rtl;" value="לרוץ במעלה הגבעה"> + <br> + + <textarea id="LTRtextarea" style="direction: ltr;" + rows="3" cols="8">Words in a box</textarea> + <textarea id="RTLtextarea" style="direction: rtl;" + rows="3" cols="8">הספר הוא טוב</textarea> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/mobile/android/tests/browser/robocop/testSelectionCarets.java @@ -0,0 +1,106 @@ +/** + * 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/. + */ +package org.mozilla.gecko.tests; + +import org.mozilla.gecko.AppConstants; +import org.mozilla.gecko.EventDispatcher; +import org.mozilla.gecko.GeckoAppShell; +import org.mozilla.gecko.GeckoEvent; +import org.mozilla.gecko.Tab; +import org.mozilla.gecko.Tabs; +import org.mozilla.gecko.util.GeckoEventListener; + +import android.os.SystemClock; +import android.util.Log; +import android.view.MotionEvent; + +import org.json.JSONException; +import org.json.JSONObject; + +public class testSelectionCarets extends JavascriptTest implements GeckoEventListener { + private static final String LOGTAG = "testSelectionCarets"; + + private static final String LONGPRESS_EVENT = "testSelectionCarets:Longpress"; + private static final String TAB_CHANGE_EVENT = "testSelectionCarets:TabChange"; + + private final TabsListener tabsListener; + + public testSelectionCarets() { + super("testSelectionCarets.js"); + + tabsListener = new TabsListener(); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + + Tabs.registerOnTabsChangedListener(tabsListener); + EventDispatcher.getInstance().registerGeckoThreadListener(this, LONGPRESS_EVENT); + } + + @Override + public void testJavascript() throws Exception { + // This feature is currently only available in Nightly. + if (!AppConstants.NIGHTLY_BUILD) { + mAsserter.dumpLog(LOGTAG + " is disabled on non-Nightly builds: returning"); + return; + } + super.testJavascript(); + } + + @Override + public void tearDown() throws Exception { + Tabs.unregisterOnTabsChangedListener(tabsListener); + EventDispatcher.getInstance().unregisterGeckoThreadListener(this, LONGPRESS_EVENT); + + super.tearDown(); + } + + /** + * The test script will request us to trigger Longpress AndroidGeckoEvents. + */ + @Override + public void handleMessage(String event, final JSONObject message) { + switch(event) { + case LONGPRESS_EVENT: { + final long meTime = SystemClock.uptimeMillis(); + final int meX = Math.round(message.optInt("x", 0)); + final int meY = Math.round(message.optInt("y", 0)); + final MotionEvent motionEvent = + MotionEvent.obtain(meTime, meTime, MotionEvent.ACTION_DOWN, meX, meY, 0); + + final GeckoEvent geckoEvent = GeckoEvent.createLongPressEvent(motionEvent); + GeckoAppShell.sendEventToGecko(geckoEvent); + break; + } + } + } + + /** + * Observes tab change events to broadcast to the test script. + */ + private class TabsListener implements Tabs.OnTabsChangedListener { + @Override + public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) { + switch (msg) { + case STOP: + final JSONObject args = new JSONObject(); + try { + args.put("tabId", tab.getId()); + args.put("event", msg.toString()); + } catch (JSONException e) { + Log.e(LOGTAG, "Error building JSON arguments for " + TAB_CHANGE_EVENT, e); + return; + } + final GeckoEvent event = + GeckoEvent.createBroadcastEvent(TAB_CHANGE_EVENT, args.toString()); + GeckoAppShell.sendEventToGecko(event); + break; + } + } + } +}
new file mode 100644 --- /dev/null +++ b/mobile/android/tests/browser/robocop/testSelectionCarets.js @@ -0,0 +1,244 @@ +// -*- 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"); + +/** + * Robocop test helpers. + */ +function ok(passed, text) { + do_report_result(passed, text, Components.stack.caller, false); +} + +function is(lhs, rhs, text) { + do_report_result(lhs === rhs, "[ " + lhs + " === " + rhs + " ] " + text, + Components.stack.caller, false); +} + +/** + * 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();