Release Preview 20 shared desktop, Android and Sargo updates
Restore Gaia settings and media workflows, extend native network adapters, and align the APK payload. Generate French/English changelogs and website entries from the simplified JSON source. Validation: shared JS and Tundra unit tests; desktop workflows and persistence; signed APK payload parity; Sargo RAM and installed startup with boot readback. Android runtime qualification is recorded separately.
This commit is contained in:
parent
be2cdce823
commit
1311bed5e9
170 changed files with 19698 additions and 2495 deletions
|
|
@ -272,7 +272,7 @@ ActivityController.prototype.onActivityConfirmed = function(newMedia) {
|
|||
|
||||
// Video
|
||||
if (newMedia.isVideo) {
|
||||
media.type = 'video/3gpp';
|
||||
media.type = media.blob.type || 'video/3gpp';
|
||||
media.poster = newMedia.poster.blob;
|
||||
activity.postResult(media);
|
||||
this.app.clearSpinner();
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ OverlayController.prototype.updateOverlay = function(type, enabled, reason) {
|
|||
return;
|
||||
}
|
||||
|
||||
var closable = self.activity.pick && type !== 'request-fail';
|
||||
var closable = self.activity.pick || reason === 'request-fail';
|
||||
var view = new OverlayView({
|
||||
type: reason,
|
||||
closable: closable,
|
||||
|
|
@ -118,6 +118,10 @@ OverlayController.prototype.updateOverlay = function(type, enabled, reason) {
|
|||
|
||||
overlay.view = view.appendTo(document.body)
|
||||
.on('click:close-btn', function() {
|
||||
if (reason === 'request-fail' && !self.activity.pick) {
|
||||
self.updateOverlay(type, false);
|
||||
return;
|
||||
}
|
||||
self.app.emit('activitycanceled');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ StorageController.prototype.storeVideo = function(video) {
|
|||
var self = this;
|
||||
|
||||
// Add the poster image to the image storage
|
||||
poster.filepath = video.filepath.replace('.3gp', '.jpg');
|
||||
poster.filepath = video.filepath.replace(/\.(?:3gp|mp4|webm)$/, '.jpg');
|
||||
video.isVideo = true;
|
||||
|
||||
this.storage.addPicture(
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ ViewfinderController.prototype.hideGrid = function() {
|
|||
* @private
|
||||
*/
|
||||
ViewfinderController.prototype.bindEvents = function() {
|
||||
window.addEventListener('resize', this.configurePreview);
|
||||
|
||||
// View
|
||||
this.views.viewfinder.on('fadedin', this.app.firer('viewfinder:visible'));
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ var dcfConfig = {
|
|||
seq: null,
|
||||
postFix: 'MZLLA',
|
||||
prefix: {video: 'VID_', image: 'IMG_'},
|
||||
ext: {video: '3gp', image: 'jpg'}
|
||||
ext: {video: window.VulpesVideoExtension || '3gp', image: 'jpg'}
|
||||
};
|
||||
|
||||
exports.init = function() {
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ Storage.prototype.checkFilepath = function(filepath) {
|
|||
// image and get the video filepath by changing '.jpg' to '.3gp'
|
||||
if (filepath.indexOf('VID') != -1 &&
|
||||
filepath.lastIndexOf('.jpg') === filepath.length - 4) {
|
||||
filepath = filepath.replace('.jpg', '.3gp');
|
||||
filepath = filepath.replace('.jpg', '.' + (window.VulpesVideoExtension || '3gp'));
|
||||
}
|
||||
|
||||
return filepath;
|
||||
|
|
@ -380,7 +380,7 @@ Storage.prototype.deletePicture = function(filepath, done) {
|
|||
* @param {String} filepath
|
||||
*/
|
||||
Storage.prototype.deleteVideo = function(filepath) {
|
||||
var poster = filepath.replace('.3gp', '.jpg');
|
||||
var poster = filepath.replace(/\.(?:3gp|mp4|webm)$/, '.jpg');
|
||||
|
||||
this.video.delete(filepath).onerror = function(e) {
|
||||
console.warn(
|
||||
|
|
|
|||
|
|
@ -205,6 +205,9 @@ module.exports = View.extend({
|
|||
*/
|
||||
updatePreview: function(preview, sensorAngle, mirrored) {
|
||||
if (!preview) { return; }
|
||||
// An embedded app can initialize before its window is visible.
|
||||
this.getSize();
|
||||
if (!this.width || !this.height) { return; }
|
||||
var aspect;
|
||||
|
||||
// Invert dimensions if the camera's `sensorAngle` is
|
||||
|
|
|
|||
|
|
@ -25,6 +25,19 @@ var TelephonyHelper = (function() {
|
|||
return Promise.reject();
|
||||
}
|
||||
|
||||
// The platform adapter owns network checks on Tundra and Android.
|
||||
if (window.VulpesPlatformDial) {
|
||||
return window.VulpesPlatformDial(sanitizedNumber.split(',')[0]).then(function(obj) {
|
||||
if (window.TelephonyCall && obj instanceof TelephonyCall) {
|
||||
installHandlers(obj, sanitizedNumber, false, cardIndex,
|
||||
oncall, onconnected, ondisconnected);
|
||||
} else if (oncall) {
|
||||
oncall();
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
|
||||
var conn = navigator.mozMobileConnections &&
|
||||
navigator.mozMobileConnections[cardIndex];
|
||||
|
||||
|
|
@ -170,11 +183,13 @@ var TelephonyHelper = (function() {
|
|||
call.addEventListener('connected', onconnected);
|
||||
|
||||
call.addEventListener('disconnected', function handleDisconnect(evt) {
|
||||
loadTelephonyMessages(function() {
|
||||
TelephonyMessages.handleDisconnect(
|
||||
evt.call.disconnectedReason, number, emergencyOnly
|
||||
);
|
||||
});
|
||||
if (evt.call.disconnectedReason) {
|
||||
loadTelephonyMessages(function() {
|
||||
TelephonyMessages.handleDisconnect(
|
||||
evt.call.disconnectedReason, number, emergencyOnly
|
||||
);
|
||||
});
|
||||
}
|
||||
ondisconnected && ondisconnected();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -651,25 +651,30 @@ var metadataParser = (function() {
|
|||
|
||||
function videoMetadataParser(file, metadataCallback, errorCallback) {
|
||||
var metadata = {};
|
||||
var videofilename = file.name.replace('.jpg', '.3gp');
|
||||
metadata.video = videofilename;
|
||||
|
||||
var getreq = videostorage.get(videofilename);
|
||||
getreq.onerror = function() {
|
||||
errorCallback('cannot get video file: ' + videofilename);
|
||||
};
|
||||
getreq.onsuccess = function() {
|
||||
var videofile = getreq.result;
|
||||
getVideoRotation(videofile, function(rotation) {
|
||||
if (typeof rotation === 'number') {
|
||||
metadata.rotation = rotation;
|
||||
var extensions = ['webm', '3gp', 'mp4'];
|
||||
function findVideo() {
|
||||
var videofilename = file.name.replace(/\.jpg$/, '.' + extensions.shift());
|
||||
var getreq = videostorage.get(videofilename);
|
||||
getreq.onerror = function() {
|
||||
if (extensions.length) findVideo();
|
||||
else errorCallback('cannot get video file: ' + videofilename);
|
||||
};
|
||||
getreq.onsuccess = function() {
|
||||
metadata.video = videofilename;
|
||||
if (/\.webm$/.test(videofilename)) {
|
||||
metadata.rotation = 0;
|
||||
getVideoThumbnailAndSize();
|
||||
return;
|
||||
}
|
||||
else if (typeof rotation === 'string') {
|
||||
errorCallback('Video rotation:', rotation);
|
||||
}
|
||||
});
|
||||
};
|
||||
getVideoRotation(getreq.result, function(rotation) {
|
||||
if (typeof rotation === 'number') {
|
||||
metadata.rotation = rotation;
|
||||
getVideoThumbnailAndSize();
|
||||
} else errorCallback('Video rotation: ' + rotation);
|
||||
});
|
||||
};
|
||||
}
|
||||
findVideo();
|
||||
|
||||
function getVideoThumbnailAndSize() {
|
||||
var url = URL.createObjectURL(file);
|
||||
|
|
|
|||
|
|
@ -139,10 +139,6 @@ define(function(require) {
|
|||
*/
|
||||
DateTime.prototype._init = function() {
|
||||
this._mozTime = window.navigator.mozTime;
|
||||
if (!this._mozTime) {
|
||||
console.error('Could not get window.navigator.mozTime');
|
||||
return;
|
||||
}
|
||||
|
||||
this._boundSetTimeAutoEnabled = function(event) {
|
||||
this._clockAutoEnabled = event.settingValue;
|
||||
|
|
@ -320,9 +316,7 @@ define(function(require) {
|
|||
switch (type) {
|
||||
case 'date':
|
||||
tokens = value.split('-');
|
||||
newDate.setYear(tokens[0]);
|
||||
newDate.setMonth(tokens[1] - 1);
|
||||
newDate.setDate(tokens[2]);
|
||||
newDate.setFullYear(Number(tokens[0]), Number(tokens[1]) - 1, Number(tokens[2]));
|
||||
break;
|
||||
|
||||
case 'time':
|
||||
|
|
@ -331,7 +325,8 @@ define(function(require) {
|
|||
newDate.setMinutes(parseInt(tokens[1]));
|
||||
break;
|
||||
}
|
||||
this._mozTime.set(newDate);
|
||||
if (!this._mozTime) return;
|
||||
return this._mozTime.set(newDate);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -99,37 +99,35 @@ define(function(require) {
|
|||
_transit: function dm__transit(method, dialog, options) {
|
||||
var promise = new Promise(function(resolve) {
|
||||
var panel = dialog.panel;
|
||||
|
||||
panel.addEventListener('transitionend', function paintWait(evt) {
|
||||
if ((method === 'close' || method === 'open') &&
|
||||
evt.propertyName === 'visibility') {
|
||||
// After transition, we have to `hide` the panel, otherwise
|
||||
// the panel would still exist on the layer and would block
|
||||
// the scrolling event.
|
||||
if (method === 'close') {
|
||||
panel.hidden = true;
|
||||
}
|
||||
panel.removeEventListener('transitionend', paintWait);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
// Before transition, we have to `show` the panel, otherwise
|
||||
// the panel before applying transition class.
|
||||
if (method === 'open') {
|
||||
panel.hidden = false;
|
||||
var completed = false;
|
||||
var fallback;
|
||||
function finish(event) {
|
||||
if (event && (event.target !== panel || event.propertyName !== 'visibility')) return;
|
||||
if (completed) return;
|
||||
completed = true;
|
||||
clearTimeout(fallback);
|
||||
panel.removeEventListener('transitionend', finish);
|
||||
panel.removeEventListener('transitioncancel', finish);
|
||||
if (method === 'close') panel.hidden = true;
|
||||
resolve();
|
||||
}
|
||||
|
||||
// We need to apply class later otherwise Gecko can't apply
|
||||
// this transition and 150ms is an approximate number after doing
|
||||
// several rounds of manual tests.
|
||||
if (method === 'open') panel.hidden = false;
|
||||
setTimeout(function() {
|
||||
if (method === 'open') {
|
||||
// Let's unhide the panel first
|
||||
panel.classList.add('current');
|
||||
} else {
|
||||
panel.classList.remove('current');
|
||||
panel.addEventListener('transitionend', finish);
|
||||
panel.addEventListener('transitioncancel', finish);
|
||||
panel.classList.toggle('current', method === 'open');
|
||||
var style = window.getComputedStyle(panel);
|
||||
function milliseconds(value) {
|
||||
return parseFloat(value) * (value.trim().endsWith('ms') ? 1 : 1000) || 0;
|
||||
}
|
||||
var durations = style.transitionDuration.split(',').map(milliseconds);
|
||||
var delays = style.transitionDelay.split(',').map(milliseconds);
|
||||
var duration = Math.max.apply(Math, durations.map(function(value, index) {
|
||||
return value + delays[index % delays.length];
|
||||
}));
|
||||
// Reduced motion, display changes or interrupted transitions may
|
||||
// omit the visibility event. Always release the dialog queue.
|
||||
fallback = setTimeout(finish, Math.max(0, duration) + 100);
|
||||
}, 150);
|
||||
});
|
||||
return promise;
|
||||
|
|
|
|||
|
|
@ -190,29 +190,30 @@ define(function(require) {
|
|||
if ('#' + panelId === currentPanel) {
|
||||
defer.reject('You are showing the same panel #' + panelId);
|
||||
} else {
|
||||
options.onWrapSubmit = function() {
|
||||
DialogManager.close(dialog, 'submit', options)
|
||||
.then(function(result) {
|
||||
defer.resolve({
|
||||
type: 'submit',
|
||||
value: result
|
||||
});
|
||||
var opening;
|
||||
var closing = false;
|
||||
function close(type) {
|
||||
if (closing) return;
|
||||
closing = true;
|
||||
// A tap during the opening animation must not run hide/show
|
||||
// concurrently, or a queued dialog can inherit stale form values.
|
||||
Promise.resolve(opening).then(function() {
|
||||
return DialogManager.close(dialog, type, options);
|
||||
}).then(function(result) {
|
||||
self._navigating = false;
|
||||
defer.resolve({type: type, value: result});
|
||||
self._execPendingRequest();
|
||||
}, function(error) {
|
||||
// Panel validation rejects to keep the form open. Allow a
|
||||
// correction or cancellation without rejecting the show promise.
|
||||
closing = false;
|
||||
dialogDOM.dispatchEvent(new CustomEvent('dialogvalidationfailed', {
|
||||
detail: error
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
options.onWrapCancel = function() {
|
||||
DialogManager.close(dialog, 'cancel', options)
|
||||
.then(function(result) {
|
||||
defer.resolve({
|
||||
type: 'cancel',
|
||||
value: result
|
||||
});
|
||||
self._navigating = false;
|
||||
self._execPendingRequest();
|
||||
});
|
||||
};
|
||||
}
|
||||
options.onWrapSubmit = function() { close('submit'); };
|
||||
options.onWrapCancel = function() { close('cancel'); };
|
||||
|
||||
switch (options.type) {
|
||||
case 'alert':
|
||||
|
|
@ -229,7 +230,17 @@ define(function(require) {
|
|||
break;
|
||||
}
|
||||
this._navigating = true;
|
||||
DialogManager.open(dialog, options);
|
||||
opening = DialogManager.open(dialog, options);
|
||||
opening.catch(function(error) {
|
||||
// A missing host API must not lock every later Settings dialog.
|
||||
dialogDOM.classList.remove('current');
|
||||
dialogDOM.hidden = true;
|
||||
var overlay = document.querySelector('.settings-dialog-overlay');
|
||||
if (overlay) overlay.hidden = true;
|
||||
self._navigating = false;
|
||||
defer.reject(error);
|
||||
self._execPendingRequest();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -246,6 +246,17 @@ define(function(require) {
|
|||
// directly
|
||||
Settings._currentPanel = '#' + panelId;
|
||||
callback();
|
||||
}).catch(function(error) {
|
||||
console.error('Settings panel ' + panelId + ':', error);
|
||||
newPanelElement.classList.remove('current');
|
||||
if (currentPanelElement) currentPanelElement.classList.add('current');
|
||||
// Release the navigation lock even when a hardware query fails.
|
||||
callback(error);
|
||||
if (window.VulpesGaiaUI) {
|
||||
window.VulpesGaiaUI.notice(document.documentElement.lang.indexOf('fr') === 0 ?
|
||||
'Ce panneau n’a pas pu être chargé. Vous pouvez revenir aux autres paramètres.' :
|
||||
'This panel could not be loaded. You can return to the other settings.');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -103,9 +103,10 @@ define(function(require) {
|
|||
* @access public
|
||||
*/
|
||||
_onAppChoose: function pl__onAppChoose(evt) {
|
||||
if (evt.target.dataset && evt.target.dataset.appIndex) {
|
||||
var target = evt.target.closest('[data-app-index]');
|
||||
if (target && this._listRoot.contains(target)) {
|
||||
SettingsService.navigate('appPermissions-details', {
|
||||
app: this._apps[evt.target.dataset.appIndex],
|
||||
app: this._apps[target.dataset.appIndex],
|
||||
permissionsTable: this._permissionTable
|
||||
});
|
||||
}
|
||||
|
|
@ -262,7 +263,7 @@ define(function(require) {
|
|||
* @return {Bool}
|
||||
*/
|
||||
_isExplicitPerm: function pl_isExplicitPerm(perm) {
|
||||
return mozPerms.isExplicit(perm, this._currentApp.manifestURL,
|
||||
return mozPerms && mozPerms.isExplicit(perm, this._currentApp.manifestURL,
|
||||
this._currentApp.origin, false);
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ define(function(require) {
|
|||
* refer to the JSON File Data.
|
||||
*/
|
||||
this._callSupportInfo = null;
|
||||
var url = 'http://support.mozilla.org/products/firefox-os';
|
||||
var url = 'https://vulpes-os.org/';
|
||||
this._elements.userGuide.addEventListener('click', () => {
|
||||
this._elements.userGuide.blur();
|
||||
var activity = new window.MozActivity({
|
||||
|
|
@ -55,7 +55,10 @@ define(function(require) {
|
|||
.then(function loadSupportInfo(data) {
|
||||
this._supportInfo = data;
|
||||
callback(this._supportInfo);
|
||||
}.bind(this));
|
||||
}.bind(this)).catch(function(error) {
|
||||
console.warn('Support information:', error);
|
||||
callback({onlinesupport: {title: 'Vulpes OS', href: 'https://vulpes-os.org/'}, callsupport: []});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* create support url items
|
||||
|
|
@ -103,7 +106,7 @@ define(function(require) {
|
|||
_renderSupportInfo: function support_renderSupportInfo(result) {
|
||||
var onlineSupportInfo = null;
|
||||
var onlineSupportTitle = result['support.onlinesupport.title'];
|
||||
if (onlineSupportTitle !== '') {
|
||||
if (typeof onlineSupportTitle === 'string' && onlineSupportTitle !== '') {
|
||||
onlineSupportInfo = { title: onlineSupportTitle };
|
||||
onlineSupportInfo.href = result['support.onlinesupport.href'];
|
||||
this._setOnlineSupportInfo(onlineSupportInfo);
|
||||
|
|
@ -114,7 +117,7 @@ define(function(require) {
|
|||
// Check to see if we have a title for the first support number.
|
||||
var callSupport1Title = result['support.callsupport1.title'];
|
||||
// If we have a title we'll go ahead and load the href for it too.
|
||||
if (callSupport1Title !== '') {
|
||||
if (typeof callSupport1Title === 'string' && callSupport1Title !== '') {
|
||||
this._callSupportInfo = [
|
||||
{
|
||||
'title': callSupport1Title,
|
||||
|
|
@ -125,7 +128,7 @@ define(function(require) {
|
|||
// Now check to see if we have a title for the second
|
||||
// support number. If we do, we'll load it's href as well.
|
||||
var callSupport2Title = result['support.callsupport2.title'];
|
||||
if (callSupport2Title !== '') {
|
||||
if (typeof callSupport2Title === 'string' && callSupport2Title !== '') {
|
||||
this._callSupportInfo.push({
|
||||
'title': callSupport2Title,
|
||||
'href': result['support.callsupport2.href']
|
||||
|
|
|
|||
|
|
@ -66,8 +66,11 @@ define(function(require) {
|
|||
},
|
||||
|
||||
_bindEvents: function() {
|
||||
this._elements.passcodeInput.onkeypress =
|
||||
this._onPasscodeInputKeypress.bind(this);
|
||||
this._elements.passcodeInput.type = 'password';
|
||||
this._elements.passcodeInput.inputMode = 'numeric';
|
||||
this._elements.passcodeInput.maxLength = 8;
|
||||
this._elements.passcodeInput.oninput =
|
||||
this._onPasscodeInput.bind(this);
|
||||
this._elements.passcodeContainer.onclick =
|
||||
this._onPasscodeContainerClick.bind(this);
|
||||
},
|
||||
|
|
@ -77,37 +80,14 @@ define(function(require) {
|
|||
this._elements.passcodeInput.focus();
|
||||
},
|
||||
|
||||
_onPasscodeInputKeypress: function(evt) {
|
||||
// key code for key strokes from the keypad are 0 (numbers) and 8
|
||||
// (backspace). Filter out the events that are not from the keypad.
|
||||
var keyCode = evt.keyCode;
|
||||
if (keyCode !== 0 && keyCode !== 8) {
|
||||
return;
|
||||
_onPasscodeInput: function() {
|
||||
// Android IMEs commit text without legacy keypress/charCode events.
|
||||
var value = this._elements.passcodeInput.value.replace(/[^0-9]/g, '').slice(0, 8);
|
||||
this._hideErrorMessage();
|
||||
if (this._elements.panel.dataset.passcodeStatus === 'success') {
|
||||
this._resetPasscodeStatus();
|
||||
}
|
||||
|
||||
evt.preventDefault();
|
||||
|
||||
if (this._passcodeBuffer === '') {
|
||||
this._hideErrorMessage();
|
||||
}
|
||||
|
||||
var code = evt.charCode;
|
||||
if (code !== 0 && (code < 0x30 || code > 0x39)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var key = String.fromCharCode(code);
|
||||
if (evt.charCode === 0) {
|
||||
if (this._passcodeBuffer.length > 0) {
|
||||
this._passcodeBuffer = this._getPasscodeBuffer('all');
|
||||
if (this._elements.panel.dataset.passcodeStatus === 'success') {
|
||||
this._resetPasscodeStatus();
|
||||
}
|
||||
}
|
||||
} else if (this._passcodeBuffer.length < 8) {
|
||||
this._passcodeBuffer += key;
|
||||
}
|
||||
|
||||
this._passcodeBuffer = value;
|
||||
this._updatePassCodeUI();
|
||||
this._processPasscode();
|
||||
},
|
||||
|
|
@ -134,6 +114,7 @@ define(function(require) {
|
|||
},
|
||||
|
||||
_updatePassCodeUI: function() {
|
||||
this._elements.passcodeInput.value = this._passcodeBuffer;
|
||||
for (var i = 0; i < 8; i++) {
|
||||
if (i < this._passcodeBuffer.length) {
|
||||
this._elements.passcodeDigits[i].dataset.dot = true;
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ define(function(require) {
|
|||
*/
|
||||
_updateSimSettingsUI: function scm__updateSimSettingsUI() {
|
||||
var firstCardInfo = this._simcards[0].getInfo();
|
||||
var secondCardInfo = this._simcards[1].getInfo();
|
||||
var secondCardInfo = (this._simcards[1] ? this._simcards[1].getInfo() : {absent: true});
|
||||
var hidden = firstCardInfo.absent && secondCardInfo.absent ||
|
||||
this._isAirplaneMode;
|
||||
|
||||
|
|
@ -322,7 +322,7 @@ define(function(require) {
|
|||
*/
|
||||
_updateSelectOptionsUI: function scm__updateSelectOptionsUI() {
|
||||
var firstCardInfo = this._simcards[0].getInfo();
|
||||
var secondCardInfo = this._simcards[1].getInfo();
|
||||
var secondCardInfo = (this._simcards[1] ? this._simcards[1].getInfo() : {absent: true});
|
||||
|
||||
// two cards all are not absent, we have to update separately
|
||||
if (!firstCardInfo.absent && !secondCardInfo.absent) {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,8 @@ define(function(require) {
|
|||
var info = wifiManager.connectionInformation || {};
|
||||
elements.ip.textContent = info.ipAddress || '';
|
||||
document.l10n.setAttributes(elements.speed,
|
||||
'linkSpeedMbs', { linkSpeed: info.linkSpeed });
|
||||
Number.isFinite(info.linkSpeed) ? 'linkSpeedMbs' : 'unavailable',
|
||||
Number.isFinite(info.linkSpeed) ? { linkSpeed: info.linkSpeed } : {});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -439,7 +439,8 @@
|
|||
*/
|
||||
populate: function(callback) {
|
||||
this.initSources(function(entries) {
|
||||
this.deferredSave(entries, callback);
|
||||
// The first render waits for this write; deferring it waits for that render.
|
||||
this.save(entries, callback);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ var currentLayoutMode;
|
|||
var pendingUpdateTitleText = false;
|
||||
|
||||
// Videos recorded by our own camera have filenames of this form
|
||||
var FROMCAMERA = /DCIM\/\d{3}MZLLA\/VID_\d{4}\.3gp$/;
|
||||
var FROMCAMERA = /DCIM\/\d{3}MZLLA\/VID_\d{4}\.(?:3gp|mp4|webm)$/;
|
||||
|
||||
// We have a single instance of the loading checker because it is used
|
||||
// across functions
|
||||
|
|
@ -664,7 +664,7 @@ function deleteFile(filename) {
|
|||
// If we're deleting a video file recorded by our camera,
|
||||
// we also need to delete the poster image associated with
|
||||
// that video.
|
||||
var postername = filename.replace('.3gp', '.jpg');
|
||||
var postername = filename.replace(/\.(?:3gp|mp4|webm)$/, '.jpg');
|
||||
navigator.getDeviceStorage('pictures'). delete(postername);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,3 +70,6 @@ html[dir="rtl"] .wallpaper {
|
|||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
/* Keep preview tiles aligned when source wallpapers have different ratios. */
|
||||
#wallpapers .wallpaper { aspect-ratio: 2 / 3; object-fit: cover; display: block; }
|
||||
|
|
|
|||
|
|
@ -113,6 +113,10 @@ var DownloadHelper = (function() {
|
|||
*/
|
||||
function getBlob(download) {
|
||||
var req = new Request();
|
||||
if (download.vulpesDownload) {
|
||||
download.getBlob().then(function(blob) { req.done(blob); }, function(error) { req.failed(error); });
|
||||
return req;
|
||||
}
|
||||
var storage = getVolume(download.storageName);
|
||||
var storeAvailableReq = storage.available();
|
||||
|
||||
|
|
@ -394,6 +398,10 @@ var DownloadHelper = (function() {
|
|||
*/
|
||||
function remove(download) {
|
||||
var req = new Request();
|
||||
if (download.vulpesDownload) {
|
||||
navigator.mozDownloadManager.remove(download).then(function() { req.done(download); }, function(error) { req.failed(error); });
|
||||
return req;
|
||||
}
|
||||
var incompleteDownload = download.state !== 'succeeded';
|
||||
// If is not done, use download manager to remove it,
|
||||
// otherwise, deal with the datastore.
|
||||
|
|
|
|||
Loading…
Reference in a new issue