--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -19,16 +19,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
// lazy module getters
[
["AboutHome", "resource:///modules/AboutHome.jsm"],
["AboutNewTab", "resource:///modules/AboutNewTab.jsm"],
["AddonManager", "resource://gre/modules/AddonManager.jsm"],
["AddonWatcher", "resource://gre/modules/AddonWatcher.jsm"],
["AsyncShutdown", "resource://gre/modules/AsyncShutdown.jsm"],
+ ["AutoCompletePopup", "resource://gre/modules/AutoCompletePopup.jsm"],
["BookmarkHTMLUtils", "resource://gre/modules/BookmarkHTMLUtils.jsm"],
["BookmarkJSONUtils", "resource://gre/modules/BookmarkJSONUtils.jsm"],
["BrowserUITelemetry", "resource:///modules/BrowserUITelemetry.jsm"],
["BrowserUsageTelemetry", "resource:///modules/BrowserUsageTelemetry.jsm"],
["CaptivePortalWatcher", "resource:///modules/CaptivePortalWatcher.jsm"],
["ContentClick", "resource:///modules/ContentClick.jsm"],
["ContentPrefServiceParent", "resource://gre/modules/ContentPrefServiceParent.jsm"],
["ContentSearch", "resource:///modules/ContentSearch.jsm"],
@@ -77,17 +78,16 @@ if (AppConstants.MOZ_CRASHREPORTER) {
XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
return Services.strings.createBundle('chrome://branding/locale/brand.properties');
});
XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
return Services.strings.createBundle('chrome://browser/locale/browser.properties');
});
-
// Seconds of idle before trying to create a bookmarks backup.
const BOOKMARKS_BACKUP_IDLE_TIME_SEC = 8 * 60;
// Minimum interval between backups. We try to not create more than one backup
// per interval.
const BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS = 1;
// Maximum interval between backups. If the last backup is older than these
// days we will try to create a new one more aggressively.
const BOOKMARKS_BACKUP_MAX_INTERVAL_DAYS = 3;
@@ -1050,16 +1050,18 @@ BrowserGlue.prototype = {
this._checkForOldBuildUpdates();
if (!AppConstants.RELEASE_BUILD) {
this.checkForPendingCrashReports();
}
CaptivePortalWatcher.init();
+ AutoCompletePopup.init();
+
this._firstWindowTelemetry(aWindow);
this._firstWindowLoaded();
},
/**
* Application shutdown handler.
*/
_onQuitApplicationGranted: function () {
@@ -1075,22 +1077,21 @@ BrowserGlue.prototype = {
appStartup.trackStartupCrashEnd();
} catch (e) {
Cu.reportError("Could not end startup crash tracking in quit-application-granted: " + e);
}
BrowserUsageTelemetry.uninit();
SelfSupportBackend.uninit();
NewTabMessages.uninit();
-
CaptivePortalWatcher.uninit();
-
AboutNewTab.uninit();
webrtcUI.uninit();
FormValidationHandler.uninit();
+ AutoCompletePopup.uninit();
if (AppConstants.NIGHTLY_BUILD) {
AddonWatcher.uninit();
}
},
_initServiceDiscovery: function () {
if (!Services.prefs.getBoolPref("browser.casting.enabled")) {
return;
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -8,18 +8,18 @@ const { classes: Cc, interfaces: Ci, res
Cu.importGlobalProperties(["URL"]);
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UserAutoCompleteResult",
"resource://gre/modules/LoginManagerContent.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AutoCompleteE10S",
- "resource://gre/modules/AutoCompleteE10S.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AutoCompletePopup",
+ "resource://gre/modules/AutoCompletePopup.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
"resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginDoorhangers",
"resource://gre/modules/LoginDoorhangers.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyGetter(this, "log", () => {
@@ -96,17 +96,17 @@ var LoginManagerParent = {
case "RemoteLogins:autoCompleteLogins": {
this.doAutocompleteSearch(data, msg.target);
break;
}
case "RemoteLogins:removeLogin": {
let login = LoginHelper.vanillaObjectToLogin(data.login);
- AutoCompleteE10S.removeLogin(login);
+ AutoCompletePopup.removeLogin(login);
break;
}
}
return undefined;
},
/**
@@ -227,17 +227,16 @@ var LoginManagerParent = {
});
}),
doAutocompleteSearch: function({ formOrigin, actionOrigin,
searchString, previousResult,
rect, requestId, remote }, target) {
// Note: previousResult is a regular object, not an
// nsIAutoCompleteResult.
- var result;
let searchStringLower = searchString.toLowerCase();
let logins;
if (previousResult &&
searchStringLower.startsWith(previousResult.searchString.toLowerCase())) {
log("Using previous autocomplete result");
// We have a list of results for a shorter search string, so just
@@ -268,18 +267,18 @@ var LoginManagerParent = {
});
// XXX In the E10S case, we're responsible for showing our own
// autocomplete popup here because the autocomplete protocol hasn't
// been e10s-ized yet. In the non-e10s case, our caller is responsible
// for showing the autocomplete popup (via the regular
// nsAutoCompleteController).
if (remote) {
- result = new UserAutoCompleteResult(searchString, matchingLogins);
- AutoCompleteE10S.showPopupWithResults(target.ownerDocument.defaultView, rect, result);
+ let results = new UserAutoCompleteResult(searchString, matchingLogins);
+ AutoCompletePopup.showPopupWithResults({ browser: target.ownerDocument.defaultView, rect, results });
}
// Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
// doesn't support structured cloning.
var jsLogins = LoginHelper.loginsToVanillaObjects(matchingLogins);
target.messageManager.sendAsyncMessage("RemoteLogins:loginsAutoCompleted", {
requestId: requestId,
logins: jsLogins,
rename from toolkit/components/satchel/AutoCompleteE10S.jsm
rename to toolkit/components/satchel/AutoCompletePopup.jsm
--- a/toolkit/components/satchel/AutoCompleteE10S.jsm
+++ b/toolkit/components/satchel/AutoCompletePopup.jsm
@@ -3,302 +3,267 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
-this.EXPORTED_SYMBOLS = [ "AutoCompleteE10S" ];
+this.EXPORTED_SYMBOLS = [ "AutoCompletePopup" ];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/nsFormAutoCompleteResult.jsm");
// nsITreeView implementation that feeds the autocomplete popup
// with the search data.
-var AutoCompleteE10SView = {
+var AutoCompleteTreeView = {
// nsISupports
QueryInterface: XPCOMUtils.generateQI([Ci.nsITreeView,
Ci.nsIAutoCompleteController]),
// Private variables
treeBox: null,
- treeData: [],
- properties: [],
+ results: [],
// nsITreeView
selection: null,
- get rowCount() { return this.treeData.length; },
+ get rowCount() { return this.results.length; },
setTree: function(treeBox) { this.treeBox = treeBox; },
- getCellText: function(idx, column) { return this.treeData[idx] },
+ getCellText: function(idx, column) { return this.results[idx].value },
isContainer: function(idx) { return false; },
getCellValue: function(idx, column) { return false },
isContainerOpen: function(idx) { return false; },
isContainerEmpty: function(idx) { return false; },
isSeparator: function(idx) { return false; },
isSorted: function() { return false; },
isEditable: function(idx, column) { return false; },
canDrop: function(idx, orientation, dt) { return false; },
getLevel: function(idx) { return 0; },
getParentIndex: function(idx) { return -1; },
- hasNextSibling: function(idx, after) { return idx < this.treeData.length - 1 },
+ hasNextSibling: function(idx, after) { return idx < this.results.length - 1 },
toggleOpenState: function(idx) { },
- getCellProperties: function(idx, column) { return this.properties[idx] || ""; },
+ getCellProperties: function(idx, column) { return this.results[idx].style || ""; },
getRowProperties: function(idx) { return ""; },
getImageSrc: function(idx, column) { return null; },
getProgressMode : function(idx, column) { },
cycleHeader: function(column) { },
cycleCell: function(idx, column) { },
selectionChanged: function() { },
performAction: function(action) { },
performActionOnCell: function(action, index, column) { },
getColumnProperties: function(column) { return ""; },
// nsIAutoCompleteController
get matchCount() {
return this.rowCount;
},
handleEnter: function(aIsPopupSelection) {
- AutoCompleteE10S.handleEnter(aIsPopupSelection);
+ AutoCompletePopup.handleEnter(aIsPopupSelection);
},
stopSearch: function() {},
// Internal JS-only API
clearResults: function() {
- this.treeData = [];
- this.properties = [];
+ this.results = [];
},
- addResult: function(text, properties) {
- this.treeData.push(text);
- this.properties.push(properties);
+ setResults: function(results) {
+ this.results = results;
},
};
-this.AutoCompleteE10S = {
+this.AutoCompletePopup = {
+ MESSAGES: [
+ "FormAutoComplete:SelectBy",
+ "FormAutoComplete:GetSelectedIndex",
+ "FormAutoComplete:SetSelectedIndex",
+ "FormAutoComplete:MaybeOpenPopup",
+ "FormAutoComplete:ClosePopup",
+ "FormAutoComplete:Disconnect",
+ "FormAutoComplete:RemoveEntry",
+ "FormAutoComplete:Invalidate",
+ ],
+
init: function() {
- let messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
- getService(Ci.nsIMessageListenerManager);
- messageManager.addMessageListener("FormAutoComplete:SelectBy", this);
- messageManager.addMessageListener("FormAutoComplete:GetSelectedIndex", this);
- messageManager.addMessageListener("FormAutoComplete:MaybeOpenPopup", this);
- messageManager.addMessageListener("FormAutoComplete:ClosePopup", this);
- messageManager.addMessageListener("FormAutoComplete:Disconnect", this);
- messageManager.addMessageListener("FormAutoComplete:RemoveEntry", this);
+ for (let msg of this.MESSAGES) {
+ Services.mm.addMessageListener(msg, this);
+ }
},
- _initPopup: function(browserWindow, rect, direction) {
- if (this._popupCache) {
- this._popupCache.browserWindow.removeEventListener("unload", this);
+ uninit: function() {
+ for (let msg of this.MESSAGES) {
+ Services.mm.removeMessageListener(msg, this);
}
- browserWindow.addEventListener("unload", this);
-
- this._popupCache = { browserWindow, rect, direction };
-
- this.browser = browserWindow.gBrowser.selectedBrowser;
- this.popup = this.browser.autoCompletePopup;
- this.popup.hidden = false;
- // don't allow the popup to become overly narrow
- this.popup.setAttribute("width", Math.max(100, rect.width));
- this.popup.style.direction = direction;
-
- this.x = rect.left;
- this.y = rect.top;
- this.width = rect.width;
- this.height = rect.height;
},
handleEvent: function(evt) {
- if (evt.type === "unload") {
- this._uninitPopup();
+ if (evt.type === "popuphidden") {
+ this.openedPopup = null;
+ this.weakBrowser = null;
+ evt.target.removeEventListener("popuphidden", this);
}
},
- _uninitPopup: function() {
- this._popupCache = null;
- this.browser = null;
- this.popup = null;
- },
-
- _showPopup: function(results) {
- AutoCompleteE10SView.clearResults();
+ // Along with being called internally by the receiveMessage handler,
+ // this function is also called directly by the login manager, which
+ // uses a single message to fill in the autocomplete results. See
+ // "RemoteLogins:autoCompleteLogins".
+ showPopupWithResults: function({ browser, rect, dir, results }) {
+ if (!results.length || this.openedPopup) {
+ // We shouldn't ever be showing an empty popup, and if we
+ // already have a popup open, the old one needs to close before
+ // we consider opening a new one.
+ return;
+ }
- let resultsArray = [];
- let count = results.matchCount;
- for (let i = 0; i < count; i++) {
- // The actual result for each match in the results object is the value.
- // We return that in order to submit the correct value. However, we have
- // to make sure we display the label corresponding to it in the popup.
- resultsArray.push(results.getValueAt(i));
- AutoCompleteE10SView.addResult(results.getLabelAt(i),
- results.getStyleAt(i));
+ let window = browser.ownerDocument.defaultView;
+ let tabbrowser = window.gBrowser;
+ if (Services.focus.activeWindow != window ||
+ tabbrowser.selectedBrowser != browser) {
+ // We were sent a message from a window or tab that went into the
+ // background, so we'll ignore it for now.
+ return;
}
- this.popup.view = AutoCompleteE10SView;
+ this.weakBrowser = Cu.getWeakReference(browser);
+ this.openedPopup = browser.autoCompletePopup;
+ this.openedPopup.hidden = false;
+ // don't allow the popup to become overly narrow
+ this.openedPopup.setAttribute("width", Math.max(100, rect.width));
+ this.openedPopup.style.direction = dir;
- this.popup.selectedIndex = -1;
- this.popup.invalidate();
+ AutoCompleteTreeView.setResults(results);
+ this.openedPopup.view = AutoCompleteTreeView;
+ this.openedPopup.selectedIndex = -1;
+ this.openedPopup.invalidate();
- if (count > 0) {
+ if (results.length) {
// Reset fields that were set from the last time the search popup was open
- this.popup.mInput = null;
- this.popup.showCommentColumn = false;
- this.popup.showImageColumn = false;
- this.popup.openPopupAtScreenRect("after_start", this.x, this.y, this.width, this.height, false, false);
+ this.openedPopup.mInput = null;
+ this.openedPopup.showCommentColumn = false;
+ this.openedPopup.showImageColumn = false;
+ this.openedPopup.openPopupAtScreenRect("after_start", rect.left, rect.top,
+ rect.width, rect.height, false,
+ false);
+ this.openedPopup.addEventListener("popuphidden", this);
} else {
- this.popup.closePopup();
+ this.closePopup();
+ }
+ },
+
+ invalidate(results) {
+ if (!this.openedPopup) {
+ return;
}
- this._resultCache = results;
- return resultsArray;
+ if (!results.length) {
+ this.closePopup();
+ } else {
+ AutoCompleteTreeView.setResults(results);
+ this.openedPopup.invalidate();
+ }
},
- // This function is used by the login manager, which uses a single message
- // to fill in the autocomplete results. See
- // "RemoteLogins:autoCompleteLogins".
- showPopupWithResults: function(browserWindow, rect, results) {
- this._initPopup(browserWindow, rect);
- this._showPopup(results);
+ closePopup() {
+ if (this.openedPopup) {
+ this.openedPopup.closePopup();
+ }
+ AutoCompleteTreeView.clearResults();
},
removeLogin(login) {
Services.logins.removeLogin(login);
// It's possible to race and have the deleted login no longer be in our
// resultCache's logins, so we remove it from the database above and only
// deal with our resultCache below.
let idx = this._resultCache.logins.findIndex(cur => {
return login.guid === cur.QueryInterface(Ci.nsILoginMetaInfo).guid
});
if (idx !== -1) {
this.removeEntry(idx, false);
}
},
- removeEntry(index, updateDB = true) {
- this._resultCache.removeValueAt(index, updateDB);
-
- let selectedIndex = this.popup.selectedIndex;
- this.showPopupWithResults(this._popupCache.browserWindow,
- this._popupCache.rect,
- this._resultCache);
-
- // If we removed the last result, bump the selected index back once.
- if (selectedIndex >= this._resultCache.matchCount)
- selectedIndex--;
- this.popup.selectedIndex = selectedIndex;
- },
-
- // This function is called in response to AutoComplete requests from the
- // child (received via the message manager, see
- // "FormHistory:AutoCompleteSearchAsync").
- search: function(message) {
- let browserWindow = message.target.ownerDocument.defaultView;
- let rect = message.data;
- let direction = message.data.direction;
-
- this._initPopup(browserWindow, rect, direction);
-
- // NB: We use .wrappedJSObject here in order to pass our mock DOM object
- // without being rejected by XPConnect (which attempts to enforce that DOM
- // objects are implemented in C++.
- let formAutoComplete = Cc["@mozilla.org/satchel/form-autocomplete;1"]
- .getService(Ci.nsIFormAutoComplete).wrappedJSObject;
-
- let values, labels;
- if (message.data.datalistResult) {
- // Create a full FormAutoCompleteResult from the mock one that we pass
- // over IPC.
- message.data.datalistResult =
- new FormAutoCompleteResult(message.data.untrimmedSearchString,
- Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
- 0, "", message.data.datalistResult.values,
- message.data.datalistResult.labels,
- [], null);
- } else {
- message.data.datalistResult = null;
+ receiveMessage: function(message) {
+ if (!message.target.autoCompletePopup) {
+ // Returning false to pacify ESLint, but this return value is
+ // ignored by the messaging infrastructure.
+ return false;
}
- let previousResult = null;
- let previousSearchString = message.data.previousSearchString;
- let searchString = message.data.untrimmedSearchString.toLowerCase();
- if (previousSearchString && previousSearchString.length > 1 &&
- searchString.includes(previousSearchString)) {
- previousResult = this._resultCache;
- }
- formAutoComplete.autoCompleteSearchAsync(message.data.inputName,
- message.data.untrimmedSearchString,
- message.data.mockField,
- previousResult,
- message.data.datalistResult,
- { onSearchCompletion:
- this.onSearchComplete.bind(this) });
- },
-
- // The second half of search, this fills in the popup and returns the
- // results to the child.
- onSearchComplete: function(results) {
- let resultsArray = this._showPopup(results);
+ switch (message.name) {
+ case "FormAutoComplete:SelectBy": {
+ this.openedPopup.selectBy(message.data.reverse, message.data.page);
+ break;
+ }
- this.browser.messageManager.sendAsyncMessage(
- "FormAutoComplete:AutoCompleteSearchAsyncResult",
- {results: resultsArray}
- );
- },
-
- receiveMessage: function(message) {
- switch (message.name) {
- case "FormAutoComplete:SelectBy":
- this.popup.selectBy(message.data.reverse, message.data.page);
- break;
-
- case "FormAutoComplete:GetSelectedIndex":
- return this.popup.selectedIndex;
+ case "FormAutoComplete:GetSelectedIndex": {
+ if (this.openedPopup) {
+ return this.openedPopup.selectedIndex;
+ }
+ // If the popup was closed, then the selection
+ // has not changed.
+ return -1;
+ }
- case "FormAutoComplete:RemoveEntry":
- this.removeEntry(message.data.index);
- break;
-
- case "FormAutoComplete:MaybeOpenPopup":
- if (AutoCompleteE10SView.treeData.length > 0 &&
- !this.popup.popupOpen) {
- // This happens when one of the arrow keys is pressed after a search
- // has already been completed. nsAutoCompleteController tries to
- // re-use its own cache of the results without re-doing the search.
- // Detect that and show the popup here.
- this.showPopupWithResults(this._popupCache.browserWindow,
- this._popupCache.rect,
- this._resultCache);
+ case "FormAutoComplete:SetSelectedIndex": {
+ let { index } = message.data;
+ if (this.openedPopup) {
+ this.openedPopup.selectedIndex = index;
}
break;
+ }
- case "FormAutoComplete:ClosePopup":
- this.popup.closePopup();
+ case "FormAutoComplete:MaybeOpenPopup": {
+ let { results, rect, dir } = message.data;
+ this.showPopupWithResults({ browser: message.target, rect, dir,
+ results });
break;
+ }
- case "FormAutoComplete:Disconnect":
+ case "FormAutoComplete:Invalidate": {
+ let { results } = message.data;
+ this.invalidate(results);
+ break;
+ }
+
+ case "FormAutoComplete:ClosePopup": {
+ this.closePopup();
+ break;
+ }
+
+ case "FormAutoComplete:Disconnect": {
// The controller stopped controlling the current input, so clear
// any cached data. This is necessary cause otherwise we'd clear data
// only when starting a new search, but the next input could not support
// autocomplete and it would end up inheriting the existing data.
- AutoCompleteE10SView.clearResults();
+ AutoCompleteTreeView.clearResults();
break;
+ }
}
- return undefined;
+ // Returning false to pacify ESLint, but this return value is
+ // ignored by the messaging infrastructure.
+ return false;
},
+ /**
+ * Despite its name, handleEnter is what is called when the
+ * user clicks on one of the items in the popup.
+ */
handleEnter: function(aIsPopupSelection) {
- this.browser.messageManager.sendAsyncMessage(
- "FormAutoComplete:HandleEnter",
- { selectedIndex: this.popup.selectedIndex,
- isPopupSelection: aIsPopupSelection }
- );
+ let browser = this.weakBrowser ? this.weakBrowser.get()
+ : null;
+ if (browser && this.openedPopup) {
+ browser.messageManager.sendAsyncMessage(
+ "FormAutoComplete:HandleEnter",
+ { selectedIndex: this.openedPopup.selectedIndex,
+ isPopupSelection: aIsPopupSelection }
+ );
+ }
},
stopSearch: function() {}
}
-
-this.AutoCompleteE10S.init();
--- a/toolkit/components/satchel/FormHistoryStartup.js
+++ b/toolkit/components/satchel/FormHistoryStartup.js
@@ -6,19 +6,16 @@ const Cc = Components.classes;
const Ci = Components.interfaces;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
"resource://gre/modules/FormHistory.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AutoCompleteE10S",
- "resource://gre/modules/AutoCompleteE10S.jsm");
-
function FormHistoryStartup() { }
FormHistoryStartup.prototype = {
classID: Components.ID("{3A0012EB-007F-4BB8-AA81-A07385F77A25}"),
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIObserver,
Ci.nsISupportsWeakReference,
--- a/toolkit/components/satchel/moz.build
+++ b/toolkit/components/satchel/moz.build
@@ -29,16 +29,16 @@ EXTRA_COMPONENTS += [
'FormHistoryStartup.js',
'nsFormAutoComplete.js',
'nsFormHistory.js',
'nsInputListAutoComplete.js',
'satchel.manifest',
]
EXTRA_JS_MODULES += [
- 'AutoCompleteE10S.jsm',
+ 'AutoCompletePopup.jsm',
'FormHistory.jsm',
'nsFormAutoCompleteResult.jsm',
]
FINAL_LIBRARY = 'xul'
JAR_MANIFESTS += ['jar.mn']
--- a/toolkit/components/search/tests/xpcshell/test_searchSuggest.js
+++ b/toolkit/components/search/tests/xpcshell/test_searchSuggest.js
@@ -6,16 +6,23 @@
*/
"use strict";
Cu.import("resource://gre/modules/FormHistory.jsm");
Cu.import("resource://gre/modules/SearchSuggestionController.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
+// We must make sure the FormHistoryStartup component is
+// initialized in order for it to respond to FormHistory
+// requests from nsFormAutoComplete.js.
+var formHistoryStartup = Cc["@mozilla.org/satchel/form-history-startup;1"].
+ getService(Ci.nsIObserver);
+formHistoryStartup.observe(null, "profile-after-change", null);
+
var httpServer = new HttpServer();
var getEngine, postEngine, unresolvableEngine;
function run_test() {
Services.prefs.setBoolPref("browser.search.suggest.enabled", true);
removeMetadata();
updateAppInfo();
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -8,16 +8,18 @@ var Ci = Components.interfaces;
var Cu = Components.utils;
var Cr = Components.results;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
"resource://gre/modules/ReaderMode.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
var global = this;
// Lazily load the finder code
addMessageListener("Finder:Initialize", function () {
let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {});
new RemoteFinderListener(global);
@@ -767,17 +769,16 @@ var FindBar = {
}
},
/**
* Returns whether FAYT can be used for the given event in
* the current content state.
*/
_canAndShouldFastFind() {
- let {BrowserUtils} = Cu.import("resource://gre/modules/BrowserUtils.jsm", {});
let should = false;
let can = BrowserUtils.canFastFind(content);
if (can) {
//XXXgijs: why all these shenanigans? Why not use the event's target?
let focusedWindow = {};
let elt = Services.focus.getFocusedElementForWindow(content, true, focusedWindow);
let win = focusedWindow.value;
should = BrowserUtils.shouldFastFind(elt, win);
@@ -1425,50 +1426,86 @@ let AutoCompletePopup = {
controller.detachFromBrowser(docShell);
this._connected = false;
}
},
get input () { return this._input; },
get overrideValue () { return null; },
- set selectedIndex (index) { },
+ set selectedIndex (index) {
+ sendAsyncMessage("FormAutoComplete:SetSelectedIndex", { index });
+ },
get selectedIndex () {
// selectedIndex getter must be synchronous because we need the
// correct value when the controller is in controller::HandleEnter.
// We can't easily just let the parent inform us the new value every
// time it changes because not every action that can change the
// selectedIndex is trivial to catch (e.g. moving the mouse over the
// list).
return sendSyncMessage("FormAutoComplete:GetSelectedIndex", {});
},
get popupOpen () {
return this._popupOpen;
},
openAutocompletePopup: function (input, element) {
- if (!this._popupOpen) {
- // The search itself normally opens the popup itself, but in some cases,
- // nsAutoCompleteController tries to use cached results so notify our
- // popup to reuse the last results.
- sendAsyncMessage("FormAutoComplete:MaybeOpenPopup", {});
+ if (this._popupOpen || !input) {
+ return;
}
+
+ let rect = BrowserUtils.getElementBoundingScreenRect(element);
+ let window = element.ownerDocument.defaultView;
+ let dir = window.getComputedStyle(element).direction;
+ let results = this.getResultsFromController(input);
+
+ sendAsyncMessage("FormAutoComplete:MaybeOpenPopup",
+ { results, rect, dir });
this._input = input;
this._popupOpen = true;
},
closePopup: function () {
this._popupOpen = false;
sendAsyncMessage("FormAutoComplete:ClosePopup", {});
},
invalidate: function () {
+ if (this._popupOpen) {
+ let results = this.getResultsFromController(this._input);
+ sendAsyncMessage("FormAutoComplete:Invalidate", { results });
+ }
},
selectBy: function(reverse, page) {
this._index = sendSyncMessage("FormAutoComplete:SelectBy", {
reverse: reverse,
page: page
});
+ },
+
+ getResultsFromController(inputField) {
+ let results = [];
+
+ if (!inputField) {
+ return results;
+ }
+
+ let controller = inputField.controller;
+ if (!(controller instanceof Ci.nsIAutoCompleteController)) {
+ return results;
+ }
+
+ for (let i = 0; i < controller.matchCount; ++i) {
+ let result = {};
+ result.value = controller.getValueAt(i);
+ result.label = controller.getLabelAt(i);
+ result.comment = controller.getCommentAt(i);
+ result.style = controller.getStyleAt(i);
+ result.image = controller.getImageAt(i);
+ results.push(result);
+ }
+
+ return results;
}
}
AutoCompletePopup.init();