summaryrefslogtreecommitdiffstats
path: root/application/basilisk/modules/ContentSearch.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'application/basilisk/modules/ContentSearch.jsm')
-rw-r--r--application/basilisk/modules/ContentSearch.jsm566
1 files changed, 0 insertions, 566 deletions
diff --git a/application/basilisk/modules/ContentSearch.jsm b/application/basilisk/modules/ContentSearch.jsm
deleted file mode 100644
index 91b0b9ac8..000000000
--- a/application/basilisk/modules/ContentSearch.jsm
+++ /dev/null
@@ -1,566 +0,0 @@
-/* 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/. */
-/* globals XPCOMUtils, Services, Task, Promise, SearchSuggestionController, FormHistory, PrivateBrowsingUtils */
-"use strict";
-
-this.EXPORTED_SYMBOLS = [
- "ContentSearch",
-];
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
- "resource://gre/modules/FormHistory.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
- "resource://gre/modules/PrivateBrowsingUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SearchSuggestionController",
- "resource://gre/modules/SearchSuggestionController.jsm");
-
-const INBOUND_MESSAGE = "ContentSearch";
-const OUTBOUND_MESSAGE = INBOUND_MESSAGE;
-const MAX_LOCAL_SUGGESTIONS = 3;
-const MAX_SUGGESTIONS = 6;
-
-/**
- * ContentSearch receives messages named INBOUND_MESSAGE and sends messages
- * named OUTBOUND_MESSAGE. The data of each message is expected to look like
- * { type, data }. type is the message's type (or subtype if you consider the
- * type of the message itself to be INBOUND_MESSAGE), and data is data that is
- * specific to the type.
- *
- * Inbound messages have the following types:
- *
- * AddFormHistoryEntry
- * Adds an entry to the search form history.
- * data: the entry, a string
- * GetSuggestions
- * Retrieves an array of search suggestions given a search string.
- * data: { engineName, searchString, [remoteTimeout] }
- * GetState
- * Retrieves the current search engine state.
- * data: null
- * GetStrings
- * Retrieves localized search UI strings.
- * data: null
- * ManageEngines
- * Opens the search engine management window.
- * data: null
- * RemoveFormHistoryEntry
- * Removes an entry from the search form history.
- * data: the entry, a string
- * Search
- * Performs a search.
- * Any GetSuggestions messages in the queue from the same target will be
- * cancelled.
- * data: { engineName, searchString, healthReportKey, searchPurpose }
- * SetCurrentEngine
- * Sets the current engine.
- * data: the name of the engine
- * SpeculativeConnect
- * Speculatively connects to an engine.
- * data: the name of the engine
- *
- * Outbound messages have the following types:
- *
- * CurrentEngine
- * Broadcast when the current engine changes.
- * data: see _currentEngineObj
- * CurrentState
- * Broadcast when the current search state changes.
- * data: see currentStateObj
- * State
- * Sent in reply to GetState.
- * data: see currentStateObj
- * Strings
- * Sent in reply to GetStrings
- * data: Object containing string names and values for the current locale.
- * Suggestions
- * Sent in reply to GetSuggestions.
- * data: see _onMessageGetSuggestions
- * SuggestionsCancelled
- * Sent in reply to GetSuggestions when pending GetSuggestions events are
- * cancelled.
- * data: null
- */
-
-this.ContentSearch = {
-
- // Inbound events are queued and processed in FIFO order instead of handling
- // them immediately, which would result in non-FIFO responses due to the
- // asynchrononicity added by converting image data URIs to ArrayBuffers.
- _eventQueue: [],
- _currentEventPromise: null,
-
- // This is used to handle search suggestions. It maps xul:browsers to objects
- // { controller, previousFormHistoryResult }. See _onMessageGetSuggestions.
- _suggestionMap: new WeakMap(),
-
- // Resolved when we finish shutting down.
- _destroyedPromise: null,
-
- // The current controller and browser in _onMessageGetSuggestions. Allows
- // fetch cancellation from _cancelSuggestions.
- _currentSuggestion: null,
-
- init: function () {
- Cc["@mozilla.org/globalmessagemanager;1"].
- getService(Ci.nsIMessageListenerManager).
- addMessageListener(INBOUND_MESSAGE, this);
- Services.obs.addObserver(this, "browser-search-engine-modified", false);
- Services.obs.addObserver(this, "shutdown-leaks-before-check", false);
- Services.prefs.addObserver("browser.search.hiddenOneOffs", this, false);
- this._stringBundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
- },
-
- get searchSuggestionUIStrings() {
- if (this._searchSuggestionUIStrings) {
- return this._searchSuggestionUIStrings;
- }
- this._searchSuggestionUIStrings = {};
- let searchBundle = Services.strings.createBundle("chrome://browser/locale/search.properties");
- let stringNames = ["searchHeader", "searchPlaceholder", "searchForSomethingWith",
- "searchWithHeader", "searchSettings"];
-
- for (let name of stringNames) {
- this._searchSuggestionUIStrings[name] = searchBundle.GetStringFromName(name);
- }
- return this._searchSuggestionUIStrings;
- },
-
- destroy: function () {
- if (this._destroyedPromise) {
- return this._destroyedPromise;
- }
-
- Cc["@mozilla.org/globalmessagemanager;1"].
- getService(Ci.nsIMessageListenerManager).
- removeMessageListener(INBOUND_MESSAGE, this);
- Services.obs.removeObserver(this, "browser-search-engine-modified");
- Services.obs.removeObserver(this, "shutdown-leaks-before-check");
-
- this._eventQueue.length = 0;
- this._destroyedPromise = Promise.resolve(this._currentEventPromise);
- return this._destroyedPromise;
- },
-
- /**
- * Focuses the search input in the page with the given message manager.
- * @param messageManager
- * The MessageManager object of the selected browser.
- */
- focusInput: function (messageManager) {
- messageManager.sendAsyncMessage(OUTBOUND_MESSAGE, {
- type: "FocusInput"
- });
- },
-
- receiveMessage: function (msg) {
- // Add a temporary event handler that exists only while the message is in
- // the event queue. If the message's source docshell changes browsers in
- // the meantime, then we need to update msg.target. event.detail will be
- // the docshell's new parent <xul:browser> element.
- msg.handleEvent = event => {
- let browserData = this._suggestionMap.get(msg.target);
- if (browserData) {
- this._suggestionMap.delete(msg.target);
- this._suggestionMap.set(event.detail, browserData);
- }
- msg.target.removeEventListener("SwapDocShells", msg, true);
- msg.target = event.detail;
- msg.target.addEventListener("SwapDocShells", msg, true);
- };
- msg.target.addEventListener("SwapDocShells", msg, true);
-
- // Search requests cause cancellation of all Suggestion requests from the
- // same browser.
- if (msg.data.type === "Search") {
- this._cancelSuggestions(msg);
- }
-
- this._eventQueue.push({
- type: "Message",
- data: msg,
- });
- this._processEventQueue();
- },
-
- observe: function (subj, topic, data) {
- switch (topic) {
- case "nsPref:changed":
- case "browser-search-engine-modified":
- this._eventQueue.push({
- type: "Observe",
- data: data,
- });
- this._processEventQueue();
- break;
- case "shutdown-leaks-before-check":
- subj.wrappedJSObject.client.addBlocker(
- "ContentSearch: Wait until the service is destroyed", () => this.destroy());
- break;
- }
- },
-
- removeFormHistoryEntry: function (msg, entry) {
- let browserData = this._suggestionDataForBrowser(msg.target);
- if (browserData && browserData.previousFormHistoryResult) {
- let { previousFormHistoryResult } = browserData;
- for (let i = 0; i < previousFormHistoryResult.matchCount; i++) {
- if (previousFormHistoryResult.getValueAt(i) === entry) {
- previousFormHistoryResult.removeValueAt(i, true);
- break;
- }
- }
- }
- },
-
- performSearch: function (msg, data) {
- this._ensureDataHasProperties(data, [
- "engineName",
- "searchString",
- "healthReportKey",
- "searchPurpose",
- ]);
- let engine = Services.search.getEngineByName(data.engineName);
- let submission = engine.getSubmission(data.searchString, "", data.searchPurpose);
- let browser = msg.target;
- let win = browser.ownerGlobal;
- if (!win) {
- // The browser may have been closed between the time its content sent the
- // message and the time we handle it.
- return;
- }
- let where = win.whereToOpenLink(data.originalEvent);
-
- // There is a chance that by the time we receive the search message, the user
- // has switched away from the tab that triggered the search. If, based on the
- // event, we need to load the search in the same tab that triggered it (i.e.
- // where === "current"), openUILinkIn will not work because that tab is no
- // longer the current one. For this case we manually load the URI.
- if (where === "current") {
- browser.loadURIWithFlags(submission.uri.spec,
- Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null,
- submission.postData);
- } else {
- let params = {
- postData: submission.postData,
- inBackground: Services.prefs.getBoolPref("browser.tabs.loadInBackground"),
- };
- win.openUILinkIn(submission.uri.spec, where, params);
- }
- win.BrowserSearch.recordSearchInTelemetry(engine, data.healthReportKey,
- { selection: data.selection });
- return;
- },
-
- getSuggestions: Task.async(function* (engineName, searchString, browser, remoteTimeout=null) {
- let engine = Services.search.getEngineByName(engineName);
- if (!engine) {
- throw new Error("Unknown engine name: " + engineName);
- }
-
- let browserData = this._suggestionDataForBrowser(browser, true);
- let { controller } = browserData;
- let ok = SearchSuggestionController.engineOffersSuggestions(engine);
- controller.maxLocalResults = ok ? MAX_LOCAL_SUGGESTIONS : MAX_SUGGESTIONS;
- controller.maxRemoteResults = ok ? MAX_SUGGESTIONS : 0;
- controller.remoteTimeout = remoteTimeout || undefined;
- let priv = PrivateBrowsingUtils.isBrowserPrivate(browser);
- // fetch() rejects its promise if there's a pending request, but since we
- // process our event queue serially, there's never a pending request.
- this._currentSuggestion = { controller: controller, target: browser };
- let suggestions = yield controller.fetch(searchString, priv, engine);
- this._currentSuggestion = null;
-
- // suggestions will be null if the request was cancelled
- let result = {};
- if (!suggestions) {
- return result;
- }
-
- // Keep the form history result so RemoveFormHistoryEntry can remove entries
- // from it. Keeping only one result isn't foolproof because the client may
- // try to remove an entry from one set of suggestions after it has requested
- // more but before it's received them. In that case, the entry may not
- // appear in the new suggestions. But that should happen rarely.
- browserData.previousFormHistoryResult = suggestions.formHistoryResult;
- result = {
- engineName,
- term: suggestions.term,
- local: suggestions.local,
- remote: suggestions.remote,
- };
- return result;
- }),
-
- addFormHistoryEntry: Task.async(function* (browser, entry="") {
- let isPrivate = false;
- try {
- // isBrowserPrivate assumes that the passed-in browser has all the normal
- // properties, which won't be true if the browser has been destroyed.
- // That may be the case here due to the asynchronous nature of messaging.
- isPrivate = PrivateBrowsingUtils.isBrowserPrivate(browser.target);
- } catch (err) {
- return false;
- }
- if (isPrivate || entry === "") {
- return false;
- }
- let browserData = this._suggestionDataForBrowser(browser.target, true);
- FormHistory.update({
- op: "bump",
- fieldname: browserData.controller.formHistoryParam,
- value: entry,
- }, {
- handleCompletion: () => {},
- handleError: err => {
- Cu.reportError("Error adding form history entry: " + err);
- },
- });
- return true;
- }),
-
- currentStateObj: Task.async(function* (uriFlag=false) {
- let state = {
- engines: [],
- currentEngine: yield this._currentEngineObj(),
- };
- if (uriFlag) {
- state.currentEngine.iconBuffer = Services.search.currentEngine.getIconURLBySize(16, 16);
- }
- let pref = Services.prefs.getCharPref("browser.search.hiddenOneOffs");
- let hiddenList = pref ? pref.split(",") : [];
- for (let engine of Services.search.getVisibleEngines()) {
- let uri = engine.getIconURLBySize(16, 16);
- let iconBuffer = uri;
- if (!uriFlag) {
- iconBuffer = yield this._arrayBufferFromDataURI(uri);
- }
- state.engines.push({
- name: engine.name,
- iconBuffer,
- hidden: hiddenList.indexOf(engine.name) !== -1,
- });
- }
- return state;
- }),
-
- _processEventQueue: function () {
- if (this._currentEventPromise || !this._eventQueue.length) {
- return;
- }
-
- let event = this._eventQueue.shift();
-
- this._currentEventPromise = Task.spawn(function* () {
- try {
- yield this["_on" + event.type](event.data);
- } catch (err) {
- Cu.reportError(err);
- } finally {
- this._currentEventPromise = null;
- this._processEventQueue();
- }
- }.bind(this));
- },
-
- _cancelSuggestions: function (msg) {
- let cancelled = false;
- // cancel active suggestion request
- if (this._currentSuggestion && this._currentSuggestion.target === msg.target) {
- this._currentSuggestion.controller.stop();
- cancelled = true;
- }
- // cancel queued suggestion requests
- for (let i = 0; i < this._eventQueue.length; i++) {
- let m = this._eventQueue[i].data;
- if (msg.target === m.target && m.data.type === "GetSuggestions") {
- this._eventQueue.splice(i, 1);
- cancelled = true;
- i--;
- }
- }
- if (cancelled) {
- this._reply(msg, "SuggestionsCancelled");
- }
- },
-
- _onMessage: Task.async(function* (msg) {
- let methodName = "_onMessage" + msg.data.type;
- if (methodName in this) {
- yield this._initService();
- yield this[methodName](msg, msg.data.data);
- if (!Cu.isDeadWrapper(msg.target)) {
- msg.target.removeEventListener("SwapDocShells", msg, true);
- }
- }
- }),
-
- _onMessageGetState: function (msg, data) {
- return this.currentStateObj().then(state => {
- this._reply(msg, "State", state);
- });
- },
-
- _onMessageGetStrings: function (msg, data) {
- this._reply(msg, "Strings", this.searchSuggestionUIStrings);
- },
-
- _onMessageSearch: function (msg, data) {
- this.performSearch(msg, data);
- },
-
- _onMessageSetCurrentEngine: function (msg, data) {
- Services.search.currentEngine = Services.search.getEngineByName(data);
- },
-
- _onMessageManageEngines: function (msg, data) {
- let browserWin = msg.target.ownerGlobal;
- browserWin.openPreferences("paneSearch");
- },
-
- _onMessageGetSuggestions: Task.async(function* (msg, data) {
- this._ensureDataHasProperties(data, [
- "engineName",
- "searchString",
- ]);
- let {engineName, searchString} = data;
- let suggestions = yield this.getSuggestions(engineName, searchString, msg.target);
-
- this._reply(msg, "Suggestions", {
- engineName: data.engineName,
- searchString: suggestions.term,
- formHistory: suggestions.local,
- remote: suggestions.remote,
- });
- }),
-
- _onMessageAddFormHistoryEntry: Task.async(function* (msg, entry) {
- yield this.addFormHistoryEntry(msg, entry);
- }),
-
- _onMessageRemoveFormHistoryEntry: function (msg, entry) {
- this.removeFormHistoryEntry(msg, entry);
- },
-
- _onMessageSpeculativeConnect: function (msg, engineName) {
- let engine = Services.search.getEngineByName(engineName);
- if (!engine) {
- throw new Error("Unknown engine name: " + engineName);
- }
- if (msg.target.contentWindow) {
- engine.speculativeConnect({
- window: msg.target.contentWindow,
- });
- }
- },
-
- _onObserve: Task.async(function* (data) {
- if (data === "engine-current") {
- let engine = yield this._currentEngineObj();
- this._broadcast("CurrentEngine", engine);
- }
- else if (data !== "engine-default") {
- // engine-default is always sent with engine-current and isn't otherwise
- // relevant to content searches.
- let state = yield this.currentStateObj();
- this._broadcast("CurrentState", state);
- }
- }),
-
- _suggestionDataForBrowser: function (browser, create=false) {
- let data = this._suggestionMap.get(browser);
- if (!data && create) {
- // Since one SearchSuggestionController instance is meant to be used per
- // autocomplete widget, this means that we assume each xul:browser has at
- // most one such widget.
- data = {
- controller: new SearchSuggestionController(),
- };
- this._suggestionMap.set(browser, data);
- }
- return data;
- },
-
- _reply: function (msg, type, data) {
- // We reply asyncly to messages, and by the time we reply the browser we're
- // responding to may have been destroyed. messageManager is null then.
- if (!Cu.isDeadWrapper(msg.target) && msg.target.messageManager) {
- msg.target.messageManager.sendAsyncMessage(...this._msgArgs(type, data));
- }
- },
-
- _broadcast: function (type, data) {
- Cc["@mozilla.org/globalmessagemanager;1"].
- getService(Ci.nsIMessageListenerManager).
- broadcastAsyncMessage(...this._msgArgs(type, data));
- },
-
- _msgArgs: function (type, data) {
- return [OUTBOUND_MESSAGE, {
- type: type,
- data: data,
- }];
- },
-
- _currentEngineObj: Task.async(function* () {
- let engine = Services.search.currentEngine;
- let favicon = engine.getIconURLBySize(16, 16);
- let placeholder = this._stringBundle.formatStringFromName(
- "searchWithEngine", [engine.name], 1);
- let obj = {
- name: engine.name,
- placeholder: placeholder,
- iconBuffer: yield this._arrayBufferFromDataURI(favicon),
- };
- return obj;
- }),
-
- _arrayBufferFromDataURI: function (uri) {
- if (!uri) {
- return Promise.resolve(null);
- }
- let deferred = Promise.defer();
- let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
- createInstance(Ci.nsIXMLHttpRequest);
- xhr.open("GET", uri, true);
- xhr.responseType = "arraybuffer";
- xhr.onload = () => {
- deferred.resolve(xhr.response);
- };
- xhr.onerror = xhr.onabort = xhr.ontimeout = () => {
- deferred.resolve(null);
- };
- try {
- // This throws if the URI is erroneously encoded.
- xhr.send();
- }
- catch (err) {
- return Promise.resolve(null);
- }
- return deferred.promise;
- },
-
- _ensureDataHasProperties: function (data, requiredProperties) {
- for (let prop of requiredProperties) {
- if (!(prop in data)) {
- throw new Error("Message data missing required property: " + prop);
- }
- }
- },
-
- _initService: function () {
- if (!this._initServicePromise) {
- let deferred = Promise.defer();
- this._initServicePromise = deferred.promise;
- Services.search.init(() => deferred.resolve());
- }
- return this._initServicePromise;
- },
-};