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/. */
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",
15 export class FindContent {
16 constructor(docShell) {
17 this.finder = new lazy.Finder(docShell);
21 if (!this._iterator) {
22 this._iterator = new lazy.FinderIterator();
24 return this._iterator;
28 if (!this._highlighter) {
29 this._highlighter = new lazy.FinderHighlighter(this.finder, true);
31 return this._highlighter;
37 * Performs a search which will cache found ranges in `iterator._previousRanges`. Cached
38 * data can then be used by `highlightResults`, `_collectRectData` and `_serializeRangeData`.
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.
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.
55 return new Promise(resolve => {
65 this.iterator.reset();
67 // Cast `caseSensitive` and `entireWord` to boolean, otherwise _iterator.start will throw.
68 let iteratorPromise = this.iterator.start({
70 caseSensitive: !!caseSensitive,
71 entireWord: !!entireWord,
73 listener: this.finder,
74 matchDiacritics: !!matchDiacritics,
78 iteratorPromise.then(() => {
81 if (includeRangeData) {
82 rangeData = this._serializeRangeData();
84 if (includeRectData) {
85 rectData = this._collectRectData();
89 count: this.iterator._previousRanges.length,
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.
105 * @returns {Array} - serializable range data.
107 _serializeRangeData() {
108 let ranges = this.iterator._previousRanges;
111 let nodeCountWin = 0;
116 for (let range of ranges) {
117 let startContainer = range.startContainer;
118 let doc = startContainer.ownerDocument;
120 if (lastDoc !== doc) {
121 walker = doc.createTreeWalker(
123 doc.defaultView.NodeFilter.SHOW_TEXT,
128 node = walker.nextNode();
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();
142 if (node == range.startContainer) {
145 node = walker.nextNode();
148 data.startTextNodePos = nodeCountWin;
149 data.startOffset = range.startOffset;
151 if (range.startContainer != range.endContainer) {
152 node = walker.nextNode();
155 if (node == range.endContainer) {
158 node = walker.nextNode();
161 data.endTextNodePos = nodeCountWin;
162 data.endOffset = range.endOffset;
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.
175 * @returns {Array} rectData - serializable rect data.
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 });
192 * Highlights range(s) found in previous browser.find.find.
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.
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.
205 * "Success" - Highlighting succeeded.
206 * "OutOfRange" - The index supplied was out of range.
207 * "NoResults" - There were no search results to highlight.
209 highlightResults(params) {
210 let { rangeIndex, noScroll } = params;
212 this.highlighter.highlight(false);
213 let ranges = this.iterator._previousRanges;
215 let status = "Success";
218 if (typeof rangeIndex == "number") {
219 if (rangeIndex < ranges.length) {
220 let foundRange = ranges[rangeIndex];
221 this.highlighter.highlightRange(foundRange);
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_VERTICAL_CENTER
237 status = "OutOfRange";
240 for (let range of ranges) {
241 this.highlighter.highlightRange(range);
245 status = "NoResults";