author | Jim Chen <nchen@mozilla.com> |
Wed, 28 Dec 2016 17:49:29 -0500 | |
changeset 327510 | 2a43ca16d1c16f6b050fa40ebbdb6abc7a190212 |
parent 327509 | 58e189739172cf567fa33a7470ed5768f0448c38 |
child 327511 | a9d1dbc6426dd1c8b89488f62ae859a3c8a7aa0c |
push id | 35517 |
push user | kwierso@gmail.com |
push date | Thu, 29 Dec 2016 20:22:54 +0000 |
treeherder | autoland@3f2f8d77ad27 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | sebastian |
bugs | 1325155 |
milestone | 53.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/java/org/mozilla/gecko/ActionBarTextSelection.java +++ b/mobile/android/base/java/org/mozilla/gecko/ActionBarTextSelection.java @@ -3,44 +3,46 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko; import org.mozilla.gecko.menu.GeckoMenu; import org.mozilla.gecko.menu.GeckoMenuItem; import org.mozilla.gecko.util.ResourceDrawableUtils; import org.mozilla.gecko.text.TextSelection; -import org.mozilla.gecko.util.GeckoEventListener; +import org.mozilla.gecko.util.BundleEventListener; +import org.mozilla.gecko.util.EventCallback; +import org.mozilla.gecko.util.GeckoBundle; import org.mozilla.gecko.util.ThreadUtils; import org.mozilla.gecko.ActionModeCompat.Callback; import android.content.Context; import android.graphics.drawable.Drawable; import android.view.MenuItem; -import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.util.Arrays; import java.util.Timer; import java.util.TimerTask; import android.util.Log; -class ActionBarTextSelection implements TextSelection, GeckoEventListener { +class ActionBarTextSelection implements TextSelection, BundleEventListener { private static final String LOGTAG = "GeckoTextSelection"; private static final int SHUTDOWN_DELAY_MS = 250; private final Context context; private boolean mDraggingHandles; - private String selectionID; // Unique ID provided for each selection action. + private int selectionID; // Unique ID provided for each selection action. - private String mCurrentItems; + private GeckoBundle[] mCurrentItems; private TextSelectionActionModeCallback mCallback; // These timers are used to avoid flicker caused by selection handles showing/hiding quickly. // For instance when moving between single handle caret mode and two handle selection mode. private final Timer mActionModeTimer = new Timer("actionMode"); private class ActionModeTimerTask extends TimerTask { @Override @@ -60,95 +62,79 @@ class ActionBarTextSelection implements } @Override public void create() { // Only register listeners if we have valid start/middle/end handles if (context == null) { Log.e(LOGTAG, "Failed to initialize text selection because at least one context is null"); } else { - GeckoApp.getEventDispatcher().registerGeckoThreadListener(this, - "TextSelection:ActionbarInit", - "TextSelection:ActionbarStatus", - "TextSelection:ActionbarUninit", - "TextSelection:Update"); + GeckoApp.getEventDispatcher().registerUiThreadListener(this, + "TextSelection:ActionbarInit", + "TextSelection:ActionbarStatus", + "TextSelection:ActionbarUninit"); } } @Override public boolean dismiss() { // We do not call endActionMode() here because this is already handled by the activity. return false; } @Override public void destroy() { if (context == null) { Log.e(LOGTAG, "Do not unregister TextSelection:* listeners since context is null"); } else { - GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this, + GeckoApp.getEventDispatcher().unregisterUiThreadListener(this, "TextSelection:ActionbarInit", "TextSelection:ActionbarStatus", - "TextSelection:ActionbarUninit", - "TextSelection:Update"); + "TextSelection:ActionbarUninit"); } } @Override - public void handleMessage(final String event, final JSONObject message) { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - try { - if (event.equals("TextSelection:Update")) { - if (mActionModeTimerTask != null) - mActionModeTimerTask.cancel(); - showActionMode(message.getJSONArray("actions")); - } else if (event.equals("TextSelection:ActionbarInit")) { - // Init / Open the action bar. Note the current selectionID, - // cancel any pending actionBar close. - Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, - TelemetryContract.Method.CONTENT, "text_selection"); + public void handleMessage(final String event, final GeckoBundle message, + final EventCallback callback) { + if ("TextSelection:ActionbarInit".equals(event)) { + // Init / Open the action bar. Note the current selectionID, + // cancel any pending actionBar close. + Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, + TelemetryContract.Method.CONTENT, "text_selection"); - selectionID = message.getString("selectionID"); - mCurrentItems = null; - if (mActionModeTimerTask != null) { - mActionModeTimerTask.cancel(); - } + selectionID = message.getInt("selectionID"); + mCurrentItems = null; + if (mActionModeTimerTask != null) { + mActionModeTimerTask.cancel(); + } - } else if (event.equals("TextSelection:ActionbarStatus")) { - // Ensure async updates from SearchService for example are valid. - if (selectionID != message.optString("selectionID")) { - return; - } - - // Update the actionBar actions as provided by Gecko. - showActionMode(message.getJSONArray("actions")); + } else if ("TextSelection:ActionbarStatus".equals(event)) { + // Ensure async updates from SearchService for example are valid. + if (selectionID != message.getInt("selectionID")) { + return; + } - } else if (event.equals("TextSelection:ActionbarUninit")) { - // Uninit the actionbar. Schedule a cancellable close - // action to avoid UI jank. (During SelectionAll for ex). - mCurrentItems = null; - mActionModeTimerTask = new ActionModeTimerTask(); - mActionModeTimer.schedule(mActionModeTimerTask, SHUTDOWN_DELAY_MS); - } + // Update the actionBar actions as provided by Gecko. + showActionMode(message.getBundleArray("actions")); - } catch (JSONException e) { - Log.e(LOGTAG, "JSON exception", e); - } - } - }); + } else if ("TextSelection:ActionbarUninit".equals(event)) { + // Uninit the actionbar. Schedule a cancellable close + // action to avoid UI jank. (During SelectionAll for ex). + mCurrentItems = null; + mActionModeTimerTask = new ActionModeTimerTask(); + mActionModeTimer.schedule(mActionModeTimerTask, SHUTDOWN_DELAY_MS); + } } - private void showActionMode(final JSONArray items) { - String itemsString = items.toString(); - if (itemsString.equals(mCurrentItems)) { + private void showActionMode(final GeckoBundle[] items) { + if (Arrays.equals(items, mCurrentItems)) { return; } - mCurrentItems = itemsString; + mCurrentItems = items; if (mCallback != null) { mCallback.updateItems(items); return; } if (context instanceof ActionModeCompat.Presenter) { final ActionModeCompat.Presenter presenter = (ActionModeCompat.Presenter) context; @@ -162,84 +148,78 @@ class ActionBarTextSelection implements if (context instanceof ActionModeCompat.Presenter) { final ActionModeCompat.Presenter presenter = (ActionModeCompat.Presenter) context; presenter.endActionModeCompat(); } mCurrentItems = null; } private class TextSelectionActionModeCallback implements Callback { - private JSONArray mItems; + private GeckoBundle[] mItems; private ActionModeCompat mActionMode; - public TextSelectionActionModeCallback(JSONArray items) { + public TextSelectionActionModeCallback(final GeckoBundle[] items) { mItems = items; } - public void updateItems(JSONArray items) { + public void updateItems(final GeckoBundle[] items) { mItems = items; if (mActionMode != null) { mActionMode.invalidate(); } } public void animateIn() { if (mActionMode != null) { mActionMode.animateIn(); } } @Override public boolean onPrepareActionMode(final ActionModeCompat mode, final GeckoMenu menu) { - // Android would normally expect us to only update the state of menu items here - // To make the js-java interaction a bit simpler, we just wipe out the menu here and recreate all - // the javascript menu items in onPrepare instead. This will be called any time invalidate() is called on the - // action mode. + // Android would normally expect us to only update the state of menu items + // here To make the js-java interaction a bit simpler, we just wipe out the + // menu here and recreate all the javascript menu items in onPrepare instead. + // This will be called any time invalidate() is called on the action mode. menu.clear(); - int length = mItems.length(); + final int length = mItems.length; for (int i = 0; i < length; i++) { - try { - final JSONObject obj = mItems.getJSONObject(i); - final GeckoMenuItem menuitem = (GeckoMenuItem) menu.add(0, i, 0, obj.optString("label")); - final int actionEnum = obj.optBoolean("showAsAction") ? GeckoMenuItem.SHOW_AS_ACTION_ALWAYS : GeckoMenuItem.SHOW_AS_ACTION_NEVER; - menuitem.setShowAsAction(actionEnum, R.attr.menuItemActionModeStyle); + final GeckoBundle obj = mItems[i]; + final GeckoMenuItem menuitem = (GeckoMenuItem) + menu.add(0, i, 0, obj.getString("label")); + final int actionEnum = obj.getBoolean("showAsAction") ? + GeckoMenuItem.SHOW_AS_ACTION_ALWAYS : GeckoMenuItem.SHOW_AS_ACTION_NEVER; + menuitem.setShowAsAction(actionEnum, R.attr.menuItemActionModeStyle); - final String iconString = obj.optString("icon"); - ResourceDrawableUtils.getDrawable(context, iconString, new ResourceDrawableUtils.BitmapLoader() { - @Override - public void onBitmapFound(Drawable d) { - if (d != null) { - menuitem.setIcon(d); - } + final String iconString = obj.getString("icon"); + ResourceDrawableUtils.getDrawable(context, iconString, + new ResourceDrawableUtils.BitmapLoader() { + @Override + public void onBitmapFound(Drawable d) { + if (d != null) { + menuitem.setIcon(d); } - }); - } catch (Exception ex) { - Log.i(LOGTAG, "Exception building menu", ex); - } + } + }); } return true; } @Override public boolean onCreateActionMode(ActionModeCompat mode, GeckoMenu unused) { mActionMode = mode; return true; } @Override public boolean onActionItemClicked(ActionModeCompat mode, MenuItem item) { - try { - final JSONObject obj = mItems.getJSONObject(item.getItemId()); - GeckoAppShell.notifyObservers("TextSelection:Action", obj.optString("id")); - return true; - } catch (Exception ex) { - Log.i(LOGTAG, "Exception calling action", ex); - } - return false; + final GeckoBundle obj = mItems[item.getItemId()]; + GeckoAppShell.notifyObservers("TextSelection:Action", obj.getString("id")); + return true; } // Called when the user exits the action mode @Override public void onDestroyActionMode(ActionModeCompat mode) { mActionMode = null; mCallback = null; final JSONObject args = new JSONObject();
--- a/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java +++ b/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java @@ -16,42 +16,42 @@ import org.json.JSONException; import org.json.JSONObject; import org.mozilla.gecko.EventDispatcher; import org.mozilla.gecko.GeckoApp; import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.Telemetry; import org.mozilla.gecko.TelemetryContract; import org.mozilla.gecko.gfx.LayerView; -import org.mozilla.gecko.util.GeckoEventListener; +import org.mozilla.gecko.util.BundleEventListener; +import org.mozilla.gecko.util.EventCallback; +import org.mozilla.gecko.util.GeckoBundle; import org.mozilla.gecko.util.ThreadUtils; import java.util.List; -import ch.boye.httpclientandroidlib.util.TextUtils; - /** * Floating toolbar for text selection actions. Only on Android 6+. */ @TargetApi(Build.VERSION_CODES.M) -public class FloatingToolbarTextSelection implements TextSelection, GeckoEventListener { +public class FloatingToolbarTextSelection implements TextSelection, BundleEventListener { private static final String LOGTAG = "GeckoFloatTextSelection"; // This is an additional offset we add to the height of the selection. This will avoid that the // floating toolbar overlays the bottom handle(s). private static final int HANDLES_OFFSET_DP = 20; private final Activity activity; private final LayerView layerView; private final int[] locationInWindow; private final float handlesOffset; private ActionMode actionMode; private FloatingActionModeCallback actionModeCallback; - private String selectionID; + private int selectionID; /* package-private */ Rect contentRect; public FloatingToolbarTextSelection(Activity activity, LayerView layerView) { this.activity = activity; this.layerView = layerView; this.locationInWindow = new int[2]; this.handlesOffset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, @@ -64,17 +64,17 @@ public class FloatingToolbarTextSelectio endTextSelection(); return true; } return false; } private void endTextSelection() { - if (TextUtils.isEmpty(selectionID)) { + if (selectionID == 0) { return; } final JSONObject args = new JSONObject(); try { args.put("selectionID", selectionID); } catch (JSONException e) { Log.e(LOGTAG, "Error building JSON arguments for TextSelection:End", e); @@ -90,66 +90,57 @@ public class FloatingToolbarTextSelectio } @Override public void destroy() { unregisterFromEvents(); } private void registerForEvents() { - GeckoApp.getEventDispatcher().registerGeckoThreadListener(this, + GeckoApp.getEventDispatcher().registerUiThreadListener(this, "TextSelection:ActionbarInit", "TextSelection:ActionbarStatus", "TextSelection:ActionbarUninit", - "TextSelection:Update", "TextSelection:Visibility"); } private void unregisterFromEvents() { - GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this, + GeckoApp.getEventDispatcher().unregisterUiThreadListener(this, "TextSelection:ActionbarInit", "TextSelection:ActionbarStatus", "TextSelection:ActionbarUninit", - "TextSelection:Update", "TextSelection:Visibility"); } - @Override - public void handleMessage(final String event, final JSONObject message) { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - handleOnMainThread(event, message); - } - }); - } - - private void handleOnMainThread(final String event, final JSONObject message) { + @Override // BundleEventListener + public void handleMessage(final String event, final GeckoBundle message, + final EventCallback callback) { if ("TextSelection:ActionbarInit".equals(event)) { Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.CONTENT, "text_selection"); - selectionID = message.optString("selectionID"); + selectionID = message.getInt("selectionID"); + } else if ("TextSelection:ActionbarStatus".equals(event)) { // Ensure async updates from SearchService for example are valid. - if (selectionID != message.optString("selectionID")) { + if (selectionID != message.getInt("selectionID")) { return; } updateRect(message); if (!isRectVisible()) { finishActionMode(); } else { startActionMode(TextAction.fromEventMessage(message)); } + } else if ("TextSelection:ActionbarUninit".equals(event)) { finishActionMode(); - } else if ("TextSelection:Update".equals(event)) { - startActionMode(TextAction.fromEventMessage(message)); + } else if ("TextSelection:Visibility".equals(event)) { finishActionMode(); } } private void startActionMode(List<TextAction> actions) { if (actionMode != null) { actionModeCallback.updateActions(actions); @@ -178,29 +169,25 @@ public class FloatingToolbarTextSelectio */ private boolean isRectVisible() { // There's another case of an empty rect where just left == right but not top == bottom. // That's the rect for a collapsed selection. While technically this rect isn't visible too // we are not interested in this case because we do not want to hide the toolbar. return contentRect.left != contentRect.right || contentRect.top != contentRect.bottom; } - private void updateRect(JSONObject message) { - try { - final double x = message.getDouble("x"); - final double y = (int) message.getDouble("y"); - final double width = (int) message.getDouble("width"); - final double height = (int) message.getDouble("height"); - - final float zoomFactor = layerView.getZoomFactor(); - layerView.getLocationInWindow(locationInWindow); + private void updateRect(final GeckoBundle message) { + final double x = message.getDouble("x"); + final double y = (int) message.getDouble("y"); + final double width = (int) message.getDouble("width"); + final double height = (int) message.getDouble("height"); - contentRect = new Rect( - (int) (x * zoomFactor + locationInWindow[0]), - (int) (y * zoomFactor + locationInWindow[1]), - (int) ((x + width) * zoomFactor + locationInWindow[0]), - (int) ((y + height) * zoomFactor + locationInWindow[1] + - (height > 0 ? handlesOffset : 0))); - } catch (JSONException e) { - Log.w(LOGTAG, "Could not calculate content rect", e); - } + final float zoomFactor = layerView.getZoomFactor(); + layerView.getLocationInWindow(locationInWindow); + + contentRect = new Rect( + (int) (x * zoomFactor + locationInWindow[0]), + (int) (y * zoomFactor + locationInWindow[1]), + (int) ((x + width) * zoomFactor + locationInWindow[0]), + (int) ((y + height) * zoomFactor + locationInWindow[1] + + (height > 0 ? handlesOffset : 0))); } }
--- a/mobile/android/base/java/org/mozilla/gecko/text/TextAction.java +++ b/mobile/android/base/java/org/mozilla/gecko/text/TextAction.java @@ -5,51 +5,48 @@ package org.mozilla.gecko.text; import android.util.Log; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.mozilla.gecko.util.GeckoBundle; + import java.util.ArrayList; import java.util.List; /** * Text selection action like "copy", "paste", .. */ public class TextAction { private static final String LOGTAG = "GeckoTextAction"; private String id; private String label; private int order; private int floatingOrder; private TextAction() {} - public static List<TextAction> fromEventMessage(JSONObject message) { + public static List<TextAction> fromEventMessage(final GeckoBundle message) { final List<TextAction> actions = new ArrayList<>(); - - try { - final JSONArray array = message.getJSONArray("actions"); - - for (int i = 0; i < array.length(); i++) { - final JSONObject object = array.getJSONObject(i); + final GeckoBundle[] array = message.getBundleArray("actions"); - final TextAction action = new TextAction(); - action.id = object.getString("id"); - action.label = object.getString("label"); - action.order = object.getInt("order"); - action.floatingOrder = object.optInt("floatingOrder", i); + for (int i = 0; i < array.length; i++) { + final GeckoBundle object = array[i]; + final TextAction action = new TextAction(); - actions.add(action); - } - } catch (JSONException e) { - Log.w(LOGTAG, "Could not parse text actions", e); + action.id = object.getString("id"); + action.label = object.getString("label"); + action.order = object.getInt("order"); + action.floatingOrder = object.getInt("floatingOrder", i); + + actions.add(action); } return actions; } public String getId() { return id; }
--- a/mobile/android/chrome/content/ActionBarHandler.js +++ b/mobile/android/chrome/content/ActionBarHandler.js @@ -138,30 +138,30 @@ var ActionBarHandler = { } // Hold the ActionBar ID provided by Gecko. this._selectionID = this._nextSelectionID++; [this._targetElement, this._contentWindow] = [element, win]; this._boundingClientRect = boundingClientRect; // Open the ActionBar, send it's actions list. - Messaging.sendRequest({ + WindowEventDispatcher.sendRequest({ type: "TextSelection:ActionbarInit", selectionID: this._selectionID, }); this._sendActionBarActions(true); return this.START_TOUCH_ERROR.NONE; }, /** * Called when content is scrolled and handles are hidden. */ _updateVisibility: function() { - Messaging.sendRequest({ + WindowEventDispatcher.sendRequest({ type: "TextSelection:Visibility", selectionID: this._selectionID, }); }, /** * Determines the window containing the selection, and its * editable element if present. @@ -202,17 +202,17 @@ var ActionBarHandler = { */ _uninit: function(clearSelection = true) { // Bail if there's no active selection. if (!this._selectionID) { return; } // Close the ActionBar. - Messaging.sendRequest({ + WindowEventDispatcher.sendRequest({ type: "TextSelection:ActionbarUninit", }); // Clear the selection ID to complete the uninit(), but leave our reference // to selectionTargets (_targetElement, _contentWindow) in case we need // a final clearSelection(). this._selectionID = null; this._boundingClientRect = null; @@ -261,17 +261,17 @@ var ActionBarHandler = { let actionCountUnchanged = this._actionBarActions && actions.length === this._actionBarActions.length; let actionsMatch = actionCountUnchanged && this._actionBarActions.every((e,i) => { return e.id === actions[i].id; }); if (sendAlways || !actionsMatch) { - Messaging.sendRequest({ + WindowEventDispatcher.sendRequest({ type: "TextSelection:ActionbarStatus", selectionID: this._selectionID, actions: actions, x: this._boundingClientRect.x, y: this._boundingClientRect.y, width: this._boundingClientRect.width, height: this._boundingClientRect.height });