forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			250 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			250 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 | |
| /* vim: set sts=2 sw=2 et tw=80: */
 | |
| /* 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/. */
 | |
| 
 | |
| const lazy = {};
 | |
| 
 | |
| ChromeUtils.defineESModuleGetters(lazy, {
 | |
|   Finder: "resource://gre/modules/Finder.sys.mjs",
 | |
|   FinderHighlighter: "resource://gre/modules/FinderHighlighter.sys.mjs",
 | |
|   FinderIterator: "resource://gre/modules/FinderIterator.sys.mjs",
 | |
| });
 | |
| 
 | |
| export class FindContent {
 | |
|   constructor(docShell) {
 | |
|     this.finder = new lazy.Finder(docShell);
 | |
|   }
 | |
| 
 | |
|   get iterator() {
 | |
|     if (!this._iterator) {
 | |
|       this._iterator = new lazy.FinderIterator();
 | |
|     }
 | |
|     return this._iterator;
 | |
|   }
 | |
| 
 | |
|   get highlighter() {
 | |
|     if (!this._highlighter) {
 | |
|       this._highlighter = new lazy.FinderHighlighter(this.finder, true);
 | |
|     }
 | |
|     return this._highlighter;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * findRanges
 | |
|    *
 | |
|    * Performs a search which will cache found ranges in `iterator._previousRanges`.  Cached
 | |
|    * data can then be used by `highlightResults`, `_collectRectData` and `_serializeRangeData`.
 | |
|    *
 | |
|    * @param {object} params - the params.
 | |
|    * @param {string} params.queryphrase - the text to search for.
 | |
|    * @param {boolean} params.caseSensitive - whether to use case sensitive matches.
 | |
|    * @param {boolean} params.includeRangeData - whether to collect and return range data.
 | |
|    * @param {boolean} params.matchDiacritics - whether diacritics must match.
 | |
|    * @param {boolean} params.searchString - whether to collect and return rect data.
 | |
|    * @param {boolean} params.entireWord - whether to match entire words.
 | |
|    * @param {boolean} params.includeRectData - collect and return rect data.
 | |
|    *
 | |
|    * @returns {object} that includes:
 | |
|    *   {number} count - number of results found.
 | |
|    *   {array} rangeData (if opted) - serialized representation of ranges found.
 | |
|    *   {array} rectData (if opted) - rect data of ranges found.
 | |
|    */
 | |
|   findRanges(params) {
 | |
|     return new Promise(resolve => {
 | |
|       let {
 | |
|         queryphrase,
 | |
|         caseSensitive,
 | |
|         entireWord,
 | |
|         includeRangeData,
 | |
|         includeRectData,
 | |
|         matchDiacritics,
 | |
|       } = params;
 | |
| 
 | |
|       this.iterator.reset();
 | |
| 
 | |
|       // Cast `caseSensitive` and `entireWord` to boolean, otherwise _iterator.start will throw.
 | |
|       let iteratorPromise = this.iterator.start({
 | |
|         word: queryphrase,
 | |
|         caseSensitive: !!caseSensitive,
 | |
|         entireWord: !!entireWord,
 | |
|         finder: this.finder,
 | |
|         listener: this.finder,
 | |
|         matchDiacritics: !!matchDiacritics,
 | |
|         useSubFrames: false,
 | |
|       });
 | |
| 
 | |
|       iteratorPromise.then(() => {
 | |
|         let rangeData;
 | |
|         let rectData;
 | |
|         if (includeRangeData) {
 | |
|           rangeData = this._serializeRangeData();
 | |
|         }
 | |
|         if (includeRectData) {
 | |
|           rectData = this._collectRectData();
 | |
|         }
 | |
| 
 | |
|         resolve({
 | |
|           count: this.iterator._previousRanges.length,
 | |
|           rangeData,
 | |
|           rectData,
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * _serializeRangeData
 | |
|    *
 | |
|    * Optionally returned by `findRanges`.
 | |
|    * Collects DOM data from ranges found on the most recent search made by `findRanges`
 | |
|    * and encodes it into a serializable form.  Useful to extensions for custom UI presentation
 | |
|    * of search results, eg, getting surrounding context of results.
 | |
|    *
 | |
|    * @returns {Array} - serializable range data.
 | |
|    */
 | |
|   _serializeRangeData() {
 | |
|     let ranges = this.iterator._previousRanges;
 | |
| 
 | |
|     let rangeData = [];
 | |
|     let nodeCountWin = 0;
 | |
|     let lastDoc;
 | |
|     let walker;
 | |
|     let node;
 | |
| 
 | |
|     for (let range of ranges) {
 | |
|       let startContainer = range.startContainer;
 | |
|       let doc = startContainer.ownerDocument;
 | |
| 
 | |
|       if (lastDoc !== doc) {
 | |
|         walker = doc.createTreeWalker(
 | |
|           doc,
 | |
|           doc.defaultView.NodeFilter.SHOW_TEXT,
 | |
|           null,
 | |
|           false
 | |
|         );
 | |
|         // Get first node.
 | |
|         node = walker.nextNode();
 | |
|         // Reset node count.
 | |
|         nodeCountWin = 0;
 | |
|       }
 | |
|       lastDoc = doc;
 | |
| 
 | |
|       // The framePos will be set by the parent process later.
 | |
|       let data = { framePos: 0, text: range.toString() };
 | |
|       rangeData.push(data);
 | |
| 
 | |
|       if (node != range.startContainer) {
 | |
|         node = walker.nextNode();
 | |
|         while (node) {
 | |
|           nodeCountWin++;
 | |
|           if (node == range.startContainer) {
 | |
|             break;
 | |
|           }
 | |
|           node = walker.nextNode();
 | |
|         }
 | |
|       }
 | |
|       data.startTextNodePos = nodeCountWin;
 | |
|       data.startOffset = range.startOffset;
 | |
| 
 | |
|       if (range.startContainer != range.endContainer) {
 | |
|         node = walker.nextNode();
 | |
|         while (node) {
 | |
|           nodeCountWin++;
 | |
|           if (node == range.endContainer) {
 | |
|             break;
 | |
|           }
 | |
|           node = walker.nextNode();
 | |
|         }
 | |
|       }
 | |
|       data.endTextNodePos = nodeCountWin;
 | |
|       data.endOffset = range.endOffset;
 | |
|     }
 | |
| 
 | |
|     return rangeData;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * _collectRectData
 | |
|    *
 | |
|    * Optionally returned by `findRanges`.
 | |
|    * Collects rect data of ranges found by most recent search made by `findRanges`.
 | |
|    * Useful to extensions for custom highlighting of search results.
 | |
|    *
 | |
|    * @returns {Array} rectData - serializable rect data.
 | |
|    */
 | |
|   _collectRectData() {
 | |
|     let rectData = [];
 | |
| 
 | |
|     let ranges = this.iterator._previousRanges;
 | |
|     for (let range of ranges) {
 | |
|       let rectsAndTexts = this.highlighter._getRangeRectsAndTexts(range);
 | |
|       rectData.push({ text: range.toString(), rectsAndTexts });
 | |
|     }
 | |
| 
 | |
|     return rectData;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * highlightResults
 | |
|    *
 | |
|    * Highlights range(s) found in previous browser.find.find.
 | |
|    *
 | |
|    * @param {object} params - may contain any of the following properties:
 | |
|    *   all of which are optional:
 | |
|    *   {number} rangeIndex -
 | |
|    *            Found range to be highlighted held in API's ranges array for the tabId.
 | |
|    *            Default highlights all ranges.
 | |
|    *   {number} tabId - Tab to highlight.  Defaults to the active tab.
 | |
|    *   {boolean} noScroll - Don't scroll to highlighted item.
 | |
|    *
 | |
|    * @returns {string} - a string describing the resulting status of the highlighting,
 | |
|    *   which will be used as criteria for resolving or rejecting the promise.
 | |
|    *   This can be:
 | |
|    *   "Success" - Highlighting succeeded.
 | |
|    *   "OutOfRange" - The index supplied was out of range.
 | |
|    *   "NoResults" - There were no search results to highlight.
 | |
|    */
 | |
|   highlightResults(params) {
 | |
|     let { rangeIndex, noScroll } = params;
 | |
| 
 | |
|     this.highlighter.highlight(false);
 | |
|     let ranges = this.iterator._previousRanges;
 | |
| 
 | |
|     let status = "Success";
 | |
| 
 | |
|     if (ranges.length) {
 | |
|       if (typeof rangeIndex == "number") {
 | |
|         if (rangeIndex < ranges.length) {
 | |
|           let foundRange = ranges[rangeIndex];
 | |
|           this.highlighter.highlightRange(foundRange);
 | |
| 
 | |
|           if (!noScroll) {
 | |
|             let node = foundRange.startContainer;
 | |
|             let editableNode = this.highlighter._getEditableNode(node);
 | |
|             let controller = editableNode
 | |
|               ? editableNode.editor.selectionController
 | |
|               : this.finder._getSelectionController(node.ownerGlobal);
 | |
| 
 | |
|             controller.scrollSelectionIntoView(
 | |
|               controller.SELECTION_FIND,
 | |
|               controller.SELECTION_ON,
 | |
|               controller.SCROLL_CENTER_VERTICALLY
 | |
|             );
 | |
|           }
 | |
|         } else {
 | |
|           status = "OutOfRange";
 | |
|         }
 | |
|       } else {
 | |
|         for (let range of ranges) {
 | |
|           this.highlighter.highlightRange(range);
 | |
|         }
 | |
|       }
 | |
|     } else {
 | |
|       status = "NoResults";
 | |
|     }
 | |
| 
 | |
|     return status;
 | |
|   }
 | |
| }
 | 
