UrlbarProviderInputHistory.sys.mjs (8881B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /** 6 * This module exports a provider that offers input history (aka adaptive 7 * history) results. These results map typed search strings to Urlbar results. 8 * That way, a user can find a particular result again by typing the same 9 * string. 10 */ 11 12 import { 13 UrlbarProvider, 14 UrlbarUtils, 15 } from "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs"; 16 17 const lazy = {}; 18 19 ChromeUtils.defineESModuleGetters(lazy, { 20 PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", 21 UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs", 22 UrlbarProviderOpenTabs: 23 "moz-src:///browser/components/urlbar/UrlbarProviderOpenTabs.sys.mjs", 24 UrlbarResult: "moz-src:///browser/components/urlbar/UrlbarResult.sys.mjs", 25 }); 26 27 ChromeUtils.defineLazyGetter(lazy, "SQL_ADAPTIVE_QUERY", () => { 28 // Constants to support an alternative frecency algorithm. 29 const PAGES_USE_ALT_FRECENCY = 30 lazy.PlacesUtils.history.isAlternativeFrecencyEnabled; 31 const PAGES_FRECENCY_FIELD = PAGES_USE_ALT_FRECENCY 32 ? "alt_frecency" 33 : "frecency"; 34 return `/* do not warn (bug 487789) */ 35 SELECT h.url, 36 h.title, 37 EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) AS bookmarked, 38 ( SELECT title FROM moz_bookmarks WHERE fk = h.id AND title NOTNULL 39 ORDER BY lastModified DESC LIMIT 1 40 ) AS bookmark_title, 41 ( SELECT GROUP_CONCAT(t.title ORDER BY t.title) 42 FROM moz_bookmarks b 43 JOIN moz_bookmarks t ON t.id = +b.parent AND t.parent = :parent 44 WHERE b.fk = h.id 45 ) AS tags, 46 t.open_count, 47 t.userContextId, 48 h.last_visit_date 49 FROM ( 50 SELECT ROUND(MAX(use_count) * (1 + (input = :search_string)), 1) AS rank, 51 place_id 52 FROM moz_inputhistory 53 WHERE input BETWEEN :search_string AND :search_string || X'FFFF' 54 GROUP BY place_id 55 ) AS i 56 JOIN moz_places h ON h.id = i.place_id 57 LEFT JOIN moz_openpages_temp t 58 ON t.url = h.url 59 AND (t.userContextId = :userContextId OR (t.userContextId <> -1 AND :userContextId IS NULL)) 60 WHERE AUTOCOMPLETE_MATCH(NULL, h.url, 61 IFNULL(bookmark_title, h.title), tags, 62 h.visit_count, h.typed, bookmarked, 63 t.open_count, 64 :matchBehavior, :searchBehavior, 65 NULL) 66 ORDER BY rank DESC, ${PAGES_FRECENCY_FIELD} DESC 67 LIMIT :maxResults`; 68 }); 69 70 /** 71 * Class used to create the provider. 72 */ 73 export class UrlbarProviderInputHistory extends UrlbarProvider { 74 /** 75 * @returns {Values<typeof UrlbarUtils.PROVIDER_TYPE>} 76 */ 77 get type() { 78 return UrlbarUtils.PROVIDER_TYPE.PROFILE; 79 } 80 81 /** 82 * Whether this provider should be invoked for the given context. 83 * If this method returns false, the providers manager won't start a query 84 * with this provider, to save on resources. 85 * 86 * @param {UrlbarQueryContext} queryContext The query context object 87 */ 88 async isActive(queryContext) { 89 return ( 90 (lazy.UrlbarPrefs.get("suggest.history") || 91 lazy.UrlbarPrefs.get("suggest.bookmark") || 92 lazy.UrlbarPrefs.get("suggest.openpage")) && 93 !queryContext.searchMode 94 ); 95 } 96 97 /** 98 * Starts querying. 99 * 100 * @param {UrlbarQueryContext} queryContext 101 * @param {(provider: UrlbarProvider, result: UrlbarResult) => void} addCallback 102 * Callback invoked by the provider to add a new result. 103 */ 104 async startQuery(queryContext, addCallback) { 105 let instance = this.queryInstance; 106 107 let conn = await lazy.PlacesUtils.promiseLargeCacheDBConnection(); 108 if (instance != this.queryInstance) { 109 return; 110 } 111 112 let [query, params] = this._getAdaptiveQuery(queryContext); 113 let rows = await conn.executeCached(query, params); 114 if (instance != this.queryInstance) { 115 return; 116 } 117 118 for (let row of rows) { 119 const url = row.getResultByName("url"); 120 const openPageCount = row.getResultByName("open_count") || 0; 121 const historyTitle = row.getResultByName("title") || ""; 122 const bookmarked = row.getResultByName("bookmarked"); 123 const bookmarkTitle = bookmarked 124 ? row.getResultByName("bookmark_title") 125 : null; 126 const tags = row.getResultByName("tags") || ""; 127 let lastVisitPRTime = row.getResultByName("last_visit_date"); 128 let lastVisit = lastVisitPRTime 129 ? lazy.PlacesUtils.toDate(lastVisitPRTime).getTime() 130 : undefined; 131 132 let resultTitle = historyTitle; 133 if (openPageCount > 0 && lazy.UrlbarPrefs.get("suggest.openpage")) { 134 if (url == queryContext.currentPage) { 135 // Don't suggest switching to the current page. 136 continue; 137 } 138 let userContextId = row.getResultByName("userContextId") || 0; 139 let result = new lazy.UrlbarResult({ 140 type: UrlbarUtils.RESULT_TYPE.TAB_SWITCH, 141 source: UrlbarUtils.RESULT_SOURCE.TABS, 142 payload: { 143 url, 144 title: resultTitle, 145 icon: UrlbarUtils.getIconForUrl(url), 146 userContextId, 147 lastVisit, 148 action: lazy.UrlbarPrefs.get("secondaryActions.switchToTab") 149 ? UrlbarUtils.createTabSwitchSecondaryAction(userContextId) 150 : undefined, 151 }, 152 highlights: { 153 url: UrlbarUtils.HIGHLIGHT.TYPED, 154 title: UrlbarUtils.HIGHLIGHT.TYPED, 155 }, 156 }); 157 addCallback(this, result); 158 continue; 159 } 160 161 let resultSource; 162 if (bookmarked && lazy.UrlbarPrefs.get("suggest.bookmark")) { 163 resultSource = UrlbarUtils.RESULT_SOURCE.BOOKMARKS; 164 resultTitle = bookmarkTitle || historyTitle; 165 } else if (lazy.UrlbarPrefs.get("suggest.history")) { 166 resultSource = UrlbarUtils.RESULT_SOURCE.HISTORY; 167 } else { 168 continue; 169 } 170 171 let resultTags = tags.split(",").filter(tag => { 172 let lowerCaseTag = tag.toLocaleLowerCase(); 173 return queryContext.tokens.some(token => 174 lowerCaseTag.includes(token.lowerCaseValue) 175 ); 176 }); 177 178 let isBlockable = resultSource == UrlbarUtils.RESULT_SOURCE.HISTORY; 179 180 let result = new lazy.UrlbarResult({ 181 type: UrlbarUtils.RESULT_TYPE.URL, 182 source: resultSource, 183 payload: { 184 url, 185 title: resultTitle, 186 tags: resultTags, 187 icon: UrlbarUtils.getIconForUrl(url), 188 isBlockable, 189 blockL10n: isBlockable 190 ? { id: "urlbar-result-menu-remove-from-history" } 191 : undefined, 192 helpUrl: isBlockable 193 ? Services.urlFormatter.formatURLPref("app.support.baseURL") + 194 "awesome-bar-result-menu" 195 : undefined, 196 lastVisit, 197 }, 198 highlights: { 199 url: UrlbarUtils.HIGHLIGHT.TYPED, 200 title: UrlbarUtils.HIGHLIGHT.TYPED, 201 tags: UrlbarUtils.HIGHLIGHT.TYPED, 202 }, 203 }); 204 205 addCallback(this, result); 206 } 207 } 208 209 onEngagement(queryContext, controller, details) { 210 let { result } = details; 211 if ( 212 details.selType == "dismiss" && 213 result.type == UrlbarUtils.RESULT_TYPE.URL 214 ) { 215 // Even if removing history normally also removes input history, that 216 // doesn't happen if the page is bookmarked, so we do remove input history 217 // regardless for this specific search term. 218 UrlbarUtils.removeInputHistory( 219 result.payload.url, 220 queryContext.searchString 221 ).catch(console.error); 222 // Remove browsing history for the page. 223 lazy.PlacesUtils.history.remove(result.payload.url).catch(console.error); 224 controller.removeResult(result); 225 } 226 } 227 228 /** 229 * Obtains the query to search for adaptive results. 230 * 231 * @param {UrlbarQueryContext} queryContext 232 * The current queryContext. 233 * @returns {Array} Contains the optimized query with which to search the 234 * database and an object containing the params to bound. 235 */ 236 _getAdaptiveQuery(queryContext) { 237 return [ 238 lazy.SQL_ADAPTIVE_QUERY, 239 { 240 parent: lazy.PlacesUtils.tagsFolderId, 241 search_string: queryContext.lowerCaseSearchString, 242 matchBehavior: Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE, 243 searchBehavior: lazy.UrlbarPrefs.get("defaultBehavior"), 244 userContextId: lazy.UrlbarPrefs.get("switchTabs.searchAllContainers") 245 ? lazy.UrlbarProviderOpenTabs.getUserContextIdForOpenPagesTable( 246 null, 247 queryContext.isPrivate 248 ) 249 : queryContext.userContextId, 250 maxResults: queryContext.maxResults, 251 }, 252 ]; 253 } 254 }