Commit 6e5c92ad authored by Daisuke Akatsuka's avatar Daisuke Akatsuka
Browse files

Bug 1745026 - Part 1: Implement basic opt-in modal design for Firefox Suggest. r=adw

parent 92a2c719
Loading
Loading
Loading
Loading
+3 −5
Original line number Diff line number Diff line
@@ -45,15 +45,15 @@ const RESTARTS_PREF = "quicksuggest.seenRestarts";
// These values are used in telemetry events, so be careful about changing them.
const ONBOARDING_CHOICE = {
  ACCEPT: "accept",
  REJECT: "reject",
  DISMISSED_ESCAPE_KEY: "dismissed_escape_key",
  DISMISSED_OTHER: "dismissed_other",
  LEARN_MORE: "learn_more",
  NOT_NOW: "not_now_link",
  SETTINGS: "settings",
};

const ONBOARDING_URI =
  "chrome://browser/content/urlbar/quicksuggestOnboarding.xhtml";
  "chrome://browser/content/urlbar/quicksuggestOnboarding.html";

// This is a score in the range [0, 1] used by the provider to compare
// suggestions from remote settings to suggestions from Merino. Remote settings
@@ -275,10 +275,8 @@ class Suggestions {
          fromChrome: true,
        });
        break;
      case ONBOARDING_CHOICE.SETTINGS:
        win.openPreferences("privacy-locationBar");
        break;
      case ONBOARDING_CHOICE.ACCEPT:
      case ONBOARDING_CHOICE.REJECT:
      case ONBOARDING_CHOICE.NOT_NOW:
        // No other action required.
        break;
+20 −0
Original line number Diff line number Diff line
@@ -84,3 +84,23 @@ addressbar-firefox-suggest-info-sponsored = Based on your selection, you’ll re
# Sponsored suggestions: off
# Data collection: on
addressbar-firefox-suggest-info-data = Based on your selection, you won’t receive suggestions from the web or sponsored sites. We will process your search query data to develop the { -firefox-suggest-brand-name } feature.

## These strings are used in the introduction pane of the Firefox Suggest online
## onboarding opt-in dialog.

firefox-suggest-onboarding-introduction-title = Make sure you’ve got our newest search experience
firefox-suggest-onboarding-introduction-button = Next

## These strings are used in the main pane of the Firefox Suggest online
## onboarding opt-in dialog. Options are displayed as radio buttons with a label
## followed by a description.

firefox-suggest-onboarding-main-title = We’re building a richer search experience
firefox-suggest-onboarding-main-description = Finding the best of the web should be easier. Allowing { -vendor-short-name } to process your search queries will help develop our { -firefox-suggest-brand-name } feature, while keeping your privacy top of mind.
firefox-suggest-onboarding-main-accept-option-label =
    Allow improved search experience. <a data-l10n-name="firefox-suggest-onboarding-learn-more-link">Learn more</a>
firefox-suggest-onboarding-main-accept-option-description = Include suggestions for a more curated web experience.
firefox-suggest-onboarding-main-reject-option-label = Keep my default experience.
firefox-suggest-onboarding-main-reject-option-description = Don’t allow search query processing.
firefox-suggest-onboarding-main-submit-button = Save preferences
firefox-suggest-onboarding-main-skip-link = Skip action
+201 −22
Original line number Diff line number Diff line
@@ -2,40 +2,219 @@
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#infoContainer {
/**
 * We follow the example of the upgrade dialog for font sizes, line heights,
 * etc. See: https://searchfox.org/mozilla-central/source/browser/themes/shared/upgradeDialog.css
 */

:root {
  /* As this dialog is designed under assuming the base font size is 11px,
     specify image size using current font-size. */
  --introduction-logo-width: calc(158 / 11 * 1em);
  --introduction-logo-height: calc(114 / 11 * 1em);
  --main-logo-width: calc(100 / 11 * 1em);
  --main-logo-height: calc(64 / 11 * 1em);
  /* --fixed-large-margin is constant regardless of compact mode. */
  --fixed-large-margin: 24px;
  --large-margin: 24px;
  --small-margin: 16px;
  --section-vertical-padding: 32px;
  --section-horizontal-padding: 64px;
}

body.compact {
  /* 15px is the usual font-size. */
  font-size: 13px;
  --main-logo-width: calc(100 / 13 * 1em);
  --main-logo-height: calc(64 / 13 * 1em);
  --large-margin: 12px;
  --small-margin: 8px;
  --section-vertical-padding: 16px;
  --section-horizontal-padding: 32px;
}

section {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  text-align: center;
  padding: 1em 0;
  padding: var(--section-vertical-padding) var(--section-horizontal-padding);
  width: 536px;
  /* This is the natural height of the main section. */
  min-height: 587px;
}

body.compact section {
  /* This is the natural height of the main section in compact mode. */
  min-height: 450px;
}

.title {
  font-size: 1.6em;
  font-weight: 600;
  line-height: 1.5;
}

.logo {
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  border: none;
}

.pager > span {
  display: inline-block;
  border-radius: 3px;
  width: 6px;
  height: 6px;
  background-color: var(--in-content-border-color);
  margin-inline: 4px;
}

.pager > .current {
  background-color: var(--in-content-primary-button-background);
}

#introduction-section .logo {
  background-image: url("quicksuggestOnboarding_magglass_animation.svg");
  width: var(--introduction-logo-width);
  height: var(--introduction-logo-height);
  margin-block-end: var(--large-margin);
}

@media (prefers-reduced-motion: reduce) {
  #introduction-section .logo {
    background-image: url("quicksuggestOnboarding_magglass.svg");
  }
}

#introduction-section .title {
  margin-block-end: var(--large-margin);
}

#main-section {
  display: none;
}

#main-section .logo {
  background-image: url("quicksuggestOnboarding_magglass.svg");
  width: var(--main-logo-width);
  height: var(--main-logo-height);
  margin-block-end: var(--large-margin);
}

#main-section .title {
  margin-block-end: var(--large-margin);
}

#main-section .description {
  margin-block: 0 var(--fixed-large-margin);
  font-size: 1.1em;
  font-weight: 400;
  line-height: 1.6;
}

#main-section .option {
  border-radius: 4px;
  border: 2px solid var(--in-content-box-info-background);
  display: flex;
  text-align: start;
  padding: var(--small-margin);
  width: calc(100% - (var(--small-margin) + 2px) * 2);
  flex-direction: row;
  position: relative;
}

#main-section .option.selected {
  border-color: var(--in-content-primary-button-background);
}

a {
#main-section .option.accept {
  margin-block-end: var(--small-margin);
}

#main-section .option.reject {
  margin-block-end: var(--fixed-large-margin);
}

#main-section .option > label {
  /* Make whole option area selectable for the radio button. */
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

body:not(.compact) #main-section .option > input {
  /* Vertically align the radio button with the .option-label. */
  margin-block-start: 0.25em;
}

#main-section .option > div {
  /* Make this area be front than label to make the link included clickable.
     And, make this area not recieve pointer-events so that this area as well
     will be selectable area for the radio. */
  z-index: 1;
  pointer-events: none;
}

#main-section .option > div a {
  /* Make a clickable explicitly since the parent element might not receive the
     event. */
  pointer-events: auto;
}

#main-section .option > div > .option-label {
  font-size: 1.1em;
  font-weight: 600;
  margin-block-end: 2px;
}

#main-section .option > div > .option-description {
  font-size: 1em;
}

#main-section a {
  cursor: pointer;
  font-weight: normal;
}

#notNowLinkContainer {
  text-align: end;
  padding-inline-end: 1em;
button,
#onboardingNotNow {
  margin-block-end: var(--small-margin);
}

#infoIcon {
  width: 112px;
  height: 112px;
  margin: 0 auto 1em;
  background-image: url("chrome://branding/content/about-logo.svg");
  background-size: 100%;
/* transition from introduction to main */
#introduction-section.inactive {
  /* Avoid including this section size */
  position: fixed;
  pointer-events: none;
  animation: fadeout 0.3s forwards;
}

#infoTitle {
  font-size: 1.5em;
  margin: 0;
#main-section.active {
  display: flex;
  animation: fadein 0.3s forwards;
}

#infoBody {
  margin: 1em 0;
@keyframes fadeout {
  0% {
    opacity: 1;
  }
  100% {
    visibility: hidden;
    opacity: 0;
  }
}

#illustration {
  background: url("quicksuggestOnboarding_illustration.svg") no-repeat center;
  min-width: 538px;
  height: 219px;
  margin: 1em 0 2em;
@keyframes fadein {
  0% {
    opacity: 0;
  }
  100% {
    pointer-events: initial;
    opacity: 1;
  }
}
+93 −0
Original line number Diff line number Diff line
<!DOCTYPE html>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<html>
  <head>
    <meta http-equiv="Content-Security-Policy"
          content="default-src chrome:; object-src 'none'">
    <meta name="referrer" content="no-referrer">
    <link rel="stylesheet" type="text/css"
          href="chrome://global/skin/in-content/common.css">
    <link rel="stylesheet" type="text/css"
          href="chrome://browser/content/urlbar/quicksuggestOnboarding.css">
    <link rel="localization" href="branding/brand.ftl">
    <link rel="localization" href="browser/branding/brandings.ftl"/>
    <link rel="localization" href="preview/firefoxSuggest.ftl">
    <script src="chrome://browser/content/urlbar/quicksuggestOnboarding.js"></script>
  </head>
  <body role="dialog"
        aria-labelledby="introduction-title">
    <section id="introduction-section">
      <span class="logo" role="presentation"></span>
      <h1 id="introduction-title"
          class="title"
          aria-live="polite"
          data-l10n-id="firefox-suggest-onboarding-introduction-title"></h1>
      <button id="onboardingNext"
              class="primary"
              tabindex="1"
              data-l10n-id="firefox-suggest-onboarding-introduction-button"></button>
      <div class="pager">
        <span class="current"></span>
        <span></span>
      </div>
    </section>
    <section id="main-section">
      <span class="logo" role="presentation"></span>
      <h1 class="title"
          aria-live="polite"
          data-l10n-id="firefox-suggest-onboarding-main-title"></h1>
      <h2 class="description"
           tabindex="-1"
           data-l10n-id="firefox-suggest-onboarding-main-description"></h2>
      <div class="option accept">
        <label for="onboardingAccept"></label>
        <input id="onboardingAccept"
               type="radio"
               tabindex="2"
               name="search-experience"></input>
        <div>
          <div class="option-label"
               data-l10n-id="firefox-suggest-onboarding-main-accept-option-label">
            <a id="onboardingLearnMore"
               tabindex="3"
               data-l10n-name="firefox-suggest-onboarding-learn-more-link"></a>
          </div>
          <div class="option-description"
               data-l10n-id="firefox-suggest-onboarding-main-accept-option-description">
          </div>
        </div>
      </div>
      <div class="option reject">
        <label for="onboardingReject"></label>
        <input id="onboardingReject"
               type="radio"
               tabindex="4"
               name="search-experience"></input>
        <div>
          <div class="option-label"
               data-l10n-id="firefox-suggest-onboarding-main-reject-option-label">
          </div>
          <div class="option-description"
               data-l10n-id="firefox-suggest-onboarding-main-reject-option-description">
          </div>
        </div>
      </div>
      <button id="onboardingSubmit"
              class="primary"
              disabled="true"
              tabindex="5"
              data-l10n-id="firefox-suggest-onboarding-main-submit-button"></button>
      <a id="onboardingNotNow"
         tabindex="6"
         data-l10n-id="firefox-suggest-onboarding-main-skip-link"></a>
      <div class="pager">
        <span></span>
        <span class="current"></span>
      </div>
    </section>
  </body>
</html>
+63 −46
Original line number Diff line number Diff line
@@ -8,56 +8,73 @@ const { ONBOARDING_CHOICE } = ChromeUtils.import(
  "resource:///modules/UrlbarQuickSuggest.jsm"
);

document.addEventListener("dialogaccept", event => {
  // dialogaccept is fired when the user presses the enter key even when an
  // element other than the accept button is focused. If another element is
  // focused, then peform its action.
  switch (document.activeElement?.id) {
    case "onboardingSettingsButton":
      window.arguments[0].choice = ONBOARDING_CHOICE.SETTINGS;
      event.preventDefault();
      window.close();
      return;
    case "onboardingNotNow":
      window.arguments[0].choice = ONBOARDING_CHOICE.NOT_NOW;
      event.preventDefault();
      window.close();
      return;
    case "onboardingLearnMore":
      window.arguments[0].choice = ONBOARDING_CHOICE.LEARN_MORE;
      event.preventDefault();
      window.close();
      return;
  }
// Number of height pixels to switch to compact mode to avoid showing scrollbars
// on the non-compact mode. This is based on the natural height of the full-size
// section height 587px + padding-block-start 32px + padding-block-end 32px +
// the approximate height of the tab bar 44px.
const COMPACT_MODE_HEIGHT = 587 + 32 * 2 + 44;

  window.arguments[0].choice = ONBOARDING_CHOICE.ACCEPT;
document.addEventListener("DOMContentLoaded", () => {
  addSubmitListener(document.getElementById("onboardingNext"), () => {
    document.getElementById("introduction-section").classList.add("inactive");
    document.getElementById("main-section").classList.add("active");
  });

document.addEventListener("dialogextra1", () => {
  window.arguments[0].choice = ONBOARDING_CHOICE.SETTINGS;
  addSubmitListener(document.getElementById("onboardingLearnMore"), () => {
    window.arguments[0].choice = ONBOARDING_CHOICE.LEARN_MORE;
    window.close();
  });

document.getElementById("onboardingNotNow").addEventListener("click", () => {
  addSubmitListener(document.getElementById("onboardingNotNow"), () => {
    window.arguments[0].choice = ONBOARDING_CHOICE.NOT_NOW;
    window.close();
  });

document.getElementById("onboardingLearnMore").addEventListener("click", () => {
  window.arguments[0].choice = ONBOARDING_CHOICE.LEARN_MORE;
  const onboardingSubmit = document.getElementById("onboardingSubmit");
  const onboardingAccept = document.getElementById("onboardingAccept");
  const onboardingReject = document.getElementById("onboardingReject");
  function optionChangeListener() {
    onboardingSubmit.removeAttribute("disabled");
    onboardingAccept
      .closest(".option")
      .classList.toggle("selected", onboardingAccept.checked);
    onboardingReject
      .closest(".option")
      .classList.toggle("selected", !onboardingAccept.checked);
  }
  onboardingAccept.addEventListener("change", optionChangeListener);
  onboardingReject.addEventListener("change", optionChangeListener);

  function submitListener() {
    if (!onboardingAccept.checked && !onboardingReject.checked) {
      return;
    }

    window.arguments[0].choice = onboardingAccept.checked
      ? ONBOARDING_CHOICE.ACCEPT
      : ONBOARDING_CHOICE.REJECT;
    window.close();
  }
  addSubmitListener(onboardingSubmit, submitListener);
  onboardingAccept.addEventListener("keydown", e => {
    if (e.keyCode == e.DOM_VK_RETURN) {
      submitListener();
    }
  });
  onboardingReject.addEventListener("keydown", e => {
    if (e.keyCode == e.DOM_VK_RETURN) {
      submitListener();
    }
  });

// If we change the system font size while displaying the dialog, problem that
// components are hidden might happen dependent on the size. To avoid it, we
// resize dialog explicitly whenever the size of internal components are changed.
window.addEventListener("load", () => {
  const resizeObserver = new ResizeObserver(() => {
    // resizeDialog() does not make window height smaller even if the total
    // height of internal components is smaller. So, we minimize the window
    // size once, then prompt to recalculate to be appropriate size.
    window.resizeBy(0, -window.innerHeight);
    window.opener.gDialogBox.dialog.resizeDialog();
  if (window.outerHeight < COMPACT_MODE_HEIGHT) {
    document.body.classList.add("compact");
  }
});
  resizeObserver.observe(document.getElementById("infoContainer"));

function addSubmitListener(element, listener) {
  element.addEventListener("click", listener);
  element.addEventListener("keydown", e => {
    if (e.keyCode == e.DOM_VK_RETURN) {
      listener();
    }
  });
}
Loading