Source code

Revision control

Copy as Markdown

Other Tools

/* 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, {
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
UrlbarUtils: "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs",
UrlbarTokenizer:
"moz-src:///browser/components/urlbar/UrlbarTokenizer.sys.mjs",
});
export const MENTION_TYPE = /** @type {const} */ ({
TAB_OPEN: "TAB_OPEN",
TAB_RECENTLY_CLOSED: "TAB_RECENTLY_CLOSED",
});
/**
* @typedef {object} TabResult
* @property {string} url - Tab URL
* @property {string} title - Tab title
* @property {string} icon - Tab icon
* @property {(typeof MENTION_TYPE)[keyof typeof MENTION_TYPE]} type - Tab type
* @property {number} timestamp - Tab timestamp
*/
/**
* Mentions suggestions search for the Smartbar mentions panel.
*
* Fetches and filters open and recently closed tabs for display in the
* mentions suggestions panel in the Smartbar.
*
* NOTE: This provider is not compatible with UrlbarProvidersManager and only
* intended for standalone use within the Smartbar mentions feature.
*/
export class SmartbarMentionsPanelSearch {
#tabs = null;
constructor(browserWindow) {
this.#tabs = this.#getOpenAndClosedTabs(browserWindow);
}
/**
* Return filtered tabs sorted by recency.
*
* @param {string} searchString
* @returns {TabResult[]}
*/
startQuery(searchString) {
return this.#filterTabs(searchString).sort(
(a, b) => b.timestamp - a.timestamp
);
}
#filterTabs(searchString) {
if (!this.#tabs) {
return [];
}
if (!searchString) {
return this.#tabs;
}
const truncatedSearch = searchString.substring(
0,
lazy.UrlbarUtils.MAX_TEXT_LENGTH
);
const tokens = lazy.UrlbarTokenizer.tokenize({
searchString: truncatedSearch,
trimmedSearchString: truncatedSearch.trim(),
});
if (!tokens.length) {
return this.#tabs;
}
return this.#tabs.filter(tab => {
const normalizedUrl = this.#normalizeUrl(tab.url);
const searchText = `${tab.title} ${normalizedUrl}`
.substring(0, lazy.UrlbarUtils.MAX_TEXT_LENGTH)
.toLowerCase();
// Check if ALL tokens appear in the search text
return tokens.every(token =>
searchText.includes(token.value.toLowerCase())
);
});
}
#getOpenAndClosedTabs(browserWindow) {
const results = [];
// Open tabs
for (const tab of browserWindow.gBrowser.tabs) {
const url = tab.linkedBrowser?.currentURI?.spec;
if (!url) {
continue;
}
results.push({
url,
title: tab.label || url,
// Try to avoid fetching remote images. tab.image should normally be a data uri or a
// moz-remote-image data uri. If it's an http(s) url fallback to the page-icon protocol.
icon:
tab.image && !tab.image.startsWith("http")
? tab.image
: lazy.UrlbarUtils.getIconForUrl(url),
type: MENTION_TYPE.TAB_OPEN,
timestamp: tab.lastAccessed,
});
}
// Recently closed tabs
try {
const closedTabData =
lazy.SessionStore.getClosedTabDataForWindow(browserWindow);
for (const closedTab of closedTabData) {
const state = closedTab.state;
// Get the active history entry using the same pattern as in
// RecentlyClosedTabsAndWindowsMenuUtils.
const activeIndex = (state.index || state.entries.length) - 1;
if (activeIndex < 0 || !state.entries[activeIndex]) {
continue;
}
const entry = state.entries[activeIndex];
const url = entry.url;
if (!url) {
continue;
}
results.push({
url,
title: entry.title || url,
icon: lazy.UrlbarUtils.getIconForUrl(url),
type: MENTION_TYPE.TAB_RECENTLY_CLOSED,
timestamp: closedTab.closedAt,
});
}
} catch (e) {
console.error("Error getting recently closed tabs:", e);
}
return results;
}
#normalizeUrl(url) {
try {
const [stripped] = lazy.UrlbarUtils.stripPrefixAndTrim(url, {
stripHttp: true,
stripHttps: true,
trimSlash: true,
trimEmptyQuery: true,
trimEmptyHash: true,
});
return stripped;
} catch {
return url;
}
}
}