Bug 1869647 - Mark hasStorageAccess.sub.https.window.html as intermittent after wpt...
[gecko.git] / toolkit / modules / SelectionUtils.sys.mjs
blob8dcbc0c494443fca50d05796b8ea7cd8babd4e46
1 /* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 export var SelectionUtils = {
7   /**
8    * Trim the selection text to a reasonable size and sanitize it to make it
9    * safe for search query input.
10    *
11    * @param aSelection
12    *        The selection text to trim.
13    * @param aMaxLen
14    *        The maximum string length, defaults to a reasonable size if undefined.
15    * @return The trimmed selection text.
16    */
17   trimSelection(aSelection, aMaxLen) {
18     // Selections of more than 150 characters aren't useful.
19     const maxLen = Math.min(aMaxLen || 150, aSelection.length);
21     if (aSelection.length > maxLen) {
22       // only use the first maxLen important chars. see bug 221361
23       let pattern = new RegExp("^(?:\\s*.){0," + maxLen + "}");
24       pattern.test(aSelection);
25       aSelection = RegExp.lastMatch;
26     }
28     aSelection = aSelection.trim().replace(/\s+/g, " ");
30     if (aSelection.length > maxLen) {
31       aSelection = aSelection.substr(0, maxLen);
32     }
34     return aSelection;
35   },
37   /**
38    * Retrieve the text selection details for the given window.
39    *
40    * @param  aTopWindow
41    *         The top window of the element containing the selection.
42    * @param  aCharLen
43    *         The maximum string length for the selection text.
44    * @return The selection details containing the full and trimmed selection text
45    *         and link details for link selections.
46    */
47   getSelectionDetails(aTopWindow, aCharLen) {
48     let focusedWindow = {};
49     let focusedElement = Services.focus.getFocusedElementForWindow(
50       aTopWindow,
51       true,
52       focusedWindow
53     );
54     focusedWindow = focusedWindow.value;
56     let selection = focusedWindow.getSelection();
57     let selectionStr = selection.toString();
58     let fullText;
60     let url;
61     let linkText;
63     let isDocumentLevelSelection = true;
64     // try getting a selected text in text input.
65     if (!selectionStr && focusedElement) {
66       // Don't get the selection for password fields. See bug 565717.
67       if (
68         ChromeUtils.getClassName(focusedElement) === "HTMLTextAreaElement" ||
69         (ChromeUtils.getClassName(focusedElement) === "HTMLInputElement" &&
70           focusedElement.mozIsTextField(true))
71       ) {
72         selection = focusedElement.editor.selection;
73         selectionStr = selection.toString();
74         isDocumentLevelSelection = false;
75       }
76     }
78     let collapsed = selection.isCollapsed;
80     if (selectionStr) {
81       // Have some text, let's figure out if it looks like a URL that isn't
82       // actually a link.
83       linkText = selectionStr.trim();
84       if (/^(?:https?|ftp):/i.test(linkText)) {
85         try {
86           url = Services.io.newURI(linkText);
87         } catch (ex) {}
88       } else if (/^(?:[a-z\d-]+\.)+[a-z]+$/i.test(linkText)) {
89         // Check if this could be a valid url, just missing the protocol.
90         // Now let's see if this is an intentional link selection. Our guess is
91         // based on whether the selection begins/ends with whitespace or is
92         // preceded/followed by a non-word character.
94         // selection.toString() trims trailing whitespace, so we look for
95         // that explicitly in the first and last ranges.
96         let beginRange = selection.getRangeAt(0);
97         let delimitedAtStart = /^\s/.test(beginRange);
98         if (!delimitedAtStart) {
99           let container = beginRange.startContainer;
100           let offset = beginRange.startOffset;
101           if (container.nodeType == container.TEXT_NODE && offset > 0) {
102             delimitedAtStart = /\W/.test(container.textContent[offset - 1]);
103           } else {
104             delimitedAtStart = true;
105           }
106         }
108         let delimitedAtEnd = false;
109         if (delimitedAtStart) {
110           let endRange = selection.getRangeAt(selection.rangeCount - 1);
111           delimitedAtEnd = /\s$/.test(endRange);
112           if (!delimitedAtEnd) {
113             let container = endRange.endContainer;
114             let offset = endRange.endOffset;
115             if (
116               container.nodeType == container.TEXT_NODE &&
117               offset < container.textContent.length
118             ) {
119               delimitedAtEnd = /\W/.test(container.textContent[offset]);
120             } else {
121               delimitedAtEnd = true;
122             }
123           }
124         }
126         if (delimitedAtStart && delimitedAtEnd) {
127           try {
128             url = Services.uriFixup.getFixupURIInfo(linkText).preferredURI;
129           } catch (ex) {}
130         }
131       }
132     }
134     if (selectionStr) {
135       // Pass up to 16K through unmolested.  If an add-on needs more, they will
136       // have to use a content script.
137       fullText = selectionStr.substr(0, 16384);
138       selectionStr = this.trimSelection(selectionStr, aCharLen);
139     }
141     if (url && !url.host) {
142       url = null;
143     }
145     return {
146       text: selectionStr,
147       docSelectionIsCollapsed: collapsed,
148       isDocumentLevelSelection,
149       fullText,
150       linkURL: url ? url.spec : null,
151       linkText: url ? linkText : "",
152     };
153   },