From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../actors/SelectionActionDelegateChild.sys.mjs | 442 +++++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 mobile/android/actors/SelectionActionDelegateChild.sys.mjs (limited to 'mobile/android/actors/SelectionActionDelegateChild.sys.mjs') diff --git a/mobile/android/actors/SelectionActionDelegateChild.sys.mjs b/mobile/android/actors/SelectionActionDelegateChild.sys.mjs new file mode 100644 index 0000000000..9f05906e09 --- /dev/null +++ b/mobile/android/actors/SelectionActionDelegateChild.sys.mjs @@ -0,0 +1,442 @@ +/* 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/. */ + +import { GeckoViewActorChild } from "resource://gre/modules/GeckoViewActorChild.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + LayoutUtils: "resource://gre/modules/LayoutUtils.sys.mjs", +}); + +const MAGNIFIER_PREF = "layout.accessiblecaret.magnifier.enabled"; +const ACCESSIBLECARET_HEIGHT_PREF = "layout.accessiblecaret.height"; +const PREFS = [MAGNIFIER_PREF, ACCESSIBLECARET_HEIGHT_PREF]; + +// Dispatches GeckoView:ShowSelectionAction and GeckoView:HideSelectionAction to +// the GeckoSession on accessible caret changes. +export class SelectionActionDelegateChild extends GeckoViewActorChild { + constructor(aModuleName, aMessageManager) { + super(aModuleName, aMessageManager); + + this._actionCallback = () => {}; + this._isActive = false; + this._previousMessage = ""; + + // Bug 1570744 - JSWindowActorChild's cannot be used as nsIObserver's + // directly, so we create a new function here instead to act as our + // nsIObserver, which forwards the notification to the observe method. + this._observerFunction = (subject, topic, data) => { + this.observe(subject, topic, data); + }; + for (const pref of PREFS) { + Services.prefs.addObserver(pref, this._observerFunction); + } + + this._magnifierEnabled = Services.prefs.getBoolPref(MAGNIFIER_PREF); + this._accessiblecaretHeight = parseFloat( + Services.prefs.getCharPref(ACCESSIBLECARET_HEIGHT_PREF, "0") + ); + } + + didDestroy() { + for (const pref of PREFS) { + Services.prefs.removeObserver(pref, this._observerFunction); + } + } + + _actions = [ + { + id: "org.mozilla.geckoview.HIDE", + predicate: _ => true, + perform: _ => this.handleEvent({ type: "pagehide" }), + }, + { + id: "org.mozilla.geckoview.CUT", + predicate: e => + !e.collapsed && e.selectionEditable && !this._isPasswordField(e), + perform: _ => this.docShell.doCommand("cmd_cut"), + }, + { + id: "org.mozilla.geckoview.COPY", + predicate: e => !e.collapsed && !this._isPasswordField(e), + perform: _ => this.docShell.doCommand("cmd_copy"), + }, + { + id: "org.mozilla.geckoview.PASTE", + predicate: e => + (this._isContentHtmlEditable(e) && + Services.clipboard.hasDataMatchingFlavors( + /* The following image types are considered by editor */ + ["image/gif", "image/jpeg", "image/png"], + Ci.nsIClipboard.kGlobalClipboard + )) || + (e.selectionEditable && + Services.clipboard.hasDataMatchingFlavors( + ["text/plain"], + Ci.nsIClipboard.kGlobalClipboard + )), + perform: _ => this._performPaste(), + }, + { + id: "org.mozilla.geckoview.PASTE_AS_PLAIN_TEXT", + predicate: e => + this._isContentHtmlEditable(e) && + Services.clipboard.hasDataMatchingFlavors( + ["text/html"], + Ci.nsIClipboard.kGlobalClipboard + ), + perform: _ => this._performPasteAsPlainText(), + }, + { + id: "org.mozilla.geckoview.DELETE", + predicate: e => !e.collapsed && e.selectionEditable, + perform: _ => this.docShell.doCommand("cmd_delete"), + }, + { + id: "org.mozilla.geckoview.COLLAPSE_TO_START", + predicate: e => !e.collapsed && e.selectionEditable, + perform: e => this.docShell.doCommand("cmd_moveLeft"), + }, + { + id: "org.mozilla.geckoview.COLLAPSE_TO_END", + predicate: e => !e.collapsed && e.selectionEditable, + perform: e => this.docShell.doCommand("cmd_moveRight"), + }, + { + id: "org.mozilla.geckoview.UNSELECT", + predicate: e => !e.collapsed && !e.selectionEditable, + perform: e => this.docShell.doCommand("cmd_selectNone"), + }, + { + id: "org.mozilla.geckoview.SELECT_ALL", + predicate: e => { + if (e.reason === "longpressonemptycontent") { + return false; + } + // When on design mode, focusedElement will be null. + const element = + Services.focus.focusedElement || e.target?.activeElement; + if (e.selectionEditable && e.target && element) { + let value = ""; + if (element.value) { + value = element.value; + } else if ( + element.isContentEditable || + e.target.designMode === "on" + ) { + value = element.innerText; + } + // Do not show SELECT_ALL if the editable is empty + // or all the editable text is already selected. + return value !== "" && value !== e.selectedTextContent; + } + return true; + }, + perform: e => this.docShell.doCommand("cmd_selectAll"), + }, + ]; + + receiveMessage({ name, data }) { + debug`receiveMessage ${name}`; + + switch (name) { + case "ExecuteSelectionAction": { + this._actionCallback(data); + } + } + } + + _performPaste() { + this.handleEvent({ type: "pagehide" }); + this.docShell.doCommand("cmd_paste"); + } + + _performPasteAsPlainText() { + this.handleEvent({ type: "pagehide" }); + this.docShell.doCommand("cmd_pasteNoFormatting"); + } + + _isPasswordField(aEvent) { + if (!aEvent.selectionEditable) { + return false; + } + + const win = aEvent.target.defaultView; + const focus = aEvent.target.activeElement; + return ( + win && + win.HTMLInputElement && + win.HTMLInputElement.isInstance(focus) && + !focus.mozIsTextField(/* excludePassword */ true) + ); + } + + _isContentHtmlEditable(aEvent) { + if (!aEvent.selectionEditable) { + return false; + } + + if (aEvent.target.designMode == "on") { + return true; + } + + // focused element isn't nor