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