--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -315,20 +315,21 @@ pref("browser.urlbar.match.url", "@");
// The default behavior for the urlbar can be configured to use any combination
// of the match filters with each additional filter adding more results (union).
pref("browser.urlbar.suggest.history", true);
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.
pref("browser.urlbar.maxCharsForSearchSuggestions", 20);
// Restrictions to current suggestions can also be applied (intersection).
// Typed suggestion works only if history is set to true.
pref("browser.urlbar.suggest.history.onlyTyped", false);
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -584,22 +584,22 @@ html|input.urlbar-input[textoverflow]:no
#PopupAutoCompleteRichResult {
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#urlbar-rich-result-popup");
}
#PopupAutoCompleteRichResult.showSearchSuggestionsNotification {
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;
}
#PopupAutoCompleteRichResult > richlistbox {
transition: height 100ms;
}
#PopupAutoCompleteRichResult.showSearchSuggestionsNotification > richlistbox {
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -75,17 +75,21 @@ support-files =
[browser_urlbarPrivateBrowsingWindowChange.js]
[browser_urlbarRaceWithTabs.js]
[browser_urlbarRevert.js]
[browser_urlbarSearchSingleWordNotification.js]
[browser_urlbarSearchSuggestions.js]
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
[browser_urlbarSearchTelemetry.js]
support-files =
searchSuggestionEngine.xml
searchSuggestionEngine.sjs
[browser_urlbarStop.js]
--- a/browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js
+++ b/browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js
@@ -20,21 +20,22 @@ add_task(async function switchToTab() {
gBrowser.removeTab(tab);
});
add_task(async function searchSuggestions() {
let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
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");
// Don't assume that the search doesn't match history or bookmarks left around
// by earlier tests.
Assert.ok(gURLBar.popup.richlistbox.children.length >= 3,
"Should get at least heuristic result + two search suggestions");
// The first expected search is the search term itself since the heuristic
--- a/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js
@@ -1,19 +1,20 @@
const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
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
// history now.
await PlacesTestUtils.clearHistory();
// Make sure the popup is closed for the next test.
gURLBar.blur();
rename from browser/base/content/test/urlbar/browser_urlbarSearchSuggestionsNotification.js
rename to browser/base/content/test/urlbar/browser_urlbarSearchSuggestions_opt-in.js
--- a/browser/base/content/test/urlbar/browser_urlbarSearchSuggestionsNotification.js
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions_opt-in.js
@@ -3,44 +3,47 @@ const SUGGEST_URLBAR_PREF = "browser.url
const CHOICE_PREF = "browser.urlbar.userMadeSearchSuggestionsChoice";
const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
// Must run first.
add_task(async function prepare() {
let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
let oldCurrentEngine = Services.search.currentEngine;
Services.search.currentEngine = engine;
+ let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
+ let defaults = Services.prefs.getDefaultBranch("browser.urlbar.");
+ let searchSuggestionsDefault = defaults.getBoolPref("suggest.searches");
+ defaults.setBoolPref("suggest.searches", false);
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();
Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
});
});
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);
gURLBar.openPopup();
await popupPromise;
Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
assertVisible(true);
Assert.equal(gURLBar.popup._matchCount, 0, "popup should have no results");
@@ -55,18 +58,17 @@ add_task(async function dismissWithoutRe
gURLBar.focus();
Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed");
await promiseAutocompleteResultPopup("foo");
Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
assertVisible(false);
});
add_task(async function dismissWithResults() {
- Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
- await setUserMadeChoicePref(false);
+ setupVisibleNotification();
gURLBar.blur();
gURLBar.focus();
await promiseAutocompleteResultPopup("foo");
Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
assertVisible(true);
Assert.ok(gURLBar.popup._matchCount > 0, "popup should have results");
let disableButton = document.getAnonymousElementByAttribute(
gURLBar.popup, "anonid", "search-suggestions-notification-disable"
@@ -79,18 +81,17 @@ add_task(async function dismissWithResul
gURLBar.focus();
Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed");
await promiseAutocompleteResultPopup("foo");
Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
assertVisible(false);
});
add_task(async function disable() {
- Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
- await setUserMadeChoicePref(false);
+ setupVisibleNotification();
gURLBar.blur();
gURLBar.focus();
await promiseAutocompleteResultPopup("foo");
Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
assertVisible(true);
let disableButton = document.getAnonymousElementByAttribute(
gURLBar.popup, "anonid", "search-suggestions-notification-disable"
);
@@ -98,19 +99,17 @@ add_task(async function disable() {
disableButton.click();
await transitionPromise;
gURLBar.blur();
await promiseAutocompleteResultPopup("foo");
Assert.ok(!suggestionsPresent());
});
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");
assertVisible(true);
Assert.ok(!suggestionsPresent());
let enableButton = document.getAnonymousElementByAttribute(
gURLBar.popup, "anonid", "search-suggestions-notification-enable"
);
@@ -126,31 +125,30 @@ add_task(async function enable() {
// Suggestions should still be present in a new search of course.
await promiseAutocompleteResultPopup("bar");
Assert.ok(suggestionsPresent());
});
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();
await promiseAutocompleteResultPopup("foo", win);
assertVisible(false, win);
win.gURLBar.blur();
await BrowserTestUtils.closeWindow(win);
});
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");
assertVisible(true);
let win2 = await BrowserTestUtils.openNewBrowserWindow();
win2.gURLBar.focus();
await promiseAutocompleteResultPopup("win2", win2);
@@ -179,49 +177,29 @@ add_task(async function multipleWindows(
await BrowserTestUtils.closeWindow(win2);
await BrowserTestUtils.closeWindow(win3);
});
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() {
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:([^,]+),(.*)$/);
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions_opt-out.js
@@ -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 suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
+ let defaults = Services.prefs.getDefaultBranch("browser.urlbar.");
+ let searchSuggestionsDefault = defaults.getBoolPref("suggest.searches");
+ defaults.setBoolPref("suggest.searches", true);
+ 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");
+}
--- a/browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js
@@ -2,23 +2,24 @@
Cu.import("resource:///modules/BrowserUITelemetry.jsm");
const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
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
// that history now.
await PlacesTestUtils.clearHistory();
// Make sure the popup is closed for the next test.
gURLBar.blur();
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -65,25 +65,28 @@ file, You can obtain one at http://mozil
<field name="ExtensionSearchHandler" readonly="true">
(Components.utils.import("resource://gre/modules/ExtensionSearchHandler.jsm", {})).ExtensionSearchHandler;
</field>
<constructor><![CDATA[
this._prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService)
.getBranch("browser.urlbar.");
+ this._prefs.addObserver("", this);
- 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);
this.inputField.addEventListener("mousemove", this);
this.inputField.addEventListener("mouseout", this);
this.inputField.addEventListener("overflow", this);
this.inputField.addEventListener("underflow", this);
@@ -940,24 +943,18 @@ file, You can obtain one at http://mozil
case "formatting.enabled":
this._formattingEnabled = this._prefs.getBoolPref(aData);
break;
case "suggest.searches":
// Mirror the value for future use, see the comment in the
// 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);
break;
case "oneOffSearches":
this._enableOrDisableOneOffSearches();
break;
}
@@ -1198,40 +1195,80 @@ file, You can obtain one at http://mozil
this.popup._isFirstResultHeuristic) {
this.mController.handleText();
return false;
}
return this.mController.handleDelete();
]]></body>
</method>
- <field name="_userMadeSearchSuggestionsChoice"><![CDATA[
- false
- ]]></field>
+ <property name="_userMadeSearchSuggestionsChoice" readonly="true">
+ <getter><![CDATA[
+ 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;
+ }
- <method name="_cacheUserMadeSearchSuggestionsChoice">
+ 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[
- this._userMadeSearchSuggestionsChoice =
- this._prefs.getBoolPref("userMadeSearchSuggestionsChoice") ||
- this._prefs.getBoolPref("suggest.searches");
+ 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>
- <property name="shouldShowSearchSuggestionsNotification" 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");
- ]]></getter>
- </property>
-
</implementation>
<handlers>
<handler event="keydown"><![CDATA[
if (this._noActionKeys.has(event.keyCode) &&
this.popup.selectedIndex >= 0 &&
!this._pressedNoActionKeys.has(event.keyCode)) {
if (this._pressedNoActionKeys.size == 0) {
@@ -1253,16 +1290,48 @@ file, You can obtain one at http://mozil
<handler event="focus"><![CDATA[
if (event.originalTarget == this.inputField) {
this._hideURLTooltip();
this.formatValue();
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>
<handler event="blur"><![CDATA[
if (event.originalTarget == this.inputField) {
this._clearNoActions();
this.formatValue();
if (this.getAttribute("pageproxystate") != "valid") {
@@ -1330,58 +1399,91 @@ file, You can obtain one at http://mozil
<resources>
<stylesheet src="chrome://browser/content/search/searchbarBindings.css"/>
<stylesheet src="chrome://browser/skin/searchbar.css"/>
</resources>
<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">💡 &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">
<children/>
<xul:vbox anonid="one-off-search-buttons"
class="search-one-offs"
compact="true"
includecurrentengine="true"
disabletab="true"
flex="1"/>
</xul:hbox>
</content>
<implementation>
+ <field name="DOMWindowUtils">
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ </field>
+
<field name="_maxResults">0</field>
<field name="_bundle" readonly="true">
Cc["@mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService).
createBundle("chrome://browser/locale/places/places.properties");
</field>
@@ -1458,17 +1560,20 @@ file, You can obtain one at http://mozil
this.input.mIgnoreFocus = false;
Services.prefs.setBoolPref(
"browser.urlbar.suggest.searches", enableSuggestions
);
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>
<!-- Override this so that navigating between items results in an item
always being selected. -->
<method name="getNextIndex">
<parameter name="reverse"/>
<parameter name="amount"/>
@@ -1546,39 +1651,16 @@ file, You can obtain one at http://mozil
<parameter name="aInput"/>
<parameter name="aElement"/>
<body>
<![CDATA[
// initially the panel is hidden
// 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>
</method>
<method name="_openAutocompletePopup">
<parameter name="aInput"/>
<parameter name="aElement"/>
@@ -1629,37 +1711,78 @@ file, You can obtain one at http://mozil
this.siteIconStart = popupDirection == "rtl" ? identityRect.right
: identityRect.left;
} else {
// Reset the alignment so that the site icons are positioned
// according to whatever's in the CSS.
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>
<method name="_updateFooterVisibility">
<body>
<![CDATA[
this.footer.collapsed = this._matchCount == 0;
]]>
</body>
</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
// often when the popup is very narrow and the notification's text
// must wrap. Work around them by removing the flex.
//
// But without flexing the listbox, the listbox's height animation
// sometimes fails to complete, leaving the popup too tall. Work
@@ -1699,16 +1822,17 @@ file, You can obtain one at http://mozil
</method>
<method name="_hideSearchSuggestionsNotification">
<body>
<![CDATA[
this.classList.remove("showSearchSuggestionsNotification");
this.richlistbox.flex = 1;
this.removeAttribute("dontanimate");
+ this.searchSuggestionsNotification.removeAttribute("animate");
if (this._matchCount) {
// Update popup height.
this._invalidate();
} else {
this.closePopup();
}
]]>
</body>
--- a/browser/components/extensions/test/browser/browser_ext_omnibox.js
+++ b/browser/components/extensions/test/browser/browser_ext_omnibox.js
@@ -3,25 +3,16 @@
"use strict";
// The no-cpows-in-tests check isn't very smart, simply warning if it finds
// a variable named `content`. For Chrome compatibility, the Omnibox API uses
// that name for setting the text of a suggestion, and that's all this test uses
// it for, so we can disable it for this test.
/* eslint-disable mozilla/no-cpows-in-tests */
-function setup() {
- const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
- Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
-
- registerCleanupFunction(() => {
- Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
- });
-}
-
add_task(async function() {
let keyword = "test";
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"omnibox": {
"keyword": keyword,
},
@@ -245,17 +236,16 @@ add_task(async function() {
EventUtils.synthesizeMouseAtCenter(gURLBar.popup.richlistbox.children[0], {});
await expectEvent("on-input-entered-fired", {
text,
disposition: "currentTab",
});
}
- await setup();
await extension.startup();
await SimpleTest.promiseFocus(window);
await testInputEvents();
// Test the heuristic result with default suggestions.
await testHeuristicResult("Generated extension", false /* setDefaultSuggestion */);
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_urlbar_transitions.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_urlbar_transitions.js
@@ -55,22 +55,23 @@ function addSearchEngine(basename) {
reject();
},
});
});
}
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.
gURLBar.blur();
gURLBar.popup.selectedIndex = -1;
gURLBar.popup.hidePopup();
ok(!gURLBar.popup.popupOpen, "popup should be closed");
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -413,17 +413,26 @@ These should match what Safari and other
<!ENTITY urlbar.searchSuggestionsNotification.question "Would you like to improve your search experience with suggestions?">
<!ENTITY urlbar.searchSuggestionsNotification.learnMore "Learn more…">
<!ENTITY urlbar.searchSuggestionsNotification.learnMore.accesskey "l">
<!ENTITY urlbar.searchSuggestionsNotification.disable "No">
<!ENTITY urlbar.searchSuggestionsNotification.disable.accesskey "n">
<!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): 🔎 is the magnifier icon emoji, please don't change it. -->
+<!ENTITY urlbar.searchSuggestionsNotification.hint "Get help finding things! Look for the 🔎 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:
Unix: Ctrl+J (0.8, 0.9 support)
Ctrl+K (cross platform binding)
Mac: Cmd+K (cross platform binding)
Cmd+Opt+F (platform convention)
--- a/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
@@ -67,16 +67,17 @@ add_task(async function setup() {
let engine = Services.search.getEngineByName("MozSearch");
let originalEngine = Services.search.currentEngine;
Services.search.currentEngine = engine;
// And the first one-off engine.
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.
Services.prefs.setBoolPref(ONEOFF_URLBAR_PREF, true);
// Enable Extended Telemetry.
await SpecialPowers.pushPrefEnv({"set": [["toolkit.telemetry.enabled", true]]});
@@ -91,17 +92,17 @@ add_task(async function setup() {
// test when it selects results in the urlbar.
await PlacesTestUtils.clearHistory();
// Make sure to restore the engine once we're done.
registerCleanupFunction(async function() {
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);
});
});
add_task(async function test_simpleQuery() {
// Let's reset the counts.
--- a/browser/themes/shared/urlbarSearchSuggestionsNotification.inc.css
+++ b/browser/themes/shared/urlbarSearchSuggestionsNotification.inc.css
@@ -1,54 +1,163 @@
-#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;
background-repeat: no-repeat;
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 }
+}