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,80 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global FxaModuleKeyNavigation */
/* global FxaModuleUI */
/* global HtmlHelper */
/* global KeyEvent */
/* 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-ok'
);
this.fxaErrorOk.addEventListener('mouseup', this.hide.bind(this));
this.fxaErrorOk.addEventListener('keydown', e => {
if (e.keyCode && e.keyCode === KeyEvent.DOM_VK_RETURN) {
this.fxaErrorOk.classList.add('active');
}
});
this.fxaErrorOk.addEventListener('keyup', e => {
if (e.keyCode &&
(e.keyCode === KeyEvent.DOM_VK_RETURN ||
e.keyCode === KeyEvent.DOM_VK_BACK_SPACE)) {
this.fxaErrorOk.classList.remove('active');
this.hide();
}
});
this.fxaErrorOverlay.addEventListener('submit', this.prevent);
this.initialized = true;
},
show: function fxam_error_overlay_show(titleL10n, messageL10n) {
this.init();
this.fxaErrorTitle.setAttribute('data-l10n-id', titleL10n);
if (typeof(messageL10n) === 'object') {
this.fxaErrorMsg.innerHTML = messageL10n.html;
} else {
this.fxaErrorMsg.setAttribute('data-l10n-id', messageL10n);
}
this.fxaErrorOverlay.classList.add('show');
document.activeElement.blur();
this.fxaErrorOk.focus();
FxaModuleUI.disableBackButton();
},
hide: function fxam_overlay_hide() {
this.init();
this.fxaErrorOverlay.classList.remove('show');
FxaModuleKeyNavigation.enable();
FxaModuleUI.enableBackButton();
},
prevent: function fxam_prevent(event) {
event.preventDefault();
event.stopPropagation();
}
};

View file

@ -0,0 +1,141 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
/* global BrowserFrame */
/* global EntrySheet */
/* global ERROR_INVALID_SYNC_ACCOUNT */
(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-message'
},
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'
}
};
// Sync specific error
Errors[ERROR_INVALID_SYNC_ACCOUNT] = {
title: 'fxsync-error-invalid-account',
message: 'fxsync-error-invalid-account-explanation'
};
function _getError(error) {
var l10nKeys = Errors[error] || Errors.UNKNOWN;
return {
title: l10nKeys.title,
message: error == 'COPPA_ERROR' ? _getCoppaError() : l10nKeys.message
};
}
// Tracks whether or not we have registered the coppa element.
// Registering a custom element twice will throw an error.
var coppaElementRegistered = false;
/**
* Define a custom element for the coppa link.
* A custom element is used as an easy way to survive the current FxA
* error html stringification. When the link is clicked a remote
* EntrySheet is opened.
*/
function registerCoppaLinkElement() {
if (coppaElementRegistered) {
return;
}
coppaElementRegistered = true;
var _ = navigator.mozL10n.get;
var learnMore = _('fxa-learn-more');
var coppaUrl = 'http://www.ftc.gov/news-events/media-resources/' +
'protecting-consumer-privacy/kids-privacy-coppa';
var coppaLinkProto = Object.create(HTMLElement.prototype);
coppaLinkProto.createdCallback = function() {
var template = document.createElement('template');
template.innerHTML = `<a id="coppa-link" href="#">${learnMore}</a>`;
var shadow = this.createShadowRoot();
this._template = template.content.cloneNode(true);
shadow.appendChild(this._template);
var link = shadow.getElementById('coppa-link');
link.addEventListener('click', e => {
e.preventDefault();
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:' + coppaUrl,
new BrowserFrame({
url: coppaUrl,
oop: true
})
);
this.entrySheet.open();
});
};
document.registerElement('fxa-coppa-link', {
prototype: coppaLinkProto
});
}
function _getCoppaError() {
registerCoppaLinkElement();
var _ = navigator.mozL10n.get;
var errorText = _('fxa-coppa-failure-error-message');
var learnMorePlaceholder = /{{\s*learnmore\s*}}/;
var learnMoreLink = '<fxa-coppa-link></fxa-coppa-link>';
// return as a string. fxam_error_overlay will innerHTML the whole message.
return {
html: errorText.replace(learnMorePlaceholder, learnMoreLink)
};
}
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,111 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global KeyNavigationAdapter */
/* global SpatialNavigator */
/* exported FxaModuleKeyNavigation */
'use strict';
(function(exports) {
function getElements(elementNames) {
var elements = [];
elementNames.forEach(selector => {
var element = document.querySelector(selector);
if (element) {
elements.push(element);
}
});
return elements;
}
var FxaModuleKeyNavigation = {
enabled: false,
spatialNavigator: null,
keyNavigationAdapter: null,
init(elementNames) {
var elements = elementNames ? getElements(elementNames) : null;
this.spatialNavigator = new SpatialNavigator(elements, {
navigableFilter: elem => {
return !((elem.offsetWidth <= 0 && elem.offsetHeight <= 0) ||
elem.disabled);
}
});
this.spatialNavigator.on('focus', elem => {
document.activeElement.blur();
elem.focus();
});
this.keyNavigationAdapter = new KeyNavigationAdapter();
this.keyNavigationAdapter.init();
this.keyNavigationAdapter.on('move', key => {
if (!this.enabled) {
return;
}
var element = this.spatialNavigator.getFocusedElement();
// Check if the key is pressed inside an email or password input element
if (element.tagName === 'INPUT' &&
(element.type === 'email' || element.type === 'password') &&
element.value.length > 0 &&
element.selectionStart === element.selectionEnd &&
((key === 'left' && element.selectionStart > 0) ||
(key === 'right' &&
element.selectionStart < element.value.length))) {
return;
}
this.spatialNavigator.move(key);
});
},
focus(element) {
this.spatialNavigator.focus(element);
},
add(param) {
var elements = [];
Array.isArray(param) ?
elements = elements.concat(getElements(param)) :
elements.push(document.querySelector(param));
this.spatialNavigator.multiAdd(elements);
this.spatialNavigator.focus(elements[0]);
this.enabled = true;
},
remove(param) {
var elements = [];
Array.isArray(param) ?
elements = elements.concat(getElements(param)) :
elements.push(document.querySelector(param));
this.spatialNavigator.multiRemove(elements);
if (!this.spatialNavigator.getFocusedElement()) {
this.enabled = false;
}
},
enable() {
this.enabled = true;
this.spatialNavigator.focus();
},
disable() {
this.enabled = false;
}
};
exports.FxaModuleKeyNavigation = FxaModuleKeyNavigation;
})(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,68 @@
/* -*- 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 */
/* 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', function() {
var config = FxaModuleErrors.responseToParams(resp);
FxaModuleErrorOverlay.show(config.title, config.message);
});
},
hideErrorResponse: function fxam_hideErrorResponse() {
FxaModuleErrorOverlay.hide();
FxaModuleOverlay.hide();
}
};
return Module;
}());

View file

@ -0,0 +1,116 @@
/* -*- 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,
FxaModuleKeyNavigation */
/* 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;
FxaModuleKeyNavigation.init();
}.bind(this));
},
hashchangeHandler: function fxam_nav_hashchangeHandler() {
if (!location.hash) {
return;
}
var match = /(#.*)(?=\?)|(#.*)$/.exec(location.hash);
var hash = match ? match[0] : null;
var panel = document.querySelector(hash);
if (!panel || !panel.classList.contains('screen')) {
// After a user presses backspace key at the first step of the flow,
// the panel will be null and we should close the dialog.
FxaModuleManager.close('DIALOG_CLOSED_BY_USER');
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;
this.currentModule.onAnimate &&
this.currentModule.onAnimate();
}.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,114 @@
/* -*- 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);
};
},
logout: function fxmsr_logout(onsuccess, onerror) {
_ensureFxaClient(() => {
window.parent.FxAccountsClient.logout(
onsuccess,
onerror);
});
}
};
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,218 @@
/* -*- 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, KeyEvent */
/* 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();
});
// To enable click animation on buttons, the next and done method should
// be invoked at mouseup or keyup event.
this.fxaModuleNext.addEventListener('mouseup',
FxaModuleNavigation.next.bind(FxaModuleNavigation));
this.fxaModuleDone.addEventListener('mouseup',
FxaModuleNavigation.done.bind(FxaModuleNavigation));
this.fxaModuleNext.addEventListener('keydown', e => {
if(e.keyCode && e.keyCode === KeyEvent.DOM_VK_RETURN) {
this.fxaModuleNext.classList.add('active');
}
});
this.fxaModuleDone.addEventListener('keydown', e => {
if(e.keyCode && e.keyCode === KeyEvent.DOM_VK_RETURN) {
this.fxaModuleDone.classList.add('active');
}
});
this.fxaModuleNext.addEventListener('keyup', e => {
if(e.keyCode && e.keyCode === KeyEvent.DOM_VK_RETURN) {
this.fxaModuleNext.classList.remove('active');
FxaModuleNavigation.next();
}
});
this.fxaModuleDone.addEventListener('keyup', e => {
if(e.keyCode && e.keyCode === KeyEvent.DOM_VK_RETURN) {
this.fxaModuleDone.classList.remove('active');
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);
});
this.enableBackButton();
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');
this.fxaModuleNavigation.classList.remove('navigation-done');
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');
} else {
this.fxaModuleNavigation.classList.remove('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 ? 'currentToRight' : 'currentToLeft');
from.classList.remove('current');
from.classList.remove('back');
}, false);
to.addEventListener('animationend', function toAnimEnd() {
to.removeEventListener('animationend', toAnimEnd, false);
to.classList.remove(back ? 'leftToCurrent' : 'rightToCurrent');
to.classList.add('current');
callback && callback();
}, false);
from.classList.add(back ? 'currentToRight' : 'currentToLeft');
to.classList.add(back ? 'leftToCurrent' : 'rightToCurrent');
},
_inTransition: function(elem) {
return elem.classList.contains('currentToRight') ||
elem.classList.contains('currentToLeft') ||
elem.classList.contains('rightToCurrent') ||
elem.classList.contains('leftToCurrent') || 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');
},
onkeypress: function(e) {
var inputElementTypes = ['INPUT', 'TEXTAREA'];
var active = document.activeElement;
var isInputElementType = inputElementTypes.indexOf(active.nodeName) !== -1;
if (e.keyCode === KeyEvent.DOM_VK_BACK_SPACE &&
(!isInputElementType || (isInputElementType && !active.value))) {
FxaModuleNavigation.back();
}
},
disableBackButton: function() {
window.removeEventListener('keypress', this.onkeypress);
},
enableBackButton: function() {
window.addEventListener('keypress', this.onkeypress);
},
focusDoneButton: function() {
document.activeElement.blur();
this.fxaModuleDone.focus();
}
};

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,288 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global BrowserFrame */
/* global EntrySheet */
/* global ERROR_INVALID_SYNC_ACCOUNT */
/* global FxaModule */
/* global FxaModuleKeyNavigation */
/* global FxaModuleManager */
/* global FxaModuleNavigation */
/* global FxaModuleOverlay */
/* global FxModuleServerRequest */
/* global FxaModuleStates */
/* global FxaModuleUI */
/* global KeyEvent */
/* 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() {
var _ = null;
var termsUrl = 'https://accounts.firefox.com/legal/terms';
var privacyUrl = 'https://accounts.firefox.com/legal/privacy';
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 _enableNext(emailEl) {
if (_isEmailValid(emailEl)) {
FxaModuleUI.enableNextButton();
} else {
FxaModuleUI.disableNextButton();
}
}
var Module = Object.create(FxaModule);
Module.init = function init(options) {
_ = navigator.mozL10n.get;
// Cache static HTML elements
this.importElements(
'fxa-email-input',
'fxa-email-clean-btn',
'fxa-logo',
'fxa-notice'
);
// Blocks the navigation until check the condition
_enableNext(this.fxaEmailInput);
if (this.initialized) {
return;
}
// dynamically construct and localize ToS/PN notice
// XXX This relies on the current l10n fallback mechanism which will change
// in the future; a real solution involves DOM overlays:
// https://bugzil.la/994357
var noticeText = _('fxa-notice');
var tosReplaced = noticeText.replace(
/{{\s*tos\s*}}/,
'<a id="fxa-terms" href="' + termsUrl + '">Terms of Service</a>'
);
var tosPnReplaced = tosReplaced.replace(
/{{\s*pn\s*}}/,
'<a id="fxa-privacy" href="' + privacyUrl + '">Privacy Notice</a>'
);
this.fxaNotice.innerHTML = tosPnReplaced;
// manually import a few elements after innerHTMLing
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) {
if(this.fxaEmailInput.value) {
this.fxaEmailCleanBtn.classList.add('show');
} else {
this.fxaEmailCleanBtn.classList.remove('show');
}
_enableNext(event.target);
}.bind(this)
);
this.fxaEmailInput.addEventListener(
'focus',
function onFocus() {
this.fxaLogo.setAttribute('hidden', true);
if(this.fxaEmailInput.value) {
this.fxaEmailCleanBtn.classList.add('show');
// The input can only be selected at the next event queue
setTimeout(this.fxaEmailInput.select.bind(this.fxaEmailInput));
}
}.bind(this)
);
this.fxaEmailInput.addEventListener(
'blur',
function onBlur() {
this.fxaLogo.removeAttribute('hidden');
}.bind(this)
);
this.fxaEmailCleanBtn.addEventListener(
'click',
function onMouseDown(e) {
if(e.button === 0 ||
(e.keyCode && e.keyCode === KeyEvent.DOM_VK_RETURN)) {
this.fxaEmailInput.value = '';
FxaModuleKeyNavigation.focus(this.fxaEmailInput);
this.fxaEmailCleanBtn.classList.remove('show');
}
}.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)
);
// There are 3 reasons why using setTimeout at this place:
// 1. Focus() only works in the setTimeout callback here
// 2. The input will be focused first and the keyboard will be brought
// up. We need to do this after the slide up animation of the parent
// fxa_dialog. But the fxa iframe has no way to know when the slide up
// animation is finished.
// 3. Put the FxaModuleKeyNavigation.add in the onanimate callback in
// fxam_navigation.js doesn't work, since there is no animation for the
// first page in the flow.
setTimeout(() => {
FxaModuleKeyNavigation.add([
'#fxa-email-input',
'#fxa-email-clean-btn',
'#fxa-module-next',
'#fxa-privacy',
'#fxa-terms'
]);
}, 500);
// Avoid to add listener twice
this.initialized = true;
};
Module.onAnimate = function onAnimate() {
FxaModuleKeyNavigation.add([
'#fxa-email-input',
'#fxa-email-clean-btn',
'#fxa-module-next',
'#fxa-privacy',
'#fxa-terms'
]);
};
Module.onNext = function onNext(gotoNextStepCallback) {
FxaModuleOverlay.show('fxa-connecting');
var email = this.fxaEmailInput.value;
FxModuleServerRequest.checkEmail(email, response => {
FxaModuleOverlay.hide();
FxaModuleManager.setParam('email', email);
if (response && response.registered) {
FxaModuleKeyNavigation.remove([
'#fxa-email-input',
'#fxa-email-clean-btn',
'#fxa-module-next',
'#fxa-privacy',
'#fxa-terms'
]);
_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 if (response && !response.registered) {
// XXX On the TV we don't allow the creation of new FxA users because
// we only use FxA on Sync and we are unable to create new Sync
// users. So we show an error to the user, asking her to go to
// Desktop or Android to do the creation flow.
FxaModuleKeyNavigation.disable();
this.showErrorResponse({error: ERROR_INVALID_SYNC_ACCOUNT});
} else {
FxaModuleKeyNavigation.disable();
this.showErrorResponse({error: 'OFFLINE'});
}
}, error => {
FxaModuleKeyNavigation.disable();
this.showErrorResponse(error);
});
};
Module.onBack = function onBack() {
FxaModuleUI.enableNextButton();
};
return Module;
}());

View file

@ -0,0 +1,237 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global ERROR_INVALID_SYNC_ACCOUNT */
/* global FxaModule */
/* global FxaModuleKeyNavigation */
/* global FxaModuleManager */
/* global FxaModuleNavigation */
/* global FxaModuleOverlay */
/* global FxModuleServerRequest */
/* global FxaModuleUI */
/* global KeyEvent */
/* 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() {
var _ = null;
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 _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) {
_ = navigator.mozL10n.get;
// Cache DOM elements
this.importElements(
'fxa-hello-known-user',
'fxa-pw-input',
'fxa-show-pw',
'fxa-forgot-password',
'fxa-pw-clean-btn'
);
// Add listeners
this.fxaPwInput.addEventListener(
'input',
function onInput(event) {
if(this.fxaPwInput.value) {
this.fxaPwCleanBtn.classList.add('show');
} else {
this.fxaPwCleanBtn.classList.remove('show');
}
_enableNext(event.target);
}.bind(this)
);
// 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.done();
}
}.bind(this)
);
this.fxaPwInput.addEventListener('focus', () => {
setTimeout(this.fxaPwInput.select.bind(this.fxaPwInput));
});
this.fxaPwCleanBtn.addEventListener(
'click',
function onCleanButtonClick(e) {
if(e.button === 0 ||
(e.keyCode && e.keyCode === KeyEvent.DOM_VK_RETURN)) {
this.fxaPwInput.value = '';
FxaModuleKeyNavigation.focus(this.fxaPwInput);
this.fxaPwCleanBtn.classList.remove('show');
}
}.bind(this)
);
this.fxaShowPw.addEventListener('keypress', e => {
if (e.keyCode && e.keyCode === KeyEvent.DOM_VK_RETURN) {
this.fxaShowPw.checked = !this.fxaShowPw.checked;
_togglePasswordVisibility.bind(this)();
}
});
this.fxaShowPw.addEventListener(
'change',
_togglePasswordVisibility.bind(this),
false
);
this.fxaForgotPassword.addEventListener(
'click',
_forgotPassword.bind(this),
false
);
this.fxaForgotPassword.addEventListener('keypress', e => {
if (e.keyCode && e.keyCode === KeyEvent.DOM_VK_RETURN) {
_forgotPassword.bind(this)();
}
});
// 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;
var helloUserText = _('fxa-hello-user');
helloUserText = helloUserText.replace(
/{{\s*email\s*}}/,
'<a id="fxa-known-user-email">' + this.email + '</a>'
);
this.fxaHelloKnownUser.innerHTML = helloUserText;
_cleanForm(
this.fxaPwInput,
this.fxaShowPw
);
_enableNext(this.fxaPwInput);
// There are 3 reasons why using setTimeout at this place:
// 1. Focus() only works in the setTimeout callback here
// 2. The input will be focused first and the keyboard will be brought
// up. We need to do this after the slide up animation of the parent
// fxa_dialog. But the fxa iframe has no way to know when the slide up
// animation is finished.
// 3. Put the FxaModuleKeyNavigation.add in the onanimate callback in
// fxam_navigation.js doesn't work, since there is no animation for the
// first page in the flow.
setTimeout(() => {
FxaModuleKeyNavigation.add([
'#fxa-pw-input', '#fxa-forgot-password',
'#fxa-show-pw', '#fxa-module-done', '#fxa-pw-clean-btn']);
}, 500);
};
Module.onBack = function onBack() {
FxaModuleKeyNavigation.remove([
'#fxa-pw-input', '#fxa-forgot-password',
'#fxa-show-pw', '#fxa-module-done', '#fxa-pw-clean-btn']);
};
Module.onDone = function onDone(done) {
FxaModuleOverlay.show('fxa-connecting');
FxaModuleManager.setParam('success', true);
FxModuleServerRequest.signIn(
this.email,
this.fxaPwInput.value,
function onServerResponse(response) {
FxaModuleOverlay.hide();
FxaModuleKeyNavigation.remove([
'#fxa-pw-input', '#fxa-forgot-password',
'#fxa-show-pw', '#fxa-module-done', '#fxa-pw-clean-btn']);
if (!response.authenticated) {
// XXX For now, because we don't allow the creation of new accounts
// on the TV, we log the user out and show a dialog asking the
// user to go to desktop or android to create the account and
// retry from the TV.
FxModuleServerRequest.logout(() => {
this.showErrorResponse({error: ERROR_INVALID_SYNC_ACCOUNT});
FxaModuleManager.close('DIALOG_CLOSED_BY_USER');
}, this.showErrorResponse);
return;
}
done();
}.bind(this),
function onError(response) {
FxaModuleKeyNavigation.disable();
_cleanForm(
this.fxaPwInput,
this.fxaShowPw
);
this.showErrorResponse(response);
}.bind(this)
);
};
return Module;
}());

View file

@ -0,0 +1,199 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global FxaModule */
/* global FxaModuleKeyNavigation */
/* global FxaModuleNavigation */
/* global FxaModuleOverlay */
/* global FxaModuleStates */
/* global FxaModuleUI */
/* global FxModuleServerRequest */
/* global KeyEvent */
/* 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 _togglePasswordVisibility() {
/*jshint validthis:true*/
var passwordFieldType =
!!this.fxaShowPwRefresh.checked ? 'text' : 'password';
this.fxaPwInputRefresh.setAttribute('type', passwordFieldType);
}
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',
'fxa-pw-clean-btn'
);
this.fxaPwInputRefresh.addEventListener('input', (function(event) {
if(this.fxaPwInputRefresh.value) {
this.fxaPwCleanBtn.classList.add('show');
} else {
this.fxaPwCleanBtn.classList.remove('show');
}
_enableDone(event.target);
}).bind(this));
this.fxaPwCleanBtn.addEventListener(
'click',
function onCleanButtonClick(e) {
if(e.button === 0 ||
(e.keyCode && e.keyCode === KeyEvent.DOM_VK_RETURN)) {
this.fxaPwInputRefresh.value = '';
FxaModuleKeyNavigation.focus(this.fxaPwInputRefresh);
this.fxaPwCleanBtn.classList.remove('show');
}
}.bind(this)
);
this.fxaShowPwRefresh.addEventListener('change', (function() {
var passwordFieldType = !!this.fxaShowPwRefresh.checked ? 'text' :
'password';
}).bind(this), false);
// Ensure that pressing 'ENTER' (keycode 13) we send the form
// as expected
this.fxaPwInputRefresh.addEventListener('keypress', event => {
if (_isPasswordValid(this.fxaPwInputRefresh) && event.keyCode === 13) {
document.activeElement.blur();
FxaModuleNavigation.done();
}
});
this.fxaPwInputRefresh.addEventListener('focus', () => {
setTimeout(this.fxaPwInputRefresh.select.bind(this.fxaPwInputRefresh));
});
this.fxaShowPwRefresh.addEventListener('keypress', e => {
if (e.keyCode && e.keyCode === KeyEvent.DOM_VK_RETURN) {
this.fxaShowPwRefresh.checked = !this.fxaShowPwRefresh.checked;
_togglePasswordVisibility.bind(this)();
}
});
this.fxaShowPwRefresh.addEventListener(
'change',
_togglePasswordVisibility.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);
// There are 3 reasons why using setTimeout at this place:
// 1. Focus() only works in the setTimeout callback here
// 2. The input will be focused first and the keyboard will be brought
// up. We need to do this after the slide up animation of the parent
// fxa_dialog. But the fxa iframe has no way to know when the slide up
// animation is finished.
// 3. Put the FxaModuleKeyNavigation.add in the onanimate callback in
// fxam_navigation.js doesn't work, since there is no animation for the
// first page in the flow.
setTimeout(() => {
FxaModuleKeyNavigation.add([
'#fxa-pw-input-refresh', '#fxa-show-pw-refresh',
'#fxa-forgot-password-refresh', '#fxa-module-done',
'#fxa-pw-clean-btn']);
}, 500);
};
Module.onDone = function onDone(callback) {
FxaModuleOverlay.show('fxa-connecting');
FxModuleServerRequest.signIn(
this.email,
this.fxaPwInputRefresh.value,
function onServerResponse(response) {
FxaModuleOverlay.hide();
FxaModuleKeyNavigation.remove([
'#fxa-pw-input-refresh', '#fxa-show-pw-refresh',
'#fxa-forgot-password-refresh', '#fxa-module-done',
'#fxa-pw-clean-btn']);
callback();
}, function onError(response) {
FxaModuleKeyNavigation.disable();
_cleanForm(this.fxaPwInputRefresh,
this.fxaShowPwRefresh);
this.showErrorResponse(response);
}.bind(this)
);
};
return Module;
})();

View file

@ -0,0 +1,130 @@
/* -*- 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() {
var _ = null;
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) {
// l10n manager
_ = navigator.mozL10n.get;
// 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;
var helloUserText = _('fxa-hello-user');
helloUserText = helloUserText.replace(
/{{\s*email\s*}}/,
'<a id="fxa-user-set-email">' + this.email + '</a>'
);
this.fxaHelloUser.innerHTML = helloUserText;
_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,27 @@
/* -*- 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, FxaModuleUI */
/* exported FxaModuleSigninSuccess */
'use strict';
/**
* Display the signup success message to the user.
*/
var FxaModuleSigninSuccess = (function() {
var Module = Object.create(FxaModule);
Module.init = function init() {
FxaModuleUI.focusDoneButton();
};
Module.onNext = function onNext(done) {
done(FxaModuleStates.DONE);
};
return Module;
}());

View file

@ -0,0 +1,36 @@
/* -*- 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'
);
var willSendText = navigator.mozL10n.get('fxa-will-send-email');
willSendText = willSendText.replace(
/{{\s*email\s*}}/,
'<strong>' + options.email + '</strong>'
);
this.fxaWillSendEmail.innerHTML = willSendText;
};
Module.onNext = function onNext(done) {
done(FxaModuleStates.DONE);
};
return Module;
}());