gecko-dev/browser/components/urlbar/UrlbarProviderOpenTabs.jsm

254 lines
7.9 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/. */
"use strict";
/**
* This module exports a provider, returning open tabs matches for the urlbar.
* It is also used to register and unregister open tabs.
*/
var EXPORTED_SYMBOLS = ["UrlbarProviderOpenTabs"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
UrlbarProvider: "resource:///modules/UrlbarUtils.jsm",
UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.jsm",
UrlbarResult: "resource:///modules/UrlbarResult.jsm",
UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
});
const PRIVATE_USER_CONTEXT_ID = -1;
/**
* Class used to create the provider.
*/
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 = 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(Cu.reportError);
}
}
}
);
/**
* 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(Cu.reportError);
}
/**
* 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(Cu.reportError);
}
}
}
/**
* 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 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 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 UrlbarProvidersManager.runInCriticalSection(async () => {
let conn = await 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 UrlbarProvidersManager.runInCriticalSection(async () => {
let conn = await PlacesUtils.promiseLargeCacheDBConnection();
await conn.executeCached(
`
UPDATE moz_openpages_temp
SET open_count = open_count - 1
WHERE url = :url
AND userContextId = :userContextId
`,
{ url, userContextId }
);
});
}