Bug 1344924 - Contextual onboarding for search suggestions in the awesomebar. r=adw

Make the onboarding being opt-in or opt-out depending on the default value of the
browser.urlbar.sugges.searches pref.
In both cases respect userMadeSearchSuggestionChoice though, so we won't further nag users that
made a choice already.

MozReview-Commit-ID: D4rRMRbdMrW

--HG--
rename : browser/base/content/test/urlbar/browser_urlbarSearchSuggestionsNotification.js => browser/base/content/test/urlbar/browser_urlbarSearchSuggestions_opt-in.js
extra : rebase_source : 2eb22b5965734d9702b115bc653ce55174003221
This commit is contained in:
Marco Bonardo 2017-04-05 15:01:02 +02:00
Родитель f3c162e4e1
Коммит 33d0ed1409
14 изменённых файлов: 498 добавлений и 149 удалений

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

@ -320,10 +320,11 @@ pref("browser.urlbar.suggest.bookmark", true);
pref("browser.urlbar.suggest.openpage", true);
pref("browser.urlbar.suggest.searches", false);
pref("browser.urlbar.userMadeSearchSuggestionsChoice", false);
// 4 here means the suggestion notification will be automatically
// hidden the 4th day, so it will actually be shown on 3 different days.
// The suggestion opt-in notification will be shown on 4 different days.
pref("browser.urlbar.daysBeforeHidingSuggestionsPrompt", 4);
pref("browser.urlbar.lastSuggestionsPromptDate", 20160601);
// The suggestion opt-out hint will be hidden after being shown 4 times.
pref("browser.urlbar.timesBeforeHidingSuggestionsHint", 4);
// Limit the number of characters sent to the current search engine to fetch
// suggestions.

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

@ -610,12 +610,12 @@ html|input.urlbar-input[textoverflow]:not([focused]) {
transition: height 100ms;
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] {
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] {
visibility: collapse;
transition: margin-top 100ms;
}
#PopupAutoCompleteRichResult.showSearchSuggestionsNotification > hbox[anonid="search-suggestions-notification"] {
#PopupAutoCompleteRichResult.showSearchSuggestionsNotification > deck[anonid="search-suggestions-notification"] {
visibility: visible;
}

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

@ -80,7 +80,11 @@ support-files =
support-files =
searchSuggestionEngine.xml
searchSuggestionEngine.sjs
[browser_urlbarSearchSuggestionsNotification.js]
[browser_urlbarSearchSuggestions_opt-in.js]
support-files =
searchSuggestionEngine.xml
searchSuggestionEngine.sjs
[browser_urlbarSearchSuggestions_opt-out.js]
support-files =
searchSuggestionEngine.xml
searchSuggestionEngine.sjs

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

@ -25,11 +25,12 @@ add_task(async function searchSuggestions() {
let oldCurrentEngine = Services.search.currentEngine;
Services.search.currentEngine = engine;
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
registerCleanupFunction(function() {
Services.search.currentEngine = oldCurrentEngine;
Services.prefs.clearUserPref(SUGGEST_ALL_PREF);
Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
});
await promiseAutocompleteResultPopup("foo");

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

@ -3,12 +3,13 @@ const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
// Must run first.
add_task(async function prepare() {
let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
let oldCurrentEngine = Services.search.currentEngine;
Services.search.currentEngine = engine;
registerCleanupFunction(async function() {
Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
Services.search.currentEngine = oldCurrentEngine;
// Clicking suggestions causes visits to search results pages, so clear that

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

@ -8,14 +8,19 @@ add_task(async function prepare() {
let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
let oldCurrentEngine = Services.search.currentEngine;
Services.search.currentEngine = engine;
let defaults = Services.prefs.getDefaultBranch("browser.urlbar.");
let searchSuggestionsDefault = defaults.getBoolPref("suggest.searches");
defaults.setBoolPref("suggest.searches", false);
let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
registerCleanupFunction(async function() {
defaults.setBoolPref("suggest.searches", searchSuggestionsDefault);
Services.search.currentEngine = oldCurrentEngine;
Services.prefs.clearUserPref(SUGGEST_ALL_PREF);
Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
// Disable the notification for future tests so it doesn't interfere with
// them. clearUserPref() won't work because by default the pref is false.
await setUserMadeChoicePref(true);
Services.prefs.setBoolPref(CHOICE_PREF, true);
// Make sure the popup is closed for the next test.
gURLBar.blur();
@ -26,16 +31,14 @@ add_task(async function prepare() {
add_task(async function focus() {
// Focusing the urlbar used to open the popup in order to show the
// notification, but it doesn't anymore. Make sure it does not.
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
await setUserMadeChoicePref(false);
setupVisibleNotification();
gURLBar.blur();
gURLBar.focus();
Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed");
});
add_task(async function dismissWithoutResults() {
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
await setUserMadeChoicePref(false);
setupVisibleNotification();
gURLBar.blur();
gURLBar.focus();
let popupPromise = promisePopupShown(gURLBar.popup);
@ -60,8 +63,7 @@ add_task(async function dismissWithoutResults() {
});
add_task(async function dismissWithResults() {
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
await setUserMadeChoicePref(false);
setupVisibleNotification();
gURLBar.blur();
gURLBar.focus();
await promiseAutocompleteResultPopup("foo");
@ -84,8 +86,7 @@ add_task(async function dismissWithResults() {
});
add_task(async function disable() {
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
await setUserMadeChoicePref(false);
setupVisibleNotification();
gURLBar.blur();
gURLBar.focus();
await promiseAutocompleteResultPopup("foo");
@ -103,9 +104,7 @@ add_task(async function disable() {
});
add_task(async function enable() {
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
await setUserMadeChoicePref(false);
setupVisibleNotification();
gURLBar.blur();
gURLBar.focus();
await promiseAutocompleteResultPopup("foo");
@ -131,6 +130,7 @@ add_task(async function enable() {
add_task(async function privateWindow() {
// Since suggestions are disabled in private windows, the notification should
// not appear even when suggestions are otherwise enabled.
setupVisibleNotification();
let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
win.gURLBar.blur();
win.gURLBar.focus();
@ -143,9 +143,7 @@ add_task(async function privateWindow() {
add_task(async function multipleWindows() {
// Opening multiple windows, using their urlbars, and then dismissing the
// notification in one should dismiss the notification in all.
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
await setUserMadeChoicePref(false);
setupVisibleNotification();
gURLBar.focus();
await promiseAutocompleteResultPopup("win1");
@ -184,39 +182,19 @@ add_task(async function multipleWindows() {
add_task(async function enableOutsideNotification() {
// Setting the suggest.searches pref outside the notification (e.g., by
// ticking the checkbox in the preferences window) should hide it.
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
await setUserMadeChoicePref(false);
setupVisibleNotification();
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
gURLBar.focus();
await promiseAutocompleteResultPopup("foo");
assertVisible(false);
});
/**
* Setting the choice pref triggers a pref observer in the urlbar, which hides
* the notification if it's present. This function returns a promise that's
* resolved once the observer fires.
*
* @param userMadeChoice A boolean, the pref's new value.
* @return A Promise that's resolved when the observer fires -- or, if the pref
* is currently the given value, that's resolved immediately.
*/
function setUserMadeChoicePref(userMadeChoice) {
return new Promise(resolve => {
let currentUserMadeChoice = Services.prefs.getBoolPref(CHOICE_PREF);
if (currentUserMadeChoice != userMadeChoice) {
Services.prefs.addObserver(CHOICE_PREF, function obs(subj, topic, data) {
Services.prefs.removeObserver(CHOICE_PREF, obs);
resolve();
});
}
Services.prefs.setBoolPref(CHOICE_PREF, userMadeChoice);
if (currentUserMadeChoice == userMadeChoice) {
resolve();
}
});
function setupVisibleNotification() {
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
// Toggle to reset the whichNotification cache.
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
Services.prefs.setBoolPref(CHOICE_PREF, false);
}
function suggestionsPresent() {

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

@ -0,0 +1,118 @@
// The order of the tests here matters!
const SUGGEST_ALL_PREF = "browser.search.suggest.enabled";
const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
const CHOICE_PREF = "browser.urlbar.userMadeSearchSuggestionsChoice";
const TIMES_PREF = "browser.urlbar.timesBeforeHidingSuggestionsHint";
const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
add_task(async function prepare() {
let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
let oldCurrentEngine = Services.search.currentEngine;
Services.search.currentEngine = engine;
let defaults = Services.prefs.getDefaultBranch("browser.urlbar.");
let searchSuggestionsDefault = defaults.getBoolPref("suggest.searches");
defaults.setBoolPref("suggest.searches", true);
let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
let suggestionsChoice = Services.prefs.getBoolPref(CHOICE_PREF);
Services.prefs.setBoolPref(CHOICE_PREF, false);
registerCleanupFunction(async function() {
defaults.setBoolPref("suggest.searches", searchSuggestionsDefault);
Services.search.currentEngine = oldCurrentEngine;
Services.prefs.clearUserPref(SUGGEST_ALL_PREF);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
Services.prefs.setBoolPref(CHOICE_PREF, suggestionsChoice);
// Make sure the popup is closed for the next test.
gURLBar.blur();
Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
});
});
add_task(async function focus() {
// Focusing the urlbar should open the popup in order to show the
// notification.
setupVisibleHint();
gURLBar.blur();
let popupPromise = promisePopupShown(gURLBar.popup);
gURLBar.focus();
await popupPromise;
Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
assertVisible(true);
Assert.equal(gURLBar.popup._matchCount, 0, "popup should have no results");
// Start searching.
EventUtils.synthesizeKey("r", {});
EventUtils.synthesizeKey("n", {});
EventUtils.synthesizeKey("d", {});
await promiseSearchComplete();
Assert.ok(suggestionsPresent());
assertVisible(true);
// Check the Change Options link.
let changeOptionsLink = document.getElementById("search-suggestions-change-settings");
let prefsPromise = BrowserTestUtils.waitForLocationChange(gBrowser, "about:preferences#general-search");
changeOptionsLink.click();
await prefsPromise;
Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
});
add_task(async function privateWindow() {
// Since suggestions are disabled in private windows, the notification should
// not appear even when suggestions are otherwise enabled.
setupVisibleHint();
let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
await promiseAutocompleteResultPopup("foo", win);
assertVisible(false, win);
win.gURLBar.blur();
await BrowserTestUtils.closeWindow(win);
});
add_task(async function enableOutsideNotification() {
// Setting the suggest.searches pref outside the notification (e.g., by
// ticking the checkbox in the preferences window) should hide it.
setupVisibleHint();
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
await promiseAutocompleteResultPopup("foo");
assertVisible(false);
});
add_task(async function userMadeChoice() {
// If the user made a choice already, he should not see the hint.
setupVisibleHint();
Services.prefs.setBoolPref(CHOICE_PREF, true);
await promiseAutocompleteResultPopup("foo");
assertVisible(false);
});
function setupVisibleHint() {
Services.prefs.clearUserPref(TIMES_PREF);
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
// Toggle to reset the whichNotification cache.
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
}
function suggestionsPresent() {
let controller = gURLBar.popup.input.controller;
let matchCount = controller.matchCount;
for (let i = 0; i < matchCount; i++) {
let url = controller.getValueAt(i);
let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
if (mozActionMatch) {
let [, type, paramStr] = mozActionMatch;
let params = JSON.parse(paramStr);
if (type == "searchengine" && "searchSuggestion" in params) {
return true;
}
}
}
return false;
}
function assertVisible(visible, win = window) {
let style =
win.getComputedStyle(win.gURLBar.popup.searchSuggestionsNotification);
Assert.equal(style.visibility, visible ? "visible" : "collapse");
}

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

@ -7,13 +7,14 @@ const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
// Must run first.
add_task(async function prepare() {
let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
let oldCurrentEngine = Services.search.currentEngine;
Services.search.currentEngine = engine;
registerCleanupFunction(async function() {
Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
Services.search.currentEngine = oldCurrentEngine;
// Clicking urlbar results causes visits to their associated pages, so clear

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

@ -70,15 +70,18 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
this._prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService)
.getBranch("browser.urlbar.");
this._prefs.addObserver("", this);
this._defaultPrefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService)
.getDefaultBranch("browser.urlbar.");
this.clickSelectsAll = this._prefs.getBoolPref("clickSelectsAll");
this.doubleClickSelectsAll = this._prefs.getBoolPref("doubleClickSelectsAll");
this.completeDefaultIndex = this._prefs.getBoolPref("autoFill");
this.timeout = this._prefs.getIntPref("delay");
this._formattingEnabled = this._prefs.getBoolPref("formatting.enabled");
this._mayTrimURLs = this._prefs.getBoolPref("trimURLs");
this._cacheUserMadeSearchSuggestionsChoice();
this.inputField.controllers.insertControllerAt(0, this._copyCutController);
this.inputField.addEventListener("paste", this);
this.inputField.addEventListener("mousedown", this);
@ -945,14 +948,8 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
// binding's constructor.
this._prefs.setBoolPref("searchSuggestionsChoice",
this._prefs.getBoolPref("suggest.searches"));
// fall-through.
case "userMadeSearchSuggestionsChoice":
this._cacheUserMadeSearchSuggestionsChoice();
if (this._userMadeSearchSuggestionsChoice) {
this.popup.searchSuggestionsNotificationWasDismissed(
this._prefs.getBoolPref("suggest.searches")
);
}
// Clear the cached value to allow changing conditions in tests.
delete this._whichSearchSuggestionsNotification;
break;
case "trimURLs":
this._mayTrimURLs = this._prefs.getBoolPref(aData);
@ -1203,30 +1200,70 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
]]></body>
</method>
<field name="_userMadeSearchSuggestionsChoice"><![CDATA[
false
]]></field>
<method name="_cacheUserMadeSearchSuggestionsChoice">
<body><![CDATA[
this._userMadeSearchSuggestionsChoice =
this._prefs.getBoolPref("userMadeSearchSuggestionsChoice") ||
this._prefs.getBoolPref("suggest.searches");
]]></body>
</method>
<property name="shouldShowSearchSuggestionsNotification" readonly="true">
<property name="_userMadeSearchSuggestionsChoice" readonly="true">
<getter><![CDATA[
return !this._userMadeSearchSuggestionsChoice &&
!this.inPrivateContext &&
// When _urlbarFocused is true, tabbrowser would close the
// popup if it's opened here, so don't show the notification.
!gBrowser.selectedBrowser._urlbarFocused &&
Services.prefs.getBoolPref("browser.search.suggest.enabled") &&
this._prefs.getIntPref("daysBeforeHidingSuggestionsPrompt");
return this._prefs.getBoolPref("userMadeSearchSuggestionsChoice") ||
this._defaultPrefs.getBoolPref("suggest.searches") != this._prefs.getBoolPref("suggest.searches");
]]></getter>
</property>
<property name="whichSearchSuggestionsNotification" readonly="true">
<getter><![CDATA[
// Once we return "none" once, we'll always return "none".
// If available, use the cached value, rather than running all of the
// checks again at every locationbar focus.
if (this._whichSearchSuggestionsNotification) {
return this._whichSearchSuggestionsNotification;
}
if (Services.prefs.getBoolPref("browser.search.suggest.enabled") &&
!this.inPrivateContext &&
// When _urlbarFocused is true, tabbrowser would close the
// popup if it's opened here, so don't show the notification.
!gBrowser.selectedBrowser._urlbarFocused &&
// In any case, if the user made a choice we should not nag him.
!this._userMadeSearchSuggestionsChoice) {
let enabledByDefault = this._defaultPrefs.getBoolPref("suggest.searches");
if (!enabledByDefault &&
this._prefs.getIntPref("daysBeforeHidingSuggestionsPrompt")) {
return "opt-in";
}
if (enabledByDefault &&
// Has not been switched off.
this._prefs.getBoolPref("suggest.searches") &&
this._prefs.getIntPref("timesBeforeHidingSuggestionsHint")) {
return "opt-out";
}
}
return this._whichSearchSuggestionsNotification = "none";
]]></getter>
</property>
<method name="updateSearchSuggestionsNotificationImpressions">
<parameter name="whichNotification"/>
<body><![CDATA[
if (whichNotification == "none") {
throw new Error("Unexpected notification type");
}
let useDays = whichNotification == "opt-in";
let prefName = useDays ? "daysBeforeHidingSuggestionsPrompt"
: "timesBeforeHidingSuggestionsHint";
let remaining = this._prefs.getIntPref(prefName);
if (remaining <= 0)
return;
let now = new Date();
let date = now.getFullYear() * 10000 + (now.getMonth() + 1) * 100 + now.getDate();
let previousDate = this._prefs.getIntPref("lastSuggestionsPromptDate");
if (!useDays || previousDate != date) {
this._prefs.setIntPref(prefName, remaining - 1);
}
this._prefs.setIntPref("lastSuggestionsPromptDate", date);
]]></body>
</method>
</implementation>
<handlers>
@ -1258,6 +1295,38 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
if (this.getAttribute("pageproxystate") != "valid") {
UpdatePopupNotificationsVisibility();
}
// We may want to show the search suggestions hint.
if (!gBrowser.selectedBrowser._urlbarFocused) {
// We show the opt-out notification on every kind of focus to the urlbar
// included opening a new tab, but we want to enforce at least one
// notification when the user focuses it with the mouse.
let whichNotification = this.whichSearchSuggestionsNotification;
if (whichNotification == "opt-out" &&
this._showSearchSuggestionNotificationOnMouseFocus === undefined) {
this._showSearchSuggestionNotificationOnMouseFocus = true;
}
// Check whether the focus change came from a user mouse action.
let focusMethod = Services.focus.getLastFocusMethod(window);
let mouseFocused = !!(focusMethod & Services.focus.FLAG_BYMOUSE);
if (this._showSearchSuggestionNotificationOnMouseFocus &&
mouseFocused) {
// Force showing the opt-out notification.
this._whichSearchSuggestionsNotification = whichNotification = "opt-out";
}
if (whichNotification == "opt-out") {
try {
this.popup.openAutocompletePopup(this, this);
} finally {
if (mouseFocused) {
delete this._whichSearchSuggestionsNotification;
this._showSearchSuggestionNotificationOnMouseFocus = false;
}
}
}
}
}
]]></handler>
@ -1335,34 +1404,62 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
<content ignorekeys="true" level="top" consumeoutsideclicks="never"
aria-owns="richlistbox">
<xul:hbox anonid="search-suggestions-notification"
<xul:deck anonid="search-suggestions-notification"
align="center"
role="alert"
aria-describedby="search-suggestions-notification-text">
<xul:description flex="1">
&urlbar.searchSuggestionsNotification.question;
<!-- Several things here are to make the label accessibile via an
accesskey so that a11y doesn't suck: the accesskey, using an
onclick handler instead of an href attribute, the control
attribute, and having the control attribute refer to a valid ID
that is the label itself. -->
<xul:label id="search-suggestions-notification-learn-more"
selectedIndex="0">
<!-- OPT-IN -->
<xul:hbox flex="1" align="center" anonid="search-suggestions-opt-in">
<xul:description flex="1" id="search-suggestions-question">
&urlbar.searchSuggestionsNotification.question;
<!-- Several things here are to make the label accessibile via an
accesskey so that a11y doesn't suck: the accesskey, using an
onclick handler instead of an href attribute, the control
attribute, and having the control attribute refer to a valid ID
that is the label itself. -->
<xul:label id="search-suggestions-learn-more"
class="text-link"
role="link"
value="&urlbar.searchSuggestionsNotification.learnMore;"
accesskey="&urlbar.searchSuggestionsNotification.learnMore.accesskey;"
onclick="document.getBindingParent(this).openSearchSuggestionsNotificationLearnMoreURL();"
control="search-suggestions-learn-more"/>
</xul:description>
<xul:button anonid="search-suggestions-notification-disable"
label="&urlbar.searchSuggestionsNotification.disable;"
accesskey="&urlbar.searchSuggestionsNotification.disable.accesskey;"
onclick="document.getBindingParent(this).dismissSearchSuggestionsNotification(false);"/>
<xul:button anonid="search-suggestions-notification-enable"
label="&urlbar.searchSuggestionsNotification.enable;"
accesskey="&urlbar.searchSuggestionsNotification.enable.accesskey;"
onclick="document.getBindingParent(this).dismissSearchSuggestionsNotification(true);"/>
</xul:hbox>
<!-- OPT-OUT -->
<xul:hbox flex="1" align="center" anonid="search-suggestions-opt-out">
<xul:image class="ac-site-icon" type="searchengine"/>
<xul:hbox anonid="search-suggestions-hint-typing">
<xul:description class="ac-title-text">&brandShortName;</xul:description>
</xul:hbox>
<xul:hbox anonid="search-suggestions-hint-box" flex="1">
<xul:description id="search-suggestions-hint">
<html:span class="prefix">&#x1f4a1; &urlbar.searchSuggestionsNotification.hintPrefix;</html:span>
<html:span>&urlbar.searchSuggestionsNotification.hint;</html:span>
</xul:description>
</xul:hbox>
<xul:label id="search-suggestions-change-settings"
class="text-link"
role="link"
value="&urlbar.searchSuggestionsNotification.learnMore;"
accesskey="&urlbar.searchSuggestionsNotification.learnMore.accesskey;"
onclick="document.getBindingParent(this).openSearchSuggestionsNotificationLearnMoreURL();"
control="search-suggestions-notification-learn-more"/>
</xul:description>
<xul:button anonid="search-suggestions-notification-disable"
label="&urlbar.searchSuggestionsNotification.disable;"
accesskey="&urlbar.searchSuggestionsNotification.disable.accesskey;"
onclick="document.getBindingParent(this).dismissSearchSuggestionsNotification(false);"/>
<xul:button anonid="search-suggestions-notification-enable"
label="&urlbar.searchSuggestionsNotification.enable;"
accesskey="&urlbar.searchSuggestionsNotification.enable.accesskey;"
onclick="document.getBindingParent(this).dismissSearchSuggestionsNotification(true);"/>
</xul:hbox>
#ifdef XP_WIN
value="&urlbar.searchSuggestionsNotification.changeSettingsWin;"
accesskey="&urlbar.searchSuggestionsNotification.changeSettingsWin.accesskey;"
#else
value="&urlbar.searchSuggestionsNotification.changeSettingsUnix;"
accesskey="&urlbar.searchSuggestionsNotification.changeSettingsUnix.accesskey;"
#endif
onclick="openPreferences('general-search');"
control="search-suggestions-change-settings"/>
</xul:hbox>
</xul:deck>
<xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox"
flex="1"/>
<xul:hbox anonid="footer">
@ -1377,6 +1474,11 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
</content>
<implementation>
<field name="DOMWindowUtils">
window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
</field>
<field name="_maxResults">0</field>
<field name="_bundle" readonly="true">
@ -1463,7 +1565,10 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
Services.prefs.setBoolPref(
"browser.urlbar.userMadeSearchSuggestionsChoice", true
);
// The input's pref observer will now hide the notification.
// Hide the notification.
this.searchSuggestionsNotificationWasDismissed(
Services.prefs.getBoolPref("browser.urlbar.suggest.searches")
);
]]></body>
</method>
@ -1551,29 +1656,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
// to avoid impacting startup / new window performance
aInput.popup.hidden = false;
let showNotification = aInput.shouldShowSearchSuggestionsNotification;
if (showNotification) {
let prefs = aInput._prefs;
let now = new Date();
let date = now.getFullYear() * 10000 + (now.getMonth() + 1) * 100 + now.getDate();
let previousDate = prefs.getIntPref("lastSuggestionsPromptDate");
if (previousDate < date) {
let remainingDays =
prefs.getIntPref("daysBeforeHidingSuggestionsPrompt") - 1;
prefs.setIntPref("daysBeforeHidingSuggestionsPrompt",
remainingDays);
prefs.setIntPref("lastSuggestionsPromptDate", date);
if (!remainingDays)
showNotification = false;
}
}
if (showNotification) {
this._showSearchSuggestionsNotification();
} else if (this.classList.contains("showSearchSuggestionsNotification")) {
this._hideSearchSuggestionsNotification();
}
this._openAutocompletePopup(aInput, aElement);
]]>
</body>
@ -1634,12 +1716,25 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
this.siteIconStart = undefined;
}
try {
let whichNotification = aInput.whichSearchSuggestionsNotification;
if (whichNotification != "none") {
aInput.updateSearchSuggestionsNotificationImpressions(whichNotification);
this._showSearchSuggestionsNotification(whichNotification, popupDirection);
} else if (this.classList.contains("showSearchSuggestionsNotification")) {
this._hideSearchSuggestionsNotification();
}
} catch (ex) {
// Not critical for the urlbar functionality, just report the error.
Components.utils.reportError(ex);
}
// Position the popup below the navbar. To get the y-coordinate,
// which is an offset from the bottom of the input, subtract the
// bottom of the navbar from the buttom of the input.
let yOffset =
document.getElementById("nav-bar").getBoundingClientRect().bottom -
aInput.getBoundingClientRect().bottom;
this.DOMWindowUtils.getBoundsWithoutFlushing(document.getElementById("nav-bar")).bottom -
this.DOMWindowUtils.getBoundsWithoutFlushing(aInput).bottom;
this.openPopup(aElement, "after_start", 0, yOffset, false, false);
]]></body>
</method>
@ -1653,8 +1748,36 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
</method>
<method name="_showSearchSuggestionsNotification">
<parameter name="whichNotification"/>
<parameter name="popupDirection"/>
<body>
<![CDATA[
let deckIndex = 0;
if (whichNotification == "opt-out") {
deckIndex = 1;
if (this.siteIconStart) {
let rect = this.DOMWindowUtils.getBoundsWithoutFlushing(window.document.documentElement);
let padding = popupDirection == "rtl" ? rect.right - this.siteIconStart
: this.siteIconStart;
this.searchSuggestionsNotification.style.paddingInlineStart = padding + "px";
} else {
this.searchSuggestionsNotification.style.removeProperty("padding-inline-start");
}
// We want to animate the opt-out hint only once.
if (!this._firstSearchSuggestionsNotification) {
this._firstSearchSuggestionsNotification = true;
this.searchSuggestionsNotification.setAttribute("animate", "true");
}
}
this.searchSuggestionsNotification.setAttribute("selectedIndex", deckIndex);
let ariaDescElt = whichNotification == "opt-in" ?
"search-suggestions-question" : "search-suggestions-hint";
this.searchSuggestionsNotification.setAttribute("aria-describedby", ariaDescElt);
// With the notification shown, the listbox's height can sometimes be
// too small when it's flexed, as it normally is. Also, it can start
// out slightly scrolled down. Both problems appear together, most
@ -1704,6 +1827,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
this.classList.remove("showSearchSuggestionsNotification");
this.richlistbox.flex = 1;
this.removeAttribute("dontanimate");
this.searchSuggestionsNotification.removeAttribute("animate");
if (this._matchCount) {
// Update popup height.
this._invalidate();

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

@ -10,10 +10,11 @@
function setup() {
const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
registerCleanupFunction(() => {
Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
});
}

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

@ -60,12 +60,13 @@ function addSearchEngine(basename) {
async function prepareSearchEngine() {
let oldCurrentEngine = Services.search.currentEngine;
let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
let engine = await addSearchEngine(TEST_ENGINE_BASENAME);
Services.search.currentEngine = engine;
registerCleanupFunction(async function() {
Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
Services.search.currentEngine = oldCurrentEngine;
// Make sure the popup is closed for the next test.

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

@ -419,7 +419,16 @@ These should match what Safari and other Apple applications use on OS X Lion. --
<!ENTITY urlbar.searchSuggestionsNotification.enable "Yes">
<!ENTITY urlbar.searchSuggestionsNotification.enable.accesskey "y">
<!--
<!-- LOCALIZATION NOTE (urlbar.searchSuggestionsNotification.hintPrefix): Shown just before the suggestions opt-out hint. -->
<!ENTITY urlbar.searchSuggestionsNotification.hintPrefix "Tip:">
<!-- LOCALIZATION NOTE (urlbar.searchSuggestionsNotification.hint): &#x1F50E; is the magnifier icon emoji, please don't change it. -->
<!ENTITY urlbar.searchSuggestionsNotification.hint "Get help finding things! Look for the &#x1F50E; next to search suggestions.">
<!ENTITY urlbar.searchSuggestionsNotification.changeSettingsWin "Change Options…">
<!ENTITY urlbar.searchSuggestionsNotification.changeSettingsWin.accesskey "C">
<!ENTITY urlbar.searchSuggestionsNotification.changeSettingsUnix "Change Preferences…">
<!ENTITY urlbar.searchSuggestionsNotification.changeSettingsUnix.accesskey "C">
<!--
Comment duplicated from browser-sets.inc:
Search Command Key Logic works like this:

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

@ -72,6 +72,7 @@ add_task(async function setup() {
Services.search.moveEngine(engine, 0);
// Enable search suggestions in the urlbar.
let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
// Enable the urlbar one-off buttons.
@ -96,7 +97,7 @@ add_task(async function setup() {
Services.telemetry.canRecordExtended = oldCanRecord;
Services.search.currentEngine = originalEngine;
Services.search.removeEngine(engine);
Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
Services.prefs.clearUserPref(ONEOFF_URLBAR_PREF);
await PlacesTestUtils.clearHistory();
Services.telemetry.setEventRecordingEnabled("navigation", false);

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

@ -1,9 +1,16 @@
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] {
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] {
border-bottom: 1px solid var(--panel-separator-color);
background-color: hsla(210, 4%, 10%, 0.07);
padding-inline-start: 0;
padding-inline-end: 6px;
min-height: 3em;
}
/* Opt-in notification */
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] > hbox[anonid="search-suggestions-opt-in"] {
padding: 6px 0;
padding-inline-start: 44px;
padding-inline-end: 6px;
background-color: hsla(210, 4%, 10%, 0.07);
background-image: url("chrome://browser/skin/info.svg");
background-clip: padding-box;
background-position: 20px center;
@ -11,44 +18,146 @@
background-size: 16px 16px;
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"]:-moz-locale-dir(rtl) {
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] > hbox[anonid="search-suggestions-opt-in"]:-moz-locale-dir(rtl) {
background-position: right 20px center;
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > description {
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] > hbox[anonid="search-suggestions-opt-in"] > description {
margin: 0;
padding: 0;
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > description > label.text-link {
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] > hbox[anonid="search-suggestions-opt-in"] > description > label.text-link {
margin-inline-start: 0;
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button {
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] button {
-moz-appearance: none;
min-width: 80px;
border-radius: 3px;
padding: 4px 16px;
margin: 0;
margin-inline-start: 10px;
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-disable"] {
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] button[anonid="search-suggestions-notification-disable"] {
color: hsl(210, 0%, 38%);
background-color: hsl(210, 0%, 88%);
border: 1px solid hsl(210, 0%, 82%);
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-disable"]:hover {
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] button[anonid="search-suggestions-notification-disable"]:hover {
background-color: hsl(210, 0%, 84%);
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-enable"] {
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] button[anonid="search-suggestions-notification-enable"] {
color: white;
background-color: hsl(93, 82%, 44%);
border: 1px solid hsl(93, 82%, 44%);
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-enable"]:hover {
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] button[anonid="search-suggestions-notification-enable"]:hover {
background-color: hsl(93, 82%, 40%);
}
/* Opt-out hint */
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] > hbox[anonid="search-suggestions-opt-out"] {
font: message-box;
}
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] hbox[anonid="search-suggestions-hint-box"] > description {
margin: auto;
padding: 4px 8px;
background-color: #ffeebe;
border: 1px solid #ffdf81;
border-radius: 4px;
color: #7d3500;
}
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] hbox[anonid="search-suggestions-hint-box"] > description > html|span {
unicode-bidi: embed;
}
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] hbox[anonid="search-suggestions-hint-box"] > description > html|span.prefix {
font-weight: bold;
}
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"][animate] > hbox[anonid="search-suggestions-opt-out"] > .ac-site-icon {
transform: scale(0);
animation-name: search-suggestions-hint-grow;
animation-duration: 500ms;
animation-delay: 500ms;
animation-iteration-count: 1;
animation-timing-function: ease-in-out;
animation-fill-mode: forwards;
}
@keyframes search-suggestions-hint-grow {
0% { transform: scale(0); }
40% { transform: scale(1.5); }
60% { transform: scale(1); }
80% { transform: scale(1.25); }
100% { transform: scale(1); }
}
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"][animate] hbox[anonid="search-suggestions-hint-typing"] > .ac-title-text {
text-overflow: clip;
}
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"][animate] hbox[anonid="search-suggestions-hint-typing"] {
overflow: hidden;
max-width: 8ch;
width: 0;
animation-name: search-suggestions-hint-typing;
animation-duration: 500ms;
animation-delay: 750ms;
animation-iteration-count: 1;
animation-fill-mode: forwards;
}
@keyframes search-suggestions-hint-typing {
from { width: 0; }
to { width: 8ch; }
}
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"][animate] hbox[anonid="search-suggestions-hint-box"] {
opacity: 0;
animation-duration: 250ms;
animation-delay: 1500ms;
animation-iteration-count: 1;
animation-fill-mode: forwards;
}
/* Margin-inline-start can't be animated yet */
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"][animate] hbox[anonid="search-suggestions-hint-box"]:-moz-locale-dir(ltr) {
margin-left: 160px;
animation-name: search-suggestions-hint-buildin-ltr;
}
@keyframes search-suggestions-hint-buildin-ltr {
from { margin-left: 160px; opacity: 0; }
to { margin-left: 0; opacity: 1; }
}
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"][animate] hbox[anonid="search-suggestions-hint-box"]:-moz-locale-dir(rtl) {
/* Should be margin-inline-start but that can't be animated yet */
margin-right: 160px;
animation-name: search-suggestions-hint-buildin-rtl;
}
@keyframes search-suggestions-hint-buildin-rtl {
from { margin-right: 160px; opacity: 0; }
to { margin-right: 0; opacity: 1; }
}
#search-suggestions-change-settings {
opacity: 0;
animation-name: search-suggestions-hint-fadein;
animation-duration: 500ms;
animation-delay: 1800ms;
animation-iteration-count: 1;
animation-fill-mode: forwards;
}
@keyframes search-suggestions-hint-fadein {
from { opacity: 0 }
to { opacity: 1 }
}