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

50
gaia/apps/ftu/build/build.js Executable file
View file

@ -0,0 +1,50 @@
'use strict';
/* jshint node: true */
var utils = require('utils');
var importBuild = require('import-config.js');
var DEBUG = false;
var FtuAppBuilder = function() {
};
// Set destination directory and application directory
FtuAppBuilder.prototype.setOptions = function(options) {
this.stageDir = utils.getFile(options.STAGE_APP_DIR);
this.appDir = utils.getFile(options.APP_DIR);
this.gaia = utils.gaia.getInstance(options);
this.gaia.stageDir = this.stageDir;
this.gaia.gaiaDir = options.GAIA_DIR;
this.raptorMode = options.RAPTOR === '1';
this.webapp = options.webapp;
};
FtuAppBuilder.prototype.generateManifest = function() {
var manifestObject = importBuild.generateManifest(this.webapp, this.gaia);
var file = utils.getFile(this.stageDir.path, 'manifest.webapp');
// Remove role:system from FTU so it gets an icon on the homescreen.
// Used to launch the FTU from the homescreen for Raptor tests.
if (this.raptorMode) {
delete manifestObject.role;
}
var args = DEBUG ? [manifestObject, undefined, 2] : [manifestObject];
utils.writeContent(file, JSON.stringify.apply(JSON, args));
};
FtuAppBuilder.prototype.generateAll = function() {
this.generateManifest();
importBuild.generateConfig('ftu', '/', this.gaia);
};
FtuAppBuilder.prototype.execute = function(options) {
this.setOptions(options);
this.generateAll();
};
exports.execute = function(options) {
utils.copyToStage(options);
(new FtuAppBuilder()).execute(options);
};

22
gaia/apps/ftu/config/large.json Executable file
View file

@ -0,0 +1,22 @@
{
"default": {
"steps": [
{
"image": "/style/images/tutorial/1_large.png",
"l10nKey": "tutorial-step1-large-3"
},
{
"image": "/style/images/tutorial/2_large.png",
"l10nKey": "tutorial-step2-large-2"
},
{
"image": "/style/images/tutorial/3_large.png",
"l10nKey": "tutorial-step3-large-2"
},
{
"image": "/style/images/tutorial/4_large.png",
"l10nKey": "tutorial-step4-large-2"
}
]
}
}

38
gaia/apps/ftu/config/tiny.json Executable file
View file

@ -0,0 +1,38 @@
{
"default": {
"steps": [
{
"video": "/style/images/tutorial/Homescreen.mp4",
"l10nKey": "tutorial-homescreen-tiny"
},
{
"video": "/style/images/tutorial/Notifications.mp4",
"l10nKey": "tutorial-notifications-v2-tiny"
},
{
"video": "/style/images/tutorial/Sheets.mp4",
"l10nKey": "tutorial-sheets-tiny"
},
{
"video": "/style/images/tutorial/Universal_Search.mp4",
"l10nKey": "tutorial-rocketbar-tiny"
}
]
},
"2.0..2.6": {
"steps": [
]
},
"2.1..2.6": {
"steps": [
]
},
"2.2..2.6": {
"steps": [
]
},
"2.5..2.6": {
"steps": [
]
}
}

754
gaia/apps/ftu/index.html Executable file
View file

@ -0,0 +1,754 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<meta charset="utf-8">
<meta name="theme-color" content="#eeeeee">
<!-- Common style -->
<link rel="stylesheet" href="/shared/style/input_areas.css">
<link rel="stylesheet" href="/shared/style/buttons.css">
<link rel="stylesheet" href="/shared/style/confirm.css">
<link rel="stylesheet" href="/shared/style/lists.css">
<link rel="stylesheet" href="/shared/style/progress_activity.css">
<link rel="stylesheet" href="/shared/style/status.css" rel="stylesheet">
<link rel="stylesheet" href="/shared/style/animations.css">
<!-- Specific style -->
<link rel="stylesheet" href="style/style.css">
<link rel="stylesheet" href="style/status.css">
<link rel="stylesheet" href="style/overlay.css">
<link rel="stylesheet" href="style/extensions.css">
<link rel="stylesheet" href="style/tutorial.css">
<link rel="stylesheet" href="style/responsive.css">
<link rel="stylesheet" href="/shared/pages/import/style/curtain_frame.css">
<title>Welcome to Browser OS</title>
<!-- Localization -->
<link rel="resource" type="image/png" href="/shared/resources/branding/logosmall.png"/>
<link rel="resource" type="image/png" href="/shared/resources/branding/powered.png"/>
<link rel="resource" type="image/png" href="/shared/resources/branding/privacy_sprite.png"/>
<meta name="defaultLanguage" content="en-US">
<meta name="availableLanguages" content="en-US">
<link rel="localization" href="/shared/locales/branding/branding.{locale}.properties">
<!-- This is needed for not duplicating SIM Import locales -->
<link rel="localization" href="/shared/locales/import_contacts/import_contacts.{locale}.properties">
<link rel="localization" href="locales/ftu.{locale}.properties">
<link rel="localization" href="/shared/locales/date/date.{locale}.properties">
<link rel="localization" href="/shared/locales/tz/tz.{locale}.properties">
<link rel="resource" type="application/json" href="/shared/resources/tz.json"/>
<link rel="resource" type="application/json" href="/shared/resources/languages.json"/>
<link rel="resource" type="application/json" href="/shared/resources/keyboard_layouts.json"/>
<link rel="resource" type="application/json" href="/shared/resources/apn.json"/>
<link rel="resource" type="application/json" href="/shared/resources/apn_tz.json"/>
<script defer src="/shared/js/intl/l20n.js"></script>
<script defer src="/shared/js/tz_select.js"></script>
<script defer src="/shared/js/sanitizer.js"></script>
<script defer src="/shared/js/input_mgmt/input_app_list.js"></script>
<script defer src="/shared/js/keyboard_helper.js"></script>
<script defer src="/shared/js/language_list.js"></script>
<script defer src="/shared/js/settings_listener.js"></script>
<script defer src="/shared/js/version_helper.js"></script>
<script defer src="/shared/js/event_safety.js"></script>
<script defer src="/shared/js/date_time_helper.js"></script>
<!-- Import contacts help files -->
<script defer src="/shared/js/contacts/import/utilities/status.js"></script>
<script defer src="/shared/js/contacts/import/utilities/overlay.js"></script>
<script defer src="/shared/js/contacts/import/utilities/config.js"></script>
<script defer src="/shared/js/contacts/import/utilities/misc.js"></script>
<script defer src="/shared/js/contacts/import/utilities/sdcard.js"></script>
<script defer src="/shared/js/contacts/import/utilities/vcard_parser.js"></script>
<script defer src="/shared/js/contacts/import/utilities/import_from_vcard.js"></script>
<script defer src="/shared/js/contacts/import/utilities/import_sim_contacts.js"></script>
<!-- Code for the FTU App -->
<script defer src="/shared/js/screen_layout.js"></script>
<script defer src="/shared/js/text_normalizer.js"></script>
<script defer src="/shared/js/lazy_loader.js"></script>
<script defer src="/shared/js/basket_client.js"></script>
<script defer src="js/services_launcher.js"></script>
<script defer src="/shared/js/contacts/import/import_status_data.js"></script>
<script defer src="/shared/js/async_storage.js"></script>
<script defer src="/shared/js/mobile_operator.js"></script>
<script defer src="/shared/js/icc_helper.js"></script>
<script defer src="/shared/js/wifi_helper.js"></script>
<script defer src="/shared/js/fxa_iac_client.js"></script>
<script defer src="js/sim_manager.js"></script>
<script defer src="js/memcard_manager.js"></script>
<script defer src="js/wifi.js"></script>
<script defer src="js/import_services.js"></script>
<script defer src="js/utils.js"></script>
<script defer src="js/finish_screen.js"></script>
<script defer src="js/tutorial_utils.js"></script>
<script defer src="js/tutorial.js"></script>
<script defer src="js/navigation.js"></script>
<script defer src="js/data_mobile.js"></script>
<script defer src="js/time.js"></script>
<script defer src="js/ui.js"></script>
<script defer src="js/language.js"></script>
<script defer src="js/app.js"></script>
<script defer src="js/operatorVariant.js"></script>
<!-- Confirm dialogs BB library -->
<script defer src="/shared/js/confirm.js"></script>
<!-- Components -->
<script defer src="/shared/js/component_utils.js"></script>
<script defer src="/shared/elements/gaia_switch/script.js"></script>
<script defer src="/shared/elements/gaia_checkbox/script.js"></script>
<script defer src="/shared/elements/gaia_radio/script.js"></script>
<script defer src="/shared/elements/gaia_buttons/script.js"></script>
<script defer src="/shared/elements/gaia_subheader/script.js"></script>
<script defer src="/shared/elements/config.js"></script>
<!-- <link rel="stylesheet" type="text/css" href="/shared/elements/gaia-icons/gaia-icons.css" /> -->
<link rel="stylesheet" type="text/css" href="app://theme.gaiamobile.org/shared/elements/gaia-theme/gaia-theme.css" />
<script defer src="/shared/elements/gaia-header/dist/gaia-header.js"></script>
<!-- Late Customization -->
<script defer src="/shared/js/icons_helper.js"></script>
<script defer src="js/late_customization.js"></script>
<link rel="stylesheet" href="style/late_customization.css">
<!--
<link href="/shared/pages/import/import.html">
<link href="/shared/pages/import/curtain.html">
<link href="/shared/pages/import/oauth.html">
-->
<!--
<link href="/shared/js/simple_phone_matcher.js">
<link href="/shared/js/mime_mapper.js">
<link href="/shared/js/contact_photo_helper.js">
<link href="/shared/js/contacts/contacts_matcher.js">
<link href="/shared/js/contacts/contacts_merger.js">
<link href="/shared/js/contacts/utilities/image_thumbnail.js">
<link href="/shared/js/contacts/merger_adapter.js">
<link href="/shared/js/contacts/utilities/http_rest.js">
-->
</head>
<body id="container" class="theme-settings" aria-hidden="true">
<section id="splash-screen" role="region" class="show">
</section>
<section id="unlock-sim-screen" role="region" class="skin-organic">
<gaia-header id="unlock-sim-action" action="back">
<h1 id="unlock-sim-header"></h1>
</gaia-header>
<article role="main">
<section id="pincode-screen" role="region">
<label id="pin-label"></label>
<label id="pin-retries-left" class="retries hidden"></label>
<section class="input-wrapper">
<input id="pin-input" name="simpin" type="password" x-inputmode="digit" size="8" maxlength="8" />
<label id="pin-error" class="hidden error">
<span data-l10n-id="pinError" class="main">The PIN was incorrect.</span>
<span data-l10n-id="pinLastChanceMsg" class="lastchance hidden"></span>
</label>
</section>
</section>
<section id="pukcode-screen" role="region">
<label id="puk-label"></label>
<label id="puk-retries-left" class="retries hidden"></label>
<section class="input-wrapper">
<input id="puk-input" name="simpuk" type="password" x-inputmode="digit" size="8" maxlength="8" />
<div id="puk-info" data-l10n-id="pukInfo" class="info">You must enter the Personal Unlocking Key (PUK) Code for the SIM card. Refer to your SIM card documentation or contact your carrier for more information.</div>
<div id="puk-error" class="hidden error">
<span data-l10n-id="pukError" class="main">The PUK was incorrect.</span>
<span data-l10n-id="pukLastChanceMsg" class="lastchance hidden"></span>
</div>
</section>
<label id="newpin" data-l10n-id="create_newpin">Create new PIN (4 to 8 digits)</label>
<section class="input-wrapper">
<input id="newpin-input" name="newpin" type="password" x-inputmode="digit" size="8" maxlength="8" />
<label id="newpin-error" data-l10n-id="pinValidation" class="hidden error">PIN must have between 4 and 8 characters.</label>
</section>
<label id="confirm-newpin" data-l10n-id="confirm_newpin">Confirm new PIN</label>
<section class="input-wrapper">
<input id="confirm-newpin-input" name="confirm-newpin" type="password" x-inputmode="digit" size="8" maxlength="8" />
<label id="confirm-newpin-error" data-l10n-id="confirm_newpin" class="hidden error">Confirm new PIN</label>
</section>
</section>
<section id="xckcode-screen" role="region">
<label id="xck-label" data-l10n-id="type_nck">Type your NCK code</label>
<label id="xck-retries-left" class="hidden retries"></label>
<section class="input-wrapper">
<input id="xck-input" name="simxck" type="password" x-inputmode="digit" size="16" maxlength="16" />
<label id="xck-error" class="hidden error">
<span data-l10n-id="confirm_newpin" class="main"></span>
<span data-l10n-id="nckLastChanceMsg" class="lastchance hidden"></span>
</label>
</section>
</section>
</article>
<gaia-buttons class="nav">
<button id="skip-pin-button" class="button-left" data-l10n-id="skip">
Skip
</button>
<button id="back-sim-button" class="button-left back hidden" data-l10n-id="back">
Back
</button>
<button id="unlock-sim-button" class="recommend" data-l10n-id="ok">
OK
</button>
</gaia-buttons>
</section>
<section id="sim-info-screen" role="region" class="skin-organic">
<gaia-header>
<h1 id="sim-info-header" data-l10n-id="simManager"></h1>
</gaia-header>
<article role="main">
<section>
<section id="sim-info-1" class="sim-info">
<div class="sim-image"></div>
<div class="sim-details">
<p class="sim-title">
<span data-l10n-id="simDetail" data-l10n-args='{"n": "1"}'>SIM 1</span>
<span data-l10n-id="simDefault" class="default">(default)</span>
</p>
<p id="sim-carrier-1" class="sim-carrier"></p>
<p id="sim-number-1" class="sim-number"></p>
</div>
</section>
<section id="sim-info-2" class="sim-info">
<div class="sim-image"></div>
<div class="sim-details">
<p class="sim-title">
<span data-l10n-id="simDetail" data-l10n-args='{"n": "2"}'>SIM 2</span>
</p>
<p id="sim-carrier-2" class="sim-carrier"></p>
<p id="sim-number-2" class="sim-number"></p>
</div>
</section>
</section>
<section id="sim-info-messages">
<p id="sim-default-message" data-l10n-id="simDefaultMessage">
Outgoing calls, outgoing messages and data connection are set to SIM 1 as default
</p>
<p id="sim-settings-message" data-l10n-id="simSettingsMessage">
You can change default settings at Settings &gt; SIM manager
</p>
</section>
</article>
<gaia-buttons class="nav">
<button id="sim-info-back" class="button-left icon icon-previous" data-l10n-id="navbar-back">Back</button>
<button id="sim-info-forward" class="recommend icon icon-next" data-l10n-id="navbar-next">Next</button>
</gaia-buttons>
</section>
<section id="activation-screen" role="region" class="skin-organic no-options">
<gaia-header>
<h1 id="main-title"></h1>
<button id="wifi-refresh-button" data-l10n-id="refresh">Refresh</button>
</gaia-header>
<article role="main">
<section role="region" id="languages">
<article data-type="list">
<ul>
<!-- filled by js/language.js -->
</ul>
</article>
</section>
<section role="region" id="data_3g">
<article data-type="list">
<ul>
<li>
<gaia-switch id="data-connection-switch">
<label data-l10n-id="dataConnection"></label>
</gaia-switch>
</li>
</ul>
</article>
<gaia-subheader id="dataCaution" skin="organic">
<span data-l10n-id="caution" />
</gaia-subheader>
<section class="content">
<p id="dataExplanation" data-l10n-id="dataConnectionInfo">Some text here</p>
</section>
</section>
<section role="region" id="wifi">
<div id="wifi-wrapper" role="main">
<article id="networks">
</article>
<button id="join-hidden-button" data-l10n-id="join-hidden-button">
Join hidden network
</button>
</div>
</section>
<section role="region" id="configure_network">
<section id="configure_network_params">
<form data-type="list">
<ul>
<li class="hidden">
<input type="text" id="wifi_ssid" value="" dir="auto"></input>
</li>
<li>
<label id="label_wifi_user" for="wifi_user" data-l10n-id="identity">User</label>
<input type="text" id="wifi_user" data-l10n-id="identityInput" placeholder="User" x-inputmode="verbatim" dir="auto"></input>
</li>
<li>
<label for="wifi_password" data-l10n-id="password">Password</label>
<input type="password" id="wifi_password" data-l10n-id="passwordInput" placeholder="Password" value="" maxlength="63" x-inputmode="verbatim"></input>
<gaia-checkbox class="inline" id="label_show_password" name="show_password">
<label id="wifi_show_password" data-l10n-id="showPassword"></label>
</gaia-checkbox>
</li>
</ul>
</form>
</section>
</section>
<section role="region" id="hidden-wifi-authentication">
<div>
<form data-type="list">
<ul>
<li>
<label id="label_wifi_ssid" for="hidden-wifi-ssid" data-l10n-id="ssid-name">
SSID Network Name
</label>
<input type="text" name="wifi_ssid" id="hidden-wifi-ssid" dir="auto"/>
</li>
<li>
<label id="label_wifi_security" for="hidden-wifi-security" data-l10n-id="security">
Security
</label>
<span class="button icon icon-dialog">
<select id="hidden-wifi-security">
<option data-l10n-id="securityNone">none</option>
<option data-l10n-id="securityWEP">WEP</option>
<option data-l10n-id="securityWPA-PSK" selected>WPA-PSK</option>
<option data-l10n-id="securityWPA-EAP">WPA-EAP</option>
</select>
</span>
</li>
<li class="hidden" id="hidden-wifi-identity-box">
<label id="label_wifi_identity" for="hidden-wifi-identity" data-l10n-id="identity">
Identity
</label>
<input type="text" name="wifi_identity" id="hidden-wifi-identity" dir="auto"/>
</li>
<li id="hidden-wifi-password-box">
<label id="label_hidden_wifi_password" for="hidden-wifi-password" data-l10n-id="password">
Password
</label>
<input type="password" name="wifi_hidden_password" id="hidden-wifi-password" value="" x-inputmode="verbatim" maxlength="63" />
<gaia-checkbox class="inline" id="hidden-wifi-show-password">
<label data-l10n-id="showPassword"></label>
</gaia-checkbox>
</li>
</ul>
</form>
</div>
</section>
<section role="region" id="date_and_time">
<section id="time_zone_overlay" class="gmt0">
</section>
<section id="time_zone_map">
<h1 data-l10n-id="timeZone">
Time zone
</h1>
<h2 id="time-zone-title">
UTC +00:00 Greenwich Mean Time
</h2>
</section>
<form id="time-form" data-type="list">
<ul>
<!-- timezones -->
<li>
<p id="tz-region-label" aria-label="region" data-l10n-id="region"></p>
<select id="tz-region" class="invisible"></select>
<button id="tz-region-button" class="change icon icon-dialog" data-l10n-id="change" type="button" aria-describedby="tz-region-label">
Change
</button>
</li>
<li>
<p id="tz-city-label" aria-label="city" data-l10n-id="city">GMT</p>
<select id="tz-city" class="invisible"></select>
<button id="tz-city-button" class="change icon icon-dialog" data-l10n-id="change" type="button" aria-describedby="tz-city-label">
Change
</button>
</li>
<!-- date/time -->
<li>
<p id="date-configuration-label" aria-label="date" data-l10n-id="date"></p>
<input id="date-configuration" class="invisible" type="date" min="1970-1-1" max="2037-12-31"></input>
<button id="date-configuration-button" class="change icon icon-dialog" data-l10n-id="change" aria-describedby="date-configuration-label">
Change
</button>
</li>
<li>
<p id="time-configuration-label" aria-label="time" data-l10n-id="time">00:00</p>
<input id="time-configuration" class="invisible" type="time"></input>
<button id="time-configuration-button" class="change icon icon-dialog" data-l10n-id="change" type="button" aria-describedby="time-configuration-label">
Change
</button>
</li>
</ul>
</form>
</section>
<section role="region" id="geolocation">
<article data-type="list">
<ul>
<li>
<gaia-switch id="geolocation-switch" name="geolocation.enabled" checked>
<label data-l10n-id="geolocationCheckbox"></label>
</gaia-switch>
</li>
</ul>
</article>
<gaia-subheader id="geolocationNotice" skin="organic">
<span data-l10n-id="notice" />
</gaia-subheader>
<section class="content">
<p id="geolocationInfo" data-l10n-id="geolocationInfo2">Some text here</p>
<p id="geolocationInfoMore" data-l10n-id="geolocationInfoMore">Some text here</p>
<p><a href="#about-your-privacy" data-l10n-id="aboutYourPrivacy">Your Privacy</a></p>
</section>
</section>
<section role="region" id="import_contacts">
<article data-type="list">
<ul>
<li id="sim-import" class="importOption">
<button id="sim-import-button" class="icon icon-sim" data-l10n-id="importSim2">
SIM card
</button>
<p id="no-sim" data-l10n-id="noSimMsg">To import insert a SIM card</p>
</li>
<li id="sd_import" class="importOption">
<button id="sd-import-button" class="icon icon-sd" data-l10n-id="importMemoryCard">
SD card
</button>
<p id="no-memorycard" data-l10n-id="noMemoryCardMsg">To import insert a SD card</p>
</li>
<li id="gmail_import" class="importOption">
<button id="gmail-import-button" class="icon icon-gmail" data-l10n-id="importGmail">
GMail
</button>
</li>
<li id="live_import" class="importOption">
<button id="live-import-button" class="icon icon-wlive" data-l10n-id="importOutlook">
Outlook
</button>
</li>
</ul>
</article>
</section>
<section role="region" id="firefox_accounts">
<div class="fxa-logo"></div>
<p data-l10n-id="fxa-upsell" id="fxa-intro"></p>
<p>
<button id="fxa-create-account" data-l10n-id="fxa-create-account-or-sign-in">
Create Account or Sign In
</button>
</p>
</section>
<section role="region" id="about-your-rights">
<p data-l10n-id="about-your-rights-0">
Browser OS is built on free and open source software
by a community of thousands from all over the world.
There are a few things you should know:
</p>
<p data-l10n-id="about-your-rights-1">
Browser OS is made available to you under the terms of several
open source licenses including the Mozilla Public License.
A device running Browser OS may also contain proprietary
software from third parties. Any code provided under open licenses
give you the right to modify the source code and distribute your
modified versions as long as you comply with their terms.
</p>
<p data-l10n-id="about-your-rights-2">
You are not granted any trademark rights or licenses to the trademarks
of the Mozilla Foundation or any party, including without limitation
the Browser OS name or logo.
</p>
<p data-l10n-id="about-your-rights-3">
Some features in Browser OS, such as the Crash Reporter,
give you the option to provide feedback to Mozilla.
By choosing to submit feedback, you give Mozilla permission to use
the feedback to improve its products, to publish the feedback
on its websites, and to distribute the feedback.
</p>
</section>
<section role="region" id="about-your-privacy">
<i class='hack-first-child'></i> <!-- see Bug 819886 -->
<article data-type="list">
<ul>
<li>
<a class="privacy-menuitem privacy-browserOS external"
href="https://www.mozilla.org/privacy/firefox-os"
data-l10n-id="privacy-b2g"
title="B2G OS">Browser OS</a>
</li>
<li>
<a class="privacy-menuitem privacy-marketPlace external"
href="https://marketplace.firefox.com/privacy-policy"
data-l10n-id="privacy-marketplace"
title="Marketplace">
Marketplace
</a>
</li>
</ul>
</article>
</section>
<section role="region" id="sharing-performance-data">
<p data-l10n-id='telemetry-sends2'>
Telemetry sends Mozilla data about performance, hardware,
configurations, customizations and app usage in your
Browser OS device. We use this to make our products better,
faster and more responsive.
</p>
<!-- Links destinations are discussed in https://bugzilla.mozilla.org/show_bug.cgi?id=812956 -->
<p data-l10n-id='learn-more-telemetry2'
data-l10n-args='{"url": "www.mozilla.org/telemetry"}'>
<a href="http://www.mozilla.org/telemetry" class="external"></a>
</p>
<p data-l10n-id='learn-more-information2'
data-l10n-args='{"url": "www.mozilla.org/privacy"}'>
<a href="http://www.mozilla.org/privacy" class="external"></a>
</p>
</section>
<section role="region" id="SIM_mandatory">
<p data-l10n-id="SIM_mandatory_warm">
Please, insert a SIM card to continue.
</p>
</section>
<section role="region" id="browser_privacy">
<p data-l10n-id="mozillaCommunity2">
When you use Browser OS, you become
part of a global community helping to
build a brighter future for the Web.
If you’d like to know more about the
Mozilla community, our other products
and events near you, please enter your
email address below.
</p>
<section id="newsletter-form">
<input type="email" data-l10n-id="emailAddress" placeholder="Email address" id="newsletter-input"></input>
</section>
<section id="newsletter-success-screen">
<p data-l10n-id="email-successfully-added">
Email successfully added!
</p>
</section>
<p data-l10n-id="learn-more-privacy2">
<a href="http://www.mozilla.org/privacy/firefox-os" class="external"></a>
</p>
</section>
<section role="region" id="welcome_browser">
<section id="browser_logo_horizontal">
<img src="/shared/resources/branding/logosmall.png" role="presentation">
</section>
<p data-l10n-id="helpImprove3">
<a href="https://www.mozilla.org/privacy/firefox-os" class="external"></a>
</p>
<p data-l10n-id="learn-more-privacy3">
<a href="http://www.mozilla.org/privacy/firefox-os" class="external"></a>
</p>
<section id="form_share_statistics">
<ul>
<li id="menuItem-basicmetrics">
<gaia-radio name="metricsGroup" id="metrics-basic" value="Basic">
<label data-l10n-id="basicMetricsRadio"></label>
<details data-l10n-id="basicMetricsExplain"></details>
</gaia-radio>
</li>
<li id="menuItem-enhancedMetrics">
<gaia-radio name="metricsGroup" id="metrics-enhanced" value="Enhanced">
<label data-l10n-id="enhancedMetricsRadio"></label>
<details data-l10n-id="enhancedMetricsExplain"></details>
</gaia-radio>
</li>
<li id="menuItem-noneMetrics">
<gaia-radio name="metricsGroup" id="metrics-none" value="None">
<label data-l10n-id="noneMetricsRadio"></label>
<details data-l10n-id="noneMetricsExplain"></details>
</gaia-radio>
</li>
</ul>
</section>
</section>
</article>
<gaia-buttons id="nav-bar" class="nav forward-only">
<button id="back" class="button-left icon icon-previous" data-l10n-id="navbar-back">Back</button>
<button id="forward" class="recommend icon icon-next" data-l10n-id="navbar-next">Next</button>
<button id="wifi-join-button" class="recommend" data-l10n-id="navbar-join">Join</button>
</gaia-buttons>
</section>
<section id="finish-screen" class="finish-screen-base" role="region">
<section class="content">
<!-- for tiny devices -->
<div class="for-tiny">
<h1 data-l10n-id="startYourPhone">
Start your Phone Tour!
</h1>
<h2 data-l10n-id="joinTutorial">
Join us for a quick tutorial about your new phone.
</h2>
<p data-l10n-id="optionsAvailable">
You can alter the language, Date / Time, Wi-Fi connection in the settings area.
</p>
</div>
<!-- for large devices -->
<div class="for-large">
<p data-l10n-id="startYourTablet">
Start your tablet tour!
</p>
</div>
</section>
<gaia-buttons skin="dark" class="nav">
<button id="skip-tutorial-button" class="button-left" data-l10n-id="skip">
Skip
</button>
<button id="lets-go-button" class="recommend" data-l10n-id="startTour">
Start Tour!
</button>
</gaia-buttons>
</section>
</section>
<section id="update-screen" class="finish-screen-base" role="region">
<section class="content">
<h1 data-l10n-id="startYourPhone">
Start your phone tour!
</h1>
<p data-l10n-id="joinTutorialUpdated">
Join us for a quick tutorial about the updates to your phone!
</p>
</section>
<gaia-buttons skin="dark" class="nav">
<button id="update-skip-tutorial-button" class="button-left" data-l10n-id="skip">
Skip
</button>
<button id="update-lets-go-button" class="recommend" data-l10n-id="startTour">
Start Tour!
</button>
</gaia-buttons>
</section>
<section id="tutorial" role="region" data-step="1">
<article id="tutorial-steps-container" role="main">
<section id="tutorial-step-header">
<p id="tutorial-step-title">
</p>
</section>
<section id="tutorial-step-media">
<img id="tutorial-step-image" class="step-img-base" role="presentation">
<video id="tutorial-step-video" class="step-video-base" role="presentation" autoplay hidden loop></video>
</section>
</article>
<gaia-buttons id="tutorial-nav-bar" class="nav" skin="dark">
<button id="back-tutorial" class="button-left icon icon-previous" data-l10n-id="navbar-back">Back</button>
<button id="forward-tutorial" class="recommend icon icon-next" data-l10n-id="navbar-next">Next</button>
</gaia-buttons>
</section>
<section id="tutorial-finish-tiny" class="tutorial-finish-base" role="region">
<section class="content">
<h1 data-l10n-id="thatsIt">
That's it!
</h1>
<h2 id="enjoyYourPhone" data-l10n-id="enjoyYourPhone">
You're ready to enjoy your new phone ;)
</h2>
<h2 id="enjoyYourPhoneUpdated" data-l10n-id="enjoyYourPhoneUpdated" hidden>
You're ready to enjoy your updated phone.
</h2>
</section>
<gaia-buttons skin="dark" class="forward-only">
<button class="recommend" id="tutorialFinished" data-l10n-id="letsGo">
</button>
</menu>
</section>
</section>
<section id="tutorial-finish-large" class="tutorial-finish-base" role="region">
<section class="content">
<p data-l10n-id="thatsIt1Tablet-2">
You can always swipe from the bottom edge to enter home screen.
</p>
<p data-l10n-id="thatsIt2Tablet-2">
Let's do it !
</p>
</section>
</section>
<form role='dialog' data-type='confirm' id='offline-error-dialog'>
<section>
<h1 data-l10n-id='offline-dialog-title'>You must be connected to the Internet to view the page.</h1>
<p><small>Please go back and connect to
the Internet. You can also view the page later from Settings or by
entering {{url}} in any browser.</small></p>
<gaia-buttons skin="dark">
<button data-l10n-id='OK'>OK</button>
</gaia-buttons>
</section>
</form>
<form role='dialog' data-type='confirm' id='offline-newsletter-error-dialog'>
<section>
<h1 data-l10n-id='offline-newsletter-dialog-title'>You must be connected to the Internet to subscribe to the newsletter.</h1>
<p><small data-l10n-id='offline-newsletter-dialog-text'>Please go back and connect to
the Internet.</small></p>
<gaia-buttons skin="dark">
<button data-l10n-id='OK'>OK</button>
</gaia-buttons>
</section>
</form>
<form role='dialog' data-type='confirm' id='invalid-email-error-dialog'>
<section>
<h1 data-l10n-id='invalid-email-dialog-title'>Your email is not valid</h1>
<p><small data-l10n-id='invalid-email-dialog-text'>Please go back and correct your entry.</small></p>
<gaia-buttons skin="dark">
<button data-l10n-id='OK'>OK</button>
</gaia-buttons>
</section>
</form>
<form id="confirmation-message" class="hide" role="dialog" data-type="confirm">
<section>
<h1>Confirmation title</h1>
<p>Confirmation message</p>
</section>
<menu>
<button>Action 1</button>
<button>Action 2</button>
</menu>
</form>
<form data-type="confirm" id="loading-overlay" role="dialog" class="hide no-opacity">
<section id="progress-activity" class="hide">
<h1 id="progress-title"></h1>
<p role="status">
<span id="progress-msg"></span>
<progress id="progress-element" class="pack-activity" value="0" max="100"></progress>
</p>
<menu>
<button data-l10n-id="cancel" id="cancel-overlay">Cancel</button>
</menu>
</section>
</form>
<section id="statusMsg" role="status" class="ftu">
<p></p>
</section>
<iframe id="iframe_curtain" src="/shared/pages/import/curtain.html"></iframe>
<iframe id="iframe_oauth" hidden src="/shared/pages/import/oauth.html"></iframe>
<iframe id="iframe_extensions"></iframe>
</body>
</html>

125
gaia/apps/ftu/js/app.js Executable file
View file

@ -0,0 +1,125 @@
/* global DataMobile, Navigation, SimManager, TimeManager,
UIManager, WifiManager, ImportIntegration, Tutorial,
VersionHelper, LanguageManager, eventSafety */
/* exported AppManager */
'use strict';
var AppManager = {
EVENT_PREFIX: 'ftu-',
init: function init(versionInfo) {
window.performance.mark('initialize');
this.isInitialized = true;
LanguageManager.init();
UIManager.init();
Navigation.init();
window.performance.mark('navigationLoaded');
this.publish('setup');
var splashTimeout = 1025;
var splashScreenHidden = this.whenEvent(UIManager.splashScreen,
'transitionend',
splashTimeout).then(() => {
UIManager.container.removeAttribute('aria-hidden');
window.performance.mark('visuallyLoaded');
});
var languageListReady = this.whenEvent(window, 'languagelistready');
Promise.all([splashScreenHidden, languageListReady]).then(() => {
window.performance.mark('fullyLoaded');
});
// if it's an upgrade we can jump to tutorial directly
if (versionInfo && versionInfo.isUpgrade()) {
var stepsKey = versionInfo.delta();
// Play the FTU Tuto steps directly on update
UIManager.changeStatusBarColor(UIManager.DARK_THEME);
UIManager.splashScreen.classList.remove('show');
UIManager.activationScreen.classList.remove('show');
UIManager.updateScreen.classList.add('show');
// Load and play the what's new tutorial
Tutorial.init(stepsKey, function() {
Tutorial.start();
});
return;
}
SimManager.init();
WifiManager.init();
ImportIntegration.init();
TimeManager.init();
DataMobile.init();
Tutorial.init();
var kSplashTimeout = 700;
// Retrieve mobile connection if available
// this is used to keep all tests passing while introducing multi-sim APIs
var conn = window.navigator.mozMobileConnections &&
window.navigator.mozMobileConnections[0];
if (!conn) {
setTimeout(function() {
// For desktop
window.location.hash = '#';
UIManager.activationScreen.classList.add('show');
window.location.hash = '#languages';
UIManager.splashScreen.classList.remove('show');
window.performance.mark('navigationInteractive');
window.performance.mark('contentInteractive');
}, kSplashTimeout);
return;
}
// Do we need pin code after splash screen?
setTimeout(function() {
// TODO Include VIVO SIM Card management
// https://bugzilla.mozilla.org/show_bug.cgi?id=801269#c6
Navigation.manageStep();
UIManager.activationScreen.classList.add('show');
// Remove the splash
UIManager.splashScreen.classList.remove('show');
window.performance.mark('navigationInteractive');
window.performance.mark('contentInteractive');
}, kSplashTimeout);
},
whenEvent: function (target, name, timeoutMs) {
return new Promise((resolve, reject) => {
eventSafety(target, name, resolve, timeoutMs || 1000);
});
},
publish: function(name, detail) {
var evt = new CustomEvent(this.EVENT_PREFIX + name,
{
bubbles: true,
detail: detail || this
});
window.dispatchEvent(evt);
}
};
function init() {
VersionHelper.getVersionInfo().then(function(versionInfo) {
if (!AppManager.isInitialized) {
AppManager.init(versionInfo);
} else {
UIManager.initTZ();
if (!UIManager.mainTitle.hasAttribute('data-l10n-id')) {
UIManager.mainTitle.setAttribute('data-l10n-id', 'language');
}
}
});
}
init();
document.addEventListener('DOMRetranslated', init);
document.l10n.ready.then(() => {
window.performance.mark('l10nready');
});

115
gaia/apps/ftu/js/data_mobile.js Executable file
View file

@ -0,0 +1,115 @@
/* exported DataMobile */
/* global Navigation */
/* global UIManager */
'use strict';
var DataMobile = {
key: 'ril.data.enabled',
keySV: 'ftu.ril.data.enabled',
STEP_DATA_3G: 2,
apnRetrieved: false,
init: function dm_init() {
var settings = navigator.mozSettings;
if (!settings) {
console.log('Settings is not available');
return;
}
this.settings = settings;
},
removeSVStatusObserver: function dm_removeSVStatusObserver() {
this.settings.removeObserver(this.keySV, this.getStatus);
},
getStatus: function dm_getStatus(aCallback) {
var self = this;
var reqSV = this.settings.createLock().get(this.keySV);
reqSV.onsuccess = function gst_svsuccess() {
// We need to be sure that we are on Cellular Data screen
// if the user has passed the screen very fast this configuration will
// have no effect
var svStatus = reqSV.result[self.keySV];
if (svStatus !== undefined && !UIManager.dataConnectionChangedByUsr) {
self.settings.removeObserver(self.keySV, self.getStatus);
if (Navigation.currentStep === self.STEP_DATA_3G) {
self.toggle(svStatus);
if (typeof aCallback === 'function') {
aCallback(svStatus);
}
} else {
self.getRealStatus(aCallback);
}
} else {
self.getRealStatus(aCallback);
}
};
reqSV.onerror = function gst_error() {
console.log('Error retrieving ' + self.keySV);
};
this.settings.addObserver(this.keySV, this.getStatus.bind(this, aCallback));
},
getRealStatus: function dm_getRealStatus(callback) {
var request = this.settings.createLock().get(this.key);
var self = this;
request.onsuccess = function gst_success() {
var currentStatus = request.result[self.key];
self.isDataAvailable = currentStatus;
callback(currentStatus);
};
request.onerror = function gst_error() {
console.log('Error retrieving ' + self.key);
};
},
toggle: function dm_toggle(status, callback) {
var options = {};
options[this.key] = status;
if (!this.apnRetrieved) {
// I need to retrieve APN
var self = this;
this.getAPN(function apn_recovered() {
self.settings.createLock().set(options);
self.apnRetrieved = true;
self.isDataAvailable = status;
if (callback) {
callback();
}
});
return;
}
this.settings.createLock().set(options);
this.apnRetrieved = true;
this.isDataAvailable = status;
if (callback) {
callback();
}
},
getAPN: function dm_getapn(callback) {
// By the time the APN settings are needed in the FTU before enabling data
// calls the system app (through the operator variant logic) might store the
// APN settings into the settings database. If not wait for that before
// enabling data calls.
var _self = this;
function ensureApnSettings() {
var req = _self.settings.createLock().get('ril.data.apnSettings');
req.onsuccess = function loadApn() {
var apnSettings = req.result['ril.data.apnSettings'];
if (apnSettings) {
if (callback) {
callback(req.result);
}
_self.settings.removeObserver('ril.data.apnSettings',
ensureApnSettings);
}
};
}
ensureApnSettings();
this.settings.addObserver('ril.data.apnSettings', ensureApnSettings);
}
};

View file

@ -0,0 +1,70 @@
/* exported FinishScreen */
/* global ScreenLayout, Utils */
(function(exports) {
'use strict';
// Defaut layout is 'tiny', the one for mobile device
var currentLayout = 'tiny';
var elementIDs = [
'tutorialFinished',
'enjoyYourPhone',
'enjoyYourPhoneUpdated'
];
var dom = {};
var initialized = false;
var FinishScreen = {
init: function (isUpgrade) {
if (initialized) {
return;
}
// We consider only 'tablet' & 'mobile' (tiny)
if (ScreenLayout.getCurrentLayout() !== 'tiny') {
currentLayout = 'large';
}
// Get panel selector based on layout
var panelSelector = 'tutorial-finish-' + currentLayout;
// Show finish panel
var finishPanel = document.getElementById(panelSelector);
finishPanel.classList.add('show');
// Cache non-layout-related DOM elements
elementIDs.forEach(function (name) {
dom[Utils.camelCase(name)] = document.getElementById(name);
}, this);
if (isUpgrade) {
dom.enjoyYourPhone.hidden = true;
dom.enjoyYourPhoneUpdated.hidden = false;
}
// Add regular listener if needed
if (currentLayout === 'tiny') {
dom.tutorialFinished.addEventListener('click', function ftuEnd() {
window.close();
});
} else {
// In 'tablet', we have to use IAC to tell system ftu is done
navigator.mozApps.getSelf().onsuccess = function(evt) {
var app = evt.target.result;
app.connect('ftucomms').then(function onConnAccepted(ports) {
ports.forEach(function(port) {
port.postMessage('done');
});
}, function onConnRejected(reason) {
console.warn('FTU is rejected due to ' + reason);
});
};
}
initialized = true;
}
};
exports.FinishScreen = FinishScreen;
}(this));

View file

@ -0,0 +1,48 @@
/* global ServicesLauncher */
/* exported ImportIntegration */
'use strict';
var ImportIntegration = {
get liveImportButton() {
delete this.liveImportButton;
return (this.liveImportButton =
document.getElementById('live-import-button'));
},
get gmailImportButton() {
delete this.gmailImportButton;
return (this.gmailImportButton =
document.getElementById('gmail-import-button'));
},
init: function () {
this.liveImportButton.addEventListener('click', this);
this.gmailImportButton.addEventListener('click', this);
},
handleEvent: function (event) {
switch (event.type) {
case 'click':
if (event.target === this.liveImportButton) {
ServicesLauncher.start('live');
}
else if (event.target === this.gmailImportButton) {
ServicesLauncher.start('gmail');
}
break;
}
},
checkImport: function (nextState) {
if (nextState === 'disabled') {
this.gmailImportButton.setAttribute('disabled', 'disabled');
this.liveImportButton.setAttribute('disabled', 'disabled');
}
else if (nextState === 'enabled') {
this.gmailImportButton.removeAttribute('disabled');
this.liveImportButton.removeAttribute('disabled');
}
}
};

85
gaia/apps/ftu/js/language.js Executable file
View file

@ -0,0 +1,85 @@
/* global KeyboardHelper, LanguageList */
/* exported LanguageManager */
'use strict';
var LanguageManager = {
settings: window.navigator.mozSettings,
init: function init() {
this.buildLanguageList();
document.getElementById('languages').addEventListener('change', this);
var onSettingChange = this._boundHandleSettingChange =
this.handleSettingChange.bind(this);
this.settings.addObserver('language.current', onSettingChange);
this.settings.addObserver('accessibility.screenreader', onSettingChange);
},
uninit: function uninit() {
if (this._boundHandleSettingChange) {
this.settings.removeObserver('language.current',
this._boundHandleSettingChange);
this.settings.removeObserver('accessibility.screenreader',
this._boundHandleSettingChange);
}
},
handleSettingChange: function(evt) {
switch (evt.settingName) {
case 'accessibility.screenreader':
this.buildLanguageList();
break;
case 'language.current':
// the 2nd parameter is to reset the current enabled layouts
KeyboardHelper.changeDefaultLayouts(evt.settingValue, true);
break;
}
},
handleEvent: function handleEvent(evt) {
if (!this.settings || evt.target.name != 'language.current') {
return true;
}
this.setLanguage(evt.target.value);
return false;
},
// update current launguage settings
setLanguage: function settings_setLanguage(language) {
this.settings.createLock().set({
'language.current': language
});
},
buildLanguageList: function settings_buildLanguageList() {
var panel = document.querySelector('#languages');
var container = panel.querySelector('ul');
container.innerHTML = '';
LanguageList.get(function fillLanguageList(allLanguages, currentLanguage) {
for (var lang in allLanguages) {
var label = document.createElement('label');
// wrap the name of the language in Unicode control codes which force
// the proper direction of the text regardless of the direction of
// the whole app
label.textContent = LanguageList.wrapBidi(lang, allLanguages[lang]);
var radio = document.createElement('gaia-radio');
radio.className = 'checkone';
radio.setAttribute('name', 'language.current');
radio.setAttribute('value', lang);
radio.checked = (lang === currentLanguage);
radio.appendChild(label);
// Needed to use a custom class with dynamic insertion.
radio.configureClass();
var li = document.createElement('li');
li.dataset.value = lang;
li.appendChild(radio);
container.appendChild(li);
}
panel.dataset.languagesReady = true;
window.dispatchEvent(new CustomEvent('languagelistready'));
});
}
};

View file

@ -0,0 +1,644 @@
/* global URLSearchParams, Navigation, UIManager,
IconsHelper, MobileOperator, Sanitizer, LazyLoader */
(function(exports) {
'use strict';
const MANIFEST_SETTING_NAME = 'latecustomization.url';
const OPERATOR_INFO_SETTING_NAME = 'latecustomization.operatorInfo';
const COMMS_CHANNEL = 'ftucomms';
const ICON_SIZE = 32;
const IMG_PATH = 'style/images/late_customization';
const ICON_INSTALLING_URL = `${IMG_PATH}/app_installing.svg`;
const ICON_FAILED_URL = `${IMG_PATH}/app_install_unrecoverable.svg`;
const ICON_DEFAULT_URL = `${IMG_PATH}/default_icon.svg`;
function LateCustomizationPanel() {
this.debug('LateCustomizationPanel ctor');
}
LateCustomizationPanel.prototype = {
DEBUG: false,
name: 'LateCustomizationPanel',
constructor: LateCustomizationPanel,
_initialized: false,
_downloadPossible: false,
/* The manifest URL for the customization (comes from mozSettings) */
// e.g. https://marketplace-dev.allizom.org/\
// api/v2/late-customization/?carrier=telenor&region=ca
_customizationManifestUrl: '',
get enabled() {
return Boolean(this._customizationManifestUrl &&
(this._operatorInfo || this._predefinedOperatorInfo));
},
get operatorInfo() {
var info = {};
Object.assign(info,
this._operatorInfo || this.getInfoFromMobileConnection(),
this._predefinedOperatorInfo);
return info;
},
get carrierName() {
var info = this.operatorInfo;
return info.carrier || info.operator;
},
get region() {
return this.operatorInfo.region || '';
},
start: function() {
window.addEventListener('hashchange', this);
window.addEventListener('ftu-done', this);
window.addEventListener('online', this);
window.addEventListener('offline', this);
this._operatorInfo = this.getInfoFromMobileConnection();
this.waitForSettingValues().then((results) => {
var [url, predefinedInfo] = results;
if (url) {
this._customizationManifestUrl = url;
}
if (predefinedInfo) {
// store operator/params separate from
// bona-fide mobileconnection details
this._predefinedOperatorInfo = predefinedInfo;
}
this.debug(
'start, resolved manifest url: ' + this._customizationManifestUrl,
' is enabled? ' + this.enabled,
' operatorInfo: ', this._operatorInfo);
if (this.enabled) {
// bail completely if not enabled: don't register step, don't render
this.init();
} else {
this.debug('Not enabled; not initializing');
if (!this._operatorInfo) {
// keep watching in case we can init later
this.waitForOperatorInfo().then((info) => {
if (!this._initialized && this.enabled) {
this.init();
}
});
}
}
}).catch(e => {
this.debug('start: didnt init, error from waitForSettingValues: ', e);
});
},
init: function() {
// conditions for displaying Late Customization pane
// are embodied in this.enabled. If not enabled, init is skipped
// and the step is not registered at all.
if (this._initialized || !this.enabled) {
return;
}
this._initialized = true;
this.appManifestUrls = [];
this.render();
Navigation.registerStep({
hash: '#late_customization',
afterStep: 'firefox_accounts'
});
this.attemptToPopulateAppList();
},
postConnectMessage: function(message) {
navigator.mozApps.getSelf().onsuccess = function(evt) {
var app = evt.target.result;
if (app.connect) {
app.connect(COMMS_CHANNEL).then(function onConnAccepted(ports) {
ports.forEach(function(port) {
port.postMessage(message);
});
}, function onConnRejected(reason) {
console.error('Cannot connect to comms channel: ', reason);
});
} else {
console.error ('mozApps does not have a connect method. ' +
'Cannot connect to the comms channel.');
}
};
},
attemptToPopulateAppList: function() {
this.debug('attemptToPopulateAppList, waiting to go online');
// start waiting to go online.
// We bail if ftu completes first
var connected = this.whenUsableConnection();
connected.then(() => {
this.debug('attemptToPopulateAppList, came online');
return this.fetchAppsToInstall().then((appsMap) => {
if (!(this._appsToInstall && this._appsToInstall.size)) {
// no apps, bail early
this.debug('No apps in response from ' +
this._customizationManifestUrl + ', bailing');
return;
}
var urls = Array.from(this._appsToInstall.keys());
var postMessageDetail = {
type: 'late-customization-apps',
urls: urls
};
this.postConnectMessage(postMessageDetail);
this.updateAppList(this._appsToInstall);
}).catch(err => {
console.warn('LateCustomizationPanel, error fetching customization ' +
'manifest from: ' + this._customizationManifestUrl,
err.message);
});
}).catch((e) => {
this.debug('No connection available in time to ' +
'fetch late customization manifest:', e);
});
return connected;
},
fetchAppsToInstall: function() {
if (this._appsToInstall) {
return Promise.resolve(this._appsToInstall);
}
return this.fetchManifest().then((manifestResp) => {
var appsMap = this.getAppsFromManifest(manifestResp);
if (!appsMap) {
return Promise.reject('getAppsFromManifest didnt return appsMap');
}
return (this._appsToInstall = appsMap);
}, (err) => {
console.warn('LateCustomization: ', err.message || err);
});
},
waitForSettingValues: function() {
var haveUrl, haveInfo;
var lock = navigator.mozSettings.createLock();
function getSetting(name) {
return new Promise((resolve, reject) => {
lock = (lock && !lock.closed) ?
lock : navigator.mozSettings.createLock();
var get = lock.get(name);
get.addEventListener('success', (function() {
resolve(get.result[name]);
}));
get.addEventListener('error', (function() {
reject();
}));
});
}
if (this._customizationManifestUrl) {
haveUrl = Promise.resolve(this._customizationManifestUrl);
} else {
haveUrl = getSetting(MANIFEST_SETTING_NAME);
}
haveInfo = getSetting(OPERATOR_INFO_SETTING_NAME);
return Promise.all([haveUrl, haveInfo]);
},
waitForOperatorInfo: function() {
var info = this.getInfoFromMobileConnection();
if (info) {
this._operatorInfo = info;
return Promise.resolve(info);
}
return new Promise((resolve, reject) => {
this._operatorInfoRequest = {
resolve: resolve,
reject: reject
};
});
},
getInfoFromMobileConnection: function() {
var connections = navigator.mozMobileConnections;
if (connections) {
for (var i=0, conn; i<connections.length; i++) {
conn = connections[i];
if(conn && conn.voice && conn.voice.connected) {
var operatorInfos = MobileOperator.userFacingInfo(conn);
if (operatorInfos.operator) {
operatorInfos.mcc = conn.voice.network.mcc;
operatorInfos.mnc = conn.voice.network.mnc;
return operatorInfos;
}
}
}
}
return null;
},
getAppsFromManifest: function(data) {
var apps = new Map();
var langs = navigator.languages;
var list = data && (data.apps || data.objects);
if (!(list && list.length)) {
return apps;
}
list.filter((app) => {
return app.is_disabled !== true &&
(new Set(app.device_types)).has('firefoxos');
}).forEach((appData) => {
appData.manifestURL = appData.manifest_url;
// get locale-appropriate name for user display
var appName = '';
langs.some(function tryLanguage(lang) {
if (this[lang]) {
appName = this[lang];
// aborts [].some
return true;
}
}, appData.name);
appData.displayName = appName;
this.debug('app data: ', appData.displayName, appData);
apps.set(appData.manifestURL, appData);
});
return apps;
},
attemptInstalls: function() {
this._installQueue = [];
if (this._appsToInstall && this._appsToInstall.size) {
var attempts = [];
this._appsToInstall.forEach((appData, manifestURL) => {
if (appData.installState == 'installed' ||
appData.installState == 'pending') {
this.debug('attemptInstalls: app install already underway: ',
manifestURL, appData.installState);
} else {
attempts.push(this.enqueAppInstall(appData));
}
});
return Promise.all(attempts);
} else {
return Promise.resolve();
}
},
installApp: function(appData) {
this.debug('installApp: ', appData.manifestURL);
return this.whenUsableConnection().then(() => {
return new Promise((resolve, reject) => {
var req = window.navigator.mozApps.install(appData.manifestURL);
var onOutcome = (arg) => {
var selector = `li[data-manifest-url="${appData.manifestURL}"]`;
var itemNode = this.appListNode.querySelector(selector);
var manifestURL = req.result && req.result.manifestURL;
this.debug('appinstall DOMRequest outcome:', arg, req.result);
if (req.error) {
delete appData.installState;
itemNode && itemNode.classList.add('failed');
console.warn('Install error: ', req.error);
} else if (req.result) {
appData.installState = req.result.installState;
this.debug('Install success, app: ',
manifestURL, appData.installState);
}
itemNode && itemNode.classList.remove('pending');
resolve(req);
};
req.onsuccess = req.onerror = onOutcome;
});
}).catch(() => {
this.debug('Lost connection while attempting install of ' +
appData.manifestURL);
});
},
enqueAppInstall: function(app) {
var installQueue = this._installQueue;
installQueue.push(app);
this.debug('enqueAppInstall, queue: ', app.manifestURL);
var currentInstallAttempt = this._installAttempt || Promise.resolve();
return currentInstallAttempt.then(req => {
if (req && req.error) {
console.warn('install error:', req.error);
}
if (this._installQueue.length) {
this._installAttempt =
this.installApp(this._installQueue.shift());
}
});
},
debug: function() {
if (!this.DEBUG) {
return;
}
var args = Array.from(arguments);
args.unshift('LateCustomizationPanel');
console.log.apply(console, args);
},
buildManifestUrl: function(url, infos={}) {
var apiURL = new URL(url);
var params = new URLSearchParams(apiURL.search.substr(1));
for(var key in infos) {
if (typeof infos[key] !== 'undefined') {
params.set(key, infos[key]);
}
}
apiURL.search = params.toString();
return apiURL.href;
},
fetchManifest: function() {
var url = this.buildManifestUrl(this._customizationManifestUrl,
this.operatorInfo);
if (!url) {
return Promise.reject(new Error('No manifest URL available'));
}
this.debug('fetchManifest at: ' + url);
return new Promise((resolve, reject) => {
LazyLoader.getJSON(url, true).then((resp) => {
if (resp.objects) {
resolve(resp);
} else {
reject('No apps in reponse to: ' + url);
}
}).catch(ex => {
reject(ex);
});
});
},
whenUsableConnection: function() {
if (this._pendingNextOnline) {
return this._pendingNextOnline;
}
var canDownload = navigator.onLine;
if (canDownload) {
return Promise.resolve(true);
}
return new Promise((resolve, reject) => {
this._nextOnlineRequest = {
resolve: resolve,
reject: reject
};
});
},
handleEvent: function(evt) {
switch (evt.type) {
case 'hashchange':
if (this.enabled &&
evt.target.location.hash === '#late_customization') {
if (this._appsToInstall && this._appsToInstall.size) {
this.onPanelShown();
} else {
Navigation.skipStep();
}
}
// TODO: is some event I can listen to to know the sim is ready
// to be asked these questions?
if (this._operatorInfoRequest) {
var info = this.getInfoFromMobileConnection();
if (info) {
this._operatorInfo = info;
this._operatorInfoRequest.resolve(info);
this._operatorInfoRequest = null;
}
}
break;
case 'online':
case 'offline':
// install is underway, try to resume
if (navigator.onLine &&
this._installQueue && this._installQueue.length) {
return this.attemptInstalls();
}
if (navigator.onLine && this._nextOnlineRequest) {
this._nextOnlineRequest.resolve(true);
} else if (this._nextOnlineRequest) {
this._nextOnlineRequest.reject(new Error('offline'));
// re-start the wait/fetch/install process
if (!this._appsToInstall) {
this.attemptToPopulateAppList();
}
}
this._nextOnlineRequest = null;
break;
case 'ftu-done':
// abort anything pending
if (this._operatorInfoRequest) {
this._operatorInfoRequest.reject(
new Error('FTU complete with no viable operator'));
this._operatorInfoRequest = null;
}
if (this._nextOnlineRequest) {
this._nextOnlineRequest.reject(
new Error('done before connection became available'));
this._nextOnlineRequest = null;
}
break;
}
},
onPanelShown: function() {
UIManager.mainTitle.setAttribute('data-l10n-id',
'late-customization');
this.finalizeRender();
this.attemptInstalls();
},
_createElement: function(name, options) {
var elm = document.createElement(name);
if (options.id) {
elm.id = options.id;
}
if (options.className) {
elm.className = options.className;
}
if (options.dataset) {
for(var p in options.dataset) {
elm.dataset[p] = options.dataset[p];
}
}
if (options.attributes) {
for(var a in options.attributes) {
elm.setAttribute(a, options.attributes[a]);
}
}
if ('textContent' in options) {
elm.textContent = options.textContent;
}
return elm;
},
render: function() {
var section = this.element =
document.getElementById('late_customization');
if (!section) {
section = this._createElement('section', {
attributes: { role: 'region' },
id: 'late_customization'
});
this.element = section;
}
section.innerHTML = Sanitizer.escapeHTML `
<section id="late-customization-cando" class="content hidden">
<p id="late_customization-downloading-message"></p>
</section>
<section id="late-customization-nocando" class="content">
<p id="late_customization-download-later-message"></p>
<p id="late_customization-download-later-marketplace"></p>
</section>
<article data-type="list">
<ul id="late_customization-applist" class="hidden"></ul>
</article>`;
function setTranslationById(domId, l10Id, attrs) {
var elem = section.querySelector('#' + domId);
document.l10n.setAttributes(elem, l10Id, attrs);
}
var carrierName = this.carrierName;
if (carrierName) {
setTranslationById(
'late_customization-downloading-message',
'late-customization-downloading-message',
{ carrierName: this.carrierName });
setTranslationById(
'late_customization-download-later-message',
'late-customization-download-later',
{ carrierName: this.carrierName });
} else {
// Use an alternate string if we have no carrier name to display
setTranslationById(
'late_customization-downloading-message',
'late-customization-downloading-message-default');
setTranslationById(
'late_customization-download-later-message',
'late-customization-download-later-default');
}
setTranslationById(
'late_customization-download-later-marketplace',
'late-customization-download-later-marketplace',
{ carrierName: this.carrierName });
if (!section.parentNode) {
document.querySelector('#activation-screen > article')
.appendChild(section);
}
this.appListNode = document.querySelector('#late_customization-applist');
},
renderAppItem: function(appData) {
var itemNode = this._createElement('li', {
dataset: { manifestUrl: appData.manifestURL },
className: 'pending'
});
var listNode = this.element.querySelector('#late_customization-applist');
if (!listNode) {
this.debug('No element found to render app details into');
return;
}
itemNode.innerHTML = Sanitizer.escapeHTML `
<span class="icon"
style="background-image: url(${ICON_INSTALLING_URL})"></span>
<p dir="auto">${appData.displayName}</p>
`;
listNode.appendChild(itemNode);
var iconElem = itemNode.querySelector('.icon');
this.getAppIconUrl(appData).then((iconUrl) => {
this.debug('getAppIconUrl returned:', iconUrl);
var img = new Image();
img.onload = () => {
iconElem.style.backgroundImage = 'url('+img.src+')';
};
img.onerror = () => {
iconElem.style.backgroundImage = 'url('+ICON_DEFAULT_URL+')';
};
img.src = iconUrl.href;
});
return itemNode;
},
getAppIconUrl: function(appData, iconSize = ICON_SIZE) {
var siteObj = {};
var manifest = appData;
var manifestURL = appData.manifest_url;
var origin = manifest.origin = new URL(manifestURL).origin;
var hasIcons = (manifest.icons && Object.keys(manifest.icons).length);
if (!hasIcons) {
return Promise.resolve(new URL(ICON_DEFAULT_URL));
}
//Getting the icons from the FirefoxOS-style manifest
siteObj.manifestURL = manifestURL;
siteObj.manifest = manifest;
var gotIcon = new Promise((resolve, reject) => {
IconsHelper.getIcon(origin, iconSize, null, siteObj)
.then((iconUrl) => {
this.debug('getIcon returned: ', iconUrl);
resolve(iconUrl);
}).catch(err => {
this.debug('Failed to load icon from: ' +
JSON.stringify(appData.icons));
console.warn('getIcon errback, err: ',err);
resolve(new URL(ICON_FAILED_URL));
});
});
return gotIcon;
},
finalizeRender: function() {
var container = this.element;
var appsMap = this._appsToInstall;
var hasApps = (appsMap && appsMap.size);
var canDoElm = container.querySelector('#late-customization-cando');
var noCanDoElm = container.querySelector('#late-customization-nocando');
var appListElm = container.querySelector('#late_customization-applist');
canDoElm.classList.toggle('hidden', !hasApps);
appListElm.classList.toggle('hidden', !hasApps);
noCanDoElm.classList.toggle('hidden', !!hasApps);
},
updateAppList: function(appsMap) {
var list = this.element.querySelector('#late_customization-applist');
this.debug('updateAppList: ', appsMap);
while (list.firstChild) {
list.firstChild.remove();
}
if (appsMap && appsMap.size) {
for (var appData of appsMap.values()) {
this.debug('calling renderAppItem with app: ', appData.manifestURL);
this.renderAppItem(appData);
}
}
this.finalizeRender();
}
};
exports.LateCustomizationPanel = LateCustomizationPanel;
var panel;
document.l10n.ready.then(() => {
panel = exports.lateCustomizationPanel = new LateCustomizationPanel();
});
window.addEventListener('ftu-setup', function onSetup(evt) {
window.removeEventListener('ftu-setup', onSetup);
panel && (panel.manager = evt.detail);
panel && panel.start();
});
})(window);

View file

@ -0,0 +1,164 @@
/* global ConfirmDialog,
UIManager,
utils,
VCFReader */
/* exported SdManager */
'use strict';
var SdManager = {
available: function() {
return utils.sdcard.checkStorageCard();
},
checkSDButton: function sm_checkSDButton() {
var sdOption = UIManager.sdImportButton;
// If there is an unlocked SD we activate import from SD
if (!SdManager.alreadyImported && SdManager.available()) {
sdOption.removeAttribute('disabled');
UIManager.noMemorycard.classList.add('hidden');
} else {
sdOption.setAttribute('disabled', 'disabled');
if (!SdManager.alreadyImported) {
UIManager.noMemorycard.classList.remove('hidden');
}
}
},
importContacts: function sm_importContacts() {
var importedContacts = 0;
UIManager.navBar.setAttribute('aria-disabled', 'true');
var cancelled = false;
var importer = null;
var importButton;
var progress = utils.overlay.show(
'memoryCardContacts-importing',
'activityBar',
'infiniteProgress'
);
utils.overlay.showMenu();
return new Promise((resolve, reject) => {
utils.overlay.oncancel = () => {
cancelled = true;
if (importer) {
importer.finish();
} else {
UIManager.navBar.removeAttribute('aria-disabled');
utils.overlay.hide();
}
resolve();
};
importButton = UIManager.sdImportButton;
utils.sdcard.retrieveFiles([
'text/vcard',
'text/directory;profile=vCard',
'text/directory'
], ['vcf', 'vcard'], (error, fileArray) => {
if (!error && !fileArray.length) {
error = 'No contacts were found';
}
if (error) {
reject(error);
return import_error(error);
}
if (cancelled) {
resolve();
return;
}
var promises = [];
fileArray.forEach(file => {
promises.push(utils.sdcard.getTextFromFile(file, onContacts));
});
Promise.all(promises).then(() => {
utils.misc.setTimestamp('sd');
UIManager.navBar.removeAttribute('aria-disabled');
utils.overlay.hide();
if (cancelled) {
resolve();
return;
}
SdManager.alreadyImported = true;
importButton.setAttribute('disabled', 'disabled');
utils.status.show({
id: 'memoryCardContacts-imported3',
args: { n: importedContacts }
});
resolve();
}).catch(reject);
});
});
function onContacts(text) {
if (cancelled) {
return Promise.reject();
}
return new Promise((resolve, reject) => {
importer = new VCFReader(text);
if (!text || !importer) {
var error = 'No contacts were found';
import_error(error);
reject(error);
return;
}
importer.onimported = imported_contact;
importer.onerror = error => {
import_error(error);
reject(error);
};
importer.process(() => {
if (cancelled) {
reject('Cancelled');
utils.overlay.hide();
return;
}
resolve();
});
});
}
function imported_contact() {
importedContacts++;
progress.update();
}
function import_error(e) {
UIManager.navBar.removeAttribute('aria-disabled');
utils.overlay.hide();
// Just in case the user decides to do so later
importButton.removeAttribute('disabled');
// Showing error message allowing user to retry
var cancel = {
title: 'cancel',
callback: function() {
ConfirmDialog.hide();
}
};
var retry = {
title: 'retry',
isRecommend: true,
callback: function() {
ConfirmDialog.hide();
// And now the action is reproduced one more time
importButton.click();
}
};
ConfirmDialog.show(null, 'memoryCardContacts-error', cancel, retry);
}
}
};

532
gaia/apps/ftu/js/navigation.js Executable file
View file

@ -0,0 +1,532 @@
/* global DataMobile, SimManager, IccHelper,
SdManager, UIManager, WifiManager, WifiUI,
ImportIntegration,
OperatorVariant */
/* exported Navigation */
(function(exports) {
'use strict';
const DOGFOODSETTING = 'debug.performance_data.dogfooding';
const METRICSSETTING = 'metrics.selectedMetrics.level';
/*
Steps of the First Time Usage App
*/
var steps = [
{
onlyForward: true,
hash: '#languages',
requireSIM: false
},
{
onlyForward: false,
hash: '#data_3g',
requireSIM: true
},
{
onlyForward: false,
hash: '#wifi',
requireSIM: false
},
{
onlyForward: false,
hash: '#date_and_time',
requireSIM: false
},
{
onlyForward: false,
hash: '#geolocation',
requireSIM: false
},
{
onlyForward: false,
hash: '#import_contacts',
requireSIM: false
},
{
onlyForward: false,
hash: '#firefox_accounts',
requireSIM: false
},
{
onlyForward: false,
hash: '#welcome_browser',
requireSIM: false
},
{
onlyForward: false,
hash: '#browser_privacy',
requireSIM: false
}
];
exports.Navigation = {
get stepCount() {
return this._stepOrder ? this._stepOrder.length : 0;
},
currentStepIndex: 0,
previousStepIndex: 0,
simMandatory: false,
skipMobileDataScreen: false,
skipDateTimeScreen: false,
tzInitialized: false,
_stepsById: null,
_stepOrder: null,
init: function n_init() {
this._stepOrder = [];
this._stepsById = {};
var settings = navigator.mozSettings;
var forward = document.getElementById('forward');
var back = document.getElementById('back');
this._onForwardClick = this.forward.bind(this);
this._onBackClick = this.back.bind(this);
forward.addEventListener('click', this._onForwardClick);
back.addEventListener('click', this._onBackClick);
window.addEventListener('hashchange', this);
this._onActivationScreenClick = this.handleExternalLinksClick.bind(this);
UIManager.activationScreen.addEventListener('click',
this._onActivationScreenClick);
// register pre-defined steps in the oder they appear
steps.forEach(step => {
this.registerStep(step);
});
var self = this;
var reqSIM =
settings && settings.createLock().get('ftu.sim.mandatory') || {};
reqSIM.onsuccess = function onSuccess() {
self.simMandatory = reqSIM.result['ftu.sim.mandatory'] || false;
};
navigator.mozApps.getSelf().onsuccess = function(evt) {
var app = evt.target.result;
app.connect('ftucomms').then(function onConnAccepted(ports) {
self.ports = ports;
ports.forEach(port => port.postMessage('started'));
}, function onConnRejected(reason) {
console.warn('FTU navigation cannot use IAC: ' + reason);
});
};
},
uninit: function() {
this._stepOrder = null;
this._stepsById = null;
this.currentStepIndex = 0;
this.previousStepIndex = 0;
this.ports = null;
var forward = document.getElementById('forward');
var back = document.getElementById('back');
var activationScreen = UIManager && UIManager.activationScreen;
if (forward && this._onForwardClick) {
forward.removeEventListener('click', this._onForwardClick);
}
if (back && this._onBackClick) {
back.removeEventListener('click', this._onBackClick);
}
if (activationScreen && this._onActivationScreenClick) {
activationScreen.removeEventListener('click',
this._onActivationScreenClick);
}
window.removeEventListener('hashchange', this);
},
registerStep: function(step) {
if (!step.id) {
step.id = step.hash.substring(1); // strip off '#' to make an id;
}
var id = step.id;
this._stepsById[id] = step;
var beforeStepIdx = step.hasOwnProperty('beforeStep') ?
this._stepOrder.indexOf(step.beforeStep) : -1;
var afterStepIdx = step.hasOwnProperty('afterStep') ?
this._stepOrder.indexOf(step.afterStep) : -1;
if (beforeStepIdx > -1) {
this._stepOrder.splice(beforeStepIdx, 0, id);
} else if (afterStepIdx > -1) {
this._stepOrder.splice(afterStepIdx+1, 0, id);
} else {
this._stepOrder.push(id);
}
},
stepAt: function(idx) {
var stepId = this._stepOrder[idx];
if (stepId) {
return this._stepsById[stepId];
}
},
indexOfStep: function(step) {
var id = (typeof id === 'string') ? step.id : step;
return this._stepOrder.indexOf(id);
},
back: function n_back(event) {
var currentStep = this.stepAt(this.currentStepIndex);
var actualHash = window.location.hash;
if (actualHash != currentStep.hash) {
window.history.back();
} else {
var self = this;
var goToStep = function() {
self.previousStepIndex = self.currentStepIndex;
self.currentStepIndex--;
if (self.currentStepIndex >= 0) {
self.manageStep();
}
};
goToStep();
}
},
forward: function n_forward(event) {
var self = this;
var goToStepForward = function() {
self.previousStepIndex = self.currentStepIndex;
self.currentStepIndex++;
if (self.currentStepIndex >= self.stepCount) {
// Try to send Newsletter here
UIManager.sendNewsletter(function newsletterSent(result) {
if (result) { // sending process ok, we advance
UIManager.activationScreen.classList.remove('show');
UIManager.changeStatusBarColor(UIManager.DARK_THEME);
UIManager.finishScreen.classList.add('show');
UIManager.hideActivationScreenFromScreenReader();
} else { // error on sending, we stay where we are
self.currentStepIndex--;
}
});
return;
}
self.manageStep();
};
goToStepForward();
},
handleExternalLinksClick: function n_handleExternalLinksClick(e) {
var link = e.target;
if (! link.mozMatchesSelector('a.external')) {
return;
}
e.preventDefault();
var href = link.href,
title = link.getAttribute('title') || link.textContent;
if (navigator.onLine) {
this.displayExternalLink(href, title);
} else {
UIManager.displayOfflineDialog(href, title);
}
},
displayExternalLink: function n_displayExternalLink(href, title) {
window.open(href, '', 'dialog');
},
ensureTZInitialized: function () {
if (!this.tzInitialized) {
return UIManager.initTZ().then(() => {
this.skipDateTimeScreen = !UIManager.timeZoneNeedsConfirmation;
this.tzInitialized = true;
});
} else {
return Promise.resolve();
}
},
handleEvent: function n_handleEvent(event) {
var actualHash = window.location.hash;
switch (actualHash) {
case '#languages':
UIManager.mainTitle.setAttribute('data-l10n-id', 'language');
break;
case '#data_3g':
UIManager.mainTitle.setAttribute('data-l10n-id', '3g');
DataMobile.
getStatus(UIManager.updateDataConnectionStatus.bind(UIManager));
break;
case '#wifi':
DataMobile.removeSVStatusObserver();
UIManager.mainTitle.setAttribute('data-l10n-id', 'selectNetwork');
UIManager.activationScreen.classList.remove('no-options');
if (UIManager.navBar.classList.contains('secondary-menu')) {
UIManager.navBar.classList.remove('secondary-menu');
return;
}
// This might seem like an odd place to call UIManager.initTZ, but
// there's a reason for it. initTZ tries to determine the timezone
// using information about the current mobile network connection.
// We want to call initTZ as late as possible to give the mobile
// network time to connect before the function is called.
// But we have to call initTZ *before* the Date & Time page
// appears so that it doesn't delay the appearance of the page.
// This is the last good opportunity to call it.
WifiManager.getNetworks(networks => {
this.ensureTZInitialized().then(() => {
WifiUI.renderNetworks(networks);
});
});
break;
case '#date_and_time':
UIManager.mainTitle.setAttribute('data-l10n-id', 'dateAndTime');
break;
case '#geolocation':
UIManager.mainTitle.setAttribute('data-l10n-id', 'geolocation');
break;
case '#import_contacts':
UIManager.mainTitle.setAttribute('data-l10n-id', 'importContacts3');
// Enabling or disabling SIM import depending on card status
SimManager.checkSIMButton();
// Enabling or disabling SD import depending on card status
SdManager.checkSDButton();
// If we have 3G or Wifi activate online services import
var onlineState;
if (!WifiManager.api) {
// Desktop
ImportIntegration.checkImport('enabled');
break;
}
onlineState = window.navigator.onLine ? 'enabled' : 'disabled';
ImportIntegration.checkImport(onlineState);
break;
case '#firefox_accounts':
UIManager.mainTitle.setAttribute('data-l10n-id', 'firefox-accounts');
break;
case '#welcome_browser':
UIManager.mainTitle.setAttribute('data-l10n-id', 'aboutBrowser');
// Initialize the radio button to preselect the basic metrics.
var settings = navigator.mozSettings;
var basicMetrics = document.getElementById('metrics-basic');
var enhancedMetrics = document.getElementById('metrics-enhanced');
var noneMetrics = document.getElementById('metrics-none');
var defaultLevel = settings &&
settings.createLock().get(METRICSSETTING);
if (defaultLevel) {
defaultLevel.onsuccess = function() {
var result = defaultLevel.result[METRICSSETTING];
if (result === 'Basic') {
basicMetrics.checked = true;
} else if (result === 'Enhanced') {
enhancedMetrics.checked = true;
} else if (result === 'None') {
noneMetrics.checked = true;
}
};
}
// If it's a dogfooder, we don't want them to disable the metrics.
var dogfood = settings && settings.createLock().get(DOGFOODSETTING);
if (dogfood) {
dogfood.onsuccess = function() {
if (dogfood.result[DOGFOODSETTING]) {
basicMetrics.setAttribute('disabled', 'true');
enhancedMetrics.setAttribute('disabled', 'true');
noneMetrics.setAttribute('disabled', 'true');
} else {
basicMetrics.removeAttribute('disabled');
enhancedMetrics.removeAttribute('disabled');
noneMetrics.removeAttribute('disabled');
}
};
}
break;
case '#browser_privacy':
UIManager.mainTitle.setAttribute('data-l10n-id', 'aboutBrowser');
break;
case '#SIM_mandatory':
UIManager.mainTitle.setAttribute('data-l10n-id', 'SIM_mandatory');
break;
case '#about-your-rights':
case '#about-your-privacy':
UIManager.mainTitle.setAttribute('data-l10n-id', 'aboutBrowser');
UIManager.navBar.classList.add('back-only');
break;
case '#sharing-performance-data':
UIManager.mainTitle.setAttribute('data-l10n-id', 'aboutBrowser');
UIManager.navBar.classList.add('back-only');
break;
}
// If SIM card is mandatory, we hide the button skip
if (this.simMandatory) {
UIManager.skipPinButton.classList.add('hidden');
UIManager.backSimButton.classList.remove('hidden');
} else {
UIManager.skipPinButton.classList.remove('hidden');
UIManager.backSimButton.classList.add('hidden');
}
// Managing options button
var lastIndex = this.stepCount - 1;
if (this.currentStepIndex <= lastIndex &&
(this.stepAt(this.currentStepIndex).hash !== '#wifi')) {
UIManager.activationScreen.classList.add('no-options');
}
// Managing nav buttons when coming back from out-of-steps (privacy)
if (this.currentStepIndex <= lastIndex &&
this.stepAt(this.currentStepIndex).hash === actualHash) {
UIManager.navBar.classList.remove('back-only');
}
},
/**
* Posts IAC message about FTU steps passed.
*/
postStepMessage: function n_postStepMessage(stepIndex) {
if (!this.ports) {
return;
}
var step = this.stepAt(stepIndex);
this.ports.forEach(function(port) {
port.postMessage({
type: 'step',
hash: step.hash
});
});
},
skipStep: function n_skipStep() {
var stepIndex = this.currentStepIndex;
stepIndex += stepIndex > this.previousStepIndex ? 1 : -1;
if (stepIndex <= 0) {
this.previousStepIndex = stepIndex = 0;
}
if (stepIndex > this.stepCount) {
this.previousStepIndex = stepIndex = this.stepCount;
}
this.currentStepIndex = stepIndex;
this.manageStep();
},
manageStep: function n_manageStep() {
var self = this;
// If we moved forward in FTU, post iac message about progress.
if (self.currentStepIndex > self.previousStepIndex) {
self.postStepMessage(self.previousStepIndex);
}
//SV - We need remember if phone startup with SIM
if (self.currentStepIndex >= self.stepCount) {
OperatorVariant.setSIMOnFirstBootState();
}
// Reset skip screen flags at beginning of navigation
if (self.currentStepIndex === 0) {
self.skipMobileDataScreen = false;
}
// Retrieve future location
var futureLocation = this.stepAt(self.currentStepIndex);
// If SIMcard is mandatory and no SIM, go to message window
if (this.simMandatory &&
!IccHelper.cardState &&
futureLocation.requireSIM) {
//Send to SIM Mandatory message
futureLocation.hash = '#SIM_mandatory';
futureLocation.requireSIM = false;
futureLocation.onlyBackward = true;
}
// Navigation bar management
if (this.stepAt(this.currentStepIndex).onlyForward) {
UIManager.navBar.classList.add('forward-only');
} else {
UIManager.navBar.classList.remove('forward-only');
}
var nextButton = document.getElementById('forward');
if (this.stepAt(this.currentStepIndex).onlyBackward) {
nextButton.setAttribute('disabled', 'disabled');
} else {
nextButton.removeAttribute('disabled');
}
// Substitute button content on last step
if (this.currentStepIndex === this.stepCount) {
nextButton.setAttribute('data-l10n-id', 'done');
} else {
nextButton.setAttribute('data-l10n-id', 'navbar-next');
}
if (futureLocation.hash === '#firefox_accounts') {
nextButton.setAttribute('data-l10n-id', 'skip');
}
// Change hash to the right location
window.location.hash = futureLocation.hash;
// SIM card management
if (futureLocation.requireSIM) {
var check_cardState = function(response) {
if (!response || (!SimManager.available() &&
// Don't skip it if next step is data 3g
futureLocation.hash !== '#data_3g')) {
self.skipStep();
if (self.currentStepIndex > self.previousStepIndex) {
self.skipMobileDataScreen = true;
}
}
};
// if we are navigating backwards, we do not want to
// show the SIM unlock screens for the data_3g step
var skipUnlockScreens = this.currentStepIndex < this.previousStepIndex;
SimManager.handleCardState(check_cardState, skipUnlockScreens);
}
// Only show the Date & Time screen if we couldn't determine the
// timezone from the network. (We assume that if we can
// determine the timezone, we can determine the time too.)
if (this.stepAt(this.currentStepIndex).hash === '#date_and_time') {
if (!self.tzInitialized) {
self.skipDateTimeScreen = !UIManager.timeZoneNeedsConfirmation;
}
if (self.skipDateTimeScreen) {
self.postStepMessage(self.currentStepIndex);
if(navigator.onLine) {
//if you are online you can get a more accurate guess for the time
//time you just need to trigger it
UIManager.updateSetting(
'time.timezone.automatic-update.enabled',
true
);
UIManager.updateSetting(
'time.clock.automatic-update.enabled',
true
);
}
self.skipStep();
}
}
// if we are not connected we should not try fxa
if ((futureLocation.hash === '#firefox_accounts' &&
!navigator.onLine) ||
(futureLocation.hash === '#firefox_accounts' &&
UIManager.skipFxA)) {
self.postStepMessage(self.currentStepIndex);
self.skipStep();
}
}
};
})(window);

View file

@ -0,0 +1,85 @@
/* global IccHelper */
/* exported OperatorVariant */
'use strict';
var OperatorVariant = (function() {
var NO_SIM = '000-000';
/**
* If ftu.simPresentOnFirstBoot setting has value do nothing otherwise
* set ftu.simPresentOnFirstBoot = true if
* IccHelper.cardState value is:
* 'ready'.
* otherwise set to false.
* 'pinRequired',
* 'pukRequired',
* 'networkLocked',
* 'corporateLocked',
* 'serviceProviderLocked',
* 'network1Locked',
* 'network2Locked',
* 'hrpdNetworkLocked',
* 'ruimCorporateLocked',
* 'ruimServiceProviderLocked'
*/
function setSIMOnFirstBootState() {
var cardValue;
function normalizeCode(aCode) {
var ncode = '' + aCode;
while (ncode.length < 3) {
ncode = '0' + ncode;
}
return ncode;
}
try {
if (!IccHelper || !IccHelper.cardState) {
cardValue = NO_SIM;
} else {
var mcc = IccHelper.iccInfo.mcc;
var mnc = IccHelper.iccInfo.mnc;
if ((mcc !== undefined) && (mcc !== null) &&
(mnc !== undefined) && (mnc !== null)) {
cardValue = normalizeCode(mcc) + '-' + normalizeCode(mnc);
} else {
cardValue = NO_SIM;
}
}
var settings = navigator.mozSettings;
if (!settings) {
console.log('Settings is not available');
return;
}
var req = settings.createLock().get('ftu.simPresentOnFirstBoot');
req.onsuccess = function ov_onsuccess() {
var currentStatus = req.result['ftu.simPresentOnFirstBoot'];
if (currentStatus === undefined || currentStatus === null) {
var result = navigator.mozSettings.createLock().set(
{ 'ftu.simPresentOnFirstBoot' : cardValue });
result.onerror = function ov_error() {
console.error('An error occurre setting ' +
'ftu.simPresentOnFirstBoot: ' + cardValue);
};
}
cardValue = null;
};
req.onerror = function ov_error() {
console.error('Error retrieving ftu.simPresentOnFirstBoot');
};
} catch (e) {
console.error('Error setSIMOnFirstBootState. ' + e);
}
}
return {
setSIMOnFirstBootState: setSIMOnFirstBootState
};
})();

View file

@ -0,0 +1,95 @@
/* global utils */
/* exported ServicesLauncher */
'use strict';
var FTU_APP_ORIGIN = document.location.protocol + '//' +
document.location.host;
var ServicesLauncher = (function(document) {
var extensionFrame = document.querySelector('#iframe_extensions');
var oauthFrame = document.querySelector('#iframe_oauth');
var BASE_IMPORT = '/shared/pages/import/import.html?ftu=1';
function getServiceURI(service) {
return BASE_IMPORT + '&service=' + service;
}
var access_token, currentURI, currentService;
function open() {
extensionFrame.className = 'opening';
}
function load(targetService) {
currentService = targetService;
currentURI = getServiceURI(targetService);
window.addEventListener('message', messageHandler);
oauthFrame.contentWindow.postMessage({
type: 'start',
data: {
from: 'friends',
service: targetService
}
}, FTU_APP_ORIGIN);
}
function unload() {
window.removeEventListener('message', messageHandler);
extensionFrame.src = null;
}
function close(messageId) {
extensionFrame.addEventListener('transitionend', function tclose() {
extensionFrame.removeEventListener('transitionend', tclose);
extensionFrame.src = null;
if (messageId) {
utils.status.show(messageId);
}
});
extensionFrame.className = 'closing';
window.removeEventListener('message', messageHandler);
}
// This function can also be executed when other messages arrive
// That's why we cannot call notifySettings outside the switch block
function messageHandler(e) {
if (e.origin !== FTU_APP_ORIGIN) {
return;
}
var data = e.data;
switch (data.type) {
case 'ready':
open();
break;
case 'abort':
unload();
break;
case 'window_close':
close(data.messageId);
break;
case 'authenticated':
extensionFrame.src = currentURI;
access_token = data.data;
break;
case 'messaging_ready':
extensionFrame.contentWindow.postMessage({
type: 'token',
data: access_token
}, FTU_APP_ORIGIN);
break;
}
}
return {
start: load
};
})(document);

840
gaia/apps/ftu/js/sim_manager.js Executable file
View file

@ -0,0 +1,840 @@
/* global ConfirmDialog,
MobileOperator,
Navigation,
SimContactsImporter,
UIManager,
utils */
'use strict';
var SimManager = (function() {
/*
* Icc helper object to track FTE related SIM state
*/
function Icc(mozIcc) {
this.mozIcc = mozIcc;
}
Icc.prototype = {
skipped: false,
unlocked: false,
alreadyImported: false,
mozIcc: null,
isLocked: function() {
return !this.unlocked && lockStates.indexOf(this.mozIcc.cardState) !== -1;
}
};
/*
* Update ui element with a retry count message for the given sim
*/
function showRetryCount(icc, lockType, uiElement) {
var request = icc.mozIcc.getCardLockRetryCount(lockType);
request.onsuccess = function() {
var retryCount = request.result.retryCount;
if (retryCount) {
var l10nArgs = {n: retryCount};
document.l10n.setAttributes(uiElement,'inputCodeRetriesLeft',
l10nArgs);
uiElement.classList.remove('hidden');
}
};
request.onerror = function() {
console.error('Could not fetch CardLockRetryCount', request.error.name);
};
}
// mozIcc.cardState values for a locked SIM
var lockStates = ['pinRequired', 'pukRequired', 'networkLocked',
'corporateLocked', 'serviceProviderLocked', 'network1Locked',
'network2Locked', 'hrpdNetworkLocked', 'ruimCorporateLocked',
'ruimServiceProviderLocked'];
return {
icc0: null,
icc1: null,
simSlots: window.navigator.mozMobileConnections ?
window.navigator.mozMobileConnections.length : 0,
// track the SIM that is currently being unlocked
_unlockingIcc: null,
init: function sm_init() {
this.mobConn = window.navigator.mozMobileConnections;
if (!this.mobConn) {
return;
}
this.iccManager = window.navigator.mozIccManager;
if (!this.iccManager) {
return;
}
// keep track of sim card info in slot 0 and 1
this.icc0 = null;
this.icc1 = null;
if (this.iccManager.iccIds[0]) {
this.updateIccState(this.iccManager.iccIds[0]);
}
if (this.iccManager.iccIds[1]) {
this.updateIccState(this.iccManager.iccIds[1]);
}
this.iccManager.addEventListener('iccdetected',
this.handleIccState.bind(this));
this.alreadyImported = false;
},
handleUnlockError: function sm_handleUnlockError(lockType, retryCount) {
var l10nArgs = {n: retryCount};
switch (lockType) {
case 'pin':
if (retryCount === 0) {
this.showPukScreen(this._unlockingIcc);
break;
}
UIManager.pinInput.value = '';
UIManager.pinInput.classList.add('onerror');
UIManager.pinError.classList.remove('hidden');
document.l10n.setAttributes(UIManager.pinRetriesLeft,
'inputCodeRetriesLeft', l10nArgs);
UIManager.pinRetriesLeft.classList.remove('hidden');
if (retryCount === 1) {
UIManager.pinError.
querySelector('.lastchance').classList.remove('hidden');
} else {
document.l10n.setAttributes(
UIManager.pinError.querySelector('.main'),
'pinAttemptMsg2',
l10nArgs
);
UIManager.pinError.querySelector('.main').classList.remove('hidden');
}
break;
case 'puk':
UIManager.pukInput.value = '';
UIManager.pukInput.classList.add('onerror');
UIManager.pukError.classList.remove('hidden');
UIManager.pukInfo.classList.add('hidden');
document.l10n.setAttributes(UIManager.pukRetriesLeft,
'inputCodeRetriesLeft', l10nArgs);
UIManager.pukRetriesLeft.classList.remove('hidden');
if (retryCount === 1) {
UIManager.pukError.
querySelector('.lastchance').classList.remove('hidden');
} else {
document.l10n.setAttributes(
UIManager.pukError.querySelector('.main'),
'pukAttemptMsg2',
l10nArgs
);
UIManager.pukError.querySelector('.main').classList.remove('hidden');
}
// TODO what if counter gets to 0 ??
break;
case 'nck':
case 'cck':
case 'spck':
UIManager.xckInput.value = '';
UIManager.xckInput.classList.add('onerror');
UIManager.xckError.classList.remove('hidden');
document.l10n.setAttributes(UIManager.xckRetriesLeft,
'inputCodeRetriesLeft', l10nArgs);
UIManager.xckRetriesLeft.classList.remove('hidden');
if (retryCount == 1) {
UIManager.xckError.
querySelector('.lastchance').classList.remove('hidden');
} else {
document.l10n.setAttributes(
UIManager.xckError.querySelector('.main'),
'nckAttemptMsg2',
l10nArgs
);
UIManager.xckError.querySelector('.main').classList.remove('hidden');
}
break;
}
},
available: function sm_available() {
var icc = this.guessIcc();
return (icc && icc.cardState === 'ready');
},
handleIccState: function sm_handleIccState(event) {
this.updateIccState(event.iccId);
},
updateIccState: function(iccId) {
var iccInfo = this.iccManager.getIccById(iccId);
if (!iccInfo) {
throw new Error('Unrecognized iccID: ' + iccId);
}
// determine SIM slot number
if (this.mobConn[0] && iccId === this.mobConn[0].iccId) {
this.icc0 = new Icc(iccInfo);
} else if (this.mobConn[1] && iccId === this.mobConn[1].iccId) {
this.icc1 = new Icc(iccInfo);
} else {
console.warn('ICC detected in unsupported slot', iccId);
}
},
/*
* Possible values:
* null,
* 'unknown',
* 'pinRequired',
* 'pukRequired',
* 'networkLocked',
* 'corporateLocked',
* 'serviceProviderLocked',
* 'network1Locked',
* 'network2Locked',
* 'hrpdNetworkLocked',
* 'ruimCorporateLocked',
* 'ruimServiceProviderLocked'
* 'ready'.
*/
handleCardState: function sm_handleCardState(callback, skipUnlockScreen) {
// used to track which SIM's PIN unlock
// screen we are currently displaying
this._unlockingIcc = null;
SimManager.checkSIMButton();
if (typeof callback === 'function') {
this.finishCallback = callback;
}
if (this.shouldShowUnlockScreen(this.icc0) && !skipUnlockScreen) {
this.showUnlockScreen(this.icc0);
} else if (this.shouldShowUnlockScreen(this.icc1) && !skipUnlockScreen) {
this.showUnlockScreen(this.icc1);
} else if (this.shouldShowSIMInfoScreen()) {
// reset skipped states so if we navigate back
// we will redisplay the SIM unlock screens
this.resetSkipped();
this.showSIMInfoScreen();
} else {
this.resetSkipped();
this.finish();
}
},
finish: function() {
this.hideScreen();
this.hideSIMInfoScreen();
// card state has been handled, so return
// to normal navigation, and only show cell
// data step if we have an unlocked sim
var showCellData = (this.icc0 && !this.icc0.isLocked()) ||
(this.icc1 && !this.icc1.isLocked());
this.finishCallback && this.finishCallback(showCellData);
},
shouldShowUnlockScreen: function sm_shouldShowLockScreen(icc) {
return icc && !icc.skipped && icc.isLocked();
},
// only show sim info screen if we have two SIMs inserted
shouldShowSIMInfoScreen: function sm_shouldShowSIMInfoScreen() {
return (this.icc0 && this.icc1);
},
showUnlockScreen: function sm_showUnlockScreen(icc) {
if (icc.unlocked) {
return;
}
this._unlockingIcc = icc;
switch (icc.mozIcc.cardState) {
case 'pinRequired':
this.showPinScreen(icc);
break;
case 'pukRequired':
this.showPukScreen(icc);
break;
case 'networkLocked':
case 'corporateLocked':
case 'serviceProviderLocked':
case 'network1Locked':
case 'network2Locked':
case 'hrpdNetworkLocked':
case 'ruimCorporateLocked':
case 'ruimServiceProviderLocked':
this.showXckScreen(icc);
break;
default:
throw new Error('Cannot show SIM unlock screen, unknown cardState ' +
icc.mozIcc.cardState);
}
},
/**
* A container of event listeners to avoid listening several times to the same
* event/object pair.
*/
voiceChangeListeners: [],
updateSIMInfoText: function sm_updateSIMInfoText(icc) {
var iccNumber = (icc === this.icc0) ? 1 : 2;
if (icc && icc.isLocked()) {
UIManager['simInfo' + iccNumber].classList.add('locked');
UIManager['simCarrier' + iccNumber].setAttribute('data-l10n-id',
'simPinLocked');
UIManager['simNumber' + iccNumber].removeAttribute('data-l10n-id');
UIManager['simNumber' + iccNumber].textContent = '';
} else {
UIManager['simInfo' + iccNumber].classList.remove('locked');
var mobConn = this.mobConn[iccNumber - 1];
var operator = MobileOperator
.userFacingInfo(mobConn)
.operator;
if (!operator && !this.voiceChangeListeners[iccNumber - 1]) {
// The operator is not yet populated, let's listen to the `voicechange`
// event once and update the UI later.
this.voiceChangeListeners[iccNumber - 1] = function(evt) {
var operator = MobileOperator
.userFacingInfo(mobConn)
.operator;
if (operator) {
// If we have an operator, we update the UI and stop listening.
UIManager['simCarrier' + iccNumber].removeAttribute('data-l10n-id');
UIManager['simCarrier' + iccNumber].textContent = operator;
mobConn.removeEventListener(
'voicechange', this.voiceChangeListeners[iccNumber - 1]);
this.voiceChangeListeners[iccNumber - 1] = null;
}
}.bind(this);
mobConn.addEventListener(
'voicechange', this.voiceChangeListeners[iccNumber - 1]);
}
if (operator) {
UIManager['simCarrier' + iccNumber].removeAttribute('data-l10n-id');
UIManager['simCarrier' + iccNumber].textContent = operator;
} else {
UIManager['simCarrier' + iccNumber].setAttribute(
'data-l10n-id', 'searchingOperator'
);
}
var number = icc.mozIcc.iccInfo.msisdn ||
icc.mozIcc.iccInfo.mdn || '';
if (number) {
UIManager['simNumber' + iccNumber].textContent = number;
UIManager['simInfo' + iccNumber].classList.remove('no-number');
} else {
UIManager['simInfo' + iccNumber].classList.add('no-number');
}
}
},
showSIMInfoScreen: function sm_showSIMInfoScreen() {
this.updateSIMInfoText(this.icc0);
this.updateSIMInfoText(this.icc1);
UIManager.activationScreen.classList.remove('show');
UIManager.simInfoScreen.classList.add('show');
},
hideSIMInfoScreen: function sm_hidescreen() {
UIManager.simInfoScreen.classList.remove('show');
UIManager.activationScreen.classList.add('show');
},
checkSIMButton: function sm_checkSIMButton() {
if (!this.mobConn) {
UIManager.simImport.classList.add('hidden');
return;
}
var simOption = UIManager.simImportButton;
// If there is an unlocked SIM we activate import from SIM
if (!SimManager.alreadyImported && SimManager.available()) {
simOption.removeAttribute('disabled');
UIManager.noSim.classList.add('hidden');
} else {
simOption.setAttribute('disabled', 'disabled');
if (!SimManager.alreadyImported) {
UIManager.noSim.classList.remove('hidden');
}
}
},
showPinScreen: function sm_showPinScreen(icc) {
showRetryCount(icc, 'pin', UIManager.pinRetriesLeft);
// Button management
UIManager.unlockSimButton.disabled = true;
UIManager.pinInput.addEventListener('input', function sm_checkInput(event) {
UIManager.unlockSimButton.disabled = (event.target.value.length < 4);
});
// Screen management
UIManager.activationScreen.classList.remove('show');
UIManager.unlockSimScreen.classList.add('show');
UIManager.pincodeScreen.classList.add('show');
UIManager.xckcodeScreen.classList.remove('show');
UIManager.unlockSimHeader.setAttribute('data-l10n-id', 'pincode2');
if (this.simSlots > 1) {
var simNumber = icc === this.icc0 ? 1 : 2;
document.l10n.setAttributes(UIManager.pinLabel,
'pincodeLabel', {n: simNumber});
} else {
UIManager.pinLabel.setAttribute('data-l10n-id', 'type_pin');
}
UIManager.pinInput.focus();
},
showPukScreen: function sm_showPukScreen(icc) {
showRetryCount(icc, 'puk', UIManager.pukRetriesLeft);
UIManager.unlockSimScreen.classList.add('show');
UIManager.activationScreen.classList.remove('show');
UIManager.pincodeScreen.classList.remove('show');
UIManager.pukcodeScreen.classList.add('show');
UIManager.xckcodeScreen.classList.remove('show');
UIManager.unlockSimHeader.setAttribute('data-l10n-id', 'pukcode');
if (this.simSlots > 1) {
var simNumber = icc === this.icc0 ? 1 : 2;
document.l10n.setAttributes(UIManager.pukLabel,
'pukcodeLabel', {n: simNumber});
} else {
UIManager.pukLabel.setAttribute('data-l10n-id', 'type_puk');
}
UIManager.pukInput.focus();
},
showXckScreen: function sm_showXckScreen(icc) {
var lockType;
switch (icc.mozIcc.cardState) {
case 'networkLocked':
lockType = 'nck';
break;
case 'corporateLocked':
lockType = 'cck';
break;
case 'serviceProviderLocked':
lockType = 'spck';
break;
case 'network1Locked':
lockType = 'nck1';
break;
case 'network2Locked':
lockType = 'nck2';
break;
case 'hrpdNetworkLocked':
lockType = 'hnck';
break;
case 'ruimCorporateLocked':
lockType = 'rcck';
break;
case 'ruimServiceProviderLocked':
lockType = 'rspck';
break;
default:
return; // We shouldn't be here.
}
showRetryCount(icc, lockType, UIManager.xckRetriesLeft);
UIManager.unlockSimScreen.classList.add('show');
UIManager.activationScreen.classList.remove('show');
UIManager.pincodeScreen.classList.remove('show');
UIManager.pukcodeScreen.classList.remove('show');
UIManager.xckcodeScreen.classList.add('show');
var simNumber = icc === this.icc0 ? 1 : 2;
switch (icc.mozIcc.cardState) {
case 'networkLocked':
document.l10n.setAttributes(UIManager.unlockSimHeader,
'nckcodeTitle', {n: simNumber});
document.l10n.setAttributes(UIManager.xckLabel, 'nckcodeLabel',
{n: simNumber});
break;
case 'corporateLocked':
document.l10n.setAttributes(UIManager.unlockSimHeader,
'cckcodeTitle', {n: simNumber});
document.l10n.setAttributes(UIManager.xckLabel, 'cckcodeLabel',
{n: simNumber});
break;
case 'serviceProviderLocked':
document.l10n.setAttributes(UIManager.unlockSimHeader,
'spckcodeTitle', {n: simNumber});
document.l10n.setAttributes(UIManager.xckLabel, 'spckcodeLabel',
{n: simNumber});
break;
case 'network1Locked':
document.l10n.setAttributes(UIManager.unlockSimHeader,
'nck1codeTitle', {n: simNumber});
document.l10n.setAttributes(UIManager.xckLabel, 'nck1codeLabel',
{n: simNumber});
break;
case 'network2Locked':
document.l10n.setAttributes(UIManager.unlockSimHeader,
'nck2codeTitle', {n: simNumber});
document.l10n.setAttributes(UIManager.xckLabel, 'nck2codeLabel',
{n: simNumber});
break;
case 'hrpdNetworkLocked':
document.l10n.setAttributes(UIManager.unlockSimHeader,
'hnckcodeTitle', {n: simNumber});
document.l10n.setAttributes(UIManager.xckLabel, 'hnckcodeLabel',
{n: simNumber});
break;
case 'ruimCorporateLocked':
document.l10n.setAttributes(UIManager.unlockSimHeader,
'rcckcodeTitle', {n: simNumber});
document.l10n.setAttributes(UIManager.xckLabel, 'rcckcodeLabel',
{n: simNumber});
break;
case 'ruimServiceProviderLocked':
document.l10n.setAttributes(UIManager.unlockSimHeader,
'rspckcodeTitle', {n: simNumber});
document.l10n.setAttributes(UIManager.xckLabel, 'rspckcodeLabel',
{n: simNumber});
break;
}
UIManager.xckInput.focus();
},
resetForm: function sm_clearInputs() {
this.clearFields();
UIManager.newpinInput.value = '';
UIManager.confirmNewpinInput.value = '';
UIManager.pinInput.value = '';
UIManager.pukInput.value = '';
UIManager.pukInfo.classList.remove('hidden');
UIManager.xckInput.value = '';
UIManager.unlockSimButton.disabled = false;
},
hideScreen: function sm_hideScreen() {
UIManager.unlockSimScreen.classList.remove('show');
UIManager.pincodeScreen.classList.remove('show');
UIManager.pukcodeScreen.classList.remove('show');
UIManager.xckcodeScreen.classList.remove('show');
UIManager.activationScreen.classList.add('show');
},
skip: function sm_skip() {
if (this._unlockingIcc) {
this._unlockingIcc.skipped = true;
}
this.resetForm();
this.hideScreen();
this.handleCardState();
},
resetSkipped: function sm_resetSkipped() {
if (this.icc0) {
this.icc0.skipped = false;
}
if (this.icc1) {
this.icc1.skipped = false;
}
},
back: function sm_back() {
this.resetForm();
this.resetSkipped();
this.hideScreen();
this.hideSIMInfoScreen();
Navigation.back();
},
simUnlockBack: function sm_simUnlockBack() {
if (this.icc0 && this.icc0.skipped) {
this.icc0.skipped = false;
this.hideScreen();
this.handleCardState();
} else {
this.back();
}
},
unlock: function sm_unlock() {
var icc = this._unlockingIcc;
if (!icc) {
throw new Error('Cannot unlock SIM, no current ICC');
}
this.clearFields();
switch (icc.mozIcc.cardState) {
case 'pinRequired':
this.unlockPin(icc);
break;
case 'pukRequired':
this.unlockPuk(icc);
break;
case 'networkLocked':
case 'corporateLocked':
case 'serviceProviderLocked':
case 'network1Locked':
case 'network2Locked':
case 'hrpdNetworkLocked':
case 'ruimCorporateLocked':
case 'ruimServiceProviderLocked':
this.unlockXck(icc);
break;
}
},
unlockPin: function sm_unlockPin(icc) {
var pin = UIManager.pinInput.value;
if (pin.length < 4 || pin.length > 8) {
UIManager.pinError.
querySelector('.main').setAttribute('data-l10n-id', 'pinValidation');
UIManager.pinInput.classList.add('onerror');
UIManager.pinError.classList.remove('hidden');
UIManager.pinInput.focus();
return;
} else {
UIManager.pinInput.classList.remove('onerror');
UIManager.pinError.classList.add('hidden');
}
// Unlock SIM
this.attemptUnlock(icc, {lockType: 'pin', pin: pin });
},
clearFields: function sm_clearFields() {
UIManager.pukInput.classList.remove('onerror');
UIManager.pukError.classList.add('hidden');
UIManager.newpinInput.classList.remove('onerror');
UIManager.newpinError.classList.add('hidden');
UIManager.confirmNewpinInput.classList.remove('onerror');
UIManager.confirmNewpinError.classList.add('hidden');
UIManager.pinError.classList.add('hidden');
UIManager.pinError.querySelector('.main').classList.add('hidden');
UIManager.pinError.querySelector('.lastchance').classList.add('hidden');
UIManager.pinRetriesLeft.classList.add('hidden');
UIManager.pukError.classList.add('hidden');
UIManager.pukError.querySelector('.main').classList.add('hidden');
UIManager.pukError.querySelector('.lastchance').classList.add('hidden');
UIManager.pukRetriesLeft.classList.add('hidden');
UIManager.xckError.classList.add('hidden');
UIManager.xckError.querySelector('.main').classList.add('hidden');
UIManager.xckError.querySelector('.lastchance').classList.add('hidden');
UIManager.xckRetriesLeft.classList.add('hidden');
},
unlockPuk: function sm_unlockPuk(icc) {
var pukCode = UIManager.pukInput.value;
if (pukCode.length !== 8) {
UIManager.pukError.querySelector('.main')
.setAttribute('data-l10n-id', 'pukValidation');
UIManager.pukError.classList.remove('hidden');
UIManager.pukInfo.classList.add('hidden');
UIManager.pukInput.classList.add('onerror');
UIManager.pukError.focus();
return;
}
var newpinCode = UIManager.newpinInput.value;
var confirmNewpin = UIManager.confirmNewpinInput.value;
if (newpinCode.length < 4 || newpinCode.length > 8) {
UIManager.newpinError.setAttribute('data-l10n-id', 'pinValidation');
UIManager.newpinError.classList.remove('hidden');
UIManager.newpinInput.classList.add('onerror');
UIManager.newpinError.focus();
return;
}
if (newpinCode != confirmNewpin) {
UIManager.confirmNewpinError.setAttribute('data-l10n-id',
'newpinConfirmation');
UIManager.confirmNewpinError.classList.remove('hidden');
UIManager.newpinInput.classList.add('onerror');
UIManager.confirmNewpinInput.classList.add('onerror');
UIManager.confirmNewpinError.focus();
return;
}
// Unlock SIM with PUK and new PIN
var options = {lockType: 'puk', puk: pukCode, newPin: newpinCode };
this.attemptUnlock(icc, options);
},
unlockXck: function sm_unlockXck(icc) {
var xck = UIManager.xckInput.value;
var lockType;
switch (icc.mozIcc.cardState) {
case 'networkLocked':
lockType = 'nck';
break;
case 'corporateLocked':
lockType = 'cck';
break;
case 'serviceProviderLocked':
lockType = 'spck';
break;
case 'network1Locked':
lockType = 'nck1';
break;
case 'network2Locked':
lockType = 'nck2';
break;
case 'hrpdNetworkLocked':
lockType = 'hnck';
break;
case 'ruimCorporateLocked':
lockType = 'rcck';
break;
case 'ruimServiceProviderLocked':
lockType = 'rspck';
break;
}
if (xck.length < 8 || xck.length > 16) {
UIManager.xckInput.classList.add('onerror');
UIManager.xckError.classList.remove('hidden');
UIManager.xckError.querySelector('.main').
setAttribute('data-l10n-id', lockType + 'Validation');
UIManager.xckInput.focus();
return;
} else {
UIManager.pinInput.classList.remove('onerror');
UIManager.pinError.classList.add('hidden');
}
// Unlock SIM
var options = {lockType: lockType, pin: xck };
this.attemptUnlock(icc, options);
},
attemptUnlock: function sm_attemptUnlock(icc, options) {
var req = icc.mozIcc.unlockCardLock(options);
req.onsuccess = (function sm_unlockSuccess() {
icc.unlocked = true;
this.resetForm();
this.hideScreen();
this.handleCardState();
}).bind(this);
req.onerror = (function sm_unlockError() {
this.handleUnlockError(options.lockType, req.error.retryCount);
}).bind(this);
},
// Try to infer whats the default SIM
guessIcc: function guessIcc() {
var tempIcc = null;
if (navigator.mozMobileConnections) {
// New multi-sim api, use mozMobileConnections to guess
// the first inserted sim
for (var i = 0;
i < navigator.mozMobileConnections.length && tempIcc === null; i++) {
if (navigator.mozMobileConnections[i] !== null &&
navigator.mozMobileConnections[i].iccId !== null) {
tempIcc = navigator.mozIccManager.getIccById(
navigator.mozMobileConnections[i].iccId);
}
}
} else {
tempIcc = navigator.mozIccManager;
}
return tempIcc;
},
importContacts: function sm_importContacts() {
// Delay for showing feedback to the user after importing
var DELAY_FEEDBACK = 300;
UIManager.navBar.setAttribute('aria-disabled', 'true');
var progress = utils.overlay.show('simContacts-reading',
'activityBar');
var importButton = UIManager.simImportButton;
var cancelled = false,
contactsRead = false;
var importer = new SimContactsImporter(SimManager.guessIcc());
utils.overlay.showMenu();
utils.overlay.oncancel = function oncancel() {
cancelled = true;
importer.finish();
if (contactsRead) {
// A message about canceling will be displayed while the current chunk
// is being cooked
progress.setClass('activityBar');
utils.overlay.hideMenu();
progress.setHeaderMsg('messageCanceling');
} else {
importer.onfinish(); // Early return while reading contacts
}
};
var importedContacts = 0;
importer.onread = function sim_import_read(n) {
contactsRead = true;
if (n > 0) {
progress.setClass('progressBar');
progress.setHeaderMsg('simContacts-importing');
progress.setTotal(n);
}
};
importer.onimported = function imported_contact() {
importedContacts++;
if (!cancelled) {
progress.update();
}
};
importer.onfinish = function sim_import_finish(numDupsMerged, iccId) {
window.setTimeout(function do_sim_import_finish() {
UIManager.navBar.removeAttribute('aria-disabled');
utils.overlay.hide();
if (importedContacts > 0) {
utils.misc.setTimestamp('sim-' + iccId);
}
if (!cancelled) {
SimManager.alreadyImported = true;
importButton.setAttribute('disabled', 'disabled');
utils.status.show({
id: 'simContacts-imported3',
args: {n: importedContacts}
});
}
}, DELAY_FEEDBACK);
importer.onfinish = null;
};
importer.onerror = function sim_import_error() {
UIManager.navBar.removeAttribute('aria-disabled');
utils.overlay.hide();
// Just in case the user decides to do so later
importButton.removeAttribute('disabled');
// Showing error message allowing user to retry
var cancel = {
title: 'cancel',
callback: function() {
ConfirmDialog.hide();
}
};
var retry = {
title: 'retry',
isRecommend: true,
callback: function() {
ConfirmDialog.hide();
// And now the action is reproduced one more time
importButton.click();
}
};
ConfirmDialog.show(null, 'simContacts-error', cancel, retry);
}; // importer.onerror
importer.start();
}
};
})();

35
gaia/apps/ftu/js/time.js Executable file
View file

@ -0,0 +1,35 @@
/* exported TimeManager */
'use strict';
var TimeManager = {
elementIDs: [
'tz-region',
'tz-city',
'date-configuration',
'time-configuration'
],
init: function tm_init() {
if (window.navigator.mozTime) {
this.time = window.navigator.mozTime;
} else {
console.log('There is no mozTime available in window');
}
this.elementIDs.forEach(function(elementID) {
document.getElementById(elementID + '-button').addEventListener('click',
function(evt) {
evt.preventDefault();
var input = document.getElementById(elementID);
// Inspired by app/clock/js/form_button.js
// It seems like setting focus instantly does not trigger the input/
// select dialog.
setTimeout(input.focus.bind(input), 10);
});
});
},
set: function tm_set(date) {
this.time.set(date);
}
};

383
gaia/apps/ftu/js/tutorial.js Executable file
View file

@ -0,0 +1,383 @@
/* global ScreenLayout, Promise,
Utils, FinishScreen, LazyLoader, TutorialUtils */
/* exported Tutorial */
(function(exports) {
'use strict';
// default layout
var currentLayout = 'tiny';
// Most used DOM elements
var dom = {};
/**
* Manages and controls the configuration, content and state of the tutorial
* @module Tutorial
*/
var elementIDs = [
'tutorial',
'tutorial-step-title',
'tutorial-step-media',
'tutorial-step-image',
'tutorial-step-video',
'forward-tutorial',
'back-tutorial'
];
var Tutorial = {
// A configuration object.
config: null,
// Config for the current tutorial steps
_stepsConfig: {},
// Which tutorial steps to use
_stepsKey: '',
// Keeps track of the current step
_currentStep: 1,
// Track initialized state of tutorial
_initialized: false,
/**
* Initialize the tutorial. This is async as a config file must be loaded.
* A Sequence (array of sync or async functions) is used to manage the init
* tasks.
* When complete, the tutorial is ready to be shown via the 'start' method
*
* @param {String} stepsKey a key into the tutorial config object, i
* i.e. a version or version delta like 1.3..2.0
* @param {Function} onLoaded optional callback for when init is complete
* @memberof Tutorial
*/
init: function(stepsKey, onLoaded) {
// init is async
// need to load config, then load the first step and its assets.
if (this._initialized || this._initialization) {
// init already underway
return;
}
var initTasks = this._initialization = new TutorialUtils.Sequence(
// config should load or already be loaded.
// failure should abort
this.loadConfig.bind(this),
this._initWithConfig.bind(this, stepsKey)
);
initTasks.onabort = this._onAbortInitialization.bind(this);
initTasks.oncomplete =
this._onCompleteInitialization.bind(this, onLoaded);
// first time here: cache DOM elements
elementIDs.forEach(function(name) {
dom[Utils.camelCase(name)] = document.getElementById(name);
if (!dom[Utils.camelCase(name)]) {
console.error('Cache DOM elements: couldnt cache: ' + name);
}
}, this);
initTasks.next();
// watch a ton of video events as context for the tutorial
// media loading/playing
var mediaEvents = this._mediaEvents = ['abort',
'canplay',
'canplaythrough',
'durationchange',
'emptied',
'ended',
'error',
'interruptbegin',
'interruptend',
'loadeddata',
'loadedmetadata',
'loadstart',
'mozaudioavailable',
'pause',
'play',
'playing',
'stalled',
'suspend',
'waiting'];
this._debugEventHandler = {
handleEvent: function(evt) {
var errCodes = {
'1': 'MEDIA_ERR_ABORTED',
'2': 'MEDIA_ERR_NETWORK',
'3': 'MEDIA_ERR_DECODE',
'4': 'MEDIA_ERR_SRC_NOT_SUPPORTED'
};
console.log('TUTORIAL: media-event: '+ evt.type);
if (evt.type == 'error') {
var errtype = errCodes[evt.target.error.code] || 'unknown';
console.log('TUTORIAL: media-event, ' + errtype + ' error');
}
}
};
mediaEvents.forEach(name => {
dom.tutorialStepVideo.addEventListener(name, this._debugEventHandler);
});
},
/**
* Show the tutorial and play the first step
* May be called during or after the init process
* We defer rasing the 'tutorialinitialized' event until this point
* as it signals the tutorial step content is loaded and displayed
*
* @param {Function} onReady optional callback for when start is complete
* @memberof Tutorial
*/
start: function(onReady) {
var sequence;
var initInProgress = false;
if (this._initialization) {
// init still underway, tack steps onto existing sequence
sequence = this._initialization;
initInProgress = true;
} else {
// init already complete, create new sequence
this._initialization = sequence = new TutorialUtils.Sequence();
sequence.onabort = this._onAbortInitialization.bind(this);
sequence.oncomplete =
this._onCompleteInitialization.bind(this);
}
sequence.push(function setInitialStep() {
// setStep should return promise given by _loadMedia
console.log('TUTORIAL: setInitialStep');
return this._setStep();
}.bind(this));
sequence.push(function showTutorialAndFinishInit() {
// Show the panel
dom.tutorial.classList.add('show');
// Custom event that can be used to apply (screen reader) visibility
// changes.
console.log('TUTORIAL: dispatching tutorialinitialized');
window.dispatchEvent(new CustomEvent('tutorialinitialized'));
});
if(typeof onReady === 'function') {
sequence.push(onReady);
}
if (!initInProgress) {
// init done, starting start sequence
sequence.next();
}
},
/**
* Continue initialization once config data is loaded
* Called as a part of the init sequence
*
* @param {String} stepsKey a key into the tutorial config object, i
* i.e. a version or version delta like 1.3..2.0
* @memberof Tutorial
*/
_initWithConfig: function(stepsKey) {
stepsKey = stepsKey || 'default';
this._stepsKey = stepsKey;
this._stepsConfig = this.config[stepsKey] || this.config['default'];
// Add event listeners
dom.forwardTutorial.addEventListener('click', this);
dom.backTutorial.addEventListener('click', this);
// Set the first step
this._currentStep = 1;
},
_onAbortInitialization: function() {
this._initialization = null;
},
_onCompleteInitialization: function(onReady) {
this._initialization = null;
this._initialized = true;
if(typeof onReady === 'function') {
onReady();
}
},
/**
* Advance the tutorial to the given step (first step if no value given)
*
* @param {Number} value number of the step to show (1-based)
* @memberof Tutorial
*/
_setStep: function (value) {
// If value is bigger than the max, show finish screen
value = (typeof value === 'number') ? value : this._currentStep;
var stepIndex = value - 1;
if (stepIndex >= this._stepsConfig.steps.length) {
return Promise.resolve().then(function() {
Tutorial.done();
});
}
var stepData = this._stepsConfig.steps[stepIndex];
if (!stepData) {
return Promise.reject('No data for step: ' + value);
}
// Set the step
dom.tutorial.dataset.step = this._currentStep;
// Internationalize
document.l10n.setAttributes(
dom.tutorialStepTitle,
stepData.l10nKey
);
// Update the image/video
var imgElement = dom.tutorialStepImage,
videoElement = dom.tutorialStepVideo;
var stepPromise;
if (stepData.video) {
stepPromise = TutorialUtils.getBestAssetForDirection(stepData.video)
.then((bestSrc) => {
return TutorialUtils.loadAndPlayMedia(videoElement, bestSrc);
});
videoElement.hidden = false;
imgElement.hidden = true;
} else {
imgElement.hidden = false;
stepPromise = TutorialUtils.loadMedia(imgElement, stepData.image);
imgElement.hidden = false;
videoElement.hidden = true;
}
return stepPromise;
},
/**
* DOM Event handler
*
* @param {DOMEvent} evt Event object
* @memberof Tutorial
*/
handleEvent: function(evt) {
if (evt.type === 'click') {
switch(evt.target) {
case dom.forwardTutorial:
this.next(evt);
break;
case dom.backTutorial:
this.back(evt);
break;
}
}
},
/**
* Advance to the next step in the tutorial
* @memberof Tutorial
*/
next: function() {
return this._setStep(++this._currentStep);
},
/**
* Go back to the previous step in the tutorial
* @memberof Tutorial
*/
back: function() {
return this._setStep(--this._currentStep);
},
/**
* Tutorial complete
* @memberof Tutorial
*/
done: function() {
var isUpgrade = this._stepsKey && this._stepsKey !== 'default';
FinishScreen.init(isUpgrade);
dom.tutorial.classList.remove('show');
dom.tutorialStepVideo.removeAttribute('src');
dom.tutorialStepImage.removeAttribute('src');
},
/**
* Load the config with steps and associated resources for the tutorial
* We have different config files for each screen category:
* phone is 'tiny', tablet is 'large'
* and corresponding tiny.json, large.json
*
* @returns {Promise}
* @memberof Tutorial
*/
loadConfig: function() {
if (!this._configPromise) {
// Update the value of the layout if needed
// 'ScreenLayout' give us 4 different values
// tiny: '(max-width: 767px)',
// small: '(min-width: 768px) and (max-width: 991px)',
// medium: '(min-width: 992px) and (max-width: 1200px)',
// large: '(min-width: 1201px)',
//
// Currently we are taking into account only 'tiny', and we are
// going to consider 'tablet' as 'large'. If we want to add more
// or specific features for 'small' & 'medium', we should add more
// logic here.
currentLayout = ScreenLayout.getCurrentLayout() === 'tiny' ?
'tiny' : 'large';
var configUrl = '/config/' + currentLayout + '.json';
this._configPromise = LazyLoader.getJSON(configUrl)
.then(function(json) {
Tutorial.config = json;
return Tutorial.config;
}, function() {
return new Error('Tutorial config failed to load from: ' + configUrl);
});
}
return this._configPromise;
},
/**
* Test helper to reset the tutorial to its pre-initialized state
* to allow init to be called again
* @memberof Tutorial
*/
reset: function() {
var resetPromise = Promise.resolve();
if (dom.tutorialStepVideo) {
this._mediaEvents.forEach(name => {
dom.tutorialStepVideo.removeEventListener(
name,
this._debugEventHandler
);
});
dom.tutorialStepVideo.hidden = true;
if (dom.tutorialStepVideo.src) {
resetPromise = new Promise((resolve, reject) => {
dom.tutorialStepVideo.addEventListener('emptied', () => {
resolve();
});
dom.tutorialStepVideo.removeAttribute('src');
dom.tutorialStepVideo.load();
});
}
}
if (this._initialization) {
this._initialization.abort();
this._initialization = null;
}
this._configPromise = null;
this._currentStep = 1;
this._stepsConfig = this.config = null;
if (this._initialized) {
dom.tutorial.classList.remove('show');
this._initialized = false;
}
document.getElementById('tutorial').classList.remove('show');
return resetPromise;
}
};
exports.Tutorial = Tutorial;
})(this);

View file

@ -0,0 +1,179 @@
(function(exports) {
'use strict';
var TutorialUtils = {
/**
* Helper function to load images and video
* @param {DOMNode} mediaElement video or image to assign new src to
* @param {String} src URL for video/image resource
* @returns {Promise}
*/
loadMedia: function (mediaElement, src) {
console.log('loadMedia, got mediaElement: ', mediaElement);
var isVideo = (mediaElement.nodeName === 'VIDEO');
console.log('TutorialUtils.loadMedia, isVideo: ',
isVideo, mediaElement.nodeName);
return new Promise(function(resolve, reject) {
function onMediaLoadOrError(evt) {
console.log('TutorialUtils.loadMedia, onMediaLoadOrError for ' +
'evt.type: ' + (evt && evt.type));
evt.target.removeEventListener('error', onMediaLoadOrError);
if (isVideo) {
evt.target.removeEventListener('canplay', onMediaLoadOrError);
evt.target.removeEventListener('abort', onMediaLoadOrError);
} else {
evt.target.removeEventListener('load', onMediaLoadOrError);
}
// Dont block progress on failure to load media
if (evt.type === 'error') {
console.error('Failed to load tutorial media: ' + src);
} else if (evt.type === 'abort') {
console.error('Loading of tutorial media aborted: ' + src);
}
resolve(evt);
}
function onVideoUnloaded(evt) {
console.log('TutorialUtils.loadMedia, onVideoUnloaded for evt.type: ' +
(evt && evt.type));
mediaElement.removeEventListener('emptied', onVideoUnloaded);
mediaElement.addEventListener('canplay', onMediaLoadOrError);
mediaElement.addEventListener('abort', onMediaLoadOrError);
mediaElement.addEventListener('error', onMediaLoadOrError);
console.log('TutorialUtils.loadMedia, onVideoUnloaded, ' +
'assigning src: ' + src);
mediaElement.src = src;
mediaElement.load();
}
if (isVideo) {
// must unload video and force load before switching to new source
if (mediaElement.src) {
console.log('TutorialUtils.loadMedia, removing src attribute');
mediaElement.removeAttribute('src');
mediaElement.addEventListener('emptied', onVideoUnloaded, false);
console.log('TutorialUtils.loadMedia, calling load');
mediaElement.load();
} else {
console.log('TutorialUtils.loadMedia, no src, ' +
'just calling onVideoUnloaded');
onVideoUnloaded();
}
} else {
console.log('TutorialUtils.loadMedia, not a video, ' +
'listen for load/error only');
mediaElement.addEventListener('load', onMediaLoadOrError, false);
mediaElement.addEventListener('error', onMediaLoadOrError, false);
mediaElement.src = src;
}
});
},
/**
* Convenience to load and then play video
* @param {DOMNode} mediaElement video or image to assign new src to
* @param {String} src URL for video/image resource
* @returns {Promise}
*/
loadAndPlayMedia: function(mediaElement, src) {
return this.loadMedia(mediaElement, src).then((evt) => {
if (evt.type !== 'error' && mediaElement.nodeName === 'VIDEO') {
mediaElement.play();
}
});
},
/**
* Helper to check (via HEAD request) a given resource exists
* The returned promise resolves with a true|false value
*
* @param {String} src URL for video/image resource
* @returns {Promise}
*/
fileExists: function (src) {
return new Promise((resolve) => {
window.fetch(src, { method: 'HEAD' }).then((response) => {
resolve(response.ok);
}).catch(() => {
resolve(false);
});
});
},
/**
* Helper to get best-match resource url for the current document.dir
* @param {String} src URL for video/image resource
* @returns {Promise}
*/
getBestAssetForDirection: function(src) {
var dir = document.dir || 'ltr';
var re =
/(-rtl|ltr)?\.(png|gif|jpg|webm|mp4|m4v|ogg|ogv)$/;
var directionSrc = src.replace(re, '-'+dir+'.$2');
return this.fileExists(directionSrc).then((result) => {
return result ? directionSrc : src;
}).catch((e) => {
console.log('Exception from getBestAssetForDirection: ', e);
});
},
/**
* Private helper class to manage a series of sync or async functions
*
* The array may be manipulated using standard array methods while the
* sequence runs. The sequence completes when there are no more functions or
* an exception is raised.
* At the end of the sequence, any 'oncomplete' assigned will be called with
* the return value from the last function
* Functions may return a 'thenable' to indicate async return
* Exceptions will be passed into the oncomplete function
* A Sequence may be cleanly aborted by calling abort() - no callbacks will
* be fired
* @class Sequence
*/
Sequence: function() {
var sequence = Array.slice(arguments);
var aborted = false;
sequence.abort = function() {
aborted = true;
this.length = 0;
if (typeof this.onabort === 'function') {
this.onabort();
}
};
sequence.complete = function(result) {
if(!aborted && typeof this.oncomplete === 'function') {
this.oncomplete(result);
}
};
sequence.fail = function(reason) {
this.complete(reason);
};
sequence.next = function(previousTaskResult) {
var result, exception;
if (aborted) {
return;
}
var task = this.shift();
if (task) {
try {
result = task.apply(null, arguments);
} catch(e) {
exception = e;
}
if (exception) {
this.fail(exception);
} else if (result && typeof result.then === 'function') {
result.then(this.next.bind(this), this.fail.bind(this));
} else {
this.next(result);
}
} else {
this.complete(previousTaskResult);
}
};
return sequence;
}
};
exports.TutorialUtils = TutorialUtils;
})(window);

650
gaia/apps/ftu/js/ui.js Executable file
View file

@ -0,0 +1,650 @@
/* global utils, tzSelect, FinishScreen,
Basket, ConfirmDialog, ScreenLayout,
DataMobile, SimManager, SdManager,
Tutorial, TimeManager, WifiManager,
WifiUI, WifiHelper, FxAccountsIACHelper, SettingsListener */
/* exported UIManager */
'use strict';
var UIManager = {
// As in other Gaia apps, we store all the dom selectors in one
// place and then camelCase them and attach to the main object,
// eg. instead of calling document.getElementById('splash-screen')
// we can access this.splashScreen in our code.
domSelectors: [
'container',
'splash-screen',
'activation-screen',
'finish-screen',
'update-screen',
'nav-bar',
'main-title',
// Unlock SIM Screen
'unlock-sim-screen',
'unlock-sim-header',
'unlock-sim-action',
// PIN Screen
'pincode-screen',
'pin-label',
'pin-retries-left',
'pin-input',
'back-sim-button',
'pin-error',
'skip-pin-button',
'unlock-sim-button',
// PUK Screen
'pukcode-screen',
'puk-label',
'puk-retries-left',
'puk-input',
'puk-info',
'puk-error',
'newpin-input',
'newpin-error',
'confirm-newpin-input',
'confirm-newpin-error',
// XCK Screen
'xckcode-screen',
'xck-label',
'xck-retries-left',
'xck-input',
'xck-error',
// SIM info
'sim-info-screen',
'sim-info-back',
'sim-info-forward',
'sim-info-1',
'sim-info-2',
'sim-number-1',
'sim-number-2',
'sim-carrier-1',
'sim-carrier-2',
// Import contacts
'sim-import',
'sim-import-button',
'no-sim',
'sd-import-button',
'no-memorycard',
// Fxa Intro
'fxa-create-account',
'fxa-intro',
// Wifi
'networks',
'wifi-refresh-button',
'wifi-join-button',
'join-hidden-button',
// Hidden Wifi
'hidden-wifi-authentication',
'hidden-wifi-ssid',
'hidden-wifi-security',
'hidden-wifi-password',
'hidden-wifi-password-box',
'hidden-wifi-identity',
'hidden-wifi-identity-box',
'hidden-wifi-show-password',
//Date & Time
'date-configuration',
'time-configuration',
'date-configuration-label',
'time-configuration-label',
'time-form',
// 3G
'data-connection-switch',
// Geolocation
'geolocation-switch',
// Tutorial
'lets-go-button',
'update-lets-go-button',
'skip-tutorial-button',
'update-skip-tutorial-button',
// Privacy Settings
'metrics-basic',
'metrics-enhanced',
'metrics-none',
'offline-error-dialog',
// Browser privacy newsletter subscription
'newsletter-form',
'newsletter-input',
'newsletter-success-screen',
'offline-newsletter-error-dialog',
'invalid-email-error-dialog'
],
dataConnectionChangedByUsr: false,
timeZoneNeedsConfirmation: true,
DARK_THEME: '#242d33',
LIGHT_THEME: '#eeeeee',
init: function ui_init() {
// Initialization of the DOM selectors
this.domSelectors.forEach(function createElementRef(name) {
this[toCamelCase(name)] = document.getElementById(name);
}.bind(this));
// Setup settings observers
this._settingsObserveHandler = {
'geolocation.enabled': {
// the "checked" attribute in the DOM is currently the source of truth
// for default value, when the setting is not initially defined
defaultValue: this.geolocationSwitch.checked,
callback: function(value) {
var isEnabled = !!value;
if (this.geolocationSwitch.checked !== isEnabled) {
this.geolocationSwitch.checked = isEnabled;
}
}.bind(this)
}
};
for (var name in this._settingsObserveHandler) {
SettingsListener.observe(
name,
this._settingsObserveHandler[name].defaultValue,
this._settingsObserveHandler[name].callback
);
}
var currentDate = new Date();
this.timeConfigurationLabel.textContent = currentDate.toLocaleString(
navigator.languages,
{
hour12: navigator.mozHour12,
hour: 'numeric',
minute: 'numeric',
second: 'numeric'
}
);
this.dateConfigurationLabel.textContent = currentDate.toLocaleString(
navigator.languages,
{
year: 'numeric',
month: 'numeric',
day: 'numeric'
}
);
// Add events to DOM
this.simImportButton.addEventListener('click', this);
this.sdImportButton.addEventListener('click', this);
this.skipPinButton.addEventListener('click', this);
this.backSimButton.addEventListener('click', this);
this.unlockSimButton.addEventListener('click', this);
this.unlockSimAction.addEventListener('action', this);
this.simInfoBack.addEventListener('click', this);
this.simInfoForward.addEventListener('click', this);
this.dataConnectionSwitch.addEventListener('change', this);
this.wifiRefreshButton.addEventListener('click', this);
this.wifiJoinButton.addEventListener('click', this);
this.networks.addEventListener('click', this);
this.joinHiddenButton.addEventListener('click', this);
this.hiddenWifiSecurity.addEventListener('change', this);
this.wifiJoinButton.disabled = true;
var checkHiddenWifiJoin = function() {
this.wifiJoinButton.disabled = this.hiddenWifiSsid.value === '' ||
!WifiHelper.isValidInput(this.hiddenWifiSecurity.value,
this.hiddenWifiPassword.value,
this.hiddenWifiIdentity.value
);
}.bind(this);
this.hiddenWifiSsid.addEventListener('keyup', checkHiddenWifiJoin);
this.hiddenWifiIdentity.addEventListener('keyup', checkHiddenWifiJoin);
this.hiddenWifiPassword.addEventListener('keyup', checkHiddenWifiJoin);
this.hiddenWifiSecurity.addEventListener('change', checkHiddenWifiJoin);
this.hiddenWifiShowPassword.onchange = function togglePasswordVisibility() {
UIManager.hiddenWifiPassword.type = this.checked ? 'text' : 'password';
};
this.timeConfiguration.addEventListener('input', this);
this.dateConfiguration.addEventListener('input', this);
this.geolocationSwitch.addEventListener('change', this);
this.fxaCreateAccount.addEventListener('click', this);
// Prevent form submit in case something tries to send it
this.timeForm.addEventListener('submit', function(event) {
event.preventDefault();
});
// Input scroll workaround
this.newsletterInput.addEventListener('focus', function() {
window.addEventListener('resize', function resize() {
window.removeEventListener('resize', resize);
// Need to wait till resize is done
setTimeout(function() {
var page = document.getElementById('browser_privacy');
UIManager.scrollToElement(page, UIManager.newsletterInput);
}, 30);
});
});
this.offlineNewsletterErrorDialog
.querySelector('button')
.addEventListener('click',
function offlineDialogClick() {
this.offlineNewsletterErrorDialog.classList.remove('visible');
}.bind(this));
this.invalidEmailErrorDialog
.querySelector('button')
.addEventListener('click',
function invalidEmailDialogClick() {
this.invalidEmailErrorDialog.classList.remove('visible');
}.bind(this));
var skipTutorialAction = function() {
// Stop Wifi Manager
WifiManager.finish();
// For tiny devices
if (ScreenLayout.getCurrentLayout() === 'tiny') {
window.close();
} else {
// for large devices
FinishScreen.init();
}
};
var startTutorialAction = function(evt) {
// Stop Wifi Manager
WifiManager.finish();
// Play the tutorial steps as soon as config is done loading
Tutorial.start(function onTutorialLoaded() {
UIManager.activationScreen.classList.remove('show');
UIManager.updateScreen.classList.remove('show');
UIManager.finishScreen.classList.remove('show');
});
};
this.skipTutorialButton.addEventListener('click', skipTutorialAction);
this.updateSkipTutorialButton.addEventListener('click', skipTutorialAction);
this.letsGoButton.addEventListener('click', startTutorialAction);
this.updateLetsGoButton.addEventListener('click', startTutorialAction);
// Enable saving metrics level choice to settings.
this.metricsBasic.addEventListener('change', this);
this.metricsEnhanced.addEventListener('change', this);
this.metricsNone.addEventListener('change', this);
var button = this.offlineErrorDialog.querySelector('button');
button.addEventListener('click',
this.onOfflineDialogButtonClick.bind(this));
// Handle activation screen visibility.
['confirmdialogshowing',
'loadingoverlayshowing',
'tutorialinitialized'].forEach(function(event) {
window.addEventListener(event,
this.hideActivationScreenFromScreenReader.bind(this));
}, this);
['confirmdialoghiding',
'loadingoverlayhiding'].forEach(function(event) {
window.addEventListener(event,
this.showActivationScreenToScreenReader.bind(this));
}, this);
this.checkInitialFxAStatus();
},
scrollToElement: function ui_scrollToElement(container, element) {
container.scrollTop = element.offsetTop;
},
sendNewsletter: function ui_sendNewsletter(callback) {
var self = this;
var emailValue = self.newsletterInput.value;
if (emailValue === '') {
return callback(true);
} else {
utils.overlay.show('email-loading', 'spinner');
if (self.newsletterInput.checkValidity()) {
if (window.navigator.onLine) {
Basket.send(emailValue, function emailSent(err, data) {
if (err) {
if (err.code && err.code === Basket.errors.INVALID_EMAIL) {
ConfirmDialog.show('invalid-email-dialog-title',
'invalid-email-dialog-text',
{
title: 'cancel',
callback: function ok() {
ConfirmDialog.hide();
}
});
utils.overlay.hide();
return callback(false);
} else {
Basket.store(emailValue);
}
}
utils.overlay.hide();
return callback(true);
});
} else {
Basket.store(emailValue, function stored(errorStoring) {
utils.overlay.hide();
return callback(!errorStoring);
});
}
} else {
utils.overlay.hide();
ConfirmDialog.show('invalid-email-dialog-title',
'invalid-email-dialog-text',
{
title: 'cancel',
callback: function ok() {
ConfirmDialog.hide();
}
});
return callback(false);
}
}
},
initTZ: function ui_initTZ() {
// Initialize the timezone selector, see /shared/js/tz_select.js
var self = this;
var tzRegion = document.getElementById('tz-region');
var tzCity = document.getElementById('tz-city');
return new Promise(function(resolve) {
var onchange = self.setTimeZone.bind(self);
var onload = function() {
self.setTimeZone.apply(self, arguments);
resolve();
};
tzSelect(tzRegion, tzCity, onchange, onload);
});
},
handleEvent: function ui_handleEvent(event) {
switch (event.target.id) {
// SIM
case 'skip-pin-button':
SimManager.skip();
break;
case 'back-sim-button':
case 'sim-info-back':
SimManager.back();
break;
case 'unlock-sim-button':
SimManager.unlock();
break;
case 'unlock-sim-action':
SimManager.simUnlockBack();
break;
case 'sim-info-forward':
SimManager.finish();
break;
case 'sim-import-button':
// Needed to give the browser the opportunity to properly refresh the UI
// Particularly the button toggling cycle (from inactive to active)
window.setTimeout(SimManager.importContacts, 0);
break;
case 'sd-import-button':
// Needed to give the browser the opportunity to properly refresh the UI
// Particularly the button toggling cycle (from inactive to active)
window.setTimeout(SdManager.importContacts, 0);
break;
// 3G
case 'data-connection-switch':
this.dataConnectionChangedByUsr = true;
var status = event.target.checked;
DataMobile.toggle(status);
break;
// WIFI
case 'wifi-refresh-button':
WifiManager.scan(WifiUI.renderNetworks);
break;
case 'wifi-join-button':
if (window.location.hash === '#hidden-wifi-authentication') {
WifiUI.joinHiddenNetwork();
} else {
WifiUI.joinNetwork();
}
break;
case 'join-hidden-button':
WifiUI.addHiddenNetwork();
break;
case 'hidden-wifi-security':
// Assuming that [0] is None, we prefer '' for collision on translations
var securityType = event.target.selectedIndex ? event.target.value : '';
WifiUI.handleHiddenWifiSecurity(securityType);
break;
// Date & Time
case 'time-configuration':
this.setTime();
break;
case 'date-configuration':
this.setDate();
break;
// Geolocation
case 'geolocation-switch':
this.updateSetting(event.target.name, event.target.checked);
break;
// Privacy
case 'metrics-enhanced':
this.updateSetting('hud.hide', (event.target.value === 'Enhanced'));
this.updateSetting('devtools.overlay',
(event.target.value === 'Enhanced'));
/* falls through */
case 'metrics-basic':
case 'metrics-none':
this.updateSetting('metrics.selectedMetrics.level', event.target.value);
break;
case 'fxa-create-account':
this.createFirefoxAccount();
break;
default:
// wifi selection
if (event.target.parentNode.id === 'networks-list') {
WifiUI.chooseNetwork(event);
}
break;
}
},
updateSetting: function ui_updateSetting(name, value) {
if (!name) {
return;
}
var cset = {}; cset[name] = value;
return SettingsListener.getSettingsLock().set(cset);
},
setForwardButtonLabel: function ui_setForwardButtonLabel(label) {
var nextButton = document.getElementById('forward');
nextButton.setAttribute('data-l10n-id', label);
},
checkInitialFxAStatus: function ui_checkInitialFxAStatus() {
// It is possible that we enter the FTU after the user aborted a FTU
// session where she logged into her FxA. In that case, we show the
// information of her account if possible. If there is any reason why we
// can't get the account information, we try to log her out.
// It is quite unlikely that logging out fails, but in that case, we simply
// hide the FxA panel to avoid potential errors such as the one reported on
// bug 1113551. In any case, the user should be able to manage her account
// from the Settings app afterwards.
this.skipFxA = true;
FxAccountsIACHelper.getAccount((account) => {
this.skipFxA = false;
this.onFxALogin(account);
}, () => {
FxAccountsIACHelper.logout(() => {
this.skipFxA = false;
});
});
},
createFirefoxAccount: function ui_createFirefoxAccount() {
FxAccountsIACHelper.openFlow(UIManager.onFxAFlowDone,
UIManager.onFxAError);
},
onFxAFlowDone: function ui_onFxAFlowDone() {
FxAccountsIACHelper.getAccount((account) => {
if (!account) {
return;
}
UIManager.onFxALogin(account);
UIManager.setForwardButtonLabel('navbar-next');
}, UIManager.onFxAError);
},
onFxALogin: function ui_onFxALogin(account) {
if (!account) {
return;
}
// Update the email
UIManager.newsletterInput.value = account.email;
// Update the string
UIManager.fxaIntro.innerHTML = '';
document.l10n.setAttributes(
UIManager.fxaIntro,
account.verified ? 'fxa-signed-in' : 'fxa-email-sent',
{
email: account.email
}
);
// Disable the button
UIManager.fxaCreateAccount.disabled = true;
},
onFxAError: function ui_onFxAError(response) {
console.error('Create FxA Error: ' + JSON.stringify(response));
// Clean fields
UIManager.newsletterInput.value = '';
// Reset the field
document.l10n.setAttributes(
UIManager.fxaIntro,
'fxa-upsell'
);
// Enable the button
UIManager.fxaCreateAccount.disabled = false;
// Change the forward button label
UIManager.setForwardButtonLabel('skip');
},
displayOfflineDialog: function ui_displayOfflineDialog(href, title) {
var dialog = this.offlineErrorDialog;
document.l10n.setAttributes(dialog.querySelector('small'),
'offline-dialog-text', { url: href });
dialog.classList.add('visible');
this.hideActivationScreenFromScreenReader();
},
onOfflineDialogButtonClick: function ui_onOfflineDialogButtonClick(e) {
this.offlineErrorDialog.classList.remove('visible');
this.showActivationScreenToScreenReader();
},
hideActivationScreenFromScreenReader:
function ui_hideActivationScreenFromScreenReader() {
this.activationScreen.setAttribute('aria-hidden', true);
},
showActivationScreenToScreenReader:
function ui_showActivationScreenToScreenReader() {
this.activationScreen.setAttribute('aria-hidden', false);
},
setDate: function ui_sd() {
if (!!this.lock) {
return;
}
// Current time
var now = new Date();
// Format: 2012-09-01
var currentDate = this.dateConfiguration.value;
var currentTime = now.toLocaleFormat('%H:%M');
var timeToSet = new Date(currentDate + 'T' + currentTime);
TimeManager.set(timeToSet);
this.dateConfigurationLabel.textContent = timeToSet.toLocaleString(
navigator.languages,
{
year: 'numeric',
month: 'numeric',
day: 'numeric'
}
);
},
setTime: function ui_st() {
if (!!this.lock) {
return;
}
var timeLabel = document.getElementById('time-configuration-label');
// Current time
var now = new Date();
// Format: 2012-09-01
var currentTime = document.getElementById('time-configuration').value;
if (currentTime.indexOf(':') === 1) { // Format: 8:05 --> 08:05
currentTime = '0' + currentTime;
}
var currentDate = now.toLocaleFormat('%Y-%m-%d');
var timeToSet = new Date(currentDate + 'T' + currentTime);
// Set date through API
TimeManager.set(timeToSet);
// Set DATE properly
timeLabel.textContent = timeToSet.toLocaleString(
navigator.languages,
{
hour12: navigator.mozHour12,
hour: 'numeric',
minute: 'numeric',
second: 'numeric'
}
);
},
setTimeZone: function ui_stz(timezone, needsConfirmation) {
this.timeZoneNeedsConfirmation = !!needsConfirmation;
var utcOffset = timezone.utcOffset;
document.getElementById('time_zone_overlay').className =
'UTC' + utcOffset.replace(/[+:]/g, '');
var timezoneTitle = document.getElementById('time-zone-title');
document.l10n.setAttributes(timezoneTitle, 'timezoneTitle', {
utcOffset: utcOffset,
region: timezone.region,
city: timezone.city
});
document.getElementById('tz-region-label').textContent = timezone.region;
document.getElementById('tz-city-label').textContent = timezone.city;
var now = new Date();
var timeLabel = document.getElementById('time-configuration-label');
timeLabel.textContent = now.toLocaleString(
navigator.languages,
{
hour12: navigator.mozHour12,
hour: 'numeric',
minute: 'numeric',
second: 'numeric'
}
);
},
updateDataConnectionStatus: function ui_udcs(status) {
this.dataConnectionSwitch.checked = status;
},
changeStatusBarColor: function ui_csbc(color) {
var themeMeta = document.head.querySelector('meta[name="theme-color"]');
themeMeta.setAttribute('content', color);
}
};
function toCamelCase(str) {
return str.replace(/\-(.)/g, function replacer(str, p1) {
return p1.toUpperCase();
});
}

16
gaia/apps/ftu/js/utils.js Executable file
View file

@ -0,0 +1,16 @@
(function(exports) {
'use strict';
var rdashes = /-(.)/g;
var Utils = {
camelCase: function ut_camelCase(str) {
return str.replace(rdashes, function(str, p1) {
return p1.toUpperCase();
});
}
};
exports.Utils = Utils;
}(this));

485
gaia/apps/ftu/js/wifi.js Executable file
View file

@ -0,0 +1,485 @@
/* global utils, UIManager, WifiHelper, WifiUI:true */
/* exported WifiManager, WifiUI */
(function(exports) {
'use strict';
const WIFI_STATUS = {
connecting: 'connecting',
associated: 'associated',
connected: 'connected',
connectingfailed: 'connectingfailed',
disconnected: 'disconnected'
};
var WifiManager = {
STATUS: Object.freeze(WIFI_STATUS),
init: function wn_init() {
this.api = WifiHelper.getWifiManager();
this.changeStatus();
// Ensure that wifi is on.
if (navigator.mozSettings) {
var lock = window.navigator.mozSettings.createLock();
this.enable(lock);
this.enableDebugging(lock);
}
this.gCurrentNetwork = this.api ? this.api.connection.network : null;
if (this.gCurrentNetwork !== null) {
this.api.forget(this.gCurrentNetwork);
this.gCurrentNetwork = null;
}
},
getNetworks: function wn_getNetworks(callback) {
this.networks ? callback(this.networks) : this.scan(callback);
},
scan: function wn_scan(callback) {
if (this._scanning) {
return;
}
this._scanning = true;
utils.overlay.show('scanningNetworks', 'spinner');
var SCAN_TIMEOUT = 10000;
var self = this;
self.onScan = callback;
var req = this.api ? this.api.getNetworks() : null;
if (!req) {
// When no wifi API is available (ie. shimless B2G-desktop),
// we will yield to the event loop before calling the callback
// to prevent a race condition with the wifi overlay.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=957769#c5
setTimeout(function() {
self._scanning = false;
callback();
});
return;
}
var handleRequest = function handleRequest() {
self._scanning = false;
clearTimeout(self.scanTimeout);
self.scanTimeout = null;
self.onScan(self.networks);
self.onScan = null;
};
req.onsuccess = function onScanSuccess() {
self.networks = req.result;
handleRequest();
};
req.onerror = function onScanError() {
console.error('Error reading networks: ' + req.error.name);
self.networks = [];
handleRequest();
};
// Timeout in case of scanning errors not thrown by the API
// We can't block the user in the screen (bug 889623)
if (!self.scanTimeout) {
self.scanTimeout = setTimeout(function() {
self._scanning = false;
console.warn('Timeout while reading networks');
self.onScan();
}, SCAN_TIMEOUT);
}
},
enable: function wn_enable(lock) {
lock.set({'wifi.enabled': true});
},
enableDebugging: function wn_enableDebugging(lock) {
// For bug 819947: turn on wifi debugging output to help track down a bug
// in wifi. We turn on wifi output only while the FTU app is active.
this._prevDebuggingValue = false;
var req = lock.get('wifi.debugging.enabled');
req.onsuccess = function wn_getDebuggingSuccess() {
this._prevDebuggingValue = req.result['wifi.debugging.enabled'];
};
lock.set({ 'wifi.debugging.enabled': true });
},
finish: function wn_finish() {
if (!this._prevDebuggingValue && navigator.mozSettings) {
var resetLock = window.navigator.mozSettings.createLock();
resetLock.set({'wifi.debugging.enabled': false});
}
},
getNetwork: function wm_gn(ssid) {
var list = this.networks;
for (var i = 0; i < list.length; i++) {
if (list[i].ssid === ssid) {
return list[i];
}
}
return null;
},
connect: function wn_connect(ssid, password, user) {
var network = this.getNetwork(ssid);
if (!network) {
console.error('Network not found');
return;
}
this.ssid = ssid;
// TODO: Hardcoded for resolving bug 1019146, replace hardcoded eap
// method with user selected eap method after bug 1036829.
WifiHelper.setPassword(network, password, user, 'PEAP');
this.gCurrentNetwork = network;
this.api.associate(network);
},
changeStatus: function wn_cs(callback) {
/**
* mozWifiManager status
* see dom/wifi/nsIWifi.idl -- the 4 possible statuses are:
* - connecting:
* fires when we start the process of connecting to a network.
* - associated:
* fires when we have connected to an access point but do not yet
* have an IP address.
* - connected:
* fires once we are fully connected to an access point.
* - connectingfailed:
* fires when we fail to connect to an access point.
* - disconnected:
* fires when we were connected to a network but have been
* disconnected.
*/
var self = this;
if (WifiManager.api) {
WifiManager.api.onstatuschange = function(event) {
if (event.status === WifiManager.STATUS.disconnected && self.onScan) {
self.scan(self.onScan);
} else {
WifiUI.updateNetworkStatus(event.network.ssid, event.status);
}
};
}
}
};
var WifiUI = {
joinNetwork: function wui_jn() {
var password = document.getElementById('wifi_password').value;
var user = document.getElementById('wifi_user').value;
var ssid = document.getElementById('wifi_ssid').value;
WifiUI.connect(ssid, password, user);
window.history.back();
},
joinHiddenNetwork: function wui_jhn() {
var password = UIManager.hiddenWifiPassword.value;
var user = UIManager.hiddenWifiIdentity.value;
var ssid = UIManager.hiddenWifiSsid.value;
var security = UIManager.hiddenWifiSecurity.value;
var network;
if (ssid.length) {
if (!Array.isArray(WifiManager.networks)) {
WifiManager.networks = [];
}
network = new window.MozWifiNetwork({
ssid: ssid,
capabilities: [],
security: [security],
relSignalStrength: 0,
hidden: true
});
WifiManager.networks.push(network);
this.renderNetworks(WifiManager.networks);
WifiUI.connect(ssid, password, user);
}
// like in Settings: if we don't provide correct
// network data it just get back to the wifi screen
window.history.back();
},
_selectNetworkItem: function(ssid, isSelected=true) {
var elem = document.getElementById(ssid);
if (!elem) {
return;
}
if (isSelected) {
// We should update the state to 'connecting' in here to show the change
// to the user, instead of waiting for the api (takes longer).
// But currently
// there's no feedback from the api when connecting to a hidden wifi fails
// so we skip the visual changes
// see https://bugzilla.mozilla.org/show_bug.cgi?id=1107431#c25
elem.setAttribute('data-wifi-selected', true);
} else {
var icon = elem.querySelector('aside');
elem.removeAttribute('data-wifi-selected');
elem.classList.remove('connected');
icon.classList.remove('connecting');
icon.classList.remove('connected');
var security = elem.dataset.security;
var securityLevelDOM = elem.querySelector('p[data-security-level]');
securityLevelDOM.setAttribute('data-l10n-id',
security ? security : 'securityOpen');
}
},
connect: function wui_connect(ssid, password, user) {
// First we check if there is a previous selected network
// and we remove their status
// And then end we update the selected network
var newWifi = document.getElementById(ssid);
Array.from(document.querySelectorAll('li[data-wifi-selected]'))
.forEach((networkSelected) => {
this._selectNetworkItem(networkSelected,
networkSelected === newWifi);
});
// Finally we try to connect to the network
WifiManager.connect(ssid, password, user);
},
chooseNetwork: function wui_cn(event) {
// Retrieve SSID from dataset
var ssid = event.target.dataset.ssid;
var selectedNetwork = WifiManager.getNetwork(ssid);
if (!selectedNetwork) {
console.error('Network not found');
return;
}
// Do we need to type password?
if (WifiHelper.isOpen(selectedNetwork)) {
WifiUI.connect(ssid);
return;
}
// Remove refresh option
UIManager.activationScreen.classList.add('no-options');
// Update title
UIManager.mainTitle.textContent = ssid;
// Update network values
var ssidHeader = document.getElementById('wifi_ssid');
var userInput = document.getElementById('wifi_user');
var passwordInput = document.getElementById('wifi_password');
var showPassword = document.querySelector(
'gaia-checkbox[name=show_password]');
var joinButton = UIManager.wifiJoinButton;
joinButton.disabled = true;
passwordInput.addEventListener('input', function validatePassword() {
// disable the "Join" button if the password is on wrong format
joinButton.disabled =
!WifiHelper.isValidInput(WifiHelper.getKeyManagement(selectedNetwork),
passwordInput.value, userInput.value);
});
// Show / Hide password
passwordInput.type = 'password';
passwordInput.value = '';
showPassword.checked = false;
showPassword.addEventListener('change', e => {
passwordInput.type = e.target.checked ? 'text' : 'password';
});
// Update form
passwordInput.value = '';
ssidHeader.value = ssid;
// Activate secondary menu
UIManager.navBar.classList.add('secondary-menu');
// Update changes in form
if (WifiHelper.isEap(selectedNetwork)) {
userInput.parentNode.classList.remove('hidden');
} else {
userInput.parentNode.classList.add('hidden');
}
// Change hash
window.location.hash = '#configure_network';
},
addHiddenNetwork: function wui_addHiddenNetwork() {
// Remove refresh option
UIManager.activationScreen.classList.add('no-options');
// Update title
UIManager.mainTitle.setAttribute('data-l10n-id', 'authentication');
UIManager.navBar.classList.add('secondary-menu');
// Clean input contents
UIManager.hiddenWifiSsid.value = '';
UIManager.hiddenWifiPassword.value = '';
UIManager.hiddenWifiIdentity.value = '';
// Reset join button state
UIManager.wifiJoinButton.disabled = true;
window.location.hash = '#hidden-wifi-authentication';
},
handleHiddenWifiSecurity: function wui_handleSecurity(securityType) {
// no need for password if network is open
if (securityType === '') {
UIManager.hiddenWifiPasswordBox.classList.add('hidden');
} else {
UIManager.hiddenWifiPasswordBox.classList.remove('hidden');
}
// need of username is security is WPA-EAP
if (securityType.indexOf('EAP') !== -1) {
UIManager.hiddenWifiIdentityBox.classList.remove('hidden');
} else {
UIManager.hiddenWifiIdentityBox.classList.add('hidden');
}
},
renderNetworks: function wui_rn(networks) {
var networksDOM = document.getElementById('networks');
networksDOM.innerHTML = '';
var networksList;
if (!networks || networks.length === 0) {
var noResult = '<div id="no-result-container">' +
' <div id="no-result-message">' +
' <p data-l10n-id="noWifiFound3"></p>' +
' </div>' +
'</div>';
networksDOM.innerHTML = noResult;
} else {
networksList = document.createElement('ul');
networksList.id = 'networks-list';
networksList.setAttribute('role', 'listbox');
networksList.setAttribute('data-l10n-id', 'networksList');
var networksShown = [];
networks.sort(function(a, b) {
return b.relSignalStrength - a.relSignalStrength;
});
// Add detected networks
for (var i = 0, max = networks.length; i < max; i++) {
// Retrieve the network
var network = networks[i];
// Check if is shown
if (networksShown.indexOf(network.ssid) === -1) {
// Create dom elements
var li = document.createElement('li');
var icon = document.createElement('aside');
var ssidp = document.createElement('p');
ssidp.setAttribute('dir', 'auto');
var small = document.createElement('p');
small.dataset.securityLevel = true;
// Set Icon
icon.classList.add('pack-end');
icon.classList.add('wifi-icon');
var level = Math.min(Math.floor(network.relSignalStrength / 20), 4);
icon.classList.add('level-' + level);
document.l10n.setAttributes(icon, 'wifiLevel', {level: level});
icon.setAttribute('role', 'presentation');
// Set SSID
ssidp.textContent = network.ssid;
li.dataset.ssid = network.ssid;
// Show authentication method
var keys = WifiHelper.getSecurity(network);
li.dataset.security = keys;
if (!WifiHelper.isOpen(network)) {
small.textContent = keys.join(', ');
icon.classList.add('secured');
} else {
small.setAttribute('data-l10n-id', 'securityOpen');
}
// Show connection status
icon.classList.add('wifi-signal');
var connectionStatus = WifiHelper.getNetworkStatus(network) ||
WifiManager.STATUS.disconnected;
li.dataset.status = connectionStatus;
if (connectionStatus === WifiManager.STATUS.connected) {
small.setAttribute('data-l10n-id', 'shortStatus-connected');
small.removeAttribute('aria-label');
icon.classList.add('connected');
li.classList.add('connected');
li.dataset.wifiSelected = true;
}
// Update list of shown networks
networksShown.push(network.ssid);
// Append the elements to li
li.setAttribute('id', network.ssid);
li.setAttribute('role', 'option');
li.setAttribute('aria-live', true);
li.setAttribute('aria-relevant', 'text');
li.appendChild(ssidp);
li.appendChild(small);
li.appendChild(icon);
// Append to DOM
if (WifiHelper.isConnected(network)) {
networksList.insertBefore(li, networksList.firstChild);
} else {
networksList.appendChild(li);
}
}
}
networksList.dataset.type = 'list';
networksDOM.appendChild(networksList);
}
utils.overlay.hide();
},
updateNetworkStatus: function wui_uns(ssid, status) {
var element = document.getElementById(ssid);
// Check if element exists and it's the selected network
if (!element) {
return;
}
if (status === WifiManager.STATUS.connecting && ssid !== WifiManager.ssid) {
// bogus event, see bug 1179703/1216221
return;
}
// Update the element
element.dataset.status = status;
if (status !== WifiManager.STATUS.disconnected) {
element.querySelector('p[data-security-level]').setAttribute(
'data-l10n-id', 'shortStatus-' + status);
} else {
var security = element.dataset.security || 'Open';
element.querySelector('p[data-security-level]').setAttribute(
'data-l10n-id', 'security' + security);
element.classList.remove('connected');
}
// Animate icon if connecting, stop animation if
// failed/connected/disconnected
var icon = element.querySelector('aside');
switch (status) {
case WifiManager.STATUS.connecting:
case WifiManager.STATUS.associated:
icon.classList.add('connecting');
break;
case WifiManager.STATUS.connected:
icon.classList.remove('connecting');
var networksList = document.getElementById('networks-list');
icon.classList.add('connected');
element.classList.add('connected');
networksList.removeChild(element);
networksList.insertBefore(element, networksList.firstChild);
break;
default:
icon.classList.remove('connecting');
icon.classList.remove('connected');
break;
}
}
};
exports.WifiManager = WifiManager;
exports.WifiUI = WifiUI;
})(window);

18
gaia/apps/ftu/jsdoc.json Executable file
View file

@ -0,0 +1,18 @@
{
"name": "ftu",
"tags": {
"dictionaries": ["jsdoc"]
},
"source": {
"include": ["apps/ftu/js/**/*.js"]
},
"plugins": [
"plugins/markdown",
"plugins/summarize"
],
"opts": {
"private": true,
"recurse": true,
"destination": "docs/ftu"
}
}

View file

@ -0,0 +1,342 @@
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Unlock Codes
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
pincode2=Enter PIN code
type_pin=Type your PIN code
pincodeLabel=SIM {{n}} PIN
pinValidation=PIN must have between 4 and 8 characters.
pinError=The PIN was incorrect.
pinAttemptMsg2={[ plural(n) ]}
pinAttemptMsg2[one]=You have one try left to enter the correct code before locking the SIM card.
pinAttemptMsg2[two]=You have {{n}} tries left to enter the correct code before locking the SIM card.
pinAttemptMsg2[few]=You have {{n}} tries left to enter the correct code before locking the SIM card.
pinAttemptMsg2[many]=You have {{n}} tries left to enter the correct code before locking the SIM card.
pinAttemptMsg2[other]=You have {{n}} tries left to enter the correct code before locking the SIM card.
pinLastChanceMsg=This is your last chance to enter the correct PIN. Otherwise, you must enter the PUK code to use this SIM card.
pukcode=Enter PUK code
pukcodeTitle=Enter SIM {{n}} PUK
pukcodeLabel=SIM {{n}} PUK
type_puk=PUK code
pukValidation=PUK code must have exactly 8 characters.
pukInfo=You must enter the Personal Unlocking Key (PUK) Code for the SIM card. Refer to your SIM card documentation or contact your carrier for more information.
pukError=The PUK was incorrect.
pukAttemptMsg2={[ plural(n) ]}
pukAttemptMsg2[one]=You have one try left to enter the correct code before this SIM card will be permanently unusable. Refer to your SIM card documentation or contact your carrier for more information.
pukAttemptMsg2[two]=You have {{n}} tries left to enter the correct code before this SIM card will be permanently unusable. Refer to your SIM card documentation or contact your carrier for more information.
pukAttemptMsg2[few]=You have {{n}} tries left to enter the correct code before this SIM card will be permanently unusable. Refer to your SIM card documentation or contact your carrier for more information.
pukAttemptMsg2[many]=You have {{n}} tries left to enter the correct code before this SIM card will be permanently unusable. Refer to your SIM card documentation or contact your carrier for more information.
pukAttemptMsg2[other]=You have {{n}} tries left to enter the correct code before this SIM card will be permanently unusable. Refer to your SIM card documentation or contact your carrier for more information.
pukLastChanceMsg=This is your last chance to enter the correct PUK code. Your SIM card will be permanently unusable if you enter in the wrong PUK code.
create_newpin=Create new PIN (4 to 8 digits)
confirm_newpin=Confirm new PIN
newpinConfirmation=PINs don’t match
nckcode=Enter NCK code
cckcode=Enter CCK code
spckcode=Enter SPCK code
nck1code=Enter NCK1 code
nck2code=Enter NCK2 code
hnckcode=Enter HNCK code
rcckcode=Enter RCCK code
rspckcode=Enter RSPCK code
nckcodeTitle=Enter SIM {{n}} NCK
cckcodeTitle=Enter SIM {{n}} CCK
spckcodeTitle=Enter SIM {{n}} SPCK
nck1codeTitle=Enter SIM {{n}} NCK1
nck2codeTitle=Enter SIM {{n}} NCK2
hnckcodeTitle=Enter SIM {{n}} HNCK
rcckcodeTitle=Enter SIM {{n}} RCCK
rspckcodeTitle=Enter SIM {{n}} RSPCK
nckcodeLabel=SIM {{n}} NCK
cckcodeLabel=SIM {{n}} CCK
spckcodeLabel=SIM {{n}} SPCK
nck1codeLabel=SIM {{n}} NCK1
nck2codeLabel=SIM {{n}} NCK2
hnckcodeLabel=SIM {{n}} HNCK
rcckcodeLabel=SIM {{n}} RCCK
rspckcodeLabel=SIM {{n}} RSPCK
type_nck=Type your NCK code
type_cck=Type your CCK code
type_spck=Type your SPCK code
type_nck1=Type your NCK1 code
type_nck2=Type your NCK2 code
type_hnck=Type your HNCK code
type_rcck=Type your RCCK code
type_rspck=Type your RSPCK code
nckValidation=NCK must have between 8 and 16 characters.
cckValidation=CCK must have between 8 and 16 characters.
spckValidation=SPCK must have between 8 and 16 characters.
nck1Validation=NCK1 must have between 8 and 16 characters.
nck2Validation=NCK2 must have between 8 and 16 characters.
hnckValidation=HNCK must have between 8 and 16 characters.
rcckValidation=RCCK must have between 8 and 16 characters.
rspckValidation=RSPCK must have between 8 and 16 characters.
nckErrorMsg=The NCK code is incorrect.
nckAttemptMsg2={[ plural(n) ]}
nckAttemptMsg2[one]=Network depersonalization request failure. You have one try left to enter the correct code. Refer to your device documentation or contact your carrier for more information.
nckAttemptMsg2[two]=Network depersonalization request failure. You have {{n}} tries left to enter the correct code. Refer to your device documentation or contact your carrier for more information.
nckAttemptMsg2[few]=Network depersonalization request failure. You have {{n}} tries left to enter the correct code. Refer to your device documentation or contact your carrier for more information.
nckAttemptMsg2[many]=Network depersonalization request failure. You have {{n}} tries left to enter the correct code. Refer to your device documentation or contact your carrier for more information.
nckAttemptMsg2[other]=Network depersonalization request failure. You have {{n}} tries left to enter the correct code. Refer to your device documentation or contact your carrier for more information.
nckLastChanceMsg=Network depersonalization request failure. Last chance to enter the correct NCK code. Refer to your device documentation or contact your carrier for more information.
skip=Skip
send=Send
ok=OK
simLocked=The SIM card is locked
inputCodeRetriesLeft ={[ plural(n) ]}
inputCodeRetriesLeft[one] =Last try.
inputCodeRetriesLeft[two] ={{n}} tries left.
inputCodeRetriesLeft[few] ={{n}} tries left.
inputCodeRetriesLeft[many] ={{n}} tries left.
inputCodeRetriesLeft[other] ={{n}} tries left.
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# SIM Manager
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
simManager=SIM Manager
simDetail=SIM {{n}}
simDefault=(default)
simDefaultMessage=Outgoing calls, outgoing messages and data connection are set to SIM 1 as default
simSettingsMessage=You can change default settings at Settings > SIM manager
simPinLocked=SIM PIN locked
noOperator=No operator
searchingOperator=Searching
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Language
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
language=Language
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# 3g
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
3g = Cellular Data
dataConnection = Enable Data
dataConnectionInfo = Enabling data connection now will allow you to set up your phone without a Wi-Fi connection. You may also enable it later on in Settings. Just make sure you have a data plan with your carrier to avoid potentially hefty charges.
caution = Warning
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Geolocation
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
geolocation = Geolocation
geolocationCheckbox = Enable Geolocation
geolocationInfo2 = {{brandShortName}} uses data such as GPS, Wi-Fi, and mobile networks near you to help determine your approximate location.
geolocationInfoMore = Data is sent to Mozilla and other service providers and will be used in the aggregate to improve those location databases. Data may be stored on your device and data collection may occur even when no apps are running.
notice = Notice
aboutYourPrivacy = More about your privacy
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Wifi
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
scanningNetworks = Scanning for networks
selectNetwork = Select a network
shortStatus-connecting = Connecting…
shortStatus-associated = Obtaining an IP address…
shortStatus-connected = Connected
shortStatus-connectingfailed = Connection failed
shortStatus-disconnected = Offline
securedBy=secured by {{capabilities}}
security = Security
securityNone = None
securityWEP = WEP
securityWPA-PSK = WPA-PSK
securityWPA-EAP = WPA-EAP
securityOpen = Open
securityOpen.ariaLabel=Security
identity=User
identityInput.placeholder=User
password=Password
passwordInput.placeholder=Password
showPassword=Show Password
refresh = Refresh
noWifiFound3 = No wireless networks found
join-hidden-button=Join hidden network
authentication=Authentication
ssid-name=SSID Network Name
# The following strings are spoken by screen readers and not shown on the
# screen.
# LOCALIZATION NOTE(wifiLevel): {{level}} will be replaced at run-time by a
# number ranging from 1 to 4.
wifiLevel.ariaLabel={{level}} of 4 bars
networksList.ariaLabel=Networks found
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Date & Time
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
dateAndTime = Date & Time
# LOCALIZATION NOTE(timezoneTitle): Parameters in this label will be replaced
# at runtime, use this string to reorder elements, translate "UTC", or use
# different separator characters.
# Example of a resulting string for English: "UTC+01:00 Europe/Paris"
timezoneTitle = UTC{{utcOffset}} {{region}}/{{city}}
timeZone = Time zone
change = Change
region.ariaLabel = Region
city.ariaLabel = City
date.ariaLabel = Date
time.ariaLabel = Time
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Import contacts
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
importContacts3 = Import contacts
importSim2 = SIM card
importOutlook = Outlook
importGmail = Gmail
notImportedYet = Not imported yet
otherAccount = Other accounts
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Fxa Intro
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
firefox-accounts = Firefox Accounts
fxa-upsell = A Firefox Account lets you find a lost phone and buy apps from the Firefox Marketplace.
fxa-create-account-or-sign-in = Create Account or Sign In
fxa-email-sent = A verification link has been sent to {{email}}
# Copy borrowed fxa-logged-in-text string in settings app
fxa-signed-in = Signed in as {{email}}
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# About your rights
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
about-your-rights-0 = {{brandShortName}} is built on free and open source software by a community of thousands from all over the world. There are a few things you should know:
about-your-rights-1 = {{brandShortName}} is made available to you under the terms of several open source licenses including the Mozilla Public License. A device running {{brandShortName}} may also contain proprietary software from third parties. Any code provided under open licenses give you the right to modify the source code and distribute your modified versions as long as you comply with their terms.
about-your-rights-2 = You are not granted any trademark rights or licenses to the trademarks of the Mozilla Foundation or any party, including without limitation the {{brandShortName}} name or logo.
about-your-rights-3 = Some features in {{brandShortName}}, such as the Crash Reporter, give you the option to provide feedback to Mozilla. By choosing to submit feedback, you give Mozilla permission to use the feedback to improve its products, to publish the feedback on its websites, and to distribute the feedback.
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# SIM mandatory
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
SIM_mandatory = SIM Card Warning
SIM_mandatory_warm = Please, insert a SIM card and reboot your phone.
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Sharing Performance Data
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
telemetry-sends2 = Telemetry sends Mozilla data about performance, hardware, configurations, customizations and app usage in your {{brandShortName}} device. We use this to make our products better, faster and more responsive.
learn-more-telemetry2 = Learn more at <a>{{url}}</a>
learn-more-information2 = Learn how Mozilla handles your information at <a>{{url}}</a>
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# OS privacy
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
htmlWelcome2 = Welcome to <a>{{brandShortName}}</a>, the free and open source operating system from Mozilla. Our mission is to promote openness, innovation and opportunity by keeping the power of the Web in your hands.
helpImprove2 = You can help us improve our products and services by automatically sending analytics data. See our <a>Privacy Notice</a> to learn how we handle data from your device.
helpImprove3 = Help us improve our products and services by automatically sending analytics data.
learn-more-privacy2 = Learn how Mozilla handles your information in our <a>privacy policy</a>.
learn-more-privacy3 = Read our <a>Privacy Notice</a>
aboutBrowser = About {{brandShortName}}
aboutYourPrivacy2 = Your Privacy
mozillaCommunity2 = When you use {{brandShortName}}, you become part of a global community helping to build a brighter future for the Web. If you’d like to know more about the Mozilla community, our other products and events near you, please enter your email address below.
sharePerformanceAndUsageData2 = Yes! Send data.
emailAddress.placeholder = Email address
email-successfully-added=Email successfully added
email-loading=Saving your email…
privacy-b2g={{brandShortName}}
privacy-b2g.title={{brandShortName}}
privacy-marketplace=Marketplace
# please don't translate the brand name
privacy-marketplace.title={{privacy-marketplace}}
basicMetricsRadio = Basic
enhancedMetricsRadio = Enhanced
noneMetricsRadio = None
basicMetricsExplain = Send app usage and error reports.
enhancedMetricsExplain = Send Basic report plus diagnostic information.
noneMetricsExplain = Do not send any data.
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Navigation bar
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
navbar-back=Back
navbar-next=Next
navbar-join=Join
done=Done
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Welcome OS
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
thatsIt = That’s it!
startYourPhone = Start your phone tour!
startYourTablet = Start your tablet tour!
joinTutorial = Join us for a quick tutorial about your new phone.
joinTutorialUpdated = Join us for a quick tutorial about the updates to your phone!
enjoyYourPhone = You’re ready to enjoy your new phone.
enjoyYourPhoneUpdated = You’re ready to enjoy your updated phone.
optionsAvailable = You can change the language, date/time and Wi-Fi connection in settings.
letsGo = Let’s go
startTour = Start tour
thatsIt1Tablet-2 = You can always swipe from the bottom edge to enter home screen.
thatsIt2Tablet-2 = Let’s do it!
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Late Customization
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
late-customization = Free Apps
late-customization-downloading-message = {{carrierName}} is offering free apps to help get you started. They will download <em>at no charge</em> while you finish setting up your phone.
late-customization-download-later = {{carrierName}} is offering free apps to help get you started. You can download them <em>at no charge</em>.
late-customization-downloading-message-default = Your carrier is offering free apps to help get you started. They will download <em>at no charge</em> while you finish setting up your phone.
late-customization-download-later-default = Your carrier is offering free apps to help get you started. You can download them <em>at no charge</em>.
late-customization-download-later-marketplace = Find these apps and many more when you visit Marketplace!
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Tutorial
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# LOCALIZATION NOTE(tutorial): The following string is spoken by screen readers
# and not shown on the screen.
tutorial = Tutorial
tutorial-step1-large-3 = Swipe down to access recent notifications, usage information and settings.
tutorial-step2-large-2 = Swipe left or right to discover new apps on home screen.
tutorial-step3-large-2 = Tap and hold on an icon to delete or move it.
tutorial-step4-large-2 = Two fingers swipe up from the bottom edge to trigger card view.
tutorial-homescreen-tiny = Swipe up and down to browse your apps. Tap and hold an icon to delete, move, or edit it.
tutorial-notifications-v2-tiny = Swipe down to access recent notifications, usage information and settings.
tutorial-sheets-tiny = Drag from the left edge of your screen to return to recently used apps.
tutorial-rocketbar-tiny = Tap on the search box anytime to start a search or go to a website.
tutorial-bookmarks-migration-tiny = Your bookmarks have been moved to the homescreen. When editing, tap a bookmark to edit or rename it.
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Offline Error Dialog
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
offline-dialog-title=You must be connected to the Internet to view the page.
offline-dialog-text=Please go back and connect to the Internet. You can also view the page later from Settings or by entering {{url}} in any browser.
OK=OK
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Newsletter Error Dialogs
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
offline-newsletter-dialog-title=You must be connected to the Internet to subscribe to the newsletter.
offline-newsletter-dialog-text=Please go back and connect to the Internet.
invalid-email-dialog-title=Your email is not valid
invalid-email-dialog-text=Please go back and correct your entry.
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
# Memory Card Contacts Import Error Dialog
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
retry=Retry
cancel = Cancel
# The following strings are spoken by screen readers and not shown on the
# screen.
progressbar = Step {{step}} of {{total}}
progressbar.ariaLabel = First Time Experience

76
gaia/apps/ftu/manifest.webapp Executable file
View file

@ -0,0 +1,76 @@
{
"name": "FTU",
"description": "First Time Experience",
"type": "certified",
"launch_path": "/index.html",
"role": "system",
"developer": {
"name": "The Gaia Team",
"url": "https://github.com/mozilla-b2g/gaia"
},
"locales": {
"ar": {
"name": "FTU",
"description": "First Time Experience"
},
"en-US": {
"name": "FTU",
"description": "First Time Experience"
},
"fr": {
"name": "Tutoriel de prise en main",
"description": "Tutoriel de prise en main"
},
"zh-TW": {
"name": "FTU",
"description": "First Time Experience"
}
},
"default_locale": "en-US",
"permissions": {
"themeable": {},
"telephony": {},
"contacts": {
"access": "readwrite"
},
"mobileconnection": {},
"settings": {
"access": "readwrite"
},
"systemXHR": {},
"wifi-manage": {},
"time": {},
"device-storage:sdcard": {
"access": "readcreate"
},
"phonenumberservice": {},
"webapps-manage": {},
"moz-extremely-unstable-and-will-change-webcomponents": {}
},
"orientation": "default",
"redirects": [
{
"from": "live",
"to": "/shared/pages/import/redirects/redirect.html"
},
{
"from": "gmail",
"to": "/shared/pages/import/redirects/redirect.html"
}
],
"datastores-owned": {
"Basket_Newsletter": {
"description": "Email address for sending newsletter"
}
},
"datastores-access": {
"Import_Status_Data": {
"readonly": false,
"description": "Import status data shared between FTU and Contacts"
},
"icons": {
"readonly": true,
"description": "Retrieve cached website icons."
}
}
}

10
gaia/apps/ftu/package.json Executable file
View file

@ -0,0 +1,10 @@
{
"webide": {
"prepackage": {
"command": "make",
"env": ["APP=ftu"],
"cwd": "../.."
},
"packageDir": "../../build_stage/ftu/"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 KiB

View file

@ -0,0 +1,21 @@
#iframe_extensions {
position: fixed;
top: 100%;
left: 0;
z-index: 1000;
width: 100%;
height: 100%;
border: 0;
margin: 0;
padding: 0;
transition: transform .6s ease;
background: #f4f4f4;
}
#iframe_extensions.opening {
transform: translateY(-100%);
}
#iframe_extensions.closing {
transform: translateY(0);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 84 84" width="84" height="84"><path opacity=".85" fill="#E51E1E" d="M42 0c23.3 0 42 18.7 42 42S65.3 84 42 84 0 65.3 0 42 18.7 0 42 0z"/><path fill="#FFF" d="M60.7 40.1c-.9-9.3-9.3-16.8-18.7-16.8-10.3 0-18.7 8.4-18.7 18.7S31.7 60.7 42 60.7c3.7 0 7.5-.9 10.3-2.8l-2.8-3.7C46.7 56.1 44.8 57 42 57c-8.4 0-14.9-6.5-14.9-14.9s6.5-15 14.9-15c7.5 0 13.1 5.6 14 13.1h-6.5l7.5 7.5c.9.9 2.8.9 3.7 0l7.5-7.5h-7.5z"/></svg>

After

Width:  |  Height:  |  Size: 464 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 84 84" width="84" height="84"><path opacity=".85" fill="#333" d="M42 0c23.3 0 42 18.8 42 42 0 23.3-18.8 42-42 42-23.1.1-42-18.7-42-42C0 18.8 18.9 0 42 0z"/><path fill="#FFF" d="M29.9 50c.4.9 1.3 1.5 2.4 1.5h19.3c1 0 1.9-.6 2.4-1.5H29.9zm22.3-17.8L51 33.4v.1L42.4 42c-.3.3-.7.3-1 0l-8.6-8.6v-.1l-1.2-1.2c-1.6.3-3.1.6-4.6.9.2.5.4 1 .9 1.5l11.3 11.3c1.5 1.5 3.8 1.5 5.3 0l11.3-11.3c.4-.4.7-.9.9-1.5-1.4-.3-2.9-.6-4.5-.8z"/></svg>

After

Width:  |  Height:  |  Size: 480 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 84 84" width="84" height="84"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="42" x2="42" y2="84"><stop offset="0" stop-color="#E5EBEF"/><stop offset="1" stop-color="#EDF1F4"/></linearGradient><path fill="url(#a)" d="M42 0c23.3 0 42 18.8 42 42 0 23.3-18.8 42-42 42-23.1.1-42-18.7-42-42C0 18.8 18.9 0 42 0z"/><path fill="#9AB0C0" d="M41.5 25.9c-.9 0-2.5 2.6-3.5 5.7 1.1-.2 2.3-.3 3.6-.3 1.2 0 2.4.1 3.4.3-1-3.1-2.6-5.7-3.5-5.7M43 53.3h2.2c.3.9.4 2 .4 3.1 0 4-3.1 7.2-4.1 7.2s-4.1-3.2-4.1-7.2c0-1.1.1-2.2.4-3.1H40c-.2.5-.3 1.2-.3 1.9 0 1.8 1.4 3.3 1.9 3.3s1.9-1.5 1.9-3.3c-.2-.7-.3-1.4-.5-1.9M26.1 40l5.9-5.9c1.7-8.6 6.9-17.2 9.5-17.2s7.8 8.7 9.5 17.2l5.9 5.9v15.4h-4.1l-5.7-5.7c-.6.6-1.2 1.1-1.9 1.5h-7.3c-.7-.4-1.3-.9-1.9-1.5l-5.7 5.7h-4.1L26.1 40"/></svg>

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Some files were not shown because too many files have changed in this diff Show more