Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
307 lines
8.8 KiB
JavaScript
Executable file
307 lines
8.8 KiB
JavaScript
Executable file
/* 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);
|