зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1495183 - Create a first muxer implementation. r=adw
Differential Revision: https://phabricator.services.mozilla.com/D13552 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
2edebf41a2
Коммит
70a7e4ce9e
|
@ -0,0 +1,87 @@
|
|||
/* 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 component used to sort matches in a QueryContext.
|
||||
*/
|
||||
|
||||
var EXPORTED_SYMBOLS = ["UrlbarMuxerUnifiedComplete"];
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
Log: "resource://gre/modules/Log.jsm",
|
||||
UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
|
||||
UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "logger", () =>
|
||||
Log.repository.getLogger("Places.Urlbar.UrlbarMuxerUnifiedComplete"));
|
||||
|
||||
const MATCH_TYPE_TO_GROUP = new Map([
|
||||
[ UrlbarUtils.MATCH_TYPE.TAB_SWITCH, UrlbarUtils.MATCH_GROUP.GENERAL ],
|
||||
[ UrlbarUtils.MATCH_TYPE.SEARCH, UrlbarUtils.MATCH_GROUP.SUGGESTION ],
|
||||
[ UrlbarUtils.MATCH_TYPE.URL, UrlbarUtils.MATCH_GROUP.GENERAL ],
|
||||
[ UrlbarUtils.MATCH_TYPE.KEYWORD, UrlbarUtils.MATCH_GROUP.GENERAL ],
|
||||
[ UrlbarUtils.MATCH_TYPE.OMNIBOX, UrlbarUtils.MATCH_GROUP.EXTENSION ],
|
||||
[ UrlbarUtils.MATCH_TYPE.REMOTE_TAB, UrlbarUtils.MATCH_GROUP.GENERAL ],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Class used to create a muxer.
|
||||
* The muxer receives and sorts matches in a QueryContext.
|
||||
*/
|
||||
class MuxerUnifiedComplete {
|
||||
constructor() {
|
||||
// Nothing.
|
||||
}
|
||||
|
||||
get name() {
|
||||
return "MuxerUnifiedComplete";
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts matches in the given QueryContext.
|
||||
* @param {object} context a QueryContext
|
||||
*/
|
||||
sort(context) {
|
||||
if (!context.results.length) {
|
||||
return;
|
||||
}
|
||||
// Check the first match, if it's a preselected search match, use search buckets.
|
||||
let firstMatch = context.results[0];
|
||||
let buckets = context.preselected &&
|
||||
firstMatch.type == UrlbarUtils.MATCH_TYPE.SEARCH ?
|
||||
UrlbarPrefs.get("matchBucketsSearch") :
|
||||
UrlbarPrefs.get("matchBuckets");
|
||||
logger.debug(`Buckets: ${buckets}`);
|
||||
let sortedMatches = [];
|
||||
for (let [group, count] of buckets) {
|
||||
// Search all the available matches and fill this bucket.
|
||||
for (let match of context.results) {
|
||||
if (count == 0) {
|
||||
// There's no more space in this bucket.
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle the heuristic result.
|
||||
if (group == UrlbarUtils.MATCH_GROUP.HEURISTIC &&
|
||||
match == firstMatch && context.preselected) {
|
||||
sortedMatches.push(match);
|
||||
count--;
|
||||
} else if (group == MATCH_TYPE_TO_GROUP.get(match.type)) {
|
||||
sortedMatches.push(match);
|
||||
count--;
|
||||
} else {
|
||||
let errorMsg = `Match type ${match.type} is not mapped to a match group.`;
|
||||
logger.error(errorMsg);
|
||||
Cu.reportError(errorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var UrlbarMuxerUnifiedComplete = new MuxerUnifiedComplete();
|
|
@ -29,6 +29,11 @@ var localProviderModules = {
|
|||
UrlbarProviderUnifiedComplete: "resource:///modules/UrlbarProviderUnifiedComplete.jsm",
|
||||
};
|
||||
|
||||
// List of available local muxers, each is implemented in its own jsm module.
|
||||
var localMuxerModules = {
|
||||
UrlbarMuxerUnifiedComplete: "resource:///modules/UrlbarMuxerUnifiedComplete.jsm",
|
||||
};
|
||||
|
||||
// To improve dataflow and reduce UI work, when a match is added by a
|
||||
// non-immediate provider, we notify it to the controller after a delay, so
|
||||
// that we can chunk matches coming in that timeframe into a single call.
|
||||
|
@ -59,6 +64,13 @@ class ProvidersManager {
|
|||
// running a query that shouldn't be interrupted, and if so it should
|
||||
// bump this through disableInterrupt and enableInterrupt.
|
||||
this.interruptLevel = 0;
|
||||
|
||||
// This maps muxer names to muxers.
|
||||
this.muxers = new Map();
|
||||
for (let [symbol, module] of Object.entries(localMuxerModules)) {
|
||||
let {[symbol]: muxer} = ChromeUtils.import(module, {});
|
||||
this.registerMuxer(muxer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,10 +78,15 @@ class ProvidersManager {
|
|||
* @param {object} provider
|
||||
*/
|
||||
registerProvider(provider) {
|
||||
logger.info(`Registering provider ${provider.name}`);
|
||||
if (!provider || !provider.name ||
|
||||
(typeof provider.startQuery != "function") ||
|
||||
(typeof provider.cancelQuery != "function")) {
|
||||
throw new Error(`Trying to register an invalid provider`);
|
||||
}
|
||||
if (!Object.values(UrlbarUtils.PROVIDER_TYPE).includes(provider.type)) {
|
||||
throw new Error(`Unknown provider type ${provider.type}`);
|
||||
}
|
||||
logger.info(`Registering provider ${provider.name}`);
|
||||
this.providers.get(provider.type).set(provider.name, provider);
|
||||
}
|
||||
|
||||
|
@ -82,6 +99,28 @@ class ProvidersManager {
|
|||
this.providers.get(provider.type).delete(provider.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a muxer object with the manager.
|
||||
* @param {object} muxer a UrlbarMuxer object
|
||||
*/
|
||||
registerMuxer(muxer) {
|
||||
if (!muxer || !muxer.name || (typeof muxer.sort != "function")) {
|
||||
throw new Error(`Trying to register an invalid muxer`);
|
||||
}
|
||||
logger.info(`Registering muxer ${muxer.name}`);
|
||||
this.muxers.set(muxer.name, muxer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a previously registered muxer object.
|
||||
* @param {object} muxer a UrlbarMuxer object or name.
|
||||
*/
|
||||
unregisterMuxer(muxer) {
|
||||
let muxerName = typeof muxer == "string" ? muxer : muxer.name;
|
||||
logger.info(`Unregistering muxer ${muxerName}`);
|
||||
this.muxers.delete(muxerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts querying.
|
||||
* @param {object} queryContext The query context object
|
||||
|
@ -89,7 +128,13 @@ class ProvidersManager {
|
|||
*/
|
||||
async startQuery(queryContext, controller) {
|
||||
logger.info(`Query start ${queryContext.searchString}`);
|
||||
let query = new Query(queryContext, controller, this.providers);
|
||||
let muxerName = queryContext.muxer || "MuxerUnifiedComplete";
|
||||
logger.info(`Using muxer ${muxerName}`);
|
||||
let muxer = this.muxers.get(muxerName);
|
||||
if (!muxer) {
|
||||
throw new Error(`Muxer with name ${muxerName} not found`);
|
||||
}
|
||||
let query = new Query(queryContext, controller, muxer, this.providers);
|
||||
this.queries.set(queryContext, query);
|
||||
await query.start();
|
||||
}
|
||||
|
@ -145,12 +190,15 @@ class Query {
|
|||
* The query context
|
||||
* @param {object} controller
|
||||
* The controller to be notified
|
||||
* @param {object} muxer
|
||||
* The muxer to sort matches
|
||||
* @param {object} providers
|
||||
* Map of all the providers by type and name
|
||||
*/
|
||||
constructor(queryContext, controller, providers) {
|
||||
constructor(queryContext, controller, muxer, providers) {
|
||||
this.context = queryContext;
|
||||
this.context.results = [];
|
||||
this.muxer = muxer;
|
||||
this.controller = controller;
|
||||
this.providers = providers;
|
||||
this.started = false;
|
||||
|
@ -267,8 +315,7 @@ class Query {
|
|||
this._chunkTimer.cancel().catch(Cu.reportError);
|
||||
delete this._chunkTimer;
|
||||
}
|
||||
// TODO:
|
||||
// * pass results to a muxer before sending them back to the controller.
|
||||
this.muxer.sort(this.context);
|
||||
this.controller.receiveResults(this.context);
|
||||
};
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ EXTRA_JS_MODULES += [
|
|||
'UrlbarController.jsm',
|
||||
'UrlbarInput.jsm',
|
||||
'UrlbarMatch.jsm',
|
||||
'UrlbarMuxerUnifiedComplete.jsm',
|
||||
'UrlbarPrefs.jsm',
|
||||
'UrlbarProviderOpenTabs.jsm',
|
||||
'UrlbarProvidersManager.jsm',
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function test_muxer() {
|
||||
Assert.throws(() => UrlbarProvidersManager.registerMuxer(),
|
||||
/invalid muxer/,
|
||||
"Should throw with no arguments");
|
||||
Assert.throws(() => UrlbarProvidersManager.registerMuxer({}),
|
||||
/invalid muxer/,
|
||||
"Should throw with empty object");
|
||||
Assert.throws(() => UrlbarProvidersManager.registerMuxer({
|
||||
name: "",
|
||||
}),
|
||||
/invalid muxer/,
|
||||
"Should throw with empty name");
|
||||
Assert.throws(() => UrlbarProvidersManager.registerMuxer({
|
||||
name: "test",
|
||||
sort: "no",
|
||||
}),
|
||||
/invalid muxer/,
|
||||
"Should throw with invalid sort");
|
||||
|
||||
let matches = [
|
||||
new UrlbarMatch(UrlbarUtils.MATCH_TYPE.TAB_SWITCH,
|
||||
UrlbarUtils.MATCH_SOURCE.TABS,
|
||||
{ url: "http://mozilla.org/tab/" }),
|
||||
new UrlbarMatch(UrlbarUtils.MATCH_TYPE.URL,
|
||||
UrlbarUtils.MATCH_SOURCE.BOOKMARKS,
|
||||
{ url: "http://mozilla.org/bookmark/" }),
|
||||
new UrlbarMatch(UrlbarUtils.MATCH_TYPE.URL,
|
||||
UrlbarUtils.MATCH_SOURCE.HISTORY,
|
||||
{ url: "http://mozilla.org/history/" }),
|
||||
];
|
||||
registerBasicTestProvider(matches);
|
||||
|
||||
let context = createContext();
|
||||
let controller = new UrlbarController({
|
||||
browserWindow: {
|
||||
location: {
|
||||
href: AppConstants.BROWSER_CHROME_URL,
|
||||
},
|
||||
},
|
||||
});
|
||||
let muxer = {
|
||||
get name() {
|
||||
return "TestMuxer";
|
||||
},
|
||||
sort(queryContext) {
|
||||
queryContext.results.sort((a, b) => {
|
||||
if (b.source == UrlbarUtils.MATCH_SOURCE.TABS) {
|
||||
return -1;
|
||||
}
|
||||
if (b.source == UrlbarUtils.MATCH_SOURCE.BOOKMARKS) {
|
||||
return 1;
|
||||
}
|
||||
return a.source == UrlbarUtils.MATCH_SOURCE.BOOKMARKS ? -1 : 1;
|
||||
});
|
||||
},
|
||||
};
|
||||
UrlbarProvidersManager.registerMuxer(muxer);
|
||||
context.muxer = "TestMuxer";
|
||||
|
||||
info("Check results, the order should be: bookmark, history, tab");
|
||||
await UrlbarProvidersManager.startQuery(context, controller);
|
||||
Assert.deepEqual(context.results, [
|
||||
matches[1],
|
||||
matches[2],
|
||||
matches[0],
|
||||
]);
|
||||
|
||||
// Sanity check, should not throw.
|
||||
UrlbarProvidersManager.unregisterMuxer(muxer);
|
||||
UrlbarProvidersManager.unregisterMuxer("TestMuxer"); // no-op.
|
||||
});
|
|
@ -4,6 +4,31 @@
|
|||
"use strict";
|
||||
|
||||
add_task(async function test_providers() {
|
||||
Assert.throws(() => UrlbarProvidersManager.registerProvider(),
|
||||
/invalid provider/,
|
||||
"Should throw with no arguments");
|
||||
Assert.throws(() => UrlbarProvidersManager.registerProvider({}),
|
||||
/invalid provider/,
|
||||
"Should throw with empty object");
|
||||
Assert.throws(() => UrlbarProvidersManager.registerProvider({
|
||||
name: "",
|
||||
}),
|
||||
/invalid provider/,
|
||||
"Should throw with empty name");
|
||||
Assert.throws(() => UrlbarProvidersManager.registerProvider({
|
||||
name: "test",
|
||||
startQuery: "no",
|
||||
}),
|
||||
/invalid provider/,
|
||||
"Should throw with invalid startQuery");
|
||||
Assert.throws(() => UrlbarProvidersManager.registerProvider({
|
||||
name: "test",
|
||||
startQuery: () => {},
|
||||
cancelQuery: "no",
|
||||
}),
|
||||
/invalid provider/,
|
||||
"Should throw with invalid cancelQuery");
|
||||
|
||||
let match = new UrlbarMatch(UrlbarUtils.MATCH_TYPE.TAB_SWITCH,
|
||||
UrlbarUtils.MATCH_SOURCE.TABS,
|
||||
{ url: "http://mozilla.org/foo/" });
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
head = head.js
|
||||
firefox-appdir = browser
|
||||
|
||||
[test_muxer.js]
|
||||
[test_providerOpenTabs.js]
|
||||
[test_providersManager.js]
|
||||
[test_providersManager_filtering.js]
|
||||
|
|
|
@ -65,6 +65,10 @@ It is augmented as it progresses through the system, with various information:
|
|||
isPrivate; // {boolean} Whether the search started in a private context.
|
||||
userContextId; // {integer} The user context ID (containers feature).
|
||||
|
||||
// Optional properties.
|
||||
muxer; // Name of a registered muxer. Muxers can be registered through the
|
||||
// UrlbarProvidersManager
|
||||
|
||||
// Properties added by the Model.
|
||||
tokens; // {array} tokens extracted from the searchString, each token is an
|
||||
// object in the form {type, value}.
|
||||
|
@ -88,9 +92,10 @@ startup and can register/unregister providers on the fly.
|
|||
It can manage multiple concurrent queries, and tracks them internally as
|
||||
separate *Query* objects.
|
||||
|
||||
The *Controller* starts and stops queries through the *ProvidersManager*. It's
|
||||
possible to wait for the promise returned by *startQuery* to know when no more
|
||||
matches will be returned, it is not mandatory though. Queries can be canceled.
|
||||
The *Controller* starts and stops queries through the *UrlbarProvidersManager*.
|
||||
It's possible to wait for the promise returned by *startQuery* to know when no
|
||||
more matches will be returned, it is not mandatory though.
|
||||
Queries can be canceled.
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -114,6 +119,8 @@ used by the user to restrict the search to specific type of matches (See the
|
|||
UrlbarProvidersManager {
|
||||
registerProvider(providerObj);
|
||||
unregisterProvider(providerObj);
|
||||
registerMuxer(muxerObj);
|
||||
unregisterMuxer(muxerObjOrName);
|
||||
async startQuery(queryContext);
|
||||
cancelQuery(queryContext);
|
||||
// Can be used by providers to run uninterruptible queries.
|
||||
|
@ -160,14 +167,22 @@ UrlbarMuxer
|
|||
-----------
|
||||
|
||||
The *Muxer* is responsible for sorting matches based on their importance and
|
||||
additional rules that depend on the QueryContext.
|
||||
additional rules that depend on the QueryContext. The muxer to use is indicated
|
||||
by the QueryContext.muxer property.
|
||||
|
||||
.. caution
|
||||
|
||||
The Muxer is a replaceable component, as such what is described here is a
|
||||
reference for the default View, but may not be valid for other implementations.
|
||||
|
||||
*Content to be written*
|
||||
.. highlight:: JavaScript
|
||||
.. code:
|
||||
|
||||
UrlbarMuxer {
|
||||
name; // {string} A simple name to track the provider.
|
||||
// Invoked by the ProvidersManager to sort matches.
|
||||
sort(queryContext);
|
||||
}
|
||||
|
||||
|
||||
The Controller
|
||||
|
|
Загрузка…
Ссылка в новой задаче