Publish Vulpes Preview 15 sources for desktop, Android and Tundra

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

View file

@ -0,0 +1,5 @@
The contents of these fake contacts are licensed under the Creative Commons Attribution-Share Alike 3.0 United States License.
Original source attributed to: Fake Name Generator (http://www.fakenamegenerator.com/)
For more information, please see http://creativecommons.org/licenses/by-sa/3.0/us/.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,33 @@
{
"data": [
{
"name": ["DJ Trasto"],
"givenName": ["DJ"],
"familyName": ["Trasto"],
"category": ["twitter"],
"bday": "1979-11-02"
},
{
"name": ["Armin van Beer"],
"givenName": ["Armin"],
"familyName": ["van Beer"],
"category": ["facebook","twitter"],
"org": ["State of Web"],
"bday": "1974-03-15"
},
{
"name": ["Spaniard Web Mafia"],
"givenName": ["Spaniard"],
"familyName": ["Web Mafia"],
"category": ["twitter"],
"org": ["Libraries.JS"]
},
{
"name": ["David Siestta"],
"givenName": ["David"],
"familyName": ["Siestta"],
"category": ["facebook"],
"bday": "1980-04-20"
}
]
}

View file

@ -0,0 +1,152 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<meta charset="utf-8">
<title>UI tests</title>
<!-- We don't need localization in this app -->
<link rel="stylesheet" type="text/css" href="style/uitest.css">
<link rel="stylesheet" type="text/css" href="style/tabs.css">
<link rel="stylesheet" type="text/css" href="style/lists.css">
<link rel="stylesheet" type="text/css" href="shared/style/lists.css">
<!-- Gaia Components -->
<link rel="stylesheet" href="app://theme.gaiamobile.org/shared/elements/gaia-theme/style.css">
<link rel="stylesheet" href="shared/elements/gaia-icons/gaia-icons.css">
<!-- Gaia Components -->
<script src="shared/elements/config.js"></script>
<script src="shared/elements/gaia-header/dist/gaia-header.js"></script>
<script type="text/javascript" src="js/uitest.js"></script>
<!-- <script src="shared/js/mime_mapper.js"></script> -->
<!-- <script src="shared/js/settings_listener.js"></script> -->
<!-- <script src="shared/js/download/download_formatter.js"></script> -->
<!-- <script src="shared/js/download/download_helper.js"></script> -->
<!-- <script src="shared/js/download/download_store.js"></script> -->
<script defer="" src="shared/js/lazy_loader.js"></script>
<script defer="" src="shared/js/accessibility_helper.js"></script>
<meta content="ar:201601260729, bg:201601260729, bn-BD:201601260729, ca:201601260729, cs:201601260729, da:201601260729, de:201601260729, el:201601260729, en-US:201601260729, es:201601260729, eu:201601260729, fr:201601260729, gl:201601260729, hr:201601260729, hu:201601260729, it:201601260729, ja:201601260729, km:201601260729, ko:201601260729, lt:201601260729, mk:201601260729, ms:201601260729, ne-NP:201601260729, nl:201601260729, pa:201601260729, pl:201601260729, pt-BR:201601260729, ro:201601260729, ru:201601260729, sk:201601260729, sq:201601260729, sr-Cyrl:201601260729, sr-Latn:201601260729, sv-SE:201601260729, tr:201601260729, zh-CN:201601260729, zh-TW:201601260729" name="availableLanguages"><meta content="en-US" name="defaultLanguage"><meta content="2.5" name="appVersion"></head>
<body class="theme-productivity">
<div class="panel list-panel">
<div id="UI-tests" class="page">
<section role="region">
<gaia-header>
<h1>UI Tests</h1>
</gaia-header>
</section>
<section data-type="list">
<ul class="sublist">
<li><a href="#test=UI/alert">Alert/Prompt</a></li>
<li><a href="#test=UI/canvas">Canvas Tag</a></li>
<li><a href="#test=UI/contextmenu">Contextmenu</a></li>
<li><a href="#test=UI/css_dimension">CSS dimension</a></li>
<li><a href="#test=UI/empty">Empty</a></li>
<li><a href="#test=UI/fullscreen">mozRequestFullScreen</a></li>
<li><a href="#test=UI/fullscreen_controls">Fullscreen Ctr</a></li>
<li><a href="#test=UI/keyboard">Keyboard</a></li>
<li><a href="#test=UI/newnotification">new Notification()</a></li>
<li><a href="#test=UI/notification">mozNotification</a></li>
<li><a href="#test=UI/open">window.open</a></li>
<li><a href="#test=UI/scrollbar">Scroll bar</a></li>
<li><a href="#test=UI/select">Select</a></li>
</ul>
</section>
</div>
<div id="API-tests" class="page invisible">
<section role="region">
<gaia-header>
<h1>API Tests</h1>
</gaia-header>
</section>
<section data-type="list">
<ul class="sublist">
<li><a href="#test=API/audiotag">audio tag</a></li>
<li><a href="#test=API/browseractivities">Browser Activities</a></li>
<li><a href="#test=API/composeemail">Compose Email</a></li>
<li><a href="#test=API/contacts">Contacts</a></li>
<li><a href="#test=API/copypaste">Copy Paste</a></li>
<li><a href="#test=API/csp">CSP Warning</a></li>
<li><a href="#test=API/dialnumber">Dial Number</a></li>
<li><a href="#test=API/downloads">Downloads</a></li>
<li><a href="#test=API/geolocation">Geolocation</a></li>
<li><a href="#test=API/getusermedia">getUserMedia</a></li>
<li><a href="#test=API/httpauthentication">HTTP Auth</a></li>
<li><a href="#test=API/fxa" id="mozId-fxa">Firefox Accounts mozId</a></li>
<li><a href="#test=API/identity" id="mozId-persona">Persona mozId</a></li>
<li><a href="#test=API/identity-get" id="mozId-persona-get">Persona mozId.get</a></li>
<li><a href="#test=API/inlineactivities">Inline Activities</a></li>
<li><a href="#test=API/multiactivities">Multi Activities</a></li>
<li><a href="#test=API/openaudio">Open Audio</a></li>
<li><a href="#test=API/openvideo">Open Video</a></li>
<li><a href="#test=API/orientation">mozLockOrientation</a></li>
<li><a href="#test=API/pay">navigator.mozPay</a></li>
<li><a href="#test=API/pickphoto">Pick Photo</a></li>
<li><a href="#test=API/picktype">Pick Photo/Video</a></li>
<li><a href="#test=API/pickvideo">Pick Video</a></li>
<li><a href="#test=API/pickdownload">Pick Download</a></li>
<li><a href="#test=API/presentation_api">Presentation API</a></li>
<li><a href="#test=API/savebookmark">Save bookmark</a></li>
<li><a href="#test=API/sendsms">Send SMS</a></li>
<li><a href="#test=API/shareimage">Share Image</a></li>
<li><a href="#test=API/shareurl">Share URL</a></li>
<li><a href="#test=API/viewphoto">View Photo</a></li>
<li><a href="#test=API/search">Search keyword</a></li>
<li><a href="#test=API/secure_element">Secure Element (Wallet)</a></li><li>
</li></ul>
</section>
</div>
<div id="HW-tests" class="page invisible">
<section role="region">
<gaia-header>
<h1 class="header-title">HW Tests</h1>
</gaia-header>
</section>
<section data-type="list">
<ul class="sublist">
<li><a href="#test=HW/battery">Battery</a></li>
<li><a href="#test=HW/bluetooth">Bluetooth</a></li>
<li><a href="#test=HW/camera">Camera</a></li>
<li><a href="#test=HW/fmradio">FM radio</a></li>
<li><a href="#test=HW/headphone">Headphone</a></li>
<li><a href="#test=HW/lcd">LCD</a></li>
<li><a href="#test=HW/physicalkeys">Physical keys</a></li>
<li><a href="#test=HW/radio">Radio information</a></li>
<li><a href="#test=HW/ringer">Ringer</a></li>
<li><a href="#test=HW/sdcards">SD cards</a></li>
<li><a href="#test=HW/sensors">Sensors</a></li>
<li><a href="#test=HW/touchscreen">Touch screen</a></li>
<li><a href="#test=HW/vibrator">Vibrator</a></li>
<li><a href="#test=HW/wifi">Wi-Fi</a></li>
</ul>
</section>
</div>
<div id="tabs" class="tabs">
<ul role="tablist" class="bb-tablist" data-items="3">
<li id="panel1" role="presentation">
<a href="#UI" id="UI" role="tab" aria-controls="UIpanel">UI</a>
<div id="UIpanel" class="bb-tabpanel" role="tabpanel" aria-labelledby="UI"></div>
</li>
<li id="panel2" role="presentation">
<a href="#API" id="API" role="tab" aria-controls="APIpanel">API</a>
<div id="APIpanel" class="bb-tabpanel" role="tabpanel" aria-labelledby="API"></div>
</li>
<li id="panel3" role="presentation">
<a href="#HW" id="HW" role="tab" aria-controls="HWpanel">HW</a>
<div id="HWpanel" class="bb-tabpanel" role="tabpanel" aria-labelledby="HW"></div>
</li>
</ul>
</div>
</div>
<div id="test-panel" class="panel test-panel">
<section role="region">
<gaia-header id="test-panel-header" action="back">
<h1 id="test-panel-title" class="header-title"></h1>
</gaia-header>
</section>
<iframe id="test-iframe" mozallowfullscreen="true"></iframe>
</div>
</body>
</html>

View file

@ -0,0 +1,20 @@
'use strict';
var clickHandlers = {
'play': function play() {
var src = this.dataset.src;
var ringtonePlayer = new Audio();
ringtonePlayer.loop = true;
var selectedSound = '../../data/ringtones/' + src;
ringtonePlayer.src = selectedSound;
ringtonePlayer.play();
window.setTimeout(function _pauseRingtone() {
ringtonePlayer.pause();
}, 20000);
}
};
document.body.addEventListener('click', function(evt) {
if (clickHandlers[evt.target.id || evt.target.dataset.fn])
clickHandlers[evt.target.id || evt.target.dataset.fn].call(evt.target, evt);
});

View file

@ -0,0 +1,15 @@
'use strict';
var clickHandlers = {
'go': function _go() {
var URL = document.getElementById('URL').value;
var a = new MozActivity({ name: 'view', data: {type: 'url', url: URL }});
a.onsuccess = function() { alert('Success!'); };
a.onerror = function() { alert('Failure going to URL'); };
}
};
document.body.addEventListener('click', function(evt) {
if (clickHandlers[evt.target.id || evt.target.dataset.fn])
clickHandlers[evt.target.id || evt.target.dataset.fn].call(this, evt);
});

View file

@ -0,0 +1,12 @@
var composeEmail = document.querySelector('#compose-email');
if (composeEmail) {
composeEmail.onclick = function() {
var createEmail = new MozActivity({
name: 'new', // Possibly compose-mail in future versions
data: {
type: 'mail',
url: 'mailto:example@example.org'
}
});
};
}

View file

@ -0,0 +1,370 @@
'use stricts';
/**
* Just a couple of testing tools for the Contacts app.
*
* Right now, it just allows you to insert a large number of fake contacts
* into the database, and then clear the database.
*/
var ContactsTest = {
get loadButton() {
delete this.loadButton;
return this.loadButton = document.getElementById('insert-contacts');
},
get clearButton() {
delete this.clearButton;
return this.clearButton = document.getElementById('clear-contacts');
},
get getButton() {
delete this.getButton;
return this.getButton = document.getElementById('get-contacts');
},
get pickActivityButton() {
delete this.pickActivityButton;
return this.pickActivityButton = document.getElementById('activities-pick');
},
get newActivityButton() {
delete this.newActivityButton;
return this.newActivityButton = document.getElementById('activities-new');
},
get updateExistingActivityButton() {
delete this.updateExistingActivityButton;
return this.updateExistingActivityButton =
document.getElementById('activities-update');
},
get updateExistingActivityEmailButton() {
delete this.updateExistingActivityEmailButton;
return this.updateExistingActivityEmailButton =
document.getElementById('activities-update-email');
},
get updateExistingActivityEmailPhoneButton() {
delete this.updateExistingActivityEmailPhoneButton;
return this.updateExistingActivityEmailPhoneButton =
document.getElementById('activities-update-email-phone');
},
get newWithDataActivityButton() {
delete this.newWithDataActivityButton;
return this.newWithDataActivityButton =
document.getElementById('activities-new-data');
},
get insertSocialContacts() {
delete this.insertSocialContacts;
return this.insertSocialContacts =
document.getElementById('insert-social-contacts');
},
init: function ct_init() {
this.loadButton.addEventListener('click', this.loadContacts.bind(this));
this.clearButton.addEventListener('click', this.clearContacts.bind(this));
this.getButton.addEventListener('click', this.getContacts.bind(this));
this.pickActivityButton.addEventListener('click',
this.pickActivity.bind(this));
this.newActivityButton.addEventListener('click',
this.newActivity.bind(this));
this.updateExistingActivityButton.addEventListener('click',
this.updateExistingActivity.bind(this));
this.updateExistingActivityEmailButton.addEventListener('click',
this.updateExistingActivityEmail.bind(this));
this.updateExistingActivityEmailPhoneButton.addEventListener('click',
this.updateExistingActivityEmailPhone.bind(this));
this.newWithDataActivityButton.addEventListener('click',
this.newWithDataActivity.bind(this));
this.insertSocialContacts.addEventListener('click',
this.finsertSocialContacts.bind(this));
},
uninit: function ct_uninit() {
this.loadButton.removeEventListener('click', this.loadContacts.bind(this));
this.clearButton.removeEventListener('click',
this.clearContacts.bind(this));
this.getButton.removeEventListener('click', this.getContacts.bind(this));
this.newActivityButton.removeEventListener('click',
this.newActivity.bind(this));
this.updateExistingActivityButton.removeEventListener('click',
this.updateExistingActivity.bind(this));
},
clearContacts: function ct_clearContacts() {
if (!confirm('This will wipe out ALL of the contacts in the database. ' +
'Are you sure?'))
return;
// Ok, we're really doing this.
var req = window.navigator.mozContacts.clear();
req.onsuccess = function() {
alert('Contacts deleted.');
};
req.onerror = function() {
alert('Problem deleting contacts');
};
},
getContacts: function ct_getContacts() {
var options = {
sortBy: 'familyName',
sortOrder: 'ascending'
};
var start = new Date();
var req = window.navigator.mozContacts.find(options);
req.onsuccess = function() {
var duration = new Date() - start;
alert('Contacts received: ' + duration + 'msec');
};
req.onerror = function() {
alert('Problem receiving contacts');
};
},
setContactId: function ct_setContactId(id) {
this.contactId = id;
},
getContactId: function ct_getContactId() {
return this.contactId;
},
pickActivity: function ct_pickActivity() {
var activity = new MozActivity({
name: 'pick',
data: {
type: 'webcontacts/contact'
}
});
var self = this;
activity.onsuccess = function() {
var number = this.result.number;
document.getElementById('activities-result').innerHTML =
'Picked contact with number: ' + number;
};
activity.onerror = function() {
document.getElementById('activities-result').innerHTML =
'Activity canceled';
};
},
newActivity: function ct_newActivity() {
var activity = new MozActivity({
name: 'new',
data: {
type: 'webcontacts/contact'
}
});
var self = this;
activity.onsuccess = function() {
var contact = this.result.contact;
document.getElementById('activities-result').innerHTML =
'New contact' + ' create with id: ' + contact.id;
self.setContactId(contact.id);
};
activity.onerror = function() {
document.getElementById('activities-result').innerHTML =
'Activity canceled';
};
},
updateExistingActivityEmail: function ct_updateExistingActivityEmail() {
var activity = new MozActivity({
name: 'update',
data: {
type: 'webcontacts/contact',
params: {
'email': 'test@test.com'
}
}
});
var self = this;
activity.onsuccess = function() {
var contact = this.result.contact;
document.getElementById('activities-result').innerHTML =
'updated contact' + ' update with id: ' + contact.id;
self.setContactId(contact.id);
};
activity.onerror = function() {
document.getElementById('activities-result').innerHTML =
'Activity canceled';
};
},
updateExistingActivityEmailPhone: function() {
var activity = new MozActivity({
name: 'update',
data: {
type: 'webcontacts/contact',
params: {
'email': 'test@test.com',
'tel': '21312132'
}
}
});
var self = this;
activity.onsuccess = function() {
var contact = this.result.contact;
document.getElementById('activities-result').innerHTML =
'updated contact' + ' update with id: ' + contact.id;
self.setContactId(contact.id);
};
activity.onerror = function() {
document.getElementById('activities-result').innerHTML =
'Activity canceled';
};
},
updateExistingActivity: function ct_updateExistingActivity() {
var activity = new MozActivity({
name: 'update',
data: {
type: 'webcontacts/contact',
params: {
'tel': '4087469720'
}
}
});
var self = this;
activity.onsuccess = function() {
var contact = this.result.contact;
document.getElementById('activities-result').innerHTML =
'updated contact' + ' update with id: ' + contact.id;
self.setContactId(contact.id);
};
activity.onerror = function() {
document.getElementById('activities-result').innerHTML =
'Activity canceled';
};
},
newWithDataActivity: function ct_newActivity() {
var activity = new MozActivity({
name: 'new',
data: {
type: 'webcontacts/contact',
params: {
'tel': '555-555-555',
'email': 'cool-hunter@telefonica.es',
'address': 'San Francisco',
'note': 'This is a note',
'giveName': 'John',
'familyName': 'Orlock',
'company': 'Lost Industries'
}
}
});
var self = this;
activity.onsuccess = function() {
var contact = this.result.contact;
document.getElementById('activities-result').innerHTML =
'New contact' + ' create with id: ' + contact.id;
self.setContactId(contact.id);
};
activity.onerror = function() {
document.getElementById('activities-result').innerHTML =
'Activity canceled';
};
},
loadContacts: function ct_loadContacts() {
var req = new XMLHttpRequest();
req.overrideMimeType('application/json');
req.open('GET', '../../data/fakecontacts/fakecontacts.json', true);
req.onreadystatechange = function() {
// We will get a 0 status if the app is in app://
if (req.readyState === 4 && (req.status === 200 ||
req.status === 0)) {
var contacts = JSON.parse(req.responseText);
this._insertContacts(contacts);
}
};
req.onreadystatechange = req.onreadystatechange.bind(this);
this.loadButton.disabled = true;
req.send(null);
},
_insertContacts: function ct_insertContacts(aContacts) {
var self = this;
var cs = new ContactsSaver(aContacts);
cs.start();
cs.onsuccess = function() {
self.loadButton.disabled = false;
};
cs.onsaved = function(n) {
self._setInsertionCount(n, aContacts.length);
};
cs.onerror = function(c, e) {
Components.utils.reportError('Could not add contact with name: ' +
c.familyName[0]);
};
},
_setInsertionCount: function ct__setInsertionCount(aSoFar, aTotal) {
var insertionEl = document.getElementById('insertion-count');
insertionEl.textContent = aSoFar + ' / ' + aTotal;
},
_error: function ct__error(c) {
Components.utils.reportError('Could not add contact with name: ' +
c.familyName[0]);
},
finsertSocialContacts: function ct_finsertSocialContacts() {
var xhr = new XMLHttpRequest();
xhr.overrideMimeType('application/json');
xhr.open('GET', '../../data/fakesocialcontacts/contacts_social.json', true);
xhr.onload = function(e) {
// We will get a 0 status if the app is in app://
if (xhr.status === 200 || xhr.status === 0) {
var cdata = JSON.parse(xhr.responseText);
this._insertSocialContacts(cdata.data);
}
};
xhr.onload = xhr.onload.bind(this);
xhr.send(null);
},
_insertSocialContacts: function ct__insertSocialContacts(contacts) {
var cs = new ContactsSaver(contacts);
cs.start();
var self = this;
cs.onsuccess = function() { window.alert('Added!'); };
cs.onerror = function(e, c) {
self._error(c);
};
}
};
window.addEventListener('load', ContactsTest.init.bind(ContactsTest));
window.addEventListener('unload', ContactsTest.uninit.bind(ContactsTest));

View file

@ -0,0 +1,47 @@
'use strict';
function ContactsSaver(data) {
this.data = data;
var next = 0;
var self = this;
this.start = function() {
saveContact(data[0]);
};
function saveContact(cdata) {
if (cdata.bday) {
cdata.bday = new Date(cdata.bday);
}
if (cdata.anniversary) {
cdata.anniversary = new Date(cdata.anniversary);
}
var contact = new mozContact(cdata);
var req = navigator.mozContacts.save(contact);
req.onsuccess = function(e) {
if (typeof self.onsaved === 'function') {
self.onsaved(next + 1);
}
continuee();
};
req.onerror = function(e) {
if (typeof self.onerror === 'function') {
self.onerror(self.data[next], e.target.error);
}
};
}
function continuee() {
next++;
if (next < self.data.length) {
saveContact(self.data[next]);
}
else {
// End has been reached
if (typeof self.onsuccess === 'function') {
self.onsuccess();
}
}
}
}

View file

@ -0,0 +1,11 @@
var dial = document.querySelector('#dial');
if (dial) {
dial.onclick = function() {
var call = new MozActivity({
name: 'dial',
data: {
number: '+46777888999'
}
});
};
}

View file

@ -0,0 +1,84 @@
'use strict';
var Downloads = function Downloads() {
var deleteButton, statusButton;
var download = null;
var init = function init() {
deleteButton = document.getElementById('downloadDelete');
deleteButton.addEventListener('click', onDelete);
statusButton = document.getElementById('downloadStatus');
statusButton.addEventListener('click', onStatus);
listen();
};
var onDelete = function onDelete(evt) {
LazyLoader.load(['/shared/js/download/download_helper.js'],
function helperLoaded() {
var request = DownloadHelper.remove(download);
request.onsuccess = function() {
alert('Download removed succesfully');
download = null;
deleteButton.disabled = true;
statusButton.disabled = true;
};
request.onerror = function(msg) {
alert('Error : ' + msg.message);
};
}
);
};
var onStatus = function onStatus(evt) {
if (download === null) {
alert('No download initiated');
} else {
var msg = 'Current download:\n';
msg += 'Total bytes: ' + download.totalBytes + '\n';
msg += 'Current bytes: ' + download.currentBytes + '\n';
msg += 'Url: ' + download.url + '\n';
msg += 'Path: ' + download.path + '\n';
try {
msg += 'Start time: ' + download.startTime + '\n';
} catch (e) {
msg += 'Start: unknown\n';
}
msg += 'Id: ' + download.id + '\n';
alert('Current download: ' + JSON.stringify(msg));
console.log(msg);
}
};
var listen = function listen() {
navigator.mozDownloadManager.ondownloadstart = downloadStartedListener;
};
var downloadStartedListener = function downloadStartedListener(evt) {
download = evt.download;
download.onstatechange = downloadChangeListener;
statusButton.disabled = false;
statusButton.textContent = 'Status: ' + download.state;
//console.log('State new download: ' + download.state);
};
var downloadChangeListener = function downloadChangeListener(evt) {
statusButton.textContent = 'Status: ' + download.state;
console.log('State change: ' + download.state);
if (download.state === 'succeeded') {
console.log('Download finished');
deleteButton.disabled = false;
}
};
return {
'init': init
};
}();
window.addEventListener('load', Downloads.init.bind(Downloads));

View file

@ -0,0 +1,118 @@
/**
* Tests for Firefox Accounts observer API
*
* navigator.mozId.watch(), .request(), and .logout().
*/
function unpackAssertion(assertion) {
var parts = assertion.split('.');
return {
header: atob(parts[0]),
claim: atob(parts[1]),
payload: atob(parts[3])
};
}
function FXATests() {
this._eventNum = 1;
}
FXATests.prototype = {
_setupCallbacks: function() {
if (this._running) {
return;
}
var self = this;
try {
navigator.mozId.watch({
wantIssuer: 'firefox-accounts',
onlogin: function(assertion) {
var unpacked = JSON.stringify(unpackAssertion(assertion), null, 2);
self.recordEvent('login', 'login',
{assertion: assertion, unpacked: unpacked});
},
onlogout: function() {
self.recordEvent('logout', 'logout');
},
onready: function() {
self.recordEvent('ready', 'ready');
},
onerror: function(error) {
self.recordEvent('error: ' + JSON.stringify(error), 'error');
}
});
} catch (error) {
this.recordEvent('error: ' + JSON.stringify(error), 'error');
}
},
/**
* Bind UI components
*/
_bindEvents: function() {
if (this._running) {
return;
}
var self = this;
var testElementHandlers = {
'request': function() {
navigator.mozId.request({
oncancel: function() {
self.recordEvent('cancel', 'cancel');
}
});
},
'logout': function() {
navigator.mozId.logout();
}
};
Object.keys(testElementHandlers).forEach(function(selector) {
document.getElementById(selector).addEventListener(
'click', testElementHandlers[selector]);
});
},
recordEvent: function(message, cklass, params) {
var li = document.createElement('li');
if (cklass) {
li.classList.add(cklass);
}
var events = document.getElementById('event-stream');
var html = message;
if (typeof params === 'object') {
Object.keys(params).forEach(function(key) {
html += '<div>' + key + ':</div>';
html += '<div class="' + key + '">' + params[key] + '</div>';
});
}
li.innerHTML = html;
events.appendChild(li);
this._eventNum += 1;
},
init: function() {
this._setupCallbacks();
this._bindEvents();
this._running = true;
this.recordEvent('Starting. Wait for "ready" message.');
},
};
window.addEventListener('DOMContentLoaded', function() {
var fxaTests = new FXATests();
fxaTests.init();
}, false);

View file

@ -0,0 +1,22 @@
function Location(position)//location function is defining with parameter
{
//Specifies the longitude estimate in decimal degrees.
//The value range is [-180.00, +180.00].
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
//latitude value is defining in label element where id is lati
document.getElementById('lati').innerHTML = latitude;
document.getElementById('longi').innerHTML = longitude;
}
document.getElementById('submit').addEventListener('click',
function findLocation() {
//checking browser compatibility
if (navigator.geolocation)
{
//getCurrentPosition method retrieve
//the current geographic location of the user
navigator.geolocation.getCurrentPosition(Location);
}
}
);

View file

@ -0,0 +1,220 @@
'use stricts';
// This is an indivisual gUM test item, may be audio, video, or both
function gUMItem() {
var video_status = false;
var audio_status = false;
var capturing = false;
var constrain;
var media;
var video_frame;
var video;
var snapshots_frames;
var audio;
var startButtons;
var stopButtons;
var snapshotButton;
var message;
var snapshots = [];
var saved_stream;
this.init = function(content, i) {
media = content.querySelector('[data-type="media"]');
video_frame = content.querySelector('[data-type="video-frame"]');
video = content.querySelector('video');
snapshots_frames = content.querySelector('[data-type="snapshots-frames"]');
audio = content.querySelector('audio');
startButtons = content.querySelector('[data-type="startbuttons"]');
stopButtons = content.querySelector('[data-type="stopbuttons"]');
snapshotButton = content.querySelector('[data-type="snapshotButton"]');
message = content.querySelector('[data-type="message"]');
// remove them first, we will add them back when we need them
media.removeChild(video_frame);
media.removeChild(audio);
// label button with index
content.querySelector('[data-type="startVideoButton"]').textContent += i;
content.querySelector('[data-type="startAudioButton"]').textContent += i;
content.querySelector('[data-type="startAudioVideoButton"]').
textContent += i;
content.querySelector('[data-type="startVideoButton"]').
addEventListener('click', startVideo);
content.querySelector('[data-type="startAudioButton"]').
addEventListener('click', startAudio);
content.querySelector('[data-type="startAudioVideoButton"]').
addEventListener('click', startAudioVideo);
content.querySelector('[data-type="startDoubleRequest"]').
addEventListener('click', startDoubleRequest);
content.querySelector('[data-type="stopButton"]').
addEventListener('click', stopMedia);
content.querySelector('[data-type="pauseButton"]').
addEventListener('click', pauseMedia);
content.querySelector('[data-type="snapshotButton"]').
addEventListener('click', startSnapshot);
};
function startVideo() {
startMedia({video: true});
}
function startAudioVideo() {
startMedia({video: true, audio: true});
}
function startAudio() {
startMedia({audio: true});
}
function startDoubleRequest() {
// empty request, just for prompt
function empty() {}
function success(stream) {
message.innerHTML = '<p class="success">Double prompt!</p>';
}
function error(err) {
message.innerHTML = '<p class="error">' + err + '</p>';
}
window.navigator.mozGetUserMedia({audio: true}, empty, error);
window.navigator.mozGetUserMedia({audio: true}, success, error);
}
function startMedia(param) {
constrain = param;
function success(stream) {
message.innerHTML = '<p class="success">Success!</p>';
stopButtons.style.display = 'block';
startButtons.style.display = 'none';
if ('video' in constrain) {
video_status = true;
media.appendChild(video_frame);
video.mozSrcObject = stream;
video.play();
snapshots_frames.innerHTML = '';
stopButtons.appendChild(snapshotButton);
}
if ('audio' in constrain) {
audio_status = true;
media.appendChild(audio);
audio.mozSrcObject = stream;
audio.play();
}
}
function error(err) {
if (video_status)
video_status = false;
message.innerHTML = '<p class="error">' + err + '</p>';
}
window.navigator.mozGetUserMedia(param, success, error);
}
function pauseMedia() {
if (saved_stream) {
if (video_status) {
video.mozSrcObject = saved_stream;
video.play();
} else if (audio_status) {
audio.mozSrcObject = saved_stream;
audio.play();
}
saved_stream = null;
} else {
if (video_status) {
video.pause();
saved_stream = video.mozSrcObject;
video.mozSrcObject = null;
} else if (audio_status) {
audio.pause();
saved_stream = audio.mozSrcObject;
audio.mozSrcObject = null;
}
}
}
function stopMedia() {
message.innerHTML = '';
if (video_status) {
video.mozSrcObject.stop();
video.mozSrcObject = null;
snapshots = [];
snapshots_frames.innerHTML = '';
media.removeChild(video_frame);
stopButtons.removeChild(snapshotButton);
snapshotButton.textContent = 'Snapshot';
capturing = false;
video_status = false;
}
if (audio_status) {
audio.mozSrcObject.stop();
audio.mozSrcObject = null;
media.removeChild(audio);
audio_status = false;
}
saved_stream = null;
stopButtons.style.display = 'none';
startButtons.style.display = 'block';
}
function startSnapshot() {
capturing = !capturing;
if (capturing) {
captureImage();
snapshotButton.textContent = 'Stop Snapshot';
} else {
snapshotButton.textContent = 'Snapshot';
}
}
function captureImage() {
if (video_status && capturing) {
//dump('Capturing len ' + snapshots.length + '\n');
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = video.videoWidth / 4;
canvas.height = video.videoHeight / 4;
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
if (snapshots.unshift(canvas) > 4)
snapshots.length = 4;
snapshots_frames.innerHTML = '';
for (var j = 0; j < snapshots.length; j++) {
snapshots_frames.appendChild(snapshots[j]);
}
setTimeout(captureImage, 2000);
}
}
}
// You can modify max_instances to test more than one gUM instance
function gUMTest() {
var max_instances = 2;
var gUMItems = [];
(function init() {
var contents = document.getElementById('contents');
for (var i = 0; i < max_instances; i++) {
gUMItems[i] = new gUMItem;
// display elements
// first copy one from template then modify it.
var template = document.getElementById('content_template');
var content = template.cloneNode(true);
content.id = 'content' + (i + 1);
content.classList.remove('hidden');
gUMItems[i].init(content, (i + 1));
contents.appendChild(content);
}
})();
}
window.addEventListener('load', gUMTest);

View file

@ -0,0 +1,111 @@
/**
* In these tests, we exercise the native navigator.mozId.get method.
*/
function getUnverifiedOk() {
return document.getElementById('unverified-ok').checked;
}
function getIssuerName() {
return document.getElementById('issuer-name').value.trim();
}
function unpackAssertion(assertion) {
var parts = assertion.split('.');
return {
header: atob(parts[0]),
claim: atob(parts[1]),
payload: atob(parts[3])
};
}
function IdentityTests() {
/**
* recordEvent: make a note in the event stream
*
* message is the main text of the note
* cklass is the css class to apply to the enclosing li
* params is an optional dictionary of strings to append as divs
*/
this._eventNum = 1;
this.recordEvent = function id_recordEvent(message, cklass, params) {
var li = document.createElement('li');
if (cklass) {
li.classList.add(cklass);
}
var events = document.getElementById('event-stream');
var html = message;
if (typeof params === 'object') {
Object.keys(params).forEach(function(key) {
html += '<div>' + key + ':</div>';
html += "<div class='" + key + "'>" + params[key] + '</div>';
});
}
li.innerHTML = html;
events.appendChild(li);
this._eventNum += 1;
};
this.gotAssertion = function id_gotAssertion(assertion) {
var result = {assertion: assertion};
if (assertion) {
result.unpacked = JSON.stringify(unpackAssertion(assertion), null, 2);
}
this.recordEvent('Assertion', 'assertion', result);
};
/**
* Bind UI components
*/
this._bindEvents = function id__bindEvents() {
if (this._running) return;
var gotAssertion = this.gotAssertion.bind(this);
var self = this;
var testElementHandlers = {
't-get': function() {
navigator.mozId.get(gotAssertion);
},
't-get-allowUnverified': function() {
navigator.mozId.get(gotAssertion, {
allowUnverified: getUnverifiedOk()
});
},
't-get-forceIssuer': function() {
navigator.mozId.get(gotAssertion, {
forceIssuer: getIssuerName()
});
},
't-get-forceIssuer-allowUnverified': function() {
navigator.mozId.get(gotAssertion, {
allowUnverified: getUnverifiedOk(),
forceIssuer: getIssuerName()
});
}
};
Object.keys(testElementHandlers).forEach(function(selector) {
document.getElementById(selector).addEventListener(
'click', testElementHandlers[selector]);
});
};
this.init = function id_init() {
this._bindEvents();
this._running = true;
this.recordEvent('Tests are ready');
};
}
window.addEventListener('DOMContentLoaded', function() {
var identityTests = new IdentityTests();
identityTests.init();
}, false);

View file

@ -0,0 +1,143 @@
/**
* The IdentityTests are meant to be exercised from marionette as part of the
* gaia-ui-tests suites.
*
* In these tests, we exercise the native navigator.mozId directly.
*/
function getUnverifiedOk() {
return document.getElementById('unverified-ok').checked;
}
function getIssuerName() {
return document.getElementById('issuer-name').value.trim();
}
function unpackAssertion(assertion) {
var parts = assertion.split('.');
return {
header: atob(parts[0]),
claim: atob(parts[1]),
payload: atob(parts[3])
};
}
function IdentityTests() {
/**
* recordEvent: make a note in the event stream
*
* message is the main text of the note
* cklass is the css class to apply to the enclosing li
* params is an optional dictionary of strings to append as divs
*/
this._eventNum = 1;
this.recordEvent = function id_recordEvent(message, cklass, params) {
var li = document.createElement('li');
if (cklass) {
li.classList.add(cklass);
}
var events = document.getElementById('event-stream');
var html = message;
if (typeof params === 'object') {
Object.keys(params).forEach(function(key) {
html += '<div>' + key + ':</div>';
html += "<div class='" + key + "'>" + params[key] + '</div>';
});
}
li.innerHTML = html;
events.appendChild(li);
this._eventNum += 1;
};
this._setupCallbacks = function id__setupCallbacks() {
if (this._running) return;
var self = this;
try {
navigator.mozId.watch({
onlogin: function(assertion) {
var unpacked = JSON.stringify(unpackAssertion(assertion), null, 2);
self.recordEvent('login', 'login',
{assertion: assertion, unpacked: unpacked});
},
onlogout: function() {
self.recordEvent('logout', 'logout');
},
onready: function() {
self.recordEvent('ready', 'ready');
}
});
} catch (err) {
this.recordEvent('Error: ' + err, 'error');
}
};
/**
* Bind UI components
*/
this._bindEvents = function id__bindEvents() {
if (this._running) return;
var self = this;
var testElementHandlers = {
't-request': function() {
navigator.mozId.request();
},
't-request-withOnCancel': function() {
navigator.mozId.request({oncancel: function() {
self.recordEvent('cancel', 'cancel');
}
});
},
't-request-allowUnverified': function() {
navigator.mozId.request({
allowUnverified: getUnverifiedOk()
});
},
't-request-forceIssuer': function() {
navigator.mozId.request({
forceIssuer: getIssuerName()
});
},
't-request-forceIssuer-allowUnverified': function() {
navigator.mozId.request({
allowUnverified: getUnverifiedOk(),
forceIssuer: getIssuerName()
});
},
't-logout': function() {
navigator.mozId.logout();
}
};
Object.keys(testElementHandlers).forEach(function(selector) {
document.getElementById(selector).addEventListener(
'click', testElementHandlers[selector]);
});
};
this.init = function id_init() {
this._setupCallbacks();
this._bindEvents();
this._running = true;
this.recordEvent('Ready to rock');
};
}
window.addEventListener('DOMContentLoaded', function() {
var identityTests = new IdentityTests();
identityTests.init();
}, false);

View file

@ -0,0 +1,17 @@
document.getElementById('go').onclick = function _go() {
var a = new MozActivity(
{
name: 'test-inline',
data: {
}
}
);
a.onsuccess = function() {
document.getElementById('result').textContent = this.result.text;
};
a.onerror = function() {
document.getElementById('result').textContent = '(canceled)';
};
};

View file

@ -0,0 +1,5 @@
document.getElementById('go').onclick = function _go() {
var a = new MozActivity({ name: 'test', data: {type: 'text'}});
a.onsuccess = function() { alert('Success!'); };
a.onerror = function() { alert('Failure.'); };
};

View file

@ -0,0 +1,82 @@
var open = document.getElementById('open');
var opensavefile = document.getElementById('opensavefile');
var opensaveblob = document.getElementById('opensaveblob');
open.hidden = true;
opensavefile.hidden = true;
opensaveblob.hidden = true;
open.onclick = function() {
var a = new MozActivity({
name: 'open',
data: {
type: 'audio/ogg',
blob: localblob,
title: 'jonobacon-freesoftwaresong2.ogg'
}
});
a.onsuccess = function() {
console.log('activity returns', JSON.stringify(a.result));
};
};
opensavefile.onclick = function() {
var a = new MozActivity({
name: 'open',
data: {
type: 'audio/ogg',
blob: localblob,
allowSave: true,
filename: 'downloads/uitest/jonobacon-freesoftwaresong2.ogg'
}
});
a.onsuccess = function() {
console.log('activity returns', JSON.stringify(a.result));
};
};
opensaveblob.onclick = function() {
var a = new MozActivity({
name: 'open',
data: {
type: 'audio/ogg',
blob: remoteblob,
allowSave: true,
filename: 'downloads/uitest/Back_In_Black-sample.ogg'
}
});
a.onsuccess = function() {
console.log('activity returns', JSON.stringify(a.result));
};
};
// The audio we want to open
var localblob;
var remoteblob;
var localURL = '../../data/audio/jonobacon-freesoftwaresong2.ogg';
var remoteURL = 'http://upload.wikimedia.org/wikipedia/en/4/45/' +
'ACDC_-_Back_In_Black-sample.ogg';
var localxhr = new XMLHttpRequest();
localxhr.open('GET', localURL);
localxhr.responseType = 'blob';
localxhr.send();
localxhr.onload = function() {
localblob = new Blob([localxhr.response], { type: 'audio/ogg' });
open.hidden = false;
opensavefile.hidden = false;
};
localxhr.onerror = function() {
console.log('Error loading test audio', localxhr.error.name);
};
var remotexhr = new XMLHttpRequest();
remotexhr.open('GET', remoteURL);
remotexhr.responseType = 'blob';
remotexhr.send();
remotexhr.onload = function() {
remoteblob = new Blob([remotexhr.response], { type: 'audio/ogg' });
opensaveblob.hidden = false;
};
remotexhr.onerror = function() {
console.log('Error loading test audio from network', remotexhr.error.name);
};

View file

@ -0,0 +1,49 @@
var open = document.getElementById('open');
var opensave = document.getElementById('opensave');
open.hidden = true;
opensave.hidden = true;
open.onclick = function() {
var a = new MozActivity({
name: 'open',
data: {
type: 'video/3gpp',
blob: videoblob,
title: 'Stapler'
}
});
a.onsuccess = function() {
console.log('activity returns', JSON.stringify(a.result));
};
};
opensave.onclick = function() {
var a = new MozActivity({
name: 'open',
data: {
type: 'video/3gpp',
blob: videoblob,
allowSave: true,
filename: 'downloads/uitest/stapler.3gp'
}
});
a.onsuccess = function() {
console.log('activity returns', JSON.stringify(a.result));
};
};
// The image we want to view
var videoblob;
var xhr = new XMLHttpRequest();
xhr.open('GET', '../../data/video/stapler.3gp');
xhr.responseType = 'blob';
xhr.send();
xhr.onload = function() {
videoblob = new Blob([xhr.response], { type: 'video/3gpp' });
open.hidden = false;
opensave.hidden = false;
};
xhr.onerror = function() {
console.log('Error loading test video', xhr.error.name);
};

View file

@ -0,0 +1,56 @@
function orientationTest(evt) {
switch (evt.target.id) {
case 'lock1':
screen.mozLockOrientation(['portrait']);
break;
case 'lock2':
screen.mozLockOrientation(['landscape']);
break;
case 'lock3':
screen.mozLockOrientation(['portrait-primary']);
break;
case 'lock4':
screen.mozLockOrientation(['portrait-secondary']);
break;
case 'lock5':
screen.mozLockOrientation(['landscape-primary']);
break;
case 'lock6':
screen.mozLockOrientation(['landscape-secondary']);
break;
case 'lock7':
screen.mozLockOrientation(['portrait', 'landscape-primary']);
break;
case 'lock8':
screen.mozLockOrientation(['portrait', 'landscape-secondary']);
break;
case 'lock9':
screen.mozLockOrientation(['landscape', 'portrait-primary']);
break;
case 'lock10':
screen.mozLockOrientation(['landscape', 'portrait-secondary']);
break;
case 'lock11':
screen.mozLockOrientation(['portrait-primary', 'landscape-primary']);
break;
case 'lock12':
screen.mozLockOrientation(['portrait-secondary', 'landscape-primary']);
break;
case 'lock13':
screen.mozLockOrientation(['portrait-primary', 'landscape-secondary']);
break;
case 'lock14':
screen.mozLockOrientation(['portrait-secondary', 'landscape-secondary']);
break;
case 'unlock':
screen.mozUnlockOrientation();
break;
}
}
window.addEventListener('load', function() {
var buttons = document.getElementsByTagName('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', orientationTest);
}
});

View file

@ -0,0 +1,63 @@
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil -*- */
/* vim: set ft=javascript sw=2 ts=2 autoindent cindent expandtab: */
'use strict';
/**
Mock payment provider JWT
{ "iss": "323d34dc-b5cf-4822-8e47-6a4515dc74db",
"request": {
"description": "The forbidden fruit",
"id": "af1f960a-3f90-4e2d-a20f-d5170aee49f2",
"postbackURL": "https://inapp-pay-test.paas.allizom.org/mozpay/postback",
"productData": "localTransID=6fc8f207-da3f-440f-9977-eb613aae0a00",
"chargebackURL": "https://inapp-pay-test.paas.allizom.org/mozpay/chargeback",
"name": "Virtual Kiwi"
},
"typ": "tests/payments/pay/v1",
"aud": "ferjm.github.io/gaia-mock-payment-provider"
}
*/
const mockJWT = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIzMjNkMzRkYy1iNWNmLTQ4MjItOGU0Ny02YTQ1MTVkYzc0ZGIiLCJhdWQiOiJmZXJqbS5naXRodWIuaW8vZ2FpYS1tb2NrLXBheW1lbnQtcHJvdmlkZXIiLCJ0eXAiOiJ0ZXN0cy9wYXltZW50cy9wYXkvdjEiLCJyZXF1ZXN0Ijp7ImlkIjoiYWYxZjk2MGEtM2Y5MC00ZTJkLWEyMGYtZDUxNzBhZWU0OWYyIiwibmFtZSI6IlZpcnR1YWwgS2l3aSIsImRlc2NyaXB0aW9uIjoiVGhlIGZvcmJpZGRlbiBmcnVpdCIsInByb2R1Y3REYXRhIjoibG9jYWxUcmFuc0lEPTZmYzhmMjA3LWRhM2YtNDQwZi05OTc3LWViNjEzYWFlMGEwMCIsInBvc3RiYWNrVVJMIjoiaHR0cHM6Ly9pbmFwcC1wYXktdGVzdC5wYWFzLmFsbGl6b20ub3JnL21venBheS9wb3N0YmFjayIsImNoYXJnZWJhY2tVUkwiOiJodHRwczovL2luYXBwLXBheS10ZXN0LnBhYXMuYWxsaXpvbS5vcmcvbW96cGF5L2NoYXJnZWJhY2sifX0.DlHejmKRlVPLYO-HVAYDzqOcv5eXMsVlpnozcADpnh4';
// Invalid typ JWT
const invalidJWT = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.IntcImF1ZFwiOiBcImNvbm5lY3QucWEtb3BlbnRlbC0wNC5oaS5pbmV0XCIsIFwiaXNzXCI6IFwiMzRYVjM3QkRSQkJGNEtaQ1M5UVVcIiwgXCJyZXF1ZXN0XCI6IHtcIm5hbWVcIjogXCJQaWNlIG9mIENha2VcIiwgXCJwcmljZVwiOiAxMC41LCBcInByaWNlVGllclwiOiAxLCBcInByb2R1Y3RkYXRhXCI6IFwidHJhbnNhY3Rpb25faWQ9MTJcIiwgXCJjdXJyZW5jeUNvZGVcIjogXCJVU0RcIiwgXCJkZXNjcmlwdGlvblwiOiBcIlZpcnR1YWwgY2hvY29sYXRlIGNha2UgdG8gZmlsbCB5b3VyIHZpcnR1YWwgdHVtbXlcIn0sIFwiZXhwXCI6IDEzNDIwMDMwNzA1MTMsIFwiaWF0XCI6IDEzNDIwMDMwNzQxMTMsIFwidHlwXCI6IFwicGF5bWVudHMvaW5hcHAvdjFcIn0i.I5rFwoBtgyTltMJ_11rHOgcto-HdFbYlIOgVOVSlJe0';
var PayTests = {
init: function pt_init() {
window.addEventListener('DOMContentLoaded', function() {
var jwts = {
'test-pmpp': [mockJWT],
'test-invalid': [invalidJWT],
'test-nojwt': undefined,
'test-repeated': [mockJWT, mockJWT]
};
['test-pmpp',
'test-invalid',
'test-nojwt',
'test-repeated'].forEach(function(id) {
document.getElementById(id).addEventListener('click', function() {
PayTests.pay(jwts[id]);
});
});
});
},
pay: function pt_pay(JWTs) {
var request = navigator.mozPay(JWTs);
request.onsuccess = function onsuccess() {
document.getElementById('result').innerHTML = 'Payment success';
};
request.onerror = function onerror() {
console.log("Error " + request.error.name);
document.getElementById('result').innerHTML = 'Payment error ' +
request.error.name;
};
}
};
PayTests.init();

View file

@ -0,0 +1,13 @@
function pick(type) {
var data = {type: type};
var a = new MozActivity({ name: 'pick', data: data});
a.onsuccess = function(e) {
var result = document.getElementById('result');
result.textContent = a.result.name + ' ' + a.result.type + ' (' +
a.result.size + ')';
};
}
document.getElementById('b1').onclick = function() {
pick('application/*');
};

View file

@ -0,0 +1,25 @@
function pick(type, nocrop, width, height) {
var data = {type: type};
if (nocrop) data.nocrop = true;
if (width) data.width = width;
if (height) data.height = height;
var a = new MozActivity({ name: 'pick', data: data});
a.onsuccess = function(e) {
console.log('got blob of type', a.result.blob.type);
var url = URL.createObjectURL(a.result.blob);
var img = document.getElementById('result');
img.src = url;
img.onload = function() { URL.revokeObjectURL(url); };
};
a.onerror = function() { alert('Failure picking photo'); };
}
document.getElementById('b1').onclick = function() { pick('image/jpeg'); };
document.getElementById('b2').onclick = function() { pick('image/png'); };
document.getElementById('b3').onclick = function() {
pick(['image/jpeg', 'image/png']);
};
document.getElementById('b4').onclick = function() { pick('image/*'); };
document.getElementById('b5').onclick = function() { pick('image/*', true); };
document.getElementById('b6').onclick = function() { pick('image/*', false,
100, 100); };

View file

@ -0,0 +1,32 @@
function pick(type, nocrop, width, height) {
var data = {type: type};
if (nocrop) data.nocrop = true;
if (width) data.width = width;
if (height) data.height = height;
var a = new MozActivity({ name: 'pick', data: data});
a.onsuccess = function(e) {
console.log('got blob of type', a.result.blob.type);
var url = URL.createObjectURL(a.result.blob);
var img = document.getElementById('result');
img.src = url;
img.onload = function() { URL.revokeObjectURL(url); };
};
a.onerror = function() { alert('Failure picking'); };
}
document.getElementById('b1').onclick = function() {
pick(['video/*', 'image/*']);
};
document.getElementById('b2').onclick = function() {
pick(['image/*', 'video/*']);
};
document.getElementById('b3').onclick = function() {
pick(['video/*', 'image/jpg']);
};
document.getElementById('b4').onclick = function() {
pick(['video/*', 'image/png']);
};
document.getElementById('b5').onclick = function() {
pick(['video/3gpp', 'image/jpg']);
};

View file

@ -0,0 +1,18 @@
function pick(type, nocrop, width, height) {
var data = {type: type};
if (nocrop) data.nocrop = true;
if (width) data.width = width;
if (height) data.height = height;
var a = new MozActivity({ name: 'pick', data: data});
a.onsuccess = function(e) {
console.log('got blob of type', a.result.blob.type);
var url = URL.createObjectURL(a.result.blob);
var img = document.getElementById('result');
img.src = url;
img.onload = function() { URL.revokeObjectURL(url); };
};
a.onerror = function() { alert('Failure picking video'); };
}
document.getElementById('b1').onclick = function() { pick('video/3gpp'); };
document.getElementById('b2').onclick = function() { pick('video/*'); };

View file

@ -0,0 +1,48 @@
'use strict';
window.addEventListener('DOMContentLoaded', function() {
function log(aMsg) {
console.log('-*- mdns.js : ' + aMsg + '\n');
}
if (typeof navigator.mozPresentationDeviceInfo !== 'undefined') {
navigator.mozPresentationDeviceInfo.addEventListener('devicechange',
function(e) {
log('device changed');
getAll();
});
document.getElementById('discovery').onclick = function _discovery() {
log('discovery');
navigator.mozPresentationDeviceInfo.forceDiscovery();
getAll();
};
getAll();
}
function appendDevice(device) {
var node = document.createElement('li');
var textnode = document.createTextNode(device.name);
node.appendChild(textnode);
document.getElementById('devices').appendChild(node);
}
function clearDevices() {
var devNode = document.getElementById('devices');
while (devNode.firstChild) {
devNode.removeChild(devNode.firstChild);
}
}
function getAll() {
log('getAll');
navigator.mozPresentationDeviceInfo.getAll().then(function(devices) {
log('devices found: ' + devices.length);
clearDevices();
for (var device of devices) {
appendDevice(device);
}
});
}
});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,14 @@
var search = document.querySelector('#search');
if (search) {
search.onclick = function() {
var search_area = document.querySelector('#search_area');
console.log('Search:' + search_area.value);
var searching = new MozActivity({
name: 'search',
data: {
type: 'text/plain',
keyword: search_area.value
}
});
};
}

View file

@ -0,0 +1,179 @@
'use strict';
/* globals Utils, AID, APDU */
var SIMAccessManager = {
ERROR_MSG: {
NOT_IDLE: 'WRONG SIM ACCESS STATE',
ACCESS_FAILED: 'FAILED TO ENABLE SIM ACCESS',
APPLETS_FAILED: 'FAILED TO GET APPLETS DATA'
},
SIM_ACCESS_STATES: {
DISABLED: 'disabled',
IDLE: 'idle',
BUSY: 'busy'
},
_simState: null,
_seSession: null,
start: function start() {
if(!navigator.seManager) {
return Promise.reject('Securel Element API not present');
}
return navigator.seManager.getSEReaders()
.then((readers) => {
return readers[0].openSession();
})
.then((session) => {
this._seSession = session;
this._simState = this.SIM_ACCESS_STATES.IDLE;
})
.catch(() => {
this._simState = this.SIM_ACCESS_STATES.DISABLED;
return Promise.reject(this.ERROR_MSG.ACCESS_FAILED);
});
},
stop: function stop() {
return this._seSession.closeAll()
.then(() => {
this._seSession = null;
this._simState = this.SIM_ACCESS_STATES.DISABLED;
});
},
getAppletsData: function getAppletsData() {
if(this._simState !== this.SIM_ACCESS_STATES.IDLE) {
return Promise.reject(this.ERROR_MSG.NOT_IDLE);
}
this._simState = this.SIM_ACCESS_STATES.BUSY;
return this._seSession.openLogicalChannel(AID.CRS)
.then((channel) => {
return channel.transmit(APDU.CRS.getStatusAll1st);
})
.then((resp) => {
var receivedData = new Uint8Array();
// we can get different responses, also propriatary
// using counter to not allow to try to query the SIM indifinately
var count = 0;
var condition = (resp) =>
(count++ > 10 || resp.sw1 === 0x90 && resp.sw2 === 0x00);
var action = (resp) => {
receivedData = Utils.joinUint8Arrays(receivedData, resp.data);
return resp.channel.transmit(APDU.CRS.getStatusAllNext)
};
return Utils.whilePromise(condition, action, resp)
.then((lastResp) => {
lastResp.channel.close();
this._simState = this.SIM_ACCESS_STATES.IDLE;
return Utils.joinUint8Arrays(receivedData, lastResp.data);
});
})
.catch((e) => {
return this._cleanup(this.ERROR_MSG.APPLETS_FAILED);
});
},
toggleApplet: function toggleApplet(on, aid) {
if(this._simState !== this.SIM_ACCESS_STATES.IDLE) {
return Promise.resolve(false);
}
this._simState = this.SIM_ACCESS_STATES.BUSY;
var apdu = on ? APDU.CRS.activateCLF(aid) : APDU.CRS.deactivateCLF(aid);
return this._seSession.openLogicalChannel(AID.CRS)
.then((channel) => channel.transmit(apdu))
.then((resp) => {
return resp.channel.close()
.then(() => {
this._simState = this.SIM_ACCESS_STATES.IDLE;
return resp.sw1 === 0x90 && resp.sw2 === 0x00;
});
});
},
_cleanup: function cleanup(message) {
this._simState = this.SIM_ACCESS_STATES.IDLE;
return Promise.reject(message);
}
};
var WalletDemo = {
init: function init() {
if(!navigator.seManager) {
this._updateStatus('Secure Element API not available.');
return;
}
SIMAccessManager.start()
.then(() => {
this._updateStatus('Init SIM access, getting applets from CRS');
return this._readDataRefreshUI();
})
.catch((error) => {
this._updateStatus('Init failed.', error);
SIMAccessManager.stop();
});
},
_readDataRefreshUI: function readDataRefreshUI() {
return SIMAccessManager.getAppletsData()
.then((appletsData) => {
// parsing and filtering to get payment enabled applets
var list = Utils.parseAppletsData(appletsData).filter((applet) => {
return applet.state === '1F01' || applet.state === '1F00';
});
this._updateStatus(list.length + ' applets read from SIM');
this._updateUI(list);
});
},
_updateUI: function updateUI(parsedApplets) {
var div = document.querySelector('#applets-view div');
div.innerHTML = '';
parsedApplets.forEach((applet) => {
var button = document.createElement('button');
var state = (applet.state === '1F01') ? 'active' : 'not active' ;
button.innerHTML = '<p>' + applet.aid + '</p>' +
'<p> contactless state: ' + state + '</p>';
button.addEventListener('click',
() => this._toggleApplet(applet.state === '1F00', applet.aid));
div.appendChild(button);
});
},
_toggleApplet: function toggleApplet(activate, aid) {
SIMAccessManager.toggleApplet(activate, aid)
.then((success) => {
if(success) {
this._updateStatus(aid + ' ' + ((activate) ? 'active' : 'not active'));
return this._readDataRefreshUI();
}
})
.catch((error) => {
this._updateStatus('Failed to toggle applets', error);
});
},
_updateStatus: function showStatus(status, error) {
console.log(status);
var selector = '.success';
if(error) {
console.log(error);
selector = '.error';
status += ', error: ' + error;
}
document.querySelector(selector).textContent = status;
},
};
window.addEventListener('load', () => WalletDemo.init());

View file

@ -0,0 +1,122 @@
'use strict';
/* exported Utils, APDU, AID */
const Utils = {
byteToHexString: function byteToHexString(uint8arr) {
if (!uint8arr) {
return '';
}
var hexStr = '';
for (var i = 0; i < uint8arr.length; i++) {
var hex = (uint8arr[i] & 0xff).toString(16);
hex = (hex.length === 1) ? '0' + hex : hex;
hexStr += hex;
}
return hexStr.toUpperCase();
},
hexStringToByte: function hexStringToByte(str) {
var a = [];
for(var i = 0, len = str.length; i < len; i+=2) {
a.push(parseInt(str.substr(i,2),16));
}
return new Uint8Array(a);
},
joinUint8Arrays: function joinUint8Arrays() {
var args = Array.prototype.slice.call(arguments);
var length = args.reduce(function(a, b) { return a + b.length; }, 0);
var out = new Uint8Array(length);
args.reduce(function(previousLen, buffer) {
out.set(buffer, previousLen);
return previousLen + buffer.length;
}, 0);
return out;
},
whilePromise: function whilePromise(condition, action, init) {
return new Promise((resolve, reject) => {
var loop = (value) => {
if (condition(value)) {
return resolve(value);
}
return action(value).then(loop).catch(reject);
};
loop(init);
});
},
parseAppletsData: function parseAppletsData(data) {
var list = [];
for(var i = 0, len = data.length; i < len;) {
if(data[i] === 0x61) {
var end = i+2 + data[i+1];
var appletData = data.subarray(i+2, end);
var applet = this._parseSingleApplet(appletData);
if(applet) {
list.push(applet);
}
i = end;
}
}
return list;
},
_parseSingleApplet: function _parseSingleApplet(data) {
// checking for AID tag
if (data[0] === 0x4F) {
var applet = {
aid: this.byteToHexString(data.subarray(2, 2 + data[1])),
state: this.byteToHexString(data.subarray(data[1] + 5))
};
return applet;
}
},
};
/*
CRS AID, TAGS and APDUs based on:
GlobalPlatform Card Technology Contactless Services Card Specification v2.2
- Amendment C Version 1.1.1
*/
const AID = {
CRS: Utils.hexStringToByte('A00000015143525300')
};
const APDU = {
CRS: {
getStatusAll1st: { cla: 0x80, ins: 0xF2, p1: 0x40, p2: 0x00,
data: new Uint8Array([
0x4F, 0x00, // applet AID, empty here
0x5C, 0x03, // tag list to be returned
0x4F, // AID
0x9F, 0x70 // applet life cycle state - first byte;
// contactless activation state - second byte
])
},
getStatusAllNext: { cla: 0x80, ins: 0xF2, p1: 0x40, p2: 0x01,
data: new Uint8Array([0x4F, 0x00, 0x5C, 0x03, 0x4F, 0x9F, 0x70])
},
activateCLF: (aid) => {
return { cla: 0x80, ins: 0xF0, p1: 0x01, p2: 0x01, data:
Utils.joinUint8Arrays([0x4F, 0x0F], Utils.hexStringToByte(aid))
};
},
deactivateCLF: (aid) => {
return { cla: 0x80, ins: 0xF0, p1: 0x01, p2: 0x00, data:
Utils.joinUint8Arrays([0x4F, 0x0F], Utils.hexStringToByte(aid))
};
},
}
};

View file

@ -0,0 +1,12 @@
var sendSMS = document.querySelector('#send-sms');
if (sendSMS) {
sendSMS.onclick = function() {
var sms = new MozActivity({
name: 'new', // Possible compose-sms in future versions
data: {
type: 'websms/sms',
number: '+46777888999'
}
});
};
}

View file

@ -0,0 +1,31 @@
var shareImage = document.querySelector("#share-image"),
imgToShare = document.querySelector("#image-to-share");
if (shareImage && imgToShare) {
shareImage.onclick = function () {
if(imgToShare.naturalWidth > 0) {
// Create dummy canvas
var blobCanvas = document.createElement("canvas");
blobCanvas.width = imgToShare.width;
blobCanvas.height = imgToShare.height;
// Get context and draw image
var blobCanvasContext = blobCanvas.getContext("2d");
blobCanvasContext.drawImage(imgToShare, 0, 0);
// Export to blob and share through a Web Activitiy
blobCanvas.toBlob(function (blob) {
var sharingImage = new MozActivity({
name: "share",
data: {
type: "image/*",
number: 1,
blobs: [blob]
}
});
});
}
else {
alert("Image failed to load, can't be shared");
}
}
}

View file

@ -0,0 +1,13 @@
var share = document.querySelector('#share');
if (share) {
share.onclick = function() {
var sharing = new MozActivity({
name: 'share',
data: {
type: 'url', // Possibly text/html in future versions,
number: 1,
url: 'http://www.mozilla.org'
}
});
};
}

View file

@ -0,0 +1,59 @@
// The image we want to view
var imageblob;
// Create an image from a canvas
var canvas = document.createElement('canvas');
canvas.width = 600;
canvas.height = 800;
var context = canvas.getContext('2d');
context.font = '20pt Arial';
context.fillStyle = 'red';
context.fillText('Hello World', 100, 100);
context.strokeStyle = 'blue';
context.lineWidth = 4;
context.strokeRect(0, 0, 600, 800);
canvas.toBlob(function(blob) {
imageblob = blob;
}, 'image/png');
document.getElementById('view').onclick = function() {
var a = new MozActivity({
name: 'open',
data: {
type: 'image/png',
blob: imageblob
}
});
a.onsuccess = function() {
console.log('activity returns', JSON.stringify(a.result));
};
};
document.getElementById('viewsave').onclick = function() {
var a = new MozActivity({
name: 'open',
data: {
type: 'image/png',
blob: imageblob,
allowSave: true,
filename: 'downloads/uitests/helloworldhello.png'
}
});
a.onsuccess = function() {
console.log('activity returns', JSON.stringify(a.result));
};
};
document.getElementById('viewError').onclick = function() {
var a = new MozActivity({
name: 'open',
data: {
type: 'image/jpeg',
blob: new Blob(['empty-image'], {'type': 'image/jpeg'})
}
});
a.onsuccess = function() {
console.log('activity returns', JSON.stringify(a.result));
};
};

View file

@ -0,0 +1,25 @@
'use strict';
function batteryTest() {
var battery = window.navigator.battery;
var charging = document.getElementById('charging');
var level = document.getElementById('level');
var chargingTime = document.getElementById('charging-time');
var disChargingTime = document.getElementById('discharging-time');
function update(evt) {
charging.textContent = battery.charging;
level.textContent = battery.level * 100;
chargingTime.textContent = battery.chargingTime;
disChargingTime.textContent = battery.dischargingTime;
}
battery.addEventListener('chargingchange', update);
battery.addEventListener('levelchange', update);
battery.addEventListener('onchargingtimechange', update);
battery.addEventListener('ondischargingtimechange', update);
update();
}
window.addEventListener('load', batteryTest);

View file

@ -0,0 +1,99 @@
'use strict';
function bluetoothTest() {
var bluetooth = window.navigator.mozBluetooth;
var settings = window.navigator.mozSettings;
var switchButton = document.getElementById('switch');
var state = document.getElementById('state');
var address = document.getElementById('address');
var version = document.getElementById('version');
var btHelper = new BluetoothHelper();
function update() {
var req = settings.createLock().get('bluetooth.enabled');
req.onsuccess = function() {
var isBluetoothEnabled = req.result['bluetooth.enabled'];
switchButton.checked = isBluetoothEnabled;
if (!isBluetoothEnabled) {
state.innerHTML = 'Off';
address.innerHTML = 'unknown';
}
else {
state.innerHTML = 'On';
if (!btHelper.v2) {
btHelper.getAddress(function bt_gotAddress(addr) {
if (isBluetoothEnabled) {
address.innerHTML = addr;
}
});
}
}
};
}
// Click on switch will only update settings and
// it takes some time for bluetooth hardware to turns on.
// That's why we should listen to 'enabled' event of bluetooth
function bluetoothSwitch() {
settings.createLock().set({'bluetooth.enabled': switchButton.checked});
// set enabled stat for BT APIv2
if (switchButton.checked) {
btHelper.enable();
} else {
btHelper.disable();
}
}
if (btHelper.v2) {
version.textContent = "v2";
bluetooth.addEventListener('attributechanged', (evt) => {
for (var i in evt.attrs) {
switch (evt.attrs[i]) {
case 'defaultAdapter':
var adapter = bluetooth.defaultAdapter;
if (adapter.address) { // for default On case
address.innerHTML = adapter.address;
}
adapter.addEventListener('attributechanged', (evt) => {
for (var i in evt.attrs) {
switch (evt.attrs[i]) {
case 'state':
if (adapter.state === 'enabled') {
switchButton.checked = true;
state.innerHTML = 'On';
} else if (adapter.state === 'disabled') {
switchButton.checked = false;
state.innerHTML = 'Off';
address.innerHTML = 'unknown';
}
break;
case 'address':
address.innerHTML = adapter.address;
break;
default:
break;
}
}
});
break;
default:
break;
}
}
});
} else {
version.textContent = "v1";
bluetooth.addEventListener('adapteradded', update);
bluetooth.addEventListener('enabled', update);
bluetooth.addEventListener('disabled', update);
}
// init
switchButton.addEventListener('click', bluetoothSwitch);
// Update bluetooth information in the first load
update();
}
window.addEventListener('load', bluetoothTest);

View file

@ -0,0 +1,19 @@
'use strict';
var clickHandlers = {
'camera': function() {
var activity = new MozActivity({
name: 'record',
data: {
type: 'photos'
}
});
}
};
document.body.addEventListener('click', function(evt) {
if (clickHandlers[evt.target.id]) {
clickHandlers[evt.target.id].call(this, evt);
}
});

View file

@ -0,0 +1,154 @@
'use strict';
function FMRadioTest() {
var messageText = document.getElementById('message');
var frequencyMin = document.getElementById('frequency-min');
var frequencyMax = document.getElementById('frequency-max');
var container = document.getElementById('container');
var controlPanel = document.getElementById('controlPanel');
var frequencyInput = document.getElementById('frequency');
var switchBtn = document.getElementById('switch');
var seekUpBtn = document.getElementById('seekUp');
var seekDownBtn = document.getElementById('seekDown');
var seekAndCancelBtn = document.getElementById('seekAndCancel');
if (!navigator.mozFMRadio) {
console.log('mozFMRadio not support');
container.hidden = true;
return;
}
var radio = navigator.mozFMRadio;
var prevFreq = radio.frequencyLowerBound;
var lock = false;
if (radio.enabled) {
prevFreq = radio.frequency;
}
switchBtn.textContent = radio.enabled;
frequencyMin.textContent = radio.frequencyLowerBound;
frequencyMax.textContent = radio.frequencyUpperBound;
controlPanel.hidden = !radio.enabled;
frequencyInput.value = prevFreq;
togglePanel();
toggleControlPanel();
radio.addEventListener('antennaavailablechange', togglePanel);
radio.addEventListener('frequencychange', frequencyChanged);
frequencyInput.addEventListener('change', setFrequency);
switchBtn.addEventListener('click', onclick_radioSwitch);
seekUpBtn.addEventListener('click', seek);
seekDownBtn.addEventListener('click', seek);
seekAndCancelBtn.addEventListener('click', seekAndCancel);
window.addEventListener('unload', uninit);
function logError() {
messageText.textContent = this.error.name;
}
// show/hide panels
function togglePanel() {
if (radio && radio.antennaAvailable) {
container.hidden = false;
messageText.textContent = '';
}
else {
container.hidden = true;
messageText.textContent = 'Headphone is required for FMRadio';
}
}
function toggleControlPanel() {
lock = false;
controlPanel.hidden = !radio.enabled;
if (radio.enabled) {
switchBtn.textContent = 'Stop';
}
else {
switchBtn.textContent = 'Start';
}
switchBtn.disabled = false;
}
function onclick_radioSwitch() {
if (lock) {
return;
}
// lock button until request is finished
lock = true;
switchBtn.disabled = true;
var req;
if (!radio.enabled) {
messageText.textContent = 'turning on FM, please wait...';
req = radio.enable(prevFreq);
}
else {
req = radio.disable();
}
req.onsuccess = req.onerror = function() {
toggleControlPanel();
if (this.error) {
messageText.textContent = this.error.name;
}
else {
messageText.textContent = '';
}
};
}
function frequencyChanged() {
console.log('freq changed');
frequencyInput.value = radio.frequency;
prevFreq = radio.frequency;
}
function setFrequency() {
var req = radio.setFrequency(frequencyInput.value);
req.onerror = logError;
}
function seek(evt) {
var req;
if (evt.target.id == 'seekUp') {
req = radio.seekUp();
}
else {
req = radio.seekDown();
}
req.onerror = logError;
}
function seekAndCancel(evt) {
var req = radio.seekUp();
console.log('seekUP()');
req.onsuccess = function() {
console.log('seekUP().onsuccess');
};
req.onerror = function() {
console.log('seekUP().onerror:' + this.error.name);
};
var cancel = radio.cancelSeek();
console.log('cancel()');
cancel.onsuccess = function() {
console.log('canelSeek().onsuccess');
};
cancel.onerror = function() {
console.log('cancelSeek().onerror:' + this.error.name);
};
}
function uninit() {
if (radio.enabled) {
radio.disable();
}
}
}
window.addEventListener('load', FMRadioTest);

View file

@ -0,0 +1,23 @@
'use strict';
function headphoneTest() {
var status_display = document.getElementById('status');
var acm = navigator.mozAudioChannelManager;
if (acm) {
acm.addEventListener('headphoneschange', update.bind(this));
}
else {
status_display.textContent = 'mozAudioChannelManager not found';
}
function update() {
if (acm.headphones) {
status_display.textContent = 'plugged';
}
else {
status_display.textContent = 'unpluged';
}
}
update();
}
window.addEventListener('load', headphoneTest);

View file

@ -0,0 +1,71 @@
'use strict';
var LCDTest = {
get fullscreenDiv() {
delete this.fullScreenDiv;
return this.fullScreenDiv = document.getElementById('fullscreen-div');
},
firstTimeHint: null,
init: function() {
document.body.addEventListener('click', this);
this.fullscreenDiv.addEventListener('click',
this.exitFullscreen.bind(this));
this.firstTimeHint = true;
},
enterFullscreen: function(color) {
this.fullscreenDiv.mozRequestFullScreen();
this.fullscreenDiv.classList.remove('invisible');
this.fullscreenDiv.classList.remove('red');
this.fullscreenDiv.classList.remove('green');
this.fullscreenDiv.classList.remove('blue');
this.fullscreenDiv.classList.remove('white');
this.fullscreenDiv.classList.remove('black');
this.fullscreenDiv.classList.add(color);
if (this.firstTimeHint) {
this.firstTimeHint = false;
alert('Tap screen again to exit fullscreen mode');
}
},
exitFullscreen: function() {
document.mozCancelFullScreen();
this.fullscreenDiv.classList.add('invisible');
},
turnOn: function() {
navigator.mozPower.screenEnabled = true;
// no longer need the lock
this.wakeLock.unlock();
},
// Only handle clicks on button, clicks on div is handled by exitFullscreen
handleEvent: function(evt) {
switch (evt.target.id) {
case 'red':
this.enterFullscreen('red');
break;
case 'green':
this.enterFullscreen('green');
break;
case 'blue':
this.enterFullscreen('blue');
break;
case 'white':
this.enterFullscreen('white');
break;
case 'black':
this.enterFullscreen('black');
break;
case 'off':
// We are going to shutdown the screen, this may lead to
// the sleep of CPU. Use wake lock to prevent from sleeping
// which cause setTimeout stop working.
this.wakeLock = window.navigator.requestWakeLock('screen');
setTimeout(this.turnOn.bind(this), 3000);
navigator.mozPower.screenEnabled = false;
break;
}
},
wakeLock: null
};
window.addEventListener('load', LCDTest.init.bind(LCDTest));

View file

@ -0,0 +1,26 @@
'use strict';
/*
var ringer = function callsHandler() {
var phoneSoundURL = new SettingsURL();
SettingsListener.observe('dialer.ringtone', '', function(value) {
ringtonePlayer.pause();
ringtonePlayer.src = phoneSoundURL.set(value);
});
var ringtonePlayer = ringtonePlayer;
ringtonePlayer = new Audio();
ringtonePlayer.mozAudioChannelType = 'ringer';
ringtonePlayer.src = phoneSoundURL.get();
ringtonePlayer.loop = false;
window.addEventListener('home', function (evt) {
evt.preventDefault();
ringtonePlayer.play();
});
window.addEventListener('click', function (evt) {
evt.stopPropagation();
ringtonePlayer.play();
});
}();
*/

View file

@ -0,0 +1,173 @@
'use strict';
function IMEI(serviceId) {
var IMEIText = document.getElementById('IMEI');
navigator.mozTelephony.dial('*#06#', serviceId).then(function(call) {
return call.result.then(function getIMEI(message) {
if (message && (message.serviceCode === 'scImei') &&
message.statusMessage) {
IMEIText.textContent = message.statusMessage;
}
});
});
}
// SIM card information such as ICCID, MSISDN
function iccInfo(mozIcc, parentContainer)
{
var messageText = parentContainer.querySelector('[data-type="icc-message"]');
var ICCIDText = parentContainer.querySelector('[data-type="ICCID"]');
var MSISDNText = parentContainer.querySelector('[data-type="MSISDN"]');
var container = parentContainer.querySelector('[data-type="icc-container"]');
mozIcc.addEventListener('cardstatechange', update_icc);
update_icc();
function update_icc() {
if (mozIcc.cardState == null ||
!mozIcc.iccInfo) {
container.hidden = true;
messageText.textContent = 'Airplane mode on';
}
else {
ICCIDText.textContent = mozIcc.iccInfo.iccid;
MSISDNText.textContent = mozIcc.iccInfo.msisdn || 'Unknown';
container.hidden = false;
messageText.textContent = '';
}
}
}
// data & voice network information
function mobileConnectionInfo(conn, parentContainer)
{
conn.addEventListener('datachange', update_data_network);
conn.addEventListener('voicechange', update_voice_network);
update_data_network();
update_voice_network();
function update_data_network() {
var container = parentContainer.
querySelector('[data-type="data-container"]');
var message = parentContainer.
querySelector('[data-type="data-message"]');
var result = update_network(conn.data, container, message);
}
function update_voice_network() {
var container = parentContainer.
querySelector('[data-type="voice-container"]');
var message = parentContainer.
querySelector('[data-type="voice-message"]');
var result = update_network(conn.voice, container, message);
}
function update_network(network, container, message) {
if (!conn || conn.radioState != 'enabled' ||
!network || !network.connected || network.emergencyCallsOnly) {
container.hidden = true;
message.textContent = 'Disconnect';
}
else {
container.querySelector('[data-type="network"]').textContent =
network.network.shortName;
container.querySelector('[data-type="LAC"]').textContent =
network.cell.gsmCellId;
container.querySelector('[data-type="CID"]').textContent =
network.cell.gsmLocationAreaCode;
container.querySelector('[data-type="strength"]').textContent =
network.relSignalStrength;
container.querySelector('[data-type="type"]').textContent =
network.type;
container.querySelector('[data-type="roaming"]').textContent =
network.roaming;
container.hidden = false;
message.textContent = '';
}
}
}
function radioTest() {
var iccIdIndex = [];
var icc_num = 0;
if (('mozIccManager' in navigator) &&
navigator.mozIccManager &&
('mozMobileConnections' in navigator) &&
navigator.mozMobileConnections.length > 0) {
var iccManager = navigator.mozIccManager;
// IMEI should be the same no matter which connection is used
IMEI(0);
var template = document.getElementById('connection-template');
template.hidden = true;
if (iccManager.iccIds.length > 0) {
for (var i = 0; i < iccManager.iccIds.length; i++) {
addIcc(iccManager.iccIds[i], i);
showInfo(iccManager.iccIds[i]);
}
} else {
document.getElementById('global-message').textContent =
'SIM card not found, insert SIM and turn off airplane mode';
}
iccManager.addEventListener('iccdetected', function(evt) {
document.getElementById('global-message').textContent = '';
addIcc(evt.iccId);
showInfo(evt.iccId);
});
iccManager.addEventListener('iccundetected', function(evt) {
removeIcc(evt.iccId);
});
}
else {
alert('navigator.mozIccManager or navigator.mozMobileConnections ' +
'not supported');
}
function showInfo(iccId) {
var i = iccIdIndex[iccId];
var connectionContainer = document.getElementById('connection#' + i);
// show ICC#
connectionContainer.querySelector('[data-type="SIM-number"]')
.textContent = i;
iccInfo(navigator.mozIccManager.getIccById(iccId), connectionContainer);
mobileConnectionInfo(navigator.mozMobileConnections[i],
connectionContainer);
}
function addIcc(iccId) {
var connectionContainer = document.getElementById('connection#' + icc_num);
if (!connectionContainer) {
// copy connection-template then modify it.
connectionContainer = template.cloneNode(true);
connectionContainer.id = 'connection#' + icc_num;
connectionContainer.hidden = false;
document.getElementById('content').appendChild(connectionContainer);
}
iccIdIndex[iccId] = icc_num;
icc_num++;
}
function removeIcc(iccId) {
var i = iccIdIndex[iccId];
if (i != null) {
iccIdIndex[iccId] = null;
// re-organize index
for (var key in iccIdIndex) {
if (iccIdIndex[key] >= i) {
iccIdIndex[key]--;
}
}
icc_num--;
}
}
}
window.addEventListener('load', radioTest);

View file

@ -0,0 +1,17 @@
'use strict';
var ringtonePlayer = new Audio();
// Get ringer file path from settings
var phoneSoundURL = new SettingsURL();
SettingsListener.observe('dialer.ringtone', '', function(value) {
ringtonePlayer.pause();
ringtonePlayer.src = phoneSoundURL.set(value);
});
document.body.addEventListener('click', function(evt) {
if (evt.target.id == 'ring') {
ringtonePlayer.play();
}
});

View file

@ -0,0 +1,85 @@
'use strict';
// Whenever the check button is clicked,
// we delete previously displayed result (if any) then show new result
function checkSDCards() {
var sdcards = navigator.getDeviceStorages('sdcard');
document.getElementById('numOfCards').textContent = sdcards.length;
// cardInfo collection
var cardsInfo = document.getElementById('cardsInfo');
// Clear last result
while (cardsInfo.firstChild) {
cardsInfo.removeChild(cardsInfo.firstChild);
}
// Show new result
sdcards.forEach(function showCards(card, index, cards) {
// To add a new SD cardInfo,
// first copy one from card_template then modify it.
var template = document.getElementById('card_template');
var cardInfo = template.cloneNode(true);
cardInfo.id = 'card' + index;
cardInfo.classList.remove('invisible');
var cardNo =
cardInfo.querySelector('#card' + index + ' > .no > span');
var cardStatus =
cardInfo.querySelector('#card' + index + ' > .status > span');
var cardSpace =
cardInfo.querySelector('#card' + index + ' > .space');
var cardSpaceTotal =
cardInfo.querySelector('#card' + index + ' > .space > .totalSpace');
var cardSpaceFree =
cardInfo.querySelector('#card' + index + ' > .space > .freeSpace');
var cardSpaceUsed =
cardInfo.querySelector('#card' + index + ' > .space > .usedSpace');
cardNo.textContent = index;
// Show availability and hide other infomation if not available
var reqAvailable = card.available();
reqAvailable.onsuccess = function() {
cardStatus.textContent = this.result;
if (this.result == 'shared' || this.result == 'unavailable') {
cardSpace.classList.add('invisible');
}
};
reqAvailable.onerror = function() {
cardStatus.textContent = 'Unable to get sdcard status' + this.error.name;
};
// Show space info.
// If the other space info is already updated, show total space
var reqFree = card.freeSpace();
reqFree.onsuccess = function() {
var spaceGB = this.result / Math.pow(10, 9);
cardSpaceFree.textContent = spaceGB;
if (cardSpaceTotal.textContent == '') {
cardSpaceTotal.textContent = spaceGB;
}
else {
cardSpaceTotal.textContent =
parseFloat(cardSpaceTotal.textContent) + spaceGB;
}
};
var reqUsed = card.usedSpace();
reqUsed.onsuccess = function() {
var spaceGB = this.result / Math.pow(10, 9);
cardSpaceUsed.textContent = spaceGB;
if (cardSpaceTotal.textContent == '') {
cardSpaceTotal.textContent = spaceGB;
}
else {
cardSpaceTotal.textContent =
parseFloat(cardSpaceTotal.textContent) + spaceGB;
}
};
cardsInfo.appendChild(cardInfo);
});
}
window.addEventListener('load', function() {
document.getElementById('check').addEventListener('click', checkSDCards);
});

View file

@ -0,0 +1,67 @@
'use strict';
function sensorTest() {
var interval = document.getElementById('interval');
var accelerationX = document.getElementById('acceleration-x');
var accelerationY = document.getElementById('acceleration-y');
var accelerationZ = document.getElementById('acceleration-z');
var accelerationGX = document.getElementById('acceleration-gravity-x');
var accelerationGY = document.getElementById('acceleration-gravity-y');
var accelerationGZ = document.getElementById('acceleration-gravity-z');
var rotAlpha = document.getElementById('rot-rate-alpha');
var rotBeta = document.getElementById('rot-rate-beta');
var rotGamma = document.getElementById('rot-rate-gamma');
var orientationAlpha = document.getElementById('orientation-alpha');
var orientationBeta = document.getElementById('orientation-beta');
var orientationGamma = document.getElementById('orientation-gamma');
var proximityMin = document.getElementById('proximity-min');
var proximityMax = document.getElementById('proximity-max');
var proximityValue = document.getElementById('proximity-value');
var compassCalibration = document.getElementById('compassneedscalibration');
window.addEventListener('devicemotion', function(e) {
interval.textContent = e.interval;
if (e.acceleration) {
accelerationX.textContent = e.acceleration.x;
accelerationY.textContent = e.acceleration.y;
accelerationZ.textContent = e.acceleration.z;
}
if (e.accelerationIncludingGravity) {
accelerationGX.textContent = e.accelerationIncludingGravity.x;
accelerationGY.textContent = e.accelerationIncludingGravity.y;
accelerationGZ.textContent = e.accelerationIncludingGravity.z;
}
if (e.rotationRate) {
rotAlpha.textContent = e.rotationRate.alpha;
rotBeta.textContent = e.rotationRate.beta;
rotGamma.textContent = e.rotationRate.gamma;
}
}, false);
window.addEventListener('deviceorientation', function(e) {
if (e) {
orientationAlpha.textContent = e.alpha;
orientationBeta.textContent = e.beta;
orientationGamma.textContent = e.gamma;
}
}, false);
window.addEventListener('devicelight', function(event) {
document.getElementById('light').textContent = event.value;
});
window.addEventListener('deviceproximity', function(event) {
proximityMin.textContent = event.min;
proximityMax.textContent = event.max;
proximityValue.textContent = event.value;
});
window.addEventListener('compassneedscalibration', function(e) {
compassCalibration.textContent = 'true';
}, false);
}
window.addEventListener('load', sensorTest);

View file

@ -0,0 +1,448 @@
'use strict';
// test result indicator
var Status = {
NOT_FINISHED: 1,
FINISHED: 2,
ABORT: 3
};
// UTILS
var util = {
// Use this function to get correct click position on canvas
relMouseCoords: function(canvas, evt) {
var rect = canvas.getBoundingClientRect();
var eventX = evt.clientX - rect.left;
var eventY = evt.clientY - rect.top;
return {x: eventX, y: eventY};
},
clearScreen: function(ctx) {
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
},
drawRectFromCoord: function(ctx, x1, y1, x2, y2) {
ctx.translate(0.5, 0.5);
ctx.strokeRect(x1, y1, x2 - x1, y2 - y1);
ctx.translate(-0.5, -0.5);
},
// Fill rect without overlapping border,
fillRectFromCoord: function(ctx, x1, y1, x2, y2) {
ctx.fillRect(x1 + 1, y1 + 1, (x2 - x1) - 1, (y2 - y1) - 1);
},
drawCircle: function(ctx, x, y, radius, width, color) {
ctx.lineWidth = width;
ctx.strokeStyle = color;
ctx.beginPath();
ctx.translate(0.5, 0.5);
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.translate(-0.5, -0.5);
ctx.closePath();
ctx.stroke();
},
distance_sqr: function(x1, y1, x2, y2) {
return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
},
// Return the index less or equal to num
binary_search: function(arr, num) {
var begin = 0;
var end = arr.length - 1;
while (begin < end) {
var pivot = Math.round((begin + end) / 2);
if (num < arr[pivot]) {
end = pivot - 1;
}
else if (num > arr[pivot]) {
begin = pivot + 1;
}
else {
return pivot;
}
}
if (num < arr[begin]) {
return begin - 1;
}
return begin;
}
};
// TEST 1
// Draw 4 circles on the corners and 1 circle in the center
var test1 = function(canvas) {
var ctx = canvas.getContext('2d');
var circle_radius = Math.min(canvas.width, canvas.height) * 0.1;
var circles_coord =
[[circle_radius, circle_radius],
[circle_radius, canvas.height - circle_radius],
[canvas.width - circle_radius, circle_radius],
[canvas.width - circle_radius, canvas.height - circle_radius],
[canvas.width / 2, canvas.height / 2]];
var lineWidth = 2;
var lineColor = 'red';
var lineColorClicked = 'lime';
var is_clicked = [];
var exitIconPositionX;
var exitIconPositionY;
var exitIconWidth;
var exitIconHeight;
(function init() {
util.clearScreen(ctx);
// Draw not-clicked circle
for (var i = 0; i < circles_coord.length; i++) {
util.drawCircle(ctx, circles_coord[i][0], circles_coord[i][1],
circle_radius, lineWidth, lineColor);
is_clicked[i] = false;
}
// Draw exit icon
var image = document.getElementById('icon-exit');
exitIconWidth = image.width;
exitIconHeight = image.height;
// Draw icon below center
exitIconPositionX = (canvas.width - exitIconWidth) / 2;
exitIconPositionY = canvas.height * 0.65;
ctx.drawImage(image, exitIconPositionX, exitIconPositionY);
})();
// Touch test, change circle color if touch point is in the circle.
// Test ends if all circles are touched
this.test = function(x, y) {
// Clicked on exit button
if (x > exitIconPositionX && x <= exitIconPositionX + exitIconWidth &&
y > exitIconPositionY && y <= exitIconPositionY + exitIconHeight) {
return Status.ABORT;
}
for (var i = 0; i < circles_coord.length; i++) {
if (!is_clicked[i] &&
util.distance_sqr(x, y, circles_coord[i][0], circles_coord[i][1]) <
circle_radius * circle_radius) {
// Clear red circle by covering a bigger white circle on it
util.drawCircle(ctx, circles_coord[i][0], circles_coord[i][1],
circle_radius, lineWidth + 2, 'white');
// Draw new circle
util.drawCircle(ctx, circles_coord[i][0], circles_coord[i][1],
circle_radius, lineWidth, lineColorClicked);
is_clicked[i] = true;
break;
}
}
for (var i = 0; i < circles_coord.length; i++) {
if (!is_clicked[i]) {
return Status.NOT_FINISHED;
}
}
// Test over
return Status.FINISHED;
};
};
// TEST 2
// Draw several cubes on the edge of screen
var test2 = function(canvas) {
var ctx = canvas.getContext('2d');
var aspect = canvas.width / canvas.height;
var cubes_num_vertical = 20;
var cubes_num_horizontal = Math.round(cubes_num_vertical * aspect);
var cube_width = canvas.width / cubes_num_horizontal;
var cube_height = canvas.height / cubes_num_vertical;
var cube_coord_x = [];
var cube_coord_y = [];
var is_clicked_top = [];
var is_clicked_bottom = [];
var is_clicked_left = [];
var is_clicked_right = [];
var total_clicked = 0;
var exitIconPositionX;
var exitIconPositionY;
var exitIconWidth;
var exitIconHeight;
(function init() {
util.clearScreen(ctx);
ctx.strokeStyle = 'black';
ctx.fillStyle = 'lime';
ctx.lineWidth = 1;
// Because width/height of screen can not be divided evenly
// So we precaculate the grid (coordination) of cubes for future use
for (var i = 0; i < cubes_num_horizontal; i++) {
cube_coord_x[i] = Math.round(cube_width * i);
}
cube_coord_x[cubes_num_horizontal] = canvas.width - 1;
for (var i = 0; i < cubes_num_vertical; i++) {
cube_coord_y[i] = Math.round(cube_height * i);
}
cube_coord_y[cubes_num_vertical] = canvas.height - 1;
// Draw Top/Bottom/Left/Right cubes
for (var i = 0; i < cubes_num_horizontal; i++) {
util.drawRectFromCoord(ctx, cube_coord_x[i], cube_coord_y[0],
cube_coord_x[i + 1], cube_coord_y[1]);
is_clicked_top[i] = false;
}
for (var i = 0; i < cubes_num_horizontal; i++) {
util.drawRectFromCoord(ctx,
cube_coord_x[i],
cube_coord_y[cubes_num_vertical - 1],
cube_coord_x[i + 1],
cube_coord_y[cubes_num_vertical]);
is_clicked_bottom[i] = false;
}
for (var i = 0; i < cubes_num_vertical; i++) {
util.drawRectFromCoord(ctx,
cube_coord_x[0],
cube_coord_y[i],
cube_coord_x[1],
cube_coord_y[i + 1]);
is_clicked_left[i] = false;
}
for (var i = 0; i < cubes_num_vertical; i++) {
util.drawRectFromCoord(ctx,
cube_coord_x[cubes_num_horizontal - 1],
cube_coord_y[i],
cube_coord_x[cubes_num_horizontal],
cube_coord_y[i + 1]);
is_clicked_right[i] = false;
}
// Draw exit icon
var image = document.getElementById('icon-exit');
exitIconWidth = image.width;
exitIconHeight = image.height;
exitIconPositionX = (canvas.width - exitIconWidth) / 2;
exitIconPositionY = (canvas.height - exitIconHeight) / 2;
ctx.drawImage(image, exitIconPositionX, exitIconPositionY);
}
)();
// Touch test, fill cubes if touch point is in the cube.
// Test ends if all cubes are touched
this.test = function(x, y) {
// Clicked on exit button
if (x > exitIconPositionX && x <= exitIconPositionX + exitIconWidth &&
y > exitIconPositionY && y <= exitIconPositionY + exitIconHeight) {
return Status.ABORT;
}
if (cube_coord_x[1] <= x && x <= cube_coord_x[cubes_num_horizontal - 1] &&
cube_coord_y[1] <= y && y <= cube_coord_y[cubes_num_vertical - 1]) {
return Status.NOT_FINISHED;
}
var startXi = util.binary_search(cube_coord_x, x);
var startYi = util.binary_search(cube_coord_y, y);
util.fillRectFromCoord(ctx,
cube_coord_x[startXi],
cube_coord_y[startYi],
cube_coord_x[startXi + 1],
cube_coord_y[startYi + 1]);
if (startYi == 0 && !is_clicked_top[startXi]) {
is_clicked_top[startXi] = true;
}
if (startYi == cubes_num_vertical - 1 && !is_clicked_bottom[startXi]) {
is_clicked_bottom[startXi] = true;
}
if (startXi == 0 && !is_clicked_left[startYi]) {
is_clicked_left[startYi] = true;
}
if (startXi == cubes_num_horizontal - 1 && !is_clicked_right[startYi]) {
is_clicked_right[startYi] = true;
}
for (var i = 0; i < cubes_num_vertical; i++) {
if (!is_clicked_left[i] || !is_clicked_right[i]) {
return Status.NOT_FINISHED;
}
}
for (var i = 0; i < cubes_num_horizontal; i++) {
if (!is_clicked_top[i] || !is_clicked_bottom[i]) {
return Status.NOT_FINISHED;
}
}
// Test over
return Status.FINISHED;
};
};
var test_driver = function() {
var current_test = null;
var canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.width = canvas.width + 'px';
canvas.style.height = canvas.height + 'px';
// returns true if no other tests
function proceed() {
if (current_test == null) {
current_test = new test1(canvas);
return false;
}
else if (current_test instanceof test1) {
current_test = new test2(canvas);
return false;
}
else if (current_test instanceof test2) {
return true;
}
}
proceed();
// returns whether all tests are finished
this.touch = function(x, y) {
var touchResult = current_test.test(x, y);
if (touchResult == Status.ABORT) {
return Status.ABORT;
}
// one test finished, go on next test
if (touchResult == Status.FINISHED) {
var isAllFinished = proceed();
if (isAllFinished) {
return Status.FINISHED;
}
}
return Status.NOT_FINISHED;
};
};
// Handle touch input, Codes modified from
// https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Events/Touch_events
var touchTest = function() {
var ongoingTouches = [];
var driver = null;
var canvas = null;
function copyTouch(touch) {
return { identifier: touch.identifier,
pageX: touch.pageX,
pageY: touch.pageY };
}
function ongoingTouchIndexById(idToFind) {
for (var i = 0; i < ongoingTouches.length; i++) {
var id = ongoingTouches[i].identifier;
if (id == idToFind) {
return i;
}
}
return -1; // not found
}
function handleEvent(evt) {
var touches = evt.changedTouches;
var result;
evt.preventDefault();
switch (evt.type) {
case 'touchstart':
for (var i = 0; i < touches.length; i++) {
ongoingTouches.push(copyTouch(touches[i]));
var coord = util.relMouseCoords(canvas, touches[i]);
result = driver.touch(coord.x, coord.y);
}
break;
case 'touchmove':
for (var i = 0; i < touches.length; i++) {
var coord = util.relMouseCoords(canvas, touches[i]);
var idx = ongoingTouchIndexById(touches[i].identifier);
if (idx >= 0) {
var coord_last = util.relMouseCoords(canvas, ongoingTouches[idx]);
result = driver.touch(coord.x, coord.y);
// swap in the new touch record
ongoingTouches.splice(idx, 1, copyTouch(touches[i]));
} else {
console.log('can\'t figure out which touch to continue');
}
}
break;
case 'touchend':
for (var i = 0; i < touches.length; i++) {
var coord = util.relMouseCoords(canvas, touches[i]);
var idx = ongoingTouchIndexById(touches[i].identifier);
if (idx >= 0) {
ongoingTouches.splice(i, 1); // remove it; we're done
} else {
console.log('can\'t figure out which touch to end');
}
}
break;
case 'touchcancel':
for (var i = 0; i < touches.length; i++) {
ongoingTouches.splice(i, 1); // remove it; we're done
}
break;
}
// All tests over
if (result == Status.FINISHED || result == Status.ABORT) {
// Clear remaining touches
touches = null;
canvas.removeEventListener('touchstart', handleEvent, false);
canvas.removeEventListener('touchend', handleEvent, false);
canvas.removeEventListener('touchcancel', handleEvent, false);
canvas.removeEventListener('touchleave', handleEvent, false);
canvas.removeEventListener('touchmove', handleEvent, false);
console.log('uninitialized.');
if (result == Status.ABORT) {
alert('Test terminated by user');
} else {
alert('Test Success!');
}
document.mozCancelFullScreen();
// This is a hack to close touch test, uitest.js will close this iframe
window.parent.window.location.hash = '';
}
}
this.init = function() {
driver = new test_driver();
if (canvas == null) {
canvas = document.getElementsByTagName('canvas')[0];
canvas.addEventListener('touchstart', handleEvent, false);
canvas.addEventListener('touchend', handleEvent, false);
canvas.addEventListener('touchcancel', handleEvent, false);
canvas.addEventListener('touchleave', handleEvent, false);
canvas.addEventListener('touchmove', handleEvent, false);
console.log('initialized.');
}
};
};
// XXX: There is a problem of getting into fullscreen
// When we called mozRequestFullscreen, it actually resized two times.
// We get almost fullscreen (except status bar) after first event, and truely
// fullscreen after second event. So we need to wait for the second event.
var resizedTimes = 0;
window.addEventListener('resize', function startTest() {
if (resizedTimes < 1) {
resizedTimes++;
}
else {
window.removeEventListener('resize', startTest);
var test = new touchTest;
test.init();
}
});
window.addEventListener('load', function() {
document.getElementById('canvas').classList.remove('invisible');
var exitButton = document.getElementById('fullscreen-exit');
document.body.mozRequestFullScreen();
});

View file

@ -0,0 +1,54 @@
'use strict';
function vibratorTest() {
var durationText = document.getElementById('duration-text');
var duration = document.getElementById('duration');
var timesText = document.getElementById('times-text');
var times = document.getElementById('times');
var clickHandlers = {
'off': function() {
window.navigator.vibrate(0);
},
'one-short': function() {
window.navigator.vibrate(200);
},
'two-short': function() {
window.navigator.vibrate([200, 100, 200, 100]);
},
'one-long': function() {
window.navigator.vibrate(10000);
},
'custom-pattern': function() {
var timesNum = parseInt(times.value);
var pattern = [];
for (var i = 0; i < timesNum * 2; i++) {
pattern.push(parseInt(duration.value));
}
window.navigator.vibrate(pattern);
}
};
// Update text when sliding bar is changed
var changeHandlers = {
'duration': function() {
durationText.textContent = duration.value;
},
'times': function() {
timesText.textContent = times.value;
}
};
document.body.addEventListener('click', function(evt) {
if (clickHandlers[evt.target.id]) {
clickHandlers[evt.target.id].call(this, evt);
}
});
document.body.addEventListener('input', function(evt) {
if (changeHandlers[evt.target.id]) {
changeHandlers[evt.target.id].call(this, evt);
}
});
}
window.addEventListener('load', vibratorTest);

View file

@ -0,0 +1,37 @@
'use strict';
function wifiTest() {
var WiFiMACText = document.getElementById('address');
var WiFiStateText = document.getElementById('state');
var switchButton = document.getElementById('switch');
var settings = window.navigator.mozSettings;
var wifiManager;
function update() {
switchButton.checked = wifiManager.enabled;
if (wifiManager.enabled) {
WiFiStateText.textContent = 'On';
WiFiMACText.textContent = wifiManager.macAddress;
}
else {
WiFiStateText.textContent = 'Off';
WiFiMACText.textContent = 'Not available';
}
}
if ('mozWifiManager' in navigator) {
wifiManager = navigator.mozWifiManager;
// navigator.mozWifiManager does not have addEventListener()
wifiManager.onenabled = update.bind(this);
wifiManager.ondisabled = update.bind(this);
update();
}
// Click on switch will only update settings and
// it takes some time for hardware to turns on.
// That's why we should listen to enabled/disabled event of wifi
function wifiSwitch() {
settings.createLock().set({'wifi.enabled': switchButton.checked});
}
switchButton.addEventListener('click', wifiSwitch);
}
window.addEventListener('load', wifiTest);

View file

@ -0,0 +1,42 @@
'use strict';
var clickHandlers = {
'button1': function() {
window.parent.alert('Hello world!');
},
'button2': function() {
window.parent.alert(window.parent.confirm('Hello world?'));
},
'button3': function() {
window.parent.alert(window.parent.prompt('Hello world:', 'initial value'));
},
'button21': function() {
alert('Hello world!');
},
'button22': function() {
alert(confirm('Hello world?'));
},
'button23': function() {
alert(prompt('Hello world:', 'initial value'));
},
'button31': function() {
var msg = 'Hello world!1\n2\n3\n4\n5\n6\n7\n8\n9';
msg += '\n10\n11\n12\n13\n14\n15\n16\n17\n18\n';
alert(msg);
},
'button32': function() {
var msg = 'Hello world!1\n2\n3\n4\n5\n6\n7\n8\n9';
msg += '\n10\n11\n12\n13\n14\n15\n16\n17\n18\n';
alert(confirm(msg));
},
'button33': function() {
var msg = 'Hello world!1\n2\n3\n4\n5\n6\n7\n8\n9';
msg += '\n10\n11\n12\n13\n14\n15\n16\n17\n18\n';
alert(prompt(msg));
}
};
document.body.addEventListener('click', function(evt) {
if (clickHandlers[evt.target.id])
clickHandlers[evt.target.id].call(this, evt);
});

View file

@ -0,0 +1,59 @@
'use strict';
var CanvasTests = {
get drawButton() {
delete this.drawButton;
return this.drawButton = document.getElementById('button-draw');
},
get toggleHiddenButton() {
delete this.toggleHiddenButton;
return this.toggleHiddenButton =
document.getElementById('button-toggle-hidden');
},
get canvas() {
delete this.canvas;
return this.canvas = document.getElementById('canvas');
},
get hiddenStatus() {
delete this.hiddenStatus;
return this.hiddenStatus = document.getElementById('hidden-status');
},
init: function ct_init() {
this.hiddenStatus.innerHTML =
'hidden = ' +
this.canvas.classList.contains('hidden');
this.drawButton.addEventListener('click', this.draw.bind(this));
this.toggleHiddenButton.addEventListener('click',
this.toggleShowHide.bind(this));
},
draw: function ct_draw() {
// var element = document.getElementById('canvas');
var context = this.canvas.getContext('2d');
var image = new Image();
var path = '../../style/images/canardpc.jpg';
image.src = path;
var self = this;
image.onload = function() {
var w = image.width;
var h = image.height;
context.drawImage(image, 0, 0, w, h, 0, 0, w, h);
};
},
toggleShowHide: function ct_toggleShowHide() {
this.canvas.classList.toggle('hidden');
var status = this.canvas.classList.contains('hidden');
this.hiddenStatus.innerHTML = 'hidden = ' + status;
}
};
window.addEventListener('DOMContentLoaded', function() {
CanvasTests.init();
});

View file

@ -0,0 +1,24 @@
'use strict';
var clickHandlers = {
'reload': function() {
window.location.reload();
},
'comments': function() {
window.location = '#comments';
alert(window.location.href);
},
'twitter': function() {
window.open('http://twitter.com/intent/tweet?text=' +
document.title + ': ' + window.location.href, 'shareWindow');
},
'facebook': function() {
window.open('http://facebook.com/sharer/sharer.php?u=' +
window.location.href, 'shareWindow');
}
};
document.body.addEventListener('click', function(evt) {
if (clickHandlers[evt.target.id || evt.target.dataset.fn])
clickHandlers[evt.target.id || evt.target.dataset.fn].call(this, evt);
});

View file

@ -0,0 +1,19 @@
'use strict';
function css_dimension_test() {
var bar_inch = document.getElementById('bar_inch');
var bar_mm = document.getElementById('bar_mm');
var bar_px = document.getElementById('bar_px');
var bar_mozmm = document.getElementById('bar_mozmm');
bar_inch.style.width = '1in';
bar_inch.style.backgroundColor = '#00FF00';
bar_mm.style.width = '25.4mm';
bar_mm.style.backgroundColor = '#00DD44';
bar_px.style.width = '96px';
bar_px.style.backgroundColor = '#00BB88';
bar_mozmm.style.width = '25.4mozmm';
bar_mozmm.style.backgroundColor = '#0099CC';
}
window.addEventListener('load', css_dimension_test);

View file

@ -0,0 +1,13 @@
'use strict';
var clickHandlers = {
/*
'id': function () {
alert('event triggered!');
}, */
};
document.body.addEventListener('click', function(evt) {
if (clickHandlers[evt.target.id || evt.target.dataset.fn])
clickHandlers[evt.target.id || evt.target.dataset.fn].call(this, evt);
});

View file

@ -0,0 +1,45 @@
'use strict';
function fullscreenTest() {
var fullscreenDiv = document.getElementById('fullscreen-div');
var testBtnDiv = document.getElementById('testBtn-div');
var testBtn = document.getElementById('testBtn');
var isFullscreenDiv = false;
var isFullscreen = false;
var displayMode;
function clickHandlers(evt) {
switch (evt.target.id) {
case 'testBtn':
if (!isFullscreen) {
window.parent.document.getElementById('test-iframe')
.mozRequestFullScreen();
testBtn.textContent = 'Cancel fullscreen';
} else {
window.parent.document.mozCancelFullScreen();
testBtn.textContent = 'Test';
}
isFullscreen = !isFullscreen;
break;
case 'testBtn-div':
if (!isFullscreenDiv) {
fullscreenDiv.mozRequestFullScreen();
testBtnDiv.textContent = 'Cancel fullscreen';
} else {
document.mozCancelFullScreen();
testBtnDiv.textContent = 'Test';
// Also cancels fullscreen of caused by another button (if any)
isFullscreen = false;
testBtn.textContent = 'Test';
}
isFullscreenDiv = !isFullscreenDiv;
break;
}
};
document.body.addEventListener('click', clickHandlers.bind(this));
}
window.addEventListener('load', fullscreenTest);

View file

@ -0,0 +1,29 @@
function keyboardTest() {
(function designmode(id) {
var iframe = document.querySelector('#' + id);
iframe.contentDocument.designMode = 'on';
iframe.contentDocument.body.textContent = 'Hola supermercado';
})('iframe-designmode');
(function contentEditable(id) {
var iframe = document.querySelector('#' + id);
iframe.contentDocument.body.setAttribute('contenteditable', true);
iframe.contentDocument.body.textContent = 'Telebancos por aqui';
})('iframe-contenteditable');
var readme = document.getElementById('readme');
var readmeBtn = document.getElementById('readme_button');
readme.hidden = true;
readmeBtn.addEventListener('click',
function toggle_readme() {
readme.hidden = !readme.hidden;
if (readme.hidden) {
readmeBtn.textContent = 'Show README';
} else {
readmeBtn.textContent = 'Hide README';
}
}
);
}
window.addEventListener('load', keyboardTest);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,19 @@
document.getElementById('close').onclick = function() {
window.close();
};
document.getElementById('open').onclick = function() {
window.open('/tests_html/UI/popup.html', '', 'dialog');
};
window.addEventListener('resize', function() {
if (window.innerHeight > 60) {
document.body.style.backgroundColor = 'white';
document.body.style.fontSize = '20px';
document.body.style.color = 'black';
} else {
document.body.style.backgroundColor = 'green';
document.body.style.fontSize = '10px';
document.body.style.color = 'white';
}
});

View file

@ -0,0 +1,6 @@
window.addEventListener('load', function() {
var p = document.getElementById('probe');
document.getElementById('report').textContent =
'Scroll bar width calculated from Javascript: ' +
(p.offsetWidth - p.scrollWidth - 2) + 'px'; // 2 = border width * 2
});

View file

@ -0,0 +1,16 @@
function changeTest(evt) {
console.log('++select++ onchange: ' + this.selectedIndex);
}
function blurTest(evt) {
console.log('++select++ onblur: ' + this.id);
}
window.addEventListener('load', function() {
var elementList = ['singleSel', 'multiSel', 'sizeSel'];
elementList.forEach(function(elementId) {
console.log('elementId: ' + elementId);
document.getElementById(elementId).onchange = changeTest;
document.getElementById(elementId).addEventListener('blur', blurTest);
});
});

View file

@ -0,0 +1,35 @@
'use strict';
window.addEventListener('load', function() {
var popup = null;
var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
button.addEventListener('click', function(e) {
var target = e.target;
var actualWindow = window;
if (target.classList.contains('mozbrowser')) {
actualWindow = window.parent;
}
switch (target.dataset.action) {
case 'open':
var param1 = null,
param2 = null;
var hash = target.dataset.url.split('#');
if (hash.length > 1) {
param1 = hash[1];
param2 = hash[1];
}
popup = actualWindow.open(target.dataset.url, param1, param2);
break;
case 'close':
popup.close();
break;
default:
break;
}
});
}
});

View file

@ -0,0 +1,184 @@
'use stricts';
var UITest = {
get UItests() {
delete this.UItests;
return this.UItests = document.getElementById('UI-tests');
},
get APItests() {
delete this.APItests;
return this.APItests = document.getElementById('API-tests');
},
get HWtests() {
delete this.HWtests;
return this.HWtests = document.getElementById('HW-tests');
},
get iframe() {
delete this.iframe;
return this.iframe = document.getElementById('test-iframe');
},
get backHeader() {
delete this.backHeader;
return this.backHeader = document.getElementById('test-panel-header');
},
get panelTitle() {
delete this.panelTitle;
return this.panelTitle = document.getElementById('test-panel-title');
},
get tabs() {
delete this.tabs;
return this.tabs = document.querySelectorAll('[role="tab"]');
},
get UITab() {
delete this.UITab;
return this.UITab = document.getElementById('UI');
},
get APITab() {
delete this.APITab;
return this.APITab = document.getElementById('API');
},
get HWTab() {
delete this.HWTab;
return this.HWTab = document.getElementById('HW');
},
currentTab: 'UI',
handleNotificationMessage: function(message) {
if (!message.clicked) {
return;
}
this.bringSelfToForeground();
},
bringSelfToForeground: function() {
navigator.mozApps.getSelf().onsuccess = function gotSelf(evt) {
var app = evt.target.result;
app.launch();
};
},
handleActivity: function(activity) {
if (activity.source.name != 'internal-system-engineering-mode') {
return;
}
//TODO use mozEngineeringMode API to do something
},
init: function ut_init() {
this.iframe.addEventListener('load', this);
this.iframe.addEventListener('unload', this);
document.body.addEventListener('transitionend', this);
window.addEventListener('keyup', this);
window.addEventListener('hashchange', this);
this.backHeader.addEventListener('action', this);
navigator.mozSetMessageHandler('notification', function(msg) {
this.handleNotificationMessage(msg);
}.bind(this));
navigator.mozSetMessageHandler('activity', function(activity) {
this.handleActivity(activity);
}.bind(this));
var name = this.getNameFromHash();
if (name) {
this.openTest(name);
}
else {
// if no test is specified, load UI tests list (select UI tab)
window.location.hash = 'UI';
}
},
uninit: function ut_uninit() {
this.iframe.removeEventListener('load', this);
this.iframe.removeEventListener('unload', this);
document.body.removeEventListener('transitionend', this);
window.removeEventListener('keyup', this);
window.removeEventListener('hashchange', this);
this.backHeader.removeEventListener('action', this);
},
getNameFromHash: function ut_getNameFromHash() {
return (/\btest=(.+)(&|$)/.exec(window.location.hash) || [])[1];
},
handleEvent: function ut_handleEvent(ev) {
switch (ev.type) {
case 'action':
if (ev.target != this.backHeader) {
return;
}
if (window.location.hash) {
window.location.hash = '';
}
break;
case 'load':
this.iframe.contentWindow.addEventListener('keyup', this);
break;
case 'unload':
this.iframe.contentWindow.removeEventListener('keyup', this);
break;
case 'hashchange':
if (window.location.hash == '#UI')
{
this.UItests.classList.remove('invisible');
AccessibilityHelper.setAriaSelected(this.UITab, this.tabs);
if (!this.HWtests.classList.contains('invisible'))
this.HWtests.classList.add('invisible');
if (!this.APItests.classList.contains('invisible'))
this.APItests.classList.add('invisible');
}
else if (window.location.hash == '#API')
{
this.APItests.classList.remove('invisible');
AccessibilityHelper.setAriaSelected(this.APITab, this.tabs);
if (!this.UItests.classList.contains('invisible'))
this.UItests.classList.add('invisible');
if (!this.HWtests.classList.contains('invisible'))
this.HWtests.classList.add('invisible');
}
else if (window.location.hash == '#HW')
{
this.HWtests.classList.remove('invisible');
AccessibilityHelper.setAriaSelected(this.HWTab, this.tabs);
if (!this.UItests.classList.contains('invisible'))
this.UItests.classList.add('invisible');
if (!this.APItests.classList.contains('invisible'))
this.APItests.classList.add('invisible');
}
else
{
var name = this.getNameFromHash();
if (!name) {
this.closeTest();
return;
}
this.panelTitle.textContent = name;
this.openTest(name);
}
break;
case 'transitionend':
var name = this.getNameFromHash();
if (!name)
this.iframe.src = 'about:blank';
break;
}
},
openTest: function ut_openTest(name) {
// save tab name from URL
// e.g. '#test=UI/empty' => 'UI'
this.currentTab = (/=\b(.+)\//.exec(window.location.hash) || [])[1];
document.body.classList.add('test');
var self = this;
window.setTimeout(function openTestPage() {
self.iframe.src = './tests_html/' + name + '.html';
}, 200);
},
closeTest: function ut_closeTest() {
var isOpened = document.body.classList.contains('test');
if (!isOpened)
return false;
document.body.classList.remove('test');
// select tab after close test iframe
window.location.hash = this.currentTab;
return true;
}
};
window.addEventListener('load', UITest.init.bind(UITest));

View file

@ -0,0 +1 @@
{"name":"UI tests","launch_path":"/index.html","type":"certified","developer":{"name":"The Gaia Team","url":"https://github.com/mozilla-b2g/gaia"},"messages":[{"notification":"/index.html"}],"activities":{"internal-system-engineering-mode":{"filters":{},"href":"/index.html","disposition":"window"}},"icons":{"84":"/style/icons/ui-test_84.png","126":"/style/icons/ui-test_126.png","142":"/style/icons/ui-test_142.png","189":"/style/icons/ui-test_189.png","284":"/style/icons/ui-test_284.png"},"permissions":{"fmradio":{},"browser":{},"geolocation":{},"wifi-manage":{},"mobilenetwork":{},"mobileconnection":{},"power":{},"backgroundservice":{},"attention":{},"contacts":{"access":"readwrite"},"settings":{"access":"readwrite"},"desktop-notification":{},"device-storage:sdcard":{"access":"readwrite"},"audio-capture":{},"video-capture":{},"bluetooth":{},"downloads":{},"firefox-accounts":{},"engineering-mode":{},"secureelement-manage":{},"presentation-device-manage":{},"moz-extremely-unstable-and-will-change-webcomponents":{}},"origin":"app://uitest.gaiamobile.org","version":"2.5"}

View file

@ -0,0 +1 @@
window.COMPONENTS_BASE_URL = '/shared/elements/';

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,40 @@
/* Generated by grunt-webfont */
/* Based on https://github.com/endtwist/fontcustom/blob/master/lib/fontcustom/templates/fontcustom.css */
@font-face {
font-family: "gaia-icons";
src: url("fonts/gaia-icons.ttf") format("truetype");
font-weight: 500;
font-style: normal;
}
[data-icon]:before,
.ligature-icons {
font-family: "gaia-icons";
content: attr(data-icon);
display: inline-block;
font-weight: 500;
font-style: normal;
text-decoration: inherit;
text-transform: none;
text-rendering: optimizeLegibility;
font-size: 30px;
-webkit-font-smoothing: antialiased;
}
[data-icon]:not([data-l10n-id]):not([aria-label]):not([aria-hidden="true"]):before {
/* Highlight cases where icons are not localized and still visible to screen
reader */
background: rgba(255, 0, 0, 0.4);
}
[data-icon]:not([data-l10n-id]):not([aria-label]):not([aria-hidden="true"]):after {
background: #333;
color: #fff;
position: absolute;
width: 20rem;
border-radius: 0.5rem;
padding: 0.5rem;
content: "Icon is not localized and is still visible to screen reader. Please add data-l10n-id or aria-label for localization or use aria-hidden to hide the icon from screen reader.";
z-index: 9999;
}

View file

@ -0,0 +1,23 @@
'use strict';
(function(exports) {
var AccessibilityHelper = {
/**
* For a set of tab elements, set aria-selected attribute in accordance with
* the current selection.
* @param {Object} selectedTab a tab to select object.
* @param {Array} tabs an array of tabs.
*/
setAriaSelected: function ah_setAriaSelected(selectedTab, tabs) {
// In case tabs is a NodeList, that does not have forEach.
Array.prototype.forEach.call(tabs, function setAriaSelectedAttr(tab) {
tab.setAttribute('aria-selected',
tab === selectedTab ? 'true' : 'false');
});
}
};
exports.AccessibilityHelper = AccessibilityHelper;
})(window);

View file

@ -0,0 +1,322 @@
/* exported BluetoothHelper */
'use strict';
var BluetoothHelper = function() {
var profiles = {
'HFP': 0x111E,
'A2DP': 0x110D
};
var _bluetooth = window.navigator.mozBluetooth;
var _isReady = false;
var _callbacks = [];
var _adapter = null;
var _oldAdapter = null; // The varaible is accessed for v1 only.
var _v2 = true;
var _ready = function(callback) {
if (!callback || !_bluetooth) {
return;
}
if (_isReady) {
callback();
} else {
_callbacks.push(callback);
}
};
var _handleRequest = function(request, callback, errorcb) {
request.onsuccess = function() {
if (callback) {
callback(request.result);
}
};
request.onerror = function() {
console.log('Error handling bluetooth request');
if (errorcb) {
errorcb();
}
};
};
// run callbacks when adapter is ready
var _processCallbacks = function() {
if (_adapter) {
_isReady = true;
_callbacks.forEach(function(callback) {
callback();
});
// clean up the _callback queue
_callbacks = [];
} else {
// We can do nothing without default adapter.
console.log('BluetoothHelper(): connot get default adapter yet');
}
};
// API v2 get adapter via bluetooth
var _fetchAdapterV2 = function() {
// need time to get bluetooth adapter at first run
_bluetooth.onattributechanged = function onManagerAttributeChanged(evt) {
for (var i in evt.attrs) {
switch (evt.attrs[i]) {
case 'defaultAdapter':
console.log('defaultAdapter changed. address:',
_bluetooth.defaultAdapter.address);
_adapter = _bluetooth.defaultAdapter;
_processCallbacks();
break;
default:
break;
}
}
};
_adapter = _bluetooth.defaultAdapter;
if (_adapter) {
_processCallbacks();
}
};
// API v1 get adapter via bluetooth
var _fetchAdapter = function() {
var req = _bluetooth.getDefaultAdapter();
if (req) {
req.onsuccess = function() {
if (_adapter) {
_oldAdapter = _adapter;
}
_isReady = true;
_adapter = req.result;
// Put the callback function of onpairedstatuschanged to the new adapter
// because the new adapter won't remember those callback function which
// is registered before. In other word, we get a new adpater after
// turned on/off Bluetooth. The new adapter have no registered callback.
if (_oldAdapter && _oldAdapter.onpairedstatuschanged) {
_adapter.onpairedstatuschanged = _oldAdapter.onpairedstatuschanged;
}
_callbacks.forEach(function(callback) {
callback();
});
// clean up the _callback queue
_callbacks = [];
};
req.onerror = function() {
// We can do nothing without default adapter.
console.log('BluetoothHelper(): connot get default adapter!!!');
};
}
};
var _getAdapter = function() {
if (_v2) {
_fetchAdapterV2();
} else {
_fetchAdapter();
}
};
var _resetAdapter = function() {
if (_adapter) {
_oldAdapter = _adapter;
}
// clean up state and adapter
_isReady = false;
_adapter = null;
};
// init
if (_bluetooth) {
// detect API version
if (typeof(_bluetooth.onattributechanged) === 'undefined') {
_v2 = false;
}
if (_v2) {
_bluetooth.onadapteradded = function onAdapterAdded(evt) {
_getAdapter();
};
} else {
_bluetooth.addEventListener('enabled', _getAdapter);
_bluetooth.addEventListener('adapteradded', _getAdapter);
_bluetooth.addEventListener('disabled', _resetAdapter);
}
_getAdapter();
}
return {
profiles: profiles,
answerWaitingCall: function() {
_ready(function() {
_adapter.answerWaitingCall();
});
},
ignoreWaitingCall: function() {
_ready(function() {
_adapter.ignoreWaitingCall();
});
},
toggleCalls: function() {
_ready(function() {
_adapter.toggleCalls();
});
},
getConnectedDevicesByProfile: function(profileID, cb) {
_ready(function() {
_handleRequest(_adapter.getConnectedDevices(profileID), cb);
});
},
connectSco: function(cb) {
_ready(function() {
_handleRequest(_adapter.connectSco(), cb);
});
},
disconnectSco: function(cb) {
_ready(function() {
_handleRequest(_adapter.disconnectSco(), cb);
});
},
getPairedDevices: function(cb) {
_ready(function() {
_handleRequest(_adapter.getPairedDevices(), cb);
});
},
getAddress: function(cb) {
if (_v2) {
console.log('getAddress function is deprecated');
return;
}
_ready(function() {
var address = _adapter.address;
cb(address);
});
},
setPairingConfirmation: function(address, confirmed) {
if (_v2) {
console.log('setPairingConfirmation API is deprecated');
return;
}
_ready(function() {
_adapter.setPairingConfirmation(address, confirmed);
});
},
setPinCode: function(address, pincode) {
if (_v2) {
console.log('setPairingConfirmation API is deprecated');
return;
}
_ready(function() {
_adapter.setPinCode(address, pincode);
});
},
setPasskey: function(address, key) {
if (_v2) {
console.log('setPairingConfirmation API is deprecated');
return;
}
_ready(function() {
_adapter.setPasskey(address, key);
});
},
isScoConnected: function(cb, errorcb) {
_ready(function() {
_handleRequest(_adapter.isScoConnected(), cb, errorcb);
});
},
sendMediaMetaData: function(metadata, cb, errorcb) {
_ready(function() {
_handleRequest(_adapter.sendMediaMetaData(metadata), cb, errorcb);
});
},
sendMediaPlayStatus: function(metadata, cb, errorcb) {
_ready(function() {
_handleRequest(_adapter.sendMediaPlayStatus(metadata), cb, errorcb);
});
},
set onhfpstatuschanged(callback) {
_ready(function() {
_adapter.onhfpstatuschanged = callback;
});
},
set onscostatuschanged(callback) {
_ready(function() {
_adapter.onscostatuschanged = callback;
});
},
set ona2dpstatuschanged(callback) {
_ready(function() {
_adapter.ona2dpstatuschanged = callback;
});
},
set onrequestmediaplaystatus(callback) {
_ready(function() {
_adapter.onrequestmediaplaystatus = callback;
});
},
set onpairedstatuschanged(callback) {
if (_v2) {
console.log('onpairedstatuschanged API is deprecated');
return;
}
_ready(function() {
_adapter.onpairedstatuschanged = callback;
});
},
v2: _v2, // expose API version for app reference
// bypass the enable/disable state if works in APIv1
enable: function() {
if (_v2) {
_ready(function() {
_adapter.enable();
});
} else {
console.log('enable is not support in v1 API!');
}
},
disable: function() {
if (_v2) {
_ready(function() {
_adapter.disable();
});
} else {
console.log('disable is not support in v1 API!');
}
}
};
};

View file

@ -0,0 +1,84 @@
/* global mozIntl */
/**
* This lib relies on `l10n.js' to implement localizable date/time strings.
*
* The proposed `DownloadFormatter' object provides features for formatting
* the data retrieved from the new API for Downloads, taking into account
* the structure defined by the API itself.
*/
(function(exports) {
'use strict';
var NUMBER_OF_DECIMALS = 2;
var BYTE_SCALE = ['B', 'KB', 'MB', 'GB', 'TB'];
function _getFormattedSize(bytes) {
if (bytes === undefined || isNaN(bytes)) {
return Promise.resolve(null);
}
var index = 0;
while (bytes >= 1024 && index < BYTE_SCALE.length) {
bytes /= 1024;
++index;
}
return document.l10n.
formatValue('byteUnit-' + BYTE_SCALE[index]).then(unit => {
return document.l10n.formatValue('fileSize', {
size: bytes.toFixed(NUMBER_OF_DECIMALS),
unit: unit
});
});
}
function _calcPercentage(currently, total) {
if (total === 0) {
return 0;
}
return parseInt((100 * currently) / total);
}
var DownloadFormatter = {
getFormattedSize: function(bytes) {
return _getFormattedSize(bytes);
},
getPercentage: function(download) {
return _calcPercentage(download.currentBytes, download.totalBytes);
},
getFileName: function(download) {
return download.path.split('/').pop(); // filename.ext
},
getTotalSize: function(download) {
var bytes = download.totalBytes;
return _getFormattedSize(bytes);
},
getDownloadedSize: function(download) {
var bytes = download.currentBytes;
return _getFormattedSize(bytes);
},
getDate: function(download) {
var date;
try {
date = download.startTime;
} catch (ex) {
date = new Date();
console.error(ex);
}
var formatter = mozIntl._gaia.RelativeDate(navigator.languages);
return formatter.format(date);
},
getUUID: function(download) {
return download.id || this.getFileName(download);
}
};
exports.DownloadFormatter = DownloadFormatter;
}(this));

View file

@ -0,0 +1,685 @@
'use strict';
/* exported DownloadHelper */
/* global DownloadFormatter */
/* global DownloadStore */
/* global DownloadUI */
/* global LazyLoader */
/* global MimeMapper */
/* global MozActivity */
/* global SettingsListener */
/*
* DownloadHelper.js: Perform some utility functions with DOMDownload
* objects.
*
* - You have to include in you HTML:
*
* <!-- <script src="shared/js/mime_mapper.js"></script> -->
* <!-- <script src="shared/js/download/download_store.js"></script> -->
* <script defer src="shared/js/lazy_loader.js"></script>
*
* - How to use this component.
*
* For launching a download
*
* var req = DownloadHelper.open(download);
*
* req.onsuccess = function req_onsuccess() {
* alert('The download was opened so we can remove the notification');
* }
*
* req.onerror = function req_onerror() {
* alert('Something wrong!: ' + req.error.message);
* }
*
*/
var DownloadHelper = (function() {
// Exception code constants
var CODE = {
FILE_NOT_FOUND: 'FILE_NOT_FOUND',
DEVICE_STORAGE: 'DEVICE_STORAGE',
MIME_TYPE_NOT_SUPPORTED: 'MIME_TYPE_NOT_SUPPORTED',
INVALID_STATE: 'INVALID_STATE',
NO_SDCARD: 'NO_SDCARD',
NO_PROVIDER: 'NO_PROVIDER',
UNMOUNTED_SDCARD: 'UNMOUNTED_SDCARD'
};
/*
* Request auxiliary object to support asynchronous calls
*/
var Request = function() {
this.done = function(result) {
if (typeof this.onsuccess === 'function') {
this.result = result;
window.setTimeout(function() {
this.onsuccess({
target: this
});
}.bind(this), 0);
}
};
this.failed = function(error) {
if (typeof this.onerror === 'function') {
this.error = error;
window.setTimeout(function() {
this.onerror({
target: this
});
}.bind(this), 0);
}
};
};
// Storage name by default
var STORAGE_NAME_BY_DEFAULT = 'sdcard';
// Storage name settings key
var STORAGE_NAME_KEY = 'device.storage.writable.name';
// Current storage name
var storageName = STORAGE_NAME_BY_DEFAULT;
LazyLoader.load('shared/js/settings_listener.js', function settingsLoaded() {
SettingsListener.observe(STORAGE_NAME_KEY, STORAGE_NAME_BY_DEFAULT,
function setStorageName(evt) {
var settingValue = evt.settingValue;
if (settingValue) {
storageName = settingValue;
}
}
);
});
/*
* Error auxiliary method
*/
function sendError(req, message, code) {
req.failed({
message: message,
code: code
});
console.error(message);
}
/*
* Returns a blob from the sdcard
*
* @param{Object} The download object
*
*/
function getBlob(download) {
var req = new Request();
var storage = getVolume(download.storageName);
var storeAvailableReq = storage.available();
storeAvailableReq.onsuccess = function available_onsuccess(e) {
var path = download.storagePath;
switch (storeAvailableReq.result) {
case 'unavailable':
sendError(req, ' Could not open the file: ' + path + ' from ' +
download.storageName, CODE.NO_SDCARD);
break;
case 'shared':
sendError(req, ' Could not open the file: ' + path + ' from ' +
download.storageName, CODE.UNMOUNTED_SDCARD);
break;
default:
try {
var storeGetReq = storage.get(path);
storeGetReq.onsuccess = function store_onsuccess() {
req.done(storeGetReq.result);
};
storeGetReq.onerror = function store_onerror() {
sendError(req, storeGetReq.error.name +
' Could not open the file: ' + path + ' from ' +
download.storageName, CODE.FILE_NOT_FOUND);
};
} catch (ex) {
sendError(req, 'Error getting the file ' + path + ' from ' +
download.storageName, CODE.DEVICE_STORAGE);
}
}
};
storeAvailableReq.onerror = function available_onerror() {
sendError(req, 'Error getting storage state ', CODE.DEVICE_STORAGE);
};
return req;
}
/*
* Base action that implements the commom logic for actions based on the
* MozActivity API
*
* @param{Object} Configuration parameters
*/
var Action = function Action(params) {
this.req = params.request;
this.name = params.actionType.activityName;
};
Action.prototype = {
/*
* It performs the action based on MozActivity
*/
run: function a_run() {
var activity = new MozActivity({
name: this.name,
data: this.data
});
activity.onsuccess = this._onsuccess.bind(this);
activity.onerror = this._onerror.bind(this);
},
/*
* This method implements the generic onsuccess callback
*/
_onsuccess: function a_onsuccess() {
this.req.done();
},
/*
* This method implements the generic onerror callback
*/
_onerror: function a_onerror(evt) {
this.req.failed({
message: evt.target.error.name
});
}
};
/*
* This action opens downloads extending the Action Object
*
* @param{Object} Configuration parameters
*/
var OpenAction = function OpenAction(params) {
Action.call(this, params);
this.data = {
url: params.download.path,
filename: DownloadFormatter.getFileName(params.download),
type: params.type,
blob: params.blob
};
};
OpenAction.prototype = {
__proto__: Action.prototype
};
/*
* This action shares downloads extending the Action Object
*
* @param{Object} Configuration parameters
*/
var ShareAction = function ShareAction(params) {
Action.call(this, params);
this.data = {
// 'share' activities do not work with specific mime types
type: params.type.split('/')[0] + '/*',
blobs: [params.blob],
filenames: [DownloadFormatter.getFileName(params.download)]
};
};
ShareAction.prototype = {
__proto__: Action.prototype,
/*
* It overrides the generic onerror callback for another more suitable
*/
_onerror: function sa_onerror(evt) {
if (evt.target.error.name !== 'NO_PROVIDER') {
return;
}
sendError(this.req, 'No provider to share file', CODE.NO_PROVIDER);
}
};
/*
* This action gets the info for a download
*
* @param{Object} Configuration parameters
*/
var InfoAction = function InfoAction(params) {
Action.call(this, params);
this.data = {
name: DownloadFormatter.getFileName(params.download),
type: params.type,
blob: params.blob,
size: params.download.totalBytes,
path: params.download.path
};
};
InfoAction.prototype = {
__proto__: Action.prototype,
/*
* It overrides the generic run method
*/
run: function ia_run() {
this.req.done(this.data);
}
};
// This is a factory that deals with different <Action> objects
var ActionsFactory = {
TYPE: {
OPEN: {
activityName: 'open',
actionClass: OpenAction
},
SHARE: {
activityName: 'share',
actionClass: ShareAction
},
INFO: {
actionClass: InfoAction
},
WALLPAPER: {
activityName: 'setwallpaper',
actionClass: ShareAction
},
RINGTONE: {
activityName: 'setringtone',
actionClass: ShareAction
}
},
create: function af_create(params) {
return new params.actionType.actionClass(params);
}
};
/*
* Returns the mime type
*
* @param{Object} It represents a DOMDownload object
*
* @returns(String) Mime type
*/
function getType(download) {
var fileName = DownloadFormatter.getFileName(download);
var type = MimeMapper.guessTypeFromFileProperties(fileName,
download.contentType);
return type;
}
/*
* This method allows third-parties to open or share downloads
*
* @param{Object} Action types: <ActionsFactory.TYPE.OPEN> or
* <ActionsFactory.TYPE.SHARE>
*
* @param{Object} It represents a DOMDownload object
*/
function runAction(actionType, download) {
var req = new Request();
window.setTimeout(function launching() {
var state = download.state;
if (state === 'succeeded' || state === 'finalized') {
LazyLoader.load(['shared/js/mime_mapper.js',
'shared/js/download/download_formatter.js'],
function loaded() {
var type = getType(download);
//
// The 'open' action will always launch an activity using the original
// content type to allow for third party applications to handle
// arbitrary types of content.
//
// The 'share' action on the other hand only works with known mime
// types at this time.
//
if (type.length === 0) {
type = download.contentType;
if (actionType !== ActionsFactory.TYPE.OPEN &&
actionType !== ActionsFactory.TYPE.INFO) {
sendError(req, 'Mime type not supported: ' + type,
CODE.MIME_TYPE_NOT_SUPPORTED);
return;
}
}
var blobReq = getBlob(download);
blobReq.onsuccess = function() {
ActionsFactory.create({
actionType: actionType,
download: download,
type: type,
blob: blobReq.result,
request: req
}).run();
};
blobReq.onerror = function() {
// Problem getting the blob from the sdcard
req.failed(blobReq.error);
};
});
return;
}
sendError(req, 'Becareful, the download is not finished!',
CODE.INVALID_STATE);
}, 0);
return req;
}
/*
* This method allows clients to remove a download, from the
* list and the phone.
*
* @param{Object} It represents a DOMDownload object
*/
function remove(download) {
var req = new Request();
var incompleteDownload = download.state !== 'succeeded';
// If is not done, use download manager to remove it,
// otherwise, deal with the datastore.
setTimeout(function() {
if (incompleteDownload) {
if (!navigator.mozDownloadManager) {
sendError(req, 'DownloadManager not present', CODE.INVALID_STATE);
} else {
// First we pause the download so that everyone knows it's being
// stopped. The Downloads API itself won't stop the download first,
// it will simply kill it.
// XXXAus: Remove when we fix bug #1090551
download.pause().then(
function() {
navigator.mozDownloadManager.remove(download).then(
function success() {
req.done(download);
},
function error() {
sendError(req,
'DownloadManager doesnt know about this download',
CODE.INVALID_STATE);
}
);
},
function() {
sendError(req,
'Failed to pause download before removal',
CODE.INVALID_STATE);
}
);
}
} else {
req.done(download);
}
}, 0);
return incompleteDownload ? req : doRemoveFromPhone(req, download);
}
/*
* Performs the proper delete of the physical file, also
* from the datastore if the download has finished.
*
* @param{Object} This is the Request object
* @param{Object} It represents a DOMDownload object
*/
function doRemoveFromPhone(deleteRequest, download) {
var req = new Request();
deleteRequest.onsuccess = function() {
var storage = getVolume(download.storageName);
var storeAvailableReq = storage.available();
storeAvailableReq.onsuccess = function available_onsuccess(e) {
var path = download.path;
switch (storeAvailableReq.result) {
case 'unavailable':
sendError(req, ' Could not delete the file: ' + path + ' from ' +
storageName, CODE.NO_SDCARD);
break;
case 'shared':
sendError(req, ' Could not delete the file: ' + path + ' from ' +
storageName, CODE.UNMOUNTED_SDCARD);
break;
default:
var storeDeleteReq = storage.delete(download.storagePath);
storeDeleteReq.onsuccess = function store_onsuccess() {
// Remove from the datastore if status is 'succeeded'
// if we find any problem with the datastore, don't send
// an error, since the physical remove already happened
if (download.state === 'succeeded') {
LazyLoader.load(['/shared/js/download/download_store.js'],
function() {
DownloadStore.remove(download);
}
);
}
req.done(storeDeleteReq.result);
};
storeDeleteReq.onerror = function store_onerror() {
sendError(req, storeDeleteReq.error.name +
' Could not remove the file: ' + download.path + ' from ' +
storageName, CODE.FILE_NOT_FOUND);
};
}
};
storeAvailableReq.onerror = function available_onerror() {
sendError(req, 'Error getting storage state ', CODE.DEVICE_STORAGE);
};
};
deleteRequest.onerror = function(error) {
sendError(req, error.message, error.code);
};
return req;
}
function handlerError(error, download, cb) {
LazyLoader.load('shared/js/download/download_ui.js', (function loaded() {
var req;
var show = DownloadUI.show;
// Canceled activites are normal and shouldn't be interpreted as errors.
// Unfortunately, this isn't reported in a standard way by our
// applications (or third party apps for that matter). This is why we
// have this lazy filter here that may need to be updated in the future
// but hopefully will just get removed.
if (error.message &&
(error.message.endsWith('canceled') ||
error.message.endsWith('cancelled'))) {
// Since this isn't actually an error, we invoke the callback with null.
cb && cb(null);
// and return early.
return;
}
switch (error.code) {
case CODE.NO_SDCARD:
case CODE.UNMOUNTED_SDCARD:
case CODE.FILE_NOT_FOUND:
case CODE.NO_PROVIDER:
req = show(DownloadUI.TYPE[error.code], download, true);
req.onconfirm = cb;
break;
case CODE.MIME_TYPE_NOT_SUPPORTED:
req = show(DownloadUI.TYPE.UNSUPPORTED_FILE_TYPE, download, true);
req.onconfirm = function tuftOnConfirm() {
showRemoveFileUI(download, cb);
};
break;
default:
req = show(DownloadUI.TYPE.FILE_OPEN_ERROR, download, true);
req.onconfirm = function tfoeOnConfirm() {
showRemoveFileUI(download, cb);
};
break;
}
// We have to remove the notification if the user cancels
req.oncancel = cb;
}));
}
function showRemoveFileUI(download, cb) {
var req = DownloadUI.show(DownloadUI.TYPE.DELETE, download, true);
req.oncancel = cb;
req.onconfirm = function doRemove() {
if (typeof cb === 'function') {
cb(download);
} else {
remove(download);
}
};
}
function getFreeSpace(cb) {
var storage = navigator.getDeviceStorage(storageName);
if (!storage) {
console.error('Cannot get free space size in sdcard');
cb(null);
return;
}
var req = storage.freeSpace();
req.onsuccess = function(e) {
cb(e.target.result);
};
req.onerror = function() {
cb(null);
};
}
/**
* Gets the storage for the download based on the volumen
* it was saved (storageName)
*/
function getVolume(volumeName) {
// Per API design, all media type return the same volumes.
// So we use 'sdcard' here for no reason.
// https://bugzilla.mozilla.org/show_bug.cgi?id=856782#c10
var volumes = navigator.getDeviceStorages('sdcard');
if (!volumeName || volumeName === '') {
return volumes[0];
}
for (var i = 0; i < volumes.length; ++i) {
if (volumes[i].storageName === volumeName) {
return volumes[i];
}
}
return volumes[0];
}
return {
/*
* This method allows clients to open a downlaod
*
* @param{Object} It represents a DOMDownload object
*/
open: function(download) {
return runAction(ActionsFactory.TYPE.OPEN, download);
},
/*
* This method allows clients to share a downlaoded file
*
* @param{Object} It represents a DOMDownload object
*/
share: function(download) {
return runAction(ActionsFactory.TYPE.SHARE, download);
},
/*
* This method allows clients to set as wallaper a downlaoded file
*
* @param{Object} It represents a DOMDownload object
*/
wallpaper: function(download) {
return runAction(ActionsFactory.TYPE.WALLPAPER, download);
},
/*
* This method allows clients to set as ringtone a downlaoded file
*
* @param{Object} It represents a DOMDownload object
*/
ringtone: function(download) {
return runAction(ActionsFactory.TYPE.RINGTONE, download);
},
/*
* This method returns information about a download
*
* @param{Object} It represents a DOMDownload object
*/
info: function(download) {
return runAction(ActionsFactory.TYPE.INFO, download);
},
/*
* Given a download, remove it from the DownloadManager
* list, and the file system.
*
* @param{Object} It represents a DOMDownload object
*/
remove: remove,
/*
* Returns exception code constants
*/
get CODE() {
return CODE;
},
/*
* This method handles different errors when users attemp to open files
*
* @param{Object} Error object
*
* @param{Object} It represents a DOMDownload object
*
* @param{Function} This function is performed when the flow is finished
*/
handlerError: handlerError,
/*
* Returns the free memory size in bytes
*
* @param{Function} This function is performed when the free memory size has
* been calculated
*/
getFreeSpace: getFreeSpace
};
}());

View file

@ -0,0 +1,451 @@
'use strict';
/* jshint moz:true */
/* global Promise */
/* global indexedDB */
/*
* DownloadStore.js: Allows to store download finished ordered by timestamp
*
* - Adding a new download:
*
* var req = DownloadStore.add(download);
*
* req.onsuccess = function() {
* console.log('Complete download stored successfully');
* };
*
* req.onerror = function(e) {
* console.error(e);
* };
*
* - Getting all downloads:
*
* var reqStore = DownloadStore.getAll();
*
* reqStore.onsuccess = function(ev) {
* ev.target.result.forEach(function(download) {
* // Do something cool with the download
* });
* };
*
* reqStore.onerror = function(e) {
* console.error(e);
* };
*
* - Removing a existing download:
*
* var req = DownloadStore.remove(download);
*
* req.onsuccess = function() {
* console.log('Deleted download successfully');
* };
*
* req.onerror = function(e) {
* console.error(e);
* };
*
* WARNING:
*
* The system app should add this to its manifest file
*
* "datastores-owned": {
* "download_store": {
* "description": "Stores download finished"
* }
* }
*
* The settings app should add this to its manifest file
*
* "datastores-access": {
* "download_store": {
* "description": "Stores download finished"
* }
* }
*
*/
/* exported DownloadStore */
var DownloadStore = (function() {
var datastore;
// Datastore name declared on the manifest.webapp
const DATASTORE_NAME = 'download_store';
var downloadListDB;
var downloadList = [];
var lastRevisionId;
const LIST_DB = 'download_list';
const LIST_STORE = 'download_list';
const LIST_KEY = 0;
const REVISION_KEY = 1;
/*
* Request auxiliary object to support asynchronous calls
*/
var Request = function() {
this.done = function(result) {
if (typeof this.onsuccess === 'function') {
this.result = result;
window.setTimeout(function() {
this.onsuccess({
target: this
});
}.bind(this), 0);
}
};
this.failed = function(error) {
if (typeof this.onerror === 'function') {
this.error = error;
window.setTimeout(function() {
this.onerror({
target: this
});
}.bind(this), 0);
}
};
};
function promisify(request) {
return new Promise(function(resolve, reject) {
request.onsuccess = (event) => {resolve(event.target.result);};
request.onerror = (event) => {reject(event.target.errorCode);};
});
}
// Indicates the initialization state
var readyState;
function init(success, fail) {
if (readyState === 'initialized') {
success();
return;
}
if (readyState === 'initializing') {
document.addEventListener('ds-initialized', function oninitalized() {
document.removeEventListener('ds-initialized', oninitalized);
success();
});
return;
}
readyState = 'initializing';
if (!navigator.getDataStores) {
var messageError = 'Data store: DataStore API is not working';
console.error(messageError);
fail({
message: messageError
});
return;
}
navigator.getDataStores(DATASTORE_NAME).then(function(ds) {
var messageError = 'Download Store: Cannot get access to the DataStore';
if (ds.length < 1) {
console.error(messageError);
throw ({
message: messageError
});
}
datastore = ds[0];
// Checking the length as the index should be there
}).then(function() {
return openDownloadListIndexedDB();
}).then(function(db) {
downloadListDB = db;
var listStore = downloadListDB.transaction(LIST_STORE, 'readonly')
.objectStore(LIST_STORE);
return Promise.all([
promisify(listStore.get(LIST_KEY))
.then((value) => {downloadList = value;}),
promisify(listStore.get(REVISION_KEY))
.then((value) => {lastRevisionId = value;})
]);
}).then(function() {
readyState = 'initialized';
// Remove the legacy index element, if it exists. See Bug 1180250.
const LEGACY_INDEX_ID = 1;
if (downloadList && downloadList.includes(LEGACY_INDEX_ID)) {
return promisify(get(LEGACY_INDEX_ID)).then((download) => {
if ('byTimestamp' in download) {
downloadList.splice(downloadList.indexOf(LEGACY_INDEX_ID), 1);
}
});
}
}).then(function() {
notifyOpenSuccess(success);
}).catch(function(e) {
console.error('Error while opening the Download Store: ', e.message);
fail(e);
});
}
function openDownloadListIndexedDB() {
return new Promise(function(resolve, reject) {
var request = indexedDB.open(LIST_DB);
request.onsuccess = function(event) {
var db = event.target.result;
resolve(db);
};
request.onerror = function(event) {
reject(event.target.errorCode);
};
request.onupgradeneeded = function(event) {
var db = event.target.result;
promisify(db.createObjectStore(LIST_STORE)).then(() => {
var listStore = db.transaction(LIST_STORE, 'readwrite')
.objectStore(LIST_STORE);
return Promise.all([
promisify(listStore.add([], LIST_KEY)),
promisify(listStore.add(0, REVISION_KEY))
]);
}).catch(reject);
};
request.onblocked = function(event) {
reject(event.target.errorCode);
};
});
}
function notifyOpenSuccess(cb) {
window.setTimeout(cb, 0);
document.dispatchEvent(new CustomEvent('ds-initialized'));
}
// These fields will be stored in our datastore
var fieldsToPropagate = ['url', 'path', 'totalBytes', 'contentType',
'startTime', 'state', 'storageName', 'storagePath'];
function cookDownload(download) {
var ret = Object.create(null);
fieldsToPropagate.forEach(function(field) {
ret[field] = download[field];
});
// This is the timestamp when the download finished
ret.finalizeTime = new Date();
return ret;
}
function defaultError(req) {
return defaultErrorCb.bind(null, req);
}
function defaultErrorCb(request, error) {
request.failed(error);
}
function defaultSuccess(req) {
return defaultSuccessCb.bind(null, req);
}
function defaultSuccessCb(request, result) {
request.done(result);
}
function updateDownloadList() {
var cursor = datastore.sync(lastRevisionId);
function cursorResolve(task) {
switch (task.operation) {
case 'done':
lastRevisionId = task.revisionId;
var listStore = downloadListDB.transaction(LIST_STORE, 'readwrite')
.objectStore(LIST_STORE);
return Promise.all([
promisify(listStore.put(downloadList, LIST_KEY)),
promisify(listStore.put(lastRevisionId, REVISION_KEY))
]);
case 'clear':
downloadList = [];
break;
case 'add':
downloadList.push(task.id);
break;
case 'update':
// This doesn't matter for us.
break;
case 'remove':
var i = downloadList.indexOf(task.id);
if (i >= 0) {
downloadList.splice(i, 1);
}
break;
}
return cursor.next().then(cursorResolve);
}
return cursor.next().then(cursorResolve);
}
function doAdd(download, req) {
var downloadCooked = cookDownload(download);
// Adding our cooked download
datastore.add(downloadCooked).then(function(id) {
// Enriched object with the id provided by the datastore
downloadCooked.id = id;
datastore.put(downloadCooked, id)
.then(function() { req.done(downloadCooked); },
defaultError(req));
}, defaultError(req));
}
function add(download) {
var req = new Request();
window.setTimeout(function() {
init(doAdd.bind(null, download, req), req.failed.bind(req));
});
return req;
}
function doGet(id, req) {
datastore.get(id).then(function(download) { req.done(download); },
defaultError(req));
}
function get(id) {
var req = new Request();
window.setTimeout(function() {
init(doGet.bind(null, id, req), req.failed.bind(req));
});
return req;
}
function doGetAll(req) {
updateDownloadList().then(function() {
datastore.get.apply(datastore, downloadList).then(defaultSuccess(req),
defaultError(req));
}, defaultError(req));
}
function getAll() {
var req = new Request();
window.setTimeout(function() {
init(doGetAll.bind(null, req), req.failed.bind(req));
});
return req;
}
function doRemove(id, req) {
// Removing the download object from datastore
datastore.remove(id).then(function(success) {
if (success) {
defaultSuccess(req);
} else {
req.failed({
message: 'The download with id: ' + id + 'does not exist'
});
}
}, defaultError(req));
}
function remove(download) {
var req = new Request();
window.setTimeout(function() {
init(doRemove.bind(null, download.id, req), req.failed.bind(req));
});
return req;
}
function doAddListener(listener) {
datastore.addEventListener('change', listener);
}
function addListener(listener) {
var req = new Request();
window.setTimeout(function() {
init(doAddListener.bind(null, listener), req.failed.bind(req));
});
return req;
}
function doRemoveListener(listener) {
datastore.removeEventListener('change', listener);
}
function removeListener(listener) {
var req = new Request();
window.setTimeout(function() {
init(doRemoveListener.bind(null, listener), req.failed.bind(req));
});
return req;
}
return {
/*
* This method returns the download with the specified id.
*/
get: get,
/*
* This method returns an array of complete downloads
*/
getAll: getAll,
/*
* It adds a new download object and returns the new datastore
* download object
*
* @param{Object} Download object provided by the API
*
*/
add: add,
/*
* It removes a download object
*
* @param{Object} The download object to be removed from the datastore
*
*/
remove: remove,
/*
* Add a listener that will receive datastore change events.
*
* @param{Function} The listener to be added.
*/
addListener: addListener,
/*
* Remove a listener previously added via addListener.
*
* @param{Function} The listener to be removed.
*/
removeListener: removeListener
};
}());

View file

@ -0,0 +1,154 @@
/* exported LazyLoader */
/* globals HtmlImports, Promise */
'use strict';
/**
* This contains a simple LazyLoader implementation
* To use:
*
* LazyLoader.load(
* ['/path/to/file.js', '/path/to/file.css', 'domNode'], callback
* );
*/
var LazyLoader = (function() {
function LazyLoader() {
this._loaded = {};
this._isLoading = {};
}
LazyLoader.prototype = {
_js: function(file, callback) {
var script = document.createElement('script');
script.src = file;
// until bug 916255 lands async is the default so
// we must disable it so scripts load in the order they where
// required.
script.async = false;
script.addEventListener('load', callback);
document.head.appendChild(script);
this._isLoading[file] = script;
},
_css: function(file, callback) {
var style = document.createElement('link');
style.type = 'text/css';
style.rel = 'stylesheet';
style.href = file;
document.head.appendChild(style);
callback();
},
_html: function(domNode, callback) {
// The next few lines are for loading html imports in DEBUG mode
if (domNode.getAttribute('is')) {
this.load(['/shared/js/html_imports.js'], function() {
HtmlImports.populate(callback);
}.bind(this));
return;
}
for (var i = 0; i < domNode.childNodes.length; i++) {
if (domNode.childNodes[i].nodeType == document.COMMENT_NODE) {
domNode.innerHTML = domNode.childNodes[i].nodeValue;
break;
}
}
window.dispatchEvent(new CustomEvent('lazyload', {
detail: domNode
}));
callback();
},
/**
* Retrieves content of JSON file.
*
* @param {String} file Path to JSON file
* @param {Boolean} mozSystem If xhr should use mozSystem permissions
* @return {Promise} A promise that resolves to the JSON content
* or null in case of invalid path. Rejects if an error occurs.
*/
getJSON: function(file, mozSystem) {
return new Promise(function(resolve, reject) {
var xhr;
if (mozSystem) {
xhr = new XMLHttpRequest({mozSystem: true});
} else {
xhr = new XMLHttpRequest();
}
xhr.open('GET', file, true);
xhr.responseType = 'json';
xhr.onerror = function(error) {
reject(error);
};
xhr.onload = function() {
if (xhr.response !== null) {
resolve(xhr.response);
} else {
reject(new Error('No valid JSON object was found (' +
xhr.status + ' ' + xhr.statusText + ')'));
}
};
xhr.send();
});
},
load: function(files, callback) {
var deferred = {};
deferred.promise = new Promise(resolve => {
deferred.resolve = resolve;
});
if (!Array.isArray(files)) {
files = [files];
}
var loadsRemaining = files.length, self = this;
function perFileCallback(file) {
if (self._isLoading[file]) {
delete self._isLoading[file];
}
self._loaded[file] = true;
if (--loadsRemaining === 0) {
deferred.resolve();
if (callback) {
callback();
}
}
}
for (var i = 0; i < files.length; i++) {
var file = files[i];
if (this._loaded[file.id || file]) {
perFileCallback(file);
} else if (this._isLoading[file]) {
this._isLoading[file].addEventListener(
'load', perFileCallback.bind(null, file));
} else {
var method, idx;
if (typeof file === 'string') {
method = file.match(/\.([^.]+)$/)[1];
idx = file;
} else {
method = 'html';
idx = file.id;
}
this['_' + method](file, perFileCallback.bind(null, idx));
}
}
return deferred.promise;
}
};
return new LazyLoader();
}());

View file

@ -0,0 +1,135 @@
/* exported MimeMapper */
'use strict';
/**
* MimeMapper helps gaia apps to decide the mapping of mimetype and extension.
* The use cases often happen when apps need to know about the exact
* mimetypes or extensions, such as to delegate the open web activity, we must
* have suitable mimetypes or extensions to request the right activity
*
* The mapping is basically created according to:
* http://en.wikipedia.org/wiki/Internet_media_type
*
* The supported formats are considered base on the deviceStorage properties:
* http://dxr.mozilla.org/mozilla-central/toolkit/content/
* devicestorage.properties
*
*/
var MimeMapper = {
// This list only contains the extensions we currently supported
// We should make it more complete for further usages
_typeToExtensionMap: {
// Image
'image/jpeg': 'jpg',
'image/png': 'png',
'image/gif': 'gif',
'image/bmp': 'bmp',
// Audio
'audio/mpeg': 'mp3',
'audio/mp4': 'm4a',
'audio/ogg': 'ogg',
'audio/webm': 'webm',
'audio/3gpp': '3gp',
'audio/amr': 'amr',
// Video
'video/mp4': 'mp4',
'video/mpeg': 'mpg',
'video/ogg': 'ogg',
'video/webm': 'webm',
'video/3gpp': '3gp',
'video/3gpp2': '3g2',
// Application
// If we want to support some types, like pdf, just add
'application/pdf': 'pdf',
'application/vcard': 'vcf',
// Text
'text/vcard': 'vcf',
'text/x-vcard': 'vcf'
},
// This list only contains the mimetypes we currently supported
// We should make it more complete for further usages
_extensionToTypeMap: {
// Image
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'jpe': 'image/jpeg',
'png': 'image/png',
'gif': 'image/gif',
'bmp': 'image/bmp',
// Audio
'mp3': 'audio/mpeg',
'm4a': 'audio/mp4',
'm4b': 'audio/mp4',
'm4p': 'audio/mp4',
'm4r': 'audio/mp4',
'aac': 'audio/aac',
'opus': 'audio/ogg',
'amr': 'audio/amr',
// Video
'mp4': 'video/mp4',
'mpeg': 'video/mpeg',
'mpg': 'video/mpeg',
'ogv': 'video/ogg',
'ogx': 'video/ogg',
'webm': 'video/webm',
'3gp': 'video/3gpp',
'3g2': 'video/3gpp2',
'ogg': 'video/ogg',
// Application
// If we want to support some extensions, like pdf, just add
'pdf': 'application/pdf',
// Text
'vcf': 'text/vcard'
},
_parseExtension: function(filename) {
var array = filename.split('.');
return array.length > 1 ? array.pop() : '';
},
isSupportedType: function(mimetype) {
return (mimetype in this._typeToExtensionMap);
},
isSupportedExtension: function(extension) {
return (extension in this._extensionToTypeMap);
},
isFilenameMatchesType: function(filename, mimetype) {
var extension = this._parseExtension(filename);
var guessedType = this.guessTypeFromExtension(extension);
return (guessedType == mimetype);
},
guessExtensionFromType: function(mimetype) {
return this._typeToExtensionMap[mimetype];
},
guessTypeFromExtension: function(extension) {
return this._extensionToTypeMap[extension];
},
// If mimetype is not in the supported list, we will try to
// predict the possible valid mimetype based on extension.
guessTypeFromFileProperties: function(filename, mimetype) {
var extension = this._parseExtension(filename);
var type = this.isSupportedType(mimetype) ?
mimetype : this.guessTypeFromExtension(extension);
return type || '';
},
// if mimetype is not supported, preserve the original extension
// and add the predict result as new extension.
// If both filename and mimetype are not supported, return the original
// filename.
ensureFilenameMatchesType: function(filename, mimetype) {
if (!this.isFilenameMatchesType(filename, mimetype)) {
var guessedExt = this.guessExtensionFromType(mimetype);
if (guessedExt) {
filename += '.' + guessedExt;
}
}
return filename;
}
};

View file

@ -0,0 +1,76 @@
/* exported SettingsListener */
'use strict';
var SettingsListener = {
/* lock stores here */
_lock: null,
/* keep record of observers in order to remove them in the future */
_observers: [],
/**
* getSettingsLock: create a lock or retrieve one that we saved.
* mozSettings.createLock() is expensive and lock should be reused
* whenever possible.
*/
getSettingsLock: function sl_getSettingsLock() {
// If there is a lock present we return that
if (this._lock && !this._lock.closed) {
return this._lock;
}
// If there isn't we return one.
var settings = window.navigator.mozSettings;
return (this._lock = settings.createLock());
},
observe: function sl_observe(name, defaultValue, callback) {
var settings = window.navigator.mozSettings;
if (!settings) {
window.setTimeout(function() { callback(defaultValue); });
return;
}
var req;
try {
req = this.getSettingsLock().get(name);
} catch (e) {
// It is possible (but rare) for getSettingsLock() to return
// a SettingsLock object that is no longer valid.
// Until https://bugzilla.mozilla.org/show_bug.cgi?id=793239
// is fixed, we just catch the resulting exception and try
// again with a fresh lock
console.warn('Stale lock in settings_listener.js.',
'See https://bugzilla.mozilla.org/show_bug.cgi?id=793239');
this._lock = null;
req = this.getSettingsLock().get(name);
}
req.addEventListener('success', (function onsuccess() {
callback(typeof(req.result[name]) != 'undefined' ?
req.result[name] : defaultValue);
}));
var settingChanged = function settingChanged(evt) {
callback(evt.settingValue);
};
settings.addObserver(name, settingChanged);
this._observers.push({
name: name,
callback: callback,
observer: settingChanged
});
},
unobserve: function sl_unobserve(name, callback) {
var settings = window.navigator.mozSettings;
var that = this;
this._observers.forEach(function(value, index) {
if (value.name === name && value.callback === callback) {
settings.removeObserver(name, value.observer);
that._observers.splice(index, 1);
}
});
}
};

View file

@ -0,0 +1,33 @@
/* exported SettingsURL */
'use strict';
// This library helps with managing settings that can either send back
// string uris or blobs that need to be turned into blob: uris.
// Usage is as follows:
// var blobURL = new SettingsURL();
// element.src = blobURL.set(settingValue);
function SettingsURL() {
this._url = null;
this._isBlob = false;
}
SettingsURL.prototype = {
set: function(value) {
if (this._isBlob) {
window.URL.revokeObjectURL(this._url);
}
if (value instanceof Blob) {
this._isBlob = true;
this._url = window.URL.createObjectURL(value);
} else {
this._isBlob = false;
this._url = value;
}
return this._url;
},
get: function() {
return this._url;
}
};

View file

@ -0,0 +1,428 @@
/* ----------------------------------
* Buttons
* ---------------------------------- */
.button::-moz-focus-inner,
.bb-button::-moz-focus-inner,
button::-moz-focus-inner {
border: none;
outline: none;
}
button,
.bb-button,
.button {
font-family: sans-serif;
font-style: italic;
width: 100%;
height: 4rem;
margin: 0 0 1rem;
padding: 0 1.2rem;
box-sizing: border-box;
display: inline-block;
vertical-align: middle;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
background: #d8d8d8;
border: none;
border-radius: 2rem;
font-weight: normal;
font-size: 1.6rem;
line-height: 4rem;
color: #333;
text-align: center;
text-decoration: none;
outline: none;
}
/* Recommend */
button.recommend,
.bb-button.recommend {
background-color: #00caf2;
color: #fff;
}
/* Danger */
button.danger,
.danger.bb-button {
background-color: #e51e1e;
color: #fff;
}
/* Pressed */
button:active,
.bb-button:active,
.button:active {
background-color: #b2f2ff;
color: #fff;
}
/* Icons */
button[data-icon],
.bb-button[data-icon],
.button[data-icon] {
position: relative;
}
button[data-icon]:before,
.bb-button[data-icon]:before,
.button[data-icon]:before {
position: absolute;
top: 50%;
margin-top: -1.5rem;
offset-inline-end: 1.5rem;
line-height: 3rem;
}
button[data-icon]:active:before,
.bb-button[data-icon]:active:before,
.button[data-icon]:active:before {
color: #fff;
}
/* Pressed with dark skin */
.skin-dark button:active,
.skin-dark .bb-button:active,
.skin-dark .button:active {
background-color: #00aacc;
}
/* Disabled */
button[disabled],
.bb-button[aria-disabled="true"],
.button[aria-disabled="true"] {
background-color: rgba(0,0,0,0.05);
color: #c7c7c7;
pointer-events: none;
}
button[disabled].recommend,
.bb-button[aria-disabled="true"].recommend,
.button[aria-disabled="true"].recommend {
background-color: #97cbd5;
color: rgba(255,255,255,0.5);
}
button[disabled].danger,
.bb-button[aria-disabled="true"].danger,
.button[aria-disabled="true"].danger {
background-color: #eb7474;
color: rgba(255,255,255,0.5);
}
/* Disabled with dark background */
.skin-dark button[disabled],
.skin-dark .button[aria-disabled="true"],
.skin-dark .bb-button[aria-disabled="true"] {
background-color: #565656;
color: rgba(255,255,255,0.4);
}
.skin-dark button[disabled].recommend,
.skin-dark .button[aria-disabled="true"].recommend,
.skin-dark .bb-button[aria-disabled="true"].recommend {
background-color: #006579;
}
.skin-dark button[disabled].danger,
.skin-dark .button[aria-disabled="true"].danger,
.skin-dark .bb-button[aria-disabled="true"].danger {
background-color: #730f0f;
}
button[disabled]::-moz-selection,
.button[aria-disabled="true"]::-moz-selection,
.bb-button[aria-disabled="true"]::-moz-selection {
-moz-user-select: none;
}
/* ----------------------------------
* Buttons inside lists
* ---------------------------------- */
li button,
li .bb-button,
li .button {
position: relative;
border: 0.1rem solid #c7c7c7;
background: rgba(0,0,0,0.05);
margin: 0 0 1rem;
overflow: hidden;
border-radius: 0;
display: flex;
height: auto;
min-height: 4rem;
text-overflow: initial;
white-space: normal;
line-height: 2rem;
padding: 1rem 1.2rem;
}
html[dir="ltr"] li button,
html[dir="ltr"] li .bb-button,
html[dir="ltr"] li .button {
text-align: left;
}
html[dir="rtl"] li button,
html[dir="rtl"] li .bb-button,
html[dir="rtl"] li .button {
text-align: right;
}
/* Press */
li .bb-button:active:after,
li .button:active:after,
li button:active:after {
opacity: 0;
}
/* Disabled */
li button:disabled,
li .bb-button[aria-disabled="true"],
li .button[aria-disabled="true"] {
opacity: 0.5;
}
/* Icons */
li button.icon,
li .bb-button.icon,
li .button.icon {
padding-inline-end: 4rem;
}
li button.icon:before,
li .bb-button.icon:before,
li .button.icon:before {
content: "";
width: 3rem;
height: 3rem;
position: absolute;
top: 50%;
offset-inline-end: 0.5rem;
margin-top: -1.5rem;
background: transparent no-repeat center center / 100% auto;
pointer-events: none;
}
li button[data-icon]:before,
li .bb-button[data-icon]:before,
li .button[data-icon]:before {
offset-inline-end: 1rem;
}
li button.icon-view:active,
li .bb-button.icon-view:active,
li .button.icon-view:active {
background-color: #b2f2ff;
}
li button[data-icon="expand"]:before,
li .bb-button[data-icon="expand"]:before,
li .button[data-icon="expand"]:before {
color: #00aac5;
top: unset;
bottom: 0;
}
li button[data-icon="expand"][disabled]:before,
li .bb-button[data-icon="expand"][aria-disabled="true"]:before,
li .button[data-icon="expand"][aria-disabled="true"]:before {
color: #A9A9A9;
}
li button[data-icon="forward"]:before,
li .bb-button[data-icon="forward"]:before,
li .button[data-icon="forward"]:before {
color: #686f72;
}
/* Getting more space for text, as icon is shorter */
li button.icon-dialog,
li .bb-button.icon-dialog,
li .button.icon-dialog {
padding-inline-end: 3rem;
}
li button.icon-dialog:before,
li .bb-button.icon-dialog:before,
li .button.icon-dialog:before {
width: 0;
height: 0;
top: auto;
offset-inline-end: 0.8rem;
bottom: 0.5rem;
border-top: 0.7rem solid transparent;
border-bottom: 0.7rem solid transparent;
border-inline-start: 0.7rem solid #00aac5;
z-index: 1;
}
html[dir="ltr"] li button.icon-dialog:before,
html[dir="ltr"] li .bb-button.icon-dialog:before,
html[dir="ltr"] li .button.icon-dialog:before {
transform: rotate(45deg);
}
html[dir="rtl"] li button.icon-dialog:before,
html[dir="rtl"] li .bb-button.icon-dialog:before,
html[dir="rtl"] li .button.icon-dialog:before {
transform: rotate(-45deg);
}
li button.icon-dialog:disabled:before,
li .bb-button[aria-disabled="true"].icon-dialog:before,
li .button[aria-disabled="true"].icon-dialog:before {
border-inline-start: 0.7rem solid #a9a9a9;
}
/* ----------------------------------
* Theme: Dark
* ---------------------------------- */
.skin-dark li button,
.skin-dark li .bb-button,
.skin-dark li .button {
background-color: #454545;
border: none;
color: #fff;
font-weight: normal;
}
.skin-dark li button:active,
.skin-dark li .button:active,
.skin-dark li .bb-button:active {
background-color: #00aacc;
}
.skin-dark li button.icon-view:active,
.skin-dark li .button.icon-view:active,
.skin-dark li .bb-button.icon-view:active {
background-color: #00aacc;
}
.skin-dark li button.icon-dialog:active:before,
.skin-dark li .bb-button.icon-dialog:active:before,
.skin-dark li .button.icon-dialog:active:before {
border-inline-start: 0.7rem solid #b2f2ff;
}
.skin-dark li button:disabled,
.skin-dark li .bb-button[aria-disabled="true"],
.skin-dark li .button[aria-disabled="true"] {
background-color: #454545;
opacity: 0.5;
}
/* Inputs inside of .button */
.button input,
body[role="application"] .bb-button input,
body[role="application"] .button input {
border: 0;
background: none;
}
/* select */
select {
width: 100%;
box-sizing: border-box;
font-family: sans-serif;
font-style: italic;
font-size: 1.6rem;
color: #333;
margin: 0 0 1rem;
padding: 0;
padding-inline-start: 1.1rem;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
height: 4rem;
border: 0.1rem solid #c7c7c7;
background: rgba(0,0,0,0.05);
}
.skin-dark select {
background-color: rgba(87, 87, 87, 0.8);
border: none;
color: #fff;
}
/* remove dotted outline */
select:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 #333;
}
.skin-dark select:-moz-focusring {
text-shadow: 0 0 0 #fff;
}
/* Hides dropdown arrow until bug #649849 is fixed */
.bb-button.icon select,
.button.icon select,
.bb-button[data-icon] select,
.button[data-icon] select {
position: absolute;
top: -0.6rem;
offset-inline-start: 0;
width: calc(100% + 3rem);
height: 100%;
margin: 0;
padding: 0;
padding-inline-end: 5rem;
text-indent: 0;
font-weight: 400;
font-size: 1.7rem;
color: rgba(0,0,0,0);
border: none;
background: none;
}
/**
* The select element uses the text color to determine the color of the outline.
* We put a text shadow with needed offset and no blur, so that replaces the text.
*/
html[dir="ltr"] .bb-button.icon select,
html[dir="ltr"] .button.icon select,
html[dir="ltr"] .bb-button[data-icon] select,
html[dir="ltr"] .button[data-icon] select,
html[dir="ltr"] .bb-button.icon-dialog select,
html[dir="ltr"] .button.icon-dialog select {
text-shadow: 1rem 0.5rem 0 #333;
}
html[dir="rtl"] .bb-button.icon select,
html[dir="rtl"] .button.icon select,
html[dir="rtl"] .bb-button[data-icon] select,
html[dir="rtl"] .button[data-icon] select,
html[dir="rtl"] .bb-button.icon-dialog select,
html[dir="rtl"] .button.icon-dialog select {
text-shadow: -1rem 0.5rem 0 #333;
}
.bb-button.icon select option,
.button.icon select option,
.bb-button[data-icon] select option,
.button[data-icon] select option {
color: #000;
border: 0;
padding: 0.6rem 1.3rem;
text-shadow: 0 0 0 rgba(0,0,0,0);
}
.bb-button.icon-dialog,
.button.icon-dialog {
overflow: hidden;
}
html[dir="ltr"] .skin-dark .bb-button.icon select,
html[dir="ltr"] .skin-dark .button.icon select,
html[dir="ltr"] .skin-dark .bb-button[data-icon] select,
html[dir="ltr"] .skin-dark .button[data-icon] select,
html[dir="ltr"] .skin-dark .bb-button.icon-dialog select,
html[dir="ltr"] .skin-dark .button.icon-dialog select {
text-shadow: 1rem 0.5rem 0 #fff;
}
html[dir="rtl"] .skin-dark .bb-button.icon select,
html[dir="rtl"] .skin-dark .button.icon select,
html[dir="rtl"] .skin-dark .bb-button[data-icon] select,
html[dir="rtl"] .skin-dark .button[data-icon] select,
html[dir="rtl"] .skin-dark .bb-button.icon-dialog select,
html[dir="rtl"] .skin-dark .button.icon-dialog select {
text-shadow: -1rem 0.5rem 0 #fff;
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><path fill="#667174" fill-opacity=".7" d="m 18.2995,19.66 -5.056,-5.057 5.056,-5.056 c -0.31,-0.516 -0.62,-1.032 -0.93,-1.34 l -5.672,5.673 v 1.652 l 5.675,5.676 c 0.31,-0.515 0.722,-0.928 0.93,-1.547 z"/></svg>

After

Width:  |  Height:  |  Size: 294 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><path fill="#667174" fill-opacity=".7" d="m 11.698,19.66 5.056,-5.057 -5.056,-5.056 c 0.31,-0.516 0.62,-1.032 0.93,-1.34 L 18.3,13.88 v 1.652 l -5.675,5.676 c -0.31,-0.515 -0.722,-0.928 -0.93,-1.547 z"/></svg>

After

Width:  |  Height:  |  Size: 292 B

View file

@ -0,0 +1,280 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="utf-8">
<title>Buttons</title>
<link rel="stylesheet" href="../buttons.css">
<link rel="stylesheet" href="../../elements/gaia-icons/gaia-icons-embedded.css">
<link rel="stylesheet" href="../../elements/gaia-icons/bidi-helper.css">
<!--
- These <style> and <script> elements are only used for the example code,
- they are not needed for the real case.
-->
<style>
html, body {
margin: 0;
padding: 0;
font-size: 10px;
background-color: #fff;
}
label.bb-docs {
font-family: sans-serif;
font-size: 1.2rem;
margin-left: 1.5rem;
}
h2.bb-docs {
font-size: 1.8rem;
font-weight: 300;
color: #666;
margin: -0.1rem 0 0;
background-color: #f5f5f5;
padding: 0.4rem 3rem;
border: solid 0.1rem #e8e8e8;
font-family: sans-serif;
}
div.bb-docs {
padding: 1.5rem;
}
div.bb-docs.skin-organic {
background: #f4f4f4;
}
div.bb-docs.skin-dark {
background: #000;
}
ul {
padding: 0;
margin: 0;
list-style: none;
}
body[role="application"] > [role="region"] {
height: 100%;
position: relative;
}
[data-icon]:before {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</style>
<script>
window.onload = function() {
document.getElementById('dir').checked = false;
};
</script>
</head>
<body role="application">
<p dir="ltr">
<label class="bb-docs">
<input id="dir" type="checkbox" checked="false"
onchange="document.documentElement.dir = this.checked ? 'rtl' : 'ltr'">
RTL mode
</label>
</p>
<h2 class="bb-docs">Special buttons</h2>
<div class="bb-docs skin-organic">
<button>Default</button>
<button class="recommend">Recommend</button>
<button class="danger">Danger</button>
</div>
<h2 class="bb-docs">Buttons with icons</h2>
<div class="bb-docs skin-organic">
<button data-icon="forward" aria-label="forward" data-l10n-id="">Default</button>
<button data-icon="forward" aria-label="forward" data-l10n-id="" class="recommend">Recommend</button>
<button data-icon="forward" aria-label="forward" data-l10n-id="" class="danger">Danger</button>
<button data-icon="forward-light" aria-label="forward-light" data-l10n-id="">Default</button>
<button data-icon="back" aria-label="back" data-l10n-id="">Default</button>
<button data-icon="back-light" aria-label="back-light" data-l10n-id="">Default</button>
</div>
<h2 class="bb-docs">Disabled</h2>
<div class="bb-docs skin-organic">
<button disabled>Default</button>
<a href="#" role="button" aria-disabled="true" class="recommend bb-button">Recommend</a>
<button disabled class="danger">Danger</button>
</div>
<h2 class="bb-docs">Skin dark</h2>
<div class="bb-docs skin-dark">
<button>Default</button>
<button class="recommend">Recommend</button>
<button class="danger">Danger</button>
</div>
<h2 class="bb-docs">Disabled</h2>
<div class="bb-docs skin-dark">
<button disabled>Default</button>
<a href="#" role="button" aria-disabled="true" class="recommend bb-button">Recommend</a>
<button disabled class="danger">Danger</button>
</div>
<h2 class="bb-docs">List buttons</h2>
<div class="bb-docs skin-organic">
<ul>
<li>
<button>Default</button>
</li>
<li>
<button disabled>Disabled</button>
</li>
<li>
<button class="icon icon-view">
Icon view button
</button>
</li>
<li>
<button class="icon icon-dialog">
Icon dialog button
</button>
</li>
<li>
<span class="button icon icon-dialog">
<select>
<option selected>Icon dialog with options</option>
<option>option 1</option>
<option>option 2</option>
<option>option 3</option>
</select>
</span>
</li>
<li>
<button class="icon icon-dialog" disabled>
Icon dialog button disabled
</button>
</li>
<li>
<button data-icon="forward" aria-label="forward" data-l10n-id="">
Side navigation
</button>
</li>
<li>
<button disabled data-icon="forward" aria-label="forward" data-l10n-id="">
Side navigation
</button>
</li>
<li>
<a role="button" data-icon="expand" class="bb-button" aria-label="expand" data-l10n-id="">
Tuesday Sept. 18, 2012
</a>
</li>
<li>
<a role="button" aria-disabled="true" data-icon="expand" class="bb-button" aria-label="expand" data-l10n-id="">
Tuesday Sept. 18, 2012
</a>
</li>
</ul>
</div>
<h2 class="bb-docs">Skin Dark</h2>
<div class="bb-docs skin-dark">
<ul>
<li>
<button>Default</button>
</li>
<li>
<button disabled>Disabled</button>
</li>
<li>
<button class="icon icon-dialog">
Dark icon dialog button
</button>
</li>
<li>
<button data-icon="forward" aria-label="forward" data-l10n-id="">
Side navigation
</button>
</li>
<li>
<button disabled data-icon="forward" aria-label="forward" data-l10n-id="">
Side navigation
</button>
</li>
<li>
<a role="button" data-icon="expand" class="bb-button" aria-label="expand" data-l10n-id="">
Tuesday Sept. 18, 2012
</a>
</li>
<li>
<a role="button" aria-disabled="true" data-icon="expand" class="bb-button" aria-label="expand" data-l10n-id="">
Tuesday Sept. 18, 2012
</a>
</li>
</ul>
</div>
<h2 class="bb-docs">Select</h2>
<div class="bb-docs">
<select>
<option>option 1</option>
<option>option 2</option>
</select>
</div>
<h2 class="bb-docs">Fake select</h2>
<div class="bb-docs">
<ul>
<li>
<span class="bb-button" data-icon="expand" aria-label="expand" data-l10n-id="">
<select>
<option>option 1</option>
<option>option 2</option>
</select>
</span>
</li>
</ul>
</div>
<h2 class="bb-docs">Disabled select</h2>
<div class="bb-docs">
<ul>
<li>
<span aria-disabled="true" class="bb-button" data-icon="expand" aria-label="expand" data-l10n-id="">
<select>
<option>option 1</option>
<option>option 2</option>
</select>
</span>
</li>
</ul>
</div>
<h2 class="bb-docs">Skin dark</h2>
<div class="bb-docs skin-dark">
<select>
<option>option 1</option>
<option>option 2</option>
</select>
</div>
<h2 class="bb-docs">Fake select</h2>
<div class="bb-docs skin-dark">
<ul>
<li>
<span class="bb-button" data-icon="expand" aria-label="expand" data-l10n-id="">
<select>
<option>option 1</option>
<option>option 2</option>
</select>
</span>
</li>
<li>
<span aria-disabled="true" class="bb-button" data-icon="expand" aria-label="expand" data-l10n-id="">
<select>
<option>option 1</option>
<option>option 2</option>
</select>
</span>
</li>
</ul>
</div>
</body>
</html>

View file

@ -0,0 +1,575 @@
/* ----------------------------------
* Input areas
* ---------------------------------- */
/**
* BiDi notes:
*
* - as the <input>, <textarea> and [contentEditable] elements are filled by
* the user, they need a `unicode-bidi: -moz-plaintext` attribute for their
* content to be displayed properly (e.g. LTR content in an RTL document,
* or vice-versa);
*
* - consequence: *-inline-start/end properties can still be used on such
* elements content direction doesn't impact element's direction
*
* - however, the one-line inputs must still be aligned along with the
* *document* direction: to the left in LTR, to the right in RTL
* -- no matter what the *content* direction is.
*/
html[dir="ltr"] input { text-align: left; }
html[dir="rtl"] input { text-align: right; }
input[type="text"],
input[type="password"],
input[type="email"],
input[type="tel"],
input[type="search"],
input[type="url"],
input[type="number"],
textarea {
font-family: sans-serif;
box-sizing: border-box;
display: block;
width: 100%;
height: 4rem;
line-height: 4rem;
resize: none;
padding: 0 1.5rem;
font-size: 1.6rem;
border: 0.1rem solid #c7c7c7;
border-radius: 0;
box-shadow: none; /* override the box-shadow from the system (performance issue) */
color: #333;
background: #fff;
margin: 0 0 1rem;
unicode-bidi: -moz-plaintext;
}
label:active {
background-color: transparent;
}
textarea {
height: 10rem;
max-height: 10rem;
line-height: 2rem;
}
input::-moz-placeholder,
textarea::-moz-placeholder {
color: #a9a9a9;
opacity: 1;
font-style: italic;
font-weight: 400;
}
input[disabled],
textarea[disabled] {
opacity: 0.5;
}
.skin-dark input[type="text"],
.skin-dark input[type="password"],
.skin-dark input[type="email"],
.skin-dark input[type="tel"],
.skin-dark input[type="search"],
.skin-dark input[type="url"],
.skin-dark input[type="number"],
.skin-dark textarea {
color: #fff;
background-color: transparent;
border-color: #454545;
}
form p {
position: relative;
margin: 0;
}
form p input + button[type="reset"],
form p textarea + button[type="reset"] {
position: absolute;
top: 0;
width: 4rem;
height: 4rem;
padding: 0;
border: none;
font-size: 0;
opacity: 0;
pointer-events: none;
background: url(input_areas/images/clear.png) no-repeat 50% 50% / 2.4rem auto;
}
html[dir="ltr"] form p input + button[type="reset"],
html[dir="ltr"] form p textarea + button[type="reset"] {
right: -0.3rem;
}
html[dir="rtl"] form p input + button[type="reset"],
html[dir="rtl"] form p textarea + button[type="reset"] {
left: -0.3rem;
}
.skin-dark p input + button[type="reset"],
.skin-dark p textarea + button[type="reset"] {
background-image: url(input_areas/images/clear_dark.png);
}
/* To avoid colission with BB butons */
li input + button[type="reset"]:after,
li textarea + button[type="reset"]:after {
background: none;
}
textarea {
padding: 1.2rem;
}
html[dir="ltr"] form p input:focus { padding-right: 3rem; }
html[dir="rtl"] form p input:focus { padding-left: 3rem; }
form p input:focus + button[type="reset"],
form p textarea:focus + button[type="reset"] {
opacity: 1;
pointer-events: all;
}
/* Fieldset */
fieldset {
position: relative;
overflow: hidden;
margin: 1.5rem 0 0;
padding: 0;
font-size: 2rem;
line-height: 1em;
background: none;
border: 0;
}
fieldset legend,
input[type="date"],
input[type="time"],
input[type="datetime"],
input[type="datetime-local"] {
border: 0.1rem solid #c7c7c7;
width: 100%;
box-sizing: border-box;
font-family: sans-serif;
font-style: italic;
font-size: 1.6rem;
margin: 0 0 1.5rem;
padding: 0 1.5rem;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
height: 4rem;
color: #333;
border-radius: 0;
background-color: #fff;
background-repeat: no-repeat;
}
html[dir="ltr"] fieldset legend,
html[dir="ltr"] input[type="date"],
html[dir="ltr"] input[type="time"],
html[dir="ltr"] input[type="datetime"],
html[dir="ltr"] input[type="datetime-local"] {
background-image: url(input_areas/images/dialog.svg);
background-position: right 1rem bottom 1rem;
}
html[dir="rtl"] fieldset legend,
html[dir="rtl"] input[type="date"],
html[dir="rtl"] input[type="time"],
html[dir="rtl"] input[type="datetime"],
html[dir="rtl"] input[type="datetime-local"] {
background-image: url(input_areas/images/dialog_rtl.svg);
background-position: left 1rem bottom 1rem;
}
fieldset legend {
margin: 0 0 1rem;
padding: 1rem 1.5rem 0;
background-color: rgba(0,0,0,0.05);
}
fieldset[disabled] {
opacity: 0.5;
pointer-events: none;
}
.skin-dark fieldset legend,
.skin-dark input[type="date"],
.skin-dark input[type="time"],
.skin-dark input[type="datetime"],
.skin-dark input[type="datetime-local"] {
color: #fff;
background-color: transparent;
border-color: #454545;
}
fieldset[disabled] legend,
input[type="date"][disabled],
input[type="time"][disabled],
input[type="datetime"][disabled],
input[type="datetime-local"][disabled] {
background-color: transparent;
}
html[dir="ltr"] fieldset[disabled] legend,
html[dir="ltr"] input[type="date"][disabled],
html[dir="ltr"] input[type="time"][disabled],
html[dir="ltr"] input[type="datetime"][disabled],
html[dir="ltr"] input[type="datetime-local"][disabled] {
background-image: url(input_areas/images/dialog_disabled.svg);
}
html[dir="rtl"] fieldset[disabled] legend,
html[dir="rtl"] input[type="date"][disabled],
html[dir="rtl"] input[type="time"][disabled],
html[dir="rtl"] input[type="datetime"][disabled],
html[dir="rtl"] input[type="datetime-local"][disabled] {
background-image: url(input_areas/images/dialog_disabled_rtl.svg);
}
input[type="date"]:focus,
input[type="time"]:focus,
input[type="datetime"]:focus,
input[type="datetime-local"]:focus {
box-shadow: none;
border-bottom-color: #c7c7c7;
}
.skin-dark input[type="date"]:focus,
.skin-dark input[type="time"]:focus,
.skin-dark input[type="datetime"]:focus,
.skin-dark input[type="datetime-local"]:focus {
box-shadow: none;
border-bottom-color: #454545;
}
input[type="date"]:active,
input[type="time"]:active,
input[type="datetime"]:active,
input[type="datetime-local"]:active {
background-color: #b2f2ff;
color: #fff;
}
.skin-dark input[type="date"]:active,
.skin-dark input[type="time"]:active,
.skin-dark input[type="datetime"]:active,
.skin-dark input[type="datetime-local"]:active {
background-color: #00aacc;
background-image: url(input_areas/images/dialog_active.svg);
}
/* fix for required inputs with wrong or empty value e.g. [type=email] */
input:invalid,
textarea:invalid,
.skin-dark input:invalid,
.skin-dark textarea:invalid {
color: #b90000;
}
input:focus,
textarea:focus,
.skin-dark input:focus,
.skin-dark textarea:focus {
box-shadow: inset 0 -0.1rem 0 #00caf2;
border-bottom-color: #00caf2;
}
input:invalid:focus,
textarea:invalid:focus,
.skin-dark input:invalid:focus,
.skin-dark textarea:invalid:focus {
box-shadow: inset 0 -0.1rem 0 #820000;
border-bottom-color: #820000;
}
input[type="range"]:focus,
.skin-dark input[type="range"]:focus {
box-shadow: none;
border-bottom: 0;
}
/* Tidy (search/submit) */
form[role="search"] {
position: relative;
height: 3.7rem;
background: #f4f4f4;
}
form[role="search"].skin-dark {
background: #202020;
}
form[role="search"] p {
padding: 0 1.5rem;
padding-inline-start: 3rem;
overflow: hidden;
position: relative;
}
form[role="search"] p input,
form[role="search"] p textarea {
height: 3.7rem;
border: none;
background: none;
padding: 0;
margin: 0;
box-shadow: none;
}
form[role="search"] p textarea {
padding: 1rem 0 0;
}
form[role="search"] p input::-moz-placeholder {
background-image: url(input_areas/images/search.svg);
background-repeat: no-repeat;
background-size: 3rem;
}
html[dir="ltr"] form[role="search"] p input::-moz-placeholder {
background-position: right -0.5rem center;
}
html[dir="rtl"] form[role="search"] p input::-moz-placeholder {
background-position: left -0.5rem center;
}
form[role="search"].skin-dark p input::-moz-placeholder {
background-image: url(input_areas/images/search_dark.svg);
}
form[role="search"] p input:focus::-moz-placeholder {
background: none;
}
form[role="search"] p input:invalid,
form[role="search"] p textarea:invalid,
form[role="search"] p input:focus,
form[role="search"] p textarea:focus {
border: none;
box-shadow: none;
}
form[role="search"].skin-dark p input,
form[role="search"].skin-dark p textarea {
color: #fff;
background: none;
box-shadow: none;
}
form[role="search"] button[type="submit"] {
min-width: 6rem;
height: 3.7rem;
padding: 0 1.5rem;
border: none;
color: #00aac5;
font-weight: normal;
font-size: 1.6rem;
line-height: 3.7rem;
width: auto;
border-radius: 0;
margin: 0;
position: relative;
background-image: none;
background-color: unset;
font-style: italic;
overflow: visible;
}
html[dir="ltr"] form[role="search"] button[type="submit"] { float: right; }
html[dir="rtl"] form[role="search"] button[type="submit"] { float: left; }
form[role="search"] button[type="submit"]:after {
content: "";
position: absolute;
offset-inline-start: -0.1rem;
top: 0.7rem;
bottom: 0.7rem;
width: 0.1rem;
background: #c7c7c7;
}
form[role="search"] button[type="submit"].icon {
font-size: 0;
background-repeat: no-repeat;
background-position: center;
background-size: 3rem;
}
form[role="search"] p input + button[type="reset"]{
height: 3.7rem;
offset-inline-end: 0.5rem;
}
form[role="search"].skin-dark button[type="submit"]:after {
background-color: #575757;
}
form[role="search"] button[type="submit"] + p > textarea {
height: 3.7rem;
line-height: 2rem;
}
form[role="search"] button[type="submit"] + p button {
height: 3.7rem;
}
form[role="search"].full button[type="submit"] {
display: none;
}
form[role="search"] button.icon:active,
form[role="search"] button[type="submit"]:active {
background-color: #b2f2ff;
color: #fff;
}
form[role="search"].skin-dark button.icon:active,
form[role="search"].skin-dark button[type="submit"]:active {
background: #00aacc;
}
form[role="search"] button[type="submit"][disabled] {
color: #adadad;
pointer-events: none;
}
form button::-moz-focus-inner {
border: none;
outline: none;
}
/* .bb-editable - e.g email & SMS recipients */
.bb-editable [contenteditable] {
display: inline-block;
box-sizing: border-box;
position: relative;
max-width: calc(100% - 1.4rem);
overflow: hidden;
padding: 0 1.5rem;
margin: 0 0 0.6rem;
line-height: 2.8rem;
border-radius: 1.4rem;
text-overflow: ellipsis;
font-size: 1.6rem;
font-style: italic;
white-space: nowrap;
color: #333;
background: #fff;
}
html[dir="ltr"] .bb-editable [contenteditable].icon {
padding-right: 3.7rem;
}
html[dir="rtl"] .bb-editable [contenteditable].icon {
padding-left: 3.7rem;
}
.bb-editable [contenteditable="false"].invalid {
background-color: #fedcdc;
padding: 0 1.5rem;
}
html[dir="ltr"] .bb-editable [contenteditable="false"].invalid { padding-left: 3.3rem; }
html[dir="rtl"] .bb-editable [contenteditable="false"].invalid { padding-right: 3.3rem; }
.bb-editable [contenteditable="false"].invalid:before {
content: "!";
position: absolute;
top: 0.6rem;
display: block;
width: 1.7rem;
height: 1.6rem;
padding: 0;
border-radius: 50%;
line-height: 1.6rem;
font-size: 1.2rem;
text-align: center;
text-indent: -0.1rem;
font-style: normal;
font-weight: 600;
color: #fff;
background-color: #b80404;
}
html[dir="ltr"] .bb-editable [contenteditable="false"].invalid:before { left: 0.8rem; }
html[dir="rtl"] .bb-editable [contenteditable="false"].invalid:before { right: 0.8rem; }
.bb-editable [contenteditable="false"].invalid:hover:before,
.bb-editable [contenteditable="false"].invalid:active:before {
color: #b2f2ff;
background-color: #fff;
}
.bb-editable [contenteditable]:hover,
.bb-editable [contenteditable]:active,
.bb-editable [contenteditable].invalid:hover,
.bb-editable [contenteditable].invalid:active {
color: #fff;
background-color: #b2f2ff;
}
.bb-editable [contenteditable].icon:after {
content: "";
position: absolute;
top: 0.7rem;
width: 3rem;
height: 3rem;
background-repeat: no-repeat;
background-size: 3rem;
}
html[dir="ltr"] .bb-editable [contenteditable].icon:after {
right: 0;
background-position: -0.7rem -3.8rem;
}
html[dir="rtl"] .bb-editable [contenteditable].icon:after {
left: 0;
background-position: 0.7rem -3.8rem;
}
html[dir="ltr"] .bb-editable [contenteditable].icon:hover:after,
html[dir="ltr"] .bb-editable [contenteditable].icon:active:after {
background-position: -0.7rem -0.8rem;
}
html[dir="rtl"] .bb-editable [contenteditable].icon:hover:after,
html[dir="rtl"] .bb-editable [contenteditable].icon:active:after {
background-position: 0.7rem -0.8rem;
}
html[dir="ltr"] .bb-editable [contenteditable].invalid.icon {
padding-right: 3.7rem;
}
html[dir="rtl"] .bb-editable [contenteditable].invalid.icon {
padding-left: 3.7rem;
}
html[dir="ltr"] .bb-editable [contenteditable].invalid.icon:after {
background-position: -0.7rem -6.8rem;
}
html[dir="rtl"] .bb-editable [contenteditable].invalid.icon:after {
background-position: 0.7rem -6.8rem;
}
html[dir="ltr"] .bb-editable [contenteditable].invalid.icon:hover:after,
html[dir="ltr"] .bb-editable [contenteditable].invalid.icon:active:after {
background-position: -0.7rem -0.8rem;
}
html[dir="rtl"] .bb-editable [contenteditable].invalid.icon:hover:after,
html[dir="rtl"] .bb-editable [contenteditable].invalid.icon:active:after {
background-position: 0.7rem -0.8rem;
}
.bb-editable [contenteditable="true"],
.bb-editable [contenteditable="true"]:hover,
.skin-comms .bb-editable [contenteditable="true"]:hover {
padding: 0 0.2rem;
white-space: normal;
font-size: 1.8rem;
font-style: normal;
color: #000;
background: none repeat scroll 0 0 transparent;
}
.skin-comms .bb-editable [contenteditable]:hover,
.skin-comms .bb-editable [contenteditable]:active {
color: #fff;
background-color: #29bbb5;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 9 9"><path fill="#00AAC5" enable-background="new" d="M9 0L9 9 0 9"/></svg>

After

Width:  |  Height:  |  Size: 148 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 9 9"><path fill="#B2F2FF" d="M9 0v9h-9"/></svg>

After

Width:  |  Height:  |  Size: 121 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 9 9"><path opacity=".5" fill="#A9A9A9" d="M9 0L9 9 0 9"/></svg>

After

Width:  |  Height:  |  Size: 137 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 9 9"><path opacity=".5" fill="#A9A9A9" enable-background="new" d="M0 0L0 9 9 9"/></svg>

After

Width:  |  Height:  |  Size: 161 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 9 9"><path fill="#00AAC5" enable-background="new" d="M0 0L0 9 9 9"/></svg>

After

Width:  |  Height:  |  Size: 148 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><path fill-rule="evenodd" clip-rule="evenodd" fill="#424242" d="M23.385 21.494c.266.266.057.903-.465 1.426s-1.16.729-1.426.465l-4.041-4.039c-1.01.656-2.21 1.047-3.505 1.047-3.561 0-6.448-2.885-6.448-6.446 0-3.56 2.887-6.446 6.448-6.446 3.562 0 6.449 2.887 6.449 6.446 0 1.297-.393 2.499-1.051 3.509l4.041 4.039h-.002zm-9.437-12.459c-2.712 0-4.914 2.198-4.914 4.912s2.202 4.913 4.914 4.913c2.714 0 4.914-2.199 4.914-4.913-.001-2.714-2.2-4.912-4.914-4.912z"/></svg>

After

Width:  |  Height:  |  Size: 546 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><path fill-rule="evenodd" clip-rule="evenodd" fill="#8F9091" d="M23.385 21.494c.266.266.057.903-.465 1.426s-1.16.729-1.426.465l-4.041-4.039c-1.01.656-2.21 1.047-3.505 1.047-3.561 0-6.448-2.885-6.448-6.446 0-3.56 2.887-6.446 6.448-6.446 3.562 0 6.449 2.887 6.449 6.446 0 1.297-.393 2.499-1.051 3.509l4.041 4.039h-.002zm-9.437-12.459c-2.712 0-4.914 2.198-4.914 4.912s2.202 4.913 4.914 4.913c2.714 0 4.914-2.199 4.914-4.913-.001-2.714-2.2-4.912-4.914-4.912z"/></svg>

After

Width:  |  Height:  |  Size: 546 B

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