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,307 @@
/* global dump,
ModalDialog,
MozActivity,
Notification,
NotificationHelper,
Service
*/
(function(exports) {
'use strict';
const DEBUG = false;
/**
* This developer system module captures a snapshot of the current device
* logs as displayed by logcat using DeviceStorage to persist the file for
* future access. It communicates with gecko code running in
* b2g/chrome/content/shell.js using a SystemAppProxy custom event based API.
* It requires the preference 'devtools.logshake.enabled' to be enabled
*
* @class LogShake
*/
function LogShake() {
}
function debug(str) {
if (DEBUG) {
dump('LogShake: ' + str + '\n');
}
}
LogShake.prototype = {
/**
* Start existing, observing for capture-logs events caused by Gecko
* LogShake
*/
start: function() {
Service.request('handleSystemMessageNotification', 'logshake', this);
window.addEventListener('volumeup+volumedown', this);
this.startCaptureLogsListener();
},
/**
* Stop the component, removing all listeners if necessary
*/
stop: function() {
Service.request('unhandleSystemMessageNotification', 'logshake', this);
this.stopCaptureLogsListener();
},
startCaptureLogsListener: function() {
debug('starting captureLogs listener');
window.addEventListener('capture-logs-start', this);
window.addEventListener('capture-logs-success', this);
window.addEventListener('capture-logs-error', this);
},
stopCaptureLogsListener: function() {
debug('stopping captureLogs listener');
window.removeEventListener('capture-logs-start', this);
window.removeEventListener('capture-logs-success', this);
window.removeEventListener('capture-logs-error', this);
},
/**
* Handle a capture-logs-start, capture-logs-success or capture-logs-error
* event, dispatching to the appropriate handler
*/
handleEvent: function(event) {
debug('handling event ' + event.type);
switch(event.type) {
case 'volumeup+volumedown':
this.requestSystemLogs();
break;
case 'capture-logs-start':
this.handleCaptureLogsStart(event);
break;
case 'capture-logs-success':
this.handleCaptureLogsSuccess(event);
break;
case 'capture-logs-error':
this.handleCaptureLogsError(event);
break;
}
},
_shakeId: null,
handleCaptureLogsStart: function(event) {
debug('handling capture-logs-start');
this._shakeId = Date.now();
this._notify('logsSaving');
},
requestSystemLogs: function() {
window.dispatchEvent(new CustomEvent('requestSystemLogs'));
},
/**
* Handle an event of type capture-logs-success where event.detail.logPaths
* is an array of absolute paths to the saved log files.
*/
handleCaptureLogsSuccess: function(event) {
debug('handling capture-logs-success');
navigator.vibrate(100);
this._notify('logsSavedHeader', 'logsSavedBody',
this.triggerShareLogs.bind(this, event.detail.logPaths),
event.detail);
this._shakeId = null;
},
handleCaptureLogsError: function(event) {
debug('handling capture logs error');
var error = event ? event.detail.error : '';
var errorMsg = this.formatError(error);
this._notify('logsSaveError', errorMsg,
this.showErrorMessage.bind('logsSaveError', this, error),
event.detail);
this._shakeId = null;
},
getDeviceStorage: function() {
var storageName = 'sdcard';
var storages = navigator.getDeviceStorages(storageName);
for (var i = 0; i < storages.length; i++) {
if (storages[i].storageName === storageName) {
return storages[i];
}
}
return navigator.getDeviceStorage('sdcard');
},
triggerShareLogs: function(logPaths, notif) {
var logFiles = [];
var storage = this.getDeviceStorage();
var requestsRemaining = logPaths.length;
var self = this;
function onSuccess() {
/* jshint validthis: true */
logFiles.push(this.result);
requestsRemaining -= 1;
if (requestsRemaining === 0) {
var logNames = logFiles.map(function(file) {
// For some reason file.name contains the full path despite
// File's documentation explicitly stating the opposite.
var pathComponents = file.name.split('/');
return pathComponents[pathComponents.length - 1];
});
var activity = new MozActivity({
name: 'share',
data: {
type: 'application/vnd.moz-systemlog',
blobs: logFiles,
filenames: logNames
}
});
activity.onsuccess = function() {
if (!notif) {
return;
}
if ('close' in notif) {
notif.close();
} else {
self.closeSystemMessageNotification(notif);
}
};
}
}
// Only report error one time.
var inError = false;
function onError() {
if (!inError) {
inError = true;
/* jshint validthis: true */
self.showErrorMessage('logsShareError', this.error);
}
}
for (var logPath of logPaths) {
var req = storage.get(logPath);
req.onsuccess = onSuccess;
req.onerror = onError;
}
},
ERRNO_TO_MSG: {
0: 'logsGenericError',
30: 'logsSDCardMaybeShared', // errno: filesystem ro-mounted
'NotFoundError': 'logsNotFoundError'
},
formatError: function(error) {
if (typeof error === 'string') {
return error;
}
if (typeof error === 'object') {
if ('operation' in error) {
return { id: 'logsOperationFailed',
args: { operation: error.operation }
};
}
if ('name' in error && error.name === 'NotFoundError') {
return 'logsNotFoundError';
}
}
return '';
},
showErrorMessage: function(title, error, notif) {
if (notif) {
notif.close();
}
// Do nothing for error string
if (typeof error === 'string') {
return;
}
if (typeof error !== 'object') {
console.warn('Unexpected error type: ' + typeof error);
return;
}
var errorKey;
// Small heuristic for some frequent unix error cases
if ('unixErrno' in error) {
errorKey = error.unixErrno;
} else if ('name' in error) {
errorKey = error.name;
}
debug('errorKey: ' + errorKey);
// Gracefully fallback to a generic error messages if we don't know
// this errno code.
if (!this.ERRNO_TO_MSG[errorKey]) {
errorKey = 0;
}
ModalDialog.alert(title, this.ERRNO_TO_MSG[errorKey], { title: 'ok' });
},
_notify: function(titleL10n, bodyL10n, onclick, dataPayload) {
var payload = {
bodyL10n: bodyL10n,
icon: '/style/notifications/images/bug.png',
tag: 'logshake:' + this._shakeId,
data: {
systemMessageTarget: 'logshake',
logshakePayload: dataPayload || undefined
}
};
NotificationHelper.send(titleL10n, payload).then(notification => {
if (onclick) {
notification.onclick = onclick.bind(this, notification);
}
});
},
handleSystemMessageNotification: function(message) {
debug('Received system message: ' + JSON.stringify(message));
if (!('logshakePayload' in message.data)) {
console.warn('Received logshake system message notification without ' +
'payload, silently discarding.');
return;
}
debug('Message payload: ' + message.data.logshakePayload);
var payload = message.data.logshakePayload;
if ('error' in payload) {
this.showErrorMessage('logsSaveError', payload.error);
} else if ('logPaths' in payload) {
this.triggerShareLogs(payload.logPaths, message);
} else {
console.warn('Unidentified payload: ' + JSON.stringify(payload));
}
},
closeSystemMessageNotification: function(msg) {
Notification.get({ tag: msg.tag }).then(notifs => {
notifs.forEach(notif => {
if (notif.tag) {
// Close notification with the matching tag
if (notif.tag === msg.tag) {
notif.close && notif.close();
}
}
});
});
}
};
exports.LogShake = LogShake;
// XXX: See issue described in screenshot.js
exports.logshake = new LogShake();
exports.logshake.start();
})(window);