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,7 @@
;(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();},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,30 @@
;'use strict';var DownloadStore=(function(){var datastore;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;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);};});}
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];}).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';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'));}
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];});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':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);datastore.add(downloadCooked).then(function(id){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){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{get:get,getAll:getAll,add:add,remove:remove,addListener:addListener,removeListener:removeListener};}());

View file

@ -0,0 +1,358 @@
'use strict';
/* exported DownloadUI */
/* global DownloadFormatter */
/* global LazyLoader */
/* global MimeMapper */
/**
* This file defines a component to show download confirmations
*
* - Stop download (Are you sure you want to stop the download?)
* - Download stopped (Download was stopped. Try downloading again?)
* - Download failed (xfile failed to download. Try downloading again?)
* - Delete download (Delete xfile?)
* - Unsupported file type
* - File not found
* - File open error
* - No provider to share file
*
* var request = DownloadUI.show(DownloadUI.TYPE.STOP, download);
*
* request.oncancel = function() {
* alert('CANCEL');
* };
*
* request.onconfirm = function() {
* alert('CONFIRM');
* };
*
* WARNING: To use this library you need to include 'shared/js/l10n.js'
*
*/
var DownloadUI = (function() {
/**
* Download type constructor
*
* @param {String} Type name
* @param {Array} CSS classes to confirm button
* @param {Boolean} Message without parameters
*/
var DownloadType = function(name, classes, isPlainMessage) {
this.name = name;
this.classes = classes;
this.isPlainMessage = isPlainMessage;
this.numberOfButtons = classes.indexOf('full') !== -1 ? 1 : 2;
};
/**
* Errors reported by the Downloads API.
*/
var ERRORS = {
NO_MEMORY: 2152857616,
NO_SDCARD: 2152857618,
UNMOUNTED_SDCARD: 2152857621
};
var TYPES = {
STOP: new DownloadType('stop', ['danger'], true),
STOPPED: new DownloadType('stopped', ['recommend'], true),
FAILED: new DownloadType('failed', ['recommend']),
DELETE: new DownloadType('delete', ['danger']),
DELETE_ALL: new DownloadType('delete_all', ['danger']),
UNSUPPORTED_FILE_TYPE: new DownloadType('unsupported_file_type',
['danger']),
FILE_NOT_FOUND: new DownloadType('file_not_found', ['recommend', 'full'],
true),
FILE_OPEN_ERROR: new DownloadType('file_open_error', ['danger']),
NO_SDCARD: new DownloadType('no_sdcard_found_2', ['recommend', 'full'],
true),
UNMOUNTED_SDCARD: new DownloadType('unmounted_sdcard_2', ['recommend',
'full'], true),
NO_PROVIDER: new DownloadType('no_provider', ['recommend', 'full'], true),
NO_MEMORY: new DownloadType('no_memory', ['recommend', 'full'], true)
};
var DownloadAction = function(id, type) {
this.id = id;
this.name = id.toLowerCase();
this.title = this.name + '_downloaded_file';
this.type = type;
};
var ACTIONS = {
OPEN: new DownloadAction('OPEN', 'confirm'),
SHARE: new DownloadAction('SHARE', 'confirm'),
WALLPAPER: new DownloadAction('WALLPAPER', 'confirm'),
RINGTONE: new DownloadAction('RINGTONE', 'confirm')
};
// Confirm dialog containers
var confirm = null;
var actionMenu = null;
/**
* Request auxiliary object to support asynchronous calls
*/
var Request = function() {
this.cancel = function() {
removeContainers();
if (typeof this.oncancel === 'function') {
this.oncancel();
}
};
this.confirm = function(result) {
removeContainers();
if (typeof this.onconfirm === 'function') {
this.result = result;
this.onconfirm({
target: this
});
}
};
};
function removeContainers() {
removeConfirm();
removeActionMenu();
}
function addConfirm() {
if (confirm !== null) {
confirm.parentNode.removeChild(confirm);
}
confirm = document.createElement('form');
confirm.id = 'downloadConfirmUI';
confirm.setAttribute('role', 'dialog');
confirm.setAttribute('data-type', 'confirm');
confirm.setAttribute('data-z-index-level', '100');
var screen = document.getElementById('screen');
var container = screen || document.body;
container.appendChild(confirm);
}
function removeConfirm() {
if (confirm === null) {
return;
}
confirm.innerHTML = '';
confirm.style.display = 'none';
}
// When users click or hold on home button UIs should be removed
window.addEventListener('home', removeContainers);
window.addEventListener('holdhome', removeContainers);
function l10n(element, l10nid, l10nargs) {
// First set our args.
if (l10nargs) {
element.setAttribute('data-l10n-args', JSON.stringify(l10nargs));
}
// Then localize.
element.setAttribute('data-l10n-id', l10nid);
return element;
}
function createConfirm(type, req, downloads) {
addConfirm();
var dialog = document.createElement('section');
// Header
var header = document.createElement('h1');
l10n(header, type.name + '_download_title');
dialog.appendChild(header);
// Message
var message = document.createElement('p');
if (type.isPlainMessage) {
l10n(message, type.name + '_download_message');
} else {
var args = Object.create(null);
if (type === TYPES.DELETE_ALL) {
args.number = downloads.length;
} else {
args.name = DownloadFormatter.getFileName(downloads[0]);
}
l10n(message, type.name + '_download_message', args);
}
dialog.appendChild(message);
var menu = document.createElement('menu');
menu.dataset.items = type.numberOfButtons;
if (type.numberOfButtons === 2) {
// Left button
var lButton = document.createElement('button');
lButton.type = 'button';
lButton.setAttribute('data-l10n-id',
type.name + '_download_left_button');
lButton.onclick = function l_cancel() {
lButton.onclick = null;
req.cancel();
};
menu.appendChild(lButton);
}
// Right button
var rButton = document.createElement('button');
rButton.type = 'button';
type.classes.forEach(function(clazz) {
rButton.classList.add(clazz);
});
rButton.setAttribute('data-l10n-id',
type.name + '_download_right_button');
rButton.onclick = function r_confirm() {
rButton.onclick = null;
req.confirm();
};
menu.appendChild(rButton);
dialog.appendChild(menu);
confirm.appendChild(dialog);
confirm.style.display = 'block';
}
function addActionMenu() {
// If we have an actionMenu, empty it and re-create it.
if (actionMenu !== null) {
document.body.removeChild(actionMenu);
}
actionMenu = document.createElement('gaia-menu');
actionMenu.id = 'downloadActionMenuUI';
actionMenu.className = 'actions';
}
function removeActionMenu() {
if (actionMenu === null) {
return;
}
actionMenu.hide();
document.body.removeChild(actionMenu);
actionMenu = null;
}
function createActionMenu(req, download) {
var actions = [ACTIONS.OPEN, ACTIONS.SHARE];
var fileName = DownloadFormatter.getFileName(download);
var type = MimeMapper.guessTypeFromFileProperties(fileName,
download.contentType);
if (type.length > 0) {
if (type.startsWith('image/')) {
actions.push(ACTIONS.WALLPAPER);
} else if (type.startsWith('audio/')) {
actions.push(ACTIONS.RINGTONE);
}
}
doCreateActionMenu(req, fileName, actions);
}
function doCreateActionMenu(req, fileName, actions) {
addActionMenu();
var header = document.createElement('header');
header.textContent = fileName;
actionMenu.appendChild(header);
actions.forEach(function addActionButton(action) {
var button = document.createElement('button');
button.id = action.id;
l10n(button, action.title);
button.dataset.type = action.type;
actionMenu.appendChild(button);
button.addEventListener('click', function buttonCliked(evt) {
button.removeEventListener('click', buttonCliked);
req[evt.target.dataset.type](ACTIONS[evt.target.id]);
});
});
document.body.appendChild(actionMenu);
actionMenu.removeAttribute('hidden');
}
var styleSheets = [
'shared/style/buttons.css',
'shared/style/headers.css',
'shared/style/confirm.css'
];
/*
* Shows a confirmation depending on type. It returns a request object with
* oncancel and onconfirm callbacks
*
* @param {String} Confirmation type
*
* @param {Array} It represents the download(s) object(s)
*
* @param {Boolean} This optional parameter indicates if the library should
* include BBs
*/
function show(type, downloads, ignoreStyles) {
var req = new Request();
downloads = Array.isArray(downloads) ? downloads : [downloads];
window.setTimeout(function() {
var libs = ['shared/js/download/download_formatter.js'];
if (!ignoreStyles) {
libs.push.apply(libs, styleSheets);
}
// We have to discover the type of UI depending on state when type is null
if (type === null) {
type = TYPES.STOPPED;
var download = downloads[0];
if (download.state === 'finalized' ||
download.state === 'stopped' && download.error !== null) {
type = TYPES.FAILED;
}
}
LazyLoader.load(libs, createConfirm.call(this, type, req, downloads));
}, 0);
return req;
}
function showActions(download) {
var req = new Request();
window.setTimeout(function() {
LazyLoader.load(['shared/js/mime_mapper.js',
'shared/js/download/download_formatter.js',
'shared/js/component_utils.js',
'shared/elements/gaia_menu/script.js'],
createActionMenu.call(this, req, download));
}, 0);
return req;
}
return {
show: show,
showActions: showActions,
hide: removeContainers,
get ERRORS() {
return ERRORS;
},
get TYPE() {
return TYPES;
}
};
}());