summaryrefslogtreecommitdiffstats
path: root/toolkit/modules/SelectionUtils.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--toolkit/modules/SelectionUtils.sys.mjs154
1 files changed, 154 insertions, 0 deletions
diff --git a/toolkit/modules/SelectionUtils.sys.mjs b/toolkit/modules/SelectionUtils.sys.mjs
new file mode 100644
index 0000000000..8dcbc0c494
--- /dev/null
+++ b/toolkit/modules/SelectionUtils.sys.mjs
@@ -0,0 +1,154 @@
+/* -*- mode: js; 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/. */
+
+export var SelectionUtils = {
+ /**
+ * Trim the selection text to a reasonable size and sanitize it to make it
+ * safe for search query input.
+ *
+ * @param aSelection
+ * The selection text to trim.
+ * @param aMaxLen
+ * The maximum string length, defaults to a reasonable size if undefined.
+ * @return The trimmed selection text.
+ */
+ trimSelection(aSelection, aMaxLen) {
+ // Selections of more than 150 characters aren't useful.
+ const maxLen = Math.min(aMaxLen || 150, aSelection.length);
+
+ if (aSelection.length > maxLen) {
+ // only use the first maxLen important chars. see bug 221361
+ let pattern = new RegExp("^(?:\\s*.){0," + maxLen + "}");
+ pattern.test(aSelection);
+ aSelection = RegExp.lastMatch;
+ }
+
+ aSelection = aSelection.trim().replace(/\s+/g, " ");
+
+ if (aSelection.length > maxLen) {
+ aSelection = aSelection.substr(0, maxLen);
+ }
+
+ return aSelection;
+ },
+
+ /**
+ * Retrieve the text selection details for the given window.
+ *
+ * @param aTopWindow
+ * The top window of the element containing the selection.
+ * @param aCharLen
+ * The maximum string length for the selection text.
+ * @return The selection details containing the full and trimmed selection text
+ * and link details for link selections.
+ */
+ getSelectionDetails(aTopWindow, aCharLen) {
+ let focusedWindow = {};
+ let focusedElement = Services.focus.getFocusedElementForWindow(
+ aTopWindow,
+ true,
+ focusedWindow
+ );
+ focusedWindow = focusedWindow.value;
+
+ let selection = focusedWindow.getSelection();
+ let selectionStr = selection.toString();
+ let fullText;
+
+ let url;
+ let linkText;
+
+ let isDocumentLevelSelection = true;
+ // try getting a selected text in text input.
+ if (!selectionStr && focusedElement) {
+ // Don't get the selection for password fields. See bug 565717.
+ if (
+ ChromeUtils.getClassName(focusedElement) === "HTMLTextAreaElement" ||
+ (ChromeUtils.getClassName(focusedElement) === "HTMLInputElement" &&
+ focusedElement.mozIsTextField(true))
+ ) {
+ selection = focusedElement.editor.selection;
+ selectionStr = selection.toString();
+ isDocumentLevelSelection = false;
+ }
+ }
+
+ let collapsed = selection.isCollapsed;
+
+ if (selectionStr) {
+ // Have some text, let's figure out if it looks like a URL that isn't
+ // actually a link.
+ linkText = selectionStr.trim();
+ if (/^(?:https?|ftp):/i.test(linkText)) {
+ try {
+ url = Services.io.newURI(linkText);
+ } catch (ex) {}
+ } else if (/^(?:[a-z\d-]+\.)+[a-z]+$/i.test(linkText)) {
+ // Check if this could be a valid url, just missing the protocol.
+ // Now let's see if this is an intentional link selection. Our guess is
+ // based on whether the selection begins/ends with whitespace or is
+ // preceded/followed by a non-word character.
+
+ // selection.toString() trims trailing whitespace, so we look for
+ // that explicitly in the first and last ranges.
+ let beginRange = selection.getRangeAt(0);
+ let delimitedAtStart = /^\s/.test(beginRange);
+ if (!delimitedAtStart) {
+ let container = beginRange.startContainer;
+ let offset = beginRange.startOffset;
+ if (container.nodeType == container.TEXT_NODE && offset > 0) {
+ delimitedAtStart = /\W/.test(container.textContent[offset - 1]);
+ } else {
+ delimitedAtStart = true;
+ }
+ }
+
+ let delimitedAtEnd = false;
+ if (delimitedAtStart) {
+ let endRange = selection.getRangeAt(selection.rangeCount - 1);
+ delimitedAtEnd = /\s$/.test(endRange);
+ if (!delimitedAtEnd) {
+ let container = endRange.endContainer;
+ let offset = endRange.endOffset;
+ if (
+ container.nodeType == container.TEXT_NODE &&
+ offset < container.textContent.length
+ ) {
+ delimitedAtEnd = /\W/.test(container.textContent[offset]);
+ } else {
+ delimitedAtEnd = true;
+ }
+ }
+ }
+
+ if (delimitedAtStart && delimitedAtEnd) {
+ try {
+ url = Services.uriFixup.getFixupURIInfo(linkText).preferredURI;
+ } catch (ex) {}
+ }
+ }
+ }
+
+ if (selectionStr) {
+ // Pass up to 16K through unmolested. If an add-on needs more, they will
+ // have to use a content script.
+ fullText = selectionStr.substr(0, 16384);
+ selectionStr = this.trimSelection(selectionStr, aCharLen);
+ }
+
+ if (url && !url.host) {
+ url = null;
+ }
+
+ return {
+ text: selectionStr,
+ docSelectionIsCollapsed: collapsed,
+ isDocumentLevelSelection,
+ fullText,
+ linkURL: url ? url.spec : null,
+ linkText: url ? linkText : "",
+ };
+ },
+};