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,49 @@
'use strict';
var utils = window.utils || {};
if (!utils.dom) {
(function(document) {
var Dom = utils.dom = {};
// remove all children of a given parent node
Dom.removeChildNodes = function(node) {
while (node.hasChildNodes()) {
node.removeChild(node.firstChild);
}
};
// Given a container, applys the class passed to all the
// nodes matching a selector.
// CAUTION: Please be careful using this function as it could
// be quite expensive depending on the container and selector.
Dom.addClassToNodes = function(container, selector, clazz) {
var nodes = container.querySelectorAll(selector);
for (var i = 0, n = nodes.length; i < n; ++i) {
nodes[i].classList.add(clazz);
}
};
// Given a container, removes the class passed to all the
// nodes matching a selector.
// CAUTION: Please be careful using this function as it could
// be quite expensive depending on the container and selector.
Dom.removeClassFromNodes = function(container, selector, clazz) {
var nodes = container.querySelectorAll(selector);
for (var i = 0, n = nodes.length; i < n; ++i) {
nodes[i].classList.remove(clazz);
}
};
Dom.updatePhoto = function(photo, dest) {
var background = '';
if (photo != null) {
background = 'url(' + URL.createObjectURL(photo) + ')';
}
dest.style.backgroundImage = background;
// Only for testing purposes
dest.dataset.photoReady = 'true';
};
})(document);
}

View file

@ -0,0 +1,37 @@
/*jshint loopfunc: true */
'use strict';
var utils = window.utils || {};
if (!utils.listeners) {
(function(document) {
var Listeners = utils.listeners = {};
Listeners.add = function(config) {
try {
for (var id in config) {
var handler = config[id];
var nodes = document.querySelectorAll(id);
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (Array.isArray(handler)) {
handler.forEach(function handle(item) {
if (!item.hasOwnProperty('event') &&
!item.hasOwnProperty('handler')) {
return;
}
node.addEventListener(item.event, item.handler);
});
} else {
node.addEventListener('click', handler);
}
} // nodes
} // Handlers
}
catch (e) {
window.console.error('Error while registering listener for: ', id, e);
}
}; // Add function
})(document);
}

View file

@ -0,0 +1,92 @@
'use strict';
if (!window.Rest) {
window.Rest = (function() {
function RestRequest(xhr) {
var cancelled = false;
this.cancel = function oncancel() {
cancelled = true;
window.setTimeout(xhr.abort.bind(xhr), 0);
};
this.isCancelled = function isCancelled() {
return cancelled;
};
}
function Rest() { }
Rest.prototype = {
get: function(uri, callback, pOptions) {
var DEFAULT_TIMEOUT = 30000;
var options = pOptions || {};
var xhr = new XMLHttpRequest({
mozSystem: true
});
var outReq = new RestRequest(xhr);
xhr.open('GET', uri, true);
var responseType = options.responseType || 'json';
xhr.responseType = responseType;
var responseProperty = responseType === 'xml' ?
'responseXML' : 'response';
xhr.timeout = options.operationsTimeout || DEFAULT_TIMEOUT;
if (!xhr.timeout || xhr.timeout === DEFAULT_TIMEOUT &&
(parent && parent.config && parent.config.operationsTimeout)) {
xhr.timeout = parent.config.operationsTimeout;
}
if (options.requestHeaders) {
for (var header in options.requestHeaders) {
xhr.setRequestHeader(header, options.requestHeaders[header]);
}
}
xhr.onload = function(e) {
if (xhr.status === 200 || xhr.status === 400 || xhr.status === 0) {
if (callback && typeof callback.success === 'function') {
setTimeout(function() {
callback.success(xhr[responseProperty]);
},0);
}
}
else {
console.error('HTTP error executing GET. ',
uri, ' Status: ', xhr.status);
if (callback && typeof callback.error === 'function') {
setTimeout(function errorHandler() {
callback.error({ status: xhr.status });
}, 0);
}
}
}; // onload
xhr.ontimeout = function(e) {
console.error('Timeout!!! while HTTP GET: ', uri);
if (callback && typeof callback.timeout === 'function') {
setTimeout(callback.timeout, 0);
}
}; // ontimeout
xhr.onerror = function(e) {
console.error('Error while executing HTTP GET: ', uri,
': ', e);
if (callback && typeof callback.error === 'function' &&
!outReq.isCancelled()) {
setTimeout(function() {
callback.error(e);
},0);
}
}; // onerror
xhr.send();
return outReq;
} // get
}; // prototype
return new Rest();
})();
}

View file

@ -0,0 +1,103 @@
/* global Promise, Event */
'use strict';
/**
* Helper library to deal with in case of emergency (ICE)
* Contacts.
* Those contacts are suppose to be first responders to
* an emergency. More info:
* https://wiki.mozilla.org/Gaia/Comms/2.1/Ice_Contacts
*
* The information will be stored in a DS for sharing
* between contacts and dialer.
* The content will be an array of two contacts ids.
* This helper will be used for fetching:
* ICEStore.getContacts().then(function(iceContacts) {
* ...
* });
* and setting the data:
* ICEStore.setContacts(ids).then(function() {
* ...
* });
*/
(function(exports) {
var STORE_NAME = 'ice_contacts';
var store = null;
var status = 'not_initialized';
var FIELD = 'ICE_CONTACTS';
// Get a reference to the store, or returns it if
// we already have.
function init() {
if (store !== null) {
return Promise.resolve(store);
}
if (!navigator.getDataStores) {
return Promise.reject('No DS supported');
}
if (status === 'initialising') {
document.addEventListener('ice_ready', function ready() {
document.removeEventListener('ice_ready', ready);
if (store === null) {
return Promise.reject('Error getting store');
} else {
return Promise.resolve(store);
}
});
}
status = 'initialising';
return new Promise(function(resolve, reject) {
navigator.getDataStores(STORE_NAME).then(function(stores) {
if (!stores || stores.length < 0) {
document.dispatchEvent(new Event('on_ice_ready'));
return reject('No DS found');
} else {
store = stores[0];
status = 'initialised';
document.dispatchEvent(new Event('ice_ready'));
return resolve(store);
}
});
});
}
var ICEStore = {
/**
* Set the in case of emergency contacts.
* @param {Array} iceContacts array of contacts id.
* @returns {Promise}
*/
setContacts: function(iceContacts) {
return init().then(function() {
return store.put(iceContacts, FIELD).then(function() {
return Promise.resolve(iceContacts);
});
});
},
/**
* Retrieves the in case of emergency contacts
* @returns {Promise}
*/
getContacts: function() {
return init().then(function() {
return store.get(FIELD);
});
},
/**
* Subscribe to changes in the store
* @param {Function} Callback when a change happens
*/
onChange: function(cb) {
return init().then(function() {
store.addEventListener('change', function() {
store.get(FIELD).then(cb);
});
});
}
};
exports.ICEStore = ICEStore;
})(window);

View file

@ -0,0 +1,207 @@
/* exported ImageLoader */
'use strict';
if (!window.ImageLoader) {
var ImageLoader = function ImageLoader(pContainer, pItems) {
var container, items, itemsSelector, lastScrollTime, scrollLatency = 100,
scrollTimer, itemHeight, total, imgsLoading = 0,
loadImage = defaultLoadImage, self = this;
init(pContainer, pItems);
/**
* Initializer
*
*/
function init(pContainer, pItems) {
itemsSelector = pItems;
container = document.querySelector(pContainer);
attachHandlers();
window.addEventListener('image-loader-resume', resuming);
window.addEventListener('image-loader-pause', unload);
load();
}
function resuming() {
window.clearTimeout(scrollTimer);
attachHandlers();
update();
}
function onUpdate(evt) {
evt.stopPropagation();
onScroll();
}
function load() {
window.clearTimeout(scrollTimer);
scrollTimer = null;
items = container.querySelectorAll(itemsSelector);
// All items have the same height
itemHeight = items[0] ? items[0].offsetHeight : 1;
total = items.length;
// Initial check if items should appear
window.setTimeout(update, 0);
}
function attachHandlers() {
container.addEventListener('scroll', onScroll);
document.addEventListener('onupdate', onUpdate);
}
function unload() {
container.removeEventListener('scroll', onScroll);
document.removeEventListener('onupdate', onUpdate);
window.clearTimeout(scrollTimer);
scrollTimer = null;
}
function setResolver(pResolver) {
loadImage = pResolver;
}
function onScroll() {
if (imgsLoading > 0) {
// Stop the pending images load
window.stop();
imgsLoading = 0;
}
// Clearing and setting a timer on every scroll event is too slow on
// some mobile devices. Therefore, set a timer once here and then
// check how long it has been since the last scroll event in the
// timer handler to determine what to do.
lastScrollTime = Date.now();
if (!scrollTimer) {
scrollTimer = window.setTimeout(updateFromScroll, scrollLatency);
}
}
function updateFromScroll() {
scrollTimer = null;
// If we scrolled more since the timer was set, then we need to
// delay again. Otherwise, go ahead and update now.
var deltaLatency = lastScrollTime - Date.now() + scrollLatency;
if (deltaLatency > 0) {
scrollTimer = window.setTimeout(updateFromScroll, deltaLatency);
} else {
update();
}
}
/**
* Loads the image contained in a DOM Element.
*/
function defaultLoadImage(item) {
var image = item.querySelector('span[data-type=img][data-src]');
if (!image) {
image = item.querySelector('img[data-src]');
if (!image) {
// Image by default
image = item.querySelector('span[data-type=img][data-group]');
if (image) {
item.dataset.visited = 'true';
}
return;
}
}
++imgsLoading;
var tmp = new Image();
var src = tmp.src = image.dataset.src;
tmp.onload = function onload() {
--imgsLoading;
image.style.backgroundImage = 'url(' + src + ')';
if (tmp.complete) {
item.dataset.visited = 'true';
}
tmp = null;
};
tmp.onabort = tmp.onerror = function onerror() {
--imgsLoading;
item.dataset.visited = 'false';
tmp = null;
};
}
/**
* Calculates the set of items are in the current viewport
*
*/
function update() {
if (total === 0) {
return;
}
var viewTop = container.scrollTop;
// Index is always inside or below viewport
var index = Math.floor(viewTop / itemHeight);
var containerHeight = container.offsetHeight;
// Goes backward
for (var i = index; i >= 0; i--) {
var item = items[i];
if (item) {
if (item.offsetTop + itemHeight < viewTop) {
break; // Over
}
if (item.dataset.visited !== 'true' &&
item.offsetTop <= viewTop + containerHeight) {
loadImage(item, self); // Inside
}
}
}
// Goes forward
for (var j = index + 1; j < total; j++) {
var theItem = items[j];
if (!theItem) {
// Returning because of index out of bound
return;
}
if (theItem.offsetTop > viewTop + containerHeight) {
return; // Below
}
if (theItem.dataset.visited !== 'true') {
loadImage(theItem, self);
}
}
} // update
function releaseImage(item) {
var image = item.querySelector('span[data-type=img][data-src]');
if (!image) {
// Image by default
image = item.querySelector('span[data-type=img][data-group]');
if (image) {
item.dataset.visited = 'false';
}
return null;
}
image.style.backgroundImage = 'none';
item.dataset.visited = 'false';
return image;
}
// It's necessary to prevent unit test errors
function destroy() {
unload();
window.removeEventListener('image-loader-pause', unload);
window.removeEventListener('image-loader-resume', resuming);
container = items = itemsSelector = lastScrollTime = scrollLatency = null;
scrollTimer = itemHeight = total = imgsLoading = loadImage = null;
}
this.reload = load;
this.unload = unload;
this.setResolver = setResolver;
this.defaultLoad = defaultLoadImage;
this.releaseImage = releaseImage;
this.destroy = destroy;
};
}

View file

@ -0,0 +1,41 @@
'use strict';
var utils = window.utils || {};
if (typeof utils.squareImage === 'undefined') {
utils.squareImage = function(blob, callback) {
var img = document.createElement('img');
var url = URL.createObjectURL(blob);
img.src = url;
function cleanupImg() {
img.src = '';
URL.revokeObjectURL(url);
}
img.onerror = cleanupImg;
img.onload = function onBlobLoad() {
var width = img.width;
var height = img.height;
if (width === height) {
cleanupImg();
callback(blob);
} else {
var canvas = document.createElement('canvas');
var min = canvas.width = canvas.height = Math.min(width, height);
var context = canvas.getContext('2d', { willReadFrequently: true });
context.drawImage(img, (width - min) / 2, (height - min) / 2, min, min,
0, 0, min, min);
cleanupImg();
canvas.toBlob(function onCanvasToBlob(canvasBlob) {
context = null;
canvas.width = canvas.height = 0;
canvas = null;
callback(canvasBlob);
}, 'image/jpeg', 0.95);
}
};
}; // utils.squareImage
} // if

View file

@ -0,0 +1,77 @@
'use strict';
/* global ImageUtils, LazyLoader */
/*
* WARNING: This module LazyLoads
*
* '/shared/js/contacts/import/utilities/config.js',
* '/shared/js/image_utils.js'
*
*/
var utils = window.utils || {};
(function(utils) {
var DEPENDENCIES = [
'/shared/js/contacts/import/utilities/config.js',
'/shared/js/image_utils.js'
];
var dpr = window.devicePixelRatio || 1;
var CONFIG_FILE = '/config-images.json';
// Default data for creating a thumbnail. This will be used if a config file
// is not found
var DEFAULT_CONFIG = {
'thumbnail' : {
'format': 'image/jpeg',
'size': 65,
'quality': 1.0
}
};
var THUMB_CONFIG = DEFAULT_CONFIG.thumbnail;
if (typeof utils.thumbnailImage !== 'undefined') {
return;
}
// Obtains the thumbnail configuration by looking for the config file
// If no config file is found, then default values are taken
function getThumbsConfig() {
return new Promise(function(resolve, reject) {
utils.config.load(CONFIG_FILE).then(function resolved(config) {
resolve(config);
}, function rejected() {
resolve(DEFAULT_CONFIG);
});
});
}
function scaleImage(blob, configData) {
var thumbConfig = configData && configData.thumbnail || {};
var thumbnailEdge = (thumbConfig.size || THUMB_CONFIG.size) * dpr;
var format = thumbConfig.format || THUMB_CONFIG.format;
var encodingQuality = thumbConfig.quality || THUMB_CONFIG.quality;
return ImageUtils.resizeAndCropToCover(blob, thumbnailEdge,
thumbnailEdge, format,
encodingQuality);
}
// We keep the aspect ratio and make the smallest edge be
// |thumbnailEdge| long.
utils.thumbnailImage = function(blob, callback) {
LazyLoader.load(DEPENDENCIES, function() {
getThumbsConfig().then(function(config) {
return scaleImage(blob, config);
}).then(callback).catch(function(err) {
console.error('Error while converting image to thumbnail:', err.name);
callback(blob);
});
});
};
})(utils);

View file

@ -0,0 +1,261 @@
'use strict';
var utils = window.utils || {};
if (!utils.templates) {
(function() {
var Templates = utils.templates = {};
/**
* Returns a target HTMLElement from a selector or HTMLElement itself
*
* @param {HTMLElement or Selector} element target element.
*
* @return {HTMLElement} HTMLElement according to the selector or itself.
*
*
*/
function getTarget(element) {
var target = element;
if (!element.tagName) {
target = document.querySelector(element);
}
return target;
}
/**
* Given a target HTML element which contains a template set
* returns the template that will have to be applied over the data
*
* @param {HTMLElement} target which contains the template.
* @param {Object} data to be used on the template.
*
* @return {HTMLElement} HTMLElement with the template.
*
*/
function getTemplate(target, data) {
// Multi templates temporarily disabled to avoid eval usage
// TODO: Implement an alternative for eval
var templates = target.querySelectorAll('*[data-template]');
if (templates.length === 0) {
throw new Error('No template declared');
}
if (templates.length > 1 && templates.item(0).dataset.condition) {
throw new Error('Only one template supported in this version');
}
return {
template: templates.item(0),
isMulti: false
};
}
/**
* Returns a function used to replace data on a template
*
* @param {Object} data the data to be used on the template.
*
* @return {function} to be used.
*
*/
function templateReplace(data) {
return function(text, property) {
var out;
if (property.indexOf('.') === -1) {
out = data[property];
if (Array.isArray(out) && out.length > 0) {
out = out[0];
}
} else {
throw new Error('Dotted expressions not supported');
}
if (typeof out === 'undefined') {
out = text;
}
return out;
};
}
/**
* Adds (append or prepend) a new instance HTMLElement (or array of)
* of a template
* The template is assumed to be a child of the element
* passed as parameter
* The new element will be appended as a child
*
* @param {HTMLElement} ele container lement that contains the template
* and which will contain the new instance. Can be an HTMLElement
* or a CSS selector.
*
* @param {object or array} data with the data displayed by the template.
*
* @param {String} mode oneOf ('A','P').
*
* @return {HTMLElement} (or last element if data is an array).
*
*
*/
function add(element, data, mode, targetNode) {
// It is supported both the element itself or a selector
var target = getTarget(element);
var newElem;
var theData = [data];
if (data instanceof Array) {
theData = data;
}
// Optimization to avoid trying to find a template when
// only one is needed
var multiTemplate = true;
var template;
var idx = 0;
theData.forEach(function(oneData) {
// Pseudo-field with the index
oneData._idx_ = idx++;
// A suitable template for the data is firstly found
if (multiTemplate === true) {
var tresult = getTemplate(target, oneData);
template = tresult.template;
if (tresult.isMulti === false) {
multiTemplate = false;
}
}
if (template) {
newElem = this.render(template, oneData);
target = targetNode || target;
if (mode === 'A') {
target.appendChild(newElem);
} else if (mode === 'P') { // Append mode
if (target.firstChild) {
target.insertBefore(newElem, element.firstChild);
} else {
target.appendChild(newElem);
}
} // prepend mode
} // if template
}.bind(Templates)); // forEach data
return newElem;
}
/**
* Appends a new instance HTMLElement (or array of) of a template
*
* The template is assumed to be a child of the element passed
* as parameter
* The new element will be appended as a child
*
* @param {HTMLElement or String} ele container element that
* contains the template and which will contain the new instance.
* Can be an HTMLElement or a CSS selector.
*
* @param {object or array} data with the data displayed by the template.
*
* @return {HTMLelement} (or last element if data is an array).
*
*
*/
Templates.append = function(element, data, targetNode) {
var f = add.bind(this);
return f(element, data, 'A', targetNode);
};
/**
* Prepends a new instance (or array of) of a template
*
* The template is assumed to be a child of the element passed
* as parameter
*
* @param {HTMLElement or String} ele container element that
* contains the template and which will contain the new instance.
* Can be an HTMLElement or a CSS selector.
*
* @param {Object or Array} data with the data displayed.
*
* @return {HTMLElement} added.
*
*
*/
Templates.prepend = function(element, data, targetNode) {
var f = add.bind(this);
return f(element, data, 'P', targetNode);
};
/**
* Renders the content specified by a template with object data
*
* @param {HTMLElement} eleTemplate the template itself.
* @param {Object} data the data to be used.
*
* @return {HTMLElement} according to the template and with the data.
*
*
*/
Templates.render = function(eleTemplate, data) {
var newElem = eleTemplate.cloneNode(true);
newElem.removeAttribute('data-template');
newElem.removeAttribute('data-condition');
/* var pattern = /#(\w+)#/g; */
var pattern = /#(\w+[\w.]*)#/g;
var inner = newElem.innerHTML;
// Replace function
var replaceFunction = templateReplace(data);
var ninner = inner.replace(pattern, replaceFunction);
newElem.innerHTML = ninner;
var attrs = newElem.attributes;
var total = attrs.length;
for (var c = 0; c < total; c++) {
var val = attrs[c].value;
var nval = val.replace(pattern, replaceFunction);
newElem.setAttribute(attrs[c].name, nval);
}
if (!newElem.id) {
if (data.id) {
newElem.id = data.id;
}
}
return newElem;
};
/**
* Clears a container element
*
* @param {HTMLElement or String} element (selector or HTML element).
*
*
*/
Templates.clear = function(element) {
var target = getTarget(element);
var templates = target.querySelectorAll('*[data-template]');
target.innerHTML = '';
var total = templates.length;
for (var c = 0; c < total; c++) {
target.appendChild(templates.item(c));
}
};
}) ();
} // window.templates