Release Android Preview 21 and bilingual changelog

Add permission-based reading of Android contacts, SMS and call history. Include shared Gaia fixes and document device validation limits. Pixel image qualification remains pending.
This commit is contained in:
OverSu 2026-10-14 13:13:46 +02:00
commit 200d1fb235
43 changed files with 2271 additions and 65 deletions

View file

@ -265,8 +265,7 @@ var CallLog = {
// If the contacts cache is not valid, we retrieve the contacts information
// directly from the contacts API and update the DOM accordingly.
if (!this._contactCache) {
this.updateListWithContactInfo(null, null, phoneNumbers,
logGroupContainer);
this.updateListWithContactInfo(null, null, logGroupContainer);
}
},

View file

@ -9,10 +9,9 @@ var LayoutDictionaryDownloader = function(imEngineId, dictFilename) {
this._xhr = null;
};
// Remote URLs to download the data. We require the remote server to have
// CORS turned on since we are not asking for SystemXHR permission.
// Dictionaries ship with Vulpes so adding a layout also works offline.
LayoutDictionaryDownloader.prototype.REMOTE_URL =
'https://fxos.cdn.mozilla.net/dictionaries/%imEngineId/%ver/%dictFilename';
'/js/imes/%imEngineId/dictionaries/%dictFilename';
// If the data schema changes in an incompatible way, we must bump the
// version-to-download here.

View file

@ -6,6 +6,12 @@
function labels(){document.getElementById('browse').textContent=text('Ouvrir le catalogue','Open catalogue');document.getElementById('open').textContent=text('Ouvrir','Open');document.getElementById('install').textContent=text('Installer','Install');document.getElementById('cancel').textContent=text('Annuler','Cancel');}
function loadFailed(){status(text('Catalogue inaccessible. Vérifiez la connexion Internet, puis réessayez.','Catalogue unavailable. Check your Internet connection, then try again.'));document.getElementById('open').hidden=true;}
frame.addEventListener('mozbrowsererror',loadFailed);
frame.addEventListener('mozbrowserclose',()=>{
if(busy)return;
status(text('Le catalogue est fermé. Vous pouvez le rouvrir ci-dessous.',
'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');}
function status(value){frame.style.visibility='hidden';message.hidden=false;document.getElementById('status').textContent=value;}
async function prepare(url){

View file

@ -86,7 +86,16 @@ define(function(require) {
var path = pathElement ? pathElement.dataset.path : null;
var panelFuncLoaded = function(panelFunc) {
var panel = panelFunc();
var panel;
try {
panel = panelFunc();
if (!panel || typeof panel.beforeShow !== 'function') {
throw new TypeError('Invalid settings panel: ' + panelId);
}
} catch (error) {
if (callback) callback(null, error);
return;
}
_panelCache[panelId] = panel;
if (callback) {
callback(panel);
@ -97,6 +106,8 @@ define(function(require) {
require([path], function(panelFunc) {
// Create a new panel object for static panels.
panelFuncLoaded(panelFunc ? panelFunc : SettingsPanel);
}, function(error) {
if (callback) callback(null, error);
});
} else {
panelFuncLoaded(SettingsPanel);

View file

@ -187,7 +187,12 @@ define(function(require) {
}
_loadPanel(panelId, function() {
PanelCache.get(panelId, function(panel) {
PanelCache.get(panelId, function(panel, loadError) {
if (loadError || !panel) {
console.error('Settings panel ' + panelId + ':', loadError);
callback(loadError || new Error('Missing settings panel: ' + panelId));
return;
}
var newPanelElement = document.getElementById(panelId);
var currentPanelId =
_currentNavigation && _currentNavigation.panelId;

View file

@ -408,14 +408,13 @@
var appStarter = AppStarter();
if (document.readyState !== 'loading') {
// Deferred Gaia components contain UMD wrappers. Finish loading them before
// Alameda starts, otherwise an anonymous component can become a panel module.
if (document.readyState === 'complete') {
appStarter.start();
} else {
document.addEventListener('readystatechange', function readyStateChange() {
if (document.readyState == 'interactive') {
document.removeEventListener('readystatechange', readyStateChange);
appStarter.start();
}
});
document.addEventListener('DOMContentLoaded', function() {
appStarter.start();
}, { once: true });
}
})();