forked from mirrors/gecko-dev
		
	 2ffde1e92f
			
		
	
	
		2ffde1e92f
		
	
	
	
	
		
			
			Mainly automated changes. Some manual ESLint fixes and whitespace cleanup. Differential Revision: https://phabricator.services.mozilla.com/D158452
		
			
				
	
	
		
			272 lines
		
	
	
	
		
			8.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			272 lines
		
	
	
	
		
			8.7 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/. */
 | |
| 
 | |
| /* global tabTracker */
 | |
| "use strict";
 | |
| 
 | |
| ChromeUtils.defineESModuleGetters(this, {
 | |
|   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
 | |
| });
 | |
| 
 | |
| var { ExtensionError } = ExtensionUtils;
 | |
| 
 | |
| // A mapping of top-level ExtFind actors to arrays of results in each subframe.
 | |
| let findResults = new WeakMap();
 | |
| 
 | |
| function getActorForBrowsingContext(browsingContext) {
 | |
|   let windowGlobal = browsingContext.currentWindowGlobal;
 | |
|   return windowGlobal ? windowGlobal.getActor("ExtFind") : null;
 | |
| }
 | |
| 
 | |
| function getTopLevelActor(browser) {
 | |
|   return getActorForBrowsingContext(browser.browsingContext);
 | |
| }
 | |
| 
 | |
| function gatherActors(browsingContext) {
 | |
|   let list = [];
 | |
| 
 | |
|   let actor = getActorForBrowsingContext(browsingContext);
 | |
|   if (actor) {
 | |
|     list.push({ actor, result: null });
 | |
|   }
 | |
| 
 | |
|   let children = browsingContext.children;
 | |
|   for (let child of children) {
 | |
|     list.push(...gatherActors(child));
 | |
|   }
 | |
| 
 | |
|   return list;
 | |
| }
 | |
| 
 | |
| function mergeFindResults(params, list) {
 | |
|   let finalResult = {
 | |
|     count: 0,
 | |
|   };
 | |
| 
 | |
|   if (params.includeRangeData) {
 | |
|     finalResult.rangeData = [];
 | |
|   }
 | |
|   if (params.includeRectData) {
 | |
|     finalResult.rectData = [];
 | |
|   }
 | |
| 
 | |
|   let currentFramePos = -1;
 | |
|   for (let item of list) {
 | |
|     if (item.result.count == 0) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     // The framePos is incremented for each different document that has matches.
 | |
|     currentFramePos++;
 | |
| 
 | |
|     finalResult.count += item.result.count;
 | |
|     if (params.includeRangeData && item.result.rangeData) {
 | |
|       for (let range of item.result.rangeData) {
 | |
|         range.framePos = currentFramePos;
 | |
|       }
 | |
| 
 | |
|       finalResult.rangeData.push(...item.result.rangeData);
 | |
|     }
 | |
| 
 | |
|     if (params.includeRectData && item.result.rectData) {
 | |
|       finalResult.rectData.push(...item.result.rectData);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return finalResult;
 | |
| }
 | |
| 
 | |
| function sendMessageToAllActors(browser, message, params) {
 | |
|   for (let { actor } of gatherActors(browser.browsingContext)) {
 | |
|     actor.sendAsyncMessage("ext-Finder:" + message, params);
 | |
|   }
 | |
| }
 | |
| 
 | |
| async function getFindResultsForActor(findContext, message, params) {
 | |
|   findContext.result = await findContext.actor.sendQuery(
 | |
|     "ext-Finder:" + message,
 | |
|     params
 | |
|   );
 | |
|   return findContext;
 | |
| }
 | |
| 
 | |
| function queryAllActors(browser, message, params) {
 | |
|   let promises = [];
 | |
|   for (let findContext of gatherActors(browser.browsingContext)) {
 | |
|     promises.push(getFindResultsForActor(findContext, message, params));
 | |
|   }
 | |
|   return Promise.all(promises);
 | |
| }
 | |
| 
 | |
| async function collectFindResults(browser, findResults, params) {
 | |
|   let results = await queryAllActors(browser, "CollectResults", params);
 | |
|   findResults.set(getTopLevelActor(browser), results);
 | |
|   return mergeFindResults(params, results);
 | |
| }
 | |
| 
 | |
| async function runHighlight(browser, params) {
 | |
|   let hasResults = false;
 | |
|   let foundResults = false;
 | |
|   let list = findResults.get(getTopLevelActor(browser));
 | |
|   if (!list) {
 | |
|     return Promise.reject({ message: "no search results to highlight" });
 | |
|   }
 | |
| 
 | |
|   let highlightPromises = [];
 | |
| 
 | |
|   let index = params.rangeIndex;
 | |
|   const highlightAll = typeof index != "number";
 | |
| 
 | |
|   for (let c = 0; c < list.length; c++) {
 | |
|     if (list[c].result.count) {
 | |
|       hasResults = true;
 | |
|     }
 | |
| 
 | |
|     let actor = list[c].actor;
 | |
|     if (highlightAll) {
 | |
|       // Highlight all ranges.
 | |
|       highlightPromises.push(
 | |
|         actor.sendQuery("ext-Finder:HighlightResults", params)
 | |
|       );
 | |
|     } else if (!foundResults && index < list[c].result.count) {
 | |
|       foundResults = true;
 | |
|       params.rangeIndex = index;
 | |
|       highlightPromises.push(
 | |
|         actor.sendQuery("ext-Finder:HighlightResults", params)
 | |
|       );
 | |
|     } else {
 | |
|       highlightPromises.push(
 | |
|         actor.sendQuery("ext-Finder:ClearHighlighting", params)
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     index -= list[c].result.count;
 | |
|   }
 | |
| 
 | |
|   let responses = await Promise.all(highlightPromises);
 | |
|   if (hasResults) {
 | |
|     if (responses.includes("OutOfRange") || index >= 0) {
 | |
|       return Promise.reject({ message: "index supplied was out of range" });
 | |
|     } else if (responses.includes("Success")) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return Promise.reject({ message: "no search results to highlight" });
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * runFindOperation
 | |
|  * Utility for `find` and `highlightResults`.
 | |
|  *
 | |
|  * @param {BaseContext} context - context the find operation runs in.
 | |
|  * @param {object} params - params to pass to message sender.
 | |
|  * @param {string} message - identifying component of message name.
 | |
|  *
 | |
|  * @returns {Promise} a promise that will be resolved or rejected based on the
 | |
|  *          data received by the message listener.
 | |
|  */
 | |
| function runFindOperation(context, params, message) {
 | |
|   let { tabId } = params;
 | |
|   let tab = tabId ? tabTracker.getTab(tabId) : tabTracker.activeTab;
 | |
|   let browser = tab.linkedBrowser;
 | |
|   tabId = tabId || tabTracker.getId(tab);
 | |
|   if (
 | |
|     !context.privateBrowsingAllowed &&
 | |
|     PrivateBrowsingUtils.isBrowserPrivate(browser)
 | |
|   ) {
 | |
|     return Promise.reject({ message: `Unable to search: ${tabId}` });
 | |
|   }
 | |
|   // We disallow find in about: urls.
 | |
|   if (
 | |
|     tab.linkedBrowser.contentPrincipal.isSystemPrincipal ||
 | |
|     (["about", "chrome", "resource"].includes(
 | |
|       tab.linkedBrowser.currentURI.scheme
 | |
|     ) &&
 | |
|       tab.linkedBrowser.currentURI.spec != "about:blank")
 | |
|   ) {
 | |
|     return Promise.reject({ message: `Unable to search: ${tabId}` });
 | |
|   }
 | |
| 
 | |
|   if (message == "HighlightResults") {
 | |
|     return runHighlight(browser, params);
 | |
|   } else if (message == "CollectResults") {
 | |
|     // Remove prior highlights before starting a new find operation.
 | |
|     findResults.delete(getTopLevelActor(browser));
 | |
|     return collectFindResults(browser, findResults, params);
 | |
|   }
 | |
| }
 | |
| 
 | |
| this.find = class extends ExtensionAPI {
 | |
|   getAPI(context) {
 | |
|     return {
 | |
|       find: {
 | |
|         /**
 | |
|          * browser.find.find
 | |
|          * Searches document and its frames for a given queryphrase and stores all found
 | |
|          * Range objects in an array accessible by other browser.find methods.
 | |
|          *
 | |
|          * @param {string} queryphrase - The string to search for.
 | |
|          * @param {object} params optional - may contain any of the following properties,
 | |
|          *   all of which are optional:
 | |
|          *   {number} tabId - Tab to query.  Defaults to the active tab.
 | |
|          *   {boolean} caseSensitive - Highlight only ranges with case sensitive match.
 | |
|          *   {boolean} entireWord - Highlight only ranges that match entire word.
 | |
|          *   {boolean} includeRangeData - Whether to return range data.
 | |
|          *   {boolean} includeRectData - Whether to return rectangle data.
 | |
|          *
 | |
|          * @returns {object} data received by the message listener 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.
 | |
|          */
 | |
|         find(queryphrase, params) {
 | |
|           params = params || {};
 | |
|           params.queryphrase = queryphrase;
 | |
|           return runFindOperation(context, params, "CollectResults");
 | |
|         },
 | |
| 
 | |
|         /**
 | |
|          * browser.find.highlightResults
 | |
|          * Highlights range(s) found in previous browser.find.find.
 | |
|          *
 | |
|          * @param {object} params optional - may contain any of the following properties,
 | |
|          *   all of which are optional:
 | |
|          *   {number} rangeIndex - Found range to be highlighted. Default highlights all ranges.
 | |
|          *   {number} tabId - Tab to highlight.  Defaults to the active tab.
 | |
|          *   {boolean} noScroll - Don't scroll to highlighted item.
 | |
|          *
 | |
|          * @returns {string} - data received by the message listener that may be:
 | |
|          *   "Success" - Highlighting succeeded.
 | |
|          *   "OutOfRange" - The index supplied was out of range.
 | |
|          *   "NoResults" - There were no search results to highlight.
 | |
|          */
 | |
|         highlightResults(params) {
 | |
|           params = params || {};
 | |
|           return runFindOperation(context, params, "HighlightResults");
 | |
|         },
 | |
| 
 | |
|         /**
 | |
|          * browser.find.removeHighlighting
 | |
|          * Removes all highlighting from previous search.
 | |
|          *
 | |
|          * @param {number} tabId optional
 | |
|          *                 Tab to clear highlighting in.  Defaults to the active tab.
 | |
|          */
 | |
|         removeHighlighting(tabId) {
 | |
|           let tab = tabId ? tabTracker.getTab(tabId) : tabTracker.activeTab;
 | |
|           if (
 | |
|             !context.privateBrowsingAllowed &&
 | |
|             PrivateBrowsingUtils.isBrowserPrivate(tab.linkedBrowser)
 | |
|           ) {
 | |
|             throw new ExtensionError(`Invalid tab ID: ${tabId}`);
 | |
|           }
 | |
|           sendMessageToAllActors(tab.linkedBrowser, "ClearHighlighting", {});
 | |
|         },
 | |
|       },
 | |
|     };
 | |
|   }
 | |
| };
 |