Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,19 @@
<element name="fxa-coppa" extends="section">
<template>
<section class="vertical">
<h3 data-l10n-id="fxa-age-verification">
Age verification
</h3>
<ul class="fxa-verification-wrapper">
<li>
<span class="button icon icon-dialog">
<select id="fxa-age-select">
<option value="0" data-l10n-id="fxa-year-of-birth">Year of birth</option>
</select>
</span>
</li>
</ul>
</section>
<script src='js/screens/fxam_coppa.js'></script>
</template>
</element>

View file

@ -0,0 +1,16 @@
<element name="fxa-email" extends="section">
<template>
<section class="vertical">
<div id="fxa-logo" class="fxa-logo"></div>
<p class="fxa-intro" data-l10n-id="fxa-use-email-to-sign-in">
</p>
<input id="fxa-email-input" data-l10n-id="fxa-enter-email-input" type="email" placeholder="Enter your email" required="" x-inputmode="verbatim">
<p id="fxa-notice" class="fine-print" data-l10n-id="fxa-notice2">
<a id="fxa-terms" href="https://accounts.firefox.com/legal/terms"></a>
<a id="fxa-privacy" href="https://accounts.firefox.com/legal/privacy"></a>
</p>
</section>
<script src='js/screens/fxam_enter_email.js'></script>
</template>
</element>

View file

@ -0,0 +1,18 @@
<element name="fxa-enter-password" extends="section">
<template>
<section class="vertical">
<h3 data-l10n-id="fxa-enter-password-title">Enter Your Password</h3>
<p id="fxa-hello-known-user">Hello, <a id="fxa-known-user-email">holaENTER@email.com</a></p>
<input id="fxa-pw-input" type="password" placeholder="Password" required pattern=".{8,}" data-l10n-id="fxa-password" x-inputmode="verbatim">
<p class="fine-print"><a id="fxa-forgot-password" data-l10n-id="fxa-forgot-your-password">Forgot your password?</a></p>
<div id="show-pw-wrapper">
<label class="pack-checkbox" for="fxa-show-pw">
<input id="fxa-show-pw" type="checkbox">
<span></span>
</label>
<p data-l10n-id="fxa-show-password">Show password</p>
</div>
<script src='js/screens/fxam_enter_password.js'></script>
</section>
</template>
</element>

View file

@ -0,0 +1,16 @@
<element name="fxa-error-overlay" extends="form">
<template>
<section>
<h1 id="fxa-error-title"></h1>
<p>
<small id="fxa-error-msg"></small>
<small id="fxa-error-msg-coppa">
<a id="coppa-link" href="#"></a>
</small>
</p>
<menu>
<button class="full" data-l10n-id="fxa-ok" id="fxa-error-ok"></button>
</menu>
</section>
</template>
</element>

View file

@ -0,0 +1,13 @@
<element name="fxa-pp" extends="section">
<template>
<section role="region">
<h3 data-l10n-id="fxa-pp-header">Privacy Policy</h3>
<p data-l10n-id="fxa-pp1">
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eius mod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequ at. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
</p>
<p data-l10n-id="fxa-pp2">
Excepteur sint occaecat qui of fi cia deserunt mollit anim id est lab orum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. D uis aute irure dolor in.
</p>
</section>
</template>
</element>

View file

@ -0,0 +1,17 @@
<element name="fxa-refresh-auth" extends="section">
<template>
<section class="vertical">
<p id="fxa-password-refresh" data-l10n-id="fxa-password-refresh">Please enter your Firefox Account password to continue.</p>
<input id="fxa-pw-input-refresh" type="password" placeholder="Password" required="" pattern=".{8,}" data-l10n-id="fxa-password">
<p class="fine-print"><a id="fxa-forgot-password-refresh" data-l10n-id="fxa-forgot-your-password">Forgot your password?</a></p>
<div id="show-pw-wrapper">
<label class="pack-checkbox" for="fxa-show-pw-refresh">
<input id="fxa-show-pw-refresh" type="checkbox">
<span></span>
</label>
<p data-l10n-id="fxa-show-password">Show password</p>
</div>
<script src='js/screens/fxam_refresh_auth.js'></script>
</section>
</template>
</element>

View file

@ -0,0 +1,19 @@
<element name="fxa-set-password" extends="section">
<template>
<section class="vertical">
<h3 data-l10n-id="fxa-create-password-title"></h3>
<p id="fxa-hello-user"><a id="fxa-user-set-email"></a></p>
<input id="fxa-pw-set-input" type="password" placeholder="Password" required="" pattern=".{8,}" data-l10n-id="fxa-password" x-inputmode="verbatim">
<p class="fine-print" data-l10n-id="fxa-password-length-requirement">
</p>
<div id="show-pw-wrapper">
<label class="pack-checkbox" for="fxa-show-pw-set">
<input id="fxa-show-pw-set" type="checkbox">
<span></span>
</label>
<p data-l10n-id="fxa-show-password"></p>
</div>
<script src='js/screens/fxam_set_password.js'></script>
</section>
</template>
</element>

View file

@ -0,0 +1,19 @@
<element name="fxa-signin-success" extends="section">
<template>
<section class="vertical">
<h3 data-l10n-id="fxa-success">
Success!
</h3>
<p data-l10n-id="fxa-ready-to-go">
Your Firefox Account is ready to go!
</p>
<p data-l10n-id="fxa-use-features">
Now you can use features like Find My Device and apps like the Firefox Marketplace.
</p>
<p data-l10n-id="fxa-access-manage-account-settings">
In the future, you can access and manage your account from Settings.
</p>
<script src='js/screens/fxam_signin_success.js'></script>
</section>
</template>
</element>

View file

@ -0,0 +1,19 @@
<element name="fxa-signup-success" extends="section">
<template>
<section class="vertical">
<h3 data-l10n-id="fxa-confirm-account">
Confirm Your Account
</h3>
<p id="fxa-will-send-email" class="fxa-envelope">
We will send an email to: <strong id="fxa-summary-email">email</strong>
</p>
<p data-l10n-id="fxa-signup-click-verification-link-in-email">
Next time you check your email, click the verification link to confirm your Firefox Account.
</p>
<p data-l10n-id="fxa-access-manage-account-settings">
In the future, you can access and manage your account from Settings.
</p>
<script src='js/screens/fxam_signup_success.js'></script>
</section>
</template>
</element>

View file

@ -0,0 +1,13 @@
<element name="fxa-tos" extends="section">
<template>
<section role="region">
<h3 data-l10n-id="fxa-tos-header">Terms of Service</h3>
<p data-l10n-id="fxa-tos1">
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eius mod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequ at. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
</p>
<p data-l10n-id="fxa-tos2">
Excepteur sint occaecat qui of fi cia deserunt mollit anim id est lab orum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. D uis aute irure dolor in.
</p>
</section>
</template>
</element>

View file

@ -0,0 +1,104 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="../shared/style/switches.css" rel="stylesheet" type="text/css">
<link href="../shared/style/buttons.css" rel="stylesheet" type="text/css">
<link href="../shared/style/confirm.css" rel="stylesheet" type="text/css">
<link href="../shared/style/input_areas.css" rel="stylesheet" type="text/css">
<link href="../shared/style/progress_activity.css" rel="stylesheet" type="text/css">
<link href="style/fxa.css" rel="stylesheet" type="text/css">
<!-- Gaia Components -->
<link href="app://theme.gaiamobile.org/shared/elements/gaia-theme/gaia-theme.css" rel="stylesheet" type="text/css">
<link href="../shared/elements/gaia-icons/gaia-icons.css" rel="stylesheet" type="text/css">
<meta name="defaultLanguage" content="en-US">
<meta name="availableLanguages" content="en-US">
<link rel="localization" href="locales/fxa.{locale}.properties">
<link rel="import" href="elements/fxa-email.html">
<link rel="import" href="elements/fxa-set-password.html">
<link rel="import" href="elements/fxa-signup-success.html">
<link rel="import" href="elements/fxa-enter-password.html">
<link rel="import" href="elements/fxa-signin-success.html">
<link rel="import" href="elements/fxa-tos.html">
<link rel="import" href="elements/fxa-pp.html">
<link rel="import" href="elements/fxa-refresh-auth.html">
<link rel="import" href="elements/fxa-coppa.html">
<script defer src="../js/service.js"></script>
<script defer src="../js/browser_frame.js"></script>
<script defer src="../js/entry_sheet.js"></script>
<script defer src="../shared/js/lazy_loader.js"></script>
<script defer src="../shared/js/l10n.js"></script>
<script defer src="../shared/js/settings_helper.js"></script>
<script defer src="../shared/js/utilities.js"></script>
<!-- Gaia Components -->
<script defer src="../shared/elements/config.js"></script>
<script defer src="../shared/elements/gaia-header/dist/gaia-header.js"></script>
<script defer src="js/fxam_server_request.js"></script>
<script defer src="js/fxam_overlay.js"></script>
<script defer src="js/fxam_error_overlay.js"></script>
<script defer src="js/fxam_navigation.js"></script>
<script defer src="js/fxam_ui.js"></script>
<script defer src="js/fxam_manager.js"></script>
<script defer src="js/fxam_states.js"></script>
<script defer src="js/fxam_module.js"></script>
</head>
<body class="theme-settings">
<section role="region">
<gaia-header id="fxa-module-header" action="close">
<h1 data-l10n-id="firefox-accounts-title"></h1>
</gaia-header>
<div class="bb-steps">
<progress id="fxa-progress" value="" max="100"></progress>
</div>
<article id="step-container">
<section id="fxa-email" is="fxa-email" class="screen"></section>
<section id="fxa-set-password" is="fxa-set-password" class="screen fxa-password"></section>
<section id="fxa-enter-password" is="fxa-enter-password" class="screen fxa-password"></section>
<section id="fxa-signup-success" is="fxa-signup-success" class="screen"></section>
<section id="fxa-signin-success" is="fxa-signin-success" class="screen"></section>
<section id="fxa-tos" is="fxa-tos" class="screen" data-navigation="back"></section>
<section id="fxa-pp" is="fxa-pp" class="screen" data-navigation="back"></section>
<section id="fxa-refresh-auth" is="fxa-refresh-auth" class="screen fxa-password"></section>
<section id="fxa-coppa" is="fxa-coppa" class="screen fxa-coppa"></section>
</article>
<nav role="navigation" class="horizontal navigation navigation-single-button" id="fxa-module-navigation">
<button id="fxa-module-back" class="start" data-l10n-id="fxa-back">
Back
</button>
<button id="fxa-module-next" class="recommend end" data-l10n-id="fxa-next">
Next
</button>
<button id="fxa-module-done" class="recommend end" data-l10n-id="fxa-done">
Done
</button>
</nav>
</section>
<!-- Progress overlay -->
<section id="fxa-overlay" role="dialog">
<progress></progress>
<p id="fxa-overlay-msg" data-l10n-id="fxa-connecting"></p>
</section>
<!-- Error message overlay -->
<form role="dialog" data-type="confirm" id="fxa-error-overlay" class="fade-in">
<section>
<h1 id="fxa-error-title"></h1>
<p>
<small id="fxa-error-msg"></small>
<small id="fxa-error-msg-coppa">
<a id="coppa-link" href="#"></a>
</small>
</p>
</section>
<menu>
<button class="full" data-l10n-id="fxa-ok" id="fxa-error-ok"></button>
</menu>
</form>
</body>
</html>

View file

@ -0,0 +1,64 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global HtmlHelper */
/* exported FxaModuleErrorOverlay */
'use strict';
/*
* FxaModuleErrorOverlay shows an error prompt to the user with the info
* retrieved after getting an error.
* As params, we can define Title and Message.
*/
var FxaModuleErrorOverlay = {
init: function fxam_error_overlay_init() {
if (this.initialized) {
return;
}
HtmlHelper.importElements(this,
'fxa-error-overlay',
'fxa-error-title',
'fxa-error-msg',
'fxa-error-msg-coppa',
'fxa-error-ok'
);
this.fxaErrorOk.addEventListener('click', this.hide.bind(this));
this.fxaErrorOverlay.addEventListener('submit', this.prevent);
this.initialized = true;
},
show: function fxam_error_overlay_show(titleL10n, messageL10n, resp) {
this.init();
this.fxaErrorTitle.setAttribute('data-l10n-id', titleL10n);
if (resp && resp.error === 'COPPA_ERROR') {
this.fxaErrorMsg.style.display = 'none';
this.fxaErrorMsgCoppa.style.display = 'inline';
this.fxaErrorMsgCoppa.setAttribute('data-l10n-id', messageL10n);
} else {
this.fxaErrorMsg.style.display = 'inline';
this.fxaErrorMsgCoppa.style.display = 'none';
this.fxaErrorMsg.setAttribute('data-l10n-id', messageL10n);
}
this.fxaErrorOverlay.classList.add('show');
},
hide: function fxam_overlay_hide() {
this.init();
this.fxaErrorOverlay.classList.remove('show');
},
prevent: function fxam_prevent(event) {
event.preventDefault();
event.stopPropagation();
}
};

View file

@ -0,0 +1,58 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
(function(exports) {
var Errors = {
CONNECTION_ERROR: {
title: 'fxa-connection-error-title',
message: 'fxa-connection-error-message'
},
RESET_PASSWORD_ERROR: {
title: 'fxa-reset-password-error-title',
message: 'fxa-reset-password-error-message'
},
INVALID_EMAIL: {
title: 'fxa-invalid-email-title',
message: 'fxa-invalid-email-message'
},
INVALID_PASSWORD: {
title: 'fxa-invalid-password-title',
message: 'fxa-invalid-password-message'
},
COPPA_ERROR: {
title: 'fxa-coppa-failure-error-title',
message: 'fxa-coppa-failure-error-message3'
},
COPPA_FTU_ERROR: {
title: 'fxa-coppa-failure-error-title',
message: 'fxa-coppa-ftu-error-message'
},
OFFLINE: {
title: 'fxa-offline-error-title',
message: 'fxa-offline-error-message'
},
UNKNOWN: {
title: 'fxa-unknown-error-title',
message: 'fxa-unknown-error-message'
}
};
function _getError(error) {
var l10nKeys = Errors[error] || Errors.UNKNOWN;
return {
title: l10nKeys.title,
message: l10nKeys.message
};
}
var FxaModuleErrors = {
responseToParams: function fxame_responseToParams(response) {
return response && response.error ?
_getError(response.error) : _getError('GENERIC_ERROR');
}
};
exports.FxaModuleErrors = FxaModuleErrors;
}(window));

View file

@ -0,0 +1,58 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global FxaModuleUI */
'use strict';
/*
* FxaModuleManager is in charge of communication with the core of
* FxAccounts, sending the info retrieved during the process if the
* process was succeeded.
*/
var FxaModuleManager = {
paramsRetrieved: null,
init: function fxamm_init() {
// Some FxA UI flows require us to get some data from the platform, like
// the account information. This data is given by FxAccountsUI in the form
// of URL query parameters.
var hash = window.location.hash.split('?');
var flow = hash[0].replace('#', '');
if (hash.length > 1) {
var queryParams = hash[1].split('&');
for (var i = 0; i < queryParams.length; i++) {
var param = queryParams[i].split('=');
if (param.length != 2) {
console.warning('Wrong query parameter');
continue;
}
this.setParam(param[0], param[1]);
}
}
FxaModuleUI.init(flow);
},
setParam: function fxamm_setParam(key, value) {
if (!this.paramsRetrieved) {
this.paramsRetrieved = {};
}
this.paramsRetrieved[key] = value;
},
done: function fxamm_done() {
window.parent.FxAccountsUI.done(FxaModuleManager.paramsRetrieved);
},
close: function fxamm_close(error) {
// If there is no params retrieved, or the flow is not complete ()
if (!this.paramsRetrieved ||
!this.paramsRetrieved.done) {
window.parent.FxAccountsUI.error(error);
return;
}
window.parent.FxAccountsUI.done(this.paramsRetrieved);
}
};
window.addEventListener('load', function managerLoaded() {
window.removeEventListener('load', managerLoaded);
FxaModuleManager.init();
});

View file

@ -0,0 +1,97 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global HtmlHelper, FxaModuleOverlay, LazyLoader, FxaModuleErrors,
FxaModuleErrorOverlay, BrowserFrame, EntrySheet */
/* exported FxaModule */
'use strict';
var FxaModule = (function() {
var Module = {
initialized: false,
init: function fxam_init() {
// override this to do initialization
// l10n note: this method is called after mozL10n.once has fired. It is
// safe to assume strings have loaded and mozL10n.readyState is 'complete'
// inside this function and other Module functions.
},
onNext: function fxam_onNext(gotoNextStepCallback) {
// override this to take care of when the user clicks on the "next"
// button. Validate any inputs, talk with the backend, do processing.
// When complete, call gotoNextStepCallback with the next state from
// fxam_states.js
},
onBack: function fxam_onBack() {
// handle "back" button presses.
},
onDone: function fxam_onDone(doneCallback) {
doneCallback();
},
importElements: function fxam_importElements() {
var args = [].slice.call(arguments);
// context to import into is the first argument to importElements
args.unshift(this);
HtmlHelper.importElements.apply(null, args);
},
showErrorResponse: function fxam_showErrorResponse(response) {
// special case: if a network error occurs during FTE,
// we show a slightly different error message
var resp = response;
if (window.location.search.indexOf('isftu') != -1 &&
resp == 'OFFLINE_ERROR') {
resp = 'CONNECTION_ERROR';
}
FxaModuleOverlay.hide();
LazyLoader.load('js/fxam_errors.js', () => {
var config = FxaModuleErrors.responseToParams(resp);
FxaModuleErrorOverlay.show(config.title, config.message, resp);
if (resp && resp.error === 'COPPA_ERROR') {
var link =
FxaModuleErrorOverlay.fxaErrorMsgCoppa.querySelector('#coppa-link');
link.addEventListener('click', this.onCopaLinkClick);
}
});
},
onCopaLinkClick: function(e) {
e.preventDefault();
if (this.entrySheet) {
this.entrySheet.close();
this.entrySheet = null;
}
var coppaUrl = 'http://www.ftc.gov/news-events/media-resources/' +
'protecting-consumer-privacy/kids-privacy-coppa';
this.entrySheet = new EntrySheet(
window.top.document.getElementById('screen'),
'URL:' + coppaUrl,
new BrowserFrame({
url: coppaUrl,
oop: true
})
);
this.entrySheet.open();
},
hideErrorResponse: function fxam_hideErrorResponse() {
var link =
FxaModuleErrorOverlay.fxaErrorMsgCoppa.querySelector('#coppa-link');
link.removeEventListener('click', this.onCopaLinkClick);
FxaModuleErrorOverlay.hide();
FxaModuleOverlay.hide();
}
};
return Module;
}());

View file

@ -0,0 +1,106 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global LazyLoader, FxaModuleUI, View, FxaModuleManager, FxaModuleStates */
/* exported FxaModuleNavigation */
'use strict';
/*
* This code controls the navigation between modules. When tapping on
* next/back, we load the next/previous module, loading resources and
* updating the UI. Transitions are based on hash change.
*/
var FxaModuleNavigation = {
stepCount: 0,
currentModule: null,
init: function fxam_nav_init(flow) {
// Listen on hash changes for panel changes
window.addEventListener(
'hashchange',
this.hashchangeHandler.bind(this),
false
);
// Load view
LazyLoader.load('view/view_' + flow + '.js', function loaded() {
FxaModuleUI.setMaxSteps(View.length);
window.location.hash = View.start.id;
}.bind(this));
},
hashchangeHandler: function fxam_nav_hashchangeHandler() {
if (!location.hash) {
return;
}
var panel = document.querySelector(location.hash);
if (!panel || !panel.classList.contains('screen')) {
return;
}
if (this.backAnim) {
this.stepCount--;
this.loadStep(panel, true);
} else {
this.stepCount++;
this.loadStep(panel);
}
},
loadStep: function fxam_nav_loadStep(panel, back) {
if (!panel) {
return;
}
FxaModuleUI.loadScreen({
panel: panel,
count: this.stepCount,
back: this.backAnim,
onload: function() {
this.currentModule = window[this.moduleById(panel.id)];
this.currentModule.init &&
this.currentModule.init(FxaModuleManager.paramsRetrieved);
}.bind(this),
onanimate: function() {
this.backAnim = false;
this.updatingStep = false;
}.bind(this)
});
},
back: function fxam_nav_back() {
// Avoid multiple taps on 'back' if screen transition is not over.
if (this.updatingStep) {
return;
}
this.updatingStep = true;
// Execute module back (if is defined)
this.currentModule.onBack && this.currentModule.onBack();
// Go to previous step
this.backAnim = true;
window.history.back();
},
loadNextStep: function fxam_loadNextStep(nextStep) {
if (!nextStep || !nextStep.id) {
return;
}
location.hash = nextStep.id;
},
next: function fxam_nav_next() {
this.currentModule.onNext(this.loadNextStep.bind(this));
},
moduleById: function fxam_nav_moduleById(id) {
var moduleKey = Object.keys(FxaModuleStates).filter(function(module) {
return FxaModuleStates[module] &&
FxaModuleStates[module].id &&
FxaModuleStates[module].id === id;
}).pop();
if (moduleKey) {
return FxaModuleStates[moduleKey].module;
}
},
done: function fxam_nav_done() {
this.currentModule.onDone(FxaModuleManager.done);
}
};

View file

@ -0,0 +1,41 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global HtmlHelper */
/* exported FxaModuleOverlay */
'use strict';
/*
* FxaModuleOverlay shows an loading prompt to the user.
* As params, we can only a message which will be shown with
* a spinner of 'loading'.
*/
var FxaModuleOverlay = {
init: function fxam_overlay_init() {
if (this.initialized) {
return;
}
HtmlHelper.importElements(this,
'fxa-overlay',
'fxa-overlay-msg'
);
this.initialized = true;
},
show: function fxam_overlay_show(l10nId) {
this.init();
this.fxaOverlayMsg.setAttribute('data-l10n-id', l10nId);
this.fxaOverlay.classList.add('show');
},
hide: function fxam_overlay_hide() {
this.init();
this.fxaOverlay.classList.remove('show');
}
};

View file

@ -0,0 +1,107 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global SettingsHelper, FxaModuleManager, MozActivity */
'use strict';
/*
* FxModuleServerRequest wraps the functionality exposed by the server,
* letting our code to be shielded against changes in the API of FxA.
*/
(function(exports) {
var pref = 'identity.fxaccounts.reset-password.url';
var fxaSettingsHelper = SettingsHelper(pref);
var fxaURL;
fxaSettingsHelper.get(function on_fxa_get_settings(url) {
fxaURL = url;
});
function _setAccountDetails(response) {
if(response && response.user && response.user.email) {
FxaModuleManager.setParam('done', true);
FxaModuleManager.setParam('verified', response.user.verified);
}
}
function _ensureFxaClient(callback) {
window.parent.LazyLoader.load('../js/fx_accounts_client.js', function() {
callback && callback();
});
}
var FxModuleServerRequest = {
checkEmail: function fxmsr_checkEmail(email, onsuccess, onerror) {
_ensureFxaClient(function() {
window.parent.FxAccountsClient.queryAccount(
email,
onsuccess,
onerror);
});
},
signIn: function fxmsr_signIn(email, password, onsuccess, onerror) {
function successHandler(response) {
_setAccountDetails(response);
var authenticated =
(response && response.user && response.user.verified) || false;
onsuccess && onsuccess({
authenticated: authenticated
});
}
function errorHandler(response) {
onerror && onerror(response);
}
_ensureFxaClient(function signIn() {
window.parent.FxAccountsClient.signIn(
email,
password,
successHandler,
errorHandler);
});
},
signUp: function fxmsr_signUp(email, password, onsuccess, onerror) {
function successHandler(response) {
_setAccountDetails(response);
onsuccess && onsuccess(response);
}
_ensureFxaClient(function signUp() {
window.parent.FxAccountsClient.signUp(
email,
password,
successHandler,
onerror);
});
},
requestPasswordReset:
function fxmsr_requestPasswordReset(email, onsuccess, onerror) {
var url = email ? fxaURL + '?email=' + email : fxaURL;
var activity = new MozActivity({
name: 'view',
data: {
type: 'url',
url: url
}
});
activity.onsuccess = function on_reset_success() {
// TODO When the browser loads, it is *behind* the system app. So we
// need to dismiss this app in order to let the user reset their
// password.
onsuccess && onsuccess();
FxaModuleManager.close('DIALOG_CLOSED_BY_USER');
};
activity.onerror = function on_reset_error(err) {
console.error(err);
onerror && onerror(err);
};
}
};
exports.FxModuleServerRequest = FxModuleServerRequest;
}(window));

View file

@ -0,0 +1,64 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global FxaModuleNavigation */
/* exported FxaModuleStates */
'use strict';
/*
* Define the states of the firefox accounts signup/signin flow.
* The object key defines the state name, the value is the
* URL hash of the screen to show. done is a special state that has no
* corresponding screen.
*/
var FxaModuleStates = (function() {
return {
ENTER_EMAIL: {
id: 'fxa-email',
module: 'FxaModuleEnterEmail'
},
SET_PASSWORD: {
id: 'fxa-set-password',
module: 'FxaModuleSetPassword'
},
ENTER_PASSWORD: {
id: 'fxa-enter-password',
module: 'FxaModuleEnterPassword'
},
SIGNUP_SUCCESS: {
id: 'fxa-signup-success',
module: 'FxaModuleSignupSuccess'
},
SIGNIN_SUCCESS: {
id: 'fxa-signin-success',
module: 'FxaModuleSigninSuccess'
},
REFRESH_AUTH: {
id: 'fxa-refresh-auth',
module: 'FxaModuleRefreshAuth'
},
COPPA: {
id: 'fxa-coppa',
module: 'FxaModuleCoppa'
},
TOS: {
id: 'fxa-tos'
},
PP: {
id: 'fxa-pp'
},
DONE: null,
back: function() {
FxaModuleNavigation.back();
},
setState: function setState(state) {
if ( !(state in this) || typeof state === 'function') {
return;
}
document.location.hash = state.id;
}
};
}());

View file

@ -0,0 +1,168 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global HtmlHelper, FxaModule, FxaModuleManager, FxaModuleNavigation,
LazyLoader */
/* exported FxaModuleUI */
'use strict';
const OFFLINE_TIMEOUT = 30 * 1000;
var FxaModuleUI = {
maxSteps: null,
init: function(flow) {
// Add listeners to the main elements
HtmlHelper.importElements(this,
'fxa-module-header',
'fxa-module-back',
'fxa-module-next',
'fxa-module-navigation',
'fxa-module-done',
'fxa-progress'
);
this.fxaModuleHeader.addEventListener('action', function() {
FxaModuleManager.close('DIALOG_CLOSED_BY_USER');
});
this.fxaModuleBack.addEventListener('mousedown', function() {
FxaModuleNavigation.back();
});
this.fxaModuleNext.addEventListener('mousedown', function() {
FxaModuleNavigation.next();
});
this.fxaModuleDone.addEventListener('click', function() {
FxaModuleNavigation.done();
});
// Give up if the network goes offline for more than 30 seconds straight.
var offlineTimer;
window.addEventListener('online', function onOnline(evt) {
clearTimeout(offlineTimer);
FxaModule.hideErrorResponse();
});
window.addEventListener('offline', function onOffline(evt) {
if (offlineTimer) { return; }
offlineTimer = setTimeout(function onOfflineTimeout() {
return FxaModule.showErrorResponse({
error: 'OFFLINE'
});
}, OFFLINE_TIMEOUT);
});
FxaModuleNavigation.init(flow);
},
setMaxSteps: function(num) {
this.maxSteps = num;
},
increaseMaxStepsBy: function(inc) {
this.maxSteps = this.maxSteps + inc;
},
decreaseMaxStepsBy: function(dec) {
this.maxSteps = this.maxSteps - dec;
},
loadScreen: function(params) {
var currentScreen = document.querySelector('.current');
var nextScreen = params.panel;
// Set progress width
this.fxaProgress.style.width = (100 / this.maxSteps) + '%';
// Lazy load current panel
LazyLoader.load(nextScreen, function() {
// If the panel contains any new script elements,
// lazy load those as well.
var scripts = [].slice.call(nextScreen.querySelectorAll('script'))
.map(function(script) { return script.getAttribute('src'); });
// Once all scripts are loaded, load the modules/UI
LazyLoader.load(scripts, function() {
if (params.count > 1 && params.count < this.maxSteps) {
this.fxaModuleNavigation.classList.remove('navigation-single-button');
this.fxaModuleNavigation.classList.remove('navigation-back-only');
if (nextScreen.getAttribute('data-navigation') === 'back') {
this.fxaModuleNavigation.classList.add('navigation-back-only');
}
} else {
this.fxaModuleNavigation.classList.add('navigation-single-button');
if (params.count === this.maxSteps) {
this.fxaModuleNavigation.classList.add('navigation-done');
}
}
this.setProgressBar(params.count);
navigator.mozL10n.once(function() {
// fire module's init method
params.onload && params.onload();
// animate it into view - TODO unclear how nextScreen could be falsy
if (nextScreen) {
this._animate(currentScreen,
nextScreen,
params.back,
params.onanimate);
}
}.bind(this));
}.bind(this));
}.bind(this));
},
_animate: function(from, to, back, callback) {
if (!to) {
return;
}
if (!from) {
to.classList.add('current');
return;
}
if (this._inTransition(from) || this._inTransition(to)) {
return;
}
from.addEventListener('animationend', function fromAnimEnd() {
from.removeEventListener('animationend', fromAnimEnd, false);
from.classList.remove(back ? 'currentToNext' : 'currentToPrev');
from.classList.remove('current');
from.classList.remove('back');
}, false);
to.addEventListener('animationend', function toAnimEnd() {
to.removeEventListener('animationend', toAnimEnd, false);
to.classList.remove(back ? 'prevToCurrent' : 'nextToCurrent');
to.classList.add('current');
callback && callback();
}, false);
from.classList.add(back ? 'currentToNext' : 'currentToPrev');
to.classList.add(back ? 'prevToCurrent' : 'nextToCurrent');
},
_inTransition: function(elem) {
return elem.classList.contains('currentToNext') ||
elem.classList.contains('currentToPrev') ||
elem.classList.contains('nextToCurrent') ||
elem.classList.contains('prevToCurrent') || false;
},
setProgressBar: function(value) {
this.fxaProgress.value = 100 * value / this.maxSteps;
this.fxaProgress.style.transform = 'translateX(' + 100 * (value - 1) + '%)';
},
setNextText: function(l10n) {
this.fxaModuleNext.textContent = l10n;
},
disableNextButton: function() {
this.fxaModuleNext.setAttribute('disabled', 'disabled');
},
enableNextButton: function() {
this.fxaModuleNext.removeAttribute('disabled');
},
disableDoneButton: function() {
this.fxaModuleDone.setAttribute('disabled', 'disabled');
},
enableDoneButton: function() {
this.fxaModuleDone.removeAttribute('disabled');
}
};

View file

@ -0,0 +1,89 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
/**
* This module allow the user to select her age from a date picker and checks
* that she is old enough to create a Firefox account. Otherwise, it shows an
* error.
*/
/* global FxaModuleUI, FxaModule, FxaModuleStates, FxaModuleNavigation */
/* exported FxaModuleCoppa */
var FxaModuleCoppa = (function() {
var MINIMUM_AGE = 13;
function _populateSelect(select) {
var currentYear = new Date().getFullYear();
var start = currentYear - MINIMUM_AGE;
for (var year = start; year <= currentYear; year++) {
var option = document.createElement('option');
option.value= year;
if (year == start) {
option.setAttribute('data-l10n-id', 'fxa-age-earlier');
option.setAttribute('data-l10n-args', '{ "year": "' + year + '" }');
} else {
option.textContent = year;
}
select.appendChild(option);
}
}
function _enableNext(age) {
if (age === '0') {
FxaModuleUI.disableNextButton();
return;
}
FxaModuleUI.enableNextButton();
}
var Module = Object.create(FxaModule);
Module.init = function init(options) {
this.importElements('fxa-age-select');
this.isFTU = !!(options && options.isftu);
// Initially we only have one value ("Year of birth") in the age
// selection element.
if (this.fxaAgeSelect.length == 1) {
_populateSelect(this.fxaAgeSelect);
}
_enableNext(this.fxaAgeSelect.value);
// If COPPA is part of the sign up flow, then we need to add a step to the
// progress bar. Don't increment if the user is moving backwards, though.
if (!FxaModuleNavigation.backAnim) {
FxaModuleUI.increaseMaxStepsBy(1);
}
if (this.initialized) {
return;
}
this.fxaAgeSelect.addEventListener('change', () => {
_enableNext(this.fxaAgeSelect.value);
});
this.initialized = true;
};
Module.onNext = function onNext(gotoNextStepCallback) {
if (new Date().getFullYear() - this.fxaAgeSelect.value < MINIMUM_AGE) {
this.showErrorResponse({
error: this.isFTU ? 'COPPA_FTU_ERROR' : 'COPPA_ERROR'
});
return;
}
gotoNextStepCallback(FxaModuleStates.SET_PASSWORD);
};
Module.onBack = function onBack() {
FxaModuleUI.decreaseMaxStepsBy(1);
};
return Module;
})();

View file

@ -0,0 +1,193 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global FxaModuleStates, FxaModuleUI, FxaModule, FxaModuleNavigation,
FxModuleServerRequest, FxaModuleOverlay, FxaModuleManager, EntrySheet,
BrowserFrame */
/* exported FxaModuleEnterEmail */
'use strict';
/**
* This module checks the validity of an email address, and if valid,
* determines which screen to go next.
*/
var FxaModuleEnterEmail = (function() {
function _isEmailValid(emailEl) {
return emailEl && emailEl.value && emailEl.validity.valid;
}
function _loadSignIn(done) {
done(FxaModuleStates.ENTER_PASSWORD);
}
function _loadSignUp(done) {
done(FxaModuleStates.SET_PASSWORD);
}
function _loadCoppa(done) {
done(FxaModuleStates.COPPA);
}
function _enableNext(emailEl) {
if (_isEmailValid(emailEl)) {
FxaModuleUI.enableNextButton();
} else {
FxaModuleUI.disableNextButton();
}
}
var Module = Object.create(FxaModule);
Module.init = function init(options) {
// Cache static HTML elements
this.importElements(
'fxa-email-input',
'fxa-logo',
'fxa-notice'
);
// Blocks the navigation until check the condition
_enableNext(this.fxaEmailInput);
if (this.initialized) {
return;
}
this.fxaPrivacy = document.getElementById('fxa-privacy');
this.fxaPrivacy.setAttribute('data-l10n-id', 'fxa-pn');
this.fxaTerms = document.getElementById('fxa-terms');
this.fxaTerms.setAttribute('data-l10n-id', 'fxa-tos');
this.isFTU = !!(options && options.isftu);
// Add listeners
this.fxaEmailInput.addEventListener(
'input',
function onInput(event) {
_enableNext(event.target);
}
);
this.fxaEmailInput.addEventListener(
'focus',
function onFocus() {
this.fxaLogo.setAttribute('hidden', true);
}.bind(this)
);
this.fxaEmailInput.addEventListener(
'blur',
function onBlur() {
this.fxaLogo.removeAttribute('hidden');
}.bind(this)
);
this.fxaTerms.addEventListener('click', onExternalLinkClick.bind(this));
this.fxaPrivacy.addEventListener('click', onExternalLinkClick.bind(this));
function onExternalLinkClick(e) {
/*jshint validthis:true */
e.stopPropagation();
e.preventDefault();
if (!navigator.onLine) {
return this.showErrorResponse({error: 'OFFLINE'});
}
var url = e.target.href;
if (this.entrySheet) {
this.entrySheet.close();
this.entrySheet = null;
}
this.entrySheet = new EntrySheet(
window.top.document.getElementById('screen'),
// Prefix url with LRM character
// This ensures truncation occurs correctly in an RTL document
// We can remove this when bug 1154438 is fixed.
'\u200E' + url,
new BrowserFrame({
url: url,
oop: true
})
);
this.entrySheet.open();
}
document.addEventListener(
'visibilitychange',
onVisibilityChange.bind(this)
);
function onVisibilityChange() {
/*jshint validthis:true */
if (document.hidden) {
document.removeEventListener('visibilitychange', onVisibilityChange);
if (this.entrySheet) {
this.entrySheet.close();
this.entrySheet = null;
}
}
}
window.addEventListener('holdhome', hideEntrySheet.bind(this));
window.addEventListener('home', hideEntrySheet.bind(this));
window.addEventListener('activityrequesting', hideEntrySheet.bind(this));
function hideEntrySheet() {
/*jshint validthis:true */
if (this.entrySheet) {
this.entrySheet.close();
this.entrySheet = null;
}
window.removeEventListener('holdhome', hideEntrySheet);
window.removeEventListener('home', hideEntrySheet);
window.removeEventListener('activityrequesting', hideEntrySheet);
}
// Ensure that pressing 'ENTER' (keycode 13) we send the form
// as expected
this.fxaEmailInput.addEventListener(
'keypress',
function onKeypress(event) {
if (_isEmailValid(this.fxaEmailInput) && event.keyCode === 13) {
document.activeElement.blur();
FxaModuleNavigation.next();
}
}.bind(this)
);
// Avoid to add listener twice
this.initialized = true;
};
Module.onNext = function onNext(gotoNextStepCallback) {
FxaModuleOverlay.show('fxa-connecting');
var email = this.fxaEmailInput.value;
FxModuleServerRequest.checkEmail(
email,
function onSuccess(response) {
FxaModuleOverlay.hide();
FxaModuleManager.setParam('email', email);
if (response && response.registered) {
_loadSignIn(gotoNextStepCallback);
} else if (this.isFTU) {
// XXX Skip COPPA verification during FTU: if a child has a mobile
// device, we assume a parent/guardian has given it to them, which
// implies parental consent. So, we skip to the next step in the
// signup flow, the set-password screen. See also bug 1010598.
_loadSignUp(gotoNextStepCallback);
} else {
_loadCoppa(gotoNextStepCallback);
}
}.bind(this),
this.showErrorResponse);
};
Module.onBack = function onBack() {
FxaModuleUI.enableNextButton();
};
return Module;
}());

View file

@ -0,0 +1,166 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global FxaModuleStates, FxaModuleUI, FxaModule, FxaModuleNavigation,
FxModuleServerRequest, FxaModuleOverlay, FxaModuleManager */
/* exported FxaModuleEnterPassword */
'use strict';
/**
* This module checks the validity of password given email address, and if
* valid, determine which screen to go next.
*/
var FxaModuleEnterPassword = (function() {
function _isPasswordValid(passwordEl) {
var passwordValue = passwordEl.value;
return passwordValue && passwordEl.validity.valid;
}
function _enableNext(passwordEl) {
if (_isPasswordValid(passwordEl)) {
FxaModuleUI.enableNextButton();
} else {
FxaModuleUI.disableNextButton();
}
}
function _cleanForm(passwordEl, passwordCheck) {
passwordEl.value = '';
passwordCheck.checked = false;
passwordEl.setAttribute('type', 'password');
}
function _loadSigninSuccess(done) {
done(FxaModuleStates.SIGNIN_SUCCESS);
}
function _notVerifiedUser(done) {
done(FxaModuleStates.SIGNUP_SUCCESS);
}
function _togglePasswordVisibility() {
/*jshint validthis:true*/
var passwordFieldType = !!this.fxaShowPw.checked ? 'text' : 'password';
this.fxaPwInput.setAttribute('type', passwordFieldType);
}
function _forgotPassword() {
/*jshint validthis:true*/
var self = this;
if (this.isFTU) {
return this.showErrorResponse({
error: 'RESET_PASSWORD_ERROR'
});
}
// Note: we don't need to pass a success callback, but we do need an errback
FxModuleServerRequest.requestPasswordReset(
this.email,
null,
function on_reset_error() {
self.showErrorResponse({
error: 'UNKNOWN_ERROR'
});
}
);
}
var Module = Object.create(FxaModule);
Module.init = function init(options) {
if (!this.initialized) {
// Cache DOM elements
this.importElements(
'fxa-hello-known-user',
'fxa-pw-input',
'fxa-show-pw',
'fxa-forgot-password'
);
// Add listeners
this.fxaPwInput.addEventListener(
'input',
function onInput(event) {
_enableNext(event.target);
}
);
// Ensure that pressing 'ENTER' (keycode 13) we send the form
// as expected
this.fxaPwInput.addEventListener(
'keypress',
function onKeypress(event) {
if (_isPasswordValid(this.fxaPwInput) && event.keyCode === 13) {
document.activeElement.blur();
FxaModuleNavigation.next();
}
}.bind(this)
);
this.fxaShowPw.addEventListener(
'change',
_togglePasswordVisibility.bind(this),
false
);
this.fxaForgotPassword.addEventListener(
'click',
_forgotPassword.bind(this),
false
);
// Avoid repeated initialization
this.initialized = true;
}
if (!options || !options.email) {
console.error('Options are not sent properly. Email not available');
return;
}
this.isFTU = !!(options && options.isftu);
this.email = options.email;
navigator.mozL10n.setAttributes(this.fxaHelloKnownUser, 'fxa-hello-user2', {
email: this.email
});
_cleanForm(
this.fxaPwInput,
this.fxaShowPw
);
_enableNext(this.fxaPwInput);
};
Module.onNext = function onNext(gotoNextStepCallback) {
FxaModuleOverlay.show('fxa-connecting');
FxaModuleManager.setParam('success', true);
FxModuleServerRequest.signIn(
this.email,
this.fxaPwInput.value,
function onServerResponse(response) {
FxaModuleOverlay.hide();
if (!response.authenticated) {
_notVerifiedUser(gotoNextStepCallback);
return;
}
_loadSigninSuccess(gotoNextStepCallback);
}.bind(this),
function onError(response) {
_cleanForm(
this.fxaPwInput,
this.fxaShowPw
);
this.showErrorResponse(response);
}.bind(this)
);
};
return Module;
}());

View file

@ -0,0 +1,121 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global FxaModuleStates, FxaModuleUI, FxaModule, FxModuleServerRequest,
FxaModuleOverlay */
/* exported FxaModuleRefreshAuth */
/* jshint unused:false */
'use strict';
var FxaModuleRefreshAuth = (function() {
function _isPasswordValid(passwordEl) {
var passwordValue = passwordEl.value;
return passwordValue && passwordEl.validity.valid;
}
function _enableDone(passwordEl) {
if (_isPasswordValid(passwordEl)) {
FxaModuleUI.enableDoneButton();
} else {
FxaModuleUI.disableDoneButton();
}
}
function _cleanForm(passwordEl, passwordCheck) {
passwordEl.value = '';
passwordCheck.checked = false;
passwordEl.setAttribute('type', 'password');
FxaModuleUI.disableDoneButton();
}
function _requestPasswordReset(email, done) {
/*jshint validthis:true */
FxModuleServerRequest.requestPasswordReset(
email,
function onSuccess(response) {
done();
},
function onError(response) {
this._showCouldNotResetPassword();
}.bind(this)
);
}
function _showCouldNotResetPassword() {
/*jshint validthis:true */
this.showErrorResponse({
error: 'RESET_PASSWORD_ERROR'
});
}
function _forgotPassword() {
/*jshint validthis:true */
FxaModuleOverlay.show('fxa-requesting-password-reset');
_requestPasswordReset.call(
this,
this.email,
function() {
FxaModuleOverlay.hide();
FxaModuleStates.setState(FxaModuleStates.PASSWORD_RESET_SUCCESS);
}
);
}
var Module = Object.create(FxaModule);
Module.init = function init(options) {
if (!this.initialized) {
// Cache DOM elements.
this.importElements(
'fxa-pw-input-refresh',
'fxa-show-pw-refresh',
'fxa-forgot-password-refresh'
);
this.fxaPwInputRefresh.addEventListener('input', (function(event) {
_enableDone(event.target);
}).bind(this));
this.fxaShowPwRefresh.addEventListener('change', (function() {
var passwordFieldType = !!this.fxaShowPwRefresh.checked ? 'text' :
'password';
}).bind(this), false);
this.fxaForgotPasswordRefresh.addEventListener('click',
_forgotPassword.bind(this),
false);
this.initialized = true;
}
if (!options || !options.email) {
console.error('Options are not sent properly. Email not available');
return;
}
this.email = options.email;
_cleanForm(this.fxaPwInputRefresh, this.fxaShowPwRefresh);
_enableDone(this.fxaPwInputRefresh);
};
Module.onDone = function onDone(callback) {
FxaModuleOverlay.show('fxa-connecting');
FxModuleServerRequest.signIn(
this.email,
this.fxaPwInputRefresh.value,
function onServerResponse(response) {
FxaModuleOverlay.hide();
callback();
}, function onError(response) {
_cleanForm(this.fxaPwInputRefresh,
this.fxaShowPwRefresh);
this.showErrorResponse(response);
}.bind(this)
);
};
return Module;
})();

View file

@ -0,0 +1,123 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global FxaModuleUI, FxaModuleOverlay, FxaModule, FxModuleServerRequest,
FxaModuleManager, FxaModuleStates */
/* exported FxaModuleSetPassword */
'use strict';
/**
* Takes care of a new user's set password screen. If password is valid,
* attempt to stage the user.
*/
var FxaModuleSetPassword = (function() {
function _isPasswordValid(passwordEl) {
var passwordValue = passwordEl.value;
return passwordValue && passwordEl.validity.valid;
}
function _enableNext(passwordEl) {
if (_isPasswordValid(passwordEl)) {
FxaModuleUI.enableNextButton();
} else {
FxaModuleUI.disableNextButton();
}
}
function _cleanForm(passwordEl, passwordCheck) {
passwordEl.value = '';
passwordCheck.checked = false;
passwordEl.setAttribute('type', 'password');
}
function _showRegistering() {
FxaModuleOverlay.show('fxa-connecting');
}
function _hideRegistering() {
FxaModuleOverlay.hide();
}
function _showUserNotCreated() {
/*jshint validthis:true */
this.showErrorResponse({
error: 'CANNOT_CREATE_ACCOUNT'
});
}
function _togglePasswordVisibility() {
/*jshint validthis:true */
var passwordFieldType = !!this.fxaShowPwSet.checked ? 'text' : 'password';
this.fxaPwSetInput.setAttribute('type', passwordFieldType);
}
var Module = Object.create(FxaModule);
Module.init = function init(options) {
if (!this.initialized) {
// Cache DOM elements
this.importElements(
'fxa-pw-set-input',
'fxa-show-pw-set',
'fxa-hello-user'
);
// Add Listeners
this.fxaPwSetInput.addEventListener(
'input',
function onInput(event) {
_enableNext(event.target);
}
);
this.fxaShowPwSet.addEventListener(
'change',
_togglePasswordVisibility.bind(this),
false
);
}
options = options || {};
this.email = options.email;
navigator.mozL10n.setAttributes(this.fxaHelloUser, 'fxa-hello-user2', {
email: this.email
});
_cleanForm(
this.fxaPwSetInput,
this.fxaShowPwSet
);
_enableNext(this.fxaPwSetInput);
};
Module.onNext = function onNext(gotoNextStepCallback) {
_showRegistering();
FxModuleServerRequest.signUp(
this.email,
this.fxaPwSetInput.value,
function(response) {
_hideRegistering();
FxaModuleManager.setParam('success', true);
var isAccountCreated = response.accountCreated;
if (!isAccountCreated) {
_showUserNotCreated.call(this);
return;
}
gotoNextStepCallback(FxaModuleStates.SIGNUP_SUCCESS);
}.bind(this),
this.showErrorResponse
);
};
return Module;
}());

View file

@ -0,0 +1,23 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global FxaModule, FxaModuleStates */
/* exported FxaModuleSigninSuccess */
'use strict';
/**
* Display the signup success message to the user.
*/
var FxaModuleSigninSuccess = (function() {
var Module = Object.create(FxaModule);
Module.onNext = function onNext(done) {
done(FxaModuleStates.DONE);
};
return Module;
}());

View file

@ -0,0 +1,34 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global FxaModule, FxaModuleStates */
/* exported FxaModuleSignupSuccess */
'use strict';
/**
* Display the signup success message to the user.
*/
var FxaModuleSignupSuccess = (function() {
var Module = Object.create(FxaModule);
Module.init = function init(options) {
options = options || {};
this.importElements(
'fxa-will-send-email'
);
navigator.mozL10n.setAttributes(this.fxaWillSendEmail,
'fxa-will-send-email2', {
email: options.email
});
};
Module.onNext = function onNext(done) {
done(FxaModuleStates.DONE);
};
return Module;
}());

View file

@ -0,0 +1,103 @@
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Navigation
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
firefox-accounts-title = Firefox Accounts
fxa-next = Next
fxa-back = Back
fxa-done = Done
fxa-ok = OK
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Errors
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# 'spec' references below refer to the spec attached to bug 960130
# this is a real error screen (7.2 in spec)
fxa-connection-error-title = Unable to Connect
fxa-connection-error-message = You can create an account or sign in from Settings.
# this is a real error screen (5.4 in spec)
fxa-reset-password-error-title = Reset Password
fxa-reset-password-error-message = You can reset your password from Settings.
# this is a real error screen (19.4 in spec)
fxa-coppa-failure-error-title = Cannot Create Account
fxa-coppa-failure-error-message3 = You must meet certain age requirements to create a Firefox Account. <a>Learn more</a>
# this is a real error screen (13.2 and 17.2 in spec)
fxa-offline-error-title = Unable to Connect
fxa-offline-error-message = Please connect to a network from Settings.
# this is a real error screen (3.2 in spec)
fxa-invalid-email-title = Error
fxa-invalid-email-message = Please enter a valid email.
# this is a real error screen (5.2 / 11.1 in spec)
fxa-invalid-password-title = Invalid Password
fxa-invalid-password-message = Please check your password and try again.
# this is the fallback error
fxa-unknown-error-title = Unable to Connect
fxa-unknown-error-message = System unavailable, try again soon.
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Interstitial screen
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
fxa-connecting = Connecting
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Enter Email
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
fxa-enter-email-input.placeholder = Enter your email
fxa-use-email-to-sign-in = Use your email address to create an account or sign in.
fxa-notice2 = By proceeding, I agree to the <a>{{tos}}</a> and <a>{{pn}}</a> of Firefox cloud services.
# LOCALIZATION NOTE(fxa-tos): This string corresponds to the {{tos}} placeholder
# in fxa-notice.
fxa-tos = Terms of Service
# LOCALIZATION NOTE(fxa-pn): This string corresponds to the {{pn}} placeholder
# in fxa-notice.
fxa-pn = Privacy Notice
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Set & Enter Password
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
fxa-create-password-title = Create a Password
fxa-enter-password-title = Enter Your Password
fxa-hello-user2 = Hello, <a>{{email}}</a>
fxa-password-refresh = Please enter your Firefox Account password to continue.
fxa-password.placeholder = Password
fxa-forgot-your-password = Forgot your password?
fxa-show-password = Show password
fxa-password-length-requirement = Your password must be at least 8 characters long.
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Signin success
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
fxa-success = Success!
fxa-ready-to-go = Your Firefox Account is ready to go!
fxa-use-features = Now you can use features like Find My Device and apps like the Firefox Marketplace.
fxa-access-manage-account-settings = In the future, you can access and manage your account from Settings.
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Signup success
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
fxa-confirm-account = Confirm Your Account
fxa-will-send-email2 = We will send an email to: <strong>{{email}}</strong>
fxa-signup-click-verification-link-in-email = Next time you check your email, click the verification link to confirm your Firefox Account.
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# COPPA
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
fxa-age-verification = Age Verification
fxa-age-earlier = {{year}} or earlier
fxa-year-of-birth = Year of Birth

View file

@ -0,0 +1,374 @@
/*
* Basic and shared styles of the structure
* */
html, body {
font-size: 62.5%;
width: 100%;
height: 100%;
margin: 0;
color: #000;
background: #f4f4f4;
}
html, body * {
-moz-user-select: none;
}
input {
-moz-user-select: text;
}
.vertical {
width: 100%;
}
section[role="region"] {
width: 100%;
height: 100%;
margin: 0;
position: absolute;
z-index: 1;
}
body {
background: url(bg_light.png);
}
h3 {
font-size: 1.7rem;
font-weight: 400;
line-height: 2.3rem;
margin: 0 0 1rem;
text-align: start;
}
h3:-moz-dir(rtl) {
text-align: right;
}
label {
width:100%;
display:block;
}
ul {
list-style: none;
padding: 0;
}
/* * * * * * * * * * * * * * * * * * * * * * * *
* Navigation bar based on a flex model
* * * * * * * * * * * * * * * * * * * * * * * */
.navigation {
position: absolute;
background: #f4f4f4;
border-top: 0.1rem solid #a6a6a6;
padding: .75rem 1.5rem;
box-sizing: border-box;
margin: 0;
left: 0;
right:0;
bottom: 0;
overflow: hidden;
display: block;
}
/* Overwrite this property from Building Blocks */
html[dir="ltr"] .navigation button { margin-left: 0.5rem; }
html[dir="rtl"] .navigation button { margin-right: 0.5rem; }
html[dir="ltr"] .navigation button.start { margin-left: 0.5rem; }
html[dir="rtl"] .navigation button.start { margin-right: 0.5rem; }
.navigation button.start,
.navigation button.end {
width: calc((100% / 2) - 0.5rem);
position: relative;
}
html[dir="ltr"] .navigation button.start,
html[dir="ltr"] .navigation button.end { float: left; }
html[dir="rtl"] .navigation button.start,
html[dir="rtl"] .navigation button.end { float: right; }
/* When there is only one option, we remove the margin */
.navigation-single-button button.start {
display: none;
}
.navigation.navigation-single-button button.end {
margin: 0;
width: 100%
}
.navigation-back-only button {
margin: 0;
}
.navigation-back-only button.end {
display: none;
}
.navigation-done #fxa-module-back,
.navigation-done #fxa-module-next {
display: none;
}
#fxa-module-done {
display: none;
}
.navigation-done #fxa-module-done {
display: block;
}
/* * * * * * * * * * * * * * * * * * * * * * * *
* Steps & Container
* All steps are contained in several views inside
* 'step-container'. We will use HTML 'import' polyfill
* and lazy load of the views. A view is a set of steps.
* * * * * * * * * * * * * * * * * * * * * * * */
.bb-steps {
margin: 0.5rem 1.5rem;
}
#step-container {
height: calc(100% - 6.3rem - 5rem);
position: relative;
overflow: hidden;
}
.ff_account,
.screen {
font-family: sans-serif;
font-size: 1.7rem;
line-height:2.3rem;
width: 100%;
height: 100%;
position: absolute;
overflow: scroll;
display: none;
padding: 1.5rem 2.5rem;
box-sizing: border-box;
text-align: center;
}
.screen p.fxa-intro {
text-align: start;
line-height: 2.3rem;
font-size: 1.7rem;
}
.screen p {
text-align: start;
line-height: 1.9rem;
font-size: 1.5rem;
margin: 0 0 1rem;
}
.current {
display: block;
}
/*
* Intro screen styles
*/
#fxa-hello-user {
white-space: nowrap;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
#fxa-user-email,
#fxa-user-set-email {
white-space: nowrap;
overflow: hidden;
text-decoration: none;
}
p.fine-print {
font-size: 1.2rem;
line-height: 1.6rem;
}
/*
* Verification Screen Styles
*/
ul.fxa-verification-wrapper {
margin: 0;
padding: 0;
}
ul.fxa-verification-wrapper li {
list-style: none;
margin: 0;
padding: 0;
}
/*
* Password screen styles
*/
#show-pw-wrapper {
width: 100%;
display: block;
margin: 0 -1.5rem;
}
#show-pw-wrapper p {
display: block;
margin: 1.5rem 0;
}
html[dir="ltr"] #show-pw-wrapper p { float: left; }
html[dir="rtl"] #show-pw-wrapper p { float: right; }
html[dir="ltr"] #show-pw-wrapper > label { float: left; }
html[dir="rtl"] #show-pw-wrapper > label { float: right; }
/* Animate next screen */
.currentToPrev {
display:block;
}
html[dir="ltr"] .currentToPrev {
animation: currentToLeft var(--transition-duration) forwards;
}
html[dir="rtl"] .currentToPrev {
animation: currentToRight var(--transition-duration) forwards;
}
.nextToCurrent {
display:block;
}
html[dir="ltr"] .nextToCurrent {
animation: rightToCurrent var(--transition-duration) forwards;
}
html[dir="rtl"] .nextToCurrent {
animation: leftToCurrent var(--transition-duration) forwards;
}
/* Animate prev screen */
.prevToCurrent {
display: block;
}
html[dir="ltr"] .prevToCurrent {
animation: leftToCurrent var(--transition-duration) forwards;
}
html[dir="rtl"] .prevToCurrent {
animation: rightToCurrent var(--transition-duration) forwards;
}
.currentToNext {
display: block;
}
html[dir="ltr"] .currentToNext {
animation: currentToRight var(--transition-duration) forwards;
}
html[dir="rtl"] .currentToNext {
animation: currentToLeft var(--transition-duration) forwards;
}
@keyframes currentToLeft {
0% { transform: translateX(0); }
100% { transform: translateX(-100%); }
}
@keyframes rightToCurrent {
0% { transform: translateX(100%); }
100% { transform: translateX(0); }
}
@keyframes leftToCurrent {
0% { transform: translateX(-100%); }
100% { transform: translateX(0); }
}
@keyframes currentToRight {
0% { transform: translateX(0); }
100% { transform: translateX(100%); }
}
.current {
display: block;
}
.screen a {
text-decoration: underline;
color: #000;
}
.fxa-logo {
background-image: url('fxa_logo.png');
background-size: 14rem auto;
background-repeat: no-repeat;
margin: 0 auto;
width: 14rem;
height: 15rem;
}
p.fxa-envelope {
background-image: url('fxa_envelope.png');
background-repeat: no-repeat;
background-size: 7rem;
padding-inline-start: 8rem;
padding-top: 1rem;
line-height: 2rem;
min-height: 6.4rem;
max-width:100%;
overflow: hidden; /* short email addresses will only take up one line*/
text-overflow: ellipsis;
}
html[dir="ltr"] p.fxa-envelope { background-position: left top; }
html[dir="rtl"] p.fxa-envelope { background-position: right top; }
#fxa-error-overlay {
z-index: 10;
}
/* * * * * * * * * * * * * * * * * * * * * * * *
* Overlay:
* IMPORTANT: Avoid to use 'visibility' or 'opacity' due
* to performance issues. For hiding, use 'display:none;''
* * * * * * * * * * * * * * * * * * * * * * * */
#fxa-overlay {
display: none;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 2;
font-size: 2rem;
background-color: rgba(200,200,200,0.9);
flex-direction: column;
justify-content: center;
align-items: center;
}
#fxa-overlay.show {
display: flex;
}
form[role=dialog] {
display: none;
}
form[role=dialog].show {
display: block;
}
/*
* Classes to ensure functionality
*/
a.disabled {
opacity: 0.3;
pointer-events: none;
text-decoration: none;
}
.button.icon-dialog select {
border: none;
background: transparent;
font-size: 1.7rem;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View file

@ -0,0 +1,18 @@
{
"extends": "../../../../../.jshintrc",
"node": true,
"predef": [
"suite",
"test",
"setup",
"teardown",
"suiteSetup",
"suiteTeardown",
"marionette",
"marionetteScriptFinished",
"require",
"define",
"requireApp",
"sinon"
]
}

View file

@ -0,0 +1,55 @@
'use strict';
var FxA = require('./lib/fxa');
marionette('Firefox Accounts Launch Tests', function() {
var app,
selectors,
client = marionette.client({
profile: {
prefs: {
'focusmanager.testmode': true
},
settings: {
'ftu.manifestURL': FxA.FTU_ORIGIN + '/manifest.webapp'
},
apps: {
'test-fxa-client.gaiamobile.org': __dirname + '/test-fxa-client'
}
}
});
setup(function() {
app = new FxA(client);
selectors = FxA.Selectors;
});
//If we can enter email on first screen, that should prove successful launch
suite('Should launch FxA flow from FxA-consuming apps:', function () {
test('Settings app', function () {
app.launch(FxA.SETTINGS_ORIGIN);
app.runSettingsMenu();
app.enterEmailNew();
});
test('UITest app', function () {
app.launch(FxA.UITEST_ORIGIN);
app.runUITestMenu();
app.enterEmailNew();
});
test('FTU menu', function () {
app.launch(FxA.FTU_ORIGIN);
app.runFTUMenu();
app.enterEmailNew();
});
// This is an old version which uses the IAC API.
// TODO: INTERMITTENT failures?
test.skip('test-fxa-client app', function () {
app.launch(FxA.TEST_FXA_CLIENT_ORIGIN);
app.runFxAClientTestMenu();
app.enterEmailNew();
});
});
});

View file

@ -0,0 +1,62 @@
'use strict';
var FxA = require('./lib/fxa'),
FxAUser = require('./lib/fxa_user'),
Server = require('./lib/server'),
config = require('./lib/config.json');
marionette('Firefox Accounts Screen Flow Test (UITest app)', function() {
var app,
selectors,
server,
fxaUser,
client = marionette.client({
profile: {
prefs: {
'focusmanager.testmode': true,
'identity.fxaccounts.auth.uri': 'http://' +
config.SERVER_HOST + ':' +
config.SERVER_PORT + '/' +
config.SERVER_PATH
}
}
});
setup(function(done) {
fxaUser = new FxAUser();
app = new FxA(client);
Server.create(FxA.SERVER_ARGS, function (err, _server) {
if (err) {
console.error(err);
throw err;
}
server = _server;
done();
});
selectors = FxA.Selectors;
app.launch(FxA.UITEST_ORIGIN);
app.runUITestMenu();
});
teardown(function() {
server.stop();
});
test('should walk screen flow for new user', function () {
app.enterEmailNew();
app.clickNext();
app.enterCOPPANew();
app.clickNext();
app.enterPasswordNew();
app.clickNext();
app.clickDone();
});
test('should walk screen flow for existing user', function () {
app.enterEmailExisting();
app.clickNext();
app.enterPasswordExisting();
app.clickNext();
app.clickDone();
});
});

View file

@ -0,0 +1,17 @@
{
"MARIONETTE_TIMEOUT": 20000,
"USER_NEW": 0,
"USER_EXISTING": 1,
"USER_EXISTING_EMAIL": "kilroy_exists",
"USER_EXISTING_PW": "123456789",
"USERNAME_LEN": "24",
"USER_NEW_PW": "123456789",
"PASSWORD_HASH": "059b7beebaf4824e5395fb9eba6e8424b6ee27b43675130608502790e4af2178",
"MAIL_HOST": "restmail.net",
"SERVER_HOST": "localhost",
"SERVER_PORT": 8081,
"SERVER_PATH": "v1",
"AUTH_SERVER_URL": "https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format",
"DEBUG": false
}

View file

@ -0,0 +1,235 @@
'use strict';
var FxAUser = require('./fxa_user'),
config = require('./config.json');
/**
* Abstraction around FxA app.
* 'Entrance point' app URL is passed in since FxA acts more like a
* component than a standalone app
* (launched by Settings, Marketplace, FTU, FMD, dev_apps, etc.)
*
* @param client
* @constructor
*/
function FxA(client) {
this.config = config;
this.fxaUser = new FxAUser();
this.client = client.scope({ searchTimeout: config.MARIONETTE_TIMEOUT});
}
FxA.SETTINGS_ORIGIN = 'app://settings.gaiamobile.org';
FxA.UITEST_ORIGIN = 'app://uitest.gaiamobile.org';
FxA.TEST_FXA_CLIENT_ORIGIN = 'app://test-fxa-client.gaiamobile.org';
FxA.FTU_ORIGIN = 'app://ftu.gaiamobile.org';
FxA.SERVER_PATH = 'http://' +
config.SERVER_HOST + ':' +
config.SERVER_PORT + '/' +
config.SERVER_PATH;
FxA.SERVER_ARGS = [
config.SERVER_HOST,
config.SERVER_PORT,
config.SERVER_PATH
];
FxA.COPPA_YEAR = new Date().getFullYear() - 13;
FxA.Selectors = {
body: 'body',
bodyReady: 'body .view-body',
// dev_apps/uitest
apiFxaFrame: '#test-iframe',
fxaFrame: '#fxa-iframe',
tabAPI: '#API',
fxaButton: '#mozId-fxa',
requestButton: '#request',
// dev_apps/test-fxa-client
tabOpenFlow: '#openFlow',
// apps/settings
menuItemFxa: '#menuItem-fxa',
menuItemFmd: '#menuItem-findmydevice',
fxaLogin: '#fxa-login',
fmdLogin: '#findmydevice-login',
fxaCancelAccountConfirmation: '#fxa-cancel-confirmation',
// apps/ftu
forward: '#forward',
createAccountOrLogin: '#fxa-create-account',
// apps/findmydevice
emailInput: '#fxa-email-input',
fxaForgotPassword: 'fxa-forgot-password',
pwInput: '#fxa-pw-input',
//COPPA (age-restriction menu no longer appears,
// but password input tag is still different)
pwInputPostCOPPA: '#fxa-pw-set-input',
pwSetInput: '#fxa-pw-set-input',
pwRefresh: '#fxa-pw-input-refresh',
COPPAElementId: '#fxa-coppa',
COPPASelectId: '#fxa-age-select',
COPPAOptionVal: FxA.COPPA_YEAR + ' or earlier',
COPPAOptionValToFail: FxA.COPPA_YEAR.toString(),
moduleNext: '#fxa-module-next',
moduleDone: '#fxa-module-done',
errorOK: '#fxa-error-ok'
};
FxA.prototype = {
/**
* Launches FxA app and focuses on frame.
*/
launch: function (origin) {
// do unless FTU
if (origin.search('ftu') === -1) {
this.client.apps.launch(origin);
}
this.client.apps.switchToApp(origin);
},
enterInput: function (inputId, inputString) {
this.client.helper
.waitForElement(inputId)
.sendKeys(inputString);
},
enterCOPPANew: function () {
var sel = FxA.Selectors.COPPASelectId;
var inputString = FxA.Selectors.COPPAOptionVal;
this.client.helper.tapSelectOption(sel, inputString);
},
enterEmailNew: function () {
var sel = FxA.Selectors.emailInput;
var inputString = this.fxaUser.email(config.USER_NEW);
this.client.helper.fillInputField(sel, inputString);
},
enterPasswordNew: function () {
var sel = FxA.Selectors.pwInputPostCOPPA;
var inputString = this.fxaUser.password(config.USER_NEW);
this.client.helper.fillInputField(sel, inputString);
},
enterEmailExisting: function () {
var sel = FxA.Selectors.emailInput;
var inputString = this.fxaUser.email(config.USER_EXISTING);
this.client.helper.fillInputField(sel, inputString);
},
enterPasswordExisting: function () {
var sel = FxA.Selectors.pwInput;
var inputString = this.fxaUser.password(config.USER_EXISTING);
this.client.helper.fillInputField(sel, inputString);
},
onClick: function(searchSelector) {
var element = this.client.findElement(searchSelector);
this.client.helper
.waitForElement(element)
.tap();
},
clickNext: function() {
var element = this.client.findElement(FxA.Selectors.moduleNext);
var client = this.client;
client.waitFor(function () {
return client.findElement(FxA.Selectors.moduleNext)
.scriptWith(function (el) {
return el.getAttribute('disabled') !== 'disabled';
});
});
client.helper
.waitForElement(element)
.tap();
},
clickDone: function() {
var element = this.client.findElement(FxA.Selectors.moduleDone);
this.client.helper
.waitForElement(element)
.tap();
},
clickTabOpenFlow: function() {
var element = this.client.findElement(FxA.Selectors.tabOpenFlow);
this.client.helper
.waitForElement(element)
.tap();
},
clickFxaLogin: function() {
var element = this.client.findElement(FxA.Selectors.fxaLogin);
this.client.helper
.waitForElement(element)
.tap();
},
clickMenuItemFxa: function() {
var element = this.client.findElement(FxA.Selectors.menuItemFxa);
this.client.helper
.waitForElement(element)
.tap();
},
clickRequestButton: function() {
var element = this.client.findElement(FxA.Selectors.requestButton);
this.client.helper
.waitForElement(element)
.tap();
},
clickTabAPI: function() {
var element = this.client.findElement(FxA.Selectors.tabAPI);
this.client.helper
.waitForElement(element)
.tap();
},
clickFxaButton: function() {
var element = this.client.findElement(FxA.Selectors.fxaButton);
this.client.helper
.waitForElement(element)
.tap();
},
switchFrame: function(frameId) {
this.client.switchToFrame();
var frame = this.client.findElement(frameId);
this.client.switchToFrame(frame);
},
switchFrameDirect: function(frameId) {
var frame = this.client.findElement(frameId);
this.client.switchToFrame(frame);
},
clickThruPanel: function(panelId, buttonId) {
if (panelId === '#wifi') {
// The wifi panel will bring up a screen to show it is scanning for
// networks. Not waiting for this to clear will blow test timing
// and cause things to fail.
this.client.helper.waitForElementToDisappear('#loading-overlay');
}
// waitForElement is used to make sure animations and page changes have
// finished, and that the panel is displayed.
this.client.helper.waitForElement(panelId);
if (buttonId) {
var button = this.client.helper.waitForElement(buttonId);
button.click();
}
},
runSettingsMenu: function() {
this.clickMenuItemFxa();
this.clickFxaLogin();
this.switchFrame(FxA.Selectors.fxaFrame);
},
runUITestMenu: function() {
this.clickTabAPI();
this.clickFxaButton();
this.switchFrameDirect(FxA.Selectors.apiFxaFrame);
this.clickRequestButton();
this.switchFrame(FxA.Selectors.fxaFrame);
},
runFTUMenu: function() {
this.clickThruPanel('#languages', '#forward');
this.clickThruPanel('#wifi', '#forward');
this.clickThruPanel('#date_and_time', '#forward');
this.clickThruPanel('#geolocation', '#forward');
this.clickThruPanel('#import_contacts', '#forward');
this.clickThruPanel('#firefox_accounts',
FxA.Selectors.createAccountOrLogin);
this.switchFrame(FxA.Selectors.fxaFrame);
},
runFxAClientTestMenu: function() {
this.clickTabOpenFlow();
this.switchFrame(FxA.Selectors.fxaFrame);
}
};
module.exports = FxA;

View file

@ -0,0 +1,54 @@
'use strict';
var config = require('./config.json');
/**
* FxA user handler for email, password & string generations
* @constructor
*/
function FxAUser() {
}
FxAUser.prototype = {
_randString: function(len, charSet){
charSet = charSet || 'abcdefghijklmnopqrstuvwxyz0123456789';
var rand = '';
for (var i = 0; i < len; i++) {
var randomPoz = Math.floor(Math.random() * charSet.length);
rand += charSet.substring(randomPoz, randomPoz + 1);
}
return rand;
},
get _uniqueUserName() {
return this._randString(config.USERNAME_LEN);
},
/**
* @TODO bug 1052267
* add ability to generate variety of "invalid" email strings
*/
email: function(userType) {
switch(userType) {
case config.USER_NEW:
return this._uniqueUserName + '@' + config.MAIL_HOST;
default:
return config.USER_EXISTING_EMAIL + '@' + config.MAIL_HOST;
}
},
/**
* @TODO bug 1052267
* add ability to generate variety of "invalid" password strings
*/
password: function(userType) {
switch(userType) {
case config.USER_NEW:
return config.USER_NEW_PW;
default:
return config.USER_EXISTING_PW;
}
}
};
module.exports = FxAUser;

View file

@ -0,0 +1,39 @@
'use strict';
function Server(child) {
this.child = child;
}
Server.prototype = {
/**
* Sends signal to stop child process and stop server.
*
* TODO: Bug 1056186:
* [FxA] Create unit tests for Firefox Accounts mock server process
*/
stop: function() {
this.child.kill('SIGTERM');
}
};
/**
* Spawn the child process where the FxA mock server lives.
*
* @param args - [ host, port, path ]
* @param callback
*/
function create(args, callback) {
var child = require('child_process')
.fork(__dirname + '/server_mock_fxa.js', args)
.on('message', function(data) {
if (Array.isArray(data) && data[0] === 'start') {
callback(null, new Server(child));
}
})
.on('error', function (err) {
callback(err);
});
}
Server.create = create;
module.exports = Server;

View file

@ -0,0 +1,434 @@
'use strict';
var restify = require('restify'),
ecstatic = require('ecstatic'),
config = require('./config.json');
var server = restify.createServer({
name: 'Mock Firefox Accounts Server'
});
server.hostname = process.argv[2];
server.port = process.argv[3];
server.version = process.argv[4];
process.on('uncaughtException', gracefulShutdown);
server.on('uncaughtException', gracefulShutdown);
function shutdown(cb) {
try {
server.close(cb);
} catch (e) {
console.error(e.stack);
process.exit(1);
}
}
function gracefulShutdown(err) {
if (err) {
console.error(err.stack);
}
shutdown();
}
/**
* authPW = 123456789
* client accepts any hex Token the server kindly grants it
* Note: other user types could be added: i.e. verified user
*/
var account = {
'new': {
'authPW': config.PASSWORD_HASH,
'uid': randomString(32),
'sessionToken': randomString(64),
'keyFetchToken': randomString(64),
'passwordChangeToken': randomString(64),
'verified': false,
'authAt': Date.now()
},
'exists': {
'email': config.USER_EXISTING_EMAIL + '@' + config.MAIL_HOST,
'authPW': config.PASSWORD_HASH,
'uid': randomString(32),
'sessionToken': randomString(64),
'keyFetchToken': randomString(64),
'passwordChangeToken': randomString(64),
'verified': false,
'authAt': Date.now()
}
};
/**
* Mock Firefox Accounts Server
*
* Purpose:
* Mimic behavior of FxA server v1
* (SHA: 29dc9abbacb3fe1b6de763f3c7bd85e63215297c)
* with mostly 'canned' responses
* Some APIs (i.e. for authentication) provide limited logical checking
* for testing B2G
*
* Reference
* https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md
*/
var Server = {
//set to true for verbose logging
debug: config.DEBUG || false,
// allows for terminating process directly when calling server standalone
// when calling as child process, use child.kill() from parent
stop: function() {
gracefulShutdown();
console.log('SERVER STOPPED');
},
start: function() {
server.use(restify.acceptParser(server.acceptable));
server.use(restify.queryParser());
server.use(restify.bodyParser());
server.use(ecstatic({ root: __dirname + '/' }));
server.get('/', ecstatic({ root: __dirname }));
server.listen(server.port, function() {
var debug = Server.debug || false;
if (debug) {
console.log(' %s running on port: %s', server.name, server.port);
}
});
}
};
(function(err, port) {
Server.start();
process.send(['start']);
})();
// handle process messages
process.on('message', function(data) {
switch (data) {
case 'stop':
console.log('STOPPING MOCK SERVER');
Server.stop();
break;
}
});
server.post('/v1/account/create', postAccountCreate);
server.get('/v1/account/status', getAccountStatus);
server.get('/v1/account/devices', getAccountDevices);
server.get('/v1/account/keys', getAccountKeys);
server.post('/v1/account/reset', respondEmpty);
server.post('/v1/account/destroy', respondEmpty);
server.post('/v1/account/login', postAccountLogin);
server.get('/v1/session/status', respondEmpty);
server.post('/v1/session/destroy', respondEmpty);
server.get('/v1/recovery_email/status', getRecoveryEmailStatus);
server.post('/v1/recovery_email/resend_code', respondEmpty);
server.post('/v1/recovery_email/verify_code', respondEmpty);
server.post('/v1/certificate/sign', postCertificateSign);
server.post('/v1/password/change/start', postPasswordChangeStart);
server.post('/v1/password/change/finish', respondEmpty);
server.post('/v1/password/forgot/send_code', postPasswordForgotSendCode);
server.post('/v1/password/forgot/resend_code', postPasswordForgotResendCode);
server.post('/v1/password/forgot/verify_code', postPasswordForgotVerifyCode);
server.get('/v1/forgot/status', getPasswordForgotStatus);
server.post('/v1/get_random_bytes', postGetRandomBytes);
//catch any undefined requests
server.get(/^\/([a-zA-Z0-9_\.~-]+)\/(.*)/, anyUndefined);
server.post(/^\/([a-zA-Z0-9_\.~-]+)\/(.*)/, anyUndefined);
/************************************
* API: Account
************************************/
function postAccountCreate(req,res,next) {
var accountSelected = getAccountType(req.body.email);
var jsonResponse = getRespAccountCreate(accountSelected);
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
function getAccountStatus(req,res,next) {
var jsonResponse = { 'exists': false };
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
function getAccountDevices(req,res,next) {
var jsonResponse = {
'devices': [
{
'id': '4c352927-cd4f-4a4a-a03d-7d1893d950b8',
'type': 'computer',
'name': 'Foxys Macbook'
}
]
};
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
function getAccountKeys(req,res,next) {
var jsonResponse = {
'bundle': randomString(256)
};
printLog('GET /account/keys', jsonResponse);
res.send(200,jsonResponse);
}
/************************************
* API: Authentication
************************************/
function postAccountLogin(req,res,next) {
var jsonResponse;
var accountSelected = getAccountType(req.body.email);
if (req.body.email === account.exists.email) {
if (req.body.authPW === account.exists.authPW) {
jsonResponse = getRespAccountLogin(accountSelected);
res.send(200, jsonResponse);
} else {
//status code 400, errno 103: incorrect password
jsonResponse = getError(103,accountSelected);
res.send(jsonResponse);
}
} else {
//status code 400, errno 102: attempt to access a non-existent account
jsonResponse = getError(102, accountSelected);
res.send(jsonResponse);
}
printLog(req.method + ' ' + req.url, jsonResponse);
}
/************************************
* API: Recovery Email
************************************/
function getRecoveryEmailStatus(req,res,next) {
var accountSelected = getAccountType(req.body.email);
var jsonResponse = getRespRecoveryEmail(accountSelected);
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
/************************************
* API: Certificate Signing
************************************/
function postCertificateSign(req,res,next) {
var jsonResponse = {
'cert': randomString(520,
'ABCDEFGHIJKLMNOPQRSTUVWXYZ.abcdefghijklmnopqrstuvwxyz0123456789')
};
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
/************************************
* API: Password
************************************/
/**
* should verify request of format:
* {
* "email": "me@example.com",
* "oldAuthPW":
* "d486e79c9f3214b0010fe31bfb50fa6c12e1d093f7770c81c6b1c19c7ee375a6"
* }
*/
function postPasswordChangeStart(req,res,next) {
var accountSelected = getAccountType(req.body.email);
var jsonResponse;
if (req.body.email === account.exists.email) {
jsonResponse = getRespPasswordChangeStart(accountSelected);
res.send(200, jsonResponse);
} else {
//status code 400, errno 102: attempt to access non-existent account
jsonResponse = getError(102, accountSelected);
res.send(jsonResponse);
}
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
function postPasswordForgotSendCode(req,res,next) {
var jsonResponse = getRespPasswordForgotSendCode();
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
function postPasswordForgotResendCode(req,res,next) {
var jsonResponse = getRespPasswordForgotSendCode();
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
function postPasswordForgotVerifyCode(req,res,next) {
var jsonResponse = {
'accountResetToken': randomString(128)
};
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
function getPasswordForgotStatus(req,res,next) {
var jsonResponse = { 'tries': 3, 'ttl': 420 };
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
/************************************
* API: Miscellaneous
************************************/
function postGetRandomBytes(req,res,next) {
var randomData = getRandomBytes();
var jsonResponse = { 'data': randomData };
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
/************************************
* API: Undefined
* any other requests land here
************************************/
function anyUndefined(req,res,next) {
printLog(req.method + ' ' + req.url + ' (UNDEFINED)',
'value of req is: ' + req);
}
function randomString(len, charSet) {
charSet = charSet || 'abcdefghijklmnopqrstuvwxyz0123456789';
var rand = '';
for (var i = 0; i < len; i++) {
var randomPoz = Math.floor(Math.random() * charSet.length);
rand += charSet.substring(randomPoz,randomPoz+1);
}
return rand;
}
/**
* mock API will random 64 char length hex strings
* @returns {string}
*/
function getRandomBytes() {
return randomString(64);
}
function getAccountType(returnType) {
switch(returnType) {
case account.exists.email:
return account.exists;
default:
return account.new;
}
}
function respondEmpty(req,res,next) {
printLog(req.method + ' ' + req.url, {});
res.send(200, {});
}
function getRespAccountLogin(accountSelected) {
return {
'uid': accountSelected.uid,
'sessionToken': accountSelected.sessionToken,
'verified': accountSelected.verified,
'authAt': Date.now()
};
}
function getRespRecoveryEmail(accountSelected) {
return {
'email': accountSelected.email,
'verified': accountSelected.verified
};
}
function getRespAccountCreate(accountSelected) {
return {
'uid': accountSelected.uid,
'sessionToken': accountSelected.sessionToken,
'keyFetchToken': accountSelected.keyFetchToken,
'authAt': Date.now()
};
}
function getRespPasswordChangeStart(accountSelected) {
return {
'keyFetchToken': accountSelected.keyFetchToken,
'passwordChangeToken': accountSelected.passwordChangeToken,
'verified': accountSelected.verified
};
}
function getRespPasswordForgotSendCode() {
return {
'passwordForgotToken': randomString(128),
'ttl': 900,
'codeLength': 8,
'tries': 3
};
}
/**
* Use:
* var errNum = req.socket._httpMessage.statusCode;
* @param errNum
* @param accountSelected
* @returns {*}
*/
function getError(errNum,accountSelected) {
var info = config.AUTH_SERVER_URL;
switch(errNum) {
case 102:
return {
'code': 400,
'errno': errNum,
'error': 'Bad Request',
'message': 'Attempt to access an account that does not exist',
'info': info,
'email': accountSelected.email,
'log': []
};
case 103:
return {
'code': 400,
'errno': errNum,
'error': 'Bad Request',
'message': 'Incorrect password',
'info': info,
'email': accountSelected.email,
'log': []
};
case 999:
return {
'code': errNum,
'errno': 999,
'error': 'Not Found',
'message': 'Unspecified error',
'info': info,
'log': []
};
}
}
/**
* Log req/res to stdout
*/
function printLog(title,msg) {
if (config.DEBUG) {
console.log('\n========================');
console.log(title);
console.log('========================');
console.log(msg);
console.log('\n\n');
}
}

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>FxAccounts testing tools</title>
<!-- We don't need localization for this app. -->
<script defer type="text/javascript" src="/js/fxa_iac_client.js"></script>
<script defer type="text/javascript" src="/js/app.js"></script>
<link rel="stylesheet" type="text/css" href="style/style.css">
</head>
<body>
<section role="dialog">
<menu>
<button id="getAccount">Get Fx Account</button>
<button id="openFlow">Launch FxAccounts flow</button>
<button id="logout">Logout</button>
<div class="refresh-auth">
<button id="refreshAuthentication">Refresh Authentication UI</button>
<input id="accountId" type="text" value="dummy@account" x-inputmode="verbatim">
</div>
</menu>
<section id="result">
<p id="result-text"></p>
</section>
<section id="events">
<p id="events-text"></p>
</section>
</section>
<!-- Progress overlay -->
<section id="overlay" role="dialog">
<progress></progress>
<p id="overlay-msg">Connecting to Firefox</p>
</section>
</body>
</html>

View file

@ -0,0 +1,124 @@
'use strict';
/* global FxAccountsIACHelper */
var hideTimer;
var Overlay = {
show: function fxam_overlay_show() {
if (hideTimer) {
return;
}
var overlay = document.querySelector('#overlay');
overlay.classList.add('show');
hideTimer = setTimeout(Overlay.hide, 2000);
},
hide: function fxam_overlay_hide() {
hideTimer = clearTimeout(hideTimer);
var overlay = document.querySelector('#overlay');
if (!overlay) {
return;
}
overlay.classList.remove('show');
}
};
var TestFxAClient = function TestFxAClient() {
var getFxAccountsButton, launchFxAFlowButton, logoutButton,
refreshAuthButton, resultTextEl, resultEl, timer, eventsEl,
eventsTextEl, eventsTimer, accountIdInput;
var init = function init() {
resultEl = document.getElementById('result');
resultTextEl = document.getElementById('result-text');
getFxAccountsButton = document.getElementById('getAccount');
launchFxAFlowButton = document.getElementById('openFlow');
logoutButton = document.getElementById('logout');
refreshAuthButton = document.getElementById('refreshAuthentication');
eventsEl = document.getElementById('events');
eventsTextEl = document.getElementById('events-text');
accountIdInput = document.getElementById('accountId');
getFxAccountsButton.addEventListener('click', handler);
launchFxAFlowButton.addEventListener('click', handler);
logoutButton.addEventListener('click', handler);
refreshAuthButton.addEventListener('click', handler);
FxAccountsIACHelper.addEventListener('onlogin', function() {
showEvent('onlogin');
});
FxAccountsIACHelper.addEventListener('onlogout', function() {
showEvent('onlogout');
});
FxAccountsIACHelper.addEventListener('onverifiedlogin', function() {
showEvent('onverifiedlogin');
});
};
function _setResponse(response, errorDebug) {
resultTextEl.innerHTML = '';
var text = document.createTextNode((errorDebug)? 'Error ' : 'Success :');
resultTextEl.appendChild(text);
var responseBeautyfied =
document.createTextNode(JSON.stringify(response || 'No params', null, 4));
resultTextEl.appendChild(responseBeautyfied);
clearTimeout(timer);
resultEl.classList.add('show');
timer = setTimeout(function() {
resultEl.classList.remove('show');
}, 3000);
}
function showEvent(eventName) {
eventsTextEl.innerHTML = '';
var text = document.createTextNode('Event received: ' + eventName);
eventsTextEl.appendChild(text);
clearTimeout(timer);
eventsEl.classList.add('show');
eventsTimer = setTimeout(function() {
eventsEl.classList.remove('show');
}, 5000);
}
var showResponse = function showResponse(response) {
Overlay.hide();
_setResponse(response);
};
var showError = function showResponse(response) {
Overlay.hide();
_setResponse(response, true);
};
var handler = function handler(evt) {
var method = evt.target.id;
switch (method) {
case 'openFlow':
FxAccountsIACHelper[method](showResponse, showError);
break;
case 'getAccount':
case 'logout':
Overlay.show();
FxAccountsIACHelper[method](showResponse, showError);
break;
case 'refreshAuthentication':
FxAccountsIACHelper[method](accountIdInput.value,
showResponse, showError);
break;
}
};
return {
'init': init
};
}();
TestFxAClient.init();

View file

@ -0,0 +1,13 @@
{
"name": "Test FxA Client",
"launch_path": "/index.html",
"type": "certified",
"developer": {
"name": "The Gaia Team",
"url": "https://github.com/mozilla-b2g/gaia"
},
"icons": {
"120": "/style/icons/icon.png"
},
"orientation": "default"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,97 @@
html, body {
height: 100%;
padding: 0;
margin: 0;
font-size: 10px;
}
body {
margin: 0;
width: 100%;
height: 100%;
background-color: #000;
/* XXX: The below overflow property is being ignored by B2G Phone
https://bugzilla.mozilla.org/show_bug.cgi?id=777672
*/
overflow: hidden;
}
button {
display: block;
width: 100%;
height: 6rem;
line-height: 6rem;
font-size: 1.6rem;
}
menu {
margin: 0;
padding: 0;
}
#result {
display: none;
}
#result.show {
display: block;
}
#events {
display: none;
}
#events.show {
display: block;
}
h1 {
padding: 1rem 1.5rem;
-moz-box-sizing: border-box;
font-size: 2rem;
color: white;
}
p {
padding: 1rem 1.5rem;
-moz-box-sizing: border-box;
font-size: 1.6rem;
color: white;
}
/* * * * * * * * * * * * * * * * * * * * * * * *
* Overlay:
* IMPORTANT: Avoid to use 'visibility' or 'opacity' due
* to performance issues. For hiding, use 'display:none;''
* * * * * * * * * * * * * * * * * * * * * * * */
#overlay {
display: none;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 2;
font-size: 2rem;
background-color: rgba(200,200,200,0.9);
flex-direction: column;
justify-content: center;
align-items: center;
}
#overlay.show {
display: flex;
}
input {
width: 100%;
height: 4rem;
line-height: 6rem;
font-size: 1.6rem;
}
.refresh-auth {
border-top: 0.1rem solid;
margin-top: 1rem;
}

View file

@ -0,0 +1,19 @@
/* global FxaModuleStates */
/* exported View */
'use strict';
/*
*
* "View" object defines the number of steps to follow and the
* first step of the flow. Then entire flow is self-contained, so
* once the first step is loaded the next ones are loaded taking into
* consideration the decision taken by the user.
* Number of steps is needed for showing to the user the progress bar.
*
*/
var View = {
length: 3,
start: FxaModuleStates.ENTER_EMAIL
};

View file

@ -0,0 +1,18 @@
/* global FxaModuleStates */
/* exported View */
'use strict';
/*
*
* "View" object defines the number of steps to follow and the
* first step of the flow. Then entire flow is self-contained, so
* once the first step is loaded the next ones are loaded taking into
* consideration the decision taken by the user.
* Number of steps is needed for showing to the user the progress bar.
*
*/
var View = {
length: 1,
start: FxaModuleStates.REFRESH_AUTH
};