summaryrefslogtreecommitdiffstats
path: root/application/basilisk/base/content/aboutaccounts
diff options
context:
space:
mode:
Diffstat (limited to 'application/basilisk/base/content/aboutaccounts')
-rw-r--r--application/basilisk/base/content/aboutaccounts/aboutaccounts.css24
-rw-r--r--application/basilisk/base/content/aboutaccounts/aboutaccounts.js543
-rw-r--r--application/basilisk/base/content/aboutaccounts/aboutaccounts.xhtml112
-rw-r--r--application/basilisk/base/content/aboutaccounts/images/fox.pngbin0 -> 1951 bytes
-rw-r--r--application/basilisk/base/content/aboutaccounts/images/graphic_sync_intro.pngbin0 -> 6441 bytes
-rw-r--r--application/basilisk/base/content/aboutaccounts/images/graphic_sync_intro@2x.pngbin0 -> 12852 bytes
-rw-r--r--application/basilisk/base/content/aboutaccounts/main.css166
-rw-r--r--application/basilisk/base/content/aboutaccounts/normalize.css402
8 files changed, 1247 insertions, 0 deletions
diff --git a/application/basilisk/base/content/aboutaccounts/aboutaccounts.css b/application/basilisk/base/content/aboutaccounts/aboutaccounts.css
new file mode 100644
index 000000000..a2c5cb8f0
--- /dev/null
+++ b/application/basilisk/base/content/aboutaccounts/aboutaccounts.css
@@ -0,0 +1,24 @@
+html, body {
+ height: 100%;
+}
+
+#remote {
+ width: 100%;
+ height: 100%;
+ border: 0;
+ display: none;
+}
+
+#networkError, #manage, #intro, #stage, #configError {
+ display: none;
+}
+
+#oldsync {
+ background: none;
+ border: 0;
+ color: #0095dd;
+}
+
+#oldsync:focus {
+ outline: 1px dotted #0095dd;
+}
diff --git a/application/basilisk/base/content/aboutaccounts/aboutaccounts.js b/application/basilisk/base/content/aboutaccounts/aboutaccounts.js
new file mode 100644
index 000000000..a05c1ea75
--- /dev/null
+++ b/application/basilisk/base/content/aboutaccounts/aboutaccounts.js
@@ -0,0 +1,543 @@
+/* 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/. */
+
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FxAccounts.jsm");
+
+var fxAccountsCommon = {};
+Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon);
+
+// for master-password utilities
+Cu.import("resource://services-sync/util.js");
+
+const PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash";
+const PREF_SYNC_SHOW_CUSTOMIZATION = "services.sync-setup.ui.showCustomizationDialog";
+
+const ACTION_URL_PARAM = "action";
+
+const OBSERVER_TOPICS = [
+ fxAccountsCommon.ONVERIFIED_NOTIFICATION,
+ fxAccountsCommon.ONLOGOUT_NOTIFICATION,
+];
+
+function log(msg) {
+ // dump("FXA: " + msg + "\n");
+}
+
+function error(msg) {
+ console.log("Firefox Account Error: " + msg + "\n");
+}
+
+function getPreviousAccountNameHash() {
+ try {
+ return Services.prefs.getComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString).data;
+ } catch (_) {
+ return "";
+ }
+}
+
+function setPreviousAccountNameHash(acctName) {
+ let string = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ string.data = sha256(acctName);
+ Services.prefs.setComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString, string);
+}
+
+function needRelinkWarning(acctName) {
+ let prevAcctHash = getPreviousAccountNameHash();
+ return prevAcctHash && prevAcctHash != sha256(acctName);
+}
+
+// Given a string, returns the SHA265 hash in base64
+function sha256(str) {
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ // Data is an array of bytes.
+ let data = converter.convertToByteArray(str, {});
+ let hasher = Cc["@mozilla.org/security/hash;1"]
+ .createInstance(Ci.nsICryptoHash);
+ hasher.init(hasher.SHA256);
+ hasher.update(data, data.length);
+
+ return hasher.finish(true);
+}
+
+function promptForRelink(acctName) {
+ let sb = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
+ let continueLabel = sb.GetStringFromName("continue.label");
+ let title = sb.GetStringFromName("relinkVerify.title");
+ let description = sb.formatStringFromName("relinkVerify.description",
+ [acctName], 1);
+ let body = sb.GetStringFromName("relinkVerify.heading") +
+ "\n\n" + description;
+ let ps = Services.prompt;
+ let buttonFlags = (ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING) +
+ (ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL) +
+ ps.BUTTON_POS_1_DEFAULT;
+ let pressed = Services.prompt.confirmEx(window, title, body, buttonFlags,
+ continueLabel, null, null, null,
+ {});
+ return pressed == 0; // 0 is the "continue" button
+}
+
+// If the last fxa account used for sync isn't this account, we display
+// a modal dialog checking they really really want to do this...
+// (This is sync-specific, so ideally would be in sync's identity module,
+// but it's a little more seamless to do here, and sync is currently the
+// only fxa consumer, so...
+function shouldAllowRelink(acctName) {
+ return !needRelinkWarning(acctName) || promptForRelink(acctName);
+}
+
+function updateDisplayedEmail(user) {
+ let emailDiv = document.getElementById("email");
+ if (emailDiv && user) {
+ emailDiv.textContent = user.email;
+ }
+}
+
+var wrapper = {
+ iframe: null,
+
+ init: function (url, urlParams) {
+ // If a master-password is enabled, we want to encourage the user to
+ // unlock it. Things still work if not, but the user will probably need
+ // to re-auth next startup (in which case we will get here again and
+ // re-prompt)
+ Utils.ensureMPUnlocked();
+
+ let iframe = document.getElementById("remote");
+ this.iframe = iframe;
+ this.iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
+ let docShell = this.iframe.frameLoader.docShell;
+ docShell.QueryInterface(Ci.nsIWebProgress);
+ docShell.addProgressListener(this.iframeListener, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
+ iframe.addEventListener("load", this);
+
+ // Ideally we'd just merge urlParams with new URL(url).searchParams, but our
+ // URLSearchParams implementation doesn't support iteration (bug 1085284).
+ let urlParamStr = urlParams.toString();
+ if (urlParamStr) {
+ url += (url.includes("?") ? "&" : "?") + urlParamStr;
+ }
+ this.url = url;
+ // Set the iframe's location with loadURI/LOAD_FLAGS_REPLACE_HISTORY to
+ // avoid having a new history entry being added. REPLACE_HISTORY is used
+ // to replace the current entry, which is `about:blank`.
+ let webNav = iframe.frameLoader.docShell.QueryInterface(Ci.nsIWebNavigation);
+ webNav.loadURI(url, Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY, null, null, null);
+ },
+
+ retry: function () {
+ let webNav = this.iframe.frameLoader.docShell.QueryInterface(Ci.nsIWebNavigation);
+ webNav.loadURI(this.url, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY, null, null, null);
+ },
+
+ iframeListener: {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference,
+ Ci.nsISupports]),
+
+ onStateChange: function(aWebProgress, aRequest, aState, aStatus) {
+ let failure = false;
+
+ // Captive portals sometimes redirect users
+ if ((aState & Ci.nsIWebProgressListener.STATE_REDIRECTING)) {
+ failure = true;
+ } else if ((aState & Ci.nsIWebProgressListener.STATE_STOP)) {
+ if (aRequest instanceof Ci.nsIHttpChannel) {
+ try {
+ failure = aRequest.responseStatus != 200;
+ } catch (e) {
+ failure = aStatus != Components.results.NS_OK;
+ }
+ }
+ }
+
+ // Calling cancel() will raise some OnStateChange notifications by itself,
+ // so avoid doing that more than once
+ if (failure && aStatus != Components.results.NS_BINDING_ABORTED) {
+ aRequest.cancel(Components.results.NS_BINDING_ABORTED);
+ setErrorPage("networkError");
+ }
+ },
+
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
+ if (aRequest && aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
+ aRequest.cancel(Components.results.NS_BINDING_ABORTED);
+ setErrorPage("networkError");
+ }
+ },
+
+ onProgressChange: function() {},
+ onStatusChange: function() {},
+ onSecurityChange: function() {},
+ },
+
+ handleEvent: function (evt) {
+ switch (evt.type) {
+ case "load":
+ this.iframe.contentWindow.addEventListener("FirefoxAccountsCommand", this);
+ this.iframe.removeEventListener("load", this);
+ break;
+ case "FirefoxAccountsCommand":
+ this.handleRemoteCommand(evt);
+ break;
+ }
+ },
+
+ /**
+ * onLogin handler receives user credentials from the jelly after a
+ * sucessful login and stores it in the fxaccounts service
+ *
+ * @param accountData the user's account data and credentials
+ */
+ onLogin: function (accountData) {
+ log("Received: 'login'. Data:" + JSON.stringify(accountData));
+
+ if (accountData.customizeSync) {
+ Services.prefs.setBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION, true);
+ }
+ delete accountData.customizeSync;
+ // sessionTokenContext is erroneously sent by the content server.
+ // https://github.com/mozilla/fxa-content-server/issues/2766
+ // To avoid having the FxA storage manager not knowing what to do with
+ // it we delete it here.
+ delete accountData.sessionTokenContext;
+
+ // We need to confirm a relink - see shouldAllowRelink for more
+ let newAccountEmail = accountData.email;
+ // The hosted code may have already checked for the relink situation
+ // by sending the can_link_account command. If it did, then
+ // it will indicate we don't need to ask twice.
+ if (!accountData.verifiedCanLinkAccount && !shouldAllowRelink(newAccountEmail)) {
+ // we need to tell the page we successfully received the message, but
+ // then bail without telling fxAccounts
+ this.injectData("message", { status: "login" });
+ // after a successful login we return to preferences
+ openPrefs();
+ return;
+ }
+ delete accountData.verifiedCanLinkAccount;
+
+ // Remember who it was so we can log out next time.
+ setPreviousAccountNameHash(newAccountEmail);
+
+ // A sync-specific hack - we want to ensure sync has been initialized
+ // before we set the signed-in user.
+ let xps = Cc["@mozilla.org/weave/service;1"]
+ .getService(Ci.nsISupports)
+ .wrappedJSObject;
+ xps.whenLoaded().then(() => {
+ updateDisplayedEmail(accountData);
+ return fxAccounts.setSignedInUser(accountData);
+ }).then(() => {
+ // If the user data is verified, we want it to immediately look like
+ // they are signed in without waiting for messages to bounce around.
+ if (accountData.verified) {
+ openPrefs();
+ }
+ this.injectData("message", { status: "login" });
+ // until we sort out a better UX, just leave the jelly page in place.
+ // If the account email is not yet verified, it will tell the user to
+ // go check their email, but then it will *not* change state after
+ // the verification completes (the browser will begin syncing, but
+ // won't notify the user). If the email has already been verified,
+ // the jelly will say "Welcome! You are successfully signed in as
+ // EMAIL", but it won't then say "syncing started".
+ }, (err) => this.injectData("message", { status: "error", error: err })
+ );
+ },
+
+ onCanLinkAccount: function(accountData) {
+ // We need to confirm a relink - see shouldAllowRelink for more
+ let ok = shouldAllowRelink(accountData.email);
+ this.injectData("message", { status: "can_link_account", data: { ok: ok } });
+ },
+
+ /**
+ * onSignOut handler erases the current user's session from the fxaccounts service
+ */
+ onSignOut: function () {
+ log("Received: 'sign_out'.");
+
+ fxAccounts.signOut().then(
+ () => this.injectData("message", { status: "sign_out" }),
+ (err) => this.injectData("message", { status: "error", error: err })
+ );
+ },
+
+ handleRemoteCommand: function (evt) {
+ log('command: ' + evt.detail.command);
+ let data = evt.detail.data;
+
+ switch (evt.detail.command) {
+ case "login":
+ this.onLogin(data);
+ break;
+ case "can_link_account":
+ this.onCanLinkAccount(data);
+ break;
+ case "sign_out":
+ this.onSignOut(data);
+ break;
+ default:
+ log("Unexpected remote command received: " + evt.detail.command + ". Ignoring command.");
+ break;
+ }
+ },
+
+ injectData: function (type, content) {
+ return fxAccounts.promiseAccountsSignUpURI().then(authUrl => {
+ let data = {
+ type: type,
+ content: content
+ };
+ this.iframe.contentWindow.postMessage(data, authUrl);
+ })
+ .catch(e => {
+ console.log("Failed to inject data", e);
+ setErrorPage("configError");
+ });
+ },
+};
+
+
+// Button onclick handlers
+function handleOldSync() {
+ let chromeWin = window
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow)
+ .QueryInterface(Ci.nsIDOMChromeWindow);
+ let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "old-sync";
+ chromeWin.switchToTabHavingURI(url, true);
+}
+
+function getStarted() {
+ show("remote");
+}
+
+function retry() {
+ show("remote");
+ wrapper.retry();
+}
+
+function openPrefs() {
+ // Bug 1199303 calls for this tab to always be replaced with Preferences
+ // rather than it opening in a different tab.
+ window.location = "about:preferences#sync";
+}
+
+function init() {
+ fxAccounts.getSignedInUser().then(user => {
+ // tests in particular might cause the window to start closing before
+ // getSignedInUser has returned.
+ if (window.closed) {
+ return Promise.resolve();
+ }
+
+ updateDisplayedEmail(user);
+
+ // Ideally we'd use new URL(document.URL).searchParams, but for about: URIs,
+ // searchParams is empty.
+ let urlParams = new URLSearchParams(document.URL.split("?")[1] || "");
+ let action = urlParams.get(ACTION_URL_PARAM);
+ urlParams.delete(ACTION_URL_PARAM);
+
+ switch (action) {
+ case "signin":
+ if (user) {
+ // asking to sign-in when already signed in just shows manage.
+ show("stage", "manage");
+ } else {
+ return fxAccounts.promiseAccountsSignInURI().then(url => {
+ show("remote");
+ wrapper.init(url, urlParams);
+ });
+ }
+ break;
+ case "signup":
+ if (user) {
+ // asking to sign-up when already signed in just shows manage.
+ show("stage", "manage");
+ } else {
+ return fxAccounts.promiseAccountsSignUpURI().then(url => {
+ show("remote");
+ wrapper.init(url, urlParams);
+ });
+ }
+ break;
+ case "reauth":
+ // ideally we would only show this when we know the user is in a
+ // "must reauthenticate" state - but we don't.
+ // As the email address will be included in the URL returned from
+ // promiseAccountsForceSigninURI, just always show it.
+ return fxAccounts.promiseAccountsForceSigninURI().then(url => {
+ show("remote");
+ wrapper.init(url, urlParams);
+ });
+ default:
+ // No action specified.
+ if (user) {
+ show("stage", "manage");
+ } else {
+ // Attempt a migration if enabled or show the introductory page
+ // otherwise.
+ return migrateToDevEdition(urlParams).then(migrated => {
+ if (!migrated) {
+ show("stage", "intro");
+ // load the remote frame in the background
+ return fxAccounts.promiseAccountsSignUpURI().then(uri =>
+ wrapper.init(uri, urlParams));
+ }
+ return Promise.resolve();
+ });
+ }
+ break;
+ }
+ return Promise.resolve();
+ }).catch(err => {
+ console.log("Configuration or sign in error", err);
+ setErrorPage("configError");
+ });
+}
+
+function setErrorPage(errorType) {
+ show("stage", errorType);
+}
+
+// Causes the "top-level" element with |id| to be shown - all other top-level
+// elements are hidden. Optionally, ensures that only 1 "second-level" element
+// inside the top-level one is shown.
+function show(id, childId) {
+ // top-level items are either <div> or <iframe>
+ let allTop = document.querySelectorAll("body > div, iframe");
+ for (let elt of allTop) {
+ if (elt.getAttribute("id") == id) {
+ elt.style.display = 'block';
+ } else {
+ elt.style.display = 'none';
+ }
+ }
+ if (childId) {
+ // child items are all <div>
+ let allSecond = document.querySelectorAll("#" + id + " > div");
+ for (let elt of allSecond) {
+ if (elt.getAttribute("id") == childId) {
+ elt.style.display = 'block';
+ } else {
+ elt.style.display = 'none';
+ }
+ }
+ }
+}
+
+// Migrate sync data from the default profile to the dev-edition profile.
+// Returns a promise of a true value if migration succeeded, or false if it
+// failed.
+function migrateToDevEdition(urlParams) {
+ let defaultProfilePath;
+ try {
+ defaultProfilePath = window.getDefaultProfilePath();
+ } catch (e) {} // no default profile.
+ let migrateSyncCreds = false;
+ if (defaultProfilePath) {
+ try {
+ migrateSyncCreds = Services.prefs.getBoolPref("identity.fxaccounts.migrateToDevEdition");
+ } catch (e) {}
+ }
+
+ if (!migrateSyncCreds) {
+ return Promise.resolve(false);
+ }
+
+ Cu.import("resource://gre/modules/osfile.jsm");
+ let fxAccountsStorage = OS.Path.join(defaultProfilePath, fxAccountsCommon.DEFAULT_STORAGE_FILENAME);
+ return OS.File.read(fxAccountsStorage, { encoding: "utf-8" }).then(text => {
+ let accountData = JSON.parse(text).accountData;
+ updateDisplayedEmail(accountData);
+ return fxAccounts.setSignedInUser(accountData);
+ }).then(() => {
+ return fxAccounts.promiseAccountsForceSigninURI().then(url => {
+ show("remote");
+ wrapper.init(url, urlParams);
+ });
+ }).then(null, error => {
+ log("Failed to migrate FX Account: " + error);
+ show("stage", "intro");
+ // load the remote frame in the background
+ fxAccounts.promiseAccountsSignUpURI().then(uri => {
+ wrapper.init(uri, urlParams)
+ }).catch(e => {
+ console.log("Failed to load signup page", e);
+ setErrorPage("configError");
+ });
+ }).then(() => {
+ // Reset the pref after migration.
+ Services.prefs.setBoolPref("identity.fxaccounts.migrateToDevEdition", false);
+ return true;
+ }).then(null, err => {
+ Cu.reportError("Failed to reset the migrateToDevEdition pref: " + err);
+ return false;
+ });
+}
+
+// Helper function that returns the path of the default profile on disk. Will be
+// overridden in tests.
+function getDefaultProfilePath() {
+ let defaultProfile = Cc["@mozilla.org/toolkit/profile-service;1"]
+ .getService(Ci.nsIToolkitProfileService)
+ .defaultProfile;
+ return defaultProfile.rootDir.path;
+}
+
+document.addEventListener("DOMContentLoaded", function onload() {
+ document.removeEventListener("DOMContentLoaded", onload, true);
+ init();
+ var buttonGetStarted = document.getElementById('buttonGetStarted');
+ buttonGetStarted.addEventListener('click', getStarted);
+
+ var buttonRetry = document.getElementById('buttonRetry');
+ buttonRetry.addEventListener('click', retry);
+
+ var oldsync = document.getElementById('oldsync');
+ oldsync.addEventListener('click', handleOldSync);
+
+ var buttonOpenPrefs = document.getElementById('buttonOpenPrefs')
+ buttonOpenPrefs.addEventListener('click', openPrefs);
+}, true);
+
+function initObservers() {
+ function observe(subject, topic, data) {
+ log("about:accounts observed " + topic);
+ if (topic == fxAccountsCommon.ONLOGOUT_NOTIFICATION) {
+ // All about:account windows get changed to action=signin on logout.
+ window.location = "about:accounts?action=signin";
+ return;
+ }
+
+ // must be onverified - we want to open preferences.
+ openPrefs();
+ }
+
+ for (let topic of OBSERVER_TOPICS) {
+ Services.obs.addObserver(observe, topic, false);
+ }
+ window.addEventListener("unload", function(event) {
+ log("about:accounts unloading")
+ for (let topic of OBSERVER_TOPICS) {
+ Services.obs.removeObserver(observe, topic);
+ }
+ });
+}
+initObservers();
diff --git a/application/basilisk/base/content/aboutaccounts/aboutaccounts.xhtml b/application/basilisk/base/content/aboutaccounts/aboutaccounts.xhtml
new file mode 100644
index 000000000..475f0e86f
--- /dev/null
+++ b/application/basilisk/base/content/aboutaccounts/aboutaccounts.xhtml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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/. -->
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % aboutAccountsDTD SYSTEM "chrome://browser/locale/aboutAccounts.dtd">
+ %aboutAccountsDTD;
+ <!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+ %syncBrandDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml" dir="&locale.dir;">
+ <head>
+ <title>&syncBrand.fullName.label;</title>
+ <meta name="viewport" content="width=device-width"/>
+
+
+ <link rel="icon" type="image/png" id="favicon"
+ href="chrome://branding/content/icon32.png"/>
+ <link rel="stylesheet"
+ href="chrome://browser/content/aboutaccounts/normalize.css"
+ type="text/css" />
+ <link rel="stylesheet"
+ href="chrome://browser/content/aboutaccounts/main.css"
+ type="text/css" />
+ <link rel="stylesheet"
+ href="chrome://browser/content/aboutaccounts/aboutaccounts.css"
+ type="text/css" />
+ </head>
+ <body>
+ <div id="stage">
+
+ <div id="manage">
+ <header>
+ <h1>&aboutAccounts.connected;</h1>
+ <div id="email"></div>
+ </header>
+
+ <section>
+ <div class="graphic graphic-sync-intro"> </div>
+
+ <div class="button-row">
+ <button id="buttonOpenPrefs" class="button" href="#" tabindex="0">&aboutAccountsConfig.syncPreferences.label;</button>
+ </div>
+ </section>
+ </div>
+
+ <div id="intro">
+ <header>
+ <h1>&aboutAccounts.welcome;</h1>
+ </header>
+
+ <section>
+ <div class="graphic graphic-sync-intro"> </div>
+
+ <div class="description">&aboutAccountsConfig.description;</div>
+
+ <div class="button-row">
+ <button id="buttonGetStarted" class="button" tabindex="1">&aboutAccountsConfig.startButton.label;</button>
+ </div>
+
+ <div class="links">
+ <button id="oldsync" tabindex="2">&aboutAccountsConfig.useOldSync.label;</button>
+ </div>
+ </section>
+ </div>
+
+ <div id="networkError">
+ <header>
+ <h1>&aboutAccounts.noConnection.title;</h1>
+ </header>
+
+ <section>
+ <div class="graphic graphic-sync-intro"> </div>
+
+ <div class="description">&aboutAccounts.noConnection.description;</div>
+
+ <div class="button-row">
+ <button id="buttonRetry" class="button" tabindex="3">&aboutAccounts.noConnection.retry;</button>
+ </div>
+ </section>
+ </div>
+
+ <div id="configError">
+ <header>
+ <h1>&aboutAccounts.badConfig.title;</h1>
+ </header>
+
+ <section>
+ <div class="graphic graphic-sync-intro"> </div>
+
+ <div class="description">&aboutAccounts.badConfig.description;</div>
+
+ </section>
+ </div>
+
+ </div>
+
+ <iframe mozframetype="content" id="remote" />
+
+ <script type="application/javascript;version=1.8"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="text/javascript;version=1.8"
+ src="chrome://browser/content/aboutaccounts/aboutaccounts.js" />
+ </body>
+</html>
diff --git a/application/basilisk/base/content/aboutaccounts/images/fox.png b/application/basilisk/base/content/aboutaccounts/images/fox.png
new file mode 100644
index 000000000..83af78d6c
--- /dev/null
+++ b/application/basilisk/base/content/aboutaccounts/images/fox.png
Binary files differ
diff --git a/application/basilisk/base/content/aboutaccounts/images/graphic_sync_intro.png b/application/basilisk/base/content/aboutaccounts/images/graphic_sync_intro.png
new file mode 100644
index 000000000..ff5f482f0
--- /dev/null
+++ b/application/basilisk/base/content/aboutaccounts/images/graphic_sync_intro.png
Binary files differ
diff --git a/application/basilisk/base/content/aboutaccounts/images/graphic_sync_intro@2x.png b/application/basilisk/base/content/aboutaccounts/images/graphic_sync_intro@2x.png
new file mode 100644
index 000000000..89fda0681
--- /dev/null
+++ b/application/basilisk/base/content/aboutaccounts/images/graphic_sync_intro@2x.png
Binary files differ
diff --git a/application/basilisk/base/content/aboutaccounts/main.css b/application/basilisk/base/content/aboutaccounts/main.css
new file mode 100644
index 000000000..8f4c3b34e
--- /dev/null
+++ b/application/basilisk/base/content/aboutaccounts/main.css
@@ -0,0 +1,166 @@
+*,
+*:before,
+*:after {
+ box-sizing: border-box;
+}
+
+html {
+ background-color: #F2F2F2;
+ height: 100%;
+}
+
+body {
+ color: #424f59;
+ font: message-box;
+ font-size: 14px;
+ height: 100%;
+}
+
+a {
+ color: #0095dd;
+ cursor: pointer; /* Use the correct cursor for anchors without an href */
+}
+
+a:active {
+ outline: none;
+}
+
+a:focus {
+ outline: 1px dotted #0095dd;
+}
+
+
+a.no-underline {
+ text-decoration: none;
+}
+
+#stage {
+ background:#fff;
+ border-radius: 5px;
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.25);
+ margin: 0 auto;
+ min-height: 300px;
+ padding: 60px 40px 40px 40px;
+ position: relative;
+ text-align: center;
+ top: 80px;
+ width: 420px;
+}
+
+header h1
+{
+ font-size: 24px;
+ font-weight: 200;
+ line-height: 1em;
+}
+
+#intro header h1 {
+ margin: 0 0 32px 0;
+}
+
+#manage header h1 {
+ margin: 0 0 12px 0;
+}
+
+#manage header #email {
+ margin-bottom: 23px;
+ color: rgb(138, 155, 168);
+ font-size: 19px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.description {
+ font-size: 18px;
+}
+
+.button-row {
+ margin-top: 45px;
+ margin-bottom:20px;
+}
+
+.button-row button,
+.button-row a.button {
+ background: #0095dd;
+ border: none;
+ border-radius: 5px;
+ color: #FFFFFF;
+ cursor: pointer;
+ font-size: 24px;
+ padding: 15px 0;
+ transition-duration: 150ms;
+ transition-property: background-color;
+ width: 100%;
+}
+
+.button-row a.button {
+ display: inline-block;
+ text-decoration: none;
+}
+
+.button-row a.button:active,
+.button-row a.button:hover,
+.button-row a.button:focus,
+.button-row button:active,
+.button-row button:hover,
+.button-row button:focus {
+ background: #08c;
+}
+
+
+.graphic-sync-intro {
+ background-image: url(images/graphic_sync_intro.png);
+ background-repeat: no-repeat;
+ background-size: 150px 195px;
+ height: 195px;
+ margin: 0 auto;
+ overflow: hidden;
+ text-indent: 100%;
+ white-space: nowrap;
+ width: 150px;
+}
+
+.description,
+.button-row {
+ margin-top: 30px;
+}
+
+.links {
+ margin: 20px 0;
+}
+
+@media only screen and (max-width: 500px) {
+ html {
+ background: #fff;
+ }
+
+ #stage {
+ box-shadow: none;
+ margin: 30px auto 0 auto;
+ min-height: none;
+ min-width: 320px;
+ padding: 0 10px;
+ width: 100%;
+ }
+
+ .button-row {
+ margin-top: 20px;
+ }
+
+ .button-row button,
+ .button-row a.button {
+ padding: 10px 0;
+ }
+
+}
+
+/* Retina */
+@media
+only screen and (min-device-pixel-ratio: 2),
+only screen and ( min-resolution: 192dpi),
+only screen and ( min-resolution: 2dppx) {
+ .graphic-sync-intro {
+ background-image: url(images/graphic_sync_intro@2x.png);
+ }
+}
diff --git a/application/basilisk/base/content/aboutaccounts/normalize.css b/application/basilisk/base/content/aboutaccounts/normalize.css
new file mode 100644
index 000000000..c02ab25de
--- /dev/null
+++ b/application/basilisk/base/content/aboutaccounts/normalize.css
@@ -0,0 +1,402 @@
+/*! normalize.css v2.1.3 | MIT License | git.io/normalize */
+
+/* ==========================================================================
+ HTML5 display definitions
+ ========================================================================== */
+
+/**
+ * Correct `block` display not defined in IE 8/9.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+ display: block;
+}
+
+/**
+ * Correct `inline-block` display not defined in IE 8/9.
+ */
+
+audio,
+canvas,
+video {
+ display: inline-block;
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9.
+ * Hide the `template` element in IE, Safari, and Firefox < 22.
+ */
+
+[hidden],
+template {
+ display: none;
+}
+
+/* ==========================================================================
+ Base
+ ========================================================================== */
+
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ * user zoom.
+ */
+
+html {
+ font-family: sans-serif; /* 1 */
+ -ms-text-size-adjust: 100%; /* 2 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Remove default margin.
+ */
+
+body {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Links
+ ========================================================================== */
+
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+
+a {
+ background: transparent;
+}
+
+/**
+ * Address `outline` inconsistency between Chrome and other browsers.
+ */
+
+a:focus {
+ outline: thin dotted;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+/* ==========================================================================
+ Typography
+ ========================================================================== */
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari 5, and Chrome.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9, Safari 5, and Chrome.
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
+ */
+
+b,
+strong {
+ font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari 5 and Chrome.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+
+hr {
+ box-sizing: content-box;
+ height: 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/**
+ * Correct font family set oddly in Safari 5 and Chrome.
+ */
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, serif;
+ font-size: 1em;
+}
+
+/**
+ * Improve readability of pre-formatted text in all browsers.
+ */
+
+pre {
+ white-space: pre-wrap;
+}
+
+/**
+ * Set consistent quote types.
+ */
+
+q {
+ quotes: "\201C" "\201D" "\2018" "\2019";
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+/* ==========================================================================
+ Embedded content
+ ========================================================================== */
+
+/**
+ * Remove border when inside `a` element in IE 8/9.
+ */
+
+img {
+ border: 0;
+}
+
+/**
+ * Correct overflow displayed oddly in IE 9.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* ==========================================================================
+ Figures
+ ========================================================================== */
+
+/**
+ * Address margin not present in IE 8/9 and Safari 5.
+ */
+
+figure {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Forms
+ ========================================================================== */
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+
+legend {
+ border: 0; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * 1. Correct font family not being inherited in all browsers.
+ * 2. Correct font size not being inherited in all browsers.
+ * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
+ */
+
+button,
+input,
+select,
+textarea {
+ font-family: inherit; /* 1 */
+ font-size: 100%; /* 2 */
+ margin: 0; /* 3 */
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+button,
+input {
+ line-height: normal;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
+ * Correct `select` style inheritance in Firefox 4+ and Opera.
+ */
+
+button,
+select {
+ text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+ cursor: pointer; /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+/**
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome.
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ box-sizing: content-box; /* 2 */
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari 5 and Chrome
+ * on OS X.
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/**
+ * 1. Remove default vertical scrollbar in IE 8/9.
+ * 2. Improve readability and alignment in all browsers.
+ */
+
+textarea {
+ overflow: auto; /* 1 */
+ vertical-align: top; /* 2 */
+}
+
+/* ==========================================================================
+ Tables
+ ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}