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

149
gaia/apps/download/js/pick.js Executable file
View file

@ -0,0 +1,149 @@
'use strict';
/* global DownloadStore, DownloadFormatter, DownloadHelper, LazyLoader */
/*
* This library implements a picker activity for downloaded files.
*
* How it works:
*
* var activity = new MozActivity({
* name: 'pick',
* data: {
* type: 'application/*'
* }
* });
*
* ... and returns an object like this:
*
* activity.onsuccess = function(e) {
* console.log('Name of the file:', activity.result.name);
* console.log('Mime type:', activity.result.type);
* console.log('Binary content:', activity.result.blob);
* console.log('File path:', activity.result.path);
* console.log('File size in bytes:', activity.result.size);
* };
*/
(function(exports) {
const CHUNK_SIZE = 10;
function DownloadPicker() {
this.header = document.querySelector('#header');
this.list = document.querySelector('#downloads ul');
// It holds download objects indexed by id.
this.downloads = Object.create(null);
navigator.mozSetMessageHandler('activity', this.handleActivity.bind(this));
}
DownloadPicker.prototype = {
handleActivity: function(activity) {
switch (activity.source.name) {
case 'pick':
this.activity = activity;
this.attachActionHandlers();
this.renderList();
break;
default:
activity.postError('name not supported');
}
},
renderList: function() {
DownloadStore.getAll().onsuccess = (event) => {
var result = event.target.result;
if (!Array.isArray(result)) {
result = [result];
}
// Painting downloads with reverse insertion order (the last the
// most recent).
var items = result.reverse();
// If document.body.dataset.downloads === 0 the UI will show a message
// telling user that there is not downloads.
var total = document.body.dataset.downloads = items.length;
this.renderFragment(items, 0, total);
};
},
renderFragment: function(items, from, total) {
var target = document.createDocumentFragment();
var idx = from;
for (; idx < from + CHUNK_SIZE && idx < total; idx++) {
this.appendDownload(items[idx], target);
}
this.list.appendChild(target);
(idx < total) && setTimeout(() => {
this.renderFragment(items, idx, total);
});
},
appendDownload: function(download, target) {
var id = download.id;
if (this.downloads[id]) {
return;
}
this.downloads[id] = download;
var item = document.createElement('li');
// This parameter lets us know which item was clicked in the list
item.dataset.id = id;
// File name
var pFileName = document.createElement('p');
pFileName.classList.add('fileName');
pFileName.textContent = DownloadFormatter.getFileName(download);
// Additional information (start time and file size in bytes)
var pInfo = document.createElement('p');
pInfo.classList.add('info');
DownloadFormatter.getDate(download, (date) => {
DownloadFormatter.getTotalSize(download).then(totalSize => {
navigator.mozL10n.setAttributes(pInfo, 'summary', {
date: date,
status: totalSize
});
});
});
item.appendChild(pFileName);
item.appendChild(pInfo);
target.appendChild(item);
},
attachActionHandlers: function() {
this.header.addEventListener('action', () => {
// closing the picker activity
this.activity.postError('cancelled');
});
this.list.addEventListener('click', (event) => {
var download = this.downloads[event.target.dataset.id];
download && this.pick(download);
});
},
pick: function(download) {
// download was selected by the user
LazyLoader.load(['shared/js/mime_mapper.js',
'shared/js/download/download_helper.js'], () => {
var req = DownloadHelper.info(download);
req.onsuccess = (event) => {
this.activity.postResult(event.target.result);
};
req.onerror = (event) => {
DownloadHelper.handlerError(req.error, download);
};
});
}
};
exports.downloadPicker = new DownloadPicker();
}(window));

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

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

View file

@ -0,0 +1 @@
select-download = Select download

View file

@ -0,0 +1,60 @@
{
"name": "Downloads",
"description": "Gaia Download Picker",
"type": "certified",
"role": "system",
"orientation": "default",
"developer": {
"name": "The Gaia Team",
"url": "https://github.com/mozilla-b2g/gaia"
},
"permissions": {
"device-storage:pictures": {
"access": "readonly"
},
"device-storage:music": {
"access": "readonly"
},
"device-storage:videos": {
"access": "readonly"
},
"device-storage:sdcard": {
"access": "readonly"
},
"moz-extremely-unstable-and-will-change-webcomponents": {}
},
"activities": {
"pick": {
"filters": {
"type": "application/*"
},
"disposition": "inline",
"returnValue": true,
"href": "/pick.html"
}
},
"datastores-access": {
"download_store": {
"readonly": true,
"description": "Stores download finished"
}
},
"icons": {
"84": "/style/icons/download_84.png",
"126": "/style/icons/download_126.png",
"142": "/style/icons/download_142.png",
"189": "/style/icons/download_189.png",
"284": "/style/icons/download_284.png"
},
"locales": {
"en-US": {
"name": "Downloads",
"description": "Gaia Download Picker"
},
"fr": {
"name": "Téléchargements",
"description": "Sélection de téléchargements de Gaia"
}
},
"default_locale": "en-US"
}

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

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

61
gaia/apps/download/pick.html Executable file
View file

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<meta charset="utf-8">
<title>Download Picker</title>
<!-- Localization -->
<meta name="defaultLanguage" content="en-US">
<meta name="availableLanguages" content="en-US">
<link rel="localization" href="shared/locales/date/date.{locale}.properties">
<link rel="localization" href="shared/locales/download/download.{locale}.properties">
<link rel="localization" href="shared/elements/gaia_menu/locales/gaia_menu.{locale}.properties">
<link rel="localization" href="locales/download.{locale}.properties">
<script type="text/javascript" src="shared/js/l10n.js"></script>
<script defer type="text/javascript" src="shared/js/moz_intl.js"></script>
<!-- Web Components -->
<script defer src="shared/elements/config.js"></script>
<!-- <link rel="stylesheet" type="text/css" href="shared/elements/gaia-icons/gaia-icons.css" /> -->
<link rel="stylesheet" type="text/css" href="shared/elements/gaia-theme/gaia-theme.css">
<script defer src="shared/elements/gaia-header/dist/gaia-header.js"></script>
<!-- <script defer src="shared/js/component_utils.js"></script> -->
<!-- <script defer src="shared/elements/gaia_menu/script.js"></script> -->
<link rel="stylesheet" type="text/css" href="shared/style/lists.css">
<link rel="stylesheet" href="style/pick.css">
<script type="text/javascript" defer src="shared/js/lazy_loader.js"></script>
<script type="text/javascript" defer src="shared/js/download/download_store.js"></script>
<!-- <script type="text/javascript" defer src="shared/js/mime_mapper.js"></script> -->
<!-- <script type="text/javascript" defer src="shared/js/download/download_ui.js"></script> -->
<!-- <script type="text/javascript" defer src="shared/js/download/download_helper.js"></script> -->
<script type="text/javascript" defer src="shared/js/download/download_formatter.js"></script>
<!-- Specific code -->
<script type="text/javascript" defer src="js/pick.js"></script>
<!-- Error messages -->
<link rel="stylesheet" type="text/css" href="shared/style/buttons.css">
<link rel="stylesheet" type="text/css" href="shared/style/confirm.css">
</head>
<body class="theme-media">
<section role="region" class="skin-dark">
<gaia-header ignore-dir id="header" action="close">
<h1 data-l10n-id="select-download"></h1>
</gaia-header>
<section id="no-downloads">
<span></span>
<span data-l10n-id="no-downloads"></span>
</section>
<section id="downloads" data-type="list" class="active">
<ul>
</ul>
</section>
</section>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,63 @@
html, body {
width: 100%;
height: 100%;
margin: 0;
font-size: 10px;
}
body > section {
width: 100%;
height: 100%;
}
/* No downloads message */
#no-downloads {
height: calc(100% - 5rem);
text-align: center;
display: none;
}
body[data-downloads="0"] #no-downloads {
display: block;
}
#no-downloads span:first-child {
height: 100%;
display: inline-block;
width: 0;
vertical-align: middle;
}
#no-downloads span:nth-child(2) {
font-size: 1.8rem;
color: #1CABCB;
}
/* List of downloads */
#downloads {
height: calc(100% - 5rem);
font-weight: 400;
padding: 0;
overflow: hidden;
}
#downloads ul {
overflow-y: auto;
height: 100%;
}
/* Only <li> elements are touchable */
#downloads li p {
pointer-events: none;
}
#downloads li .fileName {
padding: 1rem 6rem 1rem 3.5rem;
}
#downloads li .info {
font-size: 1.2rem;
padding: 0 6rem 0 3.5rem;
}

View file

@ -0,0 +1,27 @@
'use strict';
/* jshint node: true, mocha: true */
/* global suiteSetup */
var helper = require('helper');
suite('Download tests', function() {
suiteSetup(helper.cleanupWorkspace);
teardown(helper.cleanupWorkspace);
test('APP=download make', function(done) {
helper.exec('APP=download make', function(error, stdout, stderr) {
helper.checkError(error, stdout, stderr);
var zipPath = process.cwd() +
'/profile/webapps/download.gaiamobile.org/application.zip';
var checkList = ['manifest.webapp'];
checkList.forEach(function(filePath) {
helper.checkFilePathInZip(zipPath, filePath);
});
done();
});
});
});

View file

@ -0,0 +1,165 @@
'use strict';
/* global MockL10n, loadBodyHTML, DownloadStore, downloadPicker,
DownloadFormatter, MockMozIntl */
/* global require, requireApp, suite, suiteTeardown, suiteSetup, test, assert,
MocksHelper, sinon */
require('/shared/test/unit/load_body_html_helper.js');
require('/shared/js/download/download_store.js');
require('/shared/js/download/download_formatter.js');
require('/shared/test/unit/mocks/mock_navigator_moz_set_message_handler.js');
require('/shared/test/unit/mocks/mock_l10n.js');
require('/shared/test/unit/mocks/mock_moz_intl.js');
require('/shared/test/unit/mocks/mock_lazy_loader.js');
var mocksHelperForDownloadPicker = new MocksHelper([
'LazyLoader'
]).init();
suite('pick.js >', function() {
var realSetMessageHandler = null;
var realL10n = null;
var realMozIntl = null;
var downloads = [];
function createSource(name, type) {
return {
name: name,
data: {
type: type
}
};
}
function runActivity(data) {
navigator.mozSetMessageHandler.mMessageHandlers.activity(data);
}
mocksHelperForDownloadPicker.attachTestHelpers();
suiteSetup(function(done) {
realL10n = navigator.mozL10n;
navigator.mozL10n = MockL10n;
realMozIntl = window.mozIntl;
window.mozIntl = MockMozIntl;
realSetMessageHandler = navigator.mozSetMessageHandler;
navigator.mozSetMessageHandler = window.MockNavigatormozSetMessageHandler;
navigator.mozSetMessageHandler.mSetup();
loadBodyHTML('/pick.html');
sinon.stub(DownloadStore, 'getAll', function() {
return {
set onsuccess(cb) {
cb({ target: { result: downloads }});
}
};
});
requireApp('download/js/pick.js', done);
});
suiteTeardown(function() {
navigator.mozL10n = realL10n;
navigator.mozSetMessageHandler.mTeardown();
navigator.mozSetMessageHandler = realSetMessageHandler;
window.mozIntl = realMozIntl;
DownloadStore.getAll.restore();
});
suite('Initialization >', function() {
test(' download picker ready ', function() {
var handlers = navigator.mozSetMessageHandler.mMessageHandlers;
assert.equal(Object.keys(handlers).length, 1);
assert.isFunction(handlers.activity);
});
});
suite('No downloads >', function() {
test(' document.body.dataset.downloads is 0', function() {
runActivity({
source: createSource('pick', 'application/*')
});
assert.equal(document.body.dataset.downloads, 0);
});
});
suite('Downloads >', function() {
suiteSetup(function() {
downloads = [
{
id: '1',
startTime: new Date(),
path: '/music.mp3'
}, {
id: '2',
startTime: new Date(),
path: '/image.gif'
}
];
runActivity({
source: createSource('pick', 'application/*')
});
});
suiteTeardown(function() {
downloadPicker.pick.restore();
});
function checkItem(download) {
var item = downloadPicker.list.querySelector('li[data-id="' +
download.id + '"]');
assert.equal(item.querySelector('.fileName').textContent,
DownloadFormatter.getFileName(download));
}
test(' rendered list', function() {
assert.equal(document.body.dataset.downloads, downloads.length);
assert.equal(downloadPicker.list.querySelectorAll('li').length,
downloads.length);
checkItem(downloads[0]);
checkItem(downloads[1]);
});
test(' click an item', function() {
var expectedDownload = downloads[0];
sinon.stub(downloadPicker, 'pick', function(download) {
assert.equal(download, expectedDownload);
return {};
});
var item = downloadPicker.list.querySelector('li[data-id="' +
expectedDownload.id + '"]');
item.click();
});
});
suite('Cancel action >', function() {
test(' clicking on cross icon', function() {
runActivity({
source: createSource('pick', 'application/*'),
postError: function(result) {
assert.equal(result, 'cancelled');
}
});
downloadPicker.header.dispatchEvent(new CustomEvent('action'));
});
});
suite('Error >', function() {
test(' name not supported', function() {
// "share" activities are not supported
runActivity({
source: createSource('share', 'application/*'),
postError: function(result) {
assert.equal(result, 'name not supported');
}
});
});
});
});

View file

@ -0,0 +1,4 @@
'use strict';
/* global require */
require('/shared/test/unit/mocks/mocks_helper.js');