Bug 1871127 - Add tsconfig, basic types, and fix or ignore remaining type errors...
[gecko.git] / toolkit / components / extensions / FindContent.sys.mjs
blob264d56f55603bdd82262c3cb71026e9ddb557ee9
1 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* vim: set sts=2 sw=2 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 const lazy = {};
9 ChromeUtils.defineESModuleGetters(lazy, {
10   Finder: "resource://gre/modules/Finder.sys.mjs",
11   FinderHighlighter: "resource://gre/modules/FinderHighlighter.sys.mjs",
12   FinderIterator: "resource://gre/modules/FinderIterator.sys.mjs",
13 });
15 export class FindContent {
16   constructor(docShell) {
17     this.finder = new lazy.Finder(docShell);
18   }
20   get iterator() {
21     if (!this._iterator) {
22       this._iterator = new lazy.FinderIterator();
23     }
24     return this._iterator;
25   }
27   get highlighter() {
28     if (!this._highlighter) {
29       this._highlighter = new lazy.FinderHighlighter(this.finder, true);
30     }
31     return this._highlighter;
32   }
34   /**
35    * findRanges
36    *
37    * Performs a search which will cache found ranges in `iterator._previousRanges`.  Cached
38    * data can then be used by `highlightResults`, `_collectRectData` and `_serializeRangeData`.
39    *
40    * @param {object} params - the params.
41    * @param {string} params.queryphrase - the text to search for.
42    * @param {boolean} params.caseSensitive - whether to use case sensitive matches.
43    * @param {boolean} params.includeRangeData - whether to collect and return range data.
44    * @param {boolean} params.matchDiacritics - whether diacritics must match.
45    * @param {boolean} params.searchString - whether to collect and return rect data.
46    * @param {boolean} params.entireWord - whether to match entire words.
47    * @param {boolean} params.includeRectData - collect and return rect data.
48    *
49    * @returns {object} that includes:
50    *   {number} count - number of results found.
51    *   {array} rangeData (if opted) - serialized representation of ranges found.
52    *   {array} rectData (if opted) - rect data of ranges found.
53    */
54   findRanges(params) {
55     return new Promise(resolve => {
56       let {
57         queryphrase,
58         caseSensitive,
59         entireWord,
60         includeRangeData,
61         includeRectData,
62         matchDiacritics,
63       } = params;
65       this.iterator.reset();
67       // Cast `caseSensitive` and `entireWord` to boolean, otherwise _iterator.start will throw.
68       let iteratorPromise = this.iterator.start({
69         word: queryphrase,
70         caseSensitive: !!caseSensitive,
71         entireWord: !!entireWord,
72         finder: this.finder,
73         listener: this.finder,
74         matchDiacritics: !!matchDiacritics,
75         useSubFrames: false,
76       });
78       iteratorPromise.then(() => {
79         let rangeData;
80         let rectData;
81         if (includeRangeData) {
82           rangeData = this._serializeRangeData();
83         }
84         if (includeRectData) {
85           rectData = this._collectRectData();
86         }
88         resolve({
89           count: this.iterator._previousRanges.length,
90           rangeData,
91           rectData,
92         });
93       });
94     });
95   }
97   /**
98    * _serializeRangeData
99    *
100    * Optionally returned by `findRanges`.
101    * Collects DOM data from ranges found on the most recent search made by `findRanges`
102    * and encodes it into a serializable form.  Useful to extensions for custom UI presentation
103    * of search results, eg, getting surrounding context of results.
104    *
105    * @returns {Array} - serializable range data.
106    */
107   _serializeRangeData() {
108     let ranges = this.iterator._previousRanges;
110     let rangeData = [];
111     let nodeCountWin = 0;
112     let lastDoc;
113     let walker;
114     let node;
116     for (let range of ranges) {
117       let startContainer = range.startContainer;
118       let doc = startContainer.ownerDocument;
120       if (lastDoc !== doc) {
121         walker = doc.createTreeWalker(
122           doc,
123           doc.defaultView.NodeFilter.SHOW_TEXT,
124           null,
125           false
126         );
127         // Get first node.
128         node = walker.nextNode();
129         // Reset node count.
130         nodeCountWin = 0;
131       }
132       lastDoc = doc;
134       // The framePos will be set by the parent process later.
135       let data = { framePos: 0, text: range.toString() };
136       rangeData.push(data);
138       if (node != range.startContainer) {
139         node = walker.nextNode();
140         while (node) {
141           nodeCountWin++;
142           if (node == range.startContainer) {
143             break;
144           }
145           node = walker.nextNode();
146         }
147       }
148       data.startTextNodePos = nodeCountWin;
149       data.startOffset = range.startOffset;
151       if (range.startContainer != range.endContainer) {
152         node = walker.nextNode();
153         while (node) {
154           nodeCountWin++;
155           if (node == range.endContainer) {
156             break;
157           }
158           node = walker.nextNode();
159         }
160       }
161       data.endTextNodePos = nodeCountWin;
162       data.endOffset = range.endOffset;
163     }
165     return rangeData;
166   }
168   /**
169    * _collectRectData
170    *
171    * Optionally returned by `findRanges`.
172    * Collects rect data of ranges found by most recent search made by `findRanges`.
173    * Useful to extensions for custom highlighting of search results.
174    *
175    * @returns {Array} rectData - serializable rect data.
176    */
177   _collectRectData() {
178     let rectData = [];
180     let ranges = this.iterator._previousRanges;
181     for (let range of ranges) {
182       let rectsAndTexts = this.highlighter._getRangeRectsAndTexts(range);
183       rectData.push({ text: range.toString(), rectsAndTexts });
184     }
186     return rectData;
187   }
189   /**
190    * highlightResults
191    *
192    * Highlights range(s) found in previous browser.find.find.
193    *
194    * @param {object} params - may contain any of the following properties:
195    *   all of which are optional:
196    *   {number} rangeIndex -
197    *            Found range to be highlighted held in API's ranges array for the tabId.
198    *            Default highlights all ranges.
199    *   {number} tabId - Tab to highlight.  Defaults to the active tab.
200    *   {boolean} noScroll - Don't scroll to highlighted item.
201    *
202    * @returns {string} - a string describing the resulting status of the highlighting,
203    *   which will be used as criteria for resolving or rejecting the promise.
204    *   This can be:
205    *   "Success" - Highlighting succeeded.
206    *   "OutOfRange" - The index supplied was out of range.
207    *   "NoResults" - There were no search results to highlight.
208    */
209   highlightResults(params) {
210     let { rangeIndex, noScroll } = params;
212     this.highlighter.highlight(false);
213     let ranges = this.iterator._previousRanges;
215     let status = "Success";
217     if (ranges.length) {
218       if (typeof rangeIndex == "number") {
219         if (rangeIndex < ranges.length) {
220           let foundRange = ranges[rangeIndex];
221           this.highlighter.highlightRange(foundRange);
223           if (!noScroll) {
224             let node = foundRange.startContainer;
225             let editableNode = this.highlighter._getEditableNode(node);
226             let controller = editableNode
227               ? editableNode.editor.selectionController
228               : this.finder._getSelectionController(node.ownerGlobal);
230             controller.scrollSelectionIntoView(
231               controller.SELECTION_FIND,
232               controller.SELECTION_ON,
233               controller.SCROLL_CENTER_VERTICALLY
234             );
235           }
236         } else {
237           status = "OutOfRange";
238         }
239       } else {
240         for (let range of ranges) {
241           this.highlighter.highlightRange(range);
242         }
243       }
244     } else {
245       status = "NoResults";
246     }
248     return status;
249   }