Bug 1138818 - Part 1 - Onboarding UI without the tile images. r=adw

This commit is contained in:
Marina Samuel 2015-05-13 12:23:45 -04:00
Родитель 24944a62ae
Коммит 85b489310e
5 изменённых файлов: 524 добавлений и 28 удалений

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

@ -1632,6 +1632,9 @@ pref("browser.newtab.preload", true);
// Remembers if the about:newtab intro has been shown // Remembers if the about:newtab intro has been shown
pref("browser.newtabpage.introShown", false); 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. // Toggles the content of 'about:newtab'. Shows the grid when enabled.
pref("browser.newtabpage.enabled", true); pref("browser.newtabpage.enabled", true);

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

@ -5,13 +5,72 @@
#endif #endif
const PREF_INTRO_SHOWN = "browser.newtabpage.introShown"; 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 = { let gIntro = {
_enUSStrings: {
"newtab.intro.paragraph2": "In order to provide this service, Mozilla collects and uses certain analytics information relating to your use of the tiles in accordance with our %1$S.",
"newtab.intro.paragraph4": "You can turn off this feature by clicking the gear (%1$S) button and selecting 'Show blank page' in the %2$S menu.",
"newtab.intro.paragraph5": "New Tab will show the sites you visit most frequently, along with sites we think might be of interest to you. To get started, you'll see several sites from Mozilla.",
"newtab.intro.paragraph6": "You can %1$S or %2$S any site by using the controls available on rollover.",
"newtab.intro.paragraph7": "Some of the sites you will see may be suggested by Mozilla and may be sponsored by a Mozilla partner. We'll always indicate which sites are sponsored.",
"newtab.intro.paragraph8": "Firefox will only show sites that most closely match your interests on the Web. %1$S",
"newtab.intro.paragraph9": "Now when you open New Tab, you'll also see sites we think might be interesting to you.",
"newtab.intro.controls": "New Tab Controls",
"newtab.learn.link2": "More about New Tab",
"newtab.privacy.link2": "About your privacy",
"newtab.intro.remove": "remove",
"newtab.intro.pin": "pin",
"newtab.intro.header.welcome": "Welcome to New Tab on %1$S",
"newtab.intro.header.update": "New Tab got an update!",
"newtab.intro.skip": "Skip this",
"newtab.intro.continue": "Continue tour",
"newtab.intro.back": "Back",
"newtab.intro.next": "Next",
"newtab.intro.gotit": "Got it!",
"newtab.intro.firefox": "Firefox!"
},
_nodeIDSuffixes: [ _nodeIDSuffixes: [
"panel", "panel",
"what", "what",
"mask",
"modal",
"numerical-progress",
"text",
"buttons",
"header",
"footer"
], ],
/**
* 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"]]
},
_paragraphs: [],
_nodes: {}, _nodes: {},
init: function() { init: function() {
@ -19,22 +78,149 @@ let gIntro = {
this._nodes[idSuffix] = document.getElementById("newtab-intro-" + idSuffix); this._nodes[idSuffix] = document.getElementById("newtab-intro-" + idSuffix);
} }
this._nodes.panel.addEventListener("popupshowing", e => this._setUpPanel()); if (DirectoryLinksProvider.locale != "en-US") {
this._nodes.panel.addEventListener("popuphidden", e => this._hidePanel()); this._nodes.what.style.display = "block";
this._nodes.what.addEventListener("click", e => this.showPanel()); this._nodes.panel.addEventListener("popupshowing", e => this._setUpPanel());
this._nodes.panel.addEventListener("popuphidden", e => this._hidePanel());
this._nodes.what.addEventListener("click", e => this.showPanel());
}
},
_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 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", this._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);
},
_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(this._newTabString("intro.controls"))],
"6": [this._bold(this._newTabString("intro.remove")), this._bold(this._newTabString("intro.pin"))],
"7": [this._link(TILES_INTRO_LINK, newTabString("learn.link"))],
"8": [this._link(TILES_INTRO_LINK, newTabString("learn.link"))]
}
for (let i = 1; i <= MAX_PARAGRAPH_ID; i++) {
try {
this._paragraphs.push(this._newTabString("intro.paragraph" + i, substringMappings[i]))
} catch (ex) {
// Paragraph with this ID doesn't exist so continue
}
}
},
_newTabString: function(str, substrArr) {
let regExp = /%[0-9]\$S/g;
let paragraph = this._enUSStrings["newtab." + str];
if (!paragraph) {
throw new Error("Paragraph doesn't exist");
}
let matches;
while ((matches = regExp.exec(paragraph)) !== null) {
let match = matches[0];
let index = match.charAt(1); // Get the digit in the regExp.
paragraph = paragraph.replace(match, substrArr[index - 1]);
}
return paragraph;
}, },
showIfNecessary: function() { showIfNecessary: function() {
if (!Services.prefs.getBoolPref(PREF_INTRO_SHOWN)) { 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) && DirectoryLinksProvider.locale == "en-US") {
this._onboardingType = UPDATE;
this.showPanel(); this.showPanel();
} }
Services.prefs.setBoolPref(PREF_INTRO_SHOWN, true);
Services.prefs.setBoolPref(PREF_UPDATE_INTRO_SHOWN, true);
}, },
showPanel: function() { showPanel: function() {
// Point the panel at the 'what' link if (DirectoryLinksProvider.locale != "en-US") {
this._nodes.panel.hidden = false; // Point the panel at the 'what' link
this._nodes.panel.openPopup(this._nodes.what); this._nodes.panel.hidden = false;
this._nodes.panel.openPopup(this._nodes.what);
return;
}
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._goToPage(0);
// Header text
let boldSubstr = this._onboardingType == WELCOME ? this._span(this._newTabString("intro.firefox"), "bold") : "";
this._nodes.header.innerHTML = this._newTabString("intro.header." + this._onboardingType, [boldSubstr]);
// Footer links
let footerLinkNodes = this._nodes.footer.getElementsByTagName("li");
[this._link(TILES_INTRO_LINK, this._newTabString("learn.link2")),
this._link(TILES_PRIVACY_LINK, this._newTabString("privacy.link2")),
].forEach((arg, index) => {
footerLinkNodes[index].innerHTML = arg;
});
}, },
_setUpPanel: function() { _setUpPanel: function() {

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

@ -55,6 +55,7 @@ input[type=button] {
position: absolute; position: absolute;
right: 70px; right: 70px;
top: 20px; top: 20px;
display: none;
} }
#newtab-intro-what:-moz-locale-dir(rtl) { #newtab-intro-what:-moz-locale-dir(rtl) {
@ -624,3 +625,215 @@ input[type=button] {
font: message-box; font: message-box;
font-size: 16px; 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 {
height: 100%;
width: 270px;
right: 0px;
position: absolute;
font-size: 14px;
line-height: 20px;
}
#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%;
}

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

@ -58,6 +58,35 @@
</xul:hbox> </xul:hbox>
</xul:panel> </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-scrollbox">
<div id="newtab-vertical-margin"> <div id="newtab-vertical-margin">

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

@ -2,50 +2,115 @@
http://creativecommons.org/publicdomain/zero/1.0/ */ http://creativecommons.org/publicdomain/zero/1.0/ */
const INTRO_PREF = "browser.newtabpage.introShown"; const INTRO_PREF = "browser.newtabpage.introShown";
const UPDATE_INTRO_PREF = "browser.newtabpage.updateIntroShown";
const PRELOAD_PREF = "browser.newtab.preload"; const PRELOAD_PREF = "browser.newtab.preload";
function runTests() { function runTests() {
let origIntro = Services.prefs.getBoolPref(INTRO_PREF); let origIntro = Services.prefs.getBoolPref(INTRO_PREF);
let origUpdateIntro = Services.prefs.getBoolPref(UPDATE_INTRO_PREF);
let origPreload = Services.prefs.getBoolPref(PRELOAD_PREF); let origPreload = Services.prefs.getBoolPref(PRELOAD_PREF);
registerCleanupFunction(_ => { registerCleanupFunction(_ => {
Services.prefs.setBoolPref(INTRO_PREF, origIntro); Services.prefs.setBoolPref(INTRO_PREF, origIntro);
Services.prefs.setBoolPref(INTRO_PREF, origUpdateIntro);
Services.prefs.setBoolPref(PRELOAD_PREF, origPreload); Services.prefs.setBoolPref(PRELOAD_PREF, origPreload);
}); });
// Test with preload false // Test with preload false
Services.prefs.setBoolPref(INTRO_PREF, false); Services.prefs.setBoolPref(INTRO_PREF, false);
Services.prefs.setBoolPref(UPDATE_INTRO_PREF, false);
Services.prefs.setBoolPref(PRELOAD_PREF, false); Services.prefs.setBoolPref(PRELOAD_PREF, false);
let panel; let intro;
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");
}
yield addNewTabPageTab(); yield addNewTabPageTab();
panel = getContentDocument().getElementById("newtab-intro-panel"); intro = getContentDocument().getElementById("newtab-intro-mask");
yield maybeWaitForPanel(); is(intro.style.opacity, 1, "intro automatically shown on first opening");
is(panel.state, "open", "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">Firefox!</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(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(); yield addNewTabPageTab();
panel = getContentDocument().getElementById("newtab-intro-panel"); intro = getContentDocument().getElementById("newtab-intro-mask");
is(panel.state, "closed", "intro not shown on second opening"); is(intro.style.opacity, 0, "intro not shown on second opening");
// Test with preload true // Test with preload true
Services.prefs.setBoolPref(INTRO_PREF, false); Services.prefs.setBoolPref(INTRO_PREF, false);
Services.prefs.setBoolPref(PRELOAD_PREF, true); Services.prefs.setBoolPref(PRELOAD_PREF, true);
yield addNewTabPageTab(); yield addNewTabPageTab();
panel = getContentDocument().getElementById("newtab-intro-panel"); intro = getContentDocument().getElementById("newtab-intro-mask");
yield maybeWaitForPanel(); is(intro.style.opacity, 1, "intro automatically shown on preloaded opening");
is(panel.state, "open", "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">Firefox!</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(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);
} }