Bug 1138818 - New tab user onboarding for sponsored suggested tiles. r=adw, a=lizzard
authorMarina Samuel <msamuel@mozilla.com>
Wed, 13 May 2015 12:23:45 -0400
changeset 274792 99698ae93eb8f658f9e0d0cae0f446cab42688a4
parent 274791 a91079fdbbeda788ededf4b8024b71f0280ce4a7
child 274793 f26c3976301ba22ee50fc04f24eb3d6f61e9379a
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersadw, lizzard
bugs1138818
milestone40.0a2
Bug 1138818 - New tab user onboarding for sponsored suggested tiles. r=adw, a=lizzard
browser/app/profile/firefox.js
browser/base/content/newtab/intro.js
browser/base/content/newtab/newTab.css
browser/base/content/newtab/newTab.xul
browser/base/content/test/newtab/browser_newtab_intro.js
browser/themes/linux/jar.mn
browser/themes/osx/jar.mn
browser/themes/shared/newtab/newTab.inc.css
browser/themes/shared/newtab/whimsycorn.png
browser/themes/windows/jar.mn
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1629,16 +1629,19 @@ pref("browser.panorama.animate_zoom", tr
 // Defines the url to be used for new tabs.
 pref("browser.newtab.url", "about:newtab");
 // Activates preloading of the new tab url.
 pref("browser.newtab.preload", true);
 
 // Remembers if the about:newtab intro has been shown
 pref("browser.newtabpage.introShown", false);
 
+// Remembers if the about:newtab update intro has been shown
+pref("browser.newtabpage.updateIntroShown", false);
+
 // Toggles the content of 'about:newtab'. Shows the grid when enabled.
 pref("browser.newtabpage.enabled", true);
 
 // number of rows of newtab grid
 pref("browser.newtabpage.rows", 3);
 
 // number of columns of newtab grid
 pref("browser.newtabpage.columns", 5);
--- a/browser/base/content/newtab/intro.js
+++ b/browser/base/content/newtab/intro.js
@@ -1,57 +1,240 @@
 #ifdef 0
 /* 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/. */
 #endif
 
 const PREF_INTRO_SHOWN = "browser.newtabpage.introShown";
+const PREF_UPDATE_INTRO_SHOWN = "browser.newtabpage.updateIntroShown";
+
+// These consts indicate the type of intro/onboarding we show.
+const WELCOME = "welcome";
+const UPDATE = "update";
+
+// The maximum paragraph ID listed for 'newtab.intro.paragraph'
+// strings in newTab.properties
+const MAX_PARAGRAPH_ID = 9;
+
+const NUM_INTRO_PAGES = 3;
 
 let gIntro = {
   _nodeIDSuffixes: [
-    "panel",
-    "what",
+    "mask",
+    "modal",
+    "numerical-progress",
+    "text",
+    "buttons",
+    "image",
+    "header",
+    "footer"
   ],
 
+  _imageTypes: {
+    COG : "cog",
+    PIN_REMOVE : "pin-remove",
+    SUGGESTED : "suggested"
+  },
+
+
+  /**
+   * The paragraphs & buttons to show on each page in the intros.
+   *
+   * _introPages.welcome and _introPages.update contain an array of
+   * indices of paragraphs to be used to lookup text in _paragraphs
+   * for each page of the intro.
+   *
+   * Similarly, _introPages.buttons is used to lookup text for buttons
+   * on each page of the intro.
+   */
+  _introPages: {
+    "welcome": [[0,1],[2,3],[4,5]],
+    "update": [[6,5],[4,3],[0,1]],
+    "buttons": [["skip", "continue"],["back", "next"],["back", "gotit"]],
+    "welcome-images": ["cog", "pin-remove", "suggested"],
+    "update-images": ["suggested", "pin-remove", "cog"]
+  },
+
+  _paragraphs: [],
+
   _nodes: {},
 
+  _images: {},
+
   init: function() {
     for (let idSuffix of this._nodeIDSuffixes) {
       this._nodes[idSuffix] = document.getElementById("newtab-intro-" + idSuffix);
     }
 
-    this._nodes.panel.addEventListener("popupshowing", e => this._setUpPanel());
-    this._nodes.panel.addEventListener("popuphidden", e => this._hidePanel());
-    this._nodes.what.addEventListener("click", e => this.showPanel());
+    let brand = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+    this._brandShortName = brand.GetStringFromName("brandShortName");
+  },
+
+  _setImage: function(imageType) {
+    // Remove previously existing images, if any.
+    let currImageHolder = this._nodes.image;
+    while (currImageHolder.firstChild) {
+      currImageHolder.removeChild(currImageHolder.firstChild);
+    }
+
+    this._nodes.image.appendChild(this._images[imageType]);
+  },
+
+  _goToPage: function(pageNum) {
+    this._currPage = pageNum;
+
+    this._nodes["numerical-progress"].innerHTML = `${this._bold(pageNum + 1)} / ${NUM_INTRO_PAGES}`;
+    this._nodes["numerical-progress"].setAttribute("page", pageNum);
+
+    // Set the page's image
+    let imageType = this._introPages[this._onboardingType + "-images"][pageNum];
+    this._setImage(imageType);
+
+    // Set the paragraphs
+    let paragraphNodes = this._nodes.text.getElementsByTagName("p");
+    let paragraphIDs = this._introPages[this._onboardingType][pageNum];
+    paragraphIDs.forEach((arg, index) => {
+      paragraphNodes[index].innerHTML = this._paragraphs[arg];
+    });
+
+    // Set the buttons
+    let buttonNodes = this._nodes.buttons.getElementsByTagName("input");
+    let buttonIDs = this._introPages.buttons[pageNum];
+    buttonIDs.forEach((arg, index) => {
+      buttonNodes[index].setAttribute("value", newTabString("intro." + arg));
+    });
+  },
+
+  _bold: function(str) {
+    return `<strong>${str}</strong>`
+  },
+
+  _link: function(url, text) {
+    return `<a href="${url}" target="_blank">${text}</a>`;
+  },
+
+  _span: function(text, className) {
+    return `<span class="${className}">${text}</span>`;
+  },
+
+  _exitIntro: function() {
+    this._nodes.mask.style.opacity = 0;
+    this._nodes.mask.addEventListener("transitionend", () => {
+      this._nodes.mask.style.display = "none";
+    });
+  },
+
+  _back: function() {
+    if (this._currPage == 0) {
+      // We're on the first page so 'back' means exit.
+      this._exitIntro();
+      return;
+    }
+    this._goToPage(this._currPage - 1);
+  },
+
+  _next: function() {
+    if (this._currPage == (NUM_INTRO_PAGES - 1)) {
+      // We're on the last page so 'next' means exit.
+      this._exitIntro();
+      return;
+    }
+    this._goToPage(this._currPage + 1);
+  },
+
+  _generateImages: function() {
+    Object.keys(this._imageTypes).forEach(type => {
+      let image = "";
+      let imageClass = "";
+      switch (this._imageTypes[type]) {
+        case this._imageTypes.COG:
+          image = document.getElementById("newtab-customize-panel").cloneNode(true);
+          image.removeAttribute("hidden");
+          image.removeAttribute("type");
+          image.classList.add("newtab-intro-image-customize");
+          break;
+        case this._imageTypes.PIN_REMOVE:
+          imageClass = "-hover";
+          // fall-through
+        case this._imageTypes.SUGGESTED:
+          image = document.createElementNS(HTML_NAMESPACE, "div");
+          image.classList.add("newtab-intro-cell-wrapper");
+
+          // Create the cell's inner HTML code.
+          image.innerHTML =
+            '<div class="newtab-intro-cell' + imageClass + '">' +
+            '  <div class="newtab-site newtab-intro-image-tile" type="sponsored">' +
+            '    <a class="newtab-link">' +
+            '      <span class="newtab-thumbnail"/>' +
+            '      <span class="newtab-title">Example Title</span>' +
+            '    </a>' +
+            '    <input type="button" class="newtab-control newtab-control-pin"/>' +
+            '    <input type="button" class="newtab-control newtab-control-block"/>' + (imageClass ? "" :
+            '    <span class="newtab-sponsored">' + newTabString("suggested.tag") + '</span>') +
+            '  </div>' +
+            '</div>';
+            break;
+      }
+      this._images[this._imageTypes[type]] = image;
+    });
+  },
+
+  _generateParagraphs: function() {
+    let customizeIcon = '<input type="button" class="newtab-control newtab-customize"/>';
+
+    let substringMappings = {
+      "2": [this._link(TILES_PRIVACY_LINK, newTabString("privacy.link"))],
+      "4": [customizeIcon, this._bold(newTabString("intro.controls"))],
+      "6": [this._bold(newTabString("intro.paragraph6.remove")), this._bold(newTabString("intro.paragraph6.pin"))],
+      "7": [this._link(TILES_INTRO_LINK, newTabString("learn.link"))],
+      "8": [this._brandShortName, this._link(TILES_INTRO_LINK, newTabString("learn.link"))]
+    }
+
+    for (let i = 1; i <= MAX_PARAGRAPH_ID; i++) {
+      // Skip a couple paragraphs that aren't used
+      if (i == 1 || i == 3) {
+        continue;
+      }
+      try {
+        this._paragraphs.push(newTabString("intro.paragraph" + i, substringMappings[i]));
+      } catch (ex) {
+        // Paragraph with this ID doesn't exist so continue
+      }
+    }
   },
 
   showIfNecessary: function() {
     if (!Services.prefs.getBoolPref(PREF_INTRO_SHOWN)) {
-      Services.prefs.setBoolPref(PREF_INTRO_SHOWN, true);
+      this._onboardingType = WELCOME;
+      this.showPanel();
+    } else if (!Services.prefs.getBoolPref(PREF_UPDATE_INTRO_SHOWN)) {
+      this._onboardingType = UPDATE;
       this.showPanel();
     }
+    Services.prefs.setBoolPref(PREF_INTRO_SHOWN, true);
+    Services.prefs.setBoolPref(PREF_UPDATE_INTRO_SHOWN, true);
   },
 
   showPanel: function() {
-    // Point the panel at the 'what' link
-    this._nodes.panel.hidden = false;
-    this._nodes.panel.openPopup(this._nodes.what);
-  },
+    this._nodes.mask.style.display = "block";
+    this._nodes.mask.style.opacity = 1;
+
+    if (!this._paragraphs.length) {
+      // It's our first time showing the panel. Do some initial setup
+      this._generateParagraphs();
+      this._generateImages();
+    }
+    this._goToPage(0);
 
-  _setUpPanel: function() {
-    // Build the panel if necessary
-    if (this._nodes.panel.childNodes.length == 1) {
-      ['<a href="' + TILES_INTRO_LINK + '">' + newTabString("learn.link") + "</a>",
-       '<a href="' + TILES_PRIVACY_LINK + '">' + newTabString("privacy.link") + "</a>",
-       '<input type="button" class="newtab-customize"/>',
-      ].forEach((arg, index) => {
-        let paragraph = document.createElementNS(HTML_NAMESPACE, "p");
-        this._nodes.panel.appendChild(paragraph);
-        paragraph.innerHTML = newTabString("intro.paragraph" + (index + 1), [arg]);
-      });
-    }
+    // Header text
+    let boldSubstr = this._onboardingType == WELCOME ? this._span(this._brandShortName, "bold") : "";
+    this._nodes.header.innerHTML = newTabString("intro.header." + this._onboardingType, [boldSubstr]);
+
+    // Footer links
+    let footerLinkNodes = this._nodes.footer.getElementsByTagName("li");
+    [this._link(TILES_INTRO_LINK, newTabString("learn.link2")),
+     this._link(TILES_PRIVACY_LINK, newTabString("privacy.link2")),
+    ].forEach((arg, index) => {
+      footerLinkNodes[index].innerHTML = arg;
+    });
   },
-
-  _hidePanel: function() {
-    this._nodes.panel.hidden = true;
-  }
 };
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -50,16 +50,17 @@ input[type=button] {
   right: auto;
 }
 
 #newtab-intro-what {
   cursor: pointer;
   position: absolute;
   right: 70px;
   top: 20px;
+  display: none;
 }
 
 #newtab-intro-what:-moz-locale-dir(rtl) {
   left: 70px;
   right: auto;
 }
 
 #newtab-scrollbox[page-disabled] #newtab-intro-what {
@@ -152,23 +153,34 @@ input[type=button] {
 }
 
 #newtab-grid[locked],
 #newtab-grid[page-disabled] {
   pointer-events: none;
 }
 
 /* CELLS */
-.newtab-cell {
+.newtab-cell,
+.newtab-intro-cell,
+.newtab-intro-cell-hover {
   display: -moz-box;
   height: 210px;
   margin: 20px 10px 35px;
   width: 290px;
 }
 
+.newtab-intro-cell-wrapper {
+  margin-top: -12px;
+}
+
+.newtab-intro-cell,
+.newtab-intro-cell-hover {
+  margin: 0;
+}
+
 /* SITES */
 .newtab-site {
   position: relative;
   -moz-box-flex: 1;
   transition: 100ms ease-out;
   transition-property: top, left, opacity;
 }
 
@@ -301,17 +313,18 @@ input[type=button] {
 .newtab-control {
   position: absolute;
   top: 4px;
   opacity: 0;
   transition: opacity 100ms ease-out;
 }
 
 .newtab-control:-moz-focusring,
-.newtab-cell:not([ignorehover]) > .newtab-site:hover > .newtab-control {
+.newtab-cell:not([ignorehover]) > .newtab-site:hover > .newtab-control,
+.newtab-intro-cell-hover .newtab-control {
   opacity: 1;
 }
 
 .newtab-control[dragged] {
   opacity: 0 !important;
 }
 
 @media (-moz-touch-enabled) {
@@ -553,43 +566,48 @@ input[type=button] {
 .newtab-search-panel-engine > label,
 #newtab-search-manage > label,
 .newtab-customize-complex-option {
   padding: 0;
   margin: 0;
   cursor: pointer;
 }
 
+.newtab-customize-panel-item,
+.newtab-customize-complex-option {
+  width: 100%;
+}
+
 .newtab-customize-panel-item:not([selected]),
 .newtab-customize-panel-subitem:not([selected]){
   color: #7A7A7A;
 }
 
 .newtab-customize-panel-item:not([selected]):hover {
   color: #FFFFFF;
   background-color: #4A90E2
 }
 
 .newtab-customize-complex-option:hover > .selectable:not([selected]),
 .selectable:not([selected]):hover {
   background: url("chrome://global/skin/menu/shared-menu-check-hover.svg") no-repeat #FFFFFF;
   background-size: 16px 16px;
-  background-position: 15px 20px;
+  background-position: 15px 15px;
   color: #171F26;
 }
 
 .newtab-customize-complex-option:hover > .selectable:not([selected]) + .newtab-customize-panel-subitem {
   background-color: #FFFFFF;
 }
 
 .newtab-customize-panel-item[selected],
 .newtab-search-panel-engine[selected] {
   background: url("chrome://global/skin/menu/shared-menu-check-active.svg") no-repeat transparent;
   background-size: 16px 16px;
-  background-position: 15px 20px;
+  background-position: 15px 15px;
   color: black;
   font-weight: 600;
 }
 
 .newtab-customize-panel-subitem > .checkbox {
   width: 18px;
   height: 18px;
   background-color: #FFFFFF;
@@ -601,26 +619,260 @@ input[type=button] {
   background-size: 9px 9px;
   background-position: center;
   color: #333333;
 }
 
 .newtab-customize-panel-subitem {
   font-size: 12px;
   padding-left: 40px;
-  padding-bottom: 20px;
+  padding-bottom: 15px;
   border-bottom: 1px solid threedshadow;
 }
 
 .newtab-customize-panel-subitem > label {
   padding-left: 10px;
 }
 
 .newtab-customize-panel-superitem {
   line-height: 14px;
   border-bottom: medium none !important;
-  padding: 20px 0px 10px 40px;
+  padding: 15px 0px 10px 40px;
 }
 
 .searchSuggestionTable {
   font: message-box;
   font-size: 16px;
 }
+
+/**
+ * Onboarding styling
+ */
+
+ #newtab-intro-mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: #424F5A;
+  z-index:102;
+  background-color: rgba(66,79,90,0.95);
+  transition: opacity .5s linear;
+  overflow: auto;
+  display: none;
+}
+
+#newtab-intro-modal {
+  font-family: "Helvetica";
+  width: 700px;
+  height: 500px;
+  position: fixed;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  margin: auto;
+  background: linear-gradient(#FFFFFF, #F9F9F9);
+  box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.7);
+  border-radius: 8px 8px 0px 0px;
+}
+
+#newtab-intro-header {
+  font-size: 28px;
+  color: #737980;
+  text-align: center;
+  top: 50px;
+  position: relative;
+  border-bottom: 2px solid #E0DFE0;
+  padding-bottom: 10px;
+  width: 600px;
+  display: block;
+  margin: 0px auto;
+  font-weight: 100;
+}
+
+#newtab-intro-header .bold {
+  font-weight: 500;
+  color: #343F48;
+}
+
+#newtab-intro-footer {
+  width: 100%;
+  height: 55px;
+  margin: 0px auto;
+  display: block;
+  position: absolute;
+  bottom: 0px;
+  background-color: white;
+  box-shadow: 0 -1px 4px -1px #EBEBEB;
+  text-align: center;
+  vertical-align: middle;
+  line-height: 55px;
+}
+
+#newtab-intro-footer > ul {
+  list-style-type: none;
+  margin: 0px;
+  padding: 0px;
+}
+
+#newtab-intro-footer > ul > li {
+  display: inline;
+  padding-left: 10px;
+  padding-right: 10px;
+}
+
+#newtab-intro-footer > ul > li > a {
+  text-decoration: none;
+  color: #4A90E2;
+}
+
+#newtab-intro-footer > ul > li > a:visited {
+  color: #171F26;
+}
+
+#newtab-intro-footer > ul > :first-child {
+  border-right: solid 1px #C1C1C1;
+}
+
+#newtab-intro-body {
+  height: 300px;
+  position: relative;
+  display: block;
+  top: 50px;
+  margin: 25px 50px 30px;
+}
+
+#newtab-intro-content > * {
+  display: inline-block;
+}
+
+#newtab-intro-content {
+  height: 210px;
+  position: relative;
+}
+
+#newtab-intro-buttons {
+  height: 90px;
+  text-align: center;
+  vertical-align: middle;
+  line-height: 90px;
+}
+
+#newtab-intro-tile {
+  width: 290px;
+  height: 100%;
+}
+
+#newtab-intro-text,
+#newtab-intro-image {
+  height: 100%;
+  width: 270px;
+  right: 0px;
+  position: absolute;
+  font-size: 14px;
+  line-height: 20px;
+  margin-top: -12px;
+}
+
+#newtab-intro-image {
+  left: 0px;
+  right: auto;
+}
+
+.newtab-intro-image-tile {
+}
+
+.newtab-intro-image-customize {
+  display: block;
+  box-shadow: 3px 3px 5px #888;
+  margin: 0 !important;
+}
+
+.newtab-intro-image-customize .newtab-customize-panel-item:not([selected]):hover {
+  background-color: inherit;
+  color: #7A7A7A;
+  background: none;
+}
+
+#newtab-intro-text > p {
+  margin: 0 0 1em 0;
+}
+
+#newtab-intro-text .newtab-control {
+  background-size: 18px auto;
+  height: 18px;
+  width: 18px;
+  vertical-align: middle;
+  opacity: 1;
+  position: inherit;
+}
+
+#newtab-intro-buttons > input {
+  width: 150px;
+  height: 50px;
+  margin: 0px 5px;
+  vertical-align: bottom;
+  border-radius: 2px;
+  border: solid 1px #2C72E2;
+  background-color: #FFFFFF;
+  color: #4A90E2;
+  -moz-user-focus: normal;
+}
+
+#newtab-intro-buttons > input[default] {
+  background-color: #4A90E2;
+  color: #FFFFFF;
+}
+
+#newtab-intro-buttons > input:hover {
+  background-color: #2C72E2;
+  color: #FFFFFF;
+}
+
+#newtab-intro-progress {
+  position: absolute;
+  width: 100%;
+}
+
+#newtab-intro-numerical-progress {
+  text-align: center;
+  top: 15px;
+  position: relative;
+  font-size: 12px;
+  color: #424F5A;
+}
+
+#newtab-intro-graphical-progress {
+  text-align: left;
+  border-radius: 1.5px;
+  overflow: hidden;
+  position: relative;
+  margin: 10px auto 0px;
+  height: 3px;
+  top: 8px;
+  width: 35px;
+  background-color: #DCDCDC;
+}
+
+#indicator {
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  display: inline-block;
+  width: 0%;
+  height: 4px;
+  background: none repeat scroll 0% 0% #FF9500;
+  transition: width 0.3s ease-in-out 0s;
+}
+
+#newtab-intro-numerical-progress[page="0"] + #newtab-intro-graphical-progress > #indicator {
+  width: 33%;
+}
+
+#newtab-intro-numerical-progress[page="1"] + #newtab-intro-graphical-progress > #indicator {
+  width: 66%;
+}
+
+#newtab-intro-numerical-progress[page="2"] + #newtab-intro-graphical-progress > #indicator {
+  width: 100%;
+}
--- a/browser/base/content/newtab/newTab.xul
+++ b/browser/base/content/newtab/newTab.xul
@@ -53,16 +53,45 @@
     <xul:hbox id="newtab-customize-blank" class="newtab-customize-panel-item selectable">
       <xul:label>&newtab.customize.blank2;</xul:label>
     </xul:hbox>
     <xul:hbox id="newtab-customize-learn" class="newtab-customize-panel-item">
       <xul:label>&newtab.customize.cog.learn;</xul:label>
     </xul:hbox>
   </xul:panel>
 
+  <div id="newtab-intro-mask">
+    <div id="newtab-intro-modal">
+      <div id="newtab-intro-progress">
+        <div id="newtab-intro-numerical-progress"/>
+        <div id="newtab-intro-graphical-progress">
+          <span id="indicator"/>
+        </div>
+      </div>
+      <div id="newtab-intro-header"/>
+      <div id="newtab-intro-body">
+        <div id="newtab-intro-content">
+          <div id="newtab-intro-image"/>
+          <div id="newtab-intro-text">
+            <p/><p/>
+          </div>
+        </div>
+        <div id="newtab-intro-buttons">
+          <input type="button" onclick="gIntro._back()"/>
+          <input type="button" default="true" onclick="gIntro._next()"/>
+        </div>
+      </div>
+      <div id="newtab-intro-footer">
+        <ul>
+          <li/><li/>
+        </ul>
+      </div>
+    </div>
+  </div>
+
   <div id="newtab-scrollbox">
 
     <div id="newtab-vertical-margin">
 
       <div id="newtab-margin-top"/>
 
       <div id="newtab-margin-undo-container">
         <div id="newtab-undo-container" undo-disabled="true">
--- a/browser/base/content/test/newtab/browser_newtab_intro.js
+++ b/browser/base/content/test/newtab/browser_newtab_intro.js
@@ -1,51 +1,119 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const INTRO_PREF = "browser.newtabpage.introShown";
+const UPDATE_INTRO_PREF = "browser.newtabpage.updateIntroShown";
 const PRELOAD_PREF = "browser.newtab.preload";
 
 function runTests() {
   let origIntro = Services.prefs.getBoolPref(INTRO_PREF);
+  let origUpdateIntro = Services.prefs.getBoolPref(UPDATE_INTRO_PREF);
   let origPreload = Services.prefs.getBoolPref(PRELOAD_PREF);
   registerCleanupFunction(_ => {
     Services.prefs.setBoolPref(INTRO_PREF, origIntro);
+    Services.prefs.setBoolPref(INTRO_PREF, origUpdateIntro);
     Services.prefs.setBoolPref(PRELOAD_PREF, origPreload);
   });
 
   // Test with preload false
   Services.prefs.setBoolPref(INTRO_PREF, false);
+  Services.prefs.setBoolPref(UPDATE_INTRO_PREF, false);
   Services.prefs.setBoolPref(PRELOAD_PREF, false);
 
-  let panel;
-  function maybeWaitForPanel() {
-    // If already open, no need to wait
-    if (panel.state == "open") {
-      executeSoon(TestRunner.next);
-      return;
-    }
-
-    // We're expecting the panel to open, so wait for it
-    panel.addEventListener("popupshown", TestRunner.next);
-    isnot(panel.state, "open", "intro panel can be slow to show");
-  }
+  let intro;
+  let brand = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+  let brandShortName = brand.GetStringFromName("brandShortName");
 
   yield addNewTabPageTab();
-  panel = getContentDocument().getElementById("newtab-intro-panel");
-  yield maybeWaitForPanel();
-  is(panel.state, "open", "intro automatically shown on first opening");
+  intro = getContentDocument().getElementById("newtab-intro-mask");
+  is(intro.style.opacity, 1, "intro automatically shown on first opening");
+  is(getContentDocument().getElementById("newtab-intro-header").innerHTML,
+     'Welcome to New Tab on <span xmlns="http://www.w3.org/1999/xhtml" class="bold">' + brandShortName + '</span>!', "we show the first-run intro.");
   is(Services.prefs.getBoolPref(INTRO_PREF), true, "newtab remembers that the intro was shown");
+  is(Services.prefs.getBoolPref(UPDATE_INTRO_PREF), true, "newtab avoids showing update if intro was shown");
 
   yield addNewTabPageTab();
-  panel = getContentDocument().getElementById("newtab-intro-panel");
-  is(panel.state, "closed", "intro not shown on second opening");
+  intro = getContentDocument().getElementById("newtab-intro-mask");
+  is(intro.style.opacity, 0, "intro not shown on second opening");
 
   // Test with preload true
   Services.prefs.setBoolPref(INTRO_PREF, false);
   Services.prefs.setBoolPref(PRELOAD_PREF, true);
 
   yield addNewTabPageTab();
-  panel = getContentDocument().getElementById("newtab-intro-panel");
-  yield maybeWaitForPanel();
-  is(panel.state, "open", "intro automatically shown on preloaded opening");
+  intro = getContentDocument().getElementById("newtab-intro-mask");
+  is(intro.style.opacity, 1, "intro automatically shown on preloaded opening");
+  is(getContentDocument().getElementById("newtab-intro-header").innerHTML,
+     'Welcome to New Tab on <span xmlns="http://www.w3.org/1999/xhtml" class="bold">' + brandShortName + '</span>!', "we show the first-run intro.");
   is(Services.prefs.getBoolPref(INTRO_PREF), true, "newtab remembers that the intro was shown");
+  is(Services.prefs.getBoolPref(UPDATE_INTRO_PREF), true, "newtab avoids showing update if intro was shown");
+
+  // Test with first run true but update false
+  Services.prefs.setBoolPref(UPDATE_INTRO_PREF, false);
+
+  yield addNewTabPageTab();
+  intro = getContentDocument().getElementById("newtab-intro-mask");
+  is(intro.style.opacity, 1, "intro automatically shown on preloaded opening");
+  is(getContentDocument().getElementById("newtab-intro-header").innerHTML,
+     "New Tab got an update!", "we show the update intro.");
+  is(Services.prefs.getBoolPref(INTRO_PREF), true, "INTRO_PREF stays true");
+  is(Services.prefs.getBoolPref(UPDATE_INTRO_PREF), true, "newtab remembers that the update intro was show");
+
+  // Test clicking the 'next' and 'back' buttons.
+  let buttons = getContentDocument().getElementById("newtab-intro-buttons").getElementsByTagName("input");
+  let progress = getContentDocument().getElementById("newtab-intro-numerical-progress");
+  let back = buttons[0];
+  let next = buttons[1];
+
+  is(progress.getAttribute("page"), 0, "we are on the first page");
+  is(intro.style.opacity, 1, "intro visible");
+
+
+  let createMutationObserver = function(fcn) {
+    return new Promise(resolve => {
+      let observer = new MutationObserver(function(mutations) {
+        fcn();
+        observer.disconnect();
+        resolve();
+      });
+      let config = { attributes: true, attributeFilter: ["style"], childList: true };
+      observer.observe(progress, config);
+    });
+  }
+
+  let p = createMutationObserver(function() {
+    is(progress.getAttribute("page"), 1, "we get to the 2nd page");
+    is(intro.style.opacity, 1, "intro visible");
+  });
+  next.click();
+  yield p.then(TestRunner.next);
+
+  p = createMutationObserver(function() {
+    is(progress.getAttribute("page"), 2, "we get to the 3rd page");
+    is(intro.style.opacity, 1, "intro visible");
+  });
+  next.click();
+  yield p.then(TestRunner.next);
+
+  p = createMutationObserver(function() {
+    is(progress.getAttribute("page"), 1, "go back to 2nd page");
+    is(intro.style.opacity, 1, "intro visible");
+  });
+  back.click();
+  yield p.then(TestRunner.next);
+
+  p = createMutationObserver(function() {
+    is(progress.getAttribute("page"), 0, "go back to 1st page");
+    is(intro.style.opacity, 1, "intro visible");
+  });
+  back.click();
+  yield p.then(TestRunner.next);
+
+
+  p = createMutationObserver(function() {
+    is(progress.getAttribute("page"), 0, "another back will 'skip tutorial'");
+    is(intro.style.opacity, 0, "intro exited");
+  });
+  back.click();
+  p.then(TestRunner.next);
 }
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -141,16 +141,17 @@ browser.jar:
   skin/classic/browser/downloads/download-summary.png (downloads/download-summary.png)
   skin/classic/browser/downloads/downloads.css        (downloads/downloads.css)
   skin/classic/browser/feeds/feedIcon.png             (feeds/feedIcon.png)
   skin/classic/browser/feeds/feedIcon16.png           (feeds/feedIcon16.png)
   skin/classic/browser/feeds/subscribe.css            (feeds/subscribe.css)
   skin/classic/browser/feeds/subscribe-ui.css         (feeds/subscribe-ui.css)
 * skin/classic/browser/newtab/newTab.css              (newtab/newTab.css)
   skin/classic/browser/newtab/controls.svg            (../shared/newtab/controls.svg)
+  skin/classic/browser/newtab/whimsycorn.png          (../shared/newtab/whimsycorn.png)
   skin/classic/browser/panic-panel/header.png         (../shared/panic-panel/header.png)
   skin/classic/browser/panic-panel/header-small.png   (../shared/panic-panel/header-small.png)
   skin/classic/browser/panic-panel/icons.png          (../shared/panic-panel/icons.png)
   skin/classic/browser/places/bookmarksMenu.png       (places/bookmarksMenu.png)
   skin/classic/browser/places/bookmarksToolbar.png    (places/bookmarksToolbar.png)
   skin/classic/browser/places/bookmarksToolbar-menuPanel.png (places/bookmarksToolbar-menuPanel.png)
   skin/classic/browser/places/bookmarks-notification-finish.png  (places/bookmarks-notification-finish.png)
   skin/classic/browser/places/bookmarks-menu-arrow.png           (places/bookmarks-menu-arrow.png)
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -226,16 +226,17 @@ browser.jar:
   skin/classic/browser/downloads/download-summary@2x.png    (downloads/download-summary@2x.png)
   skin/classic/browser/downloads/downloads.css              (downloads/downloads.css)
   skin/classic/browser/feeds/subscribe.css                  (feeds/subscribe.css)
   skin/classic/browser/feeds/subscribe-ui.css               (feeds/subscribe-ui.css)
   skin/classic/browser/feeds/feedIcon.png                   (feeds/feedIcon.png)
   skin/classic/browser/feeds/feedIcon16.png                 (feeds/feedIcon16.png)
 * skin/classic/browser/newtab/newTab.css                    (newtab/newTab.css)
   skin/classic/browser/newtab/controls.svg                  (../shared/newtab/controls.svg)
+  skin/classic/browser/newtab/whimsycorn.png                (../shared/newtab/whimsycorn.png)
   skin/classic/browser/setDesktopBackground.css
   skin/classic/browser/monitor.png
   skin/classic/browser/monitor_16-10.png
   skin/classic/browser/panic-panel/header.png               (../shared/panic-panel/header.png)
   skin/classic/browser/panic-panel/header@2x.png            (../shared/panic-panel/header@2x.png)
   skin/classic/browser/panic-panel/header-small.png         (../shared/panic-panel/header-small.png)
   skin/classic/browser/panic-panel/header-small@2x.png      (../shared/panic-panel/header-small@2x.png)
   skin/classic/browser/panic-panel/icons.png                (../shared/panic-panel/icons.png)
--- a/browser/themes/shared/newtab/newTab.inc.css
+++ b/browser/themes/shared/newtab/newTab.inc.css
@@ -82,17 +82,19 @@
 #newtab-customize-button:-moz-any(:hover, :active, [active]) {
   background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 64, 32, 32);
   background-color: #FFFFFF;
   border: solid 1px #CCCCCC;
   border-radius: 2px;
 }
 
 /* CELLS */
-.newtab-cell {
+.newtab-cell,
+.newtab-intro-cell,
+.newtab-intro-cell-hover {
   background-color: rgba(255,255,255,.2);
   border-radius: 8px;
 }
 
 .newtab-cell:empty {
   outline: 2px dashed #c1c1c1;
   -moz-outline-radius: 8px;
 }
@@ -101,22 +103,29 @@
 .newtab-site {
   border-radius: inherit;
   box-shadow: 0 1px 3px #c1c1c1;
   text-decoration: none;
   transition-property: top, left, opacity, box-shadow, background-color;
 }
 
 .newtab-cell:not([ignorehover]) > .newtab-site:hover,
-.newtab-site[dragged] {
+.newtab-site[dragged],
+.newtab-intro-cell-hover {
   border: 2px solid white;
   box-shadow: 0 0 6px 2px #4cb1ff;
   margin: -2px;
 }
 
+.newtab-intro-cell .newtab-thumbnail,
+.newtab-intro-cell-hover .newtab-thumbnail {
+  background-color: #cae1f4;
+  background-image: url("chrome://browser/skin/newtab/whimsycorn.png");
+}
+
 .newtab-site[dragged] {
   transition-property: box-shadow, background-color;
   background-color: rgb(242,242,242);
 }
 
 /* LINKS */
 .newtab-link {
   border-radius: 6px;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5c5c2f4989fdb128cae9fa39a8b6063d84276398
GIT binary patch
literal 3875
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
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -182,16 +182,17 @@ browser.jar:
         skin/classic/browser/feeds/feedIcon.png                      (feeds/feedIcon.png)
         skin/classic/browser/feeds/feedIcon16.png                    (feeds/feedIcon16.png)
         skin/classic/browser/feeds/feedIcon-XP.png                   (feeds/feedIcon-XP.png)
         skin/classic/browser/feeds/feedIcon16-XP.png                 (feeds/feedIcon16-XP.png)
         skin/classic/browser/feeds/subscribe.css                     (feeds/subscribe.css)
         skin/classic/browser/feeds/subscribe-ui.css                  (feeds/subscribe-ui.css)
 *       skin/classic/browser/newtab/newTab.css                       (newtab/newTab.css)
         skin/classic/browser/newtab/controls.svg                     (../shared/newtab/controls.svg)
+        skin/classic/browser/newtab/whimsycorn.png                   (../shared/newtab/whimsycorn.png)
         skin/classic/browser/panic-panel/header.png                  (../shared/panic-panel/header.png)
         skin/classic/browser/panic-panel/header-small.png            (../shared/panic-panel/header-small.png)
         skin/classic/browser/panic-panel/icons.png                   (../shared/panic-panel/icons.png)
         skin/classic/browser/places/places.css                       (places/places.css)
 *       skin/classic/browser/places/organizer.css                    (places/organizer.css)
         skin/classic/browser/places/bookmark.png                     (places/bookmark.png)
         skin/classic/browser/places/bookmark-XP.png                  (places/bookmark-XP.png)
         skin/classic/browser/places/query.png                        (places/query.png)