summaryrefslogtreecommitdiffstats
path: root/mobile/android/chrome/content/ActionBarHandler.js
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/chrome/content/ActionBarHandler.js')
-rw-r--r--mobile/android/chrome/content/ActionBarHandler.js731
1 files changed, 0 insertions, 731 deletions
diff --git a/mobile/android/chrome/content/ActionBarHandler.js b/mobile/android/chrome/content/ActionBarHandler.js
deleted file mode 100644
index 190021043..000000000
--- a/mobile/android/chrome/content/ActionBarHandler.js
+++ /dev/null
@@ -1,731 +0,0 @@
-// -*- 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";
-
-XPCOMUtils.defineLazyModuleGetter(this, "Snackbars", "resource://gre/modules/Snackbars.jsm");
-
-const PHONE_REGEX = /^\+?[0-9\s,-.\(\)*#pw]{1,30}$/; // Are we a phone #?
-
-
-/**
- * ActionBarHandler Object and methods. Interface between Gecko Text Selection code
- * (AccessibleCaret, etc) and the Mobile ActionBar UI.
- */
-var ActionBarHandler = {
- // Error codes returned from _init().
- START_TOUCH_ERROR: {
- NO_CONTENT_WINDOW: "No valid content Window found.",
- NONE: "",
- },
-
- _nextSelectionID: 1, // Next available.
- _selectionID: null, // Unique Selection ID, assigned each time we _init().
-
- _boundingClientRect: null, // Current selections boundingClientRect.
- _actionBarActions: null, // Most-recent set of actions sent to ActionBar.
-
- /**
- * Receive and act on AccessibleCarets caret state-change
- * (mozcaretstatechanged) events.
- */
- caretStateChangedHandler: function(e) {
- // Close an open ActionBar, if carets no longer logically visible.
- if (this._selectionID && !e.caretVisible) {
- this._uninit(false);
- return;
- }
-
- if (!this._selectionID && e.collapsed) {
- switch (e.reason) {
- case 'longpressonemptycontent':
- case 'taponcaret':
- // Show ActionBar when long pressing on an empty input or single
- // tapping on the caret.
- this._init(e.boundingClientRect);
- break;
-
- case 'updateposition':
- // Do not show ActionBar when single tapping on an non-empty editable
- // input.
- break;
-
- default:
- break;
- }
- return;
- }
-
- // Open a closed ActionBar if carets actually visible.
- if (!this._selectionID && e.caretVisuallyVisible) {
- this._init(e.boundingClientRect);
- return;
- }
-
- // Else, update an open ActionBar.
- if (this._selectionID) {
- if (!this._selectionHasChanged()) {
- // Still the same active selection.
- if (e.reason == 'presscaret' || e.reason == 'scroll') {
- // boundingClientRect doesn't matter since we are hiding the floating
- // toolbar.
- this._updateVisibility();
- } else {
- // Selection changes update boundingClientRect.
- this._boundingClientRect = e.boundingClientRect;
- let forceUpdate = e.reason == 'updateposition' || e.reason == 'releasecaret';
- this._sendActionBarActions(forceUpdate);
- }
- } else {
- // We've started a new selection entirely.
- this._uninit(false);
- this._init(e.boundingClientRect);
- }
- }
- },
-
- /**
- * ActionBarHandler notification observers.
- */
- observe: function(subject, topic, data) {
- switch (topic) {
- // User click an ActionBar button.
- case "TextSelection:Action": {
- if (!this._selectionID) {
- break;
- }
- for (let type in this.actions) {
- let action = this.actions[type];
- if (action.id == data) {
- action.action(this._targetElement, this._contentWindow);
- break;
- }
- }
- break;
- }
-
- // Provide selected text to FindInPageBar on request.
- case "TextSelection:Get": {
- Messaging.sendRequest({
- type: "TextSelection:Data",
- requestId: data,
- text: this._getSelectedText(),
- });
-
- this._uninit();
- break;
- }
-
- // User closed ActionBar by clicking "checkmark" button.
- case "TextSelection:End": {
- // End the requested selection only.
- if (this._selectionID == JSON.parse(data).selectionID) {
- this._uninit();
- }
- break;
- }
- }
- },
-
- /**
- * Called when Gecko AccessibleCaret becomes visible.
- */
- _init: function(boundingClientRect) {
- let [element, win] = this._getSelectionTargets();
- if (!win) {
- return this.START_TOUCH_ERROR.NO_CONTENT_WINDOW;
- }
-
- // 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({
- 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({
- type: "TextSelection:Visibility",
- selectionID: this._selectionID,
- });
- },
-
- /**
- * Determines the window containing the selection, and its
- * editable element if present.
- */
- _getSelectionTargets: function() {
- let [element, win] = [Services.focus.focusedElement, Services.focus.focusedWindow];
- if (!element) {
- // No focused editable.
- return [null, win];
- }
-
- // Return focused editable text element and its window.
- if (((element instanceof HTMLInputElement) && element.mozIsTextField(false)) ||
- (element instanceof HTMLTextAreaElement) ||
- element.isContentEditable) {
- return [element, win];
- }
-
- // Focused element can't contain text.
- return [null, win];
- },
-
- /**
- * The active Selection has changed, if the current focused element / win,
- * pair, or state of the win's designMode changes.
- */
- _selectionHasChanged: function() {
- let [element, win] = this._getSelectionTargets();
- return (this._targetElement !== element ||
- this._contentWindow !== win ||
- this._isInDesignMode(this._contentWindow) !== this._isInDesignMode(win));
- },
-
- /**
- * Called when Gecko AccessibleCaret becomes hidden,
- * ActionBar is closed by user "close" request, or as a result of object
- * methods such as SELECT_ALL, PASTE, etc.
- */
- _uninit: function(clearSelection = true) {
- // Bail if there's no active selection.
- if (!this._selectionID) {
- return;
- }
-
- // Close the ActionBar.
- Messaging.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;
-
- // Clear selection required if triggered by self, or TextSelection icon
- // actions. If called by Gecko CaretStateChangedEvent,
- // visibility state is already correct.
- if (clearSelection) {
- this._clearSelection();
- }
- },
-
- /**
- * Final UI cleanup when Actionbar is closed by icon click, or where
- * we terminate selection state after before/after actionbar actions
- * (Cut, Copy, Paste, Search, Share, Call).
- */
- _clearSelection: function(element = this._targetElement, win = this._contentWindow) {
- // Commit edit compositions, and clear focus from editables.
- if (element) {
- let imeSupport = this._getEditor(element, win).QueryInterface(Ci.nsIEditorIMESupport);
- if (imeSupport.composing) {
- imeSupport.forceCompositionEnd();
- }
- element.blur();
- }
-
- // Remove Selection from non-editables and now-unfocused contentEditables.
- if (!element || element.isContentEditable) {
- this._getSelection().removeAllRanges();
- }
- },
-
- /**
- * Called to determine current ActionBar actions and send to TextSelection
- * handler. By default we only send if current action state differs from
- * the previous.
- * @param By default we only send an ActionBarStatus update message if
- * there is a change from the previous state. sendAlways can be
- * set by init() for example, where we want to always send the
- * current state.
- */
- _sendActionBarActions: function(sendAlways) {
- let actions = this._getActionBarActions();
-
- 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({
- type: "TextSelection:ActionbarStatus",
- selectionID: this._selectionID,
- actions: actions,
- x: this._boundingClientRect.x,
- y: this._boundingClientRect.y,
- width: this._boundingClientRect.width,
- height: this._boundingClientRect.height
- });
- }
-
- this._actionBarActions = actions;
- },
-
- /**
- * Determine and return current ActionBar state.
- */
- _getActionBarActions: function(element = this._targetElement, win = this._contentWindow) {
- let actions = [];
-
- for (let type in this.actions) {
- let action = this.actions[type];
- if (action.selector.matches(element, win)) {
- let a = {
- id: action.id,
- label: this._getActionValue(action, "label", "", element),
- icon: this._getActionValue(action, "icon", "drawable://ic_status_logo", element),
- order: this._getActionValue(action, "order", 0, element),
- floatingOrder: this._getActionValue(action, "floatingOrder", 9, element),
- showAsAction: this._getActionValue(action, "showAsAction", true, element),
- };
- actions.push(a);
- }
- }
- actions.sort((a, b) => b.order - a.order);
-
- return actions;
- },
-
- /**
- * Provides a value from an action. If the action defines the value as a function,
- * we return the result of calling the function. Otherwise, we return the value
- * itself. If the value isn't defined for this action, will return a default.
- */
- _getActionValue: function(obj, name, defaultValue, element) {
- if (!(name in obj))
- return defaultValue;
-
- if (typeof obj[name] == "function")
- return obj[name](element);
-
- return obj[name];
- },
-
- /**
- * Actionbar callback methods.
- */
- actions: {
-
- SELECT_ALL: {
- id: "selectall_action",
- label: Strings.browser.GetStringFromName("contextmenu.selectAll"),
- icon: "drawable://ab_select_all",
- order: 5,
- floatingOrder: 5,
-
- selector: {
- matches: function(element, win) {
- // For editable, check its length. For default contentWindow, assume
- // true, else there'd been nothing to long-press to open ActionBar.
- return (element) ? element.textLength != 0 : true;
- },
- },
-
- action: function(element, win) {
- // Some Mobile keyboards such as SwiftKeyboard, provide auto-suggest
- // style highlights via composition selections in editables.
- if (element) {
- // If we have an active composition string, commit it, and
- // ensure proper element focus.
- let imeSupport = ActionBarHandler._getEditor(element, win).
- QueryInterface(Ci.nsIEditorIMESupport);
- if (imeSupport.composing) {
- element.blur();
- element.focus();
- }
- }
-
- // Close ActionBarHandler, then selectAll, and display handles.
- ActionBarHandler._getSelectAllController(element, win).selectAll();
- UITelemetry.addEvent("action.1", "actionbar", null, "select_all");
- },
- },
-
- CUT: {
- id: "cut_action",
- label: Strings.browser.GetStringFromName("contextmenu.cut"),
- icon: "drawable://ab_cut",
- order: 4,
- floatingOrder: 1,
-
- selector: {
- matches: function(element, win) {
- // Can cut from editable, or design-mode document.
- if (!element && !ActionBarHandler._isInDesignMode(win)) {
- return false;
- }
- // Don't allow "cut" from password fields.
- if (element instanceof Ci.nsIDOMHTMLInputElement &&
- !element.mozIsTextField(true)) {
- return false;
- }
- // Don't allow "cut" from disabled/readonly fields.
- if (element && (element.disabled || element.readOnly)) {
- return false;
- }
- // Allow if selected text exists.
- return (ActionBarHandler._getSelectedText().length > 0);
- },
- },
-
- action: function(element, win) {
- // First copy the selection text to the clipboard.
- let selectedText = ActionBarHandler._getSelectedText();
- let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
- getService(Ci.nsIClipboardHelper);
- clipboard.copyString(selectedText);
-
- let msg = Strings.browser.GetStringFromName("selectionHelper.textCopied");
- Snackbars.show(msg, Snackbars.LENGTH_LONG);
-
- // Then cut the selection text.
- ActionBarHandler._getSelection(element, win).deleteFromDocument();
-
- ActionBarHandler._uninit();
- UITelemetry.addEvent("action.1", "actionbar", null, "cut");
- },
- },
-
- COPY: {
- id: "copy_action",
- label: Strings.browser.GetStringFromName("contextmenu.copy"),
- icon: "drawable://ab_copy",
- order: 3,
- floatingOrder: 2,
-
- selector: {
- matches: function(element, win) {
- // Don't allow "copy" from password fields.
- if (element instanceof Ci.nsIDOMHTMLInputElement &&
- !element.mozIsTextField(true)) {
- return false;
- }
- // Allow if selected text exists.
- return (ActionBarHandler._getSelectedText().length > 0);
- },
- },
-
- action: function(element, win) {
- let selectedText = ActionBarHandler._getSelectedText();
- let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
- getService(Ci.nsIClipboardHelper);
- clipboard.copyString(selectedText);
-
- let msg = Strings.browser.GetStringFromName("selectionHelper.textCopied");
- Snackbars.show(msg, Snackbars.LENGTH_LONG);
-
- ActionBarHandler._uninit();
- UITelemetry.addEvent("action.1", "actionbar", null, "copy");
- },
- },
-
- PASTE: {
- id: "paste_action",
- label: Strings.browser.GetStringFromName("contextmenu.paste"),
- icon: "drawable://ab_paste",
- order: 2,
- floatingOrder: 3,
-
- selector: {
- matches: function(element, win) {
- // Can paste to editable, or design-mode document.
- if (!element && !ActionBarHandler._isInDesignMode(win)) {
- return false;
- }
- // Can't paste into disabled/readonly fields.
- if (element && (element.disabled || element.readOnly)) {
- return false;
- }
- // Can't paste if Clipboard empty.
- let flavors = ["text/unicode"];
- return Services.clipboard.hasDataMatchingFlavors(flavors, flavors.length,
- Ci.nsIClipboard.kGlobalClipboard);
- },
- },
-
- action: function(element, win) {
- // Paste the clipboard, then close the ActionBarHandler and ActionBar.
- ActionBarHandler._getEditor(element, win).
- paste(Ci.nsIClipboard.kGlobalClipboard);
- ActionBarHandler._uninit();
- UITelemetry.addEvent("action.1", "actionbar", null, "paste");
- },
- },
-
- CALL: {
- id: "call_action",
- label: Strings.browser.GetStringFromName("contextmenu.call"),
- icon: "drawable://phone",
- order: 1,
- floatingOrder: 0,
-
- selector: {
- matches: function(element, win) {
- return (ActionBarHandler._getSelectedPhoneNumber() != null);
- },
- },
-
- action: function(element, win) {
- BrowserApp.loadURI("tel:" +
- ActionBarHandler._getSelectedPhoneNumber());
-
- ActionBarHandler._uninit();
- UITelemetry.addEvent("action.1", "actionbar", null, "call");
- },
- },
-
- SEARCH: {
- id: "search_action",
- label: () => Strings.browser.formatStringFromName("contextmenu.search",
- [Services.search.defaultEngine.name], 1),
- icon: "drawable://ab_search",
- order: 1,
- floatingOrder: 6,
-
- selector: {
- matches: function(element, win) {
- // Allow if selected text exists.
- return (ActionBarHandler._getSelectedText().length > 0);
- },
- },
-
- action: function(element, win) {
- let selectedText = ActionBarHandler._getSelectedText();
- ActionBarHandler._uninit();
-
- // Set current tab as parent of new tab,
- // and set new tab as private if the parent is.
- let searchSubmission = Services.search.defaultEngine.getSubmission(selectedText);
- let parent = BrowserApp.selectedTab;
- let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(parent.browser);
- BrowserApp.addTab(searchSubmission.uri.spec,
- { parentId: parent.id,
- selected: true,
- isPrivate: isPrivate,
- }
- );
-
- UITelemetry.addEvent("action.1", "actionbar", null, "search");
- },
- },
-
- SEARCH_ADD: {
- id: "search_add_action",
- label: Strings.browser.GetStringFromName("contextmenu.addSearchEngine3"),
- icon: "drawable://ab_add_search_engine",
- order: 0,
- floatingOrder: 8,
-
- selector: {
- matches: function(element, win) {
- if(!(element instanceof HTMLInputElement)) {
- return false;
- }
- let form = element.form;
- if (!form || element.type == "password") {
- return false;
- }
-
- let method = form.method.toUpperCase();
- let canAddEngine = (method == "GET") ||
- (method == "POST" && (form.enctype != "text/plain" && form.enctype != "multipart/form-data"));
- if (!canAddEngine) {
- return false;
- }
-
- // If SearchEngine query finds it, then we don't want action to add displayed.
- if (SearchEngines.visibleEngineExists(element)) {
- return false;
- }
-
- return true;
- },
- },
-
- action: function(element, win) {
- UITelemetry.addEvent("action.1", "actionbar", null, "add_search_engine");
-
- // Engines are added asynch. If required, update SelectionUI on callback.
- SearchEngines.addEngine(element, (result) => {
- if (result) {
- ActionBarHandler._sendActionBarActions(true);
- }
- });
- },
- },
-
- SHARE: {
- id: "share_action",
- label: Strings.browser.GetStringFromName("contextmenu.share"),
- icon: "drawable://ic_menu_share",
- order: 0,
- floatingOrder: 4,
-
- selector: {
- matches: function(element, win) {
- if (!ParentalControls.isAllowed(ParentalControls.SHARE)) {
- return false;
- }
- // Allow if selected text exists.
- return (ActionBarHandler._getSelectedText().length > 0);
- },
- },
-
- action: function(element, win) {
- Messaging.sendRequest({
- type: "Share:Text",
- text: ActionBarHandler._getSelectedText(),
- });
-
- ActionBarHandler._uninit();
- UITelemetry.addEvent("action.1", "actionbar", null, "share");
- },
- },
- },
-
- /**
- * Provides UUID service for generating action ID's.
- */
- get _idService() {
- delete this._idService;
- return this._idService = Cc["@mozilla.org/uuid-generator;1"].
- getService(Ci.nsIUUIDGenerator);
- },
-
- /**
- * The targetElement holds an editable element containing a
- * selection or a caret.
- */
- get _targetElement() {
- if (this._targetElementRef)
- return this._targetElementRef.get();
- return null;
- },
-
- set _targetElement(element) {
- this._targetElementRef = Cu.getWeakReference(element);
- },
-
- /**
- * The contentWindow holds the selection, or the targetElement
- * if it's an editable.
- */
- get _contentWindow() {
- if (this._contentWindowRef)
- return this._contentWindowRef.get();
- return null;
- },
-
- set _contentWindow(aContentWindow) {
- this._contentWindowRef = Cu.getWeakReference(aContentWindow);
- },
-
- /**
- * If we have an active selection, is it part of a designMode document?
- */
- _isInDesignMode: function(win) {
- return this._selectionID && (win.document.designMode === "on");
- },
-
- /**
- * Provides the currently selected text, for either an editable,
- * or for the default contentWindow.
- */
- _getSelectedText: function() {
- // Can be called from FindInPageBar "TextSelection:Get", when there
- // is no active selection.
- if (!this._selectionID) {
- return "";
- }
-
- let selection = this._getSelection();
-
- // Textarea can contain LF, etc.
- if (this._targetElement instanceof Ci.nsIDOMHTMLTextAreaElement) {
- let flags = Ci.nsIDocumentEncoder.OutputPreformatted |
- Ci.nsIDocumentEncoder.OutputRaw;
- return selection.QueryInterface(Ci.nsISelectionPrivate).
- toStringWithFormat("text/plain", flags, 0);
- }
-
- // Return explicitly selected text.
- return selection.toString();
- },
-
- /**
- * Provides the nsISelection for either an editor, or from the
- * default window.
- */
- _getSelection: function(element = this._targetElement, win = this._contentWindow) {
- return (element instanceof Ci.nsIDOMNSEditableElement) ?
- this._getEditor(element).selection :
- win.getSelection();
- },
-
- /**
- * Returns an nsEditor or nsHTMLEditor.
- */
- _getEditor: function(element = this._targetElement, win = this._contentWindow) {
- if (element instanceof Ci.nsIDOMNSEditableElement) {
- return element.QueryInterface(Ci.nsIDOMNSEditableElement).editor;
- }
-
- return win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation).
- QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIEditingSession).
- getEditorForWindow(win);
- },
-
- /**
- * Returns a selection controller.
- */
- _getSelectionController: function(element = this._targetElement, win = this._contentWindow) {
- if (element instanceof Ci.nsIDOMNSEditableElement) {
- return this._getEditor(element, win).selectionController;
- }
-
- return win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation).
- QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsISelectionDisplay).
- QueryInterface(Ci.nsISelectionController);
- },
-
- /**
- * For selectAll(), provides the editor, or the default window selection Controller.
- */
- _getSelectAllController: function(element = this._targetElement, win = this._contentWindow) {
- let editor = this._getEditor(element, win);
- return (editor) ?
- editor : this._getSelectionController(element, win);
- },
-
- /**
- * Call / Phone Helper methods.
- */
- _getSelectedPhoneNumber: function() {
- let selectedText = this._getSelectedText().trim();
- return this._isPhoneNumber(selectedText) ?
- selectedText : null;
- },
-
- _isPhoneNumber: function(selectedText) {
- return (PHONE_REGEX.test(selectedText));
- },
-};