Source code

Revision control

Copy as Markdown

Other Tools

/* 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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
const lazy = {};
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"shortcutsDelay",
"browser.ml.chat.shortcuts.longPress"
);
// Additional events to listen with others to create the actor in BrowserGlue
const EVENTS = ["mousedown", "mouseup", "pagehide"];
/**
* JSWindowActor to detect content page events to send GenAI related data.
*/
export class GenAIChild extends JSWindowActorChild {
actorCreated() {
this.document.addEventListener("selectionchange", this);
// Use capture as some pages might stop the events
EVENTS.forEach(ev => this.contentWindow.addEventListener(ev, this, true));
}
didDestroy() {
this.document.removeEventListener("selectionchange", this);
EVENTS.forEach(ev =>
this.contentWindow?.removeEventListener(ev, this, true)
);
}
handleEvent(event) {
const sendHide = () =>
this.sendAsyncMessage("GenAI:HideShortcuts", event.type);
switch (event.type) {
case "mousedown":
this.downSelection = this.getSelectionInfo().selection;
this.downTime = Date.now();
sendHide();
break;
case "mouseup": {
// Only handle plain clicks
if (
event.button ||
event.altKey ||
event.ctrlKey ||
event.metaKey ||
event.shiftKey
) {
return;
}
// Show immediately on selection or allow long press with no selection
const selectionInfo = this.getSelectionInfo();
const delay = Date.now() - (this.downTime ?? 0);
if (
(selectionInfo.selection &&
selectionInfo.selection != this.downSelection) ||
delay > lazy.shortcutsDelay
) {
this.sendAsyncMessage("GenAI:ShowShortcuts", {
...selectionInfo,
delay,
x: event.screenX,
y: event.screenY,
});
}
break;
}
case "pagehide":
case "resize":
case "scroll":
case "selectionchange":
// Hide if selection might have shifted away from shortcuts
sendHide();
break;
}
}
/**
* Provide the selected text and input type.
*
* @returns {object} selection info
*/
getSelectionInfo() {
// Handle regular selection outside of inputs
const { activeElement } = this.document;
const selection = this.contentWindow.getSelection()?.toString().trim();
if (selection) {
return {
inputType: activeElement.closest("[contenteditable]")
? "contenteditable"
: "",
selection,
};
}
// Selection within input elements
const { selectionStart, value } = activeElement;
if (selectionStart != null && value != null) {
return {
inputType: activeElement.localName,
selection: value.slice(selectionStart, activeElement.selectionEnd),
};
}
return { inputType: "", selection: "" };
}
}