Bug 1369705 - avoid starting the search service or calling the search-one-offs XBL constructor before first paint, r=adw.

This commit is contained in:
Florian Quèze 2017-06-09 15:11:03 +02:00
Родитель 84bf3ddef3
Коммит dc628a8ae3
5 изменённых файлов: 144 добавлений и 125 удалений

Просмотреть файл

@ -47,22 +47,22 @@ const startupPhases = {
// For the following phases of startup we have only a black list for now // For the following phases of startup we have only a black list for now
// We are at this phase after creating the first browser window (ie. after final-ui-startup). // We are at this phase after creating the first browser window (ie. after final-ui-startup).
"before opening first browser window": {blacklist: { "before opening first browser window": {},
// We reach this phase right after showing the first browser window.
// This means that anything already loaded at this point has been loaded
// before first paint and delayed it.
"before first paint": {blacklist: {
components: new Set([ components: new Set([
"nsSearchService.js", "nsSearchService.js",
]) ])
}}, }},
// We reach this phase right after showing the first browser window.
// This means that anything already loaded at this point has been loaded
// before first paint and delayed it.
"before first paint": {},
// We are at this phase once we are ready to handle user events. // We are at this phase once we are ready to handle user events.
// Anything loaded at this phase or before gets in the way of the user // Anything loaded at this phase or before gets in the way of the user
// interacting with the first browser window. // interacting with the first browser window.
"before handling user events": {}, "before handling user events": {},
} };
function test() { function test() {
if (!AppConstants.NIGHTLY_BUILD && !AppConstants.DEBUG) { if (!AppConstants.NIGHTLY_BUILD && !AppConstants.DEBUG) {

Просмотреть файл

@ -121,7 +121,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
cxmenu.insertBefore(pasteAndGo, insertLocation.nextSibling); cxmenu.insertBefore(pasteAndGo, insertLocation.nextSibling);
} }
this._enableOrDisableOneOffSearches(); this.popup.addEventListener("popupshowing", () => {
this._enableOrDisableOneOffSearches();
}, {capturing: true, once: true});
// The autocomplete controller uses heuristic on some internal caches // The autocomplete controller uses heuristic on some internal caches
// to handle cases like backspace, autofill or repeated searches. // to handle cases like backspace, autofill or repeated searches.

Просмотреть файл

@ -70,6 +70,8 @@
<implementation implements="nsIObserver"> <implementation implements="nsIObserver">
<constructor><![CDATA[ <constructor><![CDATA[
this._textbox.placeholder = this._stringBundle.getString("searchPlaceholder");
if (this.parentNode.parentNode.localName == "toolbarpaletteitem") if (this.parentNode.parentNode.localName == "toolbarpaletteitem")
return; return;
@ -77,27 +79,36 @@
this._initialized = true; this._initialized = true;
Services.search.init(aStatus => { (window.delayedStartupPromise || Promise.resolve()).then(() => {
// Bail out if the binding's been destroyed window.requestIdleCallback(() => {
if (!this._initialized) Services.search.init(aStatus => {
return; // Bail out if the binding's been destroyed
if (!this._initialized)
return;
if (Components.isSuccessCode(aStatus)) { if (Components.isSuccessCode(aStatus)) {
// Refresh the display (updating icon, etc) // Refresh the display (updating icon, etc)
this.updateDisplay(); this.updateDisplay();
BrowserSearch.updateOpenSearchBadge(); BrowserSearch.updateOpenSearchBadge();
} else { } else {
Components.utils.reportError("Cannot initialize search service, bailing out: " + aStatus); Components.utils.reportError("Cannot initialize search service, bailing out: " + aStatus);
} }
});
});
}); });
// Some accessibility tests create their own <searchbar> that doesn't // Wait until the popupshowing event to avoid forcing immediate
// use the popup binding below, so null-check oneOffButtons. // attachment of the search-one-offs binding.
if (this.textbox.popup.oneOffButtons) { this.textbox.popup.addEventListener("popupshowing", () => {
this.textbox.popup.oneOffButtons.telemetryOrigin = "searchbar"; let oneOffButtons = this.textbox.popup.oneOffButtons;
this.textbox.popup.oneOffButtons.popup = this.textbox.popup; // Some accessibility tests create their own <searchbar> that doesn't
this.textbox.popup.oneOffButtons.textbox = this.textbox; // use the popup binding below, so null-check oneOffButtons.
} if (oneOffButtons) {
oneOffButtons.telemetryOrigin = "searchbar";
oneOffButtons.popup = this.textbox.popup;
oneOffButtons.textbox = this.textbox;
}
}, {capturing: true, once: true});
]]></constructor> ]]></constructor>
<destructor><![CDATA[ <destructor><![CDATA[
@ -286,8 +297,6 @@
var name = this.currentEngine.name; var name = this.currentEngine.name;
var text = this._stringBundle.getFormattedString("searchtip", [name]); var text = this._stringBundle.getFormattedString("searchtip", [name]);
this._textbox.placeholder = this._stringBundle.getString("searchPlaceholder");
this._textbox.label = text; this._textbox.label = text;
this._textbox.tooltipText = text; this._textbox.tooltipText = text;
]]></body> ]]></body>
@ -567,108 +576,115 @@
<binding id="searchbar-textbox" <binding id="searchbar-textbox"
extends="chrome://global/content/bindings/autocomplete.xml#autocomplete"> extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
<implementation implements="nsIObserver"> <implementation>
<constructor><![CDATA[ <constructor><![CDATA[
const kXULNS =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
if (document.getBindingParent(this).parentNode.parentNode.localName == if (document.getBindingParent(this).parentNode.parentNode.localName ==
"toolbarpaletteitem") "toolbarpaletteitem")
return; return;
// Initialize fields
this._stringBundle = document.getBindingParent(this)._stringBundle;
this._suggestEnabled =
Services.prefs.getBoolPref("browser.search.suggest.enabled");
if (Services.prefs.getBoolPref("browser.urlbar.clickSelectsAll")) if (Services.prefs.getBoolPref("browser.urlbar.clickSelectsAll"))
this.setAttribute("clickSelectsAll", true); this.setAttribute("clickSelectsAll", true);
// Add items to context menu and attach controller to handle them
var textBox = document.getAnonymousElementByAttribute(this, var textBox = document.getAnonymousElementByAttribute(this,
"anonid", "textbox-input-box"); "anonid", "textbox-input-box");
var cxmenu = document.getAnonymousElementByAttribute(textBox, var cxmenu = document.getAnonymousElementByAttribute(textBox,
"anonid", "input-box-contextmenu"); "anonid", "input-box-contextmenu");
var pasteAndSearch; cxmenu.addEventListener("popupshowing",
cxmenu.addEventListener("popupshowing", function() { () => { this.initContextMenu(cxmenu); },
BrowserSearch.searchBar._textbox.closePopup(); {capturing: true, once: true});
if (!pasteAndSearch)
return;
var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
var enabled = controller.isCommandEnabled("cmd_paste");
if (enabled)
pasteAndSearch.removeAttribute("disabled");
else
pasteAndSearch.setAttribute("disabled", "true");
});
var element, label, akey;
element = document.createElementNS(kXULNS, "menuseparator");
cxmenu.appendChild(element);
this.setAttribute("aria-owns", this.popup.id); this.setAttribute("aria-owns", this.popup.id);
var insertLocation = cxmenu.firstChild;
while (insertLocation.nextSibling &&
insertLocation.getAttribute("cmd") != "cmd_paste")
insertLocation = insertLocation.nextSibling;
if (insertLocation) {
element = document.createElementNS(kXULNS, "menuitem");
label = this._stringBundle.getString("cmd_pasteAndSearch");
element.setAttribute("label", label);
element.setAttribute("anonid", "paste-and-search");
element.setAttribute("oncommand", "BrowserSearch.pasteAndSearch(event)");
cxmenu.insertBefore(element, insertLocation.nextSibling);
pasteAndSearch = element;
}
element = document.createElementNS(kXULNS, "menuitem");
label = this._stringBundle.getString("cmd_clearHistory");
akey = this._stringBundle.getString("cmd_clearHistory_accesskey");
element.setAttribute("label", label);
element.setAttribute("accesskey", akey);
element.setAttribute("cmd", "cmd_clearhistory");
cxmenu.appendChild(element);
element = document.createElementNS(kXULNS, "menuitem");
label = this._stringBundle.getString("cmd_showSuggestions");
akey = this._stringBundle.getString("cmd_showSuggestions_accesskey");
element.setAttribute("anonid", "toggle-suggest-item");
element.setAttribute("label", label);
element.setAttribute("accesskey", akey);
element.setAttribute("cmd", "cmd_togglesuggest");
element.setAttribute("type", "checkbox");
element.setAttribute("checked", this._suggestEnabled);
element.setAttribute("autocheck", "false");
this._suggestMenuItem = element;
cxmenu.appendChild(element);
this.addEventListener("keypress", aEvent => {
if (navigator.platform.startsWith("Mac") && aEvent.keyCode == KeyEvent.VK_F4)
this.openSearch()
}, true);
this.controllers.appendController(this.searchbarController);
document.getBindingParent(this)._textboxInitialized = true; document.getBindingParent(this)._textboxInitialized = true;
// Add observer for suggest preference
Services.prefs.addObserver("browser.search.suggest.enabled", this);
]]></constructor> ]]></constructor>
<destructor><![CDATA[ <destructor><![CDATA[
Services.prefs.removeObserver("browser.search.suggest.enabled", this); // If the context menu has never been opened, there won't be anything
// to remove here.
// Because XBL and the customize toolbar code interacts poorly, // Also, XBL and the customize toolbar code sometimes interact poorly.
// there may not be anything to remove here
try { try {
this.controllers.removeController(this.searchbarController); this.controllers.removeController(this.searchbarController);
} catch (ex) { } } catch (ex) { }
]]></destructor> ]]></destructor>
<field name="_stringBundle"/> // Add items to context menu and attach controller to handle them the
<field name="_suggestMenuItem"/> // first time the context menu is opened.
<field name="_suggestEnabled"/> <method name="initContextMenu">
<parameter name="aMenu"/>
<body><![CDATA[
const kXULNS =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
let stringBundle = document.getBindingParent(this)._stringBundle;
let pasteAndSearch, suggestMenuItem;
let element, label, akey;
element = document.createElementNS(kXULNS, "menuseparator");
aMenu.appendChild(element);
let insertLocation = aMenu.firstChild;
while (insertLocation.nextSibling &&
insertLocation.getAttribute("cmd") != "cmd_paste")
insertLocation = insertLocation.nextSibling;
if (insertLocation) {
element = document.createElementNS(kXULNS, "menuitem");
label = stringBundle.getString("cmd_pasteAndSearch");
element.setAttribute("label", label);
element.setAttribute("anonid", "paste-and-search");
element.setAttribute("oncommand", "BrowserSearch.pasteAndSearch(event)");
aMenu.insertBefore(element, insertLocation.nextSibling);
pasteAndSearch = element;
}
element = document.createElementNS(kXULNS, "menuitem");
label = stringBundle.getString("cmd_clearHistory");
akey = stringBundle.getString("cmd_clearHistory_accesskey");
element.setAttribute("label", label);
element.setAttribute("accesskey", akey);
element.setAttribute("cmd", "cmd_clearhistory");
aMenu.appendChild(element);
element = document.createElementNS(kXULNS, "menuitem");
label = stringBundle.getString("cmd_showSuggestions");
akey = stringBundle.getString("cmd_showSuggestions_accesskey");
element.setAttribute("anonid", "toggle-suggest-item");
element.setAttribute("label", label);
element.setAttribute("accesskey", akey);
element.setAttribute("cmd", "cmd_togglesuggest");
element.setAttribute("type", "checkbox");
element.setAttribute("autocheck", "false");
suggestMenuItem = element;
aMenu.appendChild(element);
if (AppConstants.platform == "macosx") {
this.addEventListener("keypress", aEvent => {
if (aEvent.keyCode == KeyEvent.DOM_VK_F4)
this.openSearch()
}, true);
}
this.controllers.appendController(this.searchbarController);
let onpopupshowing = function() {
BrowserSearch.searchBar._textbox.closePopup();
if (suggestMenuItem) {
let enabled =
Services.prefs.getBoolPref("browser.search.suggest.enabled");
suggestMenuItem.setAttribute("checked", enabled);
}
if (!pasteAndSearch)
return;
let controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
let enabled = controller.isCommandEnabled("cmd_paste");
if (enabled)
pasteAndSearch.removeAttribute("disabled");
else
pasteAndSearch.setAttribute("disabled", "true");
};
aMenu.addEventListener("popupshowing", onpopupshowing);
onpopupshowing();
]]></body>
</method>
<!-- <!--
This overrides the searchParam property in autocomplete.xml. We're This overrides the searchParam property in autocomplete.xml. We're
@ -749,19 +765,6 @@
]]></body> ]]></body>
</method> </method>
<method name="observe">
<parameter name="aSubject"/>
<parameter name="aTopic"/>
<parameter name="aData"/>
<body><![CDATA[
if (aTopic == "nsPref:changed") {
this._suggestEnabled =
Services.prefs.getBoolPref("browser.search.suggest.enabled");
this._suggestMenuItem.setAttribute("checked", this._suggestEnabled);
}
]]></body>
</method>
<method name="openSearch"> <method name="openSearch">
<body> <body>
<![CDATA[ <![CDATA[
@ -863,10 +866,10 @@
this._self.value = ""; this._self.value = "";
break; break;
case "cmd_togglesuggest": case "cmd_togglesuggest":
// The pref observer will update _suggestEnabled and the menu let enabled =
// checkmark. Services.prefs.getBoolPref("browser.search.suggest.enabled");
Services.prefs.setBoolPref("browser.search.suggest.enabled", Services.prefs.setBoolPref("browser.search.suggest.enabled",
!this._self._suggestEnabled); !enabled);
break; break;
default: default:
// do nothing with unrecognized command // do nothing with unrecognized command

Просмотреть файл

@ -232,6 +232,14 @@ add_task(async function testAutocomplete() {
}); });
add_task(async function testClearHistory() { add_task(async function testClearHistory() {
// Open the textbox context menu to trigger controller attachment.
let textbox = searchBar.textbox;
let popupShownPromise = BrowserTestUtils.waitForEvent(textbox, "popupshown");
EventUtils.synthesizeMouseAtCenter(textbox, { type: "contextmenu", button: 2 });
await popupShownPromise;
// Close the context menu.
EventUtils.synthesizeKey("VK_ESCAPE", {});
let controller = searchBar.textbox.controllers.getControllerForCommand("cmd_clearhistory") let controller = searchBar.textbox.controllers.getControllerForCommand("cmd_clearhistory")
ok(controller.isCommandEnabled("cmd_clearhistory"), "Clear history command enabled"); ok(controller.isCommandEnabled("cmd_clearhistory"), "Clear history command enabled");
controller.doCommand("cmd_clearhistory"); controller.doCommand("cmd_clearhistory");

Просмотреть файл

@ -6,6 +6,12 @@ const {classes: Cc, utils: Cu, interfaces: Ci} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
let firstPaintNotification = "widget-first-paint";
// widget-first-paint fires much later than expected on Linux.
if (AppConstants.platform == "linux")
firstPaintNotification = "xul-window-visible";
/** /**
* The startupRecorder component observes notifications at various stages of * The startupRecorder component observes notifications at various stages of
@ -42,7 +48,7 @@ startupRecorder.prototype = {
let topics = [ let topics = [
"profile-do-change", // This catches stuff loaded during app-startup "profile-do-change", // This catches stuff loaded during app-startup
"toplevel-window-ready", // Catches stuff from final-ui-startup "toplevel-window-ready", // Catches stuff from final-ui-startup
"widget-first-paint", firstPaintNotification,
"sessionstore-windows-restored", "sessionstore-windows-restored",
]; ];
for (let t of topics) for (let t of topics)
@ -61,8 +67,8 @@ startupRecorder.prototype = {
const topicsToNames = { const topicsToNames = {
"profile-do-change": "before profile selection", "profile-do-change": "before profile selection",
"toplevel-window-ready": "before opening first browser window", "toplevel-window-ready": "before opening first browser window",
"widget-first-paint": "before first paint",
}; };
topicsToNames[firstPaintNotification] = "before first paint";
this.record(topicsToNames[topic]); this.record(topicsToNames[topic]);
} }
} }