Release Vulpes OS 2.7-preview.24

This commit is contained in:
OverSu 2026-11-04 12:52:20 +01:00
commit 97ef99f827
135 changed files with 7717 additions and 2524 deletions

View file

@ -9,6 +9,23 @@ var debug = require('debug')('view:setting-options');
var attach = require('attach');
var View = require('view');
var optionFallbacks = {
'self-timer-off': ['Off', 'Désactivé'],
'self-timer-2-seconds': ['2 seconds', '2 secondes'],
'self-timer-5-seconds': ['5 seconds', '5 secondes'],
'self-timer-10-seconds': ['10 seconds', '10 secondes'],
'grid-off': ['Off', 'Désactivée'],
'grid-on': ['On', 'Activées']
};
function fallbackTitle(key) {
var values = optionFallbacks[key];
if (!values) { return key; }
var locale = (document.documentElement.lang || navigator.language || 'en')
.toLowerCase();
return values[locale.indexOf('fr') === 0 ? 1 : 0];
}
/**
* Exports
*/
@ -67,7 +84,9 @@ module.exports = View.extend({
if (localizable) {
li.setAttribute('data-l10n-id', option.title);
this.l10n.formatValue(option.title).then((title) => {
li.setAttribute('aria-label', title);
var visibleTitle = title || fallbackTitle(option.title);
li.textContent = visibleTitle;
li.setAttribute('aria-label', visibleTitle);
});
} else {
li.textContent = option.title;

View file

@ -9,6 +9,25 @@ var debug = require('debug')('view:setting');
var bind = require('lib/bind');
var View = require('view');
var settingFallbacks = {
'self-timer': ['Self-Timer', 'Minuteur'],
'self-timer-off': ['Off', 'Désactivé'],
'self-timer-2-seconds': ['2 seconds', '2 secondes'],
'self-timer-5-seconds': ['5 seconds', '5 secondes'],
'self-timer-10-seconds': ['10 seconds', '10 secondes'],
grid: ['Grid Lines', 'Quadrillage'],
'grid-off': ['Off', 'Désactivée'],
'grid-on': ['On', 'Activé']
};
function fallbackText(key) {
var values = settingFallbacks[key];
if (!values) { return key; }
var locale = (document.documentElement.lang || navigator.language || 'en')
.toLowerCase();
return values[locale.indexOf('fr') === 0 ? 1 : 0];
}
/**
* Exports
*/
@ -50,6 +69,7 @@ module.exports = View.extend({
}
localizedValue.then(valueString => {
valueString = valueString || fallbackText(data.value);
this.l10n.setAttributes(
this.el,
'setting-option-' + data.title,
@ -62,6 +82,15 @@ module.exports = View.extend({
valueText: data.optionsLocalizable === false ? data.value : '',
});
var title = this.el.querySelector('.setting_title');
if (title && !title.textContent) {
title.textContent = fallbackText(data.title);
}
var value = this.el.querySelector('.setting_value');
if (value && !value.textContent) {
value.textContent = valueString;
}
// Clean up
delete this.template;

View file

@ -142,6 +142,11 @@ var Common = {
},
closeApplication: function() {
if (window.VulpesCompat) {
return window.VulpesCompat.call('apps.close', {}).catch(function(error) {
console.error('Unable to close Usage:', error);
});
}
return setTimeout(function _close() {
debug('Closing.');
window.close();

View file

@ -44,7 +44,13 @@
},
init: function() {
var revisionString = localStorage.getItem(this.revisionName);
var revisionString;
try {
revisionString = localStorage.getItem(this.revisionName);
} catch (e) {
// The shell may deny partitioned localStorage; IndexedDB remains usable.
console.error('Homescreen datastore revision unavailable:', e);
}
if (revisionString) {
this.lastRevision = JSON.parse(revisionString);
}
@ -187,8 +193,12 @@
updateRevision: function() {
this.lastRevision = this.datastore.revisionId;
localStorage.setItem(this.revisionName,
JSON.stringify(this.lastRevision));
try {
localStorage.setItem(this.revisionName,
JSON.stringify(this.lastRevision));
} catch (e) {
console.error('Homescreen datastore revision could not be saved:', e);
}
},
onChange: function(e) {

View file

@ -82,7 +82,15 @@
}
// Restore existing settings
var settingsString = localStorage.getItem('settings');
var settingsString;
try {
settingsString = localStorage.getItem('settings');
} catch (e) {
// Firefox partitions storage when Gaia is hosted in the desktop shell.
// Keep launching the home screen with defaults if that storage is denied.
console.error('Home screen local settings unavailable:', e);
return;
}
if (!settingsString) {
this.firstRun = true;
return;
@ -105,11 +113,15 @@
Settings.prototype = {
save: function() {
localStorage.setItem('settings', JSON.stringify({
version: SETTINGS_VERSION,
small: this.small,
scrollSnapping: this.scrollSnapping
}));
try {
localStorage.setItem('settings', JSON.stringify({
version: SETTINGS_VERSION,
small: this.small,
scrollSnapping: this.scrollSnapping
}));
} catch (e) {
console.error('Home screen settings could not be saved:', e);
}
}
};

View file

@ -12,7 +12,8 @@
'The catalogue is closed. You can reopen it below.'));
document.getElementById('open').hidden=true;
});
function browse(){labels();message.hidden=true;dialog.hidden=true;frame.style.visibility='visible';frame.src='https://vulpes-os.org/marketplace/?mode=app&lang='+(fr()?'fr':'en');}
let catalogueUrl='';
function browse(reload=false){labels();message.hidden=true;dialog.hidden=true;frame.style.visibility='visible';const next='https://vulpes-os.org/marketplace/?mode=app&lang='+(fr()?'fr':'en');if(reload||!catalogueUrl){catalogueUrl=next;frame.src=next;}}
function status(value){frame.style.visibility='hidden';message.hidden=false;document.getElementById('status').textContent=value;}
async function prepare(url){
if(busy)return;const current=++attempt;busy=true;prepared=null;installed=null;document.getElementById('open').hidden=true;
@ -22,7 +23,10 @@
document.getElementById('question').textContent=text('Installer cette application archivée ?','Install this archived application?')+' ('+(prepared.bytes/1048576).toFixed(1)+' Mio)';
document.getElementById('permissions').textContent=text('Elle sera exécutée comme une application web, sans accès aux fonctions système. Certains anciens services peuvent ne plus fonctionner.','It will run as a web application without system access. Some old services may no longer work.');
message.hidden=true;dialog.hidden=false;
}catch(error){if(current!==attempt)return;status(text('Installation impossible : ','Cannot install: ')+error.message);busy=false;}
}catch(error){if(current!==attempt)return;status(error.message==='PRIVILEGED_APP_UNSUPPORTED'?
text('Cette application demande des API privilégiées qui ne sont pas disponibles dans Vulpes OS.',
'This app requires privileged APIs that are not available in Vulpes OS.'):
text('Installation impossible : ','Cannot install: ')+error.message);busy=false;}
}
dialog.addEventListener('submit',async event=>{event.preventDefault();if(!prepared)return;const token=prepared.token;prepared=null;dialog.hidden=true;status(text('Installation…','Installing…'));
try {installed=await VulpesCompat.call('marketplace.install',{token});status(text('Application ajoutée à l’accueil.','Application added to Home.'));document.getElementById('open').hidden=false;}
@ -32,7 +36,7 @@
document.getElementById('cancel').onclick=cancel;
document.getElementById('browse').onclick=cancel;
document.getElementById('open').onclick=()=>installed && VulpesCompat.call('apps.launch',{manifestURL:installed.manifestURL});
addEventListener('languagechange',()=>{language=navigator.language;if(!busy)browse();});
addEventListener('languagechange',()=>{language=navigator.language;if(!busy)browse(true);});
browse();
setInterval(async()=>{if(busy)return;try{const url=await VulpesCompat.call('marketplace.poll');if(url)await prepare(url);}catch(error){console.error(error);}},750);
})();

View file

@ -59,12 +59,12 @@
}
},
"icons": {
"84": "/shared/resources/branding/firefox-browser.png",
"126": "/shared/resources/branding/firefox-browser.png",
"142": "/shared/resources/branding/firefox-browser.png",
"189": "/shared/resources/branding/firefox-browser.png",
"256": "/shared/resources/branding/firefox-browser.png",
"284": "/shared/resources/branding/firefox-browser.png"
"84": "/shared/resources/branding/browser_84.png",
"126": "/shared/resources/branding/browser_126.png",
"142": "/shared/resources/branding/browser_142.png",
"189": "/shared/resources/branding/browser_189.png",
"256": "/shared/resources/branding/browser_256.png",
"284": "/shared/resources/branding/browser_284.png"
},
"chrome": {
"navigation": true

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -290,8 +290,12 @@ define(function(require) {
*/
_loadApps: function pl__loadApps(apps) {
var table = this._permissionTable;
var internalRoles = ['system', 'input', 'homescreen', 'search', 'addon', 'langpack'];
apps.forEach(function(app) {
var manifest = app.manifest ? app.manifest : app.updateManifest;
if (manifest.role && internalRoles.indexOf(manifest.role) !== -1) {
return;
}
if (manifest.type != 'certified') {
this._apps.push(app);
return;

View file

@ -31,6 +31,8 @@ define(function(require) {
}
} else if (time !== Infinity && time === 0) {
l10nAttrs.id = 'battery-' + type + '-complete';
} else {
l10nAttrs.id = 'battery-' + type + '-unavailable';
}
return l10nAttrs;

View file

@ -1356,6 +1356,8 @@ battery-level-percent={{level}}%
battery-current-level=Current Level ({{level}}%)
battery-charging-calculating=Calculating time required…
battery-discharging-calculating=Calculating time remaining…
battery-charging-unavailable=Charge time unavailable
battery-discharging-unavailable=Time remaining unavailable
battery-charging-complete=Fully charged
battery-discharging-complete=Fully depleted
battery-time-hour={[ plural(hours) ]}

View file

@ -475,20 +475,25 @@
}
}
// Set the .icon property before saving for
// backwards compatibility with verticalhome
// Set the .icon property before saving for backwards compatibility with
// verticalhome. A missing favicon must not prevent pinning the site.
var saveSite = function(icon) {
siteObject.icon = icon || '';
BookmarksDatabase.put(siteObject, siteObject.id)
.then(() => {
this.app.debug('pinSite: ' + siteObject.id);
this.systemBanner.show('site-pinned-to-home-screen');
})
.catch((error) => {
console.error('pinSite, Failed to pin site: ' + error);
});
}.bind(this);
IconsHelper.getIcon(siteObject.url, null,
{icons: this.app.favicons}, siteObject).then(icon => {
siteObject.icon = icon;
BookmarksDatabase.put(siteObject, siteObject.id)
.then(() => {
this.app.debug('pinSite: ' + siteObject.id);
this.systemBanner.show('site-pinned-to-home-screen');
})
.catch((error) => {
console.error('pinSite, Failed to pin site: ' + error);
});
});
{icons: this.app.favicons}, siteObject).then(saveSite, function(error) {
this.app.debug('pinSite: using no favicon: ' + error);
saveSite('');
}.bind(this));
};
/**
@ -986,12 +991,14 @@
return;
}
// Enable/disable the bookmark option
BookmarksDatabase.get(this._currentURL).then(function resolve(result) {
// start_url is the bookmark key for installed web apps, while _currentURL
// may be a later page in the app. Check the canonical key first.
var siteUrl = this.getSiteUrl();
BookmarksDatabase.get(siteUrl).then(function resolve(result) {
this.addToHomeButton.hidden = !!result;
}.bind(this),
function reject() {
this.addToHomeButton.hidden = true;
this.addToHomeButton.hidden = false;
}.bind(this));
};

View file

@ -242,76 +242,29 @@
div.dataset.zIndexLevel = 'poweroff-splash';
div.id = 'poweroff-splash';
this.poweroffLogoLoader = new LogoLoader(this._poweroff);
// Vulpes uses Gaia's three-ring shutdown animation without a carrier
// image or video layered over it.
var inner;
for (var i = 1; i <= 3; i++) {
var outer = document.createElement('span');
outer.className = 'poweroff-ring';
outer.id = 'poweroff-ring-' + i;
div.appendChild(outer);
inner = document.createElement('span');
outer.appendChild(inner);
}
this.poweroffLogoLoader.onload = function(elem) {
// Perform customized animation.
div.appendChild(elem);
div.className = 'step1';
if (elem.tagName.toLowerCase() == 'video' && !elem.ended) {
elem.onended = function() {
elem.classList.add('hide');
// XXX workaround of bug 831747
// Unload the video. This releases the video decoding hardware
// so other apps can use it.
elem.removeAttribute('src');
elem.load();
};
elem.play();
} else {
div.addEventListener('animationend', function() {
elem.classList.add('hide');
if (elem.tagName.toLowerCase() == 'video') {
// XXX workaround of bug 831747
// Unload the video. This releases the video decoding hardware
// so other apps can use it.
elem.removeAttribute('src');
elem.load();
}
});
div.className = 'step1';
var nextAnimation = function nextAnimation(e) {
if (e.target == div) {
div.className = 'step2';
}
elem.addEventListener('transitionend', function() {
if (e.target == inner) {
self._actualPowerOff(reboot);
});
document.getElementById('screen').appendChild(div);
};
this.poweroffLogoLoader.onnotfound = function() {
// - Perform OS default animation.
// The overall animation ends when the inner span of the bottom ring
// is animated, so we store it for detecting.
var inner;
for (var i = 1; i <= 3; i++) {
var outer = document.createElement('span');
outer.className = 'poweroff-ring';
outer.id = 'poweroff-ring-' + i;
div.appendChild(outer);
inner = document.createElement('span');
outer.appendChild(inner);
}
div.className = 'step1';
var nextAnimation = function nextAnimation(e) {
// Switch to next class
if (e.target == div) {
div.className = 'step2';
}
if (e.target != inner) {
return;
}
self._actualPowerOff(reboot);
};
div.addEventListener('animationend', nextAnimation);
document.getElementById('screen').appendChild(div);
};
div.addEventListener('animationend', nextAnimation);
document.getElementById('screen').appendChild(div);
},
/**

View file

@ -265,7 +265,8 @@
// until it works. It may fail at the moment the screenchange
// event has been fired, so we may need to try it several times.
var tryLockOrientation = () => {
if (screen.mozLockOrientation('portrait-primary')) {
// Allow the lock screen to follow the physical display orientation.
if (screen.mozLockOrientation('any')) {
if (!this.orientationLockID) {
throw new Error('No orientation ID. This function should only' +
'be invoked as a interval callback');

View file

@ -346,13 +346,26 @@
case this.fullApp:
// XXX: This should be replaced probably by Web Activities
var host = document.location.host;
var domain = host.replace(/(^[\w\d]+\.)?([\w\d]+\.[a-z]+)/, '$2');
var protocol = document.location.protocol + '//';
applications.getByManifestURL(protocol + 'settings.' +
domain + '/manifest.webapp').launch();
UtilityTray.hide();
var settingsURL = new URL(document.location.href);
if (settingsURL.hostname.indexOf('system.') !== 0) {
console.error('Quick settings: cannot resolve Settings app host');
return;
}
settingsURL.hostname = 'settings.' +
settingsURL.hostname.slice('system.'.length);
settingsURL.pathname = '/manifest.webapp';
settingsURL.search = '';
settingsURL.hash = '';
applications.waitForReady().then(function() {
var settingsApp = applications.getByManifestURL(
settingsURL.href);
if (!settingsApp) {
console.error('Quick settings: Settings app is not installed');
return;
}
settingsApp.launch();
UtilityTray.hide();
});
break;
}
break;

View file

@ -187,6 +187,15 @@
},
hasPermission: function wf_hasPermission(app, permission) {
// The embedded Gaia browser is the trusted system web client. Newer
// Gecko builds no longer expose mozPermissionSettings to this code, so
// honour its declared window permission and keep popup navigation in
// Gaia instead of letting Gecko open a desktop browser window.
if (app && app.manifest && app.manifest.role === 'search' &&
permission === 'open-remote-window' && app.manifest.permissions &&
app.manifest.permissions['open-remote-window']) {
return true;
}
var mozPerms = navigator.mozPermissionSettings;
if (!mozPerms) {
return false;

View file

@ -70,7 +70,7 @@
text-overflow: ellipsis;
}
#app-install-dialog p {
font-size: 1.6rem;
font-size: 1.4rem;
font-weight: 400;
line-height: normal;
border-top: none;
@ -79,7 +79,7 @@
margin-top: 0;
}
#app-install-dialog span {
font-size: 1.6rem;
font-size: 1.4rem;
font-weight: 400;
line-height: normal;
color: inherit;

View file

@ -57,6 +57,6 @@
#os-logo { background:#071d35; display:flex; flex-direction:column; align-items:center; justify-content:center; }
#os-logo::before { content:""; width:min(42vw,190px); aspect-ratio:1; flex:none; background:url('/shared/resources/branding/vulpes-symbol.png') center / contain no-repeat; animation:vulpes-boot 2s ease-in-out infinite alternate; }
#os-logo::after { content:"VULPES\A Firefox OS Community Project"; white-space:pre-line; padding:12px 22px; color:#f5f8fa; font:500 20px/1.8 FiraSans,sans-serif; text-align:center; }
#poweroff-splash { background:#071d35 url('/shared/resources/branding/vulpes-symbol.png') center / 190px 190px no-repeat; }
#poweroff-splash { background:#071d35; }
@keyframes vulpes-boot { from {opacity:.75} to {opacity:1} }
@media (prefers-reduced-motion: reduce) { #os-logo::before {animation:none} }

View file

@ -11,7 +11,7 @@ requireApp('system/shared/test/unit/mocks/mock_navigator_moz_settings.js');
requireApp('system/shared/test/unit/mocks/mock_navigator_moz_power.js');
suite('CoverScreen >', function() {
var settingsCore, realSettings, subject, realMozPower;
var settingsCore, realSettings, subject, realMozPower, fakeScreen;
const DEFAULT_VIDEO_ON = '/resources/power/carrier_power_on.mp4';
const DEFAULT_VIDEO_OFF = '/resources/power/carrier_power_off.mp4';
const DEFAULT_IMAGE_ON = '/resources/power/carrier_power_on.png';
@ -113,6 +113,9 @@ suite('CoverScreen >', function() {
var fakeElement = document.createElement('div');
if (id === 'poweroff-splash') {
return null;
} else if (id === 'screen') {
fakeScreen = fakeElement;
return fakeScreen;
} else {
return fakeElement.cloneNode(true);
}
@ -135,10 +138,9 @@ suite('CoverScreen >', function() {
test('restart requested', function() {
var stub = this.sinon.stub(navigator.mozPower, 'reboot');
subject.poweroff(true);
var element = document.createElement('div');
var transitionStub = this.sinon.stub(element, 'addEventListener');
subject.poweroffLogoLoader.onload(element);
transitionStub.getCall(0).args[1]();
fakeScreen.lastChild.dispatchEvent(new Event('animationend'));
fakeScreen.lastChild.querySelector('#poweroff-ring-3 > span')
.dispatchEvent(new Event('animationend', { bubbles: true }));
assert.ok(stub.calledOnce);
});
@ -151,11 +153,9 @@ suite('CoverScreen >', function() {
test('poweroff requested', function() {
var stub = this.sinon.stub(navigator.mozPower, 'powerOff');
subject.poweroff(false);
var element = document.createElement('div');
var transitionStub = this.sinon.stub(element, 'addEventListener');
subject.poweroffLogoLoader.onload(element);
transitionStub.getCall(0).args[1]();
fakeScreen.lastChild.dispatchEvent(new Event('animationend'));
fakeScreen.lastChild.querySelector('#poweroff-ring-3 > span')
.dispatchEvent(new Event('animationend', { bubbles: true }));
assert.ok(stub.calledOnce);
});

View file

@ -253,9 +253,15 @@
}
};
// The Browser artwork changed with Vulpes branding. Don't keep its
// decorated icon from an older homescreen database; let mozApps return
// the manifest icon at the current grid's best available resolution.
var isVulpesBrowser = /^(?:app:\/\/search\.gaiamobile\.org|http:\/\/search\.localhost(?::\d+)?)\/manifest\.webapp$/
.test(entry.manifestURL);
return new GaiaGrid.Mozapp(app, entry.entryPoint, {
// cached decorated icon blob to load faster
decoratedIconBlob: entry.decoratedIconBlob
decoratedIconBlob: isVulpesBrowser ? null : entry.decoratedIconBlob
});
},