This repository has been archived by the owner on Nov 3, 2021. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20325 from hobinjk/1019816-logshake
Bug 1019816 - Logshake
- Loading branch information
Showing
5 changed files
with
242 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); | ||
}); |