Skip to content
This repository has been archived by the owner on Nov 3, 2021. It is now read-only.

Commit

Permalink
Merge pull request #20325 from hobinjk/1019816-logshake
Browse files Browse the repository at this point in the history
Bug 1019816 - Logshake
  • Loading branch information
mikehenrty committed Aug 13, 2014
2 parents 041babb + d33f959 commit 8a5c9ad
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 0 deletions.
6 changes: 6 additions & 0 deletions apps/settings/elements/developer.html
Expand Up @@ -200,6 +200,12 @@ <h2 data-l10n-id="debug">Debug</h2>
<span data-l10n-id="use-reviewer-certs">Use Marketplace reviewer certs</span>
</label>
</li>
<li>
<label class="pack-checkbox">
<input type="checkbox" name="devtools.logshake">
<span data-l10n-id="shake-to-log">Shake to save system log</span>
</label>
</li>
<li>
<button id="ftuLauncher" data-l10n-id="launch-ftu">Launch First Time Use</button>
</li>
Expand Down
1 change: 1 addition & 0 deletions apps/system/index.html
Expand Up @@ -372,6 +372,7 @@

<!-- Developer Tools -->
<script defer src="js/remote_debugger.js"></script>
<script defer src="js/devtools/logshake.js"></script>
<script defer src="js/devtools/developer_hud.js"></script>
<link rel="stylesheet" type="text/css" href="style/devtools/developer_hud.css">

Expand Down
137 changes: 137 additions & 0 deletions apps/system/js/devtools/logshake.js
@@ -0,0 +1,137 @@
/* global MozActivity, dump */
(function(exports) {
'use strict';
/**
* 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' to be enabled
*
* @class LogShake
*/
function LogShake() {
}

function debug(str) {
dump('LogShake: ' + str + '\n');
}

LogShake.prototype = {
/**
* Start existing, observing for capture-logs events caused by Gecko
* LogShake
*/
start: function() {
this.startCaptureLogsListener();
},

startCaptureLogsListener: function() {
debug('starting captureLogs listener');
window.addEventListener('capture-logs-success', this);
window.addEventListener('capture-logs-error', this);
},

stopCaptureLogsListener: function() {
debug('stopping captureLogs listener');
window.removeEventListener('capture-logs-success', this);
window.removeEventListener('capture-logs-error', this);
},

/**
* Handle a capture-logs-success or capture-logs-error event, dispatching
* to the appropriate handler
*/
handleEvent: function(event) {
debug('handling event ' + event.type);
if (event.type === 'capture-logs-success') {
this.handleCaptureLogsSuccess(event);
} else if (event.type === 'capture-logs-error') {
this.handleCaptureLogsError(event);
}
},

/**
* Handle an event of type capture-logs-success. event.detail.locations is
* an array of absolute paths to the saved log files, and
* event.detail.logFolder is the folder name where the logs are located.
*/
handleCaptureLogsSuccess: function(event) {
debug('handling capture-logs-success');
navigator.vibrate(100);
this._notify('logsSaved', event.detail.logPrefix, function() {
var logFilenames = event.detail.logFilenames;
var logFiles = [];

var storage = navigator.getDeviceStorage('sdcard');

var requestsRemaining = logFilenames.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 that the opposite.
var pathComponents = file.name.split('/');
return pathComponents[pathComponents.length - 1];
});
/* jshint nonew: false */
new MozActivity({
name: 'new',
data: {
type: 'mail',
blobs: logFiles,
filenames: logNames
}
});
}
}

function onError() {
/* jshint validthis: true */
self.handleCaptureLogsError({detail: {error: this.error}});
}

for (var logFilename of logFilenames) {
var req = storage.get(logFilename);
req.onsuccess = onSuccess;
req.onerror = onError;
}
});
},

handleCaptureLogsError: function(event) {
debug('handling capture logs error');
var error = '';
if (event) {
error = event.detail.error;
}
this._notify('logsFailed', error);
},

_notify: function(titleId, body, onclick) {
var title = navigator.mozL10n.get(titleId) || titleId;
var notification = new window.Notification(title, {body: body});
if (onclick) {
notification.onclick = onclick;
}
},

/**
* Stop the component, removing all listeners if necessary
*/
stop: function() {
this.stopCaptureLogsListener();
}
};

exports.LogShake = LogShake;

// XXX: See issue described in screenshot.js
exports.logshake = new LogShake();
exports.logshake.start();
})(window);
4 changes: 4 additions & 0 deletions apps/system/locales/system.en-US.properties
Expand Up @@ -245,6 +245,10 @@ screenshotNoSDCard = No memory card found
screenshotSDCardInUse = Memory card is in use
screenshotSDCardLow = Not enough space on memory card

# logshake
logsSaved = Logs saved to storage
logsFailed = The logs could not be saved

# lock screen
emergency-call-button=Emergency Call
emergency-call=Emergency Call
Expand Down
94 changes: 94 additions & 0 deletions apps/system/test/unit/logshake_test.js
@@ -0,0 +1,94 @@
'use strict';
/* global MockNavigatorGetDeviceStorage, LogShake, MockNotification, MockL10n */

requireApp('system/js/devtools/logshake.js');

require('/test/unit/mock_navigator_get_device_storage.js');
require('/shared/test/unit/mocks/mock_l10n.js');
require('/shared/test/unit/mocks/mock_notification.js');

/**
* Test shake-to-log functionality.
* Borrows heavily from screenshot_test.js
*/
suite('system/LogShake', function() {
var realL10n;
var realNavigatorGetDeviceStorage;
var realNotification;

var logshake;

setup(function() {
// XXX: Use screenshot's hack until system2 rolls around
if (window.logshake) {
window.logshake.stop();
window.logshake = null;
}

realNavigatorGetDeviceStorage = navigator.getDeviceStorage;
navigator.getDeviceStorage = MockNavigatorGetDeviceStorage;

realL10n = navigator.mozL10n;
navigator.mozL10n = MockL10n;

realNotification = window.Notification;
window.Notification = MockNotification;

logshake = new LogShake();
logshake.start();
});

teardown(function() {
logshake.stop();
navigator.getDeviceStorage = realNavigatorGetDeviceStorage;
navigator.mozL10n = realL10n;
window.Notification = realNotification;

// XXX: Do not restore window's logshake, its time is over
});

test('Create notification after capture-logs-success event', function() {
var notificationSpy = this.sinon.spy(window, 'Notification');
var filename = 'logs/2014-06-03-00-00/log.log';
var logPrefix = 'logs/2014-06-03-00-00/';

window.dispatchEvent(new CustomEvent('capture-logs-success',
{ detail: { logFilenames: [filename], logPrefix: logPrefix } }));

// LogShake should dispatch a notification of some kind
assert.isTrue(notificationSpy.calledOnce);
assert.isTrue(notificationSpy.calledWithNew());
assert.equal(notificationSpy.firstCall.args[0],
'logsSaved');
assert.equal(notificationSpy.firstCall.args[1].body,
logPrefix);
/* XXX: Cannot test without firing click event on notification
var mockDeviceStorage = MockNavigatorGetDeviceStorage();
var deviceStorageSpy = this.sinon.spy(navigator, 'getDeviceStorage');
var getSpy = this.sinon.spy(mockDeviceStorage, 'get');
assert.isTrue(deviceStorageSpy.calledOnce,
'Clicking notification should cause getDeviceStorage to be called');
assert.isTrue(getSpy.calledOnce,
'Clicking notification should cause get to be called');
assert.equal(getSpy.firstCall.args[0], filename,
'get should have been called with filename from event');
*/
});

test('Create notification after capture-logs-error event', function() {
var notificationSpy = this.sinon.spy(window, 'Notification');
var errorMessage = 'error error error';

window.dispatchEvent(new CustomEvent('capture-logs-error',
{ detail: { error: errorMessage } }));

// LogShake should dispatch a notification of some kind
assert.isTrue(notificationSpy.calledOnce, 'Notification should be called');
assert.isTrue(notificationSpy.calledWithNew(),
'Notification should be called with new');
assert.equal(notificationSpy.firstCall.args[0],
'logsFailed');
assert.equal(notificationSpy.firstCall.args[1].body,
errorMessage);
});
});

0 comments on commit 8a5c9ad

Please sign in to comment.