summaryrefslogtreecommitdiffstats
path: root/browser/components/urlbar/UrlbarProviderOpenTabs.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/urlbar/UrlbarProviderOpenTabs.sys.mjs')
-rw-r--r--browser/components/urlbar/UrlbarProviderOpenTabs.sys.mjs260
1 files changed, 260 insertions, 0 deletions
diff --git a/browser/components/urlbar/UrlbarProviderOpenTabs.sys.mjs b/browser/components/urlbar/UrlbarProviderOpenTabs.sys.mjs
new file mode 100644
index 0000000000..5994a295ed
--- /dev/null
+++ b/browser/components/urlbar/UrlbarProviderOpenTabs.sys.mjs
@@ -0,0 +1,260 @@
+/* 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, returning open tabs matches for the urlbar.
+ * It is also used to register and unregister open tabs.
+ */
+
+import {
+ UrlbarProvider,
+ UrlbarUtils,
+} from "resource:///modules/UrlbarUtils.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.sys.mjs",
+ UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs",
+});
+
+const PRIVATE_USER_CONTEXT_ID = -1;
+
+/**
+ * Class used to create the provider.
+ */
+export class UrlbarProviderOpenTabs extends UrlbarProvider {
+ constructor() {
+ super();
+ }
+
+ /**
+ * Returns the name of this provider.
+ *
+ * @returns {string} the name of this provider.
+ */
+ get name() {
+ return "OpenTabs";
+ }
+
+ /**
+ * Returns the type of this provider.
+ *
+ * @returns {integer} one of the types from 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) {
+ // For now we don't actually use this provider to query open tabs, instead
+ // we join the temp table in UrlbarProviderPlaces.
+ return false;
+ }
+
+ /**
+ * Tracks whether the memory tables have been initialized yet. Until this
+ * happens tabs are only stored in openTabs and later copied over to the
+ * memory table.
+ */
+ static memoryTableInitialized = false;
+
+ /**
+ * Maps the open tabs by userContextId.
+ */
+ static _openTabs = new Map();
+
+ /**
+ * Return urls that is opening on given user context id.
+ *
+ * @param {integer} userContextId Containers user context id
+ * @param {boolean} isInPrivateWindow In private browsing window or not
+ * @returns {Array} urls
+ */
+ static getOpenTabs(userContextId, isInPrivateWindow) {
+ userContextId = UrlbarProviderOpenTabs.getUserContextIdForOpenPagesTable(
+ userContextId,
+ isInPrivateWindow
+ );
+ return UrlbarProviderOpenTabs._openTabs.get(userContextId);
+ }
+
+ /**
+ * Return userContextId that will be used in moz_openpages_temp table.
+ *
+ * @param {integer} userContextId Containers user context id
+ * @param {boolean} isInPrivateWindow In private browsing window or not
+ * @returns {interger} userContextId
+ */
+ static getUserContextIdForOpenPagesTable(userContextId, isInPrivateWindow) {
+ return isInPrivateWindow ? PRIVATE_USER_CONTEXT_ID : userContextId;
+ }
+
+ /**
+ * Copy over cached open tabs to the memory table once the Urlbar
+ * connection has been initialized.
+ */
+ static promiseDBPopulated =
+ lazy.PlacesUtils.largeCacheDBConnDeferred.promise.then(async () => {
+ // Must be set before populating.
+ UrlbarProviderOpenTabs.memoryTableInitialized = true;
+ // Populate the table with the current cached tabs.
+ for (let [userContextId, urls] of UrlbarProviderOpenTabs._openTabs) {
+ for (let url of urls) {
+ await addToMemoryTable(url, userContextId).catch(console.error);
+ }
+ }
+ });
+
+ /**
+ * Registers a tab as open.
+ *
+ * @param {string} url Address of the tab
+ * @param {integer} userContextId Containers user context id
+ * @param {boolean} isInPrivateWindow In private browsing window or not
+ */
+ static async registerOpenTab(url, userContextId, isInPrivateWindow) {
+ userContextId = UrlbarProviderOpenTabs.getUserContextIdForOpenPagesTable(
+ userContextId,
+ isInPrivateWindow
+ );
+
+ if (!UrlbarProviderOpenTabs._openTabs.has(userContextId)) {
+ UrlbarProviderOpenTabs._openTabs.set(userContextId, []);
+ }
+ UrlbarProviderOpenTabs._openTabs.get(userContextId).push(url);
+ await addToMemoryTable(url, userContextId).catch(console.error);
+ }
+
+ /**
+ * Unregisters a previously registered open tab.
+ *
+ * @param {string} url Address of the tab
+ * @param {integer} userContextId Containers user context id
+ * @param {boolean} isInPrivateWindow In private browsing window or not
+ */
+ static async unregisterOpenTab(url, userContextId, isInPrivateWindow) {
+ userContextId = UrlbarProviderOpenTabs.getUserContextIdForOpenPagesTable(
+ userContextId,
+ isInPrivateWindow
+ );
+
+ let openTabs = UrlbarProviderOpenTabs._openTabs.get(userContextId);
+ if (openTabs) {
+ let index = openTabs.indexOf(url);
+ if (index != -1) {
+ openTabs.splice(index, 1);
+ await removeFromMemoryTable(url, userContextId).catch(console.error);
+ }
+ }
+ }
+
+ /**
+ * Starts querying.
+ *
+ * @param {object} queryContext The query context object
+ * @param {Function} addCallback Callback invoked by the provider to add a new
+ * match.
+ * @returns {Promise} resolved when the query stops.
+ */
+ async startQuery(queryContext, addCallback) {
+ // Note: this is not actually expected to be used as an internal provider,
+ // because normal history search will already coalesce with the open tabs
+ // temp table to return proper frecency.
+ // TODO:
+ // * properly search and handle tokens, this is just a mock for now.
+ let instance = this.queryInstance;
+ let conn = await lazy.PlacesUtils.promiseLargeCacheDBConnection();
+ await UrlbarProviderOpenTabs.promiseDBPopulated;
+ await conn.executeCached(
+ `
+ SELECT url, userContextId
+ FROM moz_openpages_temp
+ `,
+ {},
+ (row, cancel) => {
+ if (instance != this.queryInstance) {
+ cancel();
+ return;
+ }
+ addCallback(
+ this,
+ new lazy.UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.TAB_SWITCH,
+ UrlbarUtils.RESULT_SOURCE.TABS,
+ {
+ url: row.getResultByName("url"),
+ userContextId: row.getResultByName("userContextId"),
+ }
+ )
+ );
+ }
+ );
+ }
+}
+
+/**
+ * Adds an open page to the memory table.
+ *
+ * @param {string} url Address of the page
+ * @param {number} userContextId Containers user context id
+ * @returns {Promise} resolved after the addition.
+ */
+async function addToMemoryTable(url, userContextId) {
+ if (!UrlbarProviderOpenTabs.memoryTableInitialized) {
+ return;
+ }
+ await lazy.UrlbarProvidersManager.runInCriticalSection(async () => {
+ let conn = await lazy.PlacesUtils.promiseLargeCacheDBConnection();
+ await conn.executeCached(
+ `
+ INSERT OR REPLACE INTO moz_openpages_temp (url, userContextId, open_count)
+ VALUES ( :url,
+ :userContextId,
+ IFNULL( ( SELECT open_count + 1
+ FROM moz_openpages_temp
+ WHERE url = :url
+ AND userContextId = :userContextId ),
+ 1
+ )
+ )
+ `,
+ { url, userContextId }
+ );
+ });
+}
+
+/**
+ * Removes an open page from the memory table.
+ *
+ * @param {string} url Address of the page
+ * @param {number} userContextId Containers user context id
+ * @returns {Promise} resolved after the removal.
+ */
+async function removeFromMemoryTable(url, userContextId) {
+ if (!UrlbarProviderOpenTabs.memoryTableInitialized) {
+ return;
+ }
+ await lazy.UrlbarProvidersManager.runInCriticalSection(async () => {
+ let conn = await lazy.PlacesUtils.promiseLargeCacheDBConnection();
+ await conn.executeCached(
+ `
+ UPDATE moz_openpages_temp
+ SET open_count = open_count - 1
+ WHERE url = :url
+ AND userContextId = :userContextId
+ `,
+ { url, userContextId }
+ );
+ });
+}