forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			256 lines
		
	
	
	
		
			8.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			256 lines
		
	
	
	
		
			8.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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/. */
 | |
| 
 | |
| /**
 | |
|  * This module exports a provider that offers input history (aka adaptive
 | |
|  * history) results. These results map typed search strings to Urlbar results.
 | |
|  * That way, a user can find a particular result again by typing the same
 | |
|  * string.
 | |
|  */
 | |
| 
 | |
| import {
 | |
|   UrlbarProvider,
 | |
|   UrlbarUtils,
 | |
| } from "resource:///modules/UrlbarUtils.sys.mjs";
 | |
| 
 | |
| const lazy = {};
 | |
| 
 | |
| ChromeUtils.defineESModuleGetters(lazy, {
 | |
|   PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
 | |
|   UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
 | |
|   UrlbarProviderOpenTabs: "resource:///modules/UrlbarProviderOpenTabs.sys.mjs",
 | |
|   UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs",
 | |
| });
 | |
| 
 | |
| // Sqlite result row index constants.
 | |
| const QUERYINDEX = {
 | |
|   URL: 0,
 | |
|   TITLE: 1,
 | |
|   BOOKMARKED: 2,
 | |
|   BOOKMARKTITLE: 3,
 | |
|   TAGS: 4,
 | |
|   SWITCHTAB: 8,
 | |
| };
 | |
| 
 | |
| // Constants to support an alternative frecency algorithm.
 | |
| const PAGES_USE_ALT_FRECENCY = Services.prefs.getBoolPref(
 | |
|   "places.frecency.pages.alternative.featureGate",
 | |
|   false
 | |
| );
 | |
| const PAGES_FRECENCY_FIELD = PAGES_USE_ALT_FRECENCY
 | |
|   ? "alt_frecency"
 | |
|   : "frecency";
 | |
| 
 | |
| // This SQL query fragment provides the following:
 | |
| //   - whether the entry is bookmarked (QUERYINDEX_BOOKMARKED)
 | |
| //   - the bookmark title, if it is a bookmark (QUERYINDEX_BOOKMARKTITLE)
 | |
| //   - the tags associated with a bookmarked entry (QUERYINDEX_TAGS)
 | |
| const SQL_BOOKMARK_TAGS_FRAGMENT = `EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) AS bookmarked,
 | |
|    ( SELECT title FROM moz_bookmarks WHERE fk = h.id AND title NOTNULL
 | |
|      ORDER BY lastModified DESC LIMIT 1
 | |
|    ) AS btitle,
 | |
|    ( SELECT GROUP_CONCAT(t.title ORDER BY t.title)
 | |
|      FROM moz_bookmarks b
 | |
|      JOIN moz_bookmarks t ON t.id = +b.parent AND t.parent = :parent
 | |
|      WHERE b.fk = h.id
 | |
|    ) AS tags`;
 | |
| 
 | |
| const SQL_ADAPTIVE_QUERY = `/* do not warn (bug 487789) */
 | |
|    SELECT h.url, h.title, ${SQL_BOOKMARK_TAGS_FRAGMENT}, h.visit_count,
 | |
|           h.typed, h.id, t.open_count, ${PAGES_FRECENCY_FIELD}
 | |
|    FROM (
 | |
|      SELECT ROUND(MAX(use_count) * (1 + (input = :search_string)), 1) AS rank,
 | |
|             place_id
 | |
|      FROM moz_inputhistory
 | |
|      WHERE input BETWEEN :search_string AND :search_string || X'FFFF'
 | |
|      GROUP BY place_id
 | |
|    ) AS i
 | |
|    JOIN moz_places h ON h.id = i.place_id
 | |
|    LEFT JOIN moz_openpages_temp t
 | |
|           ON t.url = h.url
 | |
|          AND t.userContextId = :userContextId
 | |
|    WHERE AUTOCOMPLETE_MATCH(NULL, h.url,
 | |
|                             IFNULL(btitle, h.title), tags,
 | |
|                             h.visit_count, h.typed, bookmarked,
 | |
|                             t.open_count,
 | |
|                             :matchBehavior, :searchBehavior,
 | |
|                             NULL)
 | |
|    ORDER BY rank DESC, ${PAGES_FRECENCY_FIELD} DESC
 | |
|    LIMIT :maxResults`;
 | |
| 
 | |
| /**
 | |
|  * Class used to create the provider.
 | |
|  */
 | |
| class ProviderInputHistory extends UrlbarProvider {
 | |
|   /**
 | |
|    * Unique name for the provider, used by the context to filter on providers.
 | |
|    *
 | |
|    * @returns {string}
 | |
|    */
 | |
|   get name() {
 | |
|     return "InputHistory";
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * The type of the provider, must be one of UrlbarUtils.PROVIDER_TYPE.
 | |
|    *
 | |
|    * @returns {UrlbarUtils.PROVIDER_TYPE}
 | |
|    */
 | |
|   get type() {
 | |
|     return UrlbarUtils.PROVIDER_TYPE.PROFILE;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Whether this provider should be invoked for the given context.
 | |
|    * If this method returns false, the providers manager won't start a query
 | |
|    * with this provider, to save on resources.
 | |
|    *
 | |
|    * @param {UrlbarQueryContext} queryContext The query context object
 | |
|    * @returns {boolean} Whether this provider should be invoked for the search.
 | |
|    */
 | |
|   isActive(queryContext) {
 | |
|     return (
 | |
|       (lazy.UrlbarPrefs.get("suggest.history") ||
 | |
|         lazy.UrlbarPrefs.get("suggest.bookmark") ||
 | |
|         lazy.UrlbarPrefs.get("suggest.openpage")) &&
 | |
|       !queryContext.searchMode
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Starts querying. Extended classes should return a Promise resolved when the
 | |
|    * provider is done searching AND returning results.
 | |
|    *
 | |
|    * @param {UrlbarQueryContext} queryContext The query context object
 | |
|    * @param {Function} addCallback Callback invoked by the provider to add a new
 | |
|    *        result. A UrlbarResult should be passed to it.
 | |
|    * @returns {Promise}
 | |
|    */
 | |
|   async startQuery(queryContext, addCallback) {
 | |
|     let instance = this.queryInstance;
 | |
| 
 | |
|     let conn = await lazy.PlacesUtils.promiseLargeCacheDBConnection();
 | |
|     if (instance != this.queryInstance) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let [query, params] = this._getAdaptiveQuery(queryContext);
 | |
|     let rows = await conn.executeCached(query, params);
 | |
|     if (instance != this.queryInstance) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     for (let row of rows) {
 | |
|       const url = row.getResultByIndex(QUERYINDEX.URL);
 | |
|       const openPageCount = row.getResultByIndex(QUERYINDEX.SWITCHTAB) || 0;
 | |
|       const historyTitle = row.getResultByIndex(QUERYINDEX.TITLE) || "";
 | |
|       const bookmarked = row.getResultByIndex(QUERYINDEX.BOOKMARKED);
 | |
|       const bookmarkTitle = bookmarked
 | |
|         ? row.getResultByIndex(QUERYINDEX.BOOKMARKTITLE)
 | |
|         : null;
 | |
|       const tags = row.getResultByIndex(QUERYINDEX.TAGS) || "";
 | |
| 
 | |
|       let resultTitle = historyTitle;
 | |
|       if (openPageCount > 0 && lazy.UrlbarPrefs.get("suggest.openpage")) {
 | |
|         if (url == queryContext.currentPage) {
 | |
|           // Don't suggest switching to the current page.
 | |
|           continue;
 | |
|         }
 | |
|         let result = new lazy.UrlbarResult(
 | |
|           UrlbarUtils.RESULT_TYPE.TAB_SWITCH,
 | |
|           UrlbarUtils.RESULT_SOURCE.TABS,
 | |
|           ...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
 | |
|             url: [url, UrlbarUtils.HIGHLIGHT.TYPED],
 | |
|             title: [resultTitle, UrlbarUtils.HIGHLIGHT.TYPED],
 | |
|             icon: UrlbarUtils.getIconForUrl(url),
 | |
|             userContextId: queryContext.userContextId || 0,
 | |
|           })
 | |
|         );
 | |
|         addCallback(this, result);
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       let resultSource;
 | |
|       if (bookmarked && lazy.UrlbarPrefs.get("suggest.bookmark")) {
 | |
|         resultSource = UrlbarUtils.RESULT_SOURCE.BOOKMARKS;
 | |
|         resultTitle = bookmarkTitle || historyTitle;
 | |
|       } else if (lazy.UrlbarPrefs.get("suggest.history")) {
 | |
|         resultSource = UrlbarUtils.RESULT_SOURCE.HISTORY;
 | |
|       } else {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       let resultTags = tags.split(",").filter(tag => {
 | |
|         let lowerCaseTag = tag.toLocaleLowerCase();
 | |
|         return queryContext.tokens.some(token =>
 | |
|           lowerCaseTag.includes(token.lowerCaseValue)
 | |
|         );
 | |
|       });
 | |
| 
 | |
|       let result = new lazy.UrlbarResult(
 | |
|         UrlbarUtils.RESULT_TYPE.URL,
 | |
|         resultSource,
 | |
|         ...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
 | |
|           url: [url, UrlbarUtils.HIGHLIGHT.TYPED],
 | |
|           title: [resultTitle, UrlbarUtils.HIGHLIGHT.TYPED],
 | |
|           tags: [resultTags, UrlbarUtils.HIGHLIGHT.TYPED],
 | |
|           icon: UrlbarUtils.getIconForUrl(url),
 | |
|         })
 | |
|       );
 | |
| 
 | |
|       addCallback(this, result);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   onEngagement(state, queryContext, details, controller) {
 | |
|     let { result } = details;
 | |
|     if (result?.providerName != this.name) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (
 | |
|       details.selType == "dismiss" &&
 | |
|       result.type == UrlbarUtils.RESULT_TYPE.URL
 | |
|     ) {
 | |
|       // Even if removing history normally also removes input history, that
 | |
|       // doesn't happen if the page is bookmarked, so we do remove input history
 | |
|       // regardless for this specific search term.
 | |
|       UrlbarUtils.removeInputHistory(
 | |
|         result.payload.url,
 | |
|         queryContext.searchString
 | |
|       ).catch(console.error);
 | |
|       // Remove browsing history for the page.
 | |
|       lazy.PlacesUtils.history.remove(result.payload.url).catch(console.error);
 | |
|       controller.removeResult(result);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Obtains the query to search for adaptive results.
 | |
|    *
 | |
|    * @param {UrlbarQueryContext} queryContext
 | |
|    *   The current queryContext.
 | |
|    * @returns {Array} Contains the optimized query with which to search the
 | |
|    *  database and an object containing the params to bound.
 | |
|    */
 | |
|   _getAdaptiveQuery(queryContext) {
 | |
|     return [
 | |
|       SQL_ADAPTIVE_QUERY,
 | |
|       {
 | |
|         parent: lazy.PlacesUtils.tagsFolderId,
 | |
|         search_string: queryContext.searchString.toLowerCase(),
 | |
|         matchBehavior: Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE,
 | |
|         searchBehavior: lazy.UrlbarPrefs.get("defaultBehavior"),
 | |
|         userContextId:
 | |
|           lazy.UrlbarProviderOpenTabs.getUserContextIdForOpenPagesTable(
 | |
|             queryContext.userContextId,
 | |
|             queryContext.isPrivate
 | |
|           ),
 | |
|         maxResults: queryContext.maxResults,
 | |
|       },
 | |
|     ];
 | |
|   }
 | |
| }
 | |
| 
 | |
| export var UrlbarProviderInputHistory = new ProviderInputHistory();
 | 
