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:
OverSu 2026-10-13 00:19:45 +02:00
commit 1311bed5e9
170 changed files with 19698 additions and 2495 deletions

View file

@ -38,6 +38,26 @@ var oldValue=this._array[index];this._array[index]=value;this._emitEvent('replac
*
* @module PageTransitions
*/
/**
* PageTransitions provides transition functions used when navigating panels.
*
* @module PageTransitions
*/
/**
* PageTransitions provides transition functions used when navigating panels.
*
* @module PageTransitions
*/
/**
* PageTransitions provides transition functions used when navigating panels.
*
* @module PageTransitions
*/
/**
* PageTransitions provides transition functions used when navigating panels.
*
* @module PageTransitions
*/
define('modules/page_transitions',[],function() {
'use strict';
@ -155,7 +175,6 @@ define('modules/page_transitions',[],function() {
}
};
});
define('modules/panel',[],function(){'use strict';var _emptyFunc=function panel_emptyFunc(){};var Panel=function ctor_panel(options){var _initialized=false;options=options||{};options.onInit=options.onInit||_emptyFunc;options.onUninit=options.onUninit||_emptyFunc;options.onShow=options.onShow||_emptyFunc;options.onHide=options.onHide||_emptyFunc;options.onBeforeShow=options.onBeforeShow||_emptyFunc;options.onBeforeHide=options.onBeforeHide||_emptyFunc;return{get initialized(){return _initialized;},init:function(panel,initOptions){if(_initialized){return;}
_initialized=true;return options.onInit(panel,initOptions);},uninit:function(){if(!_initialized){return;}
_initialized=false;options.onUninit();},show:function(panel,showOptions){return Promise.resolve(this.init(panel,showOptions)).then(function(){return options.onShow(panel,showOptions);});},hide:function(){return options.onHide();},beforeShow:function(panel,beforeShowOptions){return Promise.resolve(this.init(panel,beforeShowOptions)).then(function(){return options.onBeforeShow(panel,beforeShowOptions);});},beforeHide:function(){return options.onBeforeHide();}};};return Panel;});define('modules/settings_cache',[],function(){'use strict';var _settings=window.navigator.mozSettings;var _settingsCache=null;var _settingsCacheRequestSent=null;var _pendingSettingsCallbacks=[];var _callbacks=[];var _onSettingsChange=function sc_onSettingsChange(event){var key=event.settingName;var value=event.settingValue;if(_settingsCache){_settingsCache[key]=value;}
@ -214,23 +233,403 @@ return this._isOnRealDevice;},getCurrentLayout:function sl_getCurrentLayout(type
if(typeof this.queries[type]!=='undefined'){return this.queries[type].matches;}
return false;},watch:function sl_watch(name,media){var mediaString=media||this.queries[name].media;if(!mediaString){return;}
this.unwatch(name);this.queries[name]=window.matchMedia(mediaString);this.boundHandleChange=this.handleChange.bind(this);this.queries[name].addListener(this.boundHandleChange);},unwatch:function sl_unwatch(name){if(this.queries[name]){this.queries[name].removeListener(this.boundHandleChange);}},handleChange:function sl_handleChange(evt){for(var key in this.queries){if(this.queries[key].media!==evt.media){continue;}
window.dispatchEvent(new CustomEvent('screenlayoutchange',{detail:{name:key,status:evt.matches}}));}}};ScreenLayout.init();define("shared/screen_layout",(function(global){return function(){var ret,fn;return ret||global.ScreenLayout;};}(this)));define('modules/settings_service',['require','modules/page_transitions','modules/panel_cache','shared/screen_layout','shared/lazy_loader','settings'],function(require){'use strict';var PageTransitions=require('modules/page_transitions');var PanelCache=require('modules/panel_cache');var ScreenLayout=require('shared/screen_layout');var LazyLoader=require('shared/lazy_loader');var Settings=require('settings');var _rootPanelId=null;var _currentNavigation=null;var _navigating=false;var _pendingNavigationRequest=null;var _cachedNavigation=null;var _cachedNavigationOptions={};var _activityHandler=null;var _loadModulesForSubPanelsPromise=null;var _getAppNameToLink=function ss_get_app_name_to_link(panelId){var reAppName=/app:(\w+)/;var name=reAppName.exec(panelId);return name&&name[1];};var _getAppInfo=function ss_get_app_info(appName){var _supportedAppInFrame={keyboard:{},bluetooth:{}};var appInfo=_supportedAppInFrame[appName];if(!appInfo){return false;}
var prefix='app://'+appName+'.gaiamobile.org/';var defaultSrc=prefix+'settings.html';var appMozapp=prefix+'manifest.webapp';var appSrc=appInfo.src?prefix+appInfo.src:defaultSrc;return{src:appSrc,mozapp:appMozapp};};var _isTabletAndLandscape=function ss_is_tablet_and_landscape(){return ScreenLayout.getCurrentLayout('tabletAndLandscaped');};var _retriveParentPanelId=function ss_retriveParentPanelId(panelId){var headerSelector='#'+panelId+' > gaia-header';var header=document.querySelector(headerSelector);return(header&&header.dataset.href||'').replace('#','');};var _shallCloseActivity=function ss_shallCloseActivity(panelId){if(panelId==='close'){return true;}
if(!_currentNavigation){return false;}
var parentPanelId=_retriveParentPanelId(_currentNavigation.panelId);return _currentNavigation.panelId===_activityHandler.targetPanelId&&panelId===parentPanelId;};var _transit=function ss_transit(oldPanel,newPanel,callback){var promise=new Promise(function(resolve){var wrappedCallback=function(){if(typeof callback==='function'){callback();}
resolve();};if(_isTabletAndLandscape()){PageTransitions.twoColumn(oldPanel,newPanel,wrappedCallback);}else{PageTransitions.oneColumn(oldPanel,newPanel,wrappedCallback);}});return promise;};var _loadPanel=function ss_loadPanel(panelId,callback){var panelElement=document.getElementById(panelId);if(panelElement.dataset.rendered){callback();return;}
panelElement.dataset.rendered=true;if(panelElement.dataset.requireSubPanels){var selector='section[id^="'+panelElement.id+'-"]';var subPanels=document.querySelectorAll(selector);for(var i=0,il=subPanels.length;i<il;i++){LazyLoader.load([subPanels[i]]);}
LazyLoader.load([panelElement],callback);}else{LazyLoader.load([panelElement],callback);}};var _loadModulesForSubPanels=function ss_loadModules(panelId){if(panelId===_rootPanelId){return Promise.resolve();}else{if(!_loadModulesForSubPanelsPromise){_loadModulesForSubPanelsPromise=new Promise(function(resolve){require(['shared/async_storage'],resolve);});}
return _loadModulesForSubPanelsPromise;}};var _onVisibilityChange=function ss_onVisibilityChange(){_handleVisibilityChange(!document.hidden);};var _handleVisibilityChange=function ss_onVisibilityChange(visible){if(!_currentNavigation){return;}
var panel=_currentNavigation.panel;var element=_currentNavigation.panelElement;var options=_currentNavigation.options;if(!panel){return;}
if(visible){panel.beforeShow(element,options);panel.show(element,options);}else{panel.beforeHide();panel.hide();}};var _navigate=function ss_navigate(panelId,options,callback){if(_currentNavigation&&_currentNavigation.panelId===panelId){callback();return;}
_loadPanel(panelId,function(){PanelCache.get(panelId,function(panel){var newPanelElement=document.getElementById(panelId);var currentPanelId=_currentNavigation&&_currentNavigation.panelId;var currentPanelElement=_currentNavigation&&_currentNavigation.panelElement;var currentPanel=_currentNavigation&&_currentNavigation.panel;_cachedNavigation=_currentNavigation;_cachedNavigationOptions=options;options=options||{};_loadModulesForSubPanels(panelId).then(function(){if(currentPanel&&currentPanelId!==_rootPanelId){return currentPanel.beforeHide();}}).then(function(){return panel.beforeShow(newPanelElement,options);}).then(function(){return _transit(currentPanelElement,newPanelElement);}).then(function(){if(currentPanel&&currentPanelId!==_rootPanelId){return currentPanel.hide();}}).then(function(){return panel.show(newPanelElement,options);}).then(function(){_currentNavigation={panelId:panelId,panelElement:newPanelElement,panel:panel,options:options};Settings._currentPanel='#'+panelId;callback();});});});};return{reset:function ss_reset(){_rootPanelId=null;_currentNavigation=null;_cachedNavigation=null;_cachedNavigationOptions={};_activityHandler=null;_navigating=false;_pendingNavigationRequest=null;window.removeEventListener('visibilitychange',_onVisibilityChange);},init:function ss_init(options){if(options){_rootPanelId=options.rootPanelId||'root';_activityHandler=options.context&&options.context.activityHandler;}
window.addEventListener('visibilitychange',_onVisibilityChange);},navigate:function ss_navigate(panelId,options,callback){if(_activityHandler&&_shallCloseActivity(panelId)){_activityHandler.postResult();return;}
if(_navigating){_pendingNavigationRequest=arguments;return;}
var appName=_getAppNameToLink(panelId);if(appName){var appInfo=_getAppInfo(appName);if(!appInfo){console.error('We only embed trust apps.');return;}
panelId='frame';options=options||{};options.mozapp=appInfo.mozapp;options.src=appInfo.src;}
_navigating=true;_navigate(panelId,options,(function(){_navigating=false;if(_pendingNavigationRequest){var args=_pendingNavigationRequest;_pendingNavigationRequest=null;this.navigate.apply(this,args);}
if(callback){callback();}}).bind(this));},back:function ss_back(){if(_activityHandler){_activityHandler.postResult();}else if(_cachedNavigation){this.navigate(_cachedNavigation.panelId,_cachedNavigationOptions);_cachedNavigation=null;_cachedNavigationOptions={};}}};});require(['config/require'],function(){'use strict';define('boot',['require','shared/settings_listener','modules/mvvm/observable','modules/mvvm/observable_array','modules/base/event_emitter','modules/settings_service','shared/screen_layout','settings'],function(require){require('shared/settings_listener');require('modules/mvvm/observable');require('modules/mvvm/observable_array');require('modules/base/event_emitter');var SettingsService=require('modules/settings_service');var ScreenLayout=require('shared/screen_layout');var Settings=require('settings');function isInitialPanel(panel){if(Settings.isTabletAndLandscape()){return panel===Settings.initialPanelForTablet;}else{return panel===('#'+window.LaunchContext.initialPanelId);}}
window.dispatchEvent(new CustomEvent('screenlayoutchange',{detail:{name:key,status:evt.matches}}));}}};ScreenLayout.init();define("shared/screen_layout",(function(global){return function(){var ret,fn;return ret||global.ScreenLayout;};}(this)));/**
* SettingsService is a singleton that provides the navigation service. It
* gets the corresponding panel module from PanelCache and call to its basic
* functions when navigating.
*
* @module SettingsService
*/
/**
* SettingsService is a singleton that provides the navigation service. It
* gets the corresponding panel module from PanelCache and call to its basic
* functions when navigating.
*
* @module SettingsService
*/
/**
* SettingsService is a singleton that provides the navigation service. It
* gets the corresponding panel module from PanelCache and call to its basic
* functions when navigating.
*
* @module SettingsService
*/
/**
* SettingsService is a singleton that provides the navigation service. It
* gets the corresponding panel module from PanelCache and call to its basic
* functions when navigating.
*
* @module SettingsService
*/
define('modules/settings_service',["require", "modules/page_transitions", "modules/panel_cache", "shared/screen_layout", "shared/lazy_loader", "settings"],function(require) {
'use strict';
var PageTransitions = require('modules/page_transitions');
var PanelCache = require('modules/panel_cache');
var ScreenLayout = require('shared/screen_layout');
var LazyLoader = require('shared/lazy_loader');
var Settings = require('settings');
var _rootPanelId = null;
/**
* _currentNavigation caches information of the current panel including id,
* element, module, and options.
*/
var _currentNavigation = null;
var _navigating = false;
var _pendingNavigationRequest = null;
var _cachedNavigation = null;
var _cachedNavigationOptions = {};
var _activityHandler = null;
var _loadModulesForSubPanelsPromise = null;
var _getAppNameToLink = function ss_get_app_name_to_link(panelId) {
var reAppName = /app:(\w+)/;
var name = reAppName.exec(panelId);
return name && name[1];
};
var _getAppInfo = function ss_get_app_info(appName) {
// We can customize the path for specific apps
var _supportedAppInFrame = {
keyboard: {},
bluetooth: {}
};
var appInfo = _supportedAppInFrame[appName];
if (!appInfo) {
return false;
}
var prefix = 'app://' + appName + '.gaiamobile.org/';
var defaultSrc = prefix + 'settings.html';
var appMozapp = prefix + 'manifest.webapp';
var appSrc = appInfo.src ? prefix + appInfo.src : defaultSrc;
return {
src: appSrc,
mozapp: appMozapp
};
};
var _isTabletAndLandscape = function ss_is_tablet_and_landscape() {
return ScreenLayout.getCurrentLayout('tabletAndLandscaped');
};
var _retriveParentPanelId = function ss_retriveParentPanelId(panelId) {
var headerSelector = '#' + panelId + ' > gaia-header';
var header = document.querySelector(headerSelector);
return (header && header.dataset.href || '').replace('#', '');
};
var _shallCloseActivity = function ss_shallCloseActivity(panelId) {
// If we're handling an activity and the 'back' button is hit, close the
// activity if the panel id to be navigated equals the parent panel id.
// This is for the root panel
if (panelId === 'close') {
return true;
}
if (!_currentNavigation) {
return false;
}
// Get the parent panel id of the current panel.
var parentPanelId = _retriveParentPanelId(_currentNavigation.panelId);
// Close the activity if the current panel is the original target panel,
// and the new panel is the parent panel of the current panel.
return _currentNavigation.panelId === _activityHandler.targetPanelId &&
panelId === parentPanelId;
};
var _transit = function ss_transit(oldPanel, newPanel, callback) {
var promise = new Promise(function(resolve) {
var wrappedCallback = function() {
if (typeof callback === 'function') {
callback();
}
resolve();
};
if (_isTabletAndLandscape()) {
PageTransitions.twoColumn(oldPanel, newPanel, wrappedCallback);
} else {
PageTransitions.oneColumn(oldPanel, newPanel, wrappedCallback);
}
});
return promise;
};
var _loadPanel = function ss_loadPanel(panelId, callback) {
var panelElement = document.getElementById(panelId);
if (panelElement.dataset.rendered) { // already initialized
callback();
return;
}
panelElement.dataset.rendered = true;
// XXX remove SubPanel loader once sub panel are modulized
if (panelElement.dataset.requireSubPanels) {
// load the panel and its sub-panels (dependencies)
// (load the main panel last because it contains the scripts)
var selector = 'section[id^="' + panelElement.id + '-"]';
var subPanels = document.querySelectorAll(selector);
for (var i = 0, il = subPanels.length; i < il; i++) {
LazyLoader.load([subPanels[i]]);
}
LazyLoader.load([panelElement], callback);
} else {
LazyLoader.load([panelElement], callback);
}
};
var _loadModulesForSubPanels = function ss_loadModules(panelId) {
if (panelId === _rootPanelId) {
return Promise.resolve();
} else {
if (!_loadModulesForSubPanelsPromise) {
_loadModulesForSubPanelsPromise = new Promise(function(resolve) {
require(['shared/async_storage'], resolve);
});
}
return _loadModulesForSubPanelsPromise;
}
};
var _onVisibilityChange = function ss_onVisibilityChange() {
_handleVisibilityChange(!document.hidden);
};
/**
* When the app becomes invisible, we should call to beforeHide and hide
* functions of the current panel. When the app becomes visible, we should
* call to beforeShow and show functions of the current panel with the
* cached options.
*/
var _handleVisibilityChange = function ss_onVisibilityChange(visible) {
if (!_currentNavigation) {
return;
}
var panel = _currentNavigation.panel;
var element = _currentNavigation.panelElement;
var options = _currentNavigation.options;
if (!panel) {
return;
}
if (visible) {
panel.beforeShow(element, options);
panel.show(element, options);
} else {
panel.beforeHide();
panel.hide();
}
};
var _navigate = function ss_navigate(panelId, options, callback) {
// Early return if the panel to be navigated is the same as the
// current one.
if (_currentNavigation && _currentNavigation.panelId === panelId) {
callback();
return;
}
_loadPanel(panelId, function() {
PanelCache.get(panelId, function(panel) {
var newPanelElement = document.getElementById(panelId);
var currentPanelId =
_currentNavigation && _currentNavigation.panelId;
var currentPanelElement =
_currentNavigation && _currentNavigation.panelElement;
var currentPanel = _currentNavigation && _currentNavigation.panel;
// Keep these to make sure we can use when going back
_cachedNavigation = _currentNavigation;
_cachedNavigationOptions = options;
// Prepare options and calls to the panel object's before
// show function.
options = options || {};
// 0. start the chain
_loadModulesForSubPanels(panelId)
// 1. beforeHide previous panel
.then(function() {
// We don't deactivate the root panel.
if (currentPanel && currentPanelId !== _rootPanelId) {
return currentPanel.beforeHide();
}
})
// 2. beforeShow next panel
.then(function() {
return panel.beforeShow(newPanelElement, options);
})
// 3. do the transition
.then(function() {
return _transit(currentPanelElement, newPanelElement);
})
// 4. hide previous panel
.then(function() {
// We don't deactivate the root panel.
if (currentPanel && currentPanelId !== _rootPanelId) {
return currentPanel.hide();
}
})
// 5. show next panel
.then(function() {
return panel.show(newPanelElement, options);
})
// 6. keep information
.then(function() {
// Update the current navigation object
_currentNavigation = {
panelId: panelId,
panelElement: newPanelElement,
panel: panel,
options: options
};
// XXX we need to remove this line in the future
// to make sure we won't manipulate Settings
// 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.');
}
});
});
});
};
return {
reset: function ss_reset() {
_rootPanelId = null;
_currentNavigation = null;
_cachedNavigation = null;
_cachedNavigationOptions = {};
_activityHandler = null;
_navigating = false;
_pendingNavigationRequest = null;
window.removeEventListener('visibilitychange', _onVisibilityChange);
},
/**
* Init SettingsService.
*
* @alias module:SettingsService#init
* @param {Object} options
* @param {String} options.rootPanelId
* Panel with the specified id is assumed to be be kept on
* on the screen always. We don't call to its hide and
* beforeHide functions.
* @param {Object} options.context
* The launch context specifying the default panel and the
* activity handler if the app is invoked by web
* activities.
* @param {String} options.context.initialPanelId
* @param {ActivityHandler} options.context.activityHandler
*/
init: function ss_init(options) {
if (options) {
_rootPanelId = options.rootPanelId || 'root';
_activityHandler = options.context && options.context.activityHandler;
}
window.addEventListener('visibilitychange', _onVisibilityChange);
},
/**
* Navigate to a panel with options. The navigation transition is
* determined based on the current screen size and orientation.
*
* @alias module:SettingsService#navigate
* @param {String} panelId
* @param {Object} options
* @param {Function} callback
*/
navigate: function ss_navigate(panelId, options, callback) {
// Check if the app is invoked by web activity and shall post result.
if (_activityHandler && _shallCloseActivity(panelId)) {
_activityHandler.postResult();
return;
}
// Cache the navigation request if it is navigating.
if (_navigating) {
_pendingNavigationRequest = arguments;
return;
}
// If we find out the link includes information about app's name,
// it means that we are going to embed the app into our app.
//
// In this way, we have to navigate to `frame` panel and embed it.
var appName = _getAppNameToLink(panelId);
if (appName) {
var appInfo = _getAppInfo(appName);
if (!appInfo) {
console.error('We only embed trust apps.');
return;
}
panelId = 'frame';
options = options || {};
options.mozapp = appInfo.mozapp;
options.src = appInfo.src;
}
_navigating = true;
_navigate(panelId, options, (function() {
_navigating = false;
// Navigate to the pending navigation if any.
if (_pendingNavigationRequest) {
var args = _pendingNavigationRequest;
_pendingNavigationRequest = null;
this.navigate.apply(this, args);
}
if (callback) {
callback();
}
}).bind(this));
},
/**
* Go back to previous panel
*
* @alias module:SettingsService#back
*/
back: function ss_back() {
if (_activityHandler) {
_activityHandler.postResult();
} else if (_cachedNavigation) {
this.navigate(_cachedNavigation.panelId, _cachedNavigationOptions);
_cachedNavigation = null;
_cachedNavigationOptions = {};
}
}
};
});
require(['config/require'],function(){'use strict';define('boot',['require','shared/settings_listener','modules/mvvm/observable','modules/mvvm/observable_array','modules/base/event_emitter','modules/settings_service','shared/screen_layout','settings'],function(require){require('shared/settings_listener');require('modules/mvvm/observable');require('modules/mvvm/observable_array');require('modules/base/event_emitter');var SettingsService=require('modules/settings_service');var ScreenLayout=require('shared/screen_layout');var Settings=require('settings');function isInitialPanel(panel){if(Settings.isTabletAndLandscape()){return panel===Settings.initialPanelForTablet;}else{return panel===('#'+window.LaunchContext.initialPanelId);}}
window.addEventListener('panelready',function onPanelReady(e){if(!isInitialPanel(e.detail.current)){return;}
var initialPanelHandler=window.LaunchContext.initialPanelHandler;if(initialPanelHandler){initialPanelHandler.release();var pendingTargetPanel=initialPanelHandler.pendingTargetPanel;switch(pendingTargetPanel){case'call':var mozMobileConnections=navigator.mozMobileConnections;if(mozMobileConnections&&mozMobileConnections.length>1){pendingTargetPanel='call-iccs';}
SettingsService.navigate(pendingTargetPanel);break;default:if(pendingTargetPanel){SettingsService.navigate(pendingTargetPanel);}